Rearrange decrypt API.

1) Change SessionBuilder to only establish sessions via
   KeyExchangeMessage and PreKeyBundles.

2) Change SessionCipher to decrypt either WhisperMessage
   or PreKeyWhisperMessage items, automatically building
   a session for the latter.

3) Change SessionCipher to tear down new sessions built
   with PreKeyWhisperMessages if the embedded WhsiperMessage
   fails to decrypt.
This commit is contained in:
Moxie Marlinspike
2014-07-20 14:35:46 -07:00
parent 42cf53e487
commit 819982af7b
15 changed files with 276 additions and 174 deletions

View File

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

View File

@@ -89,31 +89,35 @@ public class SessionBuilder {
* @throws org.whispersystems.libaxolotl.InvalidKeyException when the message is formatted incorrectly.
* @throws org.whispersystems.libaxolotl.UntrustedIdentityException when the {@link IdentityKey} of the sender is untrusted.
*/
public void process(PreKeyWhisperMessage message)
/*package*/ boolean process(PreKeyWhisperMessage message)
throws InvalidKeyIdException, InvalidKeyException, UntrustedIdentityException
{
int messageVersion = message.getMessageVersion();
IdentityKey theirIdentityKey = message.getIdentityKey();
boolean createdSession;
if (!identityKeyStore.isTrustedIdentity(recipientId, theirIdentityKey)) {
throw new UntrustedIdentityException();
}
if (messageVersion == 2) processV2(message);
else if (messageVersion == 3) processV3(message);
if (messageVersion == 2) createdSession = processV2(message);
else if (messageVersion == 3) createdSession = processV3(message);
else throw new AssertionError("Unknown version: " + messageVersion);
identityKeyStore.saveIdentity(recipientId, theirIdentityKey);
return createdSession;
}
private void processV3(PreKeyWhisperMessage message)
private boolean processV3(PreKeyWhisperMessage message)
throws UntrustedIdentityException, InvalidKeyIdException, InvalidKeyException
{
SessionRecord sessionRecord = sessionStore.loadSession(recipientId, deviceId);
if (sessionRecord.hasSessionState(message.getMessageVersion(), message.getBaseKey().serialize())) {
Log.w(TAG, "We've already setup a session for this V3 message, letting bundled message fall through...");
return;
return false;
}
boolean simultaneousInitiate = sessionRecord.getSessionState().hasUnacknowledgedPreKeyMessage();
@@ -152,9 +156,11 @@ public class SessionBuilder {
if (message.getPreKeyId() >= 0 && message.getPreKeyId() != Medium.MAX_VALUE) {
preKeyStore.removePreKey(message.getPreKeyId());
}
return true;
}
private void processV2(PreKeyWhisperMessage message)
private boolean processV2(PreKeyWhisperMessage message)
throws UntrustedIdentityException, InvalidKeyIdException, InvalidKeyException
{
@@ -162,7 +168,7 @@ public class SessionBuilder {
sessionStore.containsSession(recipientId, deviceId))
{
Log.w(TAG, "We've already processed the prekey part of this V2 session, letting bundled message fall through...");
return;
return false;
}
SessionRecord sessionRecord = sessionStore.loadSession(recipientId, deviceId);
@@ -194,6 +200,8 @@ public class SessionBuilder {
if (message.getPreKeyId() != Medium.MAX_VALUE) {
preKeyStore.removePreKey(message.getPreKeyId());
}
return true;
}
/**

View File

@@ -25,9 +25,12 @@ import org.whispersystems.libaxolotl.protocol.WhisperMessage;
import org.whispersystems.libaxolotl.ratchet.ChainKey;
import org.whispersystems.libaxolotl.ratchet.MessageKeys;
import org.whispersystems.libaxolotl.ratchet.RootKey;
import org.whispersystems.libaxolotl.state.IdentityKeyStore;
import org.whispersystems.libaxolotl.state.PreKeyStore;
import org.whispersystems.libaxolotl.state.SessionRecord;
import org.whispersystems.libaxolotl.state.SessionState;
import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.libaxolotl.state.SignedPreKeyStore;
import org.whispersystems.libaxolotl.util.ByteUtil;
import org.whispersystems.libaxolotl.util.Pair;
@@ -58,9 +61,10 @@ public class SessionCipher {
private static final Object SESSION_LOCK = new Object();
private final SessionStore sessionStore;
private final long recipientId;
private final int deviceId;
private final SessionStore sessionStore;
private final SessionBuilder sessionBuilder;
private final long recipientId;
private final int deviceId;
/**
* Construct a SessionCipher for encrypt/decrypt operations on a session.
@@ -71,10 +75,15 @@ public class SessionCipher {
* @param recipientId The remote ID that messages will be encrypted to or decrypted from.
* @param deviceId The device corresponding to the recipientId.
*/
public SessionCipher(SessionStore sessionStore, long recipientId, int deviceId) {
this.sessionStore = sessionStore;
this.recipientId = recipientId;
this.deviceId = deviceId;
public SessionCipher(SessionStore sessionStore, PreKeyStore preKeyStore,
SignedPreKeyStore signedPreKeyStore, IdentityKeyStore identityKeyStore,
long recipientId, int deviceId)
{
this.sessionStore = sessionStore;
this.recipientId = recipientId;
this.deviceId = deviceId;
this.sessionBuilder = new SessionBuilder(sessionStore, preKeyStore, signedPreKeyStore,
identityKeyStore, recipientId, deviceId);
}
/**
@@ -115,6 +124,26 @@ public class SessionCipher {
}
}
public byte[] decrypt(PreKeyWhisperMessage ciphertext)
throws DuplicateMessageException, LegacyMessageException, InvalidMessageException,
InvalidKeyIdException, InvalidKeyException, UntrustedIdentityException, NoSessionException
{
synchronized (SESSION_LOCK) {
boolean sessionCreated = sessionBuilder.process(ciphertext);
try {
return decrypt(ciphertext.getWhisperMessage());
} catch (InvalidMessageException | DuplicateMessageException | LegacyMessageException e) {
if (sessionCreated) {
sessionStore.deleteSession(recipientId, deviceId);
}
throw e;
}
}
}
/**
* Decrypt a message.
*
@@ -126,10 +155,15 @@ public class SessionCipher {
* @throws LegacyMessageException if the input is a message formatted by a protocol version that
* is no longer supported.
*/
public byte[] decrypt(byte[] ciphertext)
throws InvalidMessageException, DuplicateMessageException, LegacyMessageException
public byte[] decrypt(WhisperMessage ciphertext)
throws InvalidMessageException, DuplicateMessageException, LegacyMessageException, NoSessionException
{
synchronized (SESSION_LOCK) {
if (!sessionStore.containsSession(recipientId, deviceId)) {
throw new NoSessionException("No session for: " + recipientId + ", " + deviceId);
}
SessionRecord sessionRecord = sessionStore.loadSession(recipientId, deviceId);
SessionState sessionState = sessionRecord.getSessionState();
List<SessionState> previousStates = sessionRecord.getPreviousSessionStates();
@@ -159,15 +193,13 @@ public class SessionCipher {
}
}
private byte[] decrypt(SessionState sessionState, byte[] decodedMessage)
private byte[] decrypt(SessionState sessionState, WhisperMessage ciphertextMessage)
throws InvalidMessageException, DuplicateMessageException, LegacyMessageException
{
if (!sessionState.hasSenderChain()) {
throw new InvalidMessageException("Uninitialized session!");
}
WhisperMessage ciphertextMessage = new WhisperMessage(decodedMessage);
if (ciphertextMessage.getMessageVersion() != sessionState.getSessionVersion()) {
throw new InvalidMessageException(String.format("Message version %d, but session version %d",
ciphertextMessage.getMessageVersion(),