diff --git a/library/src/org/whispersystems/textsecure/crypto/IdentityKey.java b/library/src/org/whispersystems/textsecure/crypto/IdentityKey.java index 26898a2a94..875bd3bfb5 100644 --- a/library/src/org/whispersystems/textsecure/crypto/IdentityKey.java +++ b/library/src/org/whispersystems/textsecure/crypto/IdentityKey.java @@ -23,7 +23,6 @@ import android.os.Parcelable; import org.whispersystems.textsecure.crypto.ecc.Curve; import org.whispersystems.textsecure.crypto.ecc.ECPublicKey; import org.whispersystems.textsecure.util.Hex; -import org.whispersystems.textsecure.util.Util; /** * A class for representing an identity key. @@ -80,14 +79,7 @@ public class IdentityKey implements Parcelable, SerializableKey { } public byte[] serialize() { - if (publicKey.getType() == Curve.NIST_TYPE) { - byte[] versionBytes = {0x01}; - byte[] encodedKey = publicKey.serialize(); - - return Util.combine(versionBytes, encodedKey); - } else { - return publicKey.serialize(); - } + return publicKey.serialize(); } public String getFingerprint() { diff --git a/library/src/org/whispersystems/textsecure/crypto/KeyPair.java b/library/src/org/whispersystems/textsecure/crypto/KeyPair.java deleted file mode 100644 index fb8c1d748e..0000000000 --- a/library/src/org/whispersystems/textsecure/crypto/KeyPair.java +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Copyright (C) 2011 Whisper Systems - * Copyright (C) 2013 Open 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 . - */ -package org.whispersystems.textsecure.crypto; - -import android.util.Log; - -import org.whispersystems.textsecure.crypto.ecc.ECKeyPair; -import org.whispersystems.textsecure.crypto.ecc.ECPrivateKey; -import org.whispersystems.textsecure.util.Hex; -import org.whispersystems.textsecure.util.Util; - -/** - * Represents a session's active KeyPair. - * - * @author Moxie Marlinspike - */ - -public class KeyPair { - - private PublicKey publicKey; - private ECPrivateKey privateKey; - - private final MasterCipher masterCipher; - - public KeyPair(int keyPairId, ECKeyPair keyPair, MasterSecret masterSecret) { - this.masterCipher = new MasterCipher(masterSecret); - this.publicKey = new PublicKey(keyPairId, keyPair.getPublicKey()); - this.privateKey = keyPair.getPrivateKey(); - } - - public KeyPair(byte[] bytes, MasterCipher masterCipher) throws InvalidKeyException { - this.masterCipher = masterCipher; - deserialize(bytes); - } - - public int getId() { - return publicKey.getId(); - } - - public PublicKey getPublicKey() { - return publicKey; - } - - public ECPrivateKey getPrivateKey() { - return privateKey; - } - - public byte[] toBytes() { - return serialize(); - } - - private void deserialize(byte[] bytes) throws InvalidKeyException { - this.publicKey = new PublicKey(bytes); - byte[] privateKeyBytes = new byte[bytes.length - PublicKey.KEY_SIZE]; - System.arraycopy(bytes, PublicKey.KEY_SIZE, privateKeyBytes, 0, privateKeyBytes.length); - this.privateKey = masterCipher.decryptKey(this.publicKey.getType(), privateKeyBytes); - } - - public byte[] serialize() { - byte[] publicKeyBytes = publicKey.serialize(); - Log.w("KeyPair", "Serialized public key bytes: " + Hex.toString(publicKeyBytes)); - byte[] privateKeyBytes = masterCipher.encryptKey(privateKey); - return Util.combine(publicKeyBytes, privateKeyBytes); - } - -} diff --git a/library/src/org/whispersystems/textsecure/crypto/LegacyMessageException.java b/library/src/org/whispersystems/textsecure/crypto/LegacyMessageException.java new file mode 100644 index 0000000000..c396e04d17 --- /dev/null +++ b/library/src/org/whispersystems/textsecure/crypto/LegacyMessageException.java @@ -0,0 +1,7 @@ +package org.whispersystems.textsecure.crypto; + +public class LegacyMessageException extends Exception { + public LegacyMessageException(String s) { + super(s); + } +} diff --git a/library/src/org/whispersystems/textsecure/crypto/MasterCipher.java b/library/src/org/whispersystems/textsecure/crypto/MasterCipher.java index 358563b9f8..78b3918e58 100644 --- a/library/src/org/whispersystems/textsecure/crypto/MasterCipher.java +++ b/library/src/org/whispersystems/textsecure/crypto/MasterCipher.java @@ -83,11 +83,11 @@ public class MasterCipher { return new String(decodeAndDecryptBytes(body)); } - public ECPrivateKey decryptKey(int type, byte[] key) + public ECPrivateKey decryptKey(byte[] key) throws org.whispersystems.textsecure.crypto.InvalidKeyException { try { - return Curve.decodePrivatePoint(type, decryptBytes(key)); + return Curve.decodePrivatePoint(decryptBytes(key)); } catch (InvalidMessageException ime) { throw new org.whispersystems.textsecure.crypto.InvalidKeyException(ime); } diff --git a/library/src/org/whispersystems/textsecure/crypto/SessionCipher.java b/library/src/org/whispersystems/textsecure/crypto/SessionCipher.java index 6273f86704..3f1edb34ce 100644 --- a/library/src/org/whispersystems/textsecure/crypto/SessionCipher.java +++ b/library/src/org/whispersystems/textsecure/crypto/SessionCipher.java @@ -29,7 +29,7 @@ public abstract class SessionCipher { protected static final Object SESSION_LOCK = new Object(); public abstract CiphertextMessage encrypt(byte[] paddedMessage); - public abstract byte[] decrypt(byte[] decodedMessage) throws InvalidMessageException, DuplicateMessageException; + public abstract byte[] decrypt(byte[] decodedMessage) throws InvalidMessageException, DuplicateMessageException, LegacyMessageException; public abstract int getRemoteRegistrationId(); public static SessionCipher createFor(Context context, @@ -38,8 +38,6 @@ public abstract class SessionCipher { { if (SessionRecordV2.hasSession(context, masterSecret, recipient)) { return new SessionCipherV2(context, masterSecret, recipient); - } else if (SessionRecordV1.hasSession(context, recipient.getRecipientId())) { - return new SessionCipherV1(context, masterSecret, recipient.getRecipient()); } else { throw new AssertionError("Attempt to initialize cipher for non-existing session."); } diff --git a/library/src/org/whispersystems/textsecure/crypto/SessionCipherV1.java b/library/src/org/whispersystems/textsecure/crypto/SessionCipherV1.java deleted file mode 100644 index 23cf301a40..0000000000 --- a/library/src/org/whispersystems/textsecure/crypto/SessionCipherV1.java +++ /dev/null @@ -1,332 +0,0 @@ -package org.whispersystems.textsecure.crypto; - -import android.content.Context; -import android.util.Log; - -import org.whispersystems.textsecure.crypto.ecc.Curve; -import org.whispersystems.textsecure.crypto.ecc.ECPublicKey; -import org.whispersystems.textsecure.crypto.kdf.DerivedSecrets; -import org.whispersystems.textsecure.crypto.kdf.NKDF; -import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; -import org.whispersystems.textsecure.crypto.protocol.WhisperMessageV1; -import org.whispersystems.textsecure.storage.CanonicalRecipient; -import org.whispersystems.textsecure.storage.RecipientDevice; -import org.whispersystems.textsecure.storage.InvalidKeyIdException; -import org.whispersystems.textsecure.storage.LocalKeyRecord; -import org.whispersystems.textsecure.storage.RemoteKeyRecord; -import org.whispersystems.textsecure.storage.SessionKey; -import org.whispersystems.textsecure.storage.SessionRecordV1; -import org.whispersystems.textsecure.util.Conversions; - -import java.security.InvalidAlgorithmParameterException; -import java.security.NoSuchAlgorithmException; - -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; - -public class SessionCipherV1 extends SessionCipher { - - private final Context context; - private final MasterSecret masterSecret; - private final CanonicalRecipient recipient; - - public SessionCipherV1(Context context, - MasterSecret masterSecret, - CanonicalRecipient recipient) - { - this.context = context; - this.masterSecret = masterSecret; - this.recipient = recipient; - } - - public CiphertextMessage encrypt(byte[] paddedMessageBody) { - synchronized (SESSION_LOCK) { - SessionCipherContext encryptionContext = getEncryptionContext(); - byte[] cipherText = getCiphertext(paddedMessageBody, - encryptionContext.getSessionKey().getCipherKey(), - encryptionContext.getSessionRecord().getCounter()); - - encryptionContext.getSessionRecord().setSessionKey(encryptionContext.getSessionKey()); - encryptionContext.getSessionRecord().incrementCounter(); - encryptionContext.getSessionRecord().save(); - - return new WhisperMessageV1(encryptionContext, cipherText); - } - } - - public byte[] decrypt(byte[] decodedCiphertext) throws InvalidMessageException { - synchronized (SESSION_LOCK) { - WhisperMessageV1 message = new WhisperMessageV1(decodedCiphertext); - SessionCipherContext decryptionContext = getDecryptionContext(message); - - message.verifyMac(decryptionContext); - - byte[] plaintextWithPadding = getPlaintext(message.getBody(), - decryptionContext.getSessionKey().getCipherKey(), - decryptionContext.getCounter()); - - decryptionContext.getRemoteKeyRecord().updateCurrentRemoteKey(decryptionContext.getNextKey()); - decryptionContext.getRemoteKeyRecord().save(); - - decryptionContext.getLocalKeyRecord().advanceKeyIfNecessary(decryptionContext.getRecipientKeyId()); - decryptionContext.getLocalKeyRecord().save(); - - decryptionContext.getSessionRecord().setSessionKey(decryptionContext.getSessionKey()); - decryptionContext.getSessionRecord().save(); - - return plaintextWithPadding; - } - } - - @Override - public int getRemoteRegistrationId() { - return 0; - } - - private SessionCipherContext getEncryptionContext() { - try { - KeyRecords records = getKeyRecords(context, masterSecret, recipient); - int localKeyId = records.getLocalKeyRecord().getCurrentKeyPair().getId(); - int remoteKeyId = records.getRemoteKeyRecord().getCurrentRemoteKey().getId(); - int sessionVersion = records.getSessionRecord().getSessionVersion(); - SessionKey sessionKey = getSessionKey(masterSecret, Cipher.ENCRYPT_MODE, - records, localKeyId, remoteKeyId); - PublicKey nextKey = records.getLocalKeyRecord().getNextKeyPair().getPublicKey(); - int counter = records.getSessionRecord().getCounter(); - - - return new SessionCipherContext(records, sessionKey, localKeyId, remoteKeyId, - nextKey, counter, sessionVersion); - } catch (InvalidKeyIdException e) { - throw new IllegalArgumentException(e); - } catch (InvalidKeyException e) { - throw new IllegalArgumentException(e); - } - } - - public SessionCipherContext getDecryptionContext(WhisperMessageV1 message) - throws InvalidMessageException - { - try { - KeyRecords records = getKeyRecords(context, masterSecret, recipient); - int messageVersion = message.getCurrentVersion(); - int recipientKeyId = message.getReceiverKeyId(); - int senderKeyId = message.getSenderKeyId(); - PublicKey nextKey = new PublicKey(message.getNextKeyBytes()); - int counter = message.getCounter(); - - if (messageVersion < records.getSessionRecord().getSessionVersion()) { - throw new InvalidMessageException("Message version: " + messageVersion + - " but negotiated session version: " + - records.getSessionRecord().getSessionVersion()); - } - - SessionKey sessionKey = getSessionKey(masterSecret, Cipher.DECRYPT_MODE, - records, recipientKeyId, senderKeyId); - - return new SessionCipherContext(records, sessionKey, senderKeyId, - recipientKeyId, nextKey, counter, - messageVersion); - } catch (InvalidKeyIdException e) { - throw new InvalidMessageException(e); - } catch (InvalidKeyException e) { - throw new InvalidMessageException(e); - } - } - - private byte[] getCiphertext(byte[] message, SecretKeySpec key, int counter) { - try { - Cipher cipher = getCipher(Cipher.ENCRYPT_MODE, key, counter); - return cipher.doFinal(message); - } catch (IllegalBlockSizeException e) { - throw new AssertionError(e); - } catch (BadPaddingException e) { - throw new AssertionError(e); - } - } - - private byte[] getPlaintext(byte[] cipherText, SecretKeySpec key, int counter) { - try { - Cipher cipher = getCipher(Cipher.DECRYPT_MODE, key, counter); - return cipher.doFinal(cipherText); - } catch (IllegalBlockSizeException e) { - throw new AssertionError(e); - } catch (BadPaddingException e) { - throw new AssertionError(e); - } - } - - private Cipher getCipher(int mode, SecretKeySpec key, int counter) { - try { - Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding"); - - byte[] ivBytes = new byte[16]; - Conversions.mediumToByteArray(ivBytes, 0, counter); - - IvParameterSpec iv = new IvParameterSpec(ivBytes); - cipher.init(mode, key, iv); - - return cipher; - } catch (NoSuchAlgorithmException e) { - throw new IllegalArgumentException("AES Not Supported!"); - } catch (NoSuchPaddingException e) { - throw new IllegalArgumentException("NoPadding Not Supported!"); - } catch (java.security.InvalidKeyException e) { - Log.w("SessionCipher", e); - throw new IllegalArgumentException("Invaid Key?"); - } catch (InvalidAlgorithmParameterException e) { - Log.w("SessionCipher", e); - throw new IllegalArgumentException("Bad IV?"); - } - } - - private SessionKey getSessionKey(MasterSecret masterSecret, int mode, - KeyRecords records, - int localKeyId, int remoteKeyId) - throws InvalidKeyIdException, InvalidKeyException - { - Log.w("SessionCipher", "Getting session key for local: " + localKeyId + " remote: " + remoteKeyId); - SessionKey sessionKey = records.getSessionRecord().getSessionKey(mode, localKeyId, remoteKeyId); - - if (sessionKey != null) - return sessionKey; - - DerivedSecrets derivedSecrets = calculateSharedSecret(mode, records, localKeyId, remoteKeyId); - - return new SessionKey(mode, localKeyId, remoteKeyId, derivedSecrets.getCipherKey(), - derivedSecrets.getMacKey(), masterSecret); - } - - private DerivedSecrets calculateSharedSecret(int mode, KeyRecords records, - int localKeyId, int remoteKeyId) - throws InvalidKeyIdException, InvalidKeyException - { - NKDF kdf = new NKDF(); - KeyPair localKeyPair = records.getLocalKeyRecord().getKeyPairForId(localKeyId); - ECPublicKey remoteKey = records.getRemoteKeyRecord().getKeyForId(remoteKeyId).getKey(); - byte[] sharedSecret = Curve.calculateAgreement(remoteKey, localKeyPair.getPrivateKey()); - boolean isLowEnd = isLowEnd(records, localKeyId, remoteKeyId); - - isLowEnd = (mode == Cipher.ENCRYPT_MODE ? isLowEnd : !isLowEnd); - - return kdf.deriveSecrets(sharedSecret, isLowEnd); - } - - private boolean isLowEnd(KeyRecords records, int localKeyId, int remoteKeyId) - throws InvalidKeyIdException - { - ECPublicKey localPublic = records.getLocalKeyRecord().getKeyPairForId(localKeyId).getPublicKey().getKey(); - ECPublicKey remotePublic = records.getRemoteKeyRecord().getKeyForId(remoteKeyId).getKey(); - - return localPublic.compareTo(remotePublic) < 0; - } - - private KeyRecords getKeyRecords(Context context, MasterSecret masterSecret, - CanonicalRecipient recipient) - { - LocalKeyRecord localKeyRecord = new LocalKeyRecord(context, masterSecret, recipient); - RemoteKeyRecord remoteKeyRecord = new RemoteKeyRecord(context, recipient); - SessionRecordV1 sessionRecord = new SessionRecordV1(context, masterSecret, recipient); - return new KeyRecords(localKeyRecord, remoteKeyRecord, sessionRecord); - } - - private static class KeyRecords { - - private final LocalKeyRecord localKeyRecord; - private final RemoteKeyRecord remoteKeyRecord; - private final SessionRecordV1 sessionRecord; - - public KeyRecords(LocalKeyRecord localKeyRecord, - RemoteKeyRecord remoteKeyRecord, - SessionRecordV1 sessionRecord) - { - this.localKeyRecord = localKeyRecord; - this.remoteKeyRecord = remoteKeyRecord; - this.sessionRecord = sessionRecord; - } - - private LocalKeyRecord getLocalKeyRecord() { - return localKeyRecord; - } - - private RemoteKeyRecord getRemoteKeyRecord() { - return remoteKeyRecord; - } - - private SessionRecordV1 getSessionRecord() { - return sessionRecord; - } - } - - public static class SessionCipherContext { - - private final LocalKeyRecord localKeyRecord; - private final RemoteKeyRecord remoteKeyRecord; - private final SessionRecordV1 sessionRecord; - private final SessionKey sessionKey; - private final int senderKeyId; - private final int recipientKeyId; - private final PublicKey nextKey; - private final int counter; - private final int messageVersion; - - public SessionCipherContext(KeyRecords records, - SessionKey sessionKey, - int senderKeyId, - int receiverKeyId, - PublicKey nextKey, - int counter, - int messageVersion) - { - this.localKeyRecord = records.getLocalKeyRecord(); - this.remoteKeyRecord = records.getRemoteKeyRecord(); - this.sessionRecord = records.getSessionRecord(); - this.sessionKey = sessionKey; - this.senderKeyId = senderKeyId; - this.recipientKeyId = receiverKeyId; - this.nextKey = nextKey; - this.counter = counter; - this.messageVersion = messageVersion; - } - - public LocalKeyRecord getLocalKeyRecord() { - return localKeyRecord; - } - - public RemoteKeyRecord getRemoteKeyRecord() { - return remoteKeyRecord; - } - - public SessionRecordV1 getSessionRecord() { - return sessionRecord; - } - - public SessionKey getSessionKey() { - return sessionKey; - } - - public PublicKey getNextKey() { - return nextKey; - } - - public int getCounter() { - return counter; - } - - public int getSenderKeyId() { - return senderKeyId; - } - - public int getRecipientKeyId() { - return recipientKeyId; - } - - public int getMessageVersion() { - return messageVersion; - } - } -} diff --git a/library/src/org/whispersystems/textsecure/crypto/SessionCipherV2.java b/library/src/org/whispersystems/textsecure/crypto/SessionCipherV2.java index 61fa862dd5..f1f9846f42 100644 --- a/library/src/org/whispersystems/textsecure/crypto/SessionCipherV2.java +++ b/library/src/org/whispersystems/textsecure/crypto/SessionCipherV2.java @@ -78,7 +78,7 @@ public class SessionCipherV2 extends SessionCipher { @Override public byte[] decrypt(byte[] decodedMessage) - throws InvalidMessageException, DuplicateMessageException + throws InvalidMessageException, DuplicateMessageException, LegacyMessageException { synchronized (SESSION_LOCK) { SessionRecordV2 sessionRecord = getSessionRecord(); @@ -111,7 +111,7 @@ public class SessionCipherV2 extends SessionCipher { } public byte[] decrypt(SessionState sessionState, byte[] decodedMessage) - throws InvalidMessageException, DuplicateMessageException + throws InvalidMessageException, DuplicateMessageException, LegacyMessageException { if (!sessionState.hasSenderChain()) { throw new InvalidMessageException("Uninitialized session!"); @@ -152,7 +152,7 @@ public class SessionCipherV2 extends SessionCipher { RootKey rootKey = sessionState.getRootKey(); ECKeyPair ourEphemeral = sessionState.getSenderEphemeralPair(); Pair receiverChain = rootKey.createChain(theirEphemeral, ourEphemeral); - ECKeyPair ourNewEphemeral = Curve.generateKeyPairForType(Curve.DJB_TYPE, true); + ECKeyPair ourNewEphemeral = Curve.generateKeyPair(true); Pair senderChain = receiverChain.first.createChain(theirEphemeral, ourNewEphemeral); sessionState.setRootKey(senderChain.first); diff --git a/library/src/org/whispersystems/textsecure/crypto/ecc/Curve.java b/library/src/org/whispersystems/textsecure/crypto/ecc/Curve.java index 097d6930a2..5ae56716e8 100644 --- a/library/src/org/whispersystems/textsecure/crypto/ecc/Curve.java +++ b/library/src/org/whispersystems/textsecure/crypto/ecc/Curve.java @@ -21,26 +21,10 @@ import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; public class Curve { - public static final int NIST_TYPE = 0x02; - private static final int NIST_TYPE2 = 0x03; public static final int DJB_TYPE = 0x05; - public static ECKeyPair generateKeyPairForType(int keyType, boolean ephemeral) { - if (keyType == DJB_TYPE) { - return Curve25519.generateKeyPair(ephemeral); - } else if (keyType == NIST_TYPE || keyType == NIST_TYPE2) { - return CurveP256.generateKeyPair(); - } else { - throw new AssertionError("Bad key type: " + keyType); - } - } - - public static ECKeyPair generateKeyPairForSession(int messageVersion, boolean ephemeral) { - if (messageVersion <= CiphertextMessage.LEGACY_VERSION) { - return generateKeyPairForType(NIST_TYPE, ephemeral); - } else { - return generateKeyPairForType(DJB_TYPE, ephemeral); - } + public static ECKeyPair generateKeyPair(boolean ephemeral) { + return Curve25519.generateKeyPair(ephemeral); } public static ECPublicKey decodePoint(byte[] bytes, int offset) @@ -50,21 +34,13 @@ public class Curve { if (type == DJB_TYPE) { return Curve25519.decodePoint(bytes, offset); - } else if (type == NIST_TYPE || type == NIST_TYPE2) { - return CurveP256.decodePoint(bytes, offset); } else { throw new InvalidKeyException("Unknown key type: " + type); } } - public static ECPrivateKey decodePrivatePoint(int type, byte[] bytes) { - if (type == DJB_TYPE) { - return new DjbECPrivateKey(bytes); - } else if (type == NIST_TYPE || type == NIST_TYPE2) { - return CurveP256.decodePrivatePoint(bytes); - } else { - throw new AssertionError("Bad key type: " + type); - } + public static ECPrivateKey decodePrivatePoint(byte[] bytes) { + return new DjbECPrivateKey(bytes); } public static byte[] calculateAgreement(ECPublicKey publicKey, ECPrivateKey privateKey) @@ -76,8 +52,6 @@ public class Curve { if (publicKey.getType() == DJB_TYPE) { return Curve25519.calculateAgreement(publicKey, privateKey); - } else if (publicKey.getType() == NIST_TYPE || publicKey.getType() == NIST_TYPE2) { - return CurveP256.calculateAgreement(publicKey, privateKey); } else { throw new InvalidKeyException("Unknown type: " + publicKey.getType()); } diff --git a/library/src/org/whispersystems/textsecure/crypto/ecc/CurveP256.java b/library/src/org/whispersystems/textsecure/crypto/ecc/CurveP256.java deleted file mode 100644 index edc29eb169..0000000000 --- a/library/src/org/whispersystems/textsecure/crypto/ecc/CurveP256.java +++ /dev/null @@ -1,122 +0,0 @@ -/** - * Copyright (C) 2013 Open 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 . - */ - -package org.whispersystems.textsecure.crypto.ecc; - -import android.util.Log; - -import org.spongycastle.crypto.AsymmetricCipherKeyPair; -import org.spongycastle.crypto.agreement.ECDHBasicAgreement; -import org.spongycastle.crypto.generators.ECKeyPairGenerator; -import org.spongycastle.crypto.params.ECDomainParameters; -import org.spongycastle.crypto.params.ECKeyGenerationParameters; -import org.spongycastle.crypto.params.ECPrivateKeyParameters; -import org.spongycastle.crypto.params.ECPublicKeyParameters; -import org.spongycastle.math.ec.ECCurve; -import org.spongycastle.math.ec.ECFieldElement; -import org.spongycastle.math.ec.ECPoint; -import org.whispersystems.textsecure.crypto.InvalidKeyException; - -import java.math.BigInteger; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; - -public class CurveP256 { - - private static final BigInteger q = new BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", 16); - private static final BigInteger a = new BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC", 16); - private static final BigInteger b = new BigInteger("5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B", 16); - private static final BigInteger n = new BigInteger("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551", 16); - - private static final ECFieldElement x = new ECFieldElement.Fp(q, new BigInteger("6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296", 16)); - private static final ECFieldElement y = new ECFieldElement.Fp(q, new BigInteger("4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5", 16)); - - private static final ECCurve curve = new ECCurve.Fp(q, a, b); - private static final ECPoint g = new ECPoint.Fp(curve, x, y, true); - - private static final ECDomainParameters domainParameters = new ECDomainParameters(curve, g, n); - - public static final int P256_POINT_SIZE = 33; - - static byte[] encodePoint(ECPoint point) { - synchronized (curve) { - return point.getEncoded(); - } - } - - static ECPublicKey decodePoint(byte[] encoded, int offset) - throws InvalidKeyException - { - byte[] pointBytes = new byte[P256_POINT_SIZE]; - System.arraycopy(encoded, offset, pointBytes, 0, pointBytes.length); - - synchronized (curve) { - ECPoint Q; - - try { - Q = curve.decodePoint(pointBytes); - } catch (RuntimeException re) { - throw new InvalidKeyException(re); - } - - return new NistECPublicKey(new ECPublicKeyParameters(Q, domainParameters)); - } - } - - static ECPrivateKey decodePrivatePoint(byte[] encoded) { - BigInteger d = new BigInteger(encoded); - return new NistECPrivateKey(new ECPrivateKeyParameters(d, domainParameters)); - } - - static byte[] calculateAgreement(ECPublicKey publicKey, ECPrivateKey privateKey) { - ECDHBasicAgreement agreement = new ECDHBasicAgreement(); - agreement.init(((NistECPrivateKey)privateKey).getParameters()); - - synchronized (curve) { - return agreement.calculateAgreement(((NistECPublicKey)publicKey).getParameters()).toByteArray(); - } - } - - public static ECKeyPair generateKeyPair() { - try { - synchronized (curve) { - ECKeyGenerationParameters keyParamters = new ECKeyGenerationParameters(domainParameters, SecureRandom.getInstance("SHA1PRNG")); - ECKeyPairGenerator generator = new ECKeyPairGenerator(); - generator.init(keyParamters); - - AsymmetricCipherKeyPair keyPair = generator.generateKeyPair(); - keyPair = cloneKeyPairWithPointCompression(keyPair); - - return new ECKeyPair(new NistECPublicKey((ECPublicKeyParameters)keyPair.getPublic()), - new NistECPrivateKey((ECPrivateKeyParameters)keyPair.getPrivate())); - } - } catch (NoSuchAlgorithmException nsae) { - Log.w("CurveP256", nsae); - throw new AssertionError(nsae); - } - } - - // This is dumb, but the ECPublicKeys that the generator makes by default don't have point compression - // turned on, and there's no setter. Great. - private static AsymmetricCipherKeyPair cloneKeyPairWithPointCompression(AsymmetricCipherKeyPair keyPair) { - ECPublicKeyParameters publicKey = (ECPublicKeyParameters)keyPair.getPublic(); - ECPoint q = publicKey.getQ(); - - return new AsymmetricCipherKeyPair(new ECPublicKeyParameters(new ECPoint.Fp(q.getCurve(), q.getX(), q.getY(), true), - publicKey.getParameters()), keyPair.getPrivate()); - } -} diff --git a/library/src/org/whispersystems/textsecure/crypto/ecc/NistECPrivateKey.java b/library/src/org/whispersystems/textsecure/crypto/ecc/NistECPrivateKey.java deleted file mode 100644 index c4fa5a6879..0000000000 --- a/library/src/org/whispersystems/textsecure/crypto/ecc/NistECPrivateKey.java +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright (C) 2013 Open 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 . - */ - -package org.whispersystems.textsecure.crypto.ecc; - -import org.spongycastle.crypto.params.ECPrivateKeyParameters; - -public class NistECPrivateKey implements ECPrivateKey { - - private final ECPrivateKeyParameters privateKey; - - public NistECPrivateKey(ECPrivateKeyParameters privateKey) { - this.privateKey = privateKey; - } - - @Override - public byte[] serialize() { - return privateKey.getD().toByteArray(); - } - - @Override - public int getType() { - return Curve.NIST_TYPE; - } - - public ECPrivateKeyParameters getParameters() { - return privateKey; - } -} diff --git a/library/src/org/whispersystems/textsecure/crypto/ecc/NistECPublicKey.java b/library/src/org/whispersystems/textsecure/crypto/ecc/NistECPublicKey.java deleted file mode 100644 index 1c54fec1bb..0000000000 --- a/library/src/org/whispersystems/textsecure/crypto/ecc/NistECPublicKey.java +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Copyright (C) 2013 Open 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 . - */ - -package org.whispersystems.textsecure.crypto.ecc; - -import org.spongycastle.crypto.params.ECPublicKeyParameters; - -public class NistECPublicKey implements ECPublicKey { - - private final ECPublicKeyParameters publicKey; - - NistECPublicKey(ECPublicKeyParameters publicKey) { - this.publicKey = publicKey; - } - - @Override - public byte[] serialize() { - return CurveP256.encodePoint(publicKey.getQ()); - } - - @Override - public int getType() { - return Curve.NIST_TYPE; - } - - @Override - public boolean equals(Object other) { - if (other == null) return false; - if (!(other instanceof NistECPublicKey)) return false; - - NistECPublicKey that = (NistECPublicKey)other; - return publicKey.getQ().equals(that.publicKey.getQ()); - } - - @Override - public int hashCode() { - return publicKey.getQ().hashCode(); - } - - @Override - public int compareTo(ECPublicKey another) { - return publicKey.getQ().getX().toBigInteger() - .compareTo(((NistECPublicKey) another).publicKey.getQ().getX().toBigInteger()); - } - - public ECPublicKeyParameters getParameters() { - return publicKey; - } -} diff --git a/library/src/org/whispersystems/textsecure/crypto/protocol/CiphertextMessage.java b/library/src/org/whispersystems/textsecure/crypto/protocol/CiphertextMessage.java index b431b39ee1..7bed17ad06 100644 --- a/library/src/org/whispersystems/textsecure/crypto/protocol/CiphertextMessage.java +++ b/library/src/org/whispersystems/textsecure/crypto/protocol/CiphertextMessage.java @@ -2,15 +2,14 @@ package org.whispersystems.textsecure.crypto.protocol; public interface CiphertextMessage { - public static final int LEGACY_VERSION = 1; - public static final int CURRENT_VERSION = 2; + public static final int UNSUPPORTED_VERSION = 1; + public static final int CURRENT_VERSION = 2; - public static final int LEGACY_WHISPER_TYPE = 1; - public static final int CURRENT_WHISPER_TYPE = 2; - public static final int PREKEY_WHISPER_TYPE = 3; + public static final int WHISPER_TYPE = 2; + public static final int PREKEY_TYPE = 3; // This should be the worst case (worse than V2). So not always accurate, but good enough for padding. - public static final int ENCRYPTED_MESSAGE_OVERHEAD = WhisperMessageV1.ENCRYPTED_MESSAGE_OVERHEAD; + public static final int ENCRYPTED_MESSAGE_OVERHEAD = 53; public byte[] serialize(); public int getType(); diff --git a/library/src/org/whispersystems/textsecure/crypto/protocol/PreKeyWhisperMessage.java b/library/src/org/whispersystems/textsecure/crypto/protocol/PreKeyWhisperMessage.java index a966194f1e..2aaa915010 100644 --- a/library/src/org/whispersystems/textsecure/crypto/protocol/PreKeyWhisperMessage.java +++ b/library/src/org/whispersystems/textsecure/crypto/protocol/PreKeyWhisperMessage.java @@ -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.util.Conversions; @@ -26,7 +27,7 @@ public class PreKeyWhisperMessage implements CiphertextMessage { throws InvalidMessageException, InvalidVersionException { try { - this.version = Conversions.lowBitsToInt(serialized[0]); + this.version = Conversions.highBitsToInt(serialized[0]); if (this.version > CiphertextMessage.CURRENT_VERSION) { throw new InvalidVersionException("Unknown version: " + this.version); @@ -54,6 +55,8 @@ public class PreKeyWhisperMessage implements CiphertextMessage { throw new InvalidMessageException(e); } catch (InvalidKeyException e) { throw new InvalidMessageException(e); + } catch (LegacyMessageException e) { + throw new InvalidMessageException(e); } } @@ -106,7 +109,7 @@ public class PreKeyWhisperMessage implements CiphertextMessage { @Override public int getType() { - return CiphertextMessage.PREKEY_WHISPER_TYPE; + return CiphertextMessage.PREKEY_TYPE; } } diff --git a/library/src/org/whispersystems/textsecure/crypto/protocol/WhisperMessageV1.java b/library/src/org/whispersystems/textsecure/crypto/protocol/WhisperMessageV1.java deleted file mode 100644 index 7efe8e3b6a..0000000000 --- a/library/src/org/whispersystems/textsecure/crypto/protocol/WhisperMessageV1.java +++ /dev/null @@ -1,187 +0,0 @@ -package org.whispersystems.textsecure.crypto.protocol; - -import android.util.Log; - -import org.whispersystems.textsecure.crypto.InvalidMessageException; -import org.whispersystems.textsecure.crypto.PublicKey; -import org.whispersystems.textsecure.crypto.SessionCipherV1; -import org.whispersystems.textsecure.util.Conversions; -import org.whispersystems.textsecure.util.Hex; -import org.whispersystems.textsecure.util.Util; - -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.util.Arrays; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; - - -public class WhisperMessageV1 implements CiphertextMessage{ - - private static final int VERSION_LENGTH = 1; - private static final int SENDER_KEY_ID_LENGTH = 3; - private static final int RECEIVER_KEY_ID_LENGTH = 3; - private static final int NEXT_KEY_LENGTH = PublicKey.KEY_SIZE; - private static final int COUNTER_LENGTH = 3; - private static final int HEADER_LENGTH = VERSION_LENGTH + SENDER_KEY_ID_LENGTH + - RECEIVER_KEY_ID_LENGTH + COUNTER_LENGTH + - NEXT_KEY_LENGTH; - private static final int MAC_LENGTH = 10; - - - private static final int VERSION_OFFSET = 0; - private static final int SENDER_KEY_ID_OFFSET = VERSION_OFFSET + VERSION_LENGTH; - private static final int RECEIVER_KEY_ID_OFFSET = SENDER_KEY_ID_OFFSET + SENDER_KEY_ID_LENGTH; - private static final int NEXT_KEY_OFFSET = RECEIVER_KEY_ID_OFFSET + RECEIVER_KEY_ID_LENGTH; - private static final int COUNTER_OFFSET = NEXT_KEY_OFFSET + NEXT_KEY_LENGTH; - private static final int BODY_OFFSET = COUNTER_OFFSET + COUNTER_LENGTH; - - static final int ENCRYPTED_MESSAGE_OVERHEAD = HEADER_LENGTH + MAC_LENGTH; - - private final byte[] ciphertext; - - public WhisperMessageV1(SessionCipherV1.SessionCipherContext sessionContext, - byte[] ciphertextBody) - { - this.ciphertext = new byte[HEADER_LENGTH + ciphertextBody.length + MAC_LENGTH]; - setVersion(sessionContext.getMessageVersion(), CURRENT_VERSION); - setSenderKeyId(sessionContext.getSenderKeyId()); - setReceiverKeyId(sessionContext.getRecipientKeyId()); - setNextKeyBytes(sessionContext.getNextKey().serialize()); - setCounter(sessionContext.getCounter()); - setBody(ciphertextBody); - setMac(calculateMac(sessionContext.getSessionKey().getMacKey(), - ciphertext, 0, ciphertext.length - MAC_LENGTH)); - } - - public WhisperMessageV1(byte[] ciphertext) throws InvalidMessageException { - this.ciphertext = ciphertext; - - if (ciphertext.length < HEADER_LENGTH) { - throw new InvalidMessageException("Not long enough for ciphertext header!"); - } - - if (getCurrentVersion() > LEGACY_VERSION) { - throw new InvalidMessageException("Received non-legacy version: " + getCurrentVersion()); - } - } - - public void setVersion(int current, int supported) { - ciphertext[VERSION_OFFSET] = Conversions.intsToByteHighAndLow(current, supported); - } - - public int getCurrentVersion() { - return Conversions.highBitsToInt(ciphertext[VERSION_OFFSET]); - } - - public int getSupportedVersion() { - return Conversions.lowBitsToInt(ciphertext[VERSION_OFFSET]); - } - - public void setSenderKeyId(int senderKeyId) { - Conversions.mediumToByteArray(ciphertext, SENDER_KEY_ID_OFFSET, senderKeyId); - } - - public int getSenderKeyId() { - return Conversions.byteArrayToMedium(ciphertext, SENDER_KEY_ID_OFFSET); - } - - public void setReceiverKeyId(int receiverKeyId) { - Conversions.mediumToByteArray(ciphertext, RECEIVER_KEY_ID_OFFSET, receiverKeyId); - } - - public int getReceiverKeyId() { - return Conversions.byteArrayToMedium(ciphertext, RECEIVER_KEY_ID_OFFSET); - } - - public void setNextKeyBytes(byte[] nextKey) { - assert(nextKey.length == NEXT_KEY_LENGTH); - System.arraycopy(nextKey, 0, ciphertext, NEXT_KEY_OFFSET, nextKey.length); - } - - public byte[] getNextKeyBytes() { - byte[] nextKeyBytes = new byte[NEXT_KEY_LENGTH]; - System.arraycopy(ciphertext, NEXT_KEY_OFFSET, nextKeyBytes, 0, nextKeyBytes.length); - - return nextKeyBytes; - } - - public void setCounter(int counter) { - Conversions.mediumToByteArray(ciphertext, COUNTER_OFFSET, counter); - } - - public int getCounter() { - return Conversions.byteArrayToMedium(ciphertext, COUNTER_OFFSET); - } - - public void setBody(byte[] body) { - System.arraycopy(body, 0, ciphertext, BODY_OFFSET, body.length); - } - - public byte[] getBody() { - byte[] body = new byte[ciphertext.length - HEADER_LENGTH - MAC_LENGTH]; - System.arraycopy(ciphertext, BODY_OFFSET, body, 0, body.length); - - return body; - } - - public void setMac(byte[] mac) { - System.arraycopy(mac, 0, ciphertext, ciphertext.length-mac.length, mac.length); - } - - public byte[] getMac() { - byte[] mac = new byte[MAC_LENGTH]; - System.arraycopy(ciphertext, ciphertext.length-mac.length, mac, 0, mac.length); - - return mac; - } - - @Override - public byte[] serialize() { - return ciphertext; - } - - @Override - public int getType() { - return CiphertextMessage.LEGACY_WHISPER_TYPE; - } - - public void verifyMac(SessionCipherV1.SessionCipherContext sessionContext) - throws InvalidMessageException - { - verifyMac(sessionContext.getSessionKey().getMacKey(), - this.ciphertext, 0, this.ciphertext.length - MAC_LENGTH, getMac()); - } - - private byte[] calculateMac(SecretKeySpec macKey, byte[] message, int offset, int length) { - try { - Mac mac = Mac.getInstance("HmacSHA1"); - mac.init(macKey); - - mac.update(message, offset, length); - byte[] macBytes = mac.doFinal(); - - return Util.trim(macBytes, MAC_LENGTH); - } catch (NoSuchAlgorithmException e) { - throw new IllegalArgumentException(e); - } catch (InvalidKeyException e) { - throw new IllegalArgumentException(e); - } - } - - private void verifyMac(SecretKeySpec macKey, byte[] message, int offset, int length, - byte[] receivedMac) - throws InvalidMessageException - { - byte[] localMac = calculateMac(macKey, message, offset, length); - - Log.w("WhisperMessageV1", "Local Mac: " + Hex.toString(localMac)); - Log.w("WhisperMessageV1", "Remot Mac: " + Hex.toString(receivedMac)); - - if (!Arrays.equals(localMac, receivedMac)) { - throw new InvalidMessageException("MAC on message does not match calculated MAC."); - } - } - -} diff --git a/library/src/org/whispersystems/textsecure/crypto/protocol/WhisperMessageV2.java b/library/src/org/whispersystems/textsecure/crypto/protocol/WhisperMessageV2.java index d943147c8c..32df62adbc 100644 --- a/library/src/org/whispersystems/textsecure/crypto/protocol/WhisperMessageV2.java +++ b/library/src/org/whispersystems/textsecure/crypto/protocol/WhisperMessageV2.java @@ -7,6 +7,7 @@ import com.google.protobuf.InvalidProtocolBufferException; import org.whispersystems.textsecure.crypto.InvalidKeyException; import org.whispersystems.textsecure.crypto.InvalidMessageException; +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.WhisperProtos.WhisperMessage; @@ -31,13 +32,17 @@ public class WhisperMessageV2 implements CiphertextMessage { private final byte[] ciphertext; private final byte[] serialized; - public WhisperMessageV2(byte[] serialized) throws InvalidMessageException { + public WhisperMessageV2(byte[] serialized) throws InvalidMessageException, LegacyMessageException { try { byte[][] messageParts = Util.split(serialized, 1, serialized.length - 1 - MAC_LENGTH, MAC_LENGTH); byte version = messageParts[0][0]; byte[] message = messageParts[1]; byte[] mac = messageParts[2]; + if (Conversions.highBitsToInt(version) <= CiphertextMessage.UNSUPPORTED_VERSION) { + throw new LegacyMessageException("Legacy message: " + Conversions.highBitsToInt(version)); + } + if (Conversions.highBitsToInt(version) != CURRENT_VERSION) { throw new InvalidMessageException("Unknown version: " + Conversions.highBitsToInt(version)); } @@ -129,7 +134,7 @@ public class WhisperMessageV2 implements CiphertextMessage { @Override public int getType() { - return CiphertextMessage.CURRENT_WHISPER_TYPE; + return CiphertextMessage.WHISPER_TYPE; } } diff --git a/library/src/org/whispersystems/textsecure/crypto/ratchet/RatchetingSession.java b/library/src/org/whispersystems/textsecure/crypto/ratchet/RatchetingSession.java index a7f716bacd..c2f34fffaa 100644 --- a/library/src/org/whispersystems/textsecure/crypto/ratchet/RatchetingSession.java +++ b/library/src/org/whispersystems/textsecure/crypto/ratchet/RatchetingSession.java @@ -45,7 +45,7 @@ public class RatchetingSession { sessionState.setRemoteIdentityKey(theirIdentityKey); sessionState.setLocalIdentityKey(ourIdentityKey.getPublicKey()); - ECKeyPair sendingKey = Curve.generateKeyPairForType(ourIdentityKey.getPublicKey().getPublicKey().getType(), true); + ECKeyPair sendingKey = Curve.generateKeyPair(true); Pair receivingChain = calculate3DHE(true, ourBaseKey, theirBaseKey, ourIdentityKey, theirIdentityKey); Pair sendingChain = receivingChain.first.createChain(theirEphemeralKey, sendingKey); diff --git a/library/src/org/whispersystems/textsecure/storage/LocalKeyRecord.java b/library/src/org/whispersystems/textsecure/storage/LocalKeyRecord.java index 3f1aafd070..a3d4714bad 100644 --- a/library/src/org/whispersystems/textsecure/storage/LocalKeyRecord.java +++ b/library/src/org/whispersystems/textsecure/storage/LocalKeyRecord.java @@ -18,135 +18,14 @@ package org.whispersystems.textsecure.storage; import android.content.Context; -import android.util.Log; -import org.whispersystems.textsecure.crypto.InvalidKeyException; -import org.whispersystems.textsecure.crypto.KeyPair; -import org.whispersystems.textsecure.crypto.MasterCipher; -import org.whispersystems.textsecure.crypto.MasterSecret; -import org.whispersystems.textsecure.crypto.ecc.Curve; -import org.whispersystems.textsecure.util.Medium; - -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.channels.FileChannel; - -public class LocalKeyRecord extends Record { - - private static final Object FILE_LOCK = new Object(); - - private KeyPair localCurrentKeyPair; - private KeyPair localNextKeyPair; - - private final MasterCipher masterCipher; - private final MasterSecret masterSecret; - - public LocalKeyRecord(Context context, MasterSecret masterSecret, CanonicalRecipient recipient) { - super(context, SESSIONS_DIRECTORY, getFileNameForRecipient(recipient)); - this.masterSecret = masterSecret; - this.masterCipher = new MasterCipher(masterSecret); - loadData(); - } - - public static boolean hasRecord(Context context, CanonicalRecipient recipient) { - Log.w("LocalKeyRecord", "Checking: " + getFileNameForRecipient(recipient)); - return Record.hasRecord(context, SESSIONS_DIRECTORY, getFileNameForRecipient(recipient)); - } +public class LocalKeyRecord { public static void delete(Context context, CanonicalRecipient recipient) { - Record.delete(context, SESSIONS_DIRECTORY, getFileNameForRecipient(recipient)); + Record.delete(context, Record.SESSIONS_DIRECTORY, getFileNameForRecipient(recipient)); } private static String getFileNameForRecipient(CanonicalRecipient recipient) { return recipient.getRecipientId() + "-local"; } - - public void advanceKeyIfNecessary(int keyId) { - Log.w("LocalKeyRecord", "Remote client acknowledges receiving key id: " + keyId); - if (keyId == localNextKeyPair.getId()) { - int keyType = this.localNextKeyPair.getPublicKey().getType(); - - this.localCurrentKeyPair = this.localNextKeyPair; - this.localNextKeyPair = new KeyPair((this.localNextKeyPair.getId()+1) % Medium.MAX_VALUE, - Curve.generateKeyPairForType(keyType, true), - masterSecret); - } - } - - public void setCurrentKeyPair(KeyPair localCurrentKeyPair) { - this.localCurrentKeyPair = localCurrentKeyPair; - } - - public void setNextKeyPair(KeyPair localNextKeyPair) { - this.localNextKeyPair = localNextKeyPair; - } - - public KeyPair getCurrentKeyPair() { - return this.localCurrentKeyPair; - } - - public KeyPair getNextKeyPair() { - return this.localNextKeyPair; - } - - public KeyPair getKeyPairForId(int id) throws InvalidKeyIdException { - if (this.localCurrentKeyPair.getId() == id) return this.localCurrentKeyPair; - else if (this.localNextKeyPair.getId() == id) return this.localNextKeyPair; - else throw new InvalidKeyIdException("No local key for ID: " + id); - } - - public void save() { - synchronized (FILE_LOCK) { - try { - RandomAccessFile file = openRandomAccessFile(); - FileChannel out = file.getChannel(); - out.position(0); - - writeKeyPair(localCurrentKeyPair, out); - writeKeyPair(localNextKeyPair, out); - - out.force(true); - out.truncate(out.position()); - out.close(); - file.close(); - } catch (IOException ioe) { - Log.w("keyrecord", ioe); - // XXX - } - } - } - - private void loadData() { - Log.w("LocalKeyRecord", "Loading local key record..."); - synchronized (FILE_LOCK) { - try { - FileInputStream in = this.openInputStream(); - localCurrentKeyPair = readKeyPair(in, masterCipher); - localNextKeyPair = readKeyPair(in, masterCipher); - in.close(); - } catch (FileNotFoundException e) { - Log.w("LocalKeyRecord", "No local keypair set found."); - } catch (IOException ioe) { - Log.w("keyrecord", ioe); - // XXX - } catch (InvalidKeyException ike) { - Log.w("LocalKeyRecord", ike); - } - } - } - - private void writeKeyPair(KeyPair keyPair, FileChannel out) throws IOException { - byte[] keyPairBytes = keyPair.toBytes(); - writeBlob(keyPairBytes, out); - } - - private KeyPair readKeyPair(FileInputStream in, MasterCipher masterCipher) - throws IOException, InvalidKeyException - { - byte[] keyPairBytes = readBlob(in); - return new KeyPair(keyPairBytes, masterCipher); - } - } diff --git a/library/src/org/whispersystems/textsecure/storage/PreKeyRecord.java b/library/src/org/whispersystems/textsecure/storage/PreKeyRecord.java index d5796c054d..2018e2d92e 100644 --- a/library/src/org/whispersystems/textsecure/storage/PreKeyRecord.java +++ b/library/src/org/whispersystems/textsecure/storage/PreKeyRecord.java @@ -60,7 +60,7 @@ public class PreKeyRecord extends Record { public ECKeyPair getKeyPair() { try { ECPublicKey publicKey = Curve.decodePoint(this.structure.getPublicKey().toByteArray(), 0); - ECPrivateKey privateKey = Curve.decodePrivatePoint(publicKey.getType(), this.structure.getPrivateKey().toByteArray()); + ECPrivateKey privateKey = Curve.decodePrivatePoint(this.structure.getPrivateKey().toByteArray()); return new ECKeyPair(publicKey, privateKey); } catch (InvalidKeyException e) { diff --git a/library/src/org/whispersystems/textsecure/storage/RemoteKeyRecord.java b/library/src/org/whispersystems/textsecure/storage/RemoteKeyRecord.java index 7b4059d1e2..1cc5ff5a54 100644 --- a/library/src/org/whispersystems/textsecure/storage/RemoteKeyRecord.java +++ b/library/src/org/whispersystems/textsecure/storage/RemoteKeyRecord.java @@ -37,126 +37,13 @@ import java.nio.channels.FileChannel; * @author Moxie Marlinspike */ -public class RemoteKeyRecord extends Record { - private static final Object FILE_LOCK = new Object(); - - private PublicKey remoteKeyCurrent; - private PublicKey remoteKeyLast; - - public RemoteKeyRecord(Context context, CanonicalRecipient recipient) { - super(context, SESSIONS_DIRECTORY, getFileNameForRecipient(recipient)); - loadData(); - } +public class RemoteKeyRecord { public static void delete(Context context, CanonicalRecipient recipient) { - delete(context, SESSIONS_DIRECTORY, getFileNameForRecipient(recipient)); - } - - public static boolean hasRecord(Context context, CanonicalRecipient recipient) { - Log.w("LocalKeyRecord", "Checking: " + getFileNameForRecipient(recipient)); - return hasRecord(context, SESSIONS_DIRECTORY, getFileNameForRecipient(recipient)); + Record.delete(context, Record.SESSIONS_DIRECTORY, getFileNameForRecipient(recipient)); } private static String getFileNameForRecipient(CanonicalRecipient recipient) { return recipient.getRecipientId() + "-remote"; } - - public void updateCurrentRemoteKey(PublicKey remoteKey) { - Log.w("RemoteKeyRecord", "Updating current remote key: " + remoteKey.getId()); - if (isWrappingGreaterThan(remoteKey.getId(), remoteKeyCurrent.getId())) { - this.remoteKeyLast = this.remoteKeyCurrent; - this.remoteKeyCurrent = remoteKey; - } - } - - public void setCurrentRemoteKey(PublicKey remoteKeyCurrent) { - this.remoteKeyCurrent = remoteKeyCurrent; - } - - public void setLastRemoteKey(PublicKey remoteKeyLast) { - this.remoteKeyLast = remoteKeyLast; - } - - public PublicKey getCurrentRemoteKey() { - return this.remoteKeyCurrent; - } - - public PublicKey getLastRemoteKey() { - return this.remoteKeyLast; - } - - public PublicKey getKeyForId(int id) throws InvalidKeyIdException { - if (this.remoteKeyCurrent.getId() == id) return this.remoteKeyCurrent; - else if (this.remoteKeyLast.getId() == id) return this.remoteKeyLast; - else throw new InvalidKeyIdException("No remote key for ID: " + id); - } - - public void save() { - Log.w("RemoteKeyRecord", "Saving remote key record for recipient: " + this.address); - synchronized (FILE_LOCK) { - try { - RandomAccessFile file = openRandomAccessFile(); - FileChannel out = file.getChannel(); - Log.w("RemoteKeyRecord", "Opened file of size: " + out.size()); - out.position(0); - - writeKey(remoteKeyCurrent, out); - writeKey(remoteKeyLast, out); - - out.truncate(out.position()); - out.close(); - file.close(); - } catch (IOException ioe) { - Log.w("keyrecord", ioe); - // XXX - } - } - } - - private boolean isWrappingGreaterThan(int receivedValue, int currentValue) { - if (receivedValue > currentValue) { - return true; - } - - if (receivedValue == currentValue) { - return false; - } - - int gap = (receivedValue - currentValue) + Medium.MAX_VALUE; - - return (gap >= 0) && (gap < 5); - } - - private void loadData() { - Log.w("RemoteKeyRecord", "Loading remote key record for recipient: " + this.address); - synchronized (FILE_LOCK) { - try { - FileInputStream in = this.openInputStream(); - remoteKeyCurrent = readKey(in); - remoteKeyLast = readKey(in); - in.close(); - } catch (FileNotFoundException e) { - Log.w("RemoteKeyRecord", "No remote keys found."); - } catch (IOException ioe) { - Log.w("keyrecord", ioe); - // XXX - } - } - } - - private void writeKey(PublicKey key, FileChannel out) throws IOException { - byte[] keyBytes = key.serialize(); - Log.w("RemoteKeyRecord", "Serializing remote key bytes: " + Hex.toString(keyBytes)); - writeBlob(keyBytes, out); - } - - private PublicKey readKey(FileInputStream in) throws IOException { - try { - byte[] keyBytes = readBlob(in); - return new PublicKey(keyBytes); - } catch (InvalidKeyException ike) { - throw new AssertionError(ike); - } - } - } diff --git a/library/src/org/whispersystems/textsecure/storage/Session.java b/library/src/org/whispersystems/textsecure/storage/Session.java index 7270e9f738..5bf5bb261b 100644 --- a/library/src/org/whispersystems/textsecure/storage/Session.java +++ b/library/src/org/whispersystems/textsecure/storage/Session.java @@ -5,7 +5,6 @@ import android.util.Log; import org.whispersystems.textsecure.crypto.IdentityKey; import org.whispersystems.textsecure.crypto.MasterSecret; -import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; /** * Helper class for generating key pairs and calculating ECDH agreements. @@ -32,7 +31,8 @@ public class Session { CanonicalRecipient recipient) { Log.w("Session", "Checking session..."); - return hasV1Session(context, recipient) || hasV2Session(context, masterSecret, recipient); + return SessionRecordV2.hasSession(context, masterSecret, recipient.getRecipientId(), + RecipientDevice.DEFAULT_DEVICE_ID); } public static boolean hasEncryptCapableSession(Context context, @@ -50,30 +50,8 @@ public class Session { CanonicalRecipient recipient, RecipientDevice device) { - return - hasV1Session(context, recipient) || - (hasV2Session(context, masterSecret, recipient) && - !SessionRecordV2.needsRefresh(context, masterSecret, device)); - } - - public static boolean hasRemoteIdentityKey(Context context, - MasterSecret masterSecret, - CanonicalRecipient recipient) - { - return (hasV2Session(context, masterSecret, recipient) || (hasV1Session(context, recipient) && - new SessionRecordV1(context, masterSecret, recipient).getIdentityKey() != null)); - } - - private static boolean hasV2Session(Context context, MasterSecret masterSecret, - CanonicalRecipient recipient) - { - return SessionRecordV2.hasSession(context, masterSecret, recipient.getRecipientId(), - RecipientDevice.DEFAULT_DEVICE_ID); - } - private static boolean hasV1Session(Context context, CanonicalRecipient recipient) { - return SessionRecordV1.hasSession(context, recipient) && - RemoteKeyRecord.hasRecord(context, recipient) && - LocalKeyRecord.hasRecord(context, recipient); + return hasSession(context, masterSecret, recipient) && + !SessionRecordV2.needsRefresh(context, masterSecret, device); } public static IdentityKey getRemoteIdentityKey(Context context, MasterSecret masterSecret, @@ -92,25 +70,8 @@ public class Session { return new SessionRecordV2(context, masterSecret, recipientId, RecipientDevice.DEFAULT_DEVICE_ID).getSessionState() .getRemoteIdentityKey(); - } else if (SessionRecordV1.hasSession(context, recipientId)) { - return new SessionRecordV1(context, masterSecret, recipientId).getIdentityKey(); } else { return null; } } - - public static int getSessionVersion(Context context, MasterSecret masterSecret, - CanonicalRecipient recipient) - { - if (SessionRecordV2.hasSession(context, masterSecret, - recipient.getRecipientId(), - RecipientDevice.DEFAULT_DEVICE_ID)) - { - return CiphertextMessage.CURRENT_VERSION; - } else if (SessionRecordV1.hasSession(context, recipient)) { - return new SessionRecordV1(context, masterSecret, recipient).getSessionVersion(); - } - - return 0; - } } diff --git a/library/src/org/whispersystems/textsecure/storage/SessionRecordV1.java b/library/src/org/whispersystems/textsecure/storage/SessionRecordV1.java index 28ac91a5a9..7edca3e7f7 100644 --- a/library/src/org/whispersystems/textsecure/storage/SessionRecordV1.java +++ b/library/src/org/whispersystems/textsecure/storage/SessionRecordV1.java @@ -1,18 +1,6 @@ package org.whispersystems.textsecure.storage; import android.content.Context; -import android.util.Log; - -import org.whispersystems.textsecure.crypto.IdentityKey; -import org.whispersystems.textsecure.crypto.InvalidKeyException; -import org.whispersystems.textsecure.crypto.InvalidMessageException; -import org.whispersystems.textsecure.crypto.MasterSecret; - -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.channels.FileChannel; /** * A disk record representing a current session. @@ -20,208 +8,8 @@ import java.nio.channels.FileChannel; * @author Moxie Marlinspike */ -public class SessionRecordV1 extends Record { - - private static final int CURRENT_VERSION_MARKER = 0X55555556; - private static final int[] VALID_VERSION_MARKERS = {CURRENT_VERSION_MARKER, 0X55555555}; - private static final Object FILE_LOCK = new Object(); - - private int counter; - private byte[] localFingerprint; - private byte[] remoteFingerprint; - private int currentSessionVersion; - - private IdentityKey identityKey; - private SessionKey sessionKeyRecord; - private boolean verifiedSessionKey; - - private final MasterSecret masterSecret; - - public SessionRecordV1(Context context, MasterSecret masterSecret, CanonicalRecipient recipient) { - this(context, masterSecret, recipient.getRecipientId()); - } - - public SessionRecordV1(Context context, MasterSecret masterSecret, long recipientId) { - super(context, SESSIONS_DIRECTORY, recipientId+""); - this.masterSecret = masterSecret; - this.currentSessionVersion = 31337; - loadData(); - } - +public class SessionRecordV1 { public static void delete(Context context, CanonicalRecipient recipient) { - delete(context, SESSIONS_DIRECTORY, recipient.getRecipientId() + ""); + Record.delete(context, Record.SESSIONS_DIRECTORY, recipient.getRecipientId() + ""); } - - public static boolean hasSession(Context context, CanonicalRecipient recipient) { - return hasSession(context, recipient.getRecipientId()); - } - - public static boolean hasSession(Context context, long recipientId) { - Log.w("SessionRecordV1", "Checking: " + recipientId); - return hasRecord(context, SESSIONS_DIRECTORY, recipientId+""); - } - - public void setSessionKey(SessionKey sessionKeyRecord) { - this.sessionKeyRecord = sessionKeyRecord; - } - - public void setSessionId(byte[] localFingerprint, byte[] remoteFingerprint) { - this.localFingerprint = localFingerprint; - this.remoteFingerprint = remoteFingerprint; - } - - public void setIdentityKey(IdentityKey identityKey) { - this.identityKey = identityKey; - } - - public int getSessionVersion() { - return (currentSessionVersion == 31337 ? 0 : currentSessionVersion); - } - - public void setSessionVersion(int sessionVersion) { - this.currentSessionVersion = sessionVersion; - } - - public int getCounter() { - return this.counter; - } - - public void incrementCounter() { - this.counter++; - } - - public byte[] getLocalFingerprint() { - return this.localFingerprint; - } - - public byte[] getRemoteFingerprint() { - return this.remoteFingerprint; - } - - public IdentityKey getIdentityKey() { - return this.identityKey; - } - - public boolean isVerifiedSession() { - return this.verifiedSessionKey; - } - - private void writeIdentityKey(FileChannel out) throws IOException { - if (identityKey == null) writeBlob(new byte[0], out); - else writeBlob(identityKey.serialize(), out); - } - - private boolean isValidVersionMarker(int versionMarker) { - for (int VALID_VERSION_MARKER : VALID_VERSION_MARKERS) - if (versionMarker == VALID_VERSION_MARKER) - return true; - - return false; - } - - private void readIdentityKey(FileInputStream in) throws IOException { - try { - byte[] blob = readBlob(in); - - if (blob.length == 0) this.identityKey = null; - else this.identityKey = new IdentityKey(blob, 0); - } catch (InvalidKeyException ike) { - throw new AssertionError(ike); - } - } - - public void save() { - synchronized (FILE_LOCK) { - try { - RandomAccessFile file = openRandomAccessFile(); - FileChannel out = file.getChannel(); - out.position(0); - - writeInteger(CURRENT_VERSION_MARKER, out); - writeInteger(counter, out); - writeBlob(localFingerprint, out); - writeBlob(remoteFingerprint, out); - writeInteger(currentSessionVersion, out); - writeIdentityKey(out); - writeInteger(verifiedSessionKey ? 1 : 0, out); - - if (sessionKeyRecord != null) - writeBlob(sessionKeyRecord.serialize(), out); - - out.truncate(out.position()); - file.close(); - } catch (IOException ioe) { - throw new IllegalArgumentException(ioe); - } - } - } - - private void loadData() { - synchronized (FILE_LOCK) { - try { - FileInputStream in = this.openInputStream(); - int versionMarker = readInteger(in); - - // Sigh, always put a version number on everything. - if (!isValidVersionMarker(versionMarker)) { - this.counter = versionMarker; - this.localFingerprint = readBlob(in); - this.remoteFingerprint = readBlob(in); - this.currentSessionVersion = 31337; - - if (in.available() != 0) { - try { - this.sessionKeyRecord = new SessionKey(readBlob(in), masterSecret); - } catch (InvalidMessageException e) { - Log.w("SessionRecord", e); - this.sessionKeyRecord = null; - } - } - - in.close(); - } else { - this.counter = readInteger(in); - this.localFingerprint = readBlob (in); - this.remoteFingerprint = readBlob (in); - this.currentSessionVersion = readInteger(in); - - if (versionMarker >= 0X55555556) { - readIdentityKey(in); - this.verifiedSessionKey = (readInteger(in) == 1); - } - - if (in.available() != 0) { - try { - this.sessionKeyRecord = new SessionKey(readBlob(in), masterSecret); - } catch (InvalidMessageException e) { - Log.w("SessionRecord", e); - this.sessionKeyRecord = null; - } - } - - in.close(); - } - } catch (FileNotFoundException e) { - Log.w("SessionRecord", "No session information found."); - // XXX - } catch (IOException ioe) { - Log.w("keyrecord", ioe); - // XXX - } - } - } - - public SessionKey getSessionKey(int mode, int localKeyId, int remoteKeyId) { - if (this.sessionKeyRecord == null) return null; - - if ((this.sessionKeyRecord.getLocalKeyId() == localKeyId) && - (this.sessionKeyRecord.getRemoteKeyId() == remoteKeyId) && - (this.sessionKeyRecord.getMode() == mode)) - { - return this.sessionKeyRecord; - } - - return null; - } - } diff --git a/library/src/org/whispersystems/textsecure/storage/SessionState.java b/library/src/org/whispersystems/textsecure/storage/SessionState.java index 9e03dcfaa8..9ddc009d66 100644 --- a/library/src/org/whispersystems/textsecure/storage/SessionState.java +++ b/library/src/org/whispersystems/textsecure/storage/SessionState.java @@ -121,9 +121,8 @@ public class SessionState { } public ECKeyPair getSenderEphemeralPair() { - ECPublicKey publicKey = getSenderEphemeral(); - ECPrivateKey privateKey = Curve.decodePrivatePoint(publicKey.getType(), - sessionStructure.getSenderChain() + ECPublicKey publicKey = getSenderEphemeral(); + ECPrivateKey privateKey = Curve.decodePrivatePoint(sessionStructure.getSenderChain() .getSenderEphemeralPrivate() .toByteArray()); @@ -342,8 +341,7 @@ public class SessionState { ECPublicKey publicKey = Curve.decodePoint(sessionStructure.getPendingKeyExchange() .getLocalBaseKey().toByteArray(), 0); - ECPrivateKey privateKey = Curve.decodePrivatePoint(publicKey.getType(), - sessionStructure.getPendingKeyExchange() + ECPrivateKey privateKey = Curve.decodePrivatePoint(sessionStructure.getPendingKeyExchange() .getLocalBaseKeyPrivate() .toByteArray()); @@ -354,8 +352,7 @@ public class SessionState { ECPublicKey publicKey = Curve.decodePoint(sessionStructure.getPendingKeyExchange() .getLocalEphemeralKey().toByteArray(), 0); - ECPrivateKey privateKey = Curve.decodePrivatePoint(publicKey.getType(), - sessionStructure.getPendingKeyExchange() + ECPrivateKey privateKey = Curve.decodePrivatePoint(sessionStructure.getPendingKeyExchange() .getLocalEphemeralKeyPrivate() .toByteArray()); @@ -366,8 +363,7 @@ public class SessionState { IdentityKey publicKey = new IdentityKey(sessionStructure.getPendingKeyExchange() .getLocalIdentityKey().toByteArray(), 0); - ECPrivateKey privateKey = Curve.decodePrivatePoint(publicKey.getPublicKey().getType(), - sessionStructure.getPendingKeyExchange() + ECPrivateKey privateKey = Curve.decodePrivatePoint(sessionStructure.getPendingKeyExchange() .getLocalIdentityKeyPrivate() .toByteArray()); diff --git a/src/org/thoughtcrime/securesms/AutoInitiateActivity.java b/src/org/thoughtcrime/securesms/AutoInitiateActivity.java index 40b1011a38..028d59be2d 100644 --- a/src/org/thoughtcrime/securesms/AutoInitiateActivity.java +++ b/src/org/thoughtcrime/securesms/AutoInitiateActivity.java @@ -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); } } diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java index 577e91105b..a154fc3c38 100644 --- a/src/org/thoughtcrime/securesms/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationActivity.java @@ -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)); } diff --git a/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java b/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java index 025e9d36aa..d53e988c26 100644 --- a/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java +++ b/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java @@ -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"); diff --git a/src/org/thoughtcrime/securesms/VerifyIdentityActivity.java b/src/org/thoughtcrime/securesms/VerifyIdentityActivity.java index 8fe212ba52..ef89115d92 100644 --- a/src/org/thoughtcrime/securesms/VerifyIdentityActivity.java +++ b/src/org/thoughtcrime/securesms/VerifyIdentityActivity.java @@ -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 diff --git a/src/org/thoughtcrime/securesms/ViewLocalIdentityActivity.java b/src/org/thoughtcrime/securesms/ViewLocalIdentityActivity.java index a0e9b49c25..4a0cf56e4c 100644 --- a/src/org/thoughtcrime/securesms/ViewLocalIdentityActivity.java +++ b/src/org/thoughtcrime/securesms/ViewLocalIdentityActivity.java @@ -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(); } diff --git a/src/org/thoughtcrime/securesms/crypto/AsymmetricMasterCipher.java b/src/org/thoughtcrime/securesms/crypto/AsymmetricMasterCipher.java index 1b521e114b..2e0d05b860 100644 --- a/src/org/thoughtcrime/securesms/crypto/AsymmetricMasterCipher.java +++ b/src/org/thoughtcrime/securesms/crypto/AsymmetricMasterCipher.java @@ -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()); diff --git a/src/org/thoughtcrime/securesms/crypto/AsymmetricMasterSecret.java b/src/org/thoughtcrime/securesms/crypto/AsymmetricMasterSecret.java index aa20e5c4f2..1f620be74f 100644 --- a/src/org/thoughtcrime/securesms/crypto/AsymmetricMasterSecret.java +++ b/src/org/thoughtcrime/securesms/crypto/AsymmetricMasterSecret.java @@ -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; } } diff --git a/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java b/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java index 0ac1573b01..e0aa0faf18 100644 --- a/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java +++ b/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java @@ -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); } } } diff --git a/src/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java b/src/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java index ed63b477c4..ddb1d5d95e 100644 --- a/src/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java +++ b/src/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java @@ -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); diff --git a/src/org/thoughtcrime/securesms/crypto/KeyExchangeInitiator.java b/src/org/thoughtcrime/securesms/crypto/KeyExchangeInitiator.java index 3bb1ac9a19..679030cdce 100644 --- a/src/org/thoughtcrime/securesms/crypto/KeyExchangeInitiator.java +++ b/src/org/thoughtcrime/securesms/crypto/KeyExchangeInitiator.java @@ -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(), diff --git a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java index 408d7d44cf..ba1d23dd97 100644 --- a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java +++ b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java @@ -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) { diff --git a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV1.java b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV1.java deleted file mode 100644 index 44367f6a4a..0000000000 --- a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV1.java +++ /dev/null @@ -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); - } - } - -} diff --git a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV2.java b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV2.java index c09b05f067..ce501e5c78 100644 --- a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV2.java +++ b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV2.java @@ -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); diff --git a/src/org/thoughtcrime/securesms/crypto/MasterSecretUtil.java b/src/org/thoughtcrime/securesms/crypto/MasterSecretUtil.java index d1803344f3..d4a585739f 100644 --- a/src/org/thoughtcrime/securesms/crypto/MasterSecretUtil.java +++ b/src/org/thoughtcrime/securesms/crypto/MasterSecretUtil.java @@ -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) { diff --git a/src/org/thoughtcrime/securesms/crypto/PublicKeyAndVersion.java b/src/org/thoughtcrime/securesms/crypto/PublicKeyAndVersion.java deleted file mode 100644 index 39ab4a62ca..0000000000 --- a/src/org/thoughtcrime/securesms/crypto/PublicKeyAndVersion.java +++ /dev/null @@ -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 . - */ -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; - } -} diff --git a/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessage.java b/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessage.java index 00f32b1083..df3cdb4745 100644 --- a/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessage.java +++ b/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessage.java @@ -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); } } \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessageV1.java b/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessageV1.java deleted file mode 100644 index 191c5e6992..0000000000 --- a/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessageV1.java +++ /dev/null @@ -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 - * 2) 4 bits - * 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; - } -} diff --git a/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessageV2.java b/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessageV2.java index 6b64d340b3..32f67b911c 100644 --- a/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessageV2.java +++ b/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessageV2.java @@ -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; diff --git a/src/org/thoughtcrime/securesms/database/IdentityDatabase.java b/src/org/thoughtcrime/securesms/database/IdentityDatabase.java index 1cda2593f8..ce980c6263 100644 --- a/src/org/thoughtcrime/securesms/database/IdentityDatabase.java +++ b/src/org/thoughtcrime/securesms/database/IdentityDatabase.java @@ -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; diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java index c70eaa8671..d56095b0d4 100644 --- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -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(); diff --git a/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java b/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java index bd2ac72b63..57fda6b661 100644 --- a/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java +++ b/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java @@ -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; diff --git a/src/org/thoughtcrime/securesms/database/SmsDatabase.java b/src/org/thoughtcrime/securesms/database/SmsDatabase.java index 62a9bd0603..7140266de2 100644 --- a/src/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -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; diff --git a/src/org/thoughtcrime/securesms/service/PreKeyService.java b/src/org/thoughtcrime/securesms/service/PreKeyService.java index 9296eb51de..b21174e845 100644 --- a/src/org/thoughtcrime/securesms/service/PreKeyService.java +++ b/src/org/thoughtcrime/securesms/service/PreKeyService.java @@ -76,7 +76,7 @@ public class PreKeyService extends Service { List 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..."); diff --git a/src/org/thoughtcrime/securesms/service/RegistrationService.java b/src/org/thoughtcrime/securesms/service/RegistrationService.java index ad69f5f890..780eea0cc3 100644 --- a/src/org/thoughtcrime/securesms/service/RegistrationService.java +++ b/src/org/thoughtcrime/securesms/service/RegistrationService.java @@ -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 records = PreKeyUtil.generatePreKeys(this, masterSecret); PreKeyRecord lastResort = PreKeyUtil.generateLastResortKey(this, masterSecret); socket.registerPreKeys(identityKey, lastResort, records); diff --git a/src/org/thoughtcrime/securesms/service/SmsReceiver.java b/src/org/thoughtcrime/securesms/service/SmsReceiver.java index b43820d79f..786d596865 100644 --- a/src/org/thoughtcrime/securesms/service/SmsReceiver.java +++ b/src/org/thoughtcrime/securesms/service/SmsReceiver.java @@ -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); } } diff --git a/src/org/thoughtcrime/securesms/sms/IncomingKeyExchangeMessage.java b/src/org/thoughtcrime/securesms/sms/IncomingKeyExchangeMessage.java index 56020708bf..e04dd65704 100644 --- a/src/org/thoughtcrime/securesms/sms/IncomingKeyExchangeMessage.java +++ b/src/org/thoughtcrime/securesms/sms/IncomingKeyExchangeMessage.java @@ -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; diff --git a/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java b/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java index 662e98c2e1..ac814c905c 100644 --- a/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java +++ b/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java @@ -179,10 +179,6 @@ public class IncomingTextMessage implements Parcelable { return false; } - public boolean isIdentityUpdate() { - return false; - } - public boolean isPush() { return push; } diff --git a/src/org/thoughtcrime/securesms/transport/PushTransport.java b/src/org/thoughtcrime/securesms/transport/PushTransport.java index 50155c4dce..b6f885e42c 100644 --- a/src/org/thoughtcrime/securesms/transport/PushTransport.java +++ b/src/org/thoughtcrime/securesms/transport/PushTransport.java @@ -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()); diff --git a/src/org/thoughtcrime/securesms/transport/SmsTransport.java b/src/org/thoughtcrime/securesms/transport/SmsTransport.java index 0f337157a8..a8c1023a3d 100644 --- a/src/org/thoughtcrime/securesms/transport/SmsTransport.java +++ b/src/org/thoughtcrime/securesms/transport/SmsTransport.java @@ -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);