mirror of
https://github.com/oxen-io/session-android.git
synced 2025-10-26 09:59:28 +00:00
Remove encrypted SMS transport, simplify transport options.
Closes #2647 // FREEBIE
This commit is contained in:
@@ -1,89 +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.thoughtcrime.securesms.crypto;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore;
|
||||
import org.thoughtcrime.securesms.crypto.storage.TextSecurePreKeyStore;
|
||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||
import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.Dialogs;
|
||||
import org.whispersystems.libaxolotl.AxolotlAddress;
|
||||
import org.whispersystems.libaxolotl.SessionBuilder;
|
||||
import org.whispersystems.libaxolotl.protocol.KeyExchangeMessage;
|
||||
import org.whispersystems.libaxolotl.state.IdentityKeyStore;
|
||||
import org.whispersystems.libaxolotl.state.PreKeyStore;
|
||||
import org.whispersystems.libaxolotl.state.SessionRecord;
|
||||
import org.whispersystems.libaxolotl.state.SessionStore;
|
||||
import org.whispersystems.libaxolotl.state.SignedPreKeyStore;
|
||||
import org.whispersystems.textsecure.api.push.TextSecureAddress;
|
||||
|
||||
public class KeyExchangeInitiator {
|
||||
|
||||
public static void initiate(final Context context, final MasterSecret masterSecret, final Recipient recipient, boolean promptOnExisting) {
|
||||
if (promptOnExisting && hasInitiatedSession(context, masterSecret, recipient)) {
|
||||
AlertDialog.Builder dialog = new AlertDialog.Builder(context);
|
||||
dialog.setTitle(R.string.KeyExchangeInitiator_initiate_despite_existing_request_question);
|
||||
dialog.setMessage(R.string.KeyExchangeInitiator_youve_already_sent_a_session_initiation_request_to_this_recipient_are_you_sure);
|
||||
dialog.setIcon(Dialogs.resolveIcon(context, R.attr.dialog_alert_icon));
|
||||
dialog.setCancelable(true);
|
||||
dialog.setPositiveButton(R.string.KeyExchangeInitiator_send, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
initiateKeyExchange(context, masterSecret, recipient);
|
||||
}
|
||||
});
|
||||
dialog.setNegativeButton(android.R.string.cancel, null);
|
||||
dialog.show();
|
||||
} else {
|
||||
initiateKeyExchange(context, masterSecret, recipient);
|
||||
}
|
||||
}
|
||||
|
||||
private static void initiateKeyExchange(Context context, MasterSecret masterSecret, Recipient recipient) {
|
||||
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
|
||||
PreKeyStore preKeyStore = new TextSecurePreKeyStore(context, masterSecret);
|
||||
SignedPreKeyStore signedPreKeyStore = new TextSecurePreKeyStore(context, masterSecret);
|
||||
IdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(context, masterSecret);
|
||||
|
||||
SessionBuilder sessionBuilder = new SessionBuilder(sessionStore, preKeyStore, signedPreKeyStore,
|
||||
identityKeyStore, new AxolotlAddress(recipient.getNumber(),
|
||||
TextSecureAddress.DEFAULT_DEVICE_ID));
|
||||
|
||||
KeyExchangeMessage keyExchangeMessage = sessionBuilder.process();
|
||||
String serializedMessage = Base64.encodeBytesWithoutPadding(keyExchangeMessage.serialize());
|
||||
OutgoingKeyExchangeMessage textMessage = new OutgoingKeyExchangeMessage(recipient, serializedMessage);
|
||||
|
||||
MessageSender.send(context, masterSecret, textMessage, -1, false);
|
||||
}
|
||||
|
||||
private static boolean hasInitiatedSession(Context context, MasterSecret masterSecret,
|
||||
Recipient recipient)
|
||||
{
|
||||
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
|
||||
SessionRecord sessionRecord = sessionStore.loadSession(new AxolotlAddress(recipient.getNumber(), TextSecureAddress.DEFAULT_DEVICE_ID));
|
||||
|
||||
return sessionRecord.getSessionState().hasPendingKeyExchange();
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
package org.thoughtcrime.securesms.crypto;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.mms.TextTransport;
|
||||
import org.thoughtcrime.securesms.protocol.WirePrefix;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
||||
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.libaxolotl.AxolotlAddress;
|
||||
import org.whispersystems.libaxolotl.DuplicateMessageException;
|
||||
import org.whispersystems.libaxolotl.InvalidMessageException;
|
||||
import org.whispersystems.libaxolotl.LegacyMessageException;
|
||||
import org.whispersystems.libaxolotl.NoSessionException;
|
||||
import org.whispersystems.libaxolotl.SessionCipher;
|
||||
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
|
||||
import org.whispersystems.libaxolotl.protocol.WhisperMessage;
|
||||
import org.whispersystems.libaxolotl.state.AxolotlStore;
|
||||
import org.whispersystems.libaxolotl.util.guava.Optional;
|
||||
import org.whispersystems.textsecure.api.push.TextSecureAddress;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import ws.com.google.android.mms.ContentType;
|
||||
import ws.com.google.android.mms.pdu.EncodedStringValue;
|
||||
import ws.com.google.android.mms.pdu.MultimediaMessagePdu;
|
||||
import ws.com.google.android.mms.pdu.PduBody;
|
||||
import ws.com.google.android.mms.pdu.PduComposer;
|
||||
import ws.com.google.android.mms.pdu.PduParser;
|
||||
import ws.com.google.android.mms.pdu.PduPart;
|
||||
import ws.com.google.android.mms.pdu.RetrieveConf;
|
||||
import ws.com.google.android.mms.pdu.SendReq;
|
||||
|
||||
public class MmsCipher {
|
||||
|
||||
private static final String TAG = MmsCipher.class.getSimpleName();
|
||||
|
||||
private final TextTransport textTransport = new TextTransport();
|
||||
private final AxolotlStore axolotlStore;
|
||||
|
||||
public MmsCipher(AxolotlStore axolotlStore) {
|
||||
this.axolotlStore = axolotlStore;
|
||||
}
|
||||
|
||||
public MultimediaMessagePdu decrypt(Context context, MultimediaMessagePdu pdu)
|
||||
throws InvalidMessageException, LegacyMessageException, DuplicateMessageException,
|
||||
NoSessionException
|
||||
{
|
||||
try {
|
||||
SessionCipher sessionCipher = new SessionCipher(axolotlStore, new AxolotlAddress(pdu.getFrom().getString(), TextSecureAddress.DEFAULT_DEVICE_ID));
|
||||
Optional<byte[]> ciphertext = getEncryptedData(pdu);
|
||||
|
||||
if (!ciphertext.isPresent()) {
|
||||
throw new InvalidMessageException("No ciphertext present!");
|
||||
}
|
||||
|
||||
byte[] decodedCiphertext = textTransport.getDecodedMessage(ciphertext.get());
|
||||
byte[] plaintext;
|
||||
|
||||
if (decodedCiphertext == null) {
|
||||
throw new InvalidMessageException("failed to decode ciphertext");
|
||||
}
|
||||
|
||||
try {
|
||||
plaintext = sessionCipher.decrypt(new WhisperMessage(decodedCiphertext));
|
||||
} catch (InvalidMessageException e) {
|
||||
// NOTE - For some reason, Sprint seems to append a single character to the
|
||||
// end of message text segments. I don't know why, so here we just try
|
||||
// truncating the message by one if the MAC fails.
|
||||
if (ciphertext.get().length > 2) {
|
||||
Log.w(TAG, "Attempting truncated decrypt...");
|
||||
byte[] truncated = Util.trim(ciphertext.get(), ciphertext.get().length - 1);
|
||||
decodedCiphertext = textTransport.getDecodedMessage(truncated);
|
||||
plaintext = sessionCipher.decrypt(new WhisperMessage(decodedCiphertext));
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
MultimediaMessagePdu plaintextGenericPdu = (MultimediaMessagePdu) new PduParser(plaintext).parse();
|
||||
return new RetrieveConf(plaintextGenericPdu.getPduHeaders(), plaintextGenericPdu.getBody());
|
||||
} catch (IOException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public SendReq encrypt(Context context, SendReq message)
|
||||
throws NoSessionException, RecipientFormattingException, UndeliverableMessageException
|
||||
{
|
||||
EncodedStringValue[] encodedRecipient = message.getTo();
|
||||
String recipientString = encodedRecipient[0].getString();
|
||||
byte[] pduBytes = new PduComposer(context, message).make();
|
||||
|
||||
if (pduBytes == null) {
|
||||
throw new UndeliverableMessageException("PDU composition failed, null payload");
|
||||
}
|
||||
|
||||
if (!axolotlStore.containsSession(new AxolotlAddress(recipientString, TextSecureAddress.DEFAULT_DEVICE_ID))) {
|
||||
throw new NoSessionException("No session for: " + recipientString);
|
||||
}
|
||||
|
||||
SessionCipher cipher = new SessionCipher(axolotlStore, new AxolotlAddress(recipientString, TextSecureAddress.DEFAULT_DEVICE_ID));
|
||||
CiphertextMessage ciphertextMessage = cipher.encrypt(pduBytes);
|
||||
byte[] encryptedPduBytes = textTransport.getEncodedMessage(ciphertextMessage.serialize());
|
||||
|
||||
PduBody body = new PduBody();
|
||||
PduPart part = new PduPart();
|
||||
SendReq encryptedPdu = new SendReq(message.getPduHeaders(), body);
|
||||
|
||||
part.setContentId((System.currentTimeMillis()+"").getBytes());
|
||||
part.setContentType(ContentType.TEXT_PLAIN.getBytes());
|
||||
part.setName((System.currentTimeMillis()+"").getBytes());
|
||||
part.setData(encryptedPduBytes);
|
||||
body.addPart(part);
|
||||
encryptedPdu.setSubject(new EncodedStringValue(WirePrefix.calculateEncryptedMmsSubject()));
|
||||
encryptedPdu.setBody(body);
|
||||
|
||||
return encryptedPdu;
|
||||
}
|
||||
|
||||
|
||||
private Optional<byte[]> getEncryptedData(MultimediaMessagePdu pdu) {
|
||||
for (int i=0;i<pdu.getBody().getPartsNum();i++) {
|
||||
if (new String(pdu.getBody().getPart(i).getContentType()).equals(ContentType.TEXT_PLAIN)) {
|
||||
return Optional.of(pdu.getBody().getPart(i).getData());
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -15,6 +15,10 @@ public class SecurityEvent {
|
||||
|
||||
public static final String SECURITY_UPDATE_EVENT = "org.thoughtcrime.securesms.KEY_EXCHANGE_UPDATE";
|
||||
|
||||
public static void broadcastSecurityUpdateEvent(Context context) {
|
||||
broadcastSecurityUpdateEvent(context, -2);
|
||||
}
|
||||
|
||||
public static void broadcastSecurityUpdateEvent(Context context, long threadId) {
|
||||
Intent intent = new Intent(SECURITY_UPDATE_EVENT);
|
||||
intent.putExtra("thread_id", threadId);
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user