From 327f82be41058ca1eaf1b501dc91426f8555d892 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Tue, 3 Mar 2015 11:20:36 -0800 Subject: Use more generic AxololAddress for identifying recipients. --- .../whispersystems/libaxolotl/AxolotlAddress.java | 39 +++++++++++++++++ .../whispersystems/libaxolotl/SessionBuilder.java | 50 ++++++++++------------ .../whispersystems/libaxolotl/SessionCipher.java | 41 ++++++++---------- .../libaxolotl/groups/SenderKeyName.java | 27 +++++------- .../libaxolotl/state/IdentityKeyStore.java | 8 ++-- .../libaxolotl/state/SessionStore.java | 30 ++++++------- 6 files changed, 110 insertions(+), 85 deletions(-) create mode 100644 java/src/main/java/org/whispersystems/libaxolotl/AxolotlAddress.java (limited to 'java/src/main') diff --git a/java/src/main/java/org/whispersystems/libaxolotl/AxolotlAddress.java b/java/src/main/java/org/whispersystems/libaxolotl/AxolotlAddress.java new file mode 100644 index 00000000..9d0476c0 --- /dev/null +++ b/java/src/main/java/org/whispersystems/libaxolotl/AxolotlAddress.java @@ -0,0 +1,39 @@ +package org.whispersystems.libaxolotl; + +public class AxolotlAddress { + + private final String name; + private final int deviceId; + + public AxolotlAddress(String name, int deviceId) { + this.name = name; + this.deviceId = deviceId; + } + + public String getName() { + return name; + } + + public int getDeviceId() { + return deviceId; + } + + @Override + public String toString() { + return name + ":" + deviceId; + } + + @Override + public boolean equals(Object other) { + if (other == null) return false; + if (!(other instanceof AxolotlAddress)) return false; + + AxolotlAddress that = (AxolotlAddress)other; + return this.name.equals(that.name) && this.deviceId == that.deviceId; + } + + @Override + public int hashCode() { + return this.name.hashCode() ^ this.deviceId; + } +} diff --git a/java/src/main/java/org/whispersystems/libaxolotl/SessionBuilder.java b/java/src/main/java/org/whispersystems/libaxolotl/SessionBuilder.java index 736d9ab1..826fcb44 100644 --- a/java/src/main/java/org/whispersystems/libaxolotl/SessionBuilder.java +++ b/java/src/main/java/org/whispersystems/libaxolotl/SessionBuilder.java @@ -49,8 +49,7 @@ public class SessionBuilder { private final PreKeyStore preKeyStore; private final SignedPreKeyStore signedPreKeyStore; private final IdentityKeyStore identityKeyStore; - private final long recipientId; - private final int deviceId; + private final AxolotlAddress remoteAddress; /** * Constructs a SessionBuilder. @@ -58,31 +57,28 @@ public class SessionBuilder { * @param sessionStore The {@link org.whispersystems.libaxolotl.state.SessionStore} to store the constructed session in. * @param preKeyStore The {@link org.whispersystems.libaxolotl.state.PreKeyStore} where the client's local {@link org.whispersystems.libaxolotl.state.PreKeyRecord}s are stored. * @param identityKeyStore The {@link org.whispersystems.libaxolotl.state.IdentityKeyStore} containing the client's identity key information. - * @param recipientId The recipient ID of the remote user to build a session with. - * @param deviceId The device ID of the remote user's physical device. + * @param remoteAddress The address of the remote user to build a session with. */ public SessionBuilder(SessionStore sessionStore, PreKeyStore preKeyStore, SignedPreKeyStore signedPreKeyStore, IdentityKeyStore identityKeyStore, - long recipientId, int deviceId) + AxolotlAddress remoteAddress) { this.sessionStore = sessionStore; this.preKeyStore = preKeyStore; this.signedPreKeyStore = signedPreKeyStore; this.identityKeyStore = identityKeyStore; - this.recipientId = recipientId; - this.deviceId = deviceId; + this.remoteAddress = remoteAddress; } /** * Constructs a SessionBuilder * @param store The {@link org.whispersystems.libaxolotl.state.AxolotlStore} to store all state information in. - * @param recipientId The recipient ID of the remote user to build a session with. - * @param deviceId The device ID of the remote user's physical device. + * @param remoteAddress The address of the remote user to build a session with. */ - public SessionBuilder(AxolotlStore store, long recipientId, int deviceId) { - this(store, store, store, store, recipientId, deviceId); + public SessionBuilder(AxolotlStore store, AxolotlAddress remoteAddress) { + this(store, store, store, store, remoteAddress); } /** @@ -107,7 +103,7 @@ public class SessionBuilder { Optional unsignedPreKeyId; - if (!identityKeyStore.isTrustedIdentity(recipientId, theirIdentityKey)) { + if (!identityKeyStore.isTrustedIdentity(remoteAddress.getName(), theirIdentityKey)) { throw new UntrustedIdentityException(); } @@ -117,7 +113,7 @@ public class SessionBuilder { default: throw new AssertionError("Unknown version: " + messageVersion); } - identityKeyStore.saveIdentity(recipientId, theirIdentityKey); + identityKeyStore.saveIdentity(remoteAddress.getName(), theirIdentityKey); return unsignedPreKeyId; } @@ -169,7 +165,7 @@ public class SessionBuilder { } if (!preKeyStore.containsPreKey(message.getPreKeyId().get()) && - sessionStore.containsSession(recipientId, deviceId)) + sessionStore.containsSession(remoteAddress)) { Log.w(TAG, "We've already processed the prekey part of this V2 session, letting bundled message fall through..."); return Optional.absent(); @@ -214,7 +210,7 @@ public class SessionBuilder { */ public void process(PreKeyBundle preKey) throws InvalidKeyException, UntrustedIdentityException { synchronized (SessionCipher.SESSION_LOCK) { - if (!identityKeyStore.isTrustedIdentity(recipientId, preKey.getIdentityKey())) { + if (!identityKeyStore.isTrustedIdentity(remoteAddress.getName(), preKey.getIdentityKey())) { throw new UntrustedIdentityException(); } @@ -231,7 +227,7 @@ public class SessionBuilder { } boolean supportsV3 = preKey.getSignedPreKey() != null; - SessionRecord sessionRecord = sessionStore.loadSession(recipientId, deviceId); + SessionRecord sessionRecord = sessionStore.loadSession(remoteAddress); ECKeyPair ourBaseKey = Curve.generateKeyPair(); ECPublicKey theirSignedPreKey = supportsV3 ? preKey.getSignedPreKey() : preKey.getPreKey(); Optional theirOneTimePreKey = Optional.fromNullable(preKey.getPreKey()); @@ -258,8 +254,8 @@ public class SessionBuilder { sessionRecord.getSessionState().setRemoteRegistrationId(preKey.getRegistrationId()); sessionRecord.getSessionState().setAliceBaseKey(ourBaseKey.getPublicKey().serialize()); - sessionStore.storeSession(recipientId, deviceId, sessionRecord); - identityKeyStore.saveIdentity(recipientId, preKey.getIdentityKey()); + sessionStore.storeSession(remoteAddress, sessionRecord); + identityKeyStore.saveIdentity(remoteAddress.getName(), preKey.getIdentityKey()); } } @@ -275,7 +271,7 @@ public class SessionBuilder { throws InvalidKeyException, UntrustedIdentityException, StaleKeyExchangeException { synchronized (SessionCipher.SESSION_LOCK) { - if (!identityKeyStore.isTrustedIdentity(recipientId, message.getIdentityKey())) { + if (!identityKeyStore.isTrustedIdentity(remoteAddress.getName(), message.getIdentityKey())) { throw new UntrustedIdentityException(); } @@ -290,7 +286,7 @@ public class SessionBuilder { private KeyExchangeMessage processInitiate(KeyExchangeMessage message) throws InvalidKeyException { int flags = KeyExchangeMessage.RESPONSE_FLAG; - SessionRecord sessionRecord = sessionStore.loadSession(recipientId, deviceId); + SessionRecord sessionRecord = sessionStore.loadSession(remoteAddress); if (message.getVersion() >= 3 && !Curve.verifySignature(message.getIdentityKey().getPublicKey(), @@ -325,8 +321,8 @@ public class SessionBuilder { Math.min(message.getMaxVersion(), CiphertextMessage.CURRENT_VERSION), parameters); - sessionStore.storeSession(recipientId, deviceId, sessionRecord); - identityKeyStore.saveIdentity(recipientId, message.getIdentityKey()); + sessionStore.storeSession(remoteAddress, sessionRecord); + identityKeyStore.saveIdentity(remoteAddress.getName(), message.getIdentityKey()); byte[] baseKeySignature = Curve.calculateSignature(parameters.getOurIdentityKey().getPrivateKey(), parameters.getOurBaseKey().getPublicKey().serialize()); @@ -341,7 +337,7 @@ public class SessionBuilder { private void processResponse(KeyExchangeMessage message) throws StaleKeyExchangeException, InvalidKeyException { - SessionRecord sessionRecord = sessionStore.loadSession(recipientId, deviceId); + SessionRecord sessionRecord = sessionStore.loadSession(remoteAddress); SessionState sessionState = sessionRecord.getSessionState(); boolean hasPendingKeyExchange = sessionState.hasPendingKeyExchange(); boolean isSimultaneousInitiateResponse = message.isResponseForSimultaneousInitiate(); @@ -375,8 +371,8 @@ public class SessionBuilder { throw new InvalidKeyException("Base key signature doesn't match!"); } - sessionStore.storeSession(recipientId, deviceId, sessionRecord); - identityKeyStore.saveIdentity(recipientId, message.getIdentityKey()); + sessionStore.storeSession(remoteAddress, sessionRecord); + identityKeyStore.saveIdentity(remoteAddress.getName(), message.getIdentityKey()); } @@ -394,10 +390,10 @@ public class SessionBuilder { ECKeyPair ratchetKey = Curve.generateKeyPair(); IdentityKeyPair identityKey = identityKeyStore.getIdentityKeyPair(); byte[] baseKeySignature = Curve.calculateSignature(identityKey.getPrivateKey(), baseKey.getPublicKey().serialize()); - SessionRecord sessionRecord = sessionStore.loadSession(recipientId, deviceId); + SessionRecord sessionRecord = sessionStore.loadSession(remoteAddress); sessionRecord.getSessionState().setPendingKeyExchange(sequence, baseKey, ratchetKey, identityKey); - sessionStore.storeSession(recipientId, deviceId, sessionRecord); + sessionStore.storeSession(remoteAddress, sessionRecord); return new KeyExchangeMessage(2, sequence, flags, baseKey.getPublicKey(), baseKeySignature, ratchetKey.getPublicKey(), identityKey.getPublicKey()); diff --git a/java/src/main/java/org/whispersystems/libaxolotl/SessionCipher.java b/java/src/main/java/org/whispersystems/libaxolotl/SessionCipher.java index 381dedb8..b9cc6ac1 100644 --- a/java/src/main/java/org/whispersystems/libaxolotl/SessionCipher.java +++ b/java/src/main/java/org/whispersystems/libaxolotl/SessionCipher.java @@ -67,8 +67,7 @@ public class SessionCipher { private final SessionStore sessionStore; private final SessionBuilder sessionBuilder; private final PreKeyStore preKeyStore; - private final long recipientId; - private final int deviceId; + private final AxolotlAddress remoteAddress; /** * Construct a SessionCipher for encrypt/decrypt operations on a session. @@ -76,23 +75,21 @@ public class SessionCipher { * and stored using {@link SessionBuilder}. * * @param sessionStore The {@link SessionStore} that contains a session for this recipient. - * @param recipientId The remote ID that messages will be encrypted to or decrypted from. - * @param deviceId The device corresponding to the recipientId. + * @param remoteAddress The remote address that messages will be encrypted to or decrypted from. */ public SessionCipher(SessionStore sessionStore, PreKeyStore preKeyStore, SignedPreKeyStore signedPreKeyStore, IdentityKeyStore identityKeyStore, - long recipientId, int deviceId) + AxolotlAddress remoteAddress) { this.sessionStore = sessionStore; - this.recipientId = recipientId; - this.deviceId = deviceId; this.preKeyStore = preKeyStore; + this.remoteAddress = remoteAddress; this.sessionBuilder = new SessionBuilder(sessionStore, preKeyStore, signedPreKeyStore, - identityKeyStore, recipientId, deviceId); + identityKeyStore, remoteAddress); } - public SessionCipher(AxolotlStore store, long recipientId, int deviceId) { - this(store, store, store, store, recipientId, deviceId); + public SessionCipher(AxolotlStore store, AxolotlAddress remoteAddress) { + this(store, store, store, store, remoteAddress); } /** @@ -103,7 +100,7 @@ public class SessionCipher { */ public CiphertextMessage encrypt(byte[] paddedMessage) { synchronized (SESSION_LOCK) { - SessionRecord sessionRecord = sessionStore.loadSession(recipientId, deviceId); + SessionRecord sessionRecord = sessionStore.loadSession(remoteAddress); SessionState sessionState = sessionRecord.getSessionState(); ChainKey chainKey = sessionState.getSenderChainKey(); MessageKeys messageKeys = chainKey.getMessageKeys(); @@ -129,7 +126,7 @@ public class SessionCipher { } sessionState.setSenderChainKey(chainKey.getNextChainKey()); - sessionStore.storeSession(recipientId, deviceId, sessionRecord); + sessionStore.storeSession(remoteAddress, sessionRecord); return ciphertextMessage; } } @@ -182,13 +179,13 @@ public class SessionCipher { InvalidKeyIdException, InvalidKeyException, UntrustedIdentityException { synchronized (SESSION_LOCK) { - SessionRecord sessionRecord = sessionStore.loadSession(recipientId, deviceId); + SessionRecord sessionRecord = sessionStore.loadSession(remoteAddress); Optional unsignedPreKeyId = sessionBuilder.process(sessionRecord, ciphertext); byte[] plaintext = decrypt(sessionRecord, ciphertext.getWhisperMessage()); callback.handlePlaintext(plaintext); - sessionStore.storeSession(recipientId, deviceId, sessionRecord); + sessionStore.storeSession(remoteAddress, sessionRecord); if (unsignedPreKeyId.isPresent()) { preKeyStore.removePreKey(unsignedPreKeyId.get()); @@ -241,16 +238,16 @@ public class SessionCipher { { synchronized (SESSION_LOCK) { - if (!sessionStore.containsSession(recipientId, deviceId)) { - throw new NoSessionException("No session for: " + recipientId + ", " + deviceId); + if (!sessionStore.containsSession(remoteAddress)) { + throw new NoSessionException("No session for: " + remoteAddress); } - SessionRecord sessionRecord = sessionStore.loadSession(recipientId, deviceId); + SessionRecord sessionRecord = sessionStore.loadSession(remoteAddress); byte[] plaintext = decrypt(sessionRecord, ciphertext); callback.handlePlaintext(plaintext); - sessionStore.storeSession(recipientId, deviceId, sessionRecord); + sessionStore.storeSession(remoteAddress, sessionRecord); return plaintext; } @@ -325,18 +322,18 @@ public class SessionCipher { public int getRemoteRegistrationId() { synchronized (SESSION_LOCK) { - SessionRecord record = sessionStore.loadSession(recipientId, deviceId); + SessionRecord record = sessionStore.loadSession(remoteAddress); return record.getSessionState().getRemoteRegistrationId(); } } public int getSessionVersion() { synchronized (SESSION_LOCK) { - if (!sessionStore.containsSession(recipientId, deviceId)) { - throw new IllegalStateException(String.format("No session for (%d, %d)!", recipientId, deviceId)); + if (!sessionStore.containsSession(remoteAddress)) { + throw new IllegalStateException(String.format("No session for (%s)!", remoteAddress)); } - SessionRecord record = sessionStore.loadSession(recipientId, deviceId); + SessionRecord record = sessionStore.loadSession(remoteAddress); return record.getSessionState().getSessionVersion(); } } diff --git a/java/src/main/java/org/whispersystems/libaxolotl/groups/SenderKeyName.java b/java/src/main/java/org/whispersystems/libaxolotl/groups/SenderKeyName.java index ce4325ff..cf2e48f2 100644 --- a/java/src/main/java/org/whispersystems/libaxolotl/groups/SenderKeyName.java +++ b/java/src/main/java/org/whispersystems/libaxolotl/groups/SenderKeyName.java @@ -16,35 +16,31 @@ */ package org.whispersystems.libaxolotl.groups; +import org.whispersystems.libaxolotl.AxolotlAddress; + /** * A representation of a (groupId + senderId + deviceId) tuple. */ public class SenderKeyName { - private final String groupId; - private final long senderId; - private final int deviceId; + private final String groupId; + private final AxolotlAddress sender; - public SenderKeyName(String groupId, long senderId, int deviceId) { + public SenderKeyName(String groupId, AxolotlAddress sender) { this.groupId = groupId; - this.senderId = senderId; - this.deviceId = deviceId; + this.sender = sender; } public String getGroupId() { return groupId; } - public long getSenderId() { - return senderId; - } - - public int getDeviceId() { - return deviceId; + public AxolotlAddress getSender() { + return sender; } public String serialize() { - return groupId + "::" + String.valueOf(senderId) + "::" + String.valueOf(deviceId); + return groupId + "::" + sender.getName() + "::" + String.valueOf(sender.getDeviceId()); } @Override @@ -56,13 +52,12 @@ public class SenderKeyName { return this.groupId.equals(that.groupId) && - this.senderId == that.senderId && - this.deviceId == that.deviceId; + this.sender.equals(that.sender); } @Override public int hashCode() { - return this.groupId.hashCode() ^ (int)this.senderId ^ this.deviceId; + return this.groupId.hashCode() ^ this.sender.hashCode(); } } diff --git a/java/src/main/java/org/whispersystems/libaxolotl/state/IdentityKeyStore.java b/java/src/main/java/org/whispersystems/libaxolotl/state/IdentityKeyStore.java index d2024f78..86e18adf 100644 --- a/java/src/main/java/org/whispersystems/libaxolotl/state/IdentityKeyStore.java +++ b/java/src/main/java/org/whispersystems/libaxolotl/state/IdentityKeyStore.java @@ -32,10 +32,10 @@ public interface IdentityKeyStore { *

* Store a remote client's identity key as trusted. * - * @param recipientId The recipient ID of the remote client. + * @param name The name of the remote client. * @param identityKey The remote client's identity key. */ - public void saveIdentity(long recipientId, IdentityKey identityKey); + public void saveIdentity(String name, IdentityKey identityKey); /** @@ -48,10 +48,10 @@ public interface IdentityKeyStore { * store. Only if it mismatches an entry in the local store is it considered * 'untrusted.' * - * @param recipientId The recipient ID of the remote client. + * @param name The name of the remote client. * @param identityKey The identity key to verify. * @return true if trusted, false if untrusted. */ - public boolean isTrustedIdentity(long recipientId, IdentityKey identityKey); + public boolean isTrustedIdentity(String name, IdentityKey identityKey); } diff --git a/java/src/main/java/org/whispersystems/libaxolotl/state/SessionStore.java b/java/src/main/java/org/whispersystems/libaxolotl/state/SessionStore.java index c5ad00b0..a2a20786 100644 --- a/java/src/main/java/org/whispersystems/libaxolotl/state/SessionStore.java +++ b/java/src/main/java/org/whispersystems/libaxolotl/state/SessionStore.java @@ -1,5 +1,7 @@ package org.whispersystems.libaxolotl.state; +import org.whispersystems.libaxolotl.AxolotlAddress; + import java.util.List; /** @@ -19,50 +21,46 @@ public interface SessionStore { * durable session state (what is returned by subsequent calls to this method) without the * store method being called here first. * - * @param recipientId The recipientID of the remote client. - * @param deviceId The deviceID of the remote client. + * @param address The name and device ID of the remote client. * @return a copy of the SessionRecord corresponding to the recipientId + deviceId tuple, or * a new SessionRecord if one does not currently exist. */ - public SessionRecord loadSession(long recipientId, int deviceId); + public SessionRecord loadSession(AxolotlAddress address); /** * Returns all known devices with active sessions for a recipient * - * @param recipientId the recipient ID. + * @param name the name of the client. * @return all known sub-devices with active sessions. */ - public List getSubDeviceSessions(long recipientId); + public List getSubDeviceSessions(String name); /** * Commit to storage the {@link SessionRecord} for a given recipientId + deviceId tuple. - * @param recipientId the recipient ID of the remote client. - * @param deviceId the device ID of the remote client. + * @param address the address of the remote client. * @param record the current SessionRecord for the remote client. */ - public void storeSession(long recipientId, int deviceId, SessionRecord record); + public void storeSession(AxolotlAddress address, SessionRecord record); /** * Determine whether there is a committed {@link SessionRecord} for a recipientId + deviceId tuple. - * @param recipientId the recipient ID of the remote client. - * @param deviceId the device ID of the remote client. + * @param address the address of the remote client. * @return true if a {@link SessionRecord} exists, false otherwise. */ - public boolean containsSession(long recipientId, int deviceId); + public boolean containsSession(AxolotlAddress address); /** * Remove a {@link SessionRecord} for a recipientId + deviceId tuple. * - * @param recipientId the recipient ID of the remote client. - * @param deviceId the device ID of the remote client. + * @param address the address of the remote client. */ - public void deleteSession(long recipientId, int deviceId); + public void deleteSession(AxolotlAddress address); /** * Remove the {@link SessionRecord}s corresponding to all devices of a recipientId. * - * @param recipientId the recipient ID of the remote client. + * @param name the name of the remote client. */ - public void deleteAllSessions(long recipientId); + public void deleteAllSessions(String name); } -- cgit v1.2.3