session-android/src/org/thoughtcrime/securesms/crypto/SmsCipher.java
Moxie Marlinspike 5602a3dfc0 Update to latest version of libtextsecure for simplified interface.
1) Switch to new TextSecureAddress addressing, rather than mixing
   long-based recipient IDs into libtextsecure.

2) Get rid of RecipientFormattingException throws in calls to
   RecipientFactory.

Closes #2570
2015-03-03 12:25:09 -08:00

128 lines
6.4 KiB
Java

package org.thoughtcrime.securesms.crypto;
import android.content.Context;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage;
import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage;
import org.thoughtcrime.securesms.sms.IncomingPreKeyBundleMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage;
import org.thoughtcrime.securesms.sms.OutgoingPrekeyBundleMessage;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.sms.SmsTransportDetails;
import org.whispersystems.libaxolotl.AxolotlAddress;
import org.whispersystems.libaxolotl.DuplicateMessageException;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.InvalidKeyIdException;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.libaxolotl.InvalidVersionException;
import org.whispersystems.libaxolotl.LegacyMessageException;
import org.whispersystems.libaxolotl.NoSessionException;
import org.whispersystems.libaxolotl.SessionBuilder;
import org.whispersystems.libaxolotl.SessionCipher;
import org.whispersystems.libaxolotl.StaleKeyExchangeException;
import org.whispersystems.libaxolotl.UntrustedIdentityException;
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
import org.whispersystems.libaxolotl.protocol.KeyExchangeMessage;
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
import org.whispersystems.libaxolotl.protocol.WhisperMessage;
import org.whispersystems.libaxolotl.state.AxolotlStore;
import org.whispersystems.textsecure.api.push.TextSecureAddress;
import java.io.IOException;
public class SmsCipher {
private final SmsTransportDetails transportDetails = new SmsTransportDetails();
private final AxolotlStore axolotlStore;
public SmsCipher(AxolotlStore axolotlStore) {
this.axolotlStore = axolotlStore;
}
public IncomingTextMessage decrypt(Context context, IncomingTextMessage message)
throws LegacyMessageException, InvalidMessageException,
DuplicateMessageException, NoSessionException
{
try {
byte[] decoded = transportDetails.getDecodedMessage(message.getMessageBody().getBytes());
WhisperMessage whisperMessage = new WhisperMessage(decoded);
SessionCipher sessionCipher = new SessionCipher(axolotlStore, new AxolotlAddress(message.getSender(), TextSecureAddress.DEFAULT_DEVICE_ID));
byte[] padded = sessionCipher.decrypt(whisperMessage);
byte[] plaintext = transportDetails.getStrippedPaddingMessageBody(padded);
if (message.isEndSession() && "TERMINATE".equals(new String(plaintext))) {
axolotlStore.deleteSession(new AxolotlAddress(message.getSender(), TextSecureAddress.DEFAULT_DEVICE_ID));
}
return message.withMessageBody(new String(plaintext));
} catch (IOException e) {
throw new InvalidMessageException(e);
}
}
public IncomingEncryptedMessage decrypt(Context context, IncomingPreKeyBundleMessage message)
throws InvalidVersionException, InvalidMessageException, DuplicateMessageException,
UntrustedIdentityException, LegacyMessageException
{
try {
byte[] decoded = transportDetails.getDecodedMessage(message.getMessageBody().getBytes());
PreKeyWhisperMessage preKeyMessage = new PreKeyWhisperMessage(decoded);
SessionCipher sessionCipher = new SessionCipher(axolotlStore, new AxolotlAddress(message.getSender(), TextSecureAddress.DEFAULT_DEVICE_ID));
byte[] padded = sessionCipher.decrypt(preKeyMessage);
byte[] plaintext = transportDetails.getStrippedPaddingMessageBody(padded);
return new IncomingEncryptedMessage(message, new String(plaintext));
} catch (IOException | InvalidKeyException | InvalidKeyIdException e) {
throw new InvalidMessageException(e);
}
}
public OutgoingTextMessage encrypt(OutgoingTextMessage message) throws NoSessionException {
byte[] paddedBody = transportDetails.getPaddedMessageBody(message.getMessageBody().getBytes());
String recipientNumber = message.getRecipients().getPrimaryRecipient().getNumber();
if (!axolotlStore.containsSession(new AxolotlAddress(recipientNumber, TextSecureAddress.DEFAULT_DEVICE_ID))) {
throw new NoSessionException("No session for: " + recipientNumber);
}
SessionCipher cipher = new SessionCipher(axolotlStore, new AxolotlAddress(recipientNumber, TextSecureAddress.DEFAULT_DEVICE_ID));
CiphertextMessage ciphertextMessage = cipher.encrypt(paddedBody);
String encodedCiphertext = new String(transportDetails.getEncodedMessage(ciphertextMessage.serialize()));
if (ciphertextMessage.getType() == CiphertextMessage.PREKEY_TYPE) {
return new OutgoingPrekeyBundleMessage(message, encodedCiphertext);
} else {
return message.withBody(encodedCiphertext);
}
}
public OutgoingKeyExchangeMessage process(Context context, IncomingKeyExchangeMessage message)
throws UntrustedIdentityException, StaleKeyExchangeException,
InvalidVersionException, LegacyMessageException, InvalidMessageException
{
try {
Recipient recipient = RecipientFactory.getRecipientsFromString(context, message.getSender(), false).getPrimaryRecipient();
AxolotlAddress axolotlAddress = new AxolotlAddress(message.getSender(), TextSecureAddress.DEFAULT_DEVICE_ID);
KeyExchangeMessage exchangeMessage = new KeyExchangeMessage(transportDetails.getDecodedMessage(message.getMessageBody().getBytes()));
SessionBuilder sessionBuilder = new SessionBuilder(axolotlStore, axolotlAddress);
KeyExchangeMessage response = sessionBuilder.process(exchangeMessage);
if (response != null) {
byte[] serializedResponse = transportDetails.getEncodedMessage(response.serialize());
return new OutgoingKeyExchangeMessage(recipient, new String(serializedResponse));
} else {
return null;
}
} catch (IOException | InvalidKeyException e) {
throw new InvalidMessageException(e);
}
}
}