Remove V1 code.

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

View File

@@ -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() {

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@@ -0,0 +1,7 @@
package org.whispersystems.textsecure.crypto;
public class LegacyMessageException extends Exception {
public LegacyMessageException(String s) {
super(s);
}
}

View File

@@ -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);
}

View File

@@ -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.");
}

View File

@@ -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;
}
}
}

View File

@@ -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<RootKey, ChainKey> receiverChain = rootKey.createChain(theirEphemeral, ourEphemeral);
ECKeyPair ourNewEphemeral = Curve.generateKeyPairForType(Curve.DJB_TYPE, true);
ECKeyPair ourNewEphemeral = Curve.generateKeyPair(true);
Pair<RootKey, ChainKey> senderChain = receiverChain.first.createChain(theirEphemeral, ourNewEphemeral);
sessionState.setRootKey(senderChain.first);

View File

@@ -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());
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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());
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@@ -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();

View File

@@ -7,6 +7,7 @@ import org.whispersystems.textsecure.crypto.IdentityKey;
import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.InvalidMessageException;
import org.whispersystems.textsecure.crypto.InvalidVersionException;
import org.whispersystems.textsecure.crypto.LegacyMessageException;
import org.whispersystems.textsecure.crypto.ecc.Curve;
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
import org.whispersystems.textsecure.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;
}
}

View File

@@ -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.");
}
}
}

View File

@@ -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;
}
}

View File

@@ -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<RootKey, ChainKey> receivingChain = calculate3DHE(true, ourBaseKey, theirBaseKey, ourIdentityKey, theirIdentityKey);
Pair<RootKey, ChainKey> sendingChain = receivingChain.first.createChain(theirEphemeralKey, sendingKey);

View File

@@ -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);
}
}

View File

@@ -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) {

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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());