Migrate to Curve25519.

1) Generate a Curve25519 identity key.

2) Use Curve25519 ephemerals and identities for v2 3DHE agreements.

3) Initiate v2 key exchange messages.

4) Accept v1 key exchange messages.

5) TOFU Curve25519 identities.
This commit is contained in:
Moxie Marlinspike
2013-11-10 04:15:29 -08:00
parent a03fff8b24
commit c38a8aa699
57 changed files with 2197 additions and 498 deletions

View File

@@ -1,5 +1,6 @@
/**
* 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
@@ -16,25 +17,25 @@
*/
package org.thoughtcrime.securesms.crypto;
import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.InvalidMessageException;
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.ECPublicKey;
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.NoSuchAlgorithmException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
import org.spongycastle.crypto.agreement.ECDHBasicAgreement;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.InvalidMessageException;
import org.whispersystems.textsecure.crypto.KeyUtil;
import org.whispersystems.textsecure.crypto.MasterCipher;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.PublicKey;
import org.whispersystems.textsecure.util.Base64;
import org.whispersystems.textsecure.util.Conversions;
/**
* This class is used to asymmetricly encrypt local data. This is used in the case
* where TextSecure receives an SMS, but the user's local encryption passphrase is
@@ -58,26 +59,23 @@ import org.whispersystems.textsecure.util.Conversions;
public class AsymmetricMasterCipher {
private final AsymmetricMasterSecret asymmetricMasterSecret;
public AsymmetricMasterCipher(AsymmetricMasterSecret asymmetricMasterSecret) {
this.asymmetricMasterSecret = asymmetricMasterSecret;
}
public String decryptBody(String body) throws IOException, InvalidMessageException {
try {
byte[] combined = Base64.decode(body);
PublicKey theirPublicKey = new PublicKey(combined, 0);
byte[] encryptedBodyBytes = new byte[combined.length - PublicKey.KEY_SIZE];
System.arraycopy(combined, PublicKey.KEY_SIZE, encryptedBodyBytes, 0, encryptedBodyBytes.length);
ECDHBasicAgreement agreement = new ECDHBasicAgreement();
agreement.init(asymmetricMasterSecret.getPrivateKey());
BigInteger secret = KeyUtil.calculateAgreement(agreement, theirPublicKey.getKey());
MasterCipher masterCipher = getMasterCipherForSecret(secret);
byte[] decryptedBodyBytes = masterCipher.decryptBytes(encryptedBodyBytes);
return new String(decryptedBodyBytes);
byte[] combined = Base64.decode(body);
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());
byte[] secret = Curve.calculateAgreement(theirPublicKey.getKey(), ourPrivateKey);
MasterCipher masterCipher = getMasterCipherForSecret(secret);
byte[] decryptedBody = masterCipher.decryptBytes(parts[1]);
return new String(decryptedBody);
} catch (InvalidKeyException ike) {
throw new InvalidMessageException(ike);
} catch (InvalidMessageException e) {
@@ -86,26 +84,31 @@ public class AsymmetricMasterCipher {
}
public String encryptBody(String body) {
ECDHBasicAgreement agreement = new ECDHBasicAgreement();
AsymmetricCipherKeyPair keyPair = KeyUtil.generateKeyPair();
agreement.init(keyPair.getPrivate());
BigInteger secret = KeyUtil.calculateAgreement(agreement, asymmetricMasterSecret.getPublicKey().getKey());
MasterCipher masterCipher = getMasterCipherForSecret(secret);
byte[] encryptedBodyBytes = masterCipher.encryptBytes(body.getBytes());
PublicKey publicKey = new PublicKey(31337, (ECPublicKeyParameters)keyPair.getPublic());
byte[] publicKeyBytes = publicKey.serialize();
byte[] combined = new byte[publicKeyBytes.length + encryptedBodyBytes.length];
System.arraycopy(publicKeyBytes, 0, combined, 0, publicKeyBytes.length);
System.arraycopy(encryptedBodyBytes, 0, combined, publicKeyBytes.length, encryptedBodyBytes.length);
return Base64.encodeBytes(combined);
try {
ECPublicKey theirPublic;
if (asymmetricMasterSecret.getDjbPublicKey() != null) {
theirPublic = asymmetricMasterSecret.getDjbPublicKey();
} else {
theirPublic = asymmetricMasterSecret.getNistPublicKey();
}
ECKeyPair ourKeyPair = Curve.generateKeyPairForType(theirPublic.getType());
byte[] secret = Curve.calculateAgreement(theirPublic, ourKeyPair.getPrivateKey());
MasterCipher masterCipher = getMasterCipherForSecret(secret);
byte[] encryptedBodyBytes = masterCipher.encryptBytes(body.getBytes());
PublicKey ourPublicKey = new PublicKey(31337, ourKeyPair.getPublicKey());
byte[] publicKeyBytes = ourPublicKey.serialize();
byte[] combined = Util.combine(publicKeyBytes, encryptedBodyBytes);
return Base64.encodeBytes(combined);
} catch (InvalidKeyException e) {
throw new AssertionError(e);
}
}
private MasterCipher getMasterCipherForSecret(BigInteger secret) {
byte[] secretBytes = secret.toByteArray();
private MasterCipher getMasterCipherForSecret(byte[] secretBytes) {
SecretKeySpec cipherKey = deriveCipherKey(secretBytes);
SecretKeySpec macKey = deriveMacKey(secretBytes);
MasterSecret masterSecret = new MasterSecret(cipherKey, macKey);

View File

@@ -1,5 +1,6 @@
/**
* 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
@@ -16,8 +17,9 @@
*/
package org.thoughtcrime.securesms.crypto;
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.whispersystems.textsecure.crypto.PublicKey;
import org.whispersystems.textsecure.crypto.ecc.Curve;
import org.whispersystems.textsecure.crypto.ecc.ECPrivateKey;
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
/**
* When a user first initializes TextSecure, a few secrets
@@ -38,19 +40,35 @@ import org.whispersystems.textsecure.crypto.PublicKey;
public class AsymmetricMasterSecret {
private final PublicKey publicKey;
private final ECPrivateKeyParameters privateKey;
public AsymmetricMasterSecret(PublicKey publicKey, ECPrivateKeyParameters privateKey) {
this.publicKey = publicKey;
this.privateKey = privateKey;
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)
{
this.djbPublicKey = djbPublicKey;
this.djbPrivateKey = djbPrivateKey;
this.nistPublicKey = nistPublicKey;
this.nistPrivateKey = nistPrivateKey;
}
public PublicKey getPublicKey() {
return publicKey;
public ECPublicKey getDjbPublicKey() {
return djbPublicKey;
}
public ECPrivateKeyParameters getPrivateKey() {
return privateKey;
public ECPublicKey getNistPublicKey() {
return nistPublicKey;
}
public ECPrivateKey getPrivateKey(int type) {
if (type == Curve.DJB_TYPE) {
return djbPrivateKey;
} else {
return nistPrivateKey;
}
}
}

View File

@@ -1,5 +1,6 @@
/**
* 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
@@ -47,6 +48,8 @@ import org.whispersystems.textsecure.crypto.KeyUtil;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.MessageCipher;
import org.whispersystems.textsecure.crypto.SessionCipher;
import org.whispersystems.textsecure.crypto.ecc.Curve;
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
import org.whispersystems.textsecure.push.IncomingPushMessage;
import org.whispersystems.textsecure.util.Hex;
@@ -195,7 +198,7 @@ public class DecryptingQueue {
return;
}
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, Curve.DJB_TYPE);
MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey);
byte[] plaintextBody = messageCipher.decrypt(recipient, message.getBody());
@@ -276,7 +279,7 @@ public class DecryptingQueue {
synchronized (SessionCipher.CIPHER_LOCK) {
Log.w("DecryptingQueue", "Decrypting: " + Hex.toString(ciphertextPduBytes));
TextTransport transportDetails = new TextTransport();
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, Curve.DJB_TYPE);
MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey);
byte[] ciphertext = transportDetails.getDecodedMessage(ciphertextPduBytes);
@@ -360,7 +363,7 @@ public class DecryptingQueue {
}
SmsTransportDetails transportDetails = new SmsTransportDetails();
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, Curve.DJB_TYPE);
MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey);
byte[] ciphertext = transportDetails.getDecodedMessage(body.getBytes());
byte[] paddedPlaintext = messageCipher.decrypt(recipient, ciphertext);

View File

@@ -1,5 +1,6 @@
/**
* 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
@@ -26,17 +27,18 @@ import org.spongycastle.asn1.ASN1Primitive;
import org.spongycastle.asn1.ASN1Sequence;
import org.spongycastle.asn1.DERInteger;
import org.spongycastle.asn1.DERSequence;
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
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.KeyUtil;
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;
@@ -54,19 +56,39 @@ import java.security.NoSuchAlgorithmException;
public class IdentityKeyUtil {
private static final String IDENTITY_PUBLIC_KEY_PREF = "pref_identity_public";
private static final String IDENTITY_PRIVATE_KEY_PREF = "pref_identity_private";
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) {
public static boolean hasIdentityKey(Context context, int type) {
SharedPreferences preferences = context.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0);
return preferences.contains(IDENTITY_PUBLIC_KEY_PREF) && preferences.contains(IDENTITY_PRIVATE_KEY_PREF);
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;
}
public static IdentityKey getIdentityKey(Context context) {
if (!hasIdentityKey(context)) return null;
public static IdentityKey getIdentityKey(Context context, int type) {
if (!hasIdentityKey(context, type)) return null;
try {
byte[] publicKeyBytes = Base64.decode(retrieve(context, IDENTITY_PUBLIC_KEY_PREF));
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));
return new IdentityKey(publicKeyBytes, 0);
} catch (IOException ioe) {
Log.w("IdentityKeyUtil", ioe);
@@ -77,43 +99,78 @@ public class IdentityKeyUtil {
}
}
public static IdentityKeyPair getIdentityKeyPair(Context context, MasterSecret masterSecret) {
if (!hasIdentityKey(context))
public static IdentityKeyPair getIdentityKeyPair(Context context,
MasterSecret masterSecret,
int type)
{
if (!hasIdentityKey(context, type))
return null;
try {
MasterCipher masterCipher = new MasterCipher(masterSecret);
IdentityKey publicKey = getIdentityKey(context);
byte[] privateKeyBytes = Base64.decode(retrieve(context, IDENTITY_PRIVATE_KEY_PREF));
ECPrivateKeyParameters privateKey = masterCipher.decryptKey(privateKeyBytes);
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)));
return new IdentityKeyPair(publicKey, privateKey);
} catch (IOException e) {
throw new AssertionError(e);
} catch (InvalidKeyException e) {
throw new AssertionError(e);
}
}
public static String getFingerprint(Context context) {
if (!hasIdentityKey(context)) return null;
public static String getFingerprint(Context context, int type) {
if (!hasIdentityKey(context, type)) return null;
IdentityKey identityKey = getIdentityKey(context);
IdentityKey identityKey = getIdentityKey(context, type);
if (identityKey == null) return null;
else return identityKey.getFingerprint();
}
public static void generateIdentityKeys(Context context, MasterSecret masterSecret) {
MasterCipher masterCipher = new MasterCipher(masterSecret);
AsymmetricCipherKeyPair keyPair = KeyUtil.generateKeyPair();
IdentityKey identityKey = new IdentityKey((ECPublicKeyParameters)keyPair.getPublic());
byte[] serializedPublicKey = identityKey.serialize();
byte[] serializedPrivateKey = masterCipher.encryptKey((ECPrivateKeyParameters)keyPair.getPrivate());
save(context, IDENTITY_PUBLIC_KEY_PREF, Base64.encodeBytes(serializedPublicKey));
save(context, IDENTITY_PRIVATE_KEY_PREF, Base64.encodeBytes(serializedPrivateKey));
ECKeyPair nistKeyPair = Curve.generateKeyPairForType(Curve.NIST_TYPE);
ECKeyPair djbKeyPair = Curve.generateKeyPairForType(Curve.DJB_TYPE);
MasterCipher masterCipher = new MasterCipher(masterSecret);
IdentityKey nistIdentityKey = new IdentityKey(nistKeyPair.getPublicKey());
IdentityKey djbIdentityKey = new IdentityKey(djbKeyPair.getPublicKey());
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));
}
public static boolean hasCurve25519IdentityKeys(Context context) {
return
retrieve(context, IDENTITY_PUBLIC_KEY_DJB_PREF) != null &&
retrieve(context, IDENTITY_PRIVATE_KEY_DJB_PREF) != null;
}
public static void generateCurve25519IdentityKeys(Context context, MasterSecret masterSecret) {
MasterCipher masterCipher = new MasterCipher(masterSecret);
ECKeyPair djbKeyPair = Curve.generateKeyPairForType(Curve.DJB_TYPE);
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 {
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);
@@ -128,8 +185,12 @@ public class IdentityKeyUtil {
byte[] messageHash = getMessageHash(messageBytes, publicKeyBytes);
IdentityKey identityKey = new IdentityKey(publicKeyBytes, 0);
ECDSASigner verifier = new ECDSASigner();
verifier.init(false, identityKey.getPublicKeyParameters());
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[]{
@@ -148,17 +209,18 @@ public class IdentityKeyUtil {
}
public static byte[] getSignedKeyExchange(Context context, MasterSecret masterSecret, byte[] keyExchangeBytes) {
public static byte[] getSignedKeyExchange(Context context, MasterSecret masterSecret,
byte[] keyExchangeBytes)
{
try {
MasterCipher masterCipher = new MasterCipher(masterSecret);
byte[] publicKeyBytes = getIdentityKey(context).serialize();
byte[] messageHash = getMessageHash(keyExchangeBytes, publicKeyBytes);
byte[] privateKeyBytes = Base64.decode(retrieve(context, IDENTITY_PRIVATE_KEY_PREF));
ECPrivateKeyParameters privateKey = masterCipher.decryptKey(privateKeyBytes);
ECDSASigner signer = new ECDSASigner();
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, privateKey);
signer.init(true, ((NistECPrivateKey)privateKey).getParameters());
BigInteger[] messageSignatureInts = signer.generateSignature(messageHash);
DERInteger[] derMessageSignatureInts = new DERInteger[]{ new DERInteger(messageSignatureInts[0]), new DERInteger(messageSignatureInts[1]) };
@@ -167,12 +229,12 @@ public class IdentityKeyUtil {
Conversions.shortToByteArray(messageSignature, 0, messageSignatureBytes.length);
System.arraycopy(messageSignatureBytes, 0, messageSignature, 2, messageSignatureBytes.length);
byte[] combined = Util.combine(keyExchangeBytes, publicKeyBytes, messageSignature);
return combined;
return Util.combine(keyExchangeBytes, publicKeyBytes, messageSignature);
} catch (IOException ioe) {
throw new AssertionError(ioe);
} catch (InvalidKeyException e) {
throw new AssertionError(e);
}
}

View File

@@ -1,5 +1,6 @@
/**
* 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
@@ -28,6 +29,7 @@ import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage;
import org.whispersystems.textsecure.crypto.KeyUtil;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
import org.whispersystems.textsecure.storage.LocalKeyRecord;
public class KeyExchangeInitiator {
@@ -52,8 +54,8 @@ public class KeyExchangeInitiator {
}
private static void initiateKeyExchange(Context context, MasterSecret masterSecret, Recipient recipient) {
LocalKeyRecord record = KeyUtil.initializeRecordFor(recipient, context, masterSecret);
KeyExchangeMessage message = new KeyExchangeMessage(context, masterSecret, 1, record, 0);
LocalKeyRecord record = KeyUtil.initializeRecordFor(context, masterSecret, recipient, CiphertextMessage.CURVE25519_INTRODUCED_VERSION);
KeyExchangeMessage message = new KeyExchangeMessage(context, masterSecret, CiphertextMessage.CURVE25519_INTRODUCED_VERSION, record, 0);
OutgoingKeyExchangeMessage textMessage = new OutgoingKeyExchangeMessage(recipient, message.serialize());
Log.w("SendKeyActivity", "Sending public key: " + record.getCurrentKeyPair().getPublicKey().getFingerprint());

View File

@@ -1,5 +1,6 @@
/**
* 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
@@ -71,11 +72,7 @@ public class KeyExchangeProcessor {
}
public boolean isTrusted(KeyExchangeMessage message) {
if (!message.hasIdentityKey()) {
return false;
}
return isTrusted(message.getIdentityKey());
return message.hasIdentityKey() && isTrusted(message.getIdentityKey());
}
public boolean isTrusted(PreKeyBundleMessage message) {
@@ -155,7 +152,7 @@ public class KeyExchangeProcessor {
remoteKeyRecord.setLastRemoteKey(remoteKey);
remoteKeyRecord.save();
localKeyRecord = KeyUtil.initializeRecordFor(recipient, context, masterSecret);
localKeyRecord = KeyUtil.initializeRecordFor(context, masterSecret, recipient, CiphertextMessage.SUPPORTED_VERSION);
localKeyRecord.setNextKeyPair(localKeyRecord.getCurrentKeyPair());
localKeyRecord.save();
@@ -176,7 +173,7 @@ public class KeyExchangeProcessor {
message.getPublicKey().setId(initiateKeyId);
if (needsResponseFromUs()) {
localKeyRecord = KeyUtil.initializeRecordFor(recipient, context, masterSecret);
localKeyRecord = KeyUtil.initializeRecordFor(context, masterSecret, recipient, message.getMessageVersion());
KeyExchangeMessage ourMessage = new KeyExchangeMessage(context, masterSecret, Math.min(CiphertextMessage.SUPPORTED_VERSION, message.getMaxVersion()), localKeyRecord, initiateKeyId);
OutgoingKeyExchangeMessage textMessage = new OutgoingKeyExchangeMessage(recipient, ourMessage.serialize());
Log.w("KeyExchangeProcessor", "Responding with key exchange message fingerprint: " + ourMessage.getPublicKey().getFingerprint());

View File

@@ -1,5 +1,6 @@
/**
* 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
@@ -21,14 +22,14 @@ import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.util.Log;
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.KeyPair;
import org.whispersystems.textsecure.crypto.KeyUtil;
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.ECPublicKey;
import org.whispersystems.textsecure.util.Base64;
import org.whispersystems.textsecure.util.Util;
@@ -57,7 +58,11 @@ public class MasterSecretUtil {
public static final String UNENCRYPTED_PASSPHRASE = "unencrypted";
public static final String PREFERENCES_NAME = "SecureSMS-Preferences";
public static final String ASYMMETRIC_LOCAL_PUBLIC = "asymmetric_master_secret_public";
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";
public static MasterSecret changeMasterSecretPassphrase(Context context,
MasterSecret masterSecret,
@@ -86,7 +91,9 @@ public class MasterSecretUtil {
return masterSecret;
}
public static MasterSecret getMasterSecret(Context context, String passphrase) throws InvalidPassphraseException {
public static MasterSecret getMasterSecret(Context context, String passphrase)
throws InvalidPassphraseException
{
try {
byte[] encryptedAndMacdMasterSecret = retrieve(context, "master_secret");
byte[] encryptedMasterSecret = verifyMac(context, encryptedAndMacdMasterSecret, passphrase);
@@ -95,7 +102,7 @@ public class MasterSecretUtil {
byte[] macSecret = getMacSecret(combinedSecrets);
return new MasterSecret(new SecretKeySpec(encryptionSecret, "AES"),
new SecretKeySpec(macSecret, "HmacSHA1"));
new SecretKeySpec(macSecret, "HmacSHA1"));
} catch (GeneralSecurityException e) {
Log.w("keyutil", e);
return null; //XXX
@@ -105,17 +112,43 @@ public class MasterSecretUtil {
}
}
public static AsymmetricMasterSecret getAsymmetricMasterSecret(Context context, MasterSecret masterSecret) {
public static AsymmetricMasterSecret getAsymmetricMasterSecret(Context context,
MasterSecret masterSecret)
{
try {
PublicKey publicKey = new PublicKey(retrieve(context, ASYMMETRIC_LOCAL_PUBLIC));
ECPrivateKeyParameters privateKey = null;
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);
}
if (masterSecret != null) {
MasterCipher masterCipher = new MasterCipher(masterSecret);
privateKey = masterCipher.decryptKey(retrieve(context, "asymmetric_master_secret_private"));
if (nistPrivateBytes != null) {
nistPrivateKey = masterCipher.decryptKey(Curve.NIST_TYPE, nistPrivateBytes);
}
if (djbPrivateBytes != null) {
djbPrivateKey = masterCipher.decryptKey(Curve.DJB_TYPE, djbPrivateBytes);
}
}
return new AsymmetricMasterSecret(publicKey, privateKey);
return new AsymmetricMasterSecret(djbPublicKey, djbPrivateKey, nistPublicKey, nistPrivateKey);
} catch (InvalidKeyException ike) {
throw new AssertionError(ike);
} catch (IOException e) {
@@ -123,17 +156,16 @@ public class MasterSecretUtil {
}
}
public static AsymmetricMasterSecret generateAsymmetricMasterSecret(Context context, MasterSecret masterSecret) {
MasterCipher masterCipher = new MasterCipher(masterSecret);
AsymmetricCipherKeyPair ackp = KeyUtil.generateKeyPair();
KeyPair keyPair = new KeyPair(31337, ackp, masterSecret);
PublicKey publicKey = keyPair.getPublicKey();
ECPrivateKeyParameters privateKey = (ECPrivateKeyParameters)ackp.getPrivate();
public static AsymmetricMasterSecret generateAsymmetricMasterSecret(Context context,
MasterSecret masterSecret)
{
MasterCipher masterCipher = new MasterCipher(masterSecret);
ECKeyPair keyPair = Curve.generateKeyPairForType(Curve.DJB_TYPE);
save(context, ASYMMETRIC_LOCAL_PUBLIC, publicKey.serialize());
save(context, "asymmetric_master_secret_private", masterCipher.encryptKey(privateKey));
save(context, ASYMMETRIC_LOCAL_PUBLIC_DJB, keyPair.getPublicKey().serialize());
save(context, ASYMMETRIC_LOCAL_PRIVATE_DJB, masterCipher.encryptKey(keyPair.getPrivateKey()));
return new AsymmetricMasterSecret(publicKey, privateKey);
return new AsymmetricMasterSecret(keyPair.getPublicKey(), keyPair.getPrivateKey(), null, null);
}
public static MasterSecret generateMasterSecret(Context context, String passphrase) {
@@ -154,7 +186,10 @@ public class MasterSecretUtil {
public static boolean hasAsymmericMasterSecret(Context context) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCES_NAME, 0);
return settings.contains(ASYMMETRIC_LOCAL_PUBLIC);
return
settings.contains(ASYMMETRIC_LOCAL_PUBLIC_NIST) ||
settings.contains(ASYMMETRIC_LOCAL_PUBLIC_DJB);
}
public static boolean isPassphraseInitialized(Context context) {

View File

@@ -1,5 +1,6 @@
/**
* 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
@@ -25,6 +26,8 @@ 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.ecc.ECPublicKey;
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
import org.whispersystems.textsecure.storage.LocalKeyRecord;
import org.whispersystems.textsecure.util.Base64;
@@ -59,8 +62,6 @@ import java.io.IOException;
public class KeyExchangeMessage {
private static final int SUPPORTED_VERSION = CiphertextMessage.SUPPORTED_VERSION;
private final int messageVersion;
private final int supportedVersion;
private final PublicKey publicKey;
@@ -70,7 +71,7 @@ public class KeyExchangeMessage {
public KeyExchangeMessage(Context context, MasterSecret masterSecret, int messageVersion, LocalKeyRecord record, int highIdBits) {
this.publicKey = new PublicKey(record.getCurrentKeyPair().getPublicKey());
this.messageVersion = messageVersion;
this.supportedVersion = SUPPORTED_VERSION;
this.supportedVersion = CiphertextMessage.SUPPORTED_VERSION;
publicKey.setId(publicKey.getId() | (highIdBits << 12));
@@ -80,11 +81,11 @@ public class KeyExchangeMessage {
byte[] serializedBytes;
if (includeIdentityNoSignature(messageVersion, context)) {
byte[] identityKey = IdentityKeyUtil.getIdentityKey(context).serialize();
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);
byte[] prolog = Util.combine(versionBytes, publicKeyBytes);
serializedBytes = IdentityKeyUtil.getSignedKeyExchange(context, masterSecret, prolog);
} else {
@@ -102,14 +103,13 @@ public class KeyExchangeMessage {
this.supportedVersion = Conversions.lowBitsToInt(keyBytes[0]);
this.serialized = messageBody;
if (messageVersion > SUPPORTED_VERSION)
throw new InvalidVersionException("Key exchange with version: " + messageVersion +
" but we only support: " + SUPPORTED_VERSION);
if (messageVersion > CiphertextMessage.SUPPORTED_VERSION)
throw new InvalidVersionException("Key exchange with version: " + messageVersion);
if (messageVersion >= 1)
keyBytes = Base64.decodeWithoutPadding(messageBody);
this.publicKey = new PublicKey(keyBytes, 1);
this.publicKey = new PublicKey(keyBytes, 1);
if (keyBytes.length <= PublicKey.KEY_SIZE + 1) {
this.identityKey = null;
@@ -134,11 +134,11 @@ public class KeyExchangeMessage {
}
private static boolean includeIdentitySignature(int messageVersion, Context context) {
return IdentityKeyUtil.hasIdentityKey(context) && (messageVersion == 1);
return IdentityKeyUtil.hasIdentityKey(context, Curve.NIST_TYPE) && (messageVersion == 1);
}
private static boolean includeIdentityNoSignature(int messageVersion, Context context) {
return IdentityKeyUtil.hasIdentityKey(context) && (messageVersion >= 2);
return IdentityKeyUtil.hasIdentityKey(context, Curve.DJB_TYPE) && (messageVersion >= 2);
}
public PublicKey getPublicKey() {
@@ -152,6 +152,10 @@ public class KeyExchangeMessage {
public int getMaxVersion() {
return supportedVersion;
}
public int getMessageVersion() {
return messageVersion;
}
public boolean hasIdentityKey() {
return identityKey != null;