Remove V1 code.

This commit is contained in:
Moxie Marlinspike
2014-04-09 20:02:46 -07:00
parent ca8c950553
commit 1d07ca3e6f
51 changed files with 175 additions and 2048 deletions

View File

@@ -26,14 +26,13 @@ import android.view.View;
import android.widget.Button;
import org.thoughtcrime.securesms.crypto.KeyExchangeInitiator;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.storage.LocalKeyRecord;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.RemoteKeyRecord;
import org.thoughtcrime.securesms.protocol.Tag;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.MemoryCleaner;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.Session;
import org.whispersystems.textsecure.storage.SessionRecordV2;
/**
@@ -114,10 +113,10 @@ public class AutoInitiateActivity extends Activity {
return !sp.getBoolean("pref_thread_auto_init_exempt_" + threadId, false);
}
private static boolean isExchangeQualified(Context context, MasterSecret masterSecret, Recipient recipient) {
return
(new RemoteKeyRecord(context,recipient).getCurrentRemoteKey() == null) &&
(new LocalKeyRecord(context, masterSecret, recipient).getCurrentKeyPair() == null) &&
!SessionRecordV2.hasSession(context, masterSecret, recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID);
private static boolean isExchangeQualified(Context context,
MasterSecret masterSecret,
Recipient recipient)
{
return !Session.hasSession(context, masterSecret, recipient);
}
}

View File

@@ -163,7 +163,6 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
private long threadId;
private int distributionType;
private boolean isEncryptedConversation;
private boolean isAuthenticatedConversation;
private boolean isMmsEnabled = true;
private boolean isCharactersLeftViewEnabled;
@@ -261,12 +260,7 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
boolean pushRegistered = TextSecurePreferences.isPushRegistered(this);
if (isSingleConversation() && isEncryptedConversation) {
if (isAuthenticatedConversation) {
inflater.inflate(R.menu.conversation_secure_identity, menu);
} else {
inflater.inflate(R.menu.conversation_secure_no_identity, menu);
}
inflater.inflate(R.menu.conversation_secure_identity, menu);
inflater.inflate(R.menu.conversation_secure_sms, menu.findItem(R.id.menu_security).getSubMenu());
} else if (isSingleConversation() && !pushRegistered) {
inflater.inflate(R.menu.conversation_insecure, menu);
@@ -670,16 +664,14 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
if (isPushDestination ||
(isSingleConversation() && Session.hasSession(this, masterSecret, primaryRecipient)))
{
this.isEncryptedConversation = true;
this.isAuthenticatedConversation = Session.hasRemoteIdentityKey(this, masterSecret, primaryRecipient);
this.characterCalculator = new EncryptedCharacterCalculator();
this.isEncryptedConversation = true;
this.characterCalculator = new EncryptedCharacterCalculator();
if (isPushDestination) sendButton.setImageDrawable(drawables.getDrawable(0));
else sendButton.setImageDrawable(drawables.getDrawable(1));
} else {
this.isEncryptedConversation = false;
this.isAuthenticatedConversation = false;
this.characterCalculator = new CharacterCalculator();
this.isEncryptedConversation = false;
this.characterCalculator = new CharacterCalculator();
sendButton.setImageDrawable(drawables.getDrawable(2));
}

View File

@@ -34,6 +34,7 @@ import org.thoughtcrime.securesms.crypto.DecryptingQueue;
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessorV2;
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
import org.whispersystems.textsecure.crypto.LegacyMessageException;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.SendReceiveService;
@@ -96,6 +97,8 @@ public class ReceiveKeyActivity extends Activity {
Log.w("ReceiveKeyActivity", ive);
} catch (InvalidMessageException e) {
Log.w("ReceiveKeyActivity", e);
} catch (LegacyMessageException e) {
Log.w("ReceiveKeyActivity", e);
}
initializeListeners();
}
@@ -162,7 +165,8 @@ public class ReceiveKeyActivity extends Activity {
}
private void initializeKey()
throws InvalidKeyException, InvalidVersionException, InvalidMessageException
throws InvalidKeyException, InvalidVersionException,
InvalidMessageException, LegacyMessageException
{
try {
String messageBody = getIntent().getStringExtra("body");

View File

@@ -28,8 +28,6 @@ import org.thoughtcrime.securesms.util.DynamicTheme;
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;
/**
@@ -45,8 +43,6 @@ public class VerifyIdentityActivity extends KeyScanningActivity {
private TextView localIdentityFingerprint;
private TextView remoteIdentityFingerprint;
private int keyType;
private final DynamicTheme dynamicTheme = new DynamicTheme ();
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
@@ -78,12 +74,12 @@ public class VerifyIdentityActivity extends KeyScanningActivity {
}
private void initializeLocalIdentityKey() {
if (!IdentityKeyUtil.hasIdentityKey(this, keyType)) {
if (!IdentityKeyUtil.hasIdentityKey(this)) {
localIdentityFingerprint.setText(R.string.VerifyIdentityActivity_you_do_not_have_an_identity_key);
return;
}
localIdentityFingerprint.setText(IdentityKeyUtil.getIdentityKey(this, keyType).getFingerprint());
localIdentityFingerprint.setText(IdentityKeyUtil.getIdentityKey(this).getFingerprint());
}
private void initializeRemoteIdentityKey() {
@@ -110,19 +106,11 @@ public class VerifyIdentityActivity extends KeyScanningActivity {
this.remoteIdentityFingerprint = (TextView)findViewById(R.id.friend_reads);
this.recipient = this.getIntent().getParcelableExtra("recipient");
this.masterSecret = this.getIntent().getParcelableExtra("master_secret");
int sessionVersion = Session.getSessionVersion(this, masterSecret, recipient);
if (sessionVersion <= CiphertextMessage.LEGACY_VERSION) {
this.keyType = Curve.NIST_TYPE;
} else {
this.keyType = Curve.DJB_TYPE;
}
}
@Override
protected void initiateDisplay() {
if (!IdentityKeyUtil.hasIdentityKey(this, keyType)) {
if (!IdentityKeyUtil.hasIdentityKey(this)) {
Toast.makeText(this,
R.string.VerifyIdentityActivity_you_don_t_have_an_identity_key_exclamation,
Toast.LENGTH_LONG).show();
@@ -161,7 +149,7 @@ public class VerifyIdentityActivity extends KeyScanningActivity {
@Override
protected IdentityKey getIdentityKeyToDisplay() {
return IdentityKeyUtil.getIdentityKey(this, keyType);
return IdentityKeyUtil.getIdentityKey(this);
}
@Override

View File

@@ -27,11 +27,10 @@ import android.widget.Toast;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuInflater;
import com.actionbarsherlock.view.MenuItem;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.util.Dialogs;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.ecc.Curve;
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
/**
* Activity that displays the local identity key and offers the option to regenerate it.
@@ -45,7 +44,7 @@ public class ViewLocalIdentityActivity extends ViewIdentityActivity {
public void onCreate(Bundle bundle) {
this.masterSecret = getIntent().getParcelableExtra("master_secret");
getIntent().putExtra("identity_key", IdentityKeyUtil.getIdentityKey(this, Curve.DJB_TYPE));
getIntent().putExtra("identity_key", IdentityKeyUtil.getIdentityKey(this));
getIntent().putExtra("title", getString(R.string.ViewIdentityActivity_my_identity_fingerprint));
super.onCreate(bundle);
}
@@ -116,8 +115,7 @@ public class ViewLocalIdentityActivity extends ViewIdentityActivity {
Toast.LENGTH_LONG).show();
getIntent().putExtra("identity_key",
IdentityKeyUtil.getIdentityKey(ViewLocalIdentityActivity.this,
Curve.DJB_TYPE));
IdentityKeyUtil.getIdentityKey(ViewLocalIdentityActivity.this));
initialize();
}

View File

@@ -70,7 +70,7 @@ public class AsymmetricMasterCipher {
byte[][] parts = Util.split(combined, PublicKey.KEY_SIZE, combined.length - PublicKey.KEY_SIZE);
PublicKey theirPublicKey = new PublicKey(parts[0], 0);
ECPrivateKey ourPrivateKey = asymmetricMasterSecret.getPrivateKey(theirPublicKey.getType());
ECPrivateKey ourPrivateKey = asymmetricMasterSecret.getPrivateKey();
byte[] secret = Curve.calculateAgreement(theirPublicKey.getKey(), ourPrivateKey);
MasterCipher masterCipher = getMasterCipherForSecret(secret);
byte[] decryptedBody = masterCipher.decryptBytes(parts[1]);
@@ -85,15 +85,8 @@ public class AsymmetricMasterCipher {
public String encryptBody(String body) {
try {
ECPublicKey theirPublic;
if (asymmetricMasterSecret.getDjbPublicKey() != null) {
theirPublic = asymmetricMasterSecret.getDjbPublicKey();
} else {
theirPublic = asymmetricMasterSecret.getNistPublicKey();
}
ECKeyPair ourKeyPair = Curve.generateKeyPairForType(theirPublic.getType(), true);
ECPublicKey theirPublic = asymmetricMasterSecret.getDjbPublicKey();
ECKeyPair ourKeyPair = Curve.generateKeyPair(true);
byte[] secret = Curve.calculateAgreement(theirPublic, ourKeyPair.getPrivateKey());
MasterCipher masterCipher = getMasterCipherForSecret(secret);
byte[] encryptedBodyBytes = masterCipher.encryptBytes(body.getBytes());

View File

@@ -43,32 +43,20 @@ public class AsymmetricMasterSecret {
private final ECPublicKey djbPublicKey;
private final ECPrivateKey djbPrivateKey;
private final ECPublicKey nistPublicKey;
private final ECPrivateKey nistPrivateKey;
public AsymmetricMasterSecret(ECPublicKey djbPublicKey, ECPrivateKey djbPrivateKey,
ECPublicKey nistPublicKey, ECPrivateKey nistPrivateKey)
public AsymmetricMasterSecret(ECPublicKey djbPublicKey, ECPrivateKey djbPrivateKey)
{
this.djbPublicKey = djbPublicKey;
this.djbPrivateKey = djbPrivateKey;
this.nistPublicKey = nistPublicKey;
this.nistPrivateKey = nistPrivateKey;
}
public ECPublicKey getDjbPublicKey() {
return djbPublicKey;
}
public ECPublicKey getNistPublicKey() {
return nistPublicKey;
}
public ECPrivateKey getPrivateKey(int type) {
if (type == Curve.DJB_TYPE) {
return djbPrivateKey;
} else {
return nistPrivateKey;
}
public ECPrivateKey getPrivateKey() {
return djbPrivateKey;
}
}

View File

@@ -23,6 +23,7 @@ import android.database.Cursor;
import android.util.Log;
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
import org.whispersystems.textsecure.crypto.LegacyMessageException;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
import org.thoughtcrime.securesms.database.MmsDatabase;
@@ -217,6 +218,9 @@ public class DecryptingQueue {
} catch (DuplicateMessageException e) {
Log.w("DecryptingQueue", e);
sendResult(PushReceiver.RESULT_DECRYPT_DUPLICATE);
} catch (LegacyMessageException e) {
Log.w("DecryptionQueue", e);
sendResult(PushReceiver.RESULT_DECRYPT_FAILED);
}
}
@@ -319,6 +323,9 @@ public class DecryptingQueue {
} catch (DuplicateMessageException dme) {
Log.w("DecryptingQueue", dme);
database.markAsDecryptDuplicate(messageId, threadId);
} catch (LegacyMessageException lme) {
Log.w("DecryptingQueue", lme);
database.markAsLegacyVersion(messageId, threadId);
} catch (MmsException mme) {
Log.w("DecryptingQueue", mme);
database.markAsDecryptFailed(messageId, threadId);
@@ -390,6 +397,10 @@ public class DecryptingQueue {
Log.w("DecryptionQueue", e);
database.markAsDecryptFailed(messageId);
return;
} catch (LegacyMessageException lme) {
Log.w("DecryptionQueue", lme);
database.markAsLegacyVersion(messageId);
return;
} catch (RecipientFormattingException e) {
Log.w("DecryptionQueue", e);
database.markAsDecryptFailed(messageId);
@@ -457,6 +468,9 @@ public class DecryptingQueue {
} catch (RecipientFormattingException e) {
Log.w("DecryptingQueue", e);
DatabaseFactory.getEncryptingSmsDatabase(context).markAsCorruptKeyExchange(messageId);
} catch (LegacyMessageException e) {
Log.w("DecryptingQueue", e);
DatabaseFactory.getEncryptingSmsDatabase(context).markAsLegacyVersion(messageId);
}
}
}

View File

@@ -22,31 +22,17 @@ import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.util.Log;
import org.spongycastle.asn1.ASN1Encoding;
import org.spongycastle.asn1.ASN1Primitive;
import org.spongycastle.asn1.ASN1Sequence;
import org.spongycastle.asn1.DERInteger;
import org.spongycastle.asn1.DERSequence;
import org.spongycastle.crypto.signers.ECDSASigner;
import org.whispersystems.textsecure.crypto.IdentityKey;
import org.whispersystems.textsecure.crypto.IdentityKeyPair;
import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.MasterCipher;
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.ECKeyPair;
import org.whispersystems.textsecure.crypto.ecc.ECPrivateKey;
import org.whispersystems.textsecure.crypto.ecc.NistECPrivateKey;
import org.whispersystems.textsecure.crypto.ecc.NistECPublicKey;
import org.whispersystems.textsecure.util.Base64;
import org.whispersystems.textsecure.util.Conversions;
import org.whispersystems.textsecure.util.Util;
import java.io.IOException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* Utility class for working with identity keys.
@@ -56,39 +42,22 @@ import java.security.NoSuchAlgorithmException;
public class IdentityKeyUtil {
private static final String IDENTITY_PUBLIC_KEY_NIST_PREF = "pref_identity_public";
private static final String IDENTITY_PRIVATE_KEY_NIST_PREF = "pref_identity_private";
private static final String IDENTITY_PUBLIC_KEY_DJB_PREF = "pref_identity_public_curve25519";
private static final String IDENTITY_PRIVATE_KEY_DJB_PREF = "pref_identity_private_curve25519";
public static boolean hasIdentityKey(Context context, int type) {
public static boolean hasIdentityKey(Context context) {
SharedPreferences preferences = context.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0);
if (type == Curve.DJB_TYPE) {
return
preferences.contains(IDENTITY_PUBLIC_KEY_DJB_PREF) &&
preferences.contains(IDENTITY_PRIVATE_KEY_DJB_PREF);
} else if (type == Curve.NIST_TYPE) {
return
preferences.contains(IDENTITY_PUBLIC_KEY_NIST_PREF) &&
preferences.contains(IDENTITY_PRIVATE_KEY_NIST_PREF);
}
return false;
return
preferences.contains(IDENTITY_PUBLIC_KEY_DJB_PREF) &&
preferences.contains(IDENTITY_PRIVATE_KEY_DJB_PREF);
}
public static IdentityKey getIdentityKey(Context context, int type) {
if (!hasIdentityKey(context, type)) return null;
public static IdentityKey getIdentityKey(Context context) {
if (!hasIdentityKey(context)) return null;
try {
String key;
if (type == Curve.DJB_TYPE) key = IDENTITY_PUBLIC_KEY_DJB_PREF;
else if (type == Curve.NIST_TYPE) key = IDENTITY_PUBLIC_KEY_NIST_PREF;
else return null;
byte[] publicKeyBytes = Base64.decode(retrieve(context, key));
byte[] publicKeyBytes = Base64.decode(retrieve(context, IDENTITY_PUBLIC_KEY_DJB_PREF));
return new IdentityKey(publicKeyBytes, 0);
} catch (IOException ioe) {
Log.w("IdentityKeyUtil", ioe);
@@ -100,22 +69,15 @@ public class IdentityKeyUtil {
}
public static IdentityKeyPair getIdentityKeyPair(Context context,
MasterSecret masterSecret,
int type)
MasterSecret masterSecret)
{
if (!hasIdentityKey(context, type))
if (!hasIdentityKey(context))
return null;
try {
String key;
if (type == Curve.DJB_TYPE) key = IDENTITY_PRIVATE_KEY_DJB_PREF;
else if (type == Curve.NIST_TYPE) key = IDENTITY_PRIVATE_KEY_NIST_PREF;
else return null;
MasterCipher masterCipher = new MasterCipher(masterSecret);
IdentityKey publicKey = getIdentityKey(context, type);
ECPrivateKey privateKey = masterCipher.decryptKey(type, Base64.decode(retrieve(context, key)));
IdentityKey publicKey = getIdentityKey(context);
ECPrivateKey privateKey = masterCipher.decryptKey(Base64.decode(retrieve(context, IDENTITY_PRIVATE_KEY_DJB_PREF)));
return new IdentityKeyPair(publicKey, privateKey);
} catch (IOException e) {
@@ -124,31 +86,15 @@ public class IdentityKeyUtil {
throw new AssertionError(e);
}
}
public static String getFingerprint(Context context, int type) {
if (!hasIdentityKey(context, type)) return null;
IdentityKey identityKey = getIdentityKey(context, type);
if (identityKey == null) return null;
else return identityKey.getFingerprint();
}
public static void generateIdentityKeys(Context context, MasterSecret masterSecret) {
ECKeyPair nistKeyPair = Curve.generateKeyPairForType(Curve.NIST_TYPE, false);
ECKeyPair djbKeyPair = Curve.generateKeyPairForType(Curve.DJB_TYPE, false);
ECKeyPair djbKeyPair = Curve.generateKeyPair(false);
MasterCipher masterCipher = new MasterCipher(masterSecret);
IdentityKey nistIdentityKey = new IdentityKey(nistKeyPair.getPublicKey());
IdentityKey djbIdentityKey = new IdentityKey(djbKeyPair.getPublicKey());
MasterCipher masterCipher = new MasterCipher(masterSecret);
IdentityKey djbIdentityKey = new IdentityKey(djbKeyPair.getPublicKey());
byte[] djbPrivateKey = masterCipher.encryptKey(djbKeyPair.getPrivateKey());
byte[] nistPrivateKey = masterCipher.encryptKey(nistKeyPair.getPrivateKey());
byte[] djbPrivateKey = masterCipher.encryptKey(djbKeyPair.getPrivateKey());
save(context, IDENTITY_PUBLIC_KEY_NIST_PREF, Base64.encodeBytes(nistIdentityKey.serialize()));
save(context, IDENTITY_PUBLIC_KEY_DJB_PREF, Base64.encodeBytes(djbIdentityKey.serialize()));
save(context, IDENTITY_PRIVATE_KEY_NIST_PREF, Base64.encodeBytes(nistPrivateKey));
save(context, IDENTITY_PRIVATE_KEY_DJB_PREF, Base64.encodeBytes(djbPrivateKey));
}
@@ -160,94 +106,14 @@ public class IdentityKeyUtil {
public static void generateCurve25519IdentityKeys(Context context, MasterSecret masterSecret) {
MasterCipher masterCipher = new MasterCipher(masterSecret);
ECKeyPair djbKeyPair = Curve.generateKeyPairForType(Curve.DJB_TYPE, false);
ECKeyPair djbKeyPair = Curve.generateKeyPair(false);
IdentityKey djbIdentityKey = new IdentityKey(djbKeyPair.getPublicKey());
byte[] djbPrivateKey = masterCipher.encryptKey(djbKeyPair.getPrivateKey());
save(context, IDENTITY_PUBLIC_KEY_DJB_PREF, Base64.encodeBytes(djbIdentityKey.serialize()));
save(context, IDENTITY_PRIVATE_KEY_DJB_PREF, Base64.encodeBytes(djbPrivateKey));
}
public static IdentityKey verifySignedKeyExchange(byte[] keyExchangeBytes)
throws InvalidKeyException
{
try {
byte[] messageBytes = new byte[1 + PublicKey.KEY_SIZE];
System.arraycopy(keyExchangeBytes, 0, messageBytes, 0, messageBytes.length);
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);
byte[] signatureBytes = new byte[signatureLength];
System.arraycopy(keyExchangeBytes, messageBytes.length + publicKeyBytes.length + 2, signatureBytes, 0, signatureBytes.length);
byte[] messageHash = getMessageHash(messageBytes, publicKeyBytes);
IdentityKey identityKey = new IdentityKey(publicKeyBytes, 0);
ECDSASigner verifier = new ECDSASigner();
if (identityKey.getPublicKey().getType() != Curve.NIST_TYPE) {
throw new InvalidKeyException("Signing only support on P256 keys!");
}
verifier.init(false, ((NistECPublicKey)identityKey.getPublicKey()).getParameters());
ASN1Sequence sequence = (ASN1Sequence) ASN1Primitive.fromByteArray(signatureBytes);
BigInteger[] signatureIntegers = new BigInteger[]{
((DERInteger)sequence.getObjectAt(0)).getValue(),
((DERInteger)sequence.getObjectAt(1)).getValue()
};
if (!verifier.verifySignature(messageHash, signatureIntegers[0], signatureIntegers[1]))
throw new InvalidKeyException("Invalid signature!");
else
return identityKey;
} catch (IOException ioe) {
throw new InvalidKeyException(ioe);
}
}
public static byte[] getSignedKeyExchange(Context context, MasterSecret masterSecret,
byte[] keyExchangeBytes)
{
try {
MasterCipher masterCipher = new MasterCipher(masterSecret);
byte[] publicKeyBytes = getIdentityKey(context, Curve.NIST_TYPE).serialize();
byte[] messageHash = getMessageHash(keyExchangeBytes, publicKeyBytes);
byte[] privateKeyBytes = Base64.decode(retrieve(context, IDENTITY_PRIVATE_KEY_NIST_PREF));
ECPrivateKey privateKey = masterCipher.decryptKey(Curve.NIST_TYPE, privateKeyBytes);
ECDSASigner signer = new ECDSASigner();
signer.init(true, ((NistECPrivateKey)privateKey).getParameters());
BigInteger[] messageSignatureInts = signer.generateSignature(messageHash);
DERInteger[] derMessageSignatureInts = new DERInteger[]{ new DERInteger(messageSignatureInts[0]), new DERInteger(messageSignatureInts[1]) };
byte[] messageSignatureBytes = new DERSequence(derMessageSignatureInts).getEncoded(ASN1Encoding.DER);
byte[] messageSignature = new byte[2 + messageSignatureBytes.length];
Conversions.shortToByteArray(messageSignature, 0, messageSignatureBytes.length);
System.arraycopy(messageSignatureBytes, 0, messageSignature, 2, messageSignatureBytes.length);
return Util.combine(keyExchangeBytes, publicKeyBytes, messageSignature);
} catch (IOException ioe) {
throw new AssertionError(ioe);
} catch (InvalidKeyException e) {
throw new AssertionError(e);
}
}
private static byte[] getMessageHash(byte[] messageBytes, byte[] publicKeyBytes) {
try {
MessageDigest md = MessageDigest.getInstance("SHA1");
md.update(messageBytes);
return md.digest(publicKeyBytes);
} catch (NoSuchAlgorithmException nsae) {
throw new AssertionError(nsae);
}
}
public static String retrieve(Context context, String key) {
SharedPreferences preferences = context.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0);
return preferences.getString(key, null);

View File

@@ -31,7 +31,6 @@ 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.RecipientDevice;
import org.whispersystems.textsecure.storage.SessionRecordV2;
@@ -62,9 +61,9 @@ public class KeyExchangeInitiator {
private static void initiateKeyExchange(Context context, MasterSecret masterSecret, Recipient recipient) {
int sequence = getRandomSequence();
int flags = KeyExchangeMessageV2.INITIATE_FLAG;
ECKeyPair baseKey = Curve.generateKeyPairForSession(CiphertextMessage.CURRENT_VERSION, true);
ECKeyPair ephemeralKey = Curve.generateKeyPairForSession(CiphertextMessage.CURRENT_VERSION, true);
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, Curve.DJB_TYPE);
ECKeyPair baseKey = Curve.generateKeyPair(true);
ECKeyPair ephemeralKey = Curve.generateKeyPair(true);
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
KeyExchangeMessageV2 message = new KeyExchangeMessageV2(sequence, flags,
baseKey.getPublicKey(),

View File

@@ -41,11 +41,7 @@ public abstract class KeyExchangeProcessor {
RecipientDevice recipientDevice,
KeyExchangeMessage message)
{
if (message.isLegacy()) {
return new KeyExchangeProcessorV1(context, masterSecret, recipientDevice.getRecipient());
} else {
return new KeyExchangeProcessorV2(context, masterSecret, recipientDevice);
}
return new KeyExchangeProcessorV2(context, masterSecret, recipientDevice);
}
public static void broadcastSecurityUpdateEvent(Context context, long threadId) {

View File

@@ -1,153 +0,0 @@
package org.thoughtcrime.securesms.crypto;
import android.content.Context;
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.recipients.RecipientFactory;
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.CanonicalRecipient;
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 CanonicalRecipient recipient;
private MasterSecret masterSecret;
private LocalKeyRecord localKeyRecord;
private RemoteKeyRecord remoteKeyRecord;
private SessionRecordV1 sessionRecord;
public KeyExchangeProcessorV1(Context context, MasterSecret masterSecret, CanonicalRecipient 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.getRecipientId(),
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());
Recipient recipient = RecipientFactory.getRecipientsForIds(context,
this.recipient.getRecipientId()+"",
true).getPrimaryRecipient();
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.getRecipientId(), message.getIdentityKey());
}
DecryptingQueue.scheduleRogueMessages(context, masterSecret, recipient);
broadcastSecurityUpdateEvent(context, threadId);
}
public LocalKeyRecord initializeRecordFor(Context context,
MasterSecret masterSecret,
CanonicalRecipient 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, true), masterSecret);
KeyPair nextPair = new KeyPair(initialId + 1, Curve.generateKeyPairForSession(1, true), 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);
}
}
}

View File

@@ -99,7 +99,7 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
PreKeyRecord preKeyRecord = new PreKeyRecord(context, masterSecret, preKeyId);
ECKeyPair ourBaseKey = preKeyRecord.getKeyPair();
ECKeyPair ourEphemeralKey = ourBaseKey;
IdentityKeyPair ourIdentityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, ourBaseKey.getPublicKey().getType());
IdentityKeyPair ourIdentityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
boolean simultaneousInitiate = sessionRecord.getSessionState().hasPendingPreKey();
if (!simultaneousInitiate) sessionRecord.clear();
@@ -131,14 +131,12 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
public void processKeyExchangeMessage(PreKeyEntity message, long threadId)
throws InvalidKeyException
{
ECKeyPair ourBaseKey = Curve.generateKeyPairForSession(2, true);
ECKeyPair ourEphemeralKey = Curve.generateKeyPairForSession(2, true);
ECKeyPair ourBaseKey = Curve.generateKeyPair(true);
ECKeyPair ourEphemeralKey = Curve.generateKeyPair(true);
ECPublicKey theirBaseKey = message.getPublicKey();
ECPublicKey theirEphemeralKey = theirBaseKey;
IdentityKey theirIdentityKey = message.getIdentityKey();
IdentityKeyPair ourIdentityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret,
ourBaseKey.getPublicKey()
.getType());
IdentityKeyPair ourIdentityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
if (sessionRecord.getSessionState().getNeedsRefresh()) sessionRecord.archiveCurrentState();
else sessionRecord.clear();
@@ -184,9 +182,9 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
if (!sessionRecord.getSessionState().hasPendingKeyExchange()) {
Log.w("KeyExchangeProcessorV2", "We don't have a pending initiate...");
ourBaseKey = Curve.generateKeyPairForType(message.getBaseKey().getType(), true);
ourEphemeralKey = Curve.generateKeyPairForType(message.getBaseKey().getType(), true);
ourIdentityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, message.getBaseKey().getType());
ourBaseKey = Curve.generateKeyPair(true);
ourEphemeralKey = Curve.generateKeyPair(true);
ourIdentityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
sessionRecord.getSessionState().setPendingKeyExchange(message.getSequence(), ourBaseKey,
ourEphemeralKey, ourIdentityKey);

View File

@@ -25,7 +25,6 @@ import android.util.Log;
import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.MasterCipher;
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.ECKeyPair;
import org.whispersystems.textsecure.crypto.ecc.ECPrivateKey;
@@ -59,8 +58,6 @@ public class MasterSecretUtil {
public static final String UNENCRYPTED_PASSPHRASE = "unencrypted";
public static final String PREFERENCES_NAME = "SecureSMS-Preferences";
private static final String ASYMMETRIC_LOCAL_PUBLIC_NIST = "asymmetric_master_secret_public";
private static final String ASYMMETRIC_LOCAL_PRIVATE_NIST = "asymmetric_master_secret_private";
private static final String ASYMMETRIC_LOCAL_PUBLIC_DJB = "asymmetric_master_secret_curve25519_public";
private static final String ASYMMETRIC_LOCAL_PRIVATE_DJB = "asymmetric_master_secret_curve25519_private";
@@ -116,22 +113,12 @@ public class MasterSecretUtil {
MasterSecret masterSecret)
{
try {
byte[] nistPublicBytes = retrieve(context, ASYMMETRIC_LOCAL_PUBLIC_NIST);
byte[] djbPublicBytes = retrieve(context, ASYMMETRIC_LOCAL_PUBLIC_DJB);
byte[] nistPrivateBytes = retrieve(context, ASYMMETRIC_LOCAL_PRIVATE_NIST);
byte[] djbPrivateBytes = retrieve(context, ASYMMETRIC_LOCAL_PRIVATE_DJB);
ECPublicKey nistPublicKey = null;
ECPublicKey djbPublicKey = null;
ECPrivateKey nistPrivateKey = null;
ECPrivateKey djbPrivateKey = null;
if (nistPublicBytes != null) {
nistPublicKey = new PublicKey(nistPublicBytes, 0).getKey();
}
if (djbPublicBytes != null) {
djbPublicKey = Curve.decodePoint(djbPublicBytes, 0);
}
@@ -139,16 +126,12 @@ public class MasterSecretUtil {
if (masterSecret != null) {
MasterCipher masterCipher = new MasterCipher(masterSecret);
if (nistPrivateBytes != null) {
nistPrivateKey = masterCipher.decryptKey(Curve.NIST_TYPE, nistPrivateBytes);
}
if (djbPrivateBytes != null) {
djbPrivateKey = masterCipher.decryptKey(Curve.DJB_TYPE, djbPrivateBytes);
djbPrivateKey = masterCipher.decryptKey(djbPrivateBytes);
}
}
return new AsymmetricMasterSecret(djbPublicKey, djbPrivateKey, nistPublicKey, nistPrivateKey);
return new AsymmetricMasterSecret(djbPublicKey, djbPrivateKey);
} catch (InvalidKeyException ike) {
throw new AssertionError(ike);
} catch (IOException e) {
@@ -160,12 +143,12 @@ public class MasterSecretUtil {
MasterSecret masterSecret)
{
MasterCipher masterCipher = new MasterCipher(masterSecret);
ECKeyPair keyPair = Curve.generateKeyPairForType(Curve.DJB_TYPE, true);
ECKeyPair keyPair = Curve.generateKeyPair(true);
save(context, ASYMMETRIC_LOCAL_PUBLIC_DJB, keyPair.getPublicKey().serialize());
save(context, ASYMMETRIC_LOCAL_PRIVATE_DJB, masterCipher.encryptKey(keyPair.getPrivateKey()));
return new AsymmetricMasterSecret(keyPair.getPublicKey(), keyPair.getPrivateKey(), null, null);
return new AsymmetricMasterSecret(keyPair.getPublicKey(), keyPair.getPrivateKey());
}
public static MasterSecret generateMasterSecret(Context context, String passphrase) {
@@ -187,9 +170,7 @@ public class MasterSecretUtil {
public static boolean hasAsymmericMasterSecret(Context context) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCES_NAME, 0);
return
settings.contains(ASYMMETRIC_LOCAL_PUBLIC_NIST) ||
settings.contains(ASYMMETRIC_LOCAL_PUBLIC_DJB);
return settings.contains(ASYMMETRIC_LOCAL_PUBLIC_DJB);
}
public static boolean isPassphraseInitialized(Context context) {

View File

@@ -1,39 +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.crypto;
import org.whispersystems.textsecure.crypto.IdentityKey;
import org.whispersystems.textsecure.crypto.PublicKey;
public class PublicKeyAndVersion {
public IdentityKey identityKey;
public final PublicKey key;
public final int version;
public final int maxVersion;
public PublicKeyAndVersion(PublicKey key, int version, int maxVersion) {
this.key = key;
this.version = version;
this.maxVersion = maxVersion;
}
public PublicKeyAndVersion(PublicKey key, IdentityKey identityKey, int version, int maxVersion) {
this(key, version, maxVersion);
this.identityKey = identityKey;
}
}

View File

@@ -21,35 +21,18 @@ 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.PublicKey;
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
import org.whispersystems.textsecure.util.Base64;
import org.whispersystems.textsecure.util.Conversions;
import java.io.IOException;
import org.whispersystems.textsecure.crypto.LegacyMessageException;
public abstract class KeyExchangeMessage {
public abstract boolean isLegacy();
public abstract IdentityKey getIdentityKey();
public abstract boolean hasIdentityKey();
public abstract int getMaxVersion();
public abstract int getVersion();
public static KeyExchangeMessage createFor(String rawMessage)
throws InvalidMessageException, InvalidKeyException, InvalidVersionException
throws InvalidMessageException, InvalidKeyException, InvalidVersionException, LegacyMessageException
{
try {
byte[] decodedMessage = Base64.decodeWithoutPadding(rawMessage);
if (Conversions.highBitsToInt(decodedMessage[0]) <= CiphertextMessage.LEGACY_VERSION) {
return new KeyExchangeMessageV1(rawMessage);
} else {
return new KeyExchangeMessageV2(rawMessage);
}
} catch (IOException e) {
throw new InvalidMessageException(e);
}
return new KeyExchangeMessageV2(rawMessage);
}
}

View File

@@ -1,160 +0,0 @@
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;
}
}

View File

@@ -7,6 +7,7 @@ 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.LegacyMessageException;
import org.whispersystems.textsecure.crypto.ecc.Curve;
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
@@ -57,7 +58,7 @@ public class KeyExchangeMessageV2 extends KeyExchangeMessage {
}
public KeyExchangeMessageV2(String serializedAndEncoded)
throws InvalidMessageException, InvalidVersionException
throws InvalidMessageException, InvalidVersionException, LegacyMessageException
{
try {
byte[] serialized = Base64.decodeWithoutPadding(serializedAndEncoded);
@@ -66,6 +67,10 @@ public class KeyExchangeMessageV2 extends KeyExchangeMessage {
this.version = Conversions.highBitsToInt(parts[0][0]);
this.supportedVersion = Conversions.lowBitsToInt(parts[0][0]);
if (this.version <= CiphertextMessage.UNSUPPORTED_VERSION) {
throw new LegacyMessageException("Unsupported legacy version: " + this.version);
}
if (this.version > CiphertextMessage.CURRENT_VERSION) {
throw new InvalidVersionException("Unknown version: " + this.version);
}
@@ -106,11 +111,6 @@ public class KeyExchangeMessageV2 extends KeyExchangeMessage {
return ephemeralKey;
}
@Override
public boolean isLegacy() {
return false;
}
@Override
public IdentityKey getIdentityKey() {
return identityKey;

View File

@@ -89,12 +89,6 @@ public class IdentityDatabase extends Database {
IdentityKey ourIdentity = new IdentityKey(Base64.decode(serializedIdentity), 0);
if (theirIdentity.getPublicKey().getType() == Curve.DJB_TYPE &&
ourIdentity.getPublicKey().getType() == Curve.NIST_TYPE)
{
return true;
}
return ourIdentity.equals(theirIdentity);
} else {
return true;

View File

@@ -362,6 +362,11 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
notifyConversationListeners(threadId);
}
public void markAsLegacyVersion(long messageId, long threadId) {
updateMailboxBitmask(messageId, 0, Types.LEGACY_MESSAGE_BIT);
notifyConversationListeners(threadId);
}
public void setMessagesRead(long threadId) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
ContentValues contentValues = new ContentValues();

View File

@@ -47,6 +47,7 @@ public interface MmsSmsColumns {
protected static final long SECURE_MESSAGE_BIT = 0x800000;
protected static final long END_SESSION_BIT = 0x400000;
protected static final long PUSH_MESSAGE_BIT = 0x200000;
protected static final long LEGACY_MESSAGE_BIT = 0x100000;
// Group Message Information
protected static final long GROUP_UPDATE_BIT = 0x10000;

View File

@@ -170,6 +170,10 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
updateTypeBitmask(id, 0, Types.KEY_EXCHANGE_INVALID_VERSION_BIT);
}
public void markAsLegacyVersion(long id) {
updateTypeBitmask(id, 0, Types.LEGACY_MESSAGE_BIT);
}
public void markAsSecure(long id) {
updateTypeBitmask(id, 0, Types.SECURE_MESSAGE_BIT);
}
@@ -275,8 +279,9 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
else if (((IncomingKeyExchangeMessage)message).isProcessed()) type |= Types.KEY_EXCHANGE_PROCESSED_BIT;
else if (((IncomingKeyExchangeMessage)message).isCorrupted()) type |= Types.KEY_EXCHANGE_CORRUPTED_BIT;
else if (((IncomingKeyExchangeMessage)message).isInvalidVersion()) type |= Types.KEY_EXCHANGE_INVALID_VERSION_BIT;
else if (((IncomingKeyExchangeMessage)message).isPreKeyBundle()) type |= Types.KEY_EXCHANGE_BUNDLE_BIT;
else if (((IncomingKeyExchangeMessage)message).isIdentityUpdate()) type |= Types.KEY_EXCHANGE_IDENTITY_UPDATE_BIT;
else if (((IncomingKeyExchangeMessage)message).isLegacyVersion()) type |= Types.LEGACY_MESSAGE_BIT;
else if (((IncomingKeyExchangeMessage)message).isPreKeyBundle()) type |= Types.KEY_EXCHANGE_BUNDLE_BIT;
} else if (message.isSecureMessage()) {
type |= Types.SECURE_MESSAGE_BIT;
type |= Types.ENCRYPTION_REMOTE_BIT;

View File

@@ -76,7 +76,7 @@ public class PreKeyService extends Service {
List<PreKeyRecord> preKeyRecords = PreKeyUtil.generatePreKeys(context, masterSecret);
PreKeyRecord lastResortKeyRecord = PreKeyUtil.generateLastResortKey(context, masterSecret);
IdentityKey identityKey = IdentityKeyUtil.getIdentityKey(context, Curve.DJB_TYPE);
IdentityKey identityKey = IdentityKeyUtil.getIdentityKey(context);
Log.w(TAG, "Registering new prekeys...");

View File

@@ -261,7 +261,7 @@ public class RegistrationService extends Service {
throws GcmRegistrationTimeoutException, IOException
{
setState(new RegistrationState(RegistrationState.STATE_GENERATING_KEYS, number));
IdentityKey identityKey = IdentityKeyUtil.getIdentityKey(this, Curve.DJB_TYPE);
IdentityKey identityKey = IdentityKeyUtil.getIdentityKey(this);
List<PreKeyRecord> records = PreKeyUtil.generatePreKeys(this, masterSecret);
PreKeyRecord lastResort = PreKeyUtil.generateLastResortKey(this, masterSecret);
socket.registerPreKeys(identityKey, lastResort, records);

View File

@@ -26,6 +26,7 @@ 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.whispersystems.textsecure.crypto.LegacyMessageException;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase;
@@ -188,6 +189,9 @@ public class SmsReceiver {
} catch (RecipientFormattingException e) {
Log.w("SmsReceiver", e);
message.setCorrupted(true);
} catch (LegacyMessageException e) {
Log.w("SmsReceiver", e);
message.setLegacyVersion(true);
}
}

View File

@@ -6,6 +6,7 @@ public class IncomingKeyExchangeMessage extends IncomingTextMessage {
private boolean isProcessed;
private boolean isCorrupted;
private boolean isInvalidVersion;
private boolean isLegacyVersion;
public IncomingKeyExchangeMessage(IncomingTextMessage base, String newBody) {
super(base, newBody);
@@ -15,6 +16,7 @@ public class IncomingKeyExchangeMessage extends IncomingTextMessage {
this.isProcessed = ((IncomingKeyExchangeMessage)base).isProcessed;
this.isCorrupted = ((IncomingKeyExchangeMessage)base).isCorrupted;
this.isInvalidVersion = ((IncomingKeyExchangeMessage)base).isInvalidVersion;
this.isLegacyVersion = ((IncomingKeyExchangeMessage)base).isLegacyVersion;
}
}
@@ -23,6 +25,10 @@ public class IncomingKeyExchangeMessage extends IncomingTextMessage {
return new IncomingKeyExchangeMessage(this, messageBody);
}
public boolean isIdentityUpdate() {
return false;
}
public boolean isStale() {
return isStale;
}
@@ -55,6 +61,14 @@ public class IncomingKeyExchangeMessage extends IncomingTextMessage {
this.isInvalidVersion = isInvalidVersion;
}
public boolean isLegacyVersion() {
return isLegacyVersion;
}
public void setLegacyVersion(boolean isLegacyVersion) {
this.isLegacyVersion = isLegacyVersion;
}
@Override
public boolean isKeyExchange() {
return true;

View File

@@ -179,10 +179,6 @@ public class IncomingTextMessage implements Parcelable {
return false;
}
public boolean isIdentityUpdate() {
return false;
}
public boolean isPush() {
return push;
}

View File

@@ -352,9 +352,9 @@ public class PushTransport extends BaseTransport {
CiphertextMessage message = cipher.encrypt(plaintext);
int remoteRegistrationId = cipher.getRemoteRegistrationId();
if (message.getType() == CiphertextMessage.PREKEY_WHISPER_TYPE) {
if (message.getType() == CiphertextMessage.PREKEY_TYPE) {
return new PushBody(IncomingPushMessageSignal.Type.PREKEY_BUNDLE_VALUE, remoteRegistrationId, message.serialize());
} else if (message.getType() == CiphertextMessage.CURRENT_WHISPER_TYPE) {
} else if (message.getType() == CiphertextMessage.WHISPER_TYPE) {
return new PushBody(IncomingPushMessageSignal.Type.CIPHERTEXT_VALUE, remoteRegistrationId, message.serialize());
} else {
throw new AssertionError("Unknown ciphertext type: " + message.getType());

View File

@@ -183,7 +183,7 @@ public class SmsTransport extends BaseTransport {
CiphertextMessage ciphertextMessage = sessionCipher.encrypt(paddedPlaintext);
String encodedCiphertext = new String(transportDetails.getEncodedMessage(ciphertextMessage.serialize()));
if (ciphertextMessage.getType() == CiphertextMessage.PREKEY_WHISPER_TYPE) {
if (ciphertextMessage.getType() == CiphertextMessage.PREKEY_TYPE) {
message = new OutgoingPrekeyBundleMessage(message, encodedCiphertext);
} else {
message = message.withBody(encodedCiphertext);