mirror of
https://github.com/oxen-io/session-android.git
synced 2025-10-20 15:10:51 +00:00
Support for Axolotl protocol.
1) Split code into v1 and v2 message paths. 2) Do the Axolotl protocol for v2. 3) Switch all v2 entities to protobuf.
This commit is contained in:
@@ -96,7 +96,6 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredSherlockPr
|
||||
initializeIdentitySelection();
|
||||
initializePlatformSpecificOptions();
|
||||
initializePushMessagingToggle();
|
||||
initializeEditTextSummaries();
|
||||
|
||||
this.findPreference(TextSecurePreferences.CHANGE_PASSPHRASE_PREF)
|
||||
.setOnPreferenceClickListener(new ChangePassphraseClickListener());
|
||||
@@ -210,12 +209,6 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredSherlockPr
|
||||
});
|
||||
}
|
||||
|
||||
private void initializeEditTextSummaries() {
|
||||
initializeEditTextSummary((EditTextPreference)this.findPreference(TextSecurePreferences.MMSC_HOST_PREF));
|
||||
initializeEditTextSummary((EditTextPreference)this.findPreference(TextSecurePreferences.MMSC_PROXY_HOST_PREF));
|
||||
initializeEditTextSummary((EditTextPreference)this.findPreference(TextSecurePreferences.MMSC_PROXY_PORT_PREF));
|
||||
}
|
||||
|
||||
private void initializePushMessagingToggle() {
|
||||
CheckBoxPreference preference = (CheckBoxPreference)this.findPreference(PUSH_MESSAGING_PREF);
|
||||
preference.setChecked(TextSecurePreferences.isPushRegistered(this));
|
||||
|
@@ -51,18 +51,14 @@ import android.widget.Toast;
|
||||
import com.actionbarsherlock.view.Menu;
|
||||
import com.actionbarsherlock.view.MenuInflater;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
|
||||
import org.thoughtcrime.securesms.components.EmojiDrawer;
|
||||
import org.thoughtcrime.securesms.components.EmojiToggle;
|
||||
import org.thoughtcrime.securesms.components.RecipientsPanel;
|
||||
import org.thoughtcrime.securesms.contacts.ContactAccessor;
|
||||
import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData;
|
||||
import org.thoughtcrime.securesms.contacts.ContactAccessor.NumberData;
|
||||
import org.thoughtcrime.securesms.crypto.KeyExchangeInitiator;
|
||||
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
|
||||
import org.whispersystems.textsecure.crypto.InvalidMessageException;
|
||||
import org.whispersystems.textsecure.crypto.KeyUtil;
|
||||
import org.whispersystems.textsecure.crypto.MasterCipher;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.DraftDatabase;
|
||||
import org.thoughtcrime.securesms.database.DraftDatabase.Draft;
|
||||
@@ -89,6 +85,10 @@ import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.thoughtcrime.securesms.util.EncryptedCharacterCalculator;
|
||||
import org.thoughtcrime.securesms.util.MemoryCleaner;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.textsecure.crypto.InvalidMessageException;
|
||||
import org.whispersystems.textsecure.crypto.MasterCipher;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.storage.Session;
|
||||
import org.whispersystems.textsecure.util.Util;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -275,7 +275,6 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
|
||||
case R.id.menu_start_secure_session: handleStartSecureSession(); return true;
|
||||
case R.id.menu_abort_session: handleAbortSecureSession(); return true;
|
||||
case R.id.menu_verify_recipient: handleVerifyRecipient(); return true;
|
||||
case R.id.menu_verify_session: handleVerifySession(); return true;
|
||||
case R.id.menu_group_recipients: handleDisplayGroupRecipients(); return true;
|
||||
case R.id.menu_distribution_broadcast: handleDistributionBroadcastEnabled(item); return true;
|
||||
case R.id.menu_distribution_conversation: handleDistributionConversationEnabled(item); return true;
|
||||
@@ -329,13 +328,6 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
|
||||
startActivity(verifyIdentityIntent);
|
||||
}
|
||||
|
||||
private void handleVerifySession() {
|
||||
Intent verifyKeysIntent = new Intent(this, VerifyKeysActivity.class);
|
||||
verifyKeysIntent.putExtra("recipient", getRecipients().getPrimaryRecipient());
|
||||
verifyKeysIntent.putExtra("master_secret", masterSecret);
|
||||
startActivity(verifyKeysIntent);
|
||||
}
|
||||
|
||||
private void handleStartSecureSession() {
|
||||
if (getRecipients() == null) {
|
||||
Toast.makeText(this, getString(R.string.ConversationActivity_invalid_recipient),
|
||||
@@ -373,7 +365,7 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (isSingleConversation()) {
|
||||
KeyUtil.abortSessionFor(ConversationActivity.this, getRecipients().getPrimaryRecipient());
|
||||
Session.abortSessionFor(ConversationActivity.this, getRecipients().getPrimaryRecipient());
|
||||
initializeSecurity();
|
||||
initializeTitleBar();
|
||||
}
|
||||
@@ -567,11 +559,11 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
|
||||
TypedArray drawables = obtainStyledAttributes(attributes);
|
||||
|
||||
if (isSingleConversation() &&
|
||||
KeyUtil.isSessionFor(this, getRecipients().getPrimaryRecipient()))
|
||||
Session.hasSession(this, masterSecret, getRecipients().getPrimaryRecipient()))
|
||||
{
|
||||
sendButton.setImageDrawable(drawables.getDrawable(1));
|
||||
this.isEncryptedConversation = true;
|
||||
this.isAuthenticatedConversation = KeyUtil.isIdentityKeyFor(this, masterSecret, getRecipients().getPrimaryRecipient());
|
||||
this.isAuthenticatedConversation = Session.hasRemoteIdentityKey(this, masterSecret, getRecipients().getPrimaryRecipient());
|
||||
this.characterCalculator = new EncryptedCharacterCalculator();
|
||||
} else {
|
||||
sendButton.setImageDrawable(drawables.getDrawable(0));
|
||||
|
@@ -31,17 +31,19 @@ import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.DecryptingQueue;
|
||||
import org.thoughtcrime.securesms.sms.SmsTransportDetails;
|
||||
import org.whispersystems.textsecure.crypto.InvalidKeyException;
|
||||
import org.whispersystems.textsecure.crypto.InvalidVersionException;
|
||||
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
|
||||
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessorV2;
|
||||
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.sms.SmsTransportDetails;
|
||||
import org.thoughtcrime.securesms.util.MemoryCleaner;
|
||||
import org.whispersystems.textsecure.crypto.InvalidKeyException;
|
||||
import org.whispersystems.textsecure.crypto.InvalidMessageException;
|
||||
import org.whispersystems.textsecure.crypto.InvalidVersionException;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
|
||||
import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage;
|
||||
import org.whispersystems.textsecure.crypto.protocol.PreKeyWhisperMessage;
|
||||
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -64,9 +66,8 @@ public class ReceiveKeyActivity extends Activity {
|
||||
private long messageId;
|
||||
|
||||
private MasterSecret masterSecret;
|
||||
private PreKeyBundleMessage keyExchangeMessageBundle;
|
||||
private PreKeyWhisperMessage keyExchangeMessageBundle;
|
||||
private KeyExchangeMessage keyExchangeMessage;
|
||||
private KeyExchangeProcessor keyExchangeProcessor;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle state) {
|
||||
@@ -82,6 +83,8 @@ public class ReceiveKeyActivity extends Activity {
|
||||
Log.w("ReceiveKeyActivity", ike);
|
||||
} catch (InvalidVersionException ive) {
|
||||
Log.w("ReceiveKeyActivity", ive);
|
||||
} catch (InvalidMessageException e) {
|
||||
Log.w("ReceiveKeyActivity", e);
|
||||
}
|
||||
initializeListeners();
|
||||
}
|
||||
@@ -122,12 +125,22 @@ public class ReceiveKeyActivity extends Activity {
|
||||
descriptionText.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
}
|
||||
|
||||
private boolean isTrusted(KeyExchangeMessage message, PreKeyBundleMessage messageBundle) {
|
||||
return (message != null && keyExchangeProcessor.isTrusted(message)) ||
|
||||
(messageBundle != null && keyExchangeProcessor.isTrusted(messageBundle));
|
||||
private boolean isTrusted(KeyExchangeMessage message, PreKeyWhisperMessage messageBundle) {
|
||||
if (message != null) {
|
||||
KeyExchangeProcessor processor = KeyExchangeProcessor.createFor(this, masterSecret,
|
||||
recipient, message);
|
||||
return processor.isTrusted(message);
|
||||
} else if (messageBundle != null) {
|
||||
KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(this, masterSecret, recipient);
|
||||
return processor.isTrusted(messageBundle);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void initializeKey() throws InvalidKeyException, InvalidVersionException {
|
||||
private void initializeKey()
|
||||
throws InvalidKeyException, InvalidVersionException, InvalidMessageException
|
||||
{
|
||||
try {
|
||||
String messageBody = getIntent().getStringExtra("body");
|
||||
|
||||
@@ -135,9 +148,9 @@ public class ReceiveKeyActivity extends Activity {
|
||||
SmsTransportDetails transportDetails = new SmsTransportDetails();
|
||||
byte[] body = transportDetails.getDecodedMessage(messageBody.getBytes());
|
||||
|
||||
this.keyExchangeMessageBundle = new PreKeyBundleMessage(body);
|
||||
this.keyExchangeMessageBundle = new PreKeyWhisperMessage(body);
|
||||
} else {
|
||||
this.keyExchangeMessage = new KeyExchangeMessage(messageBody);
|
||||
this.keyExchangeMessage = KeyExchangeMessage.createFor(messageBody);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
@@ -152,7 +165,6 @@ public class ReceiveKeyActivity extends Activity {
|
||||
this.threadId = getIntent().getLongExtra("thread_id", -1);
|
||||
this.messageId = getIntent().getLongExtra("message_id", -1);
|
||||
this.masterSecret = getIntent().getParcelableExtra("master_secret");
|
||||
this.keyExchangeProcessor = new KeyExchangeProcessor(this, masterSecret, recipient);
|
||||
}
|
||||
|
||||
private void initializeListeners() {
|
||||
@@ -177,13 +189,23 @@ public class ReceiveKeyActivity extends Activity {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
if (keyExchangeMessage != null) {
|
||||
keyExchangeProcessor.processKeyExchangeMessage(keyExchangeMessage, threadId);
|
||||
DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this)
|
||||
.markAsProcessedKeyExchange(messageId);
|
||||
try {
|
||||
KeyExchangeProcessor processor = KeyExchangeProcessor.createFor(ReceiveKeyActivity.this, masterSecret, recipient, keyExchangeMessage);
|
||||
processor.processKeyExchangeMessage(keyExchangeMessage, threadId);
|
||||
DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this)
|
||||
.markAsProcessedKeyExchange(messageId);
|
||||
} catch (InvalidMessageException e) {
|
||||
Log.w("ReceiveKeyActivity", e);
|
||||
DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this)
|
||||
.markAsCorruptKeyExchange(messageId);
|
||||
}
|
||||
} else if (keyExchangeMessageBundle != null) {
|
||||
try {
|
||||
keyExchangeProcessor.processKeyExchangeMessage(keyExchangeMessageBundle);
|
||||
CiphertextMessage bundledMessage = keyExchangeMessageBundle.getBundledMessage();
|
||||
KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(ReceiveKeyActivity.this,
|
||||
masterSecret, recipient);
|
||||
processor.processKeyExchangeMessage(keyExchangeMessageBundle);
|
||||
|
||||
CiphertextMessage bundledMessage = keyExchangeMessageBundle.getWhisperMessage();
|
||||
SmsTransportDetails transportDetails = new SmsTransportDetails();
|
||||
String messageBody = new String(transportDetails.getEncodedMessage(bundledMessage.serialize()));
|
||||
|
||||
|
@@ -21,15 +21,14 @@ import android.os.Bundle;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.whispersystems.textsecure.crypto.IdentityKey;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.crypto.ecc.Curve;
|
||||
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
|
||||
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
|
||||
import org.whispersystems.textsecure.storage.SessionRecord;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.MemoryCleaner;
|
||||
import org.whispersystems.textsecure.crypto.IdentityKey;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.crypto.ecc.Curve;
|
||||
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
|
||||
import org.whispersystems.textsecure.storage.Session;
|
||||
|
||||
/**
|
||||
* Activity for verifying identity keys.
|
||||
@@ -68,15 +67,14 @@ public class VerifyIdentityActivity extends KeyScanningActivity {
|
||||
return;
|
||||
}
|
||||
|
||||
localIdentityFingerprint.setText(IdentityKeyUtil.getFingerprint(this, keyType));
|
||||
localIdentityFingerprint.setText(IdentityKeyUtil.getIdentityKey(this, keyType).getFingerprint());
|
||||
}
|
||||
|
||||
private void initializeRemoteIdentityKey() {
|
||||
IdentityKey identityKey = getIntent().getParcelableExtra("remote_identity");
|
||||
|
||||
if (identityKey == null) {
|
||||
SessionRecord sessionRecord = new SessionRecord(this, masterSecret, recipient);
|
||||
identityKey = sessionRecord.getIdentityKey();
|
||||
identityKey = Session.getRemoteIdentityKey(this, masterSecret, recipient);
|
||||
}
|
||||
|
||||
if (identityKey == null) {
|
||||
@@ -97,13 +95,12 @@ public class VerifyIdentityActivity extends KeyScanningActivity {
|
||||
this.recipient = this.getIntent().getParcelableExtra("recipient");
|
||||
this.masterSecret = this.getIntent().getParcelableExtra("master_secret");
|
||||
|
||||
SessionRecord sessionRecord = new SessionRecord(this, masterSecret, recipient);
|
||||
int sessionVersion = sessionRecord.getSessionVersion();
|
||||
int sessionVersion = Session.getSessionVersion(this, masterSecret, recipient);
|
||||
|
||||
if (sessionVersion >= CiphertextMessage.CURVE25519_INTRODUCED_VERSION) {
|
||||
this.keyType = Curve.DJB_TYPE;
|
||||
} else {
|
||||
if (sessionVersion <= CiphertextMessage.LEGACY_VERSION) {
|
||||
this.keyType = Curve.NIST_TYPE;
|
||||
} else {
|
||||
this.keyType = Curve.DJB_TYPE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,8 +118,7 @@ public class VerifyIdentityActivity extends KeyScanningActivity {
|
||||
|
||||
@Override
|
||||
protected void initiateScan() {
|
||||
SessionRecord sessionRecord = new SessionRecord(this, masterSecret, recipient);
|
||||
IdentityKey identityKey = sessionRecord.getIdentityKey();
|
||||
IdentityKey identityKey = Session.getRemoteIdentityKey(this, masterSecret, recipient);
|
||||
|
||||
if (identityKey == null) {
|
||||
Toast.makeText(this, R.string.VerifyIdentityActivity_recipient_has_no_identity_key_exclamation,
|
||||
@@ -144,8 +140,7 @@ public class VerifyIdentityActivity extends KeyScanningActivity {
|
||||
|
||||
@Override
|
||||
protected IdentityKey getIdentityKeyToCompare() {
|
||||
SessionRecord sessionRecord = new SessionRecord(this, masterSecret, recipient);
|
||||
return sessionRecord.getIdentityKey();
|
||||
return Session.getRemoteIdentityKey(this, masterSecret, recipient);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -1,130 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.crypto.SerializableKey;
|
||||
import org.whispersystems.textsecure.storage.SessionRecord;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.whispersystems.textsecure.util.Hex;
|
||||
import org.thoughtcrime.securesms.util.MemoryCleaner;
|
||||
|
||||
/**
|
||||
* Activity for verifying session keys.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
public class VerifyKeysActivity extends KeyScanningActivity {
|
||||
|
||||
private byte[] yourFingerprintBytes;
|
||||
private byte[] theirFingerprintBytes;
|
||||
|
||||
private TextView yourFingerprint;
|
||||
private TextView theirFingerprint;
|
||||
|
||||
private Recipient recipient;
|
||||
private MasterSecret masterSecret;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle state) {
|
||||
super.onCreate(state);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
setContentView(R.layout.verify_keys_activity);
|
||||
|
||||
initializeResources();
|
||||
initializeFingerprints();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
MemoryCleaner.clean(masterSecret);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
private void initializeResources() {
|
||||
this.recipient = (Recipient)this.getIntent().getParcelableExtra("recipient");
|
||||
this.masterSecret = (MasterSecret)this.getIntent().getParcelableExtra("master_secret");
|
||||
this.yourFingerprint = (TextView)findViewById(R.id.you_read);
|
||||
this.theirFingerprint = (TextView)findViewById(R.id.friend_reads);
|
||||
}
|
||||
|
||||
private void initializeFingerprints() {
|
||||
SessionRecord session = new SessionRecord(this, masterSecret, recipient);
|
||||
this.yourFingerprintBytes = session.getLocalFingerprint();
|
||||
this.theirFingerprintBytes = session.getRemoteFingerprint();
|
||||
|
||||
this.yourFingerprint.setText(Hex.toString(yourFingerprintBytes));
|
||||
this.theirFingerprint.setText(Hex.toString(theirFingerprintBytes));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDisplayString() {
|
||||
return getString(R.string.VerifyKeysActivity_get_my_fingerprint_scanned);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getScanString() {
|
||||
return getString(R.string.VerifyKeysActivity_scan_their_fingerprint);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SerializableKey getIdentityKeyToCompare() {
|
||||
return new FingerprintKey(theirFingerprintBytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SerializableKey getIdentityKeyToDisplay() {
|
||||
return new FingerprintKey(yourFingerprintBytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getNotVerifiedMessage() {
|
||||
return getString(R.string.VerifyKeysActivity_warning_the_scanned_key_does_not_match_please_check_the_fingerprint_text_carefully2);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getNotVerifiedTitle() {
|
||||
return getString(R.string.VerifyKeysActivity_not_verified_exclamation);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getVerifiedMessage() {
|
||||
return getString(R.string.VerifyKeysActivity_their_key_is_correct_it_is_also_necessary_to_get_your_fingerprint_scanned_as_well);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getVerifiedTitle() {
|
||||
return getString(R.string.VerifyKeysActivity_verified_exclamation);
|
||||
}
|
||||
|
||||
private class FingerprintKey implements SerializableKey {
|
||||
private final byte[] fingerprint;
|
||||
|
||||
public FingerprintKey(byte[] fingerprint) {
|
||||
this.fingerprint = fingerprint;
|
||||
}
|
||||
|
||||
public byte[] serialize() {
|
||||
return fingerprint;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -40,18 +40,15 @@ import org.thoughtcrime.securesms.service.PushReceiver;
|
||||
import org.thoughtcrime.securesms.service.SendReceiveService;
|
||||
import org.thoughtcrime.securesms.sms.SmsTransportDetails;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.textsecure.crypto.IdentityKeyPair;
|
||||
import org.whispersystems.textsecure.crypto.InvalidKeyException;
|
||||
import org.whispersystems.textsecure.crypto.InvalidMessageException;
|
||||
import org.whispersystems.textsecure.crypto.InvalidVersionException;
|
||||
import org.whispersystems.textsecure.crypto.KeyUtil;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.crypto.MessageCipher;
|
||||
import org.whispersystems.textsecure.crypto.SessionCipher;
|
||||
import org.whispersystems.textsecure.crypto.ecc.Curve;
|
||||
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
|
||||
import org.whispersystems.textsecure.push.IncomingPushMessage;
|
||||
import org.whispersystems.textsecure.storage.Session;
|
||||
import org.whispersystems.textsecure.util.Hex;
|
||||
import org.whispersystems.textsecure.util.Util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.Executor;
|
||||
@@ -188,29 +185,26 @@ public class DecryptingQueue {
|
||||
}
|
||||
|
||||
public void run() {
|
||||
synchronized (SessionCipher.CIPHER_LOCK) {
|
||||
try {
|
||||
Recipients recipients = RecipientFactory.getRecipientsFromString(context, message.getSource(), false);
|
||||
Recipient recipient = recipients.getPrimaryRecipient();
|
||||
try {
|
||||
Recipients recipients = RecipientFactory.getRecipientsFromString(context, message.getSource(), false);
|
||||
Recipient recipient = recipients.getPrimaryRecipient();
|
||||
|
||||
if (!KeyUtil.isSessionFor(context, recipient)) {
|
||||
sendResult(PushReceiver.RESULT_NO_SESSION);
|
||||
return;
|
||||
}
|
||||
|
||||
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, Curve.DJB_TYPE);
|
||||
MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey);
|
||||
byte[] plaintextBody = messageCipher.decrypt(recipient, message.getBody());
|
||||
|
||||
message = message.withBody(plaintextBody);
|
||||
sendResult(PushReceiver.RESULT_OK);
|
||||
} catch (InvalidMessageException e) {
|
||||
Log.w("DecryptionQueue", e);
|
||||
sendResult(PushReceiver.RESULT_DECRYPT_FAILED);
|
||||
} catch (RecipientFormattingException e) {
|
||||
Log.w("DecryptionQueue", e);
|
||||
sendResult(PushReceiver.RESULT_DECRYPT_FAILED);
|
||||
if (!Session.hasSession(context, masterSecret, recipient)) {
|
||||
sendResult(PushReceiver.RESULT_NO_SESSION);
|
||||
return;
|
||||
}
|
||||
|
||||
SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipient);
|
||||
byte[] plaintextBody = sessionCipher.decrypt(message.getBody());
|
||||
|
||||
message = message.withBody(plaintextBody);
|
||||
sendResult(PushReceiver.RESULT_OK);
|
||||
} catch (InvalidMessageException e) {
|
||||
Log.w("DecryptionQueue", e);
|
||||
sendResult(PushReceiver.RESULT_DECRYPT_FAILED);
|
||||
} catch (RecipientFormattingException e) {
|
||||
Log.w("DecryptionQueue", e);
|
||||
sendResult(PushReceiver.RESULT_DECRYPT_FAILED);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,7 +262,7 @@ public class DecryptingQueue {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!KeyUtil.isSessionFor(context, recipient)) {
|
||||
if (!Session.hasSession(context, masterSecret, recipient)) {
|
||||
Log.w("DecryptingQueue", "No such recipient session for MMS...");
|
||||
database.markAsNoSession(messageId, threadId);
|
||||
return;
|
||||
@@ -276,28 +270,24 @@ public class DecryptingQueue {
|
||||
|
||||
byte[] plaintextPduBytes;
|
||||
|
||||
synchronized (SessionCipher.CIPHER_LOCK) {
|
||||
Log.w("DecryptingQueue", "Decrypting: " + Hex.toString(ciphertextPduBytes));
|
||||
TextTransport transportDetails = new TextTransport();
|
||||
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, Curve.DJB_TYPE);
|
||||
MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey);
|
||||
byte[] ciphertext = transportDetails.getDecodedMessage(ciphertextPduBytes);
|
||||
Log.w("DecryptingQueue", "Decrypting: " + Hex.toString(ciphertextPduBytes));
|
||||
TextTransport transportDetails = new TextTransport();
|
||||
SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipient);
|
||||
byte[] decodedCiphertext = transportDetails.getDecodedMessage(ciphertextPduBytes);
|
||||
|
||||
try {
|
||||
plaintextPduBytes = messageCipher.decrypt(recipient, ciphertext);
|
||||
} catch (InvalidMessageException ime) {
|
||||
// XXX - For some reason, Sprint seems to append a single character to the
|
||||
// end of message text segments. I don't know why, so here we just try
|
||||
// truncating the message by one if the MAC fails.
|
||||
if (ciphertextPduBytes.length > 2) {
|
||||
Log.w("DecryptingQueue", "Attempting truncated decrypt...");
|
||||
byte[] truncated = new byte[ciphertextPduBytes.length - 1];
|
||||
System.arraycopy(ciphertextPduBytes, 0, truncated, 0, truncated.length);
|
||||
ciphertext = transportDetails.getDecodedMessage(truncated);
|
||||
plaintextPduBytes = messageCipher.decrypt(recipient, ciphertext);
|
||||
} else {
|
||||
throw ime;
|
||||
}
|
||||
try {
|
||||
plaintextPduBytes = sessionCipher.decrypt(decodedCiphertext);
|
||||
} catch (InvalidMessageException ime) {
|
||||
// XXX - For some reason, Sprint seems to append a single character to the
|
||||
// end of message text segments. I don't know why, so here we just try
|
||||
// truncating the message by one if the MAC fails.
|
||||
if (ciphertextPduBytes.length > 2) {
|
||||
Log.w("DecryptingQueue", "Attempting truncated decrypt...");
|
||||
byte[] truncated = Util.trim(ciphertextPduBytes, ciphertextPduBytes.length - 1);
|
||||
decodedCiphertext = transportDetails.getDecodedMessage(truncated);
|
||||
plaintextPduBytes = sessionCipher.decrypt(decodedCiphertext);
|
||||
} else {
|
||||
throw ime;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,36 +342,33 @@ public class DecryptingQueue {
|
||||
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
String plaintextBody;
|
||||
|
||||
synchronized (SessionCipher.CIPHER_LOCK) {
|
||||
try {
|
||||
Recipients recipients = RecipientFactory.getRecipientsFromString(context, originator, false);
|
||||
Recipient recipient = recipients.getPrimaryRecipient();
|
||||
try {
|
||||
Recipients recipients = RecipientFactory.getRecipientsFromString(context, originator, false);
|
||||
Recipient recipient = recipients.getPrimaryRecipient();
|
||||
|
||||
if (!KeyUtil.isSessionFor(context, recipient)) {
|
||||
database.markAsNoSession(messageId);
|
||||
return;
|
||||
}
|
||||
|
||||
SmsTransportDetails transportDetails = new SmsTransportDetails();
|
||||
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, Curve.DJB_TYPE);
|
||||
MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey);
|
||||
byte[] ciphertext = transportDetails.getDecodedMessage(body.getBytes());
|
||||
byte[] paddedPlaintext = messageCipher.decrypt(recipient, ciphertext);
|
||||
|
||||
plaintextBody = new String(transportDetails.getStrippedPaddingMessageBody(paddedPlaintext));
|
||||
} catch (InvalidMessageException e) {
|
||||
Log.w("DecryptionQueue", e);
|
||||
database.markAsDecryptFailed(messageId);
|
||||
return;
|
||||
} catch (RecipientFormattingException e) {
|
||||
Log.w("DecryptionQueue", e);
|
||||
database.markAsDecryptFailed(messageId);
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
Log.w("DecryptionQueue", e);
|
||||
database.markAsDecryptFailed(messageId);
|
||||
if (!Session.hasSession(context, masterSecret, recipient)) {
|
||||
database.markAsNoSession(messageId);
|
||||
return;
|
||||
}
|
||||
|
||||
SmsTransportDetails transportDetails = new SmsTransportDetails();
|
||||
SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipient);
|
||||
byte[] decodedCiphertext = transportDetails.getDecodedMessage(body.getBytes());
|
||||
byte[] paddedPlaintext = sessionCipher.decrypt(decodedCiphertext);
|
||||
|
||||
plaintextBody = new String(transportDetails.getStrippedPaddingMessageBody(paddedPlaintext));
|
||||
} catch (InvalidMessageException e) {
|
||||
Log.w("DecryptionQueue", e);
|
||||
database.markAsDecryptFailed(messageId);
|
||||
return;
|
||||
} catch (RecipientFormattingException e) {
|
||||
Log.w("DecryptionQueue", e);
|
||||
database.markAsDecryptFailed(messageId);
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
Log.w("DecryptionQueue", e);
|
||||
database.markAsDecryptFailed(messageId);
|
||||
return;
|
||||
}
|
||||
|
||||
database.updateMessageBody(masterSecret, messageId, plaintextBody);
|
||||
@@ -414,22 +401,25 @@ public class DecryptingQueue {
|
||||
private void handleKeyExchangeProcessing(String plaintxtBody) {
|
||||
if (TextSecurePreferences.isAutoRespondKeyExchangeEnabled(context)) {
|
||||
try {
|
||||
Recipient recipient = new Recipient(null, originator, null, null);
|
||||
KeyExchangeMessage keyExchangeMessage = new KeyExchangeMessage(plaintxtBody);
|
||||
KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipient);
|
||||
Recipient recipient = new Recipient(null, originator, null, null);
|
||||
KeyExchangeMessage message = KeyExchangeMessage.createFor(plaintxtBody);
|
||||
KeyExchangeProcessor processor = KeyExchangeProcessor.createFor(context, masterSecret, recipient, message);
|
||||
|
||||
Log.w("DecryptingQuue", "KeyExchange with fingerprint: " + keyExchangeMessage.getPublicKey().getFingerprint());
|
||||
|
||||
if (processor.isStale(keyExchangeMessage)) {
|
||||
if (processor.isStale(message)) {
|
||||
DatabaseFactory.getEncryptingSmsDatabase(context).markAsStaleKeyExchange(messageId);
|
||||
} else if (processor.isTrusted(keyExchangeMessage)) {
|
||||
} else if (processor.isTrusted(message)) {
|
||||
DatabaseFactory.getEncryptingSmsDatabase(context).markAsProcessedKeyExchange(messageId);
|
||||
processor.processKeyExchangeMessage(keyExchangeMessage, threadId);
|
||||
processor.processKeyExchangeMessage(message, threadId);
|
||||
}
|
||||
} catch (InvalidVersionException e) {
|
||||
Log.w("DecryptingQueue", e);
|
||||
DatabaseFactory.getEncryptingSmsDatabase(context).markAsInvalidVersionKeyExchange(messageId);
|
||||
} catch (InvalidKeyException e) {
|
||||
Log.w("DecryptingQueue", e);
|
||||
DatabaseFactory.getEncryptingSmsDatabase(context).markAsCorruptKeyExchange(messageId);
|
||||
} catch (InvalidMessageException e) {
|
||||
Log.w("DecryptingQueue", e);
|
||||
DatabaseFactory.getEncryptingSmsDatabase(context).markAsCorruptKeyExchange(messageId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -175,7 +175,7 @@ public class IdentityKeyUtil {
|
||||
byte[] messageBytes = new byte[1 + PublicKey.KEY_SIZE];
|
||||
System.arraycopy(keyExchangeBytes, 0, messageBytes, 0, messageBytes.length);
|
||||
|
||||
byte[] publicKeyBytes = new byte[IdentityKey.SIZE];
|
||||
byte[] publicKeyBytes = new byte[IdentityKey.NIST_SIZE];
|
||||
System.arraycopy(keyExchangeBytes, messageBytes.length, publicKeyBytes, 0, publicKeyBytes.length);
|
||||
|
||||
int signatureLength = Conversions.byteArrayToShort(keyExchangeBytes, messageBytes.length + publicKeyBytes.length);
|
||||
|
@@ -20,17 +20,21 @@ package org.thoughtcrime.securesms.crypto;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
|
||||
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessageV2;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||
import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage;
|
||||
import org.whispersystems.textsecure.crypto.KeyUtil;
|
||||
import org.whispersystems.textsecure.crypto.IdentityKeyPair;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.crypto.ecc.Curve;
|
||||
import org.whispersystems.textsecure.crypto.ecc.ECKeyPair;
|
||||
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
|
||||
import org.whispersystems.textsecure.storage.LocalKeyRecord;
|
||||
import org.whispersystems.textsecure.storage.SessionRecordV2;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public class KeyExchangeInitiator {
|
||||
|
||||
@@ -54,18 +58,42 @@ public class KeyExchangeInitiator {
|
||||
}
|
||||
|
||||
private static void initiateKeyExchange(Context context, MasterSecret masterSecret, Recipient recipient) {
|
||||
LocalKeyRecord record = KeyUtil.initializeRecordFor(context, masterSecret, recipient, CiphertextMessage.CURVE25519_INTRODUCED_VERSION);
|
||||
KeyExchangeMessage message = new KeyExchangeMessage(context, masterSecret, CiphertextMessage.CURVE25519_INTRODUCED_VERSION, record, 0);
|
||||
int sequence = getRandomSequence();
|
||||
int flags = KeyExchangeMessageV2.INITIATE_FLAG;
|
||||
ECKeyPair baseKey = Curve.generateKeyPairForSession(CiphertextMessage.CURRENT_VERSION);
|
||||
ECKeyPair ephemeralKey = Curve.generateKeyPairForSession(CiphertextMessage.CURRENT_VERSION);
|
||||
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, Curve.DJB_TYPE);
|
||||
|
||||
KeyExchangeMessageV2 message = new KeyExchangeMessageV2(sequence, flags,
|
||||
baseKey.getPublicKey(),
|
||||
ephemeralKey.getPublicKey(),
|
||||
identityKey.getPublicKey());
|
||||
|
||||
OutgoingKeyExchangeMessage textMessage = new OutgoingKeyExchangeMessage(recipient, message.serialize());
|
||||
|
||||
Log.w("SendKeyActivity", "Sending public key: " + record.getCurrentKeyPair().getPublicKey().getFingerprint());
|
||||
SessionRecordV2 sessionRecordV2 = new SessionRecordV2(context, masterSecret, recipient);
|
||||
sessionRecordV2.setPendingKeyExchange(sequence, baseKey, ephemeralKey, identityKey);
|
||||
sessionRecordV2.save();
|
||||
|
||||
MessageSender.send(context, masterSecret, textMessage, -1);
|
||||
}
|
||||
|
||||
private static boolean hasInitiatedSession(Context context, MasterSecret masterSecret, Recipient recipient) {
|
||||
private static boolean hasInitiatedSession(Context context, MasterSecret masterSecret,
|
||||
Recipient recipient)
|
||||
{
|
||||
return
|
||||
LocalKeyRecord.hasRecord(context, recipient) &&
|
||||
new LocalKeyRecord(context, masterSecret, recipient).getCurrentKeyPair() != null;
|
||||
new SessionRecordV2(context, masterSecret, recipient)
|
||||
.hasPendingKeyExchange();
|
||||
}
|
||||
|
||||
private static int getRandomSequence() {
|
||||
try {
|
||||
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
|
||||
int candidate = Math.abs(random.nextInt());
|
||||
|
||||
return candidate % 65535;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -18,200 +18,25 @@
|
||||
package org.thoughtcrime.securesms.crypto;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||
import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage;
|
||||
import org.whispersystems.textsecure.crypto.IdentityKey;
|
||||
import org.whispersystems.textsecure.crypto.InvalidKeyException;
|
||||
import org.whispersystems.textsecure.crypto.KeyPair;
|
||||
import org.whispersystems.textsecure.crypto.KeyUtil;
|
||||
import org.whispersystems.textsecure.crypto.InvalidMessageException;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.crypto.PublicKey;
|
||||
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
|
||||
import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage;
|
||||
import org.whispersystems.textsecure.push.PreKeyEntity;
|
||||
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
|
||||
import org.whispersystems.textsecure.storage.LocalKeyRecord;
|
||||
import org.whispersystems.textsecure.storage.PreKeyRecord;
|
||||
import org.whispersystems.textsecure.storage.RemoteKeyRecord;
|
||||
import org.whispersystems.textsecure.storage.SessionRecord;
|
||||
import org.whispersystems.textsecure.util.Conversions;
|
||||
import org.whispersystems.textsecure.util.Medium;
|
||||
|
||||
/**
|
||||
* This class processes key exchange interactions.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
|
||||
public class KeyExchangeProcessor {
|
||||
public abstract class KeyExchangeProcessor {
|
||||
|
||||
public static final String SECURITY_UPDATE_EVENT = "org.thoughtcrime.securesms.KEY_EXCHANGE_UPDATE";
|
||||
|
||||
private Context context;
|
||||
private Recipient recipient;
|
||||
private MasterSecret masterSecret;
|
||||
private LocalKeyRecord localKeyRecord;
|
||||
private RemoteKeyRecord remoteKeyRecord;
|
||||
private SessionRecord sessionRecord;
|
||||
public abstract boolean isStale(KeyExchangeMessage message);
|
||||
public abstract boolean isTrusted(KeyExchangeMessage message);
|
||||
public abstract void processKeyExchangeMessage(KeyExchangeMessage message, long threadid)
|
||||
throws InvalidMessageException;
|
||||
|
||||
public KeyExchangeProcessor(Context context, MasterSecret masterSecret, Recipient recipient) {
|
||||
this.context = context;
|
||||
this.recipient = recipient;
|
||||
this.masterSecret = masterSecret;
|
||||
|
||||
this.remoteKeyRecord = new RemoteKeyRecord(context, recipient);
|
||||
this.localKeyRecord = new LocalKeyRecord(context, masterSecret, recipient);
|
||||
this.sessionRecord = new SessionRecord(context, masterSecret, recipient);
|
||||
}
|
||||
|
||||
public boolean isTrusted(KeyExchangeMessage message) {
|
||||
return message.hasIdentityKey() && isTrusted(message.getIdentityKey());
|
||||
}
|
||||
|
||||
public boolean isTrusted(PreKeyBundleMessage message) {
|
||||
return isTrusted(message.getIdentityKey());
|
||||
}
|
||||
|
||||
public boolean isTrusted(IdentityKey identityKey) {
|
||||
return DatabaseFactory.getIdentityDatabase(context).isValidIdentity(masterSecret, recipient,
|
||||
identityKey);
|
||||
}
|
||||
|
||||
public boolean hasInitiatedSession() {
|
||||
return localKeyRecord.getCurrentKeyPair() != null;
|
||||
}
|
||||
|
||||
private boolean needsResponseFromUs() {
|
||||
return !hasInitiatedSession() || remoteKeyRecord.getCurrentRemoteKey() != null;
|
||||
}
|
||||
|
||||
public boolean isStale(KeyExchangeMessage message) {
|
||||
int responseKeyId = Conversions.highBitsToMedium(message.getPublicKey().getId());
|
||||
|
||||
Log.w("KeyExchangeProcessor", "Key Exchange High ID Bits: " + responseKeyId);
|
||||
|
||||
return responseKeyId != 0 &&
|
||||
(localKeyRecord.getCurrentKeyPair() != null && localKeyRecord.getCurrentKeyPair().getId() != responseKeyId);
|
||||
}
|
||||
|
||||
public void processKeyExchangeMessage(PreKeyBundleMessage message)
|
||||
throws InvalidKeyIdException, InvalidKeyException
|
||||
public static KeyExchangeProcessor createFor(Context context, MasterSecret masterSecret,
|
||||
Recipient recipient, KeyExchangeMessage message)
|
||||
{
|
||||
int preKeyId = message.getPreKeyId();
|
||||
PublicKey remoteKey = message.getPublicKey();
|
||||
IdentityKey remoteIdentity = message.getIdentityKey();
|
||||
|
||||
Log.w("KeyExchangeProcessor", "Received pre-key with remote key ID: " + remoteKey.getId());
|
||||
Log.w("KeyExchangeProcessor", "Received pre-key with local key ID: " + preKeyId);
|
||||
|
||||
if (!PreKeyRecord.hasRecord(context, preKeyId) && KeyUtil.isSessionFor(context, recipient)) {
|
||||
Log.w("KeyExchangeProcessor", "We've already processed the prekey part, letting bundled message fall through...");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!PreKeyRecord.hasRecord(context, preKeyId))
|
||||
throw new InvalidKeyIdException("No such prekey: " + preKeyId);
|
||||
|
||||
PreKeyRecord preKeyRecord = new PreKeyRecord(context, masterSecret, preKeyId);
|
||||
KeyPair preKeyPair = new KeyPair(preKeyId, preKeyRecord.getKeyPair().getKeyPair(), masterSecret);
|
||||
|
||||
localKeyRecord.setCurrentKeyPair(preKeyPair);
|
||||
localKeyRecord.setNextKeyPair(preKeyPair);
|
||||
|
||||
remoteKeyRecord.setCurrentRemoteKey(remoteKey);
|
||||
remoteKeyRecord.setLastRemoteKey(remoteKey);
|
||||
|
||||
sessionRecord.setSessionId(localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprintBytes(),
|
||||
remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes());
|
||||
sessionRecord.setIdentityKey(remoteIdentity);
|
||||
sessionRecord.setSessionVersion(Math.min(message.getSupportedVersion(), PreKeyBundleMessage.SUPPORTED_VERSION));
|
||||
sessionRecord.setNegotiatedSessionVersion(sessionRecord.getSessionVersion());
|
||||
|
||||
localKeyRecord.save();
|
||||
remoteKeyRecord.save();
|
||||
sessionRecord.save();
|
||||
|
||||
if (preKeyId != Medium.MAX_VALUE) {
|
||||
PreKeyRecord.delete(context, preKeyId);
|
||||
}
|
||||
|
||||
DatabaseFactory.getIdentityDatabase(context)
|
||||
.saveIdentity(masterSecret, recipient, remoteIdentity);
|
||||
if (message.isLegacy()) return new KeyExchangeProcessorV1(context, masterSecret, recipient);
|
||||
else return new KeyExchangeProcessorV2(context, masterSecret, recipient);
|
||||
}
|
||||
|
||||
public void processKeyExchangeMessage(PreKeyEntity message, long threadId) {
|
||||
PublicKey remoteKey = new PublicKey(message.getKeyId(), message.getPublicKey());
|
||||
remoteKeyRecord.setCurrentRemoteKey(remoteKey);
|
||||
remoteKeyRecord.setLastRemoteKey(remoteKey);
|
||||
remoteKeyRecord.save();
|
||||
|
||||
localKeyRecord = KeyUtil.initializeRecordFor(context, masterSecret, recipient, CiphertextMessage.SUPPORTED_VERSION);
|
||||
localKeyRecord.setNextKeyPair(localKeyRecord.getCurrentKeyPair());
|
||||
localKeyRecord.save();
|
||||
|
||||
sessionRecord.setSessionId(localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprintBytes(),
|
||||
remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes());
|
||||
sessionRecord.setIdentityKey(message.getIdentityKey());
|
||||
sessionRecord.setNegotiatedSessionVersion(CiphertextMessage.SUPPORTED_VERSION);
|
||||
sessionRecord.setSessionVersion(CiphertextMessage.SUPPORTED_VERSION);
|
||||
sessionRecord.setPrekeyBundleRequired(true);
|
||||
sessionRecord.save();
|
||||
|
||||
DatabaseFactory.getIdentityDatabase(context)
|
||||
.saveIdentity(masterSecret, recipient, message.getIdentityKey());
|
||||
|
||||
broadcastSecurityUpdateEvent(context, threadId);
|
||||
}
|
||||
|
||||
public void processKeyExchangeMessage(KeyExchangeMessage message, long threadId) {
|
||||
int initiateKeyId = Conversions.lowBitsToMedium(message.getPublicKey().getId());
|
||||
message.getPublicKey().setId(initiateKeyId);
|
||||
|
||||
if (needsResponseFromUs()) {
|
||||
localKeyRecord = KeyUtil.initializeRecordFor(context, masterSecret, recipient, message.getMessageVersion());
|
||||
KeyExchangeMessage ourMessage = new KeyExchangeMessage(context, masterSecret, Math.min(CiphertextMessage.SUPPORTED_VERSION, message.getMaxVersion()), localKeyRecord, initiateKeyId);
|
||||
OutgoingKeyExchangeMessage textMessage = new OutgoingKeyExchangeMessage(recipient, ourMessage.serialize());
|
||||
Log.w("KeyExchangeProcessor", "Responding with key exchange message fingerprint: " + ourMessage.getPublicKey().getFingerprint());
|
||||
Log.w("KeyExchangeProcessor", "Which has a local key record fingerprint: " + localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprint());
|
||||
MessageSender.send(context, masterSecret, textMessage, threadId);
|
||||
}
|
||||
|
||||
remoteKeyRecord.setCurrentRemoteKey(message.getPublicKey());
|
||||
remoteKeyRecord.setLastRemoteKey(message.getPublicKey());
|
||||
remoteKeyRecord.save();
|
||||
|
||||
sessionRecord.setSessionId(localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprintBytes(),
|
||||
remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes());
|
||||
sessionRecord.setIdentityKey(message.getIdentityKey());
|
||||
sessionRecord.setSessionVersion(Math.min(CiphertextMessage.SUPPORTED_VERSION, message.getMaxVersion()));
|
||||
sessionRecord.setNegotiatedSessionVersion(sessionRecord.getSessionVersion());
|
||||
|
||||
Log.w("KeyExchangeUtil", "Setting session version: " + Math.min(CiphertextMessage.SUPPORTED_VERSION, message.getMaxVersion()));
|
||||
|
||||
sessionRecord.save();
|
||||
|
||||
if (message.hasIdentityKey()) {
|
||||
DatabaseFactory.getIdentityDatabase(context)
|
||||
.saveIdentity(masterSecret, recipient, message.getIdentityKey());
|
||||
}
|
||||
|
||||
DecryptingQueue.scheduleRogueMessages(context, masterSecret, recipient);
|
||||
|
||||
broadcastSecurityUpdateEvent(context, threadId);
|
||||
}
|
||||
|
||||
private static void broadcastSecurityUpdateEvent(Context context, long threadId) {
|
||||
Intent intent = new Intent(SECURITY_UPDATE_EVENT);
|
||||
intent.putExtra("thread_id", threadId);
|
||||
intent.setPackage(context.getPackageName());
|
||||
context.sendBroadcast(intent, KeyCachingService.KEY_PERMISSION);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,154 @@
|
||||
package org.thoughtcrime.securesms.crypto;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
|
||||
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessageV1;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||
import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage;
|
||||
import org.whispersystems.textsecure.crypto.IdentityKey;
|
||||
import org.whispersystems.textsecure.crypto.KeyPair;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.crypto.ecc.Curve;
|
||||
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
|
||||
import org.whispersystems.textsecure.storage.LocalKeyRecord;
|
||||
import org.whispersystems.textsecure.storage.RemoteKeyRecord;
|
||||
import org.whispersystems.textsecure.storage.SessionRecordV1;
|
||||
import org.whispersystems.textsecure.util.Conversions;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
/**
|
||||
* This class processes key exchange interactions.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
|
||||
public class KeyExchangeProcessorV1 extends KeyExchangeProcessor {
|
||||
|
||||
private Context context;
|
||||
private Recipient recipient;
|
||||
private MasterSecret masterSecret;
|
||||
private LocalKeyRecord localKeyRecord;
|
||||
private RemoteKeyRecord remoteKeyRecord;
|
||||
private SessionRecordV1 sessionRecord;
|
||||
|
||||
public KeyExchangeProcessorV1(Context context, MasterSecret masterSecret, Recipient recipient) {
|
||||
this.context = context;
|
||||
this.recipient = recipient;
|
||||
this.masterSecret = masterSecret;
|
||||
|
||||
this.remoteKeyRecord = new RemoteKeyRecord(context, recipient);
|
||||
this.localKeyRecord = new LocalKeyRecord(context, masterSecret, recipient);
|
||||
this.sessionRecord = new SessionRecordV1(context, masterSecret, recipient);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTrusted(KeyExchangeMessage message) {
|
||||
return message.hasIdentityKey() && isTrusted(message.getIdentityKey());
|
||||
}
|
||||
|
||||
public boolean isTrusted(IdentityKey identityKey) {
|
||||
return DatabaseFactory.getIdentityDatabase(context).isValidIdentity(masterSecret, recipient,
|
||||
identityKey);
|
||||
}
|
||||
|
||||
public boolean hasInitiatedSession() {
|
||||
return localKeyRecord.getCurrentKeyPair() != null;
|
||||
}
|
||||
|
||||
private boolean needsResponseFromUs() {
|
||||
return !hasInitiatedSession() || remoteKeyRecord.getCurrentRemoteKey() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStale(KeyExchangeMessage _message) {
|
||||
KeyExchangeMessageV1 message = (KeyExchangeMessageV1)_message;
|
||||
int responseKeyId = Conversions.highBitsToMedium(message.getRemoteKey().getId());
|
||||
|
||||
Log.w("KeyExchangeProcessor", "Key Exchange High ID Bits: " + responseKeyId);
|
||||
|
||||
return responseKeyId != 0 &&
|
||||
(localKeyRecord.getCurrentKeyPair() != null && localKeyRecord.getCurrentKeyPair().getId() != responseKeyId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processKeyExchangeMessage(KeyExchangeMessage _message, long threadId) {
|
||||
KeyExchangeMessageV1 message = (KeyExchangeMessageV1)_message;
|
||||
int initiateKeyId = Conversions.lowBitsToMedium(message.getRemoteKey().getId());
|
||||
message.getRemoteKey().setId(initiateKeyId);
|
||||
|
||||
if (needsResponseFromUs()) {
|
||||
localKeyRecord = initializeRecordFor(context, masterSecret, recipient);
|
||||
|
||||
KeyExchangeMessageV1 ourMessage = new KeyExchangeMessageV1(context, masterSecret,
|
||||
Math.min(CiphertextMessage.LEGACY_VERSION,
|
||||
message.getMaxVersion()),
|
||||
localKeyRecord, initiateKeyId);
|
||||
|
||||
OutgoingKeyExchangeMessage textMessage = new OutgoingKeyExchangeMessage(recipient, ourMessage.serialize());
|
||||
Log.w("KeyExchangeProcessorV1", "Responding with key exchange message fingerprint: " + ourMessage.getRemoteKey().getFingerprint());
|
||||
Log.w("KeyExchangeProcessorV1", "Which has a local key record fingerprint: " + localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprint());
|
||||
MessageSender.send(context, masterSecret, textMessage, threadId);
|
||||
}
|
||||
|
||||
remoteKeyRecord.setCurrentRemoteKey(message.getRemoteKey());
|
||||
remoteKeyRecord.setLastRemoteKey(message.getRemoteKey());
|
||||
remoteKeyRecord.save();
|
||||
|
||||
sessionRecord.setSessionId(localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprintBytes(),
|
||||
remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes());
|
||||
sessionRecord.setIdentityKey(message.getIdentityKey());
|
||||
sessionRecord.setSessionVersion(Math.min(1, message.getMaxVersion()));
|
||||
|
||||
Log.w("KeyExchangeUtil", "Setting session version: " + Math.min(1, message.getMaxVersion()));
|
||||
|
||||
sessionRecord.save();
|
||||
|
||||
if (message.hasIdentityKey()) {
|
||||
DatabaseFactory.getIdentityDatabase(context)
|
||||
.saveIdentity(masterSecret, recipient, message.getIdentityKey());
|
||||
}
|
||||
|
||||
DecryptingQueue.scheduleRogueMessages(context, masterSecret, recipient);
|
||||
|
||||
broadcastSecurityUpdateEvent(context, threadId);
|
||||
}
|
||||
|
||||
private static void broadcastSecurityUpdateEvent(Context context, long threadId) {
|
||||
Intent intent = new Intent(SECURITY_UPDATE_EVENT);
|
||||
intent.putExtra("thread_id", threadId);
|
||||
intent.setPackage(context.getPackageName());
|
||||
context.sendBroadcast(intent, KeyCachingService.KEY_PERMISSION);
|
||||
}
|
||||
|
||||
public LocalKeyRecord initializeRecordFor(Context context,
|
||||
MasterSecret masterSecret,
|
||||
Recipient recipient)
|
||||
{
|
||||
Log.w("KeyExchangeProcessorV1", "Initializing local key pairs...");
|
||||
try {
|
||||
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
|
||||
int initialId = secureRandom.nextInt(4094) + 1;
|
||||
|
||||
KeyPair currentPair = new KeyPair(initialId, Curve.generateKeyPairForSession(1), masterSecret);
|
||||
KeyPair nextPair = new KeyPair(initialId + 1, Curve.generateKeyPairForSession(1), masterSecret);
|
||||
LocalKeyRecord record = new LocalKeyRecord(context, masterSecret, recipient);
|
||||
|
||||
record.setCurrentKeyPair(currentPair);
|
||||
record.setNextKeyPair(nextPair);
|
||||
record.save();
|
||||
|
||||
return record;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,221 @@
|
||||
package org.thoughtcrime.securesms.crypto;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
|
||||
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessageV2;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||
import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage;
|
||||
import org.whispersystems.textsecure.crypto.IdentityKey;
|
||||
import org.whispersystems.textsecure.crypto.IdentityKeyPair;
|
||||
import org.whispersystems.textsecure.crypto.InvalidKeyException;
|
||||
import org.whispersystems.textsecure.crypto.InvalidMessageException;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.crypto.ecc.Curve;
|
||||
import org.whispersystems.textsecure.crypto.ecc.ECKeyPair;
|
||||
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
|
||||
import org.whispersystems.textsecure.crypto.protocol.PreKeyWhisperMessage;
|
||||
import org.whispersystems.textsecure.crypto.ratchet.RatchetingSession;
|
||||
import org.whispersystems.textsecure.push.PreKeyEntity;
|
||||
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
|
||||
import org.whispersystems.textsecure.storage.PreKeyRecord;
|
||||
import org.whispersystems.textsecure.storage.Session;
|
||||
import org.whispersystems.textsecure.storage.SessionRecordV2;
|
||||
import org.whispersystems.textsecure.util.Medium;
|
||||
|
||||
/**
|
||||
* This class processes key exchange interactions.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
|
||||
public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
|
||||
|
||||
private Context context;
|
||||
private Recipient recipient;
|
||||
private MasterSecret masterSecret;
|
||||
private SessionRecordV2 sessionRecord;
|
||||
|
||||
public KeyExchangeProcessorV2(Context context, MasterSecret masterSecret, Recipient recipient) {
|
||||
this.context = context;
|
||||
this.recipient = recipient;
|
||||
this.masterSecret = masterSecret;
|
||||
this.sessionRecord = new SessionRecordV2(context, masterSecret, recipient);
|
||||
}
|
||||
|
||||
public boolean isTrusted(PreKeyWhisperMessage message) {
|
||||
return isTrusted(message.getIdentityKey());
|
||||
}
|
||||
|
||||
public boolean isTrusted(KeyExchangeMessage message) {
|
||||
return message.hasIdentityKey() && isTrusted(message.getIdentityKey());
|
||||
}
|
||||
|
||||
public boolean isTrusted(IdentityKey identityKey) {
|
||||
return DatabaseFactory.getIdentityDatabase(context).isValidIdentity(masterSecret, recipient,
|
||||
identityKey);
|
||||
}
|
||||
|
||||
public boolean isStale(KeyExchangeMessage m) {
|
||||
KeyExchangeMessageV2 message = (KeyExchangeMessageV2)m;
|
||||
return
|
||||
message.isResponse() &&
|
||||
(!sessionRecord.hasPendingKeyExchange() ||
|
||||
sessionRecord.getPendingKeyExchangeSequence() != message.getSequence()) &&
|
||||
!message.isResponseForSimultaneousInitiate();
|
||||
}
|
||||
|
||||
public void processKeyExchangeMessage(PreKeyWhisperMessage message)
|
||||
throws InvalidKeyIdException, InvalidKeyException
|
||||
{
|
||||
int preKeyId = message.getPreKeyId();
|
||||
ECPublicKey theirBaseKey = message.getBaseKey();
|
||||
ECPublicKey theirEphemeralKey = message.getWhisperMessage().getSenderEphemeral();
|
||||
IdentityKey theirIdentityKey = message.getIdentityKey();
|
||||
|
||||
Log.w("KeyExchangeProcessor", "Received pre-key with local key ID: " + preKeyId);
|
||||
|
||||
if (!PreKeyRecord.hasRecord(context, preKeyId) && Session.hasSession(context, masterSecret, recipient)) {
|
||||
Log.w("KeyExchangeProcessor", "We've already processed the prekey part, letting bundled message fall through...");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!PreKeyRecord.hasRecord(context, preKeyId))
|
||||
throw new InvalidKeyIdException("No such prekey: " + preKeyId);
|
||||
|
||||
PreKeyRecord preKeyRecord = new PreKeyRecord(context, masterSecret, preKeyId);
|
||||
ECKeyPair ourBaseKey = preKeyRecord.getKeyPair().getKeyPair();
|
||||
ECKeyPair ourEphemeralKey = ourBaseKey;
|
||||
IdentityKeyPair ourIdentityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, ourBaseKey.getPublicKey().getType());
|
||||
|
||||
sessionRecord.clear();
|
||||
|
||||
RatchetingSession.initializeSession(sessionRecord, ourBaseKey, theirBaseKey, ourEphemeralKey,
|
||||
theirEphemeralKey, ourIdentityKey, theirIdentityKey);
|
||||
|
||||
sessionRecord.save();
|
||||
|
||||
if (preKeyId != Medium.MAX_VALUE) {
|
||||
PreKeyRecord.delete(context, preKeyId);
|
||||
}
|
||||
|
||||
DatabaseFactory.getIdentityDatabase(context)
|
||||
.saveIdentity(masterSecret, recipient, theirIdentityKey);
|
||||
}
|
||||
|
||||
public void processKeyExchangeMessage(PreKeyEntity message, long threadId)
|
||||
throws InvalidKeyException
|
||||
{
|
||||
ECKeyPair ourBaseKey = Curve.generateKeyPairForSession(2);
|
||||
ECKeyPair ourEphemeralKey = Curve.generateKeyPairForSession(2);
|
||||
ECPublicKey theirBaseKey = message.getPublicKey().getPublicKey();
|
||||
ECPublicKey theirEphemeralKey = theirBaseKey;
|
||||
IdentityKey theirIdentityKey = message.getIdentityKey();
|
||||
IdentityKeyPair ourIdentityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret,
|
||||
ourBaseKey.getPublicKey()
|
||||
.getType());
|
||||
|
||||
sessionRecord.clear();
|
||||
|
||||
RatchetingSession.initializeSession(sessionRecord, ourBaseKey, theirBaseKey, ourEphemeralKey,
|
||||
theirEphemeralKey, ourIdentityKey, theirIdentityKey);
|
||||
|
||||
sessionRecord.setPendingPreKey(message.getKeyId(), ourBaseKey.getPublicKey());
|
||||
sessionRecord.save();
|
||||
|
||||
DatabaseFactory.getIdentityDatabase(context)
|
||||
.saveIdentity(masterSecret, recipient, message.getIdentityKey());
|
||||
|
||||
broadcastSecurityUpdateEvent(context, threadId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processKeyExchangeMessage(KeyExchangeMessage _message, long threadId)
|
||||
throws InvalidMessageException
|
||||
{
|
||||
try {
|
||||
KeyExchangeMessageV2 message = (KeyExchangeMessageV2)_message;
|
||||
|
||||
Log.w("KeyExchangeProcessorV2", "Received key exchange with sequence: " + message.getSequence());
|
||||
|
||||
if (message.isInitiate()) {
|
||||
ECKeyPair ourBaseKey, ourEphemeralKey;
|
||||
IdentityKeyPair ourIdentityKey;
|
||||
|
||||
int flags = KeyExchangeMessageV2.RESPONSE_FLAG;
|
||||
|
||||
Log.w("KeyExchangeProcessorV2", "KeyExchange is an initiate.");
|
||||
|
||||
if (!sessionRecord.hasPendingKeyExchange()) {
|
||||
Log.w("KeyExchangeProcessorV2", "We don't have a pending initiate...");
|
||||
ourBaseKey = Curve.generateKeyPairForType(message.getBaseKey().getType());
|
||||
ourEphemeralKey = Curve.generateKeyPairForType(message.getBaseKey().getType());
|
||||
ourIdentityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, message.getBaseKey().getType());
|
||||
|
||||
sessionRecord.setPendingKeyExchange(message.getSequence(), ourBaseKey, ourEphemeralKey,
|
||||
ourIdentityKey);
|
||||
} else {
|
||||
Log.w("KeyExchangeProcessorV2", "We alredy have a pending initiate, responding as simultaneous initiate...");
|
||||
ourBaseKey = sessionRecord.getPendingKeyExchangeBaseKey();
|
||||
ourEphemeralKey = sessionRecord.getPendingKeyExchangeEphemeralKey();
|
||||
ourIdentityKey = sessionRecord.getPendingKeyExchangeIdentityKey();
|
||||
flags |= KeyExchangeMessageV2.SIMULTAENOUS_INITIATE_FLAG;
|
||||
|
||||
sessionRecord.setPendingKeyExchange(message.getSequence(), ourBaseKey, ourEphemeralKey,
|
||||
ourIdentityKey);
|
||||
}
|
||||
|
||||
KeyExchangeMessageV2 ourMessage = new KeyExchangeMessageV2(message.getSequence(),
|
||||
flags, ourBaseKey.getPublicKey(),
|
||||
ourEphemeralKey.getPublicKey(),
|
||||
ourIdentityKey.getPublicKey());
|
||||
|
||||
OutgoingKeyExchangeMessage textMessage = new OutgoingKeyExchangeMessage(recipient,
|
||||
ourMessage.serialize());
|
||||
MessageSender.send(context, masterSecret, textMessage, threadId);
|
||||
}
|
||||
|
||||
if (message.getSequence() != sessionRecord.getPendingKeyExchangeSequence()) {
|
||||
Log.w("KeyExchangeProcessorV2", "No matching sequence for response. " +
|
||||
"Is simultaneous initiate response: " + message.isResponseForSimultaneousInitiate());
|
||||
return;
|
||||
}
|
||||
|
||||
ECKeyPair ourBaseKey = sessionRecord.getPendingKeyExchangeBaseKey();
|
||||
ECKeyPair ourEphemeralKey = sessionRecord.getPendingKeyExchangeEphemeralKey();
|
||||
IdentityKeyPair ourIdentityKey = sessionRecord.getPendingKeyExchangeIdentityKey();
|
||||
|
||||
sessionRecord.clear();
|
||||
|
||||
RatchetingSession.initializeSession(sessionRecord, ourBaseKey, message.getBaseKey(),
|
||||
ourEphemeralKey, message.getEphemeralKey(),
|
||||
ourIdentityKey, message.getIdentityKey());
|
||||
|
||||
sessionRecord.setSessionVersion(message.getVersion());
|
||||
Session.clearV1SessionFor(context, recipient);
|
||||
sessionRecord.save();
|
||||
|
||||
DatabaseFactory.getIdentityDatabase(context)
|
||||
.saveIdentity(masterSecret, recipient, message.getIdentityKey());
|
||||
|
||||
DecryptingQueue.scheduleRogueMessages(context, masterSecret, recipient);
|
||||
|
||||
broadcastSecurityUpdateEvent(context, threadId);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void broadcastSecurityUpdateEvent(Context context, long threadId) {
|
||||
Intent intent = new Intent(KeyExchangeProcessorV1.SECURITY_UPDATE_EVENT);
|
||||
intent.putExtra("thread_id", threadId);
|
||||
intent.setPackage(context.getPackageName());
|
||||
context.sendBroadcast(intent, KeyCachingService.KEY_PERMISSION);
|
||||
}
|
||||
|
||||
}
|
@@ -17,151 +17,39 @@
|
||||
*/
|
||||
package org.thoughtcrime.securesms.crypto.protocol;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.whispersystems.textsecure.crypto.IdentityKey;
|
||||
import org.whispersystems.textsecure.crypto.InvalidKeyException;
|
||||
import org.whispersystems.textsecure.crypto.InvalidMessageException;
|
||||
import org.whispersystems.textsecure.crypto.InvalidVersionException;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.crypto.PublicKey;
|
||||
import org.whispersystems.textsecure.crypto.ecc.Curve;
|
||||
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
|
||||
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
|
||||
import org.whispersystems.textsecure.storage.LocalKeyRecord;
|
||||
import org.whispersystems.textsecure.util.Base64;
|
||||
import org.whispersystems.textsecure.util.Conversions;
|
||||
import org.whispersystems.textsecure.util.Util;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A class for constructing and parsing key exchange messages.
|
||||
*
|
||||
* A key exchange message is basically represented by the following format:
|
||||
*
|
||||
* 1) 4 bits <protocol version number>
|
||||
* 2) 4 bits <max supported protocol version number>
|
||||
* 3) A serialized public key
|
||||
* 4) (Optional) An identity key.
|
||||
* 5) (if #4) A signature over the identity key, version bits, and serialized public key.
|
||||
*
|
||||
* A serialized public key is basically represented by the following format:
|
||||
*
|
||||
* 1) A 3 byte key ID.
|
||||
* 2) An ECC key encoded with point compression.
|
||||
*
|
||||
* An initiating key ID is initialized with the bottom 12 bits of the ID set. A responding key
|
||||
* ID does the same, but puts the initiating key ID's bottom 12 bits in the top 12 bits of its
|
||||
* ID. This is used to correlate key exchange responses.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class KeyExchangeMessage {
|
||||
public abstract class KeyExchangeMessage {
|
||||
public abstract boolean isLegacy();
|
||||
public abstract IdentityKey getIdentityKey();
|
||||
public abstract boolean hasIdentityKey();
|
||||
public abstract int getMaxVersion();
|
||||
public abstract int getVersion();
|
||||
|
||||
private final int messageVersion;
|
||||
private final int supportedVersion;
|
||||
private final PublicKey publicKey;
|
||||
private final String serialized;
|
||||
private IdentityKey identityKey;
|
||||
|
||||
public KeyExchangeMessage(Context context, MasterSecret masterSecret, int messageVersion, LocalKeyRecord record, int highIdBits) {
|
||||
this.publicKey = new PublicKey(record.getCurrentKeyPair().getPublicKey());
|
||||
this.messageVersion = messageVersion;
|
||||
this.supportedVersion = CiphertextMessage.SUPPORTED_VERSION;
|
||||
|
||||
publicKey.setId(publicKey.getId() | (highIdBits << 12));
|
||||
|
||||
byte[] versionBytes = {Conversions.intsToByteHighAndLow(messageVersion, supportedVersion)};
|
||||
byte[] publicKeyBytes = publicKey.serialize();
|
||||
|
||||
byte[] serializedBytes;
|
||||
|
||||
if (includeIdentityNoSignature(messageVersion, context)) {
|
||||
byte[] identityKey = IdentityKeyUtil.getIdentityKey(context, Curve.DJB_TYPE).serialize();
|
||||
|
||||
serializedBytes = Util.combine(versionBytes, publicKeyBytes, identityKey);
|
||||
} else if (includeIdentitySignature(messageVersion, context)) {
|
||||
byte[] prolog = Util.combine(versionBytes, publicKeyBytes);
|
||||
|
||||
serializedBytes = IdentityKeyUtil.getSignedKeyExchange(context, masterSecret, prolog);
|
||||
} else {
|
||||
serializedBytes = Util.combine(versionBytes, publicKeyBytes);
|
||||
}
|
||||
|
||||
if (messageVersion < 1) this.serialized = Base64.encodeBytes(serializedBytes);
|
||||
else this.serialized = Base64.encodeBytesWithoutPadding(serializedBytes);
|
||||
}
|
||||
|
||||
public KeyExchangeMessage(String messageBody) throws InvalidVersionException, InvalidKeyException {
|
||||
public static KeyExchangeMessage createFor(String rawMessage)
|
||||
throws InvalidMessageException, InvalidKeyException, InvalidVersionException
|
||||
{
|
||||
try {
|
||||
byte[] keyBytes = Base64.decode(messageBody);
|
||||
this.messageVersion = Conversions.highBitsToInt(keyBytes[0]);
|
||||
this.supportedVersion = Conversions.lowBitsToInt(keyBytes[0]);
|
||||
this.serialized = messageBody;
|
||||
|
||||
if (messageVersion > CiphertextMessage.SUPPORTED_VERSION)
|
||||
throw new InvalidVersionException("Key exchange with version: " + messageVersion);
|
||||
byte[] decodedMessage = Base64.decodeWithoutPadding(rawMessage);
|
||||
|
||||
if (messageVersion >= 1)
|
||||
keyBytes = Base64.decodeWithoutPadding(messageBody);
|
||||
|
||||
this.publicKey = new PublicKey(keyBytes, 1);
|
||||
|
||||
if (keyBytes.length <= PublicKey.KEY_SIZE + 1) {
|
||||
this.identityKey = null;
|
||||
} else if (messageVersion == 1) {
|
||||
try {
|
||||
this.identityKey = IdentityKeyUtil.verifySignedKeyExchange(keyBytes);
|
||||
} catch (InvalidKeyException ike) {
|
||||
Log.w("KeyUtil", ike);
|
||||
this.identityKey = null;
|
||||
}
|
||||
} else if (messageVersion == 2) {
|
||||
try {
|
||||
this.identityKey = new IdentityKey(keyBytes, 1 + PublicKey.KEY_SIZE);
|
||||
} catch (InvalidKeyException ike) {
|
||||
Log.w("KeyUtil", ike);
|
||||
this.identityKey = null;
|
||||
}
|
||||
if (Conversions.highBitsToInt(decodedMessage[0]) <= CiphertextMessage.LEGACY_VERSION) {
|
||||
return new KeyExchangeMessageV1(rawMessage);
|
||||
} else {
|
||||
return new KeyExchangeMessageV2(rawMessage);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
throw new InvalidKeyException(ioe);
|
||||
} catch (IOException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean includeIdentitySignature(int messageVersion, Context context) {
|
||||
return IdentityKeyUtil.hasIdentityKey(context, Curve.NIST_TYPE) && (messageVersion == 1);
|
||||
}
|
||||
|
||||
private static boolean includeIdentityNoSignature(int messageVersion, Context context) {
|
||||
return IdentityKeyUtil.hasIdentityKey(context, Curve.DJB_TYPE) && (messageVersion >= 2);
|
||||
}
|
||||
|
||||
public PublicKey getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public IdentityKey getIdentityKey() {
|
||||
return identityKey;
|
||||
}
|
||||
|
||||
public int getMaxVersion() {
|
||||
return supportedVersion;
|
||||
}
|
||||
|
||||
public int getMessageVersion() {
|
||||
return messageVersion;
|
||||
}
|
||||
|
||||
public boolean hasIdentityKey() {
|
||||
return identityKey != null;
|
||||
}
|
||||
|
||||
public String serialize() {
|
||||
return serialized;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,160 @@
|
||||
package org.thoughtcrime.securesms.crypto.protocol;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.whispersystems.textsecure.crypto.IdentityKey;
|
||||
import org.whispersystems.textsecure.crypto.InvalidKeyException;
|
||||
import org.whispersystems.textsecure.crypto.InvalidVersionException;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.crypto.PublicKey;
|
||||
import org.whispersystems.textsecure.crypto.ecc.Curve;
|
||||
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
|
||||
import org.whispersystems.textsecure.storage.LocalKeyRecord;
|
||||
import org.whispersystems.textsecure.util.Base64;
|
||||
import org.whispersystems.textsecure.util.Conversions;
|
||||
import org.whispersystems.textsecure.util.Util;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A class for constructing and parsing key exchange messages.
|
||||
*
|
||||
* A key exchange message is basically represented by the following format:
|
||||
*
|
||||
* 1) 4 bits <protocol version number>
|
||||
* 2) 4 bits <max supported protocol version number>
|
||||
* 3) A serialized public key
|
||||
* 4) (Optional) An identity key.
|
||||
* 5) (if #4) A signature over the identity key, version bits, and serialized public key.
|
||||
*
|
||||
* A serialized public key is basically represented by the following format:
|
||||
*
|
||||
* 1) A 3 byte key ID.
|
||||
* 2) An ECC key encoded with point compression.
|
||||
*
|
||||
* An initiating key ID is initialized with the bottom 12 bits of the ID set. A responding key
|
||||
* ID does the same, but puts the initiating key ID's bottom 12 bits in the top 12 bits of its
|
||||
* ID. This is used to correlate key exchange responses.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class KeyExchangeMessageV1 extends KeyExchangeMessage {
|
||||
|
||||
private final int messageVersion;
|
||||
private final int supportedVersion;
|
||||
private final PublicKey publicKey;
|
||||
private final String serialized;
|
||||
private IdentityKey identityKey;
|
||||
|
||||
public KeyExchangeMessageV1(Context context, MasterSecret masterSecret,
|
||||
int messageVersion, LocalKeyRecord record, int highIdBits)
|
||||
{
|
||||
this.publicKey = new PublicKey(record.getCurrentKeyPair().getPublicKey());
|
||||
this.messageVersion = messageVersion;
|
||||
this.supportedVersion = CiphertextMessage.CURRENT_VERSION;
|
||||
|
||||
publicKey.setId(publicKey.getId() | (highIdBits << 12));
|
||||
|
||||
byte[] versionBytes = {Conversions.intsToByteHighAndLow(messageVersion, supportedVersion)};
|
||||
byte[] publicKeyBytes = publicKey.serialize();
|
||||
|
||||
byte[] serializedBytes;
|
||||
|
||||
if (includeIdentityNoSignature(messageVersion, context)) {
|
||||
byte[] identityKey = IdentityKeyUtil.getIdentityKey(context, Curve.DJB_TYPE).serialize();
|
||||
|
||||
serializedBytes = Util.combine(versionBytes, publicKeyBytes, identityKey);
|
||||
} else if (includeIdentitySignature(messageVersion, context)) {
|
||||
byte[] prolog = Util.combine(versionBytes, publicKeyBytes);
|
||||
|
||||
serializedBytes = IdentityKeyUtil.getSignedKeyExchange(context, masterSecret, prolog);
|
||||
} else {
|
||||
serializedBytes = Util.combine(versionBytes, publicKeyBytes);
|
||||
}
|
||||
|
||||
if (messageVersion < 1) this.serialized = Base64.encodeBytes(serializedBytes);
|
||||
else this.serialized = Base64.encodeBytesWithoutPadding(serializedBytes);
|
||||
}
|
||||
|
||||
public KeyExchangeMessageV1(String messageBody) throws InvalidVersionException, InvalidKeyException {
|
||||
try {
|
||||
byte[] keyBytes = Base64.decode(messageBody);
|
||||
this.messageVersion = Conversions.highBitsToInt(keyBytes[0]);
|
||||
this.supportedVersion = Conversions.lowBitsToInt(keyBytes[0]);
|
||||
this.serialized = messageBody;
|
||||
|
||||
if (messageVersion > 1)
|
||||
throw new InvalidVersionException("Legacy key exchange with version: " + messageVersion);
|
||||
|
||||
if (messageVersion >= 1)
|
||||
keyBytes = Base64.decodeWithoutPadding(messageBody);
|
||||
|
||||
this.publicKey = new PublicKey(keyBytes, 1);
|
||||
|
||||
if (keyBytes.length <= PublicKey.KEY_SIZE + 1) {
|
||||
this.identityKey = null;
|
||||
} else if (messageVersion == 1) {
|
||||
try {
|
||||
this.identityKey = IdentityKeyUtil.verifySignedKeyExchange(keyBytes);
|
||||
} catch (InvalidKeyException ike) {
|
||||
Log.w("KeyUtil", ike);
|
||||
this.identityKey = null;
|
||||
}
|
||||
} else if (messageVersion == 2) {
|
||||
try {
|
||||
this.identityKey = new IdentityKey(keyBytes, 1 + PublicKey.KEY_SIZE);
|
||||
} catch (InvalidKeyException ike) {
|
||||
Log.w("KeyUtil", ike);
|
||||
this.identityKey = null;
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
throw new InvalidKeyException(ioe);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean includeIdentitySignature(int messageVersion, Context context) {
|
||||
return IdentityKeyUtil.hasIdentityKey(context, Curve.NIST_TYPE) && (messageVersion == 1);
|
||||
}
|
||||
|
||||
private static boolean includeIdentityNoSignature(int messageVersion, Context context) {
|
||||
return IdentityKeyUtil.hasIdentityKey(context, Curve.DJB_TYPE) && (messageVersion >= 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLegacy() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityKey getIdentityKey() {
|
||||
return identityKey;
|
||||
}
|
||||
|
||||
public PublicKey getRemoteKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxVersion() {
|
||||
return supportedVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVersion() {
|
||||
return messageVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasIdentityKey() {
|
||||
return identityKey != null;
|
||||
}
|
||||
|
||||
public String serialize() {
|
||||
return serialized;
|
||||
}
|
||||
}
|
@@ -0,0 +1,152 @@
|
||||
package org.thoughtcrime.securesms.crypto.protocol;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
import org.whispersystems.textsecure.crypto.IdentityKey;
|
||||
import org.whispersystems.textsecure.crypto.InvalidKeyException;
|
||||
import org.whispersystems.textsecure.crypto.InvalidMessageException;
|
||||
import org.whispersystems.textsecure.crypto.InvalidVersionException;
|
||||
import org.whispersystems.textsecure.crypto.ecc.Curve;
|
||||
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
|
||||
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
|
||||
import org.whispersystems.textsecure.crypto.protocol.WhisperProtos;
|
||||
import org.whispersystems.textsecure.util.Base64;
|
||||
import org.whispersystems.textsecure.util.Conversions;
|
||||
import org.whispersystems.textsecure.util.Util;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class KeyExchangeMessageV2 extends KeyExchangeMessage {
|
||||
|
||||
public static final int INITIATE_FLAG = 0x01;
|
||||
public static final int RESPONSE_FLAG = 0X02;
|
||||
public static final int SIMULTAENOUS_INITIATE_FLAG = 0x04;
|
||||
|
||||
private final int version;
|
||||
private final int supportedVersion;
|
||||
private final int sequence;
|
||||
private final int flags;
|
||||
|
||||
private final ECPublicKey baseKey;
|
||||
private final ECPublicKey ephemeralKey;
|
||||
private final IdentityKey identityKey;
|
||||
private final byte[] serialized;
|
||||
|
||||
public KeyExchangeMessageV2(int sequence, int flags,
|
||||
ECPublicKey baseKey, ECPublicKey ephemeralKey,
|
||||
IdentityKey identityKey)
|
||||
{
|
||||
this.supportedVersion = CiphertextMessage.CURRENT_VERSION;
|
||||
this.version = CiphertextMessage.CURRENT_VERSION;
|
||||
this.sequence = sequence;
|
||||
this.flags = flags;
|
||||
this.baseKey = baseKey;
|
||||
this.ephemeralKey = ephemeralKey;
|
||||
this.identityKey = identityKey;
|
||||
|
||||
byte[] version = {Conversions.intsToByteHighAndLow(this.version, this.supportedVersion)};
|
||||
byte[] message = WhisperProtos.KeyExchangeMessage.newBuilder()
|
||||
.setId((sequence << 5) | flags)
|
||||
.setBaseKey(ByteString.copyFrom(baseKey.serialize()))
|
||||
.setEphemeralKey(ByteString.copyFrom(ephemeralKey.serialize()))
|
||||
.setIdentityKey(ByteString.copyFrom(identityKey.serialize()))
|
||||
.build().toByteArray();
|
||||
|
||||
this.serialized = Util.combine(version, message);
|
||||
}
|
||||
|
||||
public KeyExchangeMessageV2(String serializedAndEncoded)
|
||||
throws InvalidMessageException, InvalidVersionException
|
||||
{
|
||||
try {
|
||||
byte[] serialized = Base64.decodeWithoutPadding(serializedAndEncoded);
|
||||
byte[][] parts = Util.split(serialized, 1, serialized.length - 1);
|
||||
|
||||
this.version = Conversions.highBitsToInt(parts[0][0]);
|
||||
this.supportedVersion = Conversions.lowBitsToInt(parts[0][0]);
|
||||
|
||||
if (this.version > CiphertextMessage.CURRENT_VERSION) {
|
||||
throw new InvalidVersionException("Unknown version: " + this.version);
|
||||
}
|
||||
|
||||
WhisperProtos.KeyExchangeMessage message = WhisperProtos.KeyExchangeMessage.parseFrom(parts[1]);
|
||||
|
||||
if (!message.hasId() || !message.hasBaseKey() ||
|
||||
!message.hasEphemeralKey() || !message.hasIdentityKey())
|
||||
{
|
||||
throw new InvalidMessageException("Some required fields missing!");
|
||||
}
|
||||
|
||||
this.sequence = message.getId() >> 5;
|
||||
this.flags = message.getId() & 0x1f;
|
||||
this.serialized = serialized;
|
||||
this.baseKey = Curve.decodePoint(message.getBaseKey().toByteArray(), 0);
|
||||
this.ephemeralKey = Curve.decodePoint(message.getEphemeralKey().toByteArray(), 0);
|
||||
this.identityKey = new IdentityKey(message.getIdentityKey().toByteArray(), 0);
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
} catch (IOException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public ECPublicKey getBaseKey() {
|
||||
return baseKey;
|
||||
}
|
||||
|
||||
public ECPublicKey getEphemeralKey() {
|
||||
return ephemeralKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLegacy() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityKey getIdentityKey() {
|
||||
return identityKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasIdentityKey() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxVersion() {
|
||||
return supportedVersion;
|
||||
}
|
||||
|
||||
public boolean isResponse() {
|
||||
return ((flags & RESPONSE_FLAG) != 0);
|
||||
}
|
||||
|
||||
public boolean isInitiate() {
|
||||
return (flags & INITIATE_FLAG) != 0;
|
||||
}
|
||||
|
||||
public boolean isResponseForSimultaneousInitiate() {
|
||||
return (flags & SIMULTAENOUS_INITIATE_FLAG) != 0;
|
||||
}
|
||||
|
||||
public int getFlags() {
|
||||
return flags;
|
||||
}
|
||||
|
||||
public int getSequence() {
|
||||
return sequence;
|
||||
}
|
||||
|
||||
public String serialize() {
|
||||
return Base64.encodeBytesWithoutPadding(serialized);
|
||||
}
|
||||
}
|
@@ -30,8 +30,8 @@ import org.whispersystems.textsecure.crypto.IdentityKey;
|
||||
import org.whispersystems.textsecure.crypto.InvalidMessageException;
|
||||
import org.whispersystems.textsecure.crypto.MasterCipher;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.storage.SessionRecord;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.whispersystems.textsecure.storage.Session;
|
||||
import org.whispersystems.textsecure.util.Base64;
|
||||
import org.whispersystems.textsecure.util.Util;
|
||||
|
||||
@@ -392,8 +392,7 @@ public class DatabaseFactory {
|
||||
|
||||
if (name.matches("[0-9]+")) {
|
||||
long recipientId = Long.parseLong(name);
|
||||
SessionRecord sessionRecord = new SessionRecord(context, masterSecret, recipientId);
|
||||
IdentityKey identityKey = sessionRecord.getIdentityKey();
|
||||
IdentityKey identityKey = Session.getRemoteIdentityKey(context, masterSecret, recipientId);
|
||||
|
||||
if (identityKey != null) {
|
||||
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
||||
|
@@ -166,6 +166,10 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
|
||||
updateTypeBitmask(id, 0, Types.KEY_EXCHANGE_CORRUPTED_BIT);
|
||||
}
|
||||
|
||||
public void markAsInvalidVersionKeyExchange(long id) {
|
||||
updateTypeBitmask(id, 0, Types.KEY_EXCHANGE_INVALID_VERSION_BIT);
|
||||
}
|
||||
|
||||
public void markAsSecure(long id) {
|
||||
updateTypeBitmask(id, 0, Types.SECURE_MESSAGE_BIT);
|
||||
}
|
||||
|
@@ -8,7 +8,7 @@ import android.util.Pair;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.DecryptingQueue;
|
||||
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
|
||||
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessorV2;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
@@ -24,9 +24,10 @@ import org.thoughtcrime.securesms.sms.IncomingTextMessage;
|
||||
import org.thoughtcrime.securesms.sms.SmsTransportDetails;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.textsecure.crypto.InvalidKeyException;
|
||||
import org.whispersystems.textsecure.crypto.InvalidMessageException;
|
||||
import org.whispersystems.textsecure.crypto.InvalidVersionException;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage;
|
||||
import org.whispersystems.textsecure.crypto.protocol.PreKeyWhisperMessage;
|
||||
import org.whispersystems.textsecure.push.IncomingPushMessage;
|
||||
import org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent;
|
||||
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
|
||||
@@ -46,9 +47,9 @@ public class PushReceiver {
|
||||
}
|
||||
|
||||
public void process(MasterSecret masterSecret, Intent intent) {
|
||||
if (intent.getAction().equals(SendReceiveService.RECEIVE_PUSH_ACTION)) {
|
||||
if (SendReceiveService.RECEIVE_PUSH_ACTION.equals(intent.getAction())) {
|
||||
handleMessage(masterSecret, intent);
|
||||
} else if (intent.getAction().equals(SendReceiveService.DECRYPTED_PUSH_ACTION)) {
|
||||
} else if (SendReceiveService.DECRYPTED_PUSH_ACTION.equals(intent.getAction())) {
|
||||
handleDecrypt(masterSecret, intent);
|
||||
}
|
||||
}
|
||||
@@ -81,25 +82,25 @@ public class PushReceiver {
|
||||
} else {
|
||||
Recipients recipients = RecipientFactory.getRecipientsFromMessage(context, message, false);
|
||||
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients);
|
||||
MessageNotifier.updateNotification(context, masterSecret, threadId);
|
||||
MessageNotifier.updateNotification(context, null, threadId);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleReceivedPreKeyBundle(MasterSecret masterSecret, IncomingPushMessage message) {
|
||||
if (masterSecret == null) {
|
||||
handleReceivedSecureMessage(masterSecret, message);
|
||||
handleReceivedSecureMessage(null, message);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Recipient recipient = new Recipient(null, message.getSource(), null, null);
|
||||
KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipient);
|
||||
PreKeyBundleMessage preKeyExchange = new PreKeyBundleMessage(message.getBody());
|
||||
Recipient recipient = new Recipient(null, message.getSource(), null, null);
|
||||
KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(context, masterSecret, recipient);
|
||||
PreKeyWhisperMessage preKeyExchange = new PreKeyWhisperMessage(message.getBody());
|
||||
|
||||
if (processor.isTrusted(preKeyExchange)) {
|
||||
processor.processKeyExchangeMessage(preKeyExchange);
|
||||
|
||||
IncomingPushMessage bundledMessage = message.withBody(preKeyExchange.getBundledMessage().serialize());
|
||||
IncomingPushMessage bundledMessage = message.withBody(preKeyExchange.getWhisperMessage().serialize());
|
||||
handleReceivedSecureMessage(masterSecret, bundledMessage);
|
||||
} else {
|
||||
SmsTransportDetails transportDetails = new SmsTransportDetails();
|
||||
@@ -110,13 +111,16 @@ public class PushReceiver {
|
||||
DatabaseFactory.getEncryptingSmsDatabase(context).insertMessageInbox(masterSecret, textMessage);
|
||||
}
|
||||
} catch (InvalidKeyException e) {
|
||||
Log.w("SmsReceiver", e);
|
||||
Log.w("PushReceiver", e);
|
||||
handleReceivedCorruptedKey(masterSecret, message, false);
|
||||
} catch (InvalidVersionException e) {
|
||||
Log.w("SmsReceiver", e);
|
||||
Log.w("PushReceiver", e);
|
||||
handleReceivedCorruptedKey(masterSecret, message, true);
|
||||
} catch (InvalidKeyIdException e) {
|
||||
Log.w("SmsReceiver", e);
|
||||
Log.w("PushReceiver", e);
|
||||
handleReceivedCorruptedKey(masterSecret, message, false);
|
||||
} catch (InvalidMessageException e) {
|
||||
Log.w("PushReceiver", e);
|
||||
handleReceivedCorruptedKey(masterSecret, message, false);
|
||||
}
|
||||
}
|
||||
@@ -237,9 +241,7 @@ public class PushReceiver {
|
||||
placeholder = new IncomingEncryptedMessage(placeholder, "");
|
||||
}
|
||||
|
||||
Pair<Long, Long> messageAndThreadId = DatabaseFactory.getEncryptingSmsDatabase(context)
|
||||
.insertMessageInbox(masterSecret,
|
||||
placeholder);
|
||||
return messageAndThreadId;
|
||||
return DatabaseFactory.getEncryptingSmsDatabase(context)
|
||||
.insertMessageInbox(masterSecret, placeholder);
|
||||
}
|
||||
}
|
||||
|
@@ -23,6 +23,7 @@ import android.util.Pair;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.DecryptingQueue;
|
||||
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
|
||||
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessorV2;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
||||
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
@@ -33,15 +34,17 @@ import org.thoughtcrime.securesms.protocol.WirePrefix;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingPreKeyBundleMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
|
||||
import org.thoughtcrime.securesms.sms.MultipartSmsMessageHandler;
|
||||
import org.thoughtcrime.securesms.sms.SmsTransportDetails;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.textsecure.crypto.InvalidKeyException;
|
||||
import org.whispersystems.textsecure.crypto.InvalidMessageException;
|
||||
import org.whispersystems.textsecure.crypto.InvalidVersionException;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
|
||||
import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage;
|
||||
import org.whispersystems.textsecure.crypto.protocol.PreKeyWhisperMessage;
|
||||
import org.whispersystems.textsecure.crypto.protocol.WhisperMessageV2;
|
||||
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -97,26 +100,27 @@ public class SmsReceiver {
|
||||
}
|
||||
}
|
||||
|
||||
private Pair<Long, Long> storePreKeyBundledMessage(MasterSecret masterSecret,
|
||||
IncomingKeyExchangeMessage message)
|
||||
private Pair<Long, Long> storePreKeyWhisperMessage(MasterSecret masterSecret,
|
||||
IncomingPreKeyBundleMessage message)
|
||||
{
|
||||
Log.w("SmsReceiver", "Processing prekey message...");
|
||||
|
||||
try {
|
||||
Recipient recipient = new Recipient(null, message.getSender(), null, null);
|
||||
KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipient);
|
||||
SmsTransportDetails transportDetails = new SmsTransportDetails();
|
||||
PreKeyBundleMessage preKeyExchange = new PreKeyBundleMessage(transportDetails.getDecodedMessage(message.getMessageBody().getBytes()));
|
||||
Recipient recipient = new Recipient(null, message.getSender(), null, null);
|
||||
KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(context, masterSecret, recipient);
|
||||
SmsTransportDetails transportDetails = new SmsTransportDetails();
|
||||
byte[] rawMessage = transportDetails.getDecodedMessage(message.getMessageBody().getBytes());
|
||||
PreKeyWhisperMessage preKeyExchange = new PreKeyWhisperMessage(rawMessage);
|
||||
|
||||
if (processor.isTrusted(preKeyExchange)) {
|
||||
processor.processKeyExchangeMessage(preKeyExchange);
|
||||
|
||||
CiphertextMessage ciphertextMessage = preKeyExchange.getBundledMessage();
|
||||
WhisperMessageV2 ciphertextMessage = preKeyExchange.getWhisperMessage();
|
||||
String bundledMessageBody = new String(transportDetails.getEncodedMessage(ciphertextMessage.serialize()));
|
||||
IncomingEncryptedMessage bundledMessage = new IncomingEncryptedMessage(message, bundledMessageBody);
|
||||
Pair<Long, Long> messageAndThreadId = storeSecureMessage(masterSecret, bundledMessage);
|
||||
|
||||
Intent intent = new Intent(KeyExchangeProcessor.SECURITY_UPDATE_EVENT);
|
||||
Intent intent = new Intent(KeyExchangeProcessorV2.SECURITY_UPDATE_EVENT);
|
||||
intent.putExtra("thread_id", messageAndThreadId.second);
|
||||
intent.setPackage(context.getPackageName());
|
||||
context.sendBroadcast(intent, KeyCachingService.KEY_PERMISSION);
|
||||
@@ -135,6 +139,9 @@ public class SmsReceiver {
|
||||
} catch (IOException e) {
|
||||
Log.w("SmsReceive", e);
|
||||
message.setCorrupted(true);
|
||||
} catch (InvalidMessageException e) {
|
||||
Log.w("SmsReceiver", e);
|
||||
message.setCorrupted(true);
|
||||
}
|
||||
|
||||
return storeStandardMessage(masterSecret, message);
|
||||
@@ -145,19 +152,17 @@ public class SmsReceiver {
|
||||
{
|
||||
if (masterSecret != null && TextSecurePreferences.isAutoRespondKeyExchangeEnabled(context)) {
|
||||
try {
|
||||
Recipient recipient = new Recipient(null, message.getSender(), null, null);
|
||||
KeyExchangeMessage keyExchangeMessage = new KeyExchangeMessage(message.getMessageBody());
|
||||
KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipient);
|
||||
Recipient recipient = new Recipient(null, message.getSender(), null, null);
|
||||
KeyExchangeMessage exchangeMessage = KeyExchangeMessage.createFor(message.getMessageBody());
|
||||
KeyExchangeProcessor processor = KeyExchangeProcessor.createFor(context, masterSecret, recipient, exchangeMessage);
|
||||
|
||||
Log.w("SmsReceiver", "Received key with fingerprint: " + keyExchangeMessage.getPublicKey().getFingerprint());
|
||||
|
||||
if (processor.isStale(keyExchangeMessage)) {
|
||||
if (processor.isStale(exchangeMessage)) {
|
||||
message.setStale(true);
|
||||
} else if (processor.isTrusted(keyExchangeMessage)) {
|
||||
} else if (processor.isTrusted(exchangeMessage)) {
|
||||
message.setProcessed(true);
|
||||
|
||||
Pair<Long, Long> messageAndThreadId = storeStandardMessage(masterSecret, message);
|
||||
processor.processKeyExchangeMessage(keyExchangeMessage, messageAndThreadId.second);
|
||||
processor.processKeyExchangeMessage(exchangeMessage, messageAndThreadId.second);
|
||||
|
||||
return messageAndThreadId;
|
||||
}
|
||||
@@ -167,6 +172,9 @@ public class SmsReceiver {
|
||||
} catch (InvalidKeyException e) {
|
||||
Log.w("SmsReceiver", e);
|
||||
message.setCorrupted(true);
|
||||
} catch (InvalidMessageException e) {
|
||||
Log.w("SmsReceiver", e);
|
||||
message.setCorrupted(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,7 +183,7 @@ public class SmsReceiver {
|
||||
|
||||
private Pair<Long, Long> storeMessage(MasterSecret masterSecret, IncomingTextMessage message) {
|
||||
if (message.isSecureMessage()) return storeSecureMessage(masterSecret, message);
|
||||
else if (message.isPreKeyBundle()) return storePreKeyBundledMessage(masterSecret, (IncomingKeyExchangeMessage) message);
|
||||
else if (message.isPreKeyBundle()) return storePreKeyWhisperMessage(masterSecret, (IncomingPreKeyBundleMessage) message);
|
||||
else if (message.isKeyExchange()) return storeKeyExchangeMessage(masterSecret, (IncomingKeyExchangeMessage) message);
|
||||
else return storeStandardMessage(masterSecret, message);
|
||||
}
|
||||
@@ -191,7 +199,7 @@ public class SmsReceiver {
|
||||
}
|
||||
|
||||
public void process(MasterSecret masterSecret, Intent intent) {
|
||||
if (intent.getAction().equals(SendReceiveService.RECEIVE_SMS_ACTION)) {
|
||||
if (SendReceiveService.RECEIVE_SMS_ACTION.equals(intent.getAction())) {
|
||||
handleReceiveMessage(masterSecret, intent);
|
||||
}
|
||||
}
|
||||
|
@@ -21,7 +21,6 @@ import android.content.Context;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.mms.MmsRadio;
|
||||
import org.thoughtcrime.securesms.mms.MmsRadioException;
|
||||
@@ -30,10 +29,8 @@ import org.thoughtcrime.securesms.mms.MmsSendResult;
|
||||
import org.thoughtcrime.securesms.mms.TextTransport;
|
||||
import org.thoughtcrime.securesms.protocol.WirePrefix;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.whispersystems.textsecure.crypto.IdentityKeyPair;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.crypto.MessageCipher;
|
||||
import org.whispersystems.textsecure.crypto.ecc.Curve;
|
||||
import org.whispersystems.textsecure.crypto.SessionCipher;
|
||||
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
|
||||
import org.whispersystems.textsecure.util.Hex;
|
||||
|
||||
@@ -158,9 +155,8 @@ public class MmsTransport {
|
||||
private byte[] getEncryptedPdu(MasterSecret masterSecret, String recipientString, byte[] pduBytes) {
|
||||
TextTransport transportDetails = new TextTransport();
|
||||
Recipient recipient = new Recipient(null, recipientString, null, null);
|
||||
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, Curve.DJB_TYPE);
|
||||
MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey);
|
||||
CiphertextMessage ciphertextMessage = messageCipher.encrypt(recipient, pduBytes);
|
||||
SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipient);
|
||||
CiphertextMessage ciphertextMessage = sessionCipher.encrypt(pduBytes);
|
||||
|
||||
return transportDetails.getEncodedMessage(ciphertextMessage.serialize());
|
||||
}
|
||||
|
@@ -24,7 +24,7 @@ import com.google.protobuf.ByteString;
|
||||
|
||||
import org.thoughtcrime.securesms.Release;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
|
||||
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessorV2;
|
||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.mms.PartParser;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
@@ -34,14 +34,10 @@ import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePushCredentials;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.textsecure.crypto.AttachmentCipher;
|
||||
import org.whispersystems.textsecure.crypto.IdentityKey;
|
||||
import org.whispersystems.textsecure.crypto.IdentityKeyPair;
|
||||
import org.whispersystems.textsecure.crypto.KeyUtil;
|
||||
import org.whispersystems.textsecure.crypto.InvalidKeyException;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.crypto.MessageCipher;
|
||||
import org.whispersystems.textsecure.crypto.ecc.Curve;
|
||||
import org.whispersystems.textsecure.crypto.SessionCipher;
|
||||
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
|
||||
import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage;
|
||||
import org.whispersystems.textsecure.push.OutgoingPushMessage;
|
||||
import org.whispersystems.textsecure.push.PreKeyEntity;
|
||||
import org.whispersystems.textsecure.push.PushAttachmentData;
|
||||
@@ -51,6 +47,7 @@ import org.whispersystems.textsecure.push.PushDestination;
|
||||
import org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent;
|
||||
import org.whispersystems.textsecure.push.PushServiceSocket;
|
||||
import org.whispersystems.textsecure.push.RateLimitException;
|
||||
import org.whispersystems.textsecure.storage.SessionRecordV2;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
@@ -166,62 +163,27 @@ public class PushTransport extends BaseTransport {
|
||||
PushDestination pushDestination, byte[] plaintext)
|
||||
throws IOException
|
||||
{
|
||||
if (KeyUtil.isNonPrekeySessionFor(context, masterSecret, recipient)) {
|
||||
Log.w("PushTransport", "Sending standard ciphertext message...");
|
||||
byte[] ciphertext = getEncryptedMessageForExistingSession(recipient, plaintext);
|
||||
return new PushBody(OutgoingPushMessage.TYPE_MESSAGE_CIPHERTEXT, ciphertext);
|
||||
} else if (KeyUtil.isSessionFor(context, recipient)) {
|
||||
Log.w("PushTransport", "Sending prekeybundle ciphertext message for existing session...");
|
||||
byte[] ciphertext = getEncryptedPrekeyBundleMessageForExistingSession(recipient, plaintext);
|
||||
return new PushBody(OutgoingPushMessage.TYPE_MESSAGE_PREKEY_BUNDLE, ciphertext);
|
||||
if (!SessionRecordV2.hasSession(context, masterSecret, recipient)) {
|
||||
try {
|
||||
PreKeyEntity preKey = socket.getPreKey(pushDestination);
|
||||
KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(context, masterSecret, recipient);
|
||||
|
||||
processor.processKeyExchangeMessage(preKey, threadId);
|
||||
} catch (InvalidKeyException e) {
|
||||
Log.w("PushTransport", e);
|
||||
throw new IOException("Invalid PreKey!");
|
||||
}
|
||||
}
|
||||
|
||||
SessionCipher cipher = SessionCipher.createFor(context, masterSecret, recipient);
|
||||
CiphertextMessage message = cipher.encrypt(plaintext);
|
||||
|
||||
if (message.getType() == CiphertextMessage.PREKEY_WHISPER_TYPE) {
|
||||
return new PushBody(OutgoingPushMessage.TYPE_MESSAGE_PREKEY_BUNDLE, message.serialize());
|
||||
} else if (message.getType() == CiphertextMessage.CURRENT_WHISPER_TYPE) {
|
||||
return new PushBody(OutgoingPushMessage.TYPE_MESSAGE_CIPHERTEXT, message.serialize());
|
||||
} else {
|
||||
Log.w("PushTransport", "Sending prekeybundle ciphertext message for new session...");
|
||||
byte[] ciphertext = getEncryptedPrekeyBundleMessageForNewSession(socket, threadId, recipient, pushDestination, plaintext);
|
||||
return new PushBody(OutgoingPushMessage.TYPE_MESSAGE_PREKEY_BUNDLE, ciphertext);
|
||||
throw new AssertionError("Unknown ciphertext type: " + message.getType());
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] getEncryptedPrekeyBundleMessageForExistingSession(Recipient recipient,
|
||||
byte[] plaintext)
|
||||
{
|
||||
IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, Curve.DJB_TYPE);
|
||||
IdentityKey identityKey = identityKeyPair.getPublicKey();
|
||||
MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKeyPair);
|
||||
CiphertextMessage ciphertextMessage = messageCipher.encrypt(recipient, plaintext);
|
||||
PreKeyBundleMessage preKeyBundleMessage = new PreKeyBundleMessage(ciphertextMessage, identityKey);
|
||||
|
||||
return preKeyBundleMessage.serialize();
|
||||
}
|
||||
|
||||
private byte[] getEncryptedPrekeyBundleMessageForNewSession(PushServiceSocket socket,
|
||||
long threadId,
|
||||
Recipient recipient,
|
||||
PushDestination pushDestination,
|
||||
byte[] plaintext)
|
||||
throws IOException
|
||||
{
|
||||
IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, Curve.DJB_TYPE);
|
||||
IdentityKey identityKey = identityKeyPair.getPublicKey();
|
||||
PreKeyEntity preKey = socket.getPreKey(pushDestination);
|
||||
KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipient);
|
||||
|
||||
processor.processKeyExchangeMessage(preKey, threadId);
|
||||
|
||||
MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKeyPair);
|
||||
CiphertextMessage ciphertextMessage = messageCipher.encrypt(recipient, plaintext);
|
||||
PreKeyBundleMessage preKeyBundleMessage = new PreKeyBundleMessage(ciphertextMessage, identityKey);
|
||||
|
||||
return preKeyBundleMessage.serialize();
|
||||
}
|
||||
|
||||
private byte[] getEncryptedMessageForExistingSession(Recipient recipient, byte[] plaintext)
|
||||
throws IOException
|
||||
{
|
||||
IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, Curve.DJB_TYPE);
|
||||
MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKeyPair);
|
||||
CiphertextMessage ciphertextMessage = messageCipher.encrypt(recipient, plaintext);
|
||||
|
||||
return ciphertextMessage.serialize();
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -22,7 +22,6 @@ import android.content.Context;
|
||||
import android.telephony.SmsManager;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.service.SendReceiveService;
|
||||
@@ -32,13 +31,9 @@ import org.thoughtcrime.securesms.sms.OutgoingPrekeyBundleMessage;
|
||||
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
|
||||
import org.thoughtcrime.securesms.sms.SmsTransportDetails;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.textsecure.crypto.IdentityKeyPair;
|
||||
import org.whispersystems.textsecure.crypto.KeyUtil;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.crypto.MessageCipher;
|
||||
import org.whispersystems.textsecure.crypto.ecc.Curve;
|
||||
import org.whispersystems.textsecure.crypto.SessionCipher;
|
||||
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
|
||||
import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@@ -69,7 +64,7 @@ public class SmsTransport extends BaseTransport {
|
||||
}
|
||||
|
||||
ArrayList<String> messages = multipartMessageHandler.divideMessage(transportMessage);
|
||||
ArrayList<PendingIntent> sentIntents = constructSentIntents(message.getId(), message.getType(), messages, true);
|
||||
ArrayList<PendingIntent> sentIntents = constructSentIntents(message.getId(), message.getType(), messages, message.isSecure());
|
||||
ArrayList<PendingIntent> deliveredIntents = constructDeliveredIntents(message.getId(), message.getType(), messages);
|
||||
|
||||
Log.w("SmsTransport", "Secure divide into message parts: " + messages.size());
|
||||
@@ -164,30 +159,20 @@ public class SmsTransport extends BaseTransport {
|
||||
private OutgoingTextMessage getAsymmetricEncrypt(MasterSecret masterSecret,
|
||||
OutgoingTextMessage message)
|
||||
{
|
||||
Recipient recipient = message.getRecipients().getPrimaryRecipient();
|
||||
String body = message.getMessageBody();
|
||||
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret,
|
||||
Curve.DJB_TYPE);
|
||||
Recipient recipient = message.getRecipients().getPrimaryRecipient();
|
||||
String body = message.getMessageBody();
|
||||
SmsTransportDetails transportDetails = new SmsTransportDetails();
|
||||
SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipient);
|
||||
byte[] paddedPlaintext = transportDetails.getPaddedMessageBody(body.getBytes());
|
||||
CiphertextMessage ciphertextMessage = sessionCipher.encrypt(paddedPlaintext);
|
||||
String encodedCiphertext = new String(transportDetails.getEncodedMessage(ciphertextMessage.serialize()));
|
||||
|
||||
SmsTransportDetails transportDetails = new SmsTransportDetails();
|
||||
|
||||
if (KeyUtil.isNonPrekeySessionFor(context, masterSecret, recipient)) {
|
||||
Log.w("SmsTransport", "Delivering standard ciphertext...");
|
||||
|
||||
MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey);
|
||||
byte[] paddedPlaintext = transportDetails.getPaddedMessageBody(body.getBytes());
|
||||
CiphertextMessage ciphertextMessage = messageCipher.encrypt(recipient, paddedPlaintext);
|
||||
String ciphertxt = new String(transportDetails.getEncodedMessage(ciphertextMessage.serialize()));
|
||||
|
||||
return message.withBody(ciphertxt);
|
||||
if (ciphertextMessage.getType() == CiphertextMessage.PREKEY_WHISPER_TYPE) {
|
||||
message = new OutgoingPrekeyBundleMessage(message, encodedCiphertext);
|
||||
} else {
|
||||
Log.w("SmsTransport", "Delivering prekeybundle ciphertext...");
|
||||
MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey);
|
||||
CiphertextMessage ciphertextMessage = messageCipher.encrypt(recipient, body.getBytes());
|
||||
PreKeyBundleMessage preKeyBundleMessage = new PreKeyBundleMessage(ciphertextMessage, identityKey.getPublicKey());
|
||||
byte[] cipherText = preKeyBundleMessage.serialize();
|
||||
|
||||
return new OutgoingPrekeyBundleMessage(message, new String(transportDetails.getEncodedMessage(cipherText)));
|
||||
message = message.withBody(encodedCiphertext);
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user