UX for unencrypted fallback case

This commit is contained in:
Jake McGinty
2014-03-31 18:47:24 -07:00
committed by Moxie Marlinspike
parent 40629a3bcf
commit 832763f695
16 changed files with 213 additions and 80 deletions

View File

@@ -0,0 +1,7 @@
package org.thoughtcrime.securesms.transport;
public class InsecureFallbackApprovalException extends Exception {
public InsecureFallbackApprovalException(String detailMessage) {
super(detailMessage);
}
}

View File

@@ -36,6 +36,7 @@ import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.SessionCipher;
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.Session;
import org.whispersystems.textsecure.util.Hex;
import java.io.IOException;
@@ -62,7 +63,9 @@ public class MmsTransport {
this.radio = MmsRadio.getInstance(context);
}
public MmsSendResult deliver(SendReq message) throws UndeliverableMessageException {
public MmsSendResult deliver(SendReq message) throws UndeliverableMessageException,
InsecureFallbackApprovalException
{
if (TextSecurePreferences.isPushRegistered(context) &&
!TextSecurePreferences.isSmsFallbackEnabled(context))
{
@@ -109,7 +112,7 @@ public class MmsTransport {
}
private MmsSendResult sendMms(SendReq message, boolean usingMmsRadio, boolean useProxy)
throws IOException, UndeliverableMessageException
throws IOException, UndeliverableMessageException, InsecureFallbackApprovalException
{
String number = ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE)).getLine1Number();
boolean upgradedSecure = false;
@@ -141,7 +144,7 @@ public class MmsTransport {
}
}
private SendReq getEncryptedMessage(SendReq pdu) {
private SendReq getEncryptedMessage(SendReq pdu) throws InsecureFallbackApprovalException {
EncodedStringValue[] encodedRecipient = pdu.getTo();
String recipient = encodedRecipient[0].getString();
byte[] pduBytes = new PduComposer(context, pdu).make();
@@ -162,11 +165,16 @@ public class MmsTransport {
return encryptedPdu;
}
private byte[] getEncryptedPdu(MasterSecret masterSecret, String recipientString, byte[] pduBytes) {
private byte[] getEncryptedPdu(MasterSecret masterSecret, String recipientString, byte[] pduBytes) throws InsecureFallbackApprovalException {
try {
TextTransport transportDetails = new TextTransport();
Recipient recipient = RecipientFactory.getRecipientsFromString(context, recipientString, false).getPrimaryRecipient();
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID);
if (!Session.hasEncryptCapableSession(context, masterSecret, recipient, recipientDevice)) {
throw new InsecureFallbackApprovalException("No session exists for this secure message.");
}
SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipientDevice);
CiphertextMessage ciphertextMessage = sessionCipher.encrypt(pduBytes);

View File

@@ -0,0 +1,7 @@
package org.thoughtcrime.securesms.transport;
public class SecureFallbackApprovalException extends Exception {
public SecureFallbackApprovalException(String detailMessage) {
super(detailMessage);
}
}

View File

@@ -33,6 +33,7 @@ import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.SessionCipher;
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.Session;
import java.util.ArrayList;
@@ -46,7 +47,9 @@ public class SmsTransport extends BaseTransport {
this.masterSecret = masterSecret;
}
public void deliver(SmsMessageRecord message) throws UndeliverableMessageException {
public void deliver(SmsMessageRecord message) throws UndeliverableMessageException,
InsecureFallbackApprovalException
{
if (!TextSecurePreferences.isSmsNonDataOutEnabled(context) && !TextSecurePreferences.isSmsFallbackEnabled(context)) {
throw new UndeliverableMessageException("SMS Transport is not enabled!");
}
@@ -58,7 +61,9 @@ public class SmsTransport extends BaseTransport {
}
}
private void deliverSecureMessage(SmsMessageRecord message) throws UndeliverableMessageException {
private void deliverSecureMessage(SmsMessageRecord message) throws UndeliverableMessageException,
InsecureFallbackApprovalException
{
MultipartSmsMessageHandler multipartMessageHandler = new MultipartSmsMessageHandler();
OutgoingTextMessage transportMessage = OutgoingTextMessage.from(message);
@@ -161,9 +166,16 @@ public class SmsTransport extends BaseTransport {
private OutgoingTextMessage getAsymmetricEncrypt(MasterSecret masterSecret,
OutgoingTextMessage message)
throws InsecureFallbackApprovalException
{
Recipient recipient = message.getRecipients().getPrimaryRecipient();
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID);
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(),
RecipientDevice.DEFAULT_DEVICE_ID);
if (!Session.hasEncryptCapableSession(context, masterSecret, recipient, recipientDevice)) {
throw new InsecureFallbackApprovalException("No session exists for this secure message.");
}
String body = message.getMessageBody();
SmsTransportDetails transportDetails = new SmsTransportDetails();
SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipientDevice);

View File

@@ -24,6 +24,7 @@ import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.mms.MmsSendResult;
import org.thoughtcrime.securesms.push.PushServiceSocketFactory;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.sms.IncomingGroupMessage;
import org.thoughtcrime.securesms.sms.IncomingIdentityUpdateMessage;
@@ -36,6 +37,7 @@ import org.whispersystems.textsecure.directory.NotInDirectoryException;
import org.whispersystems.textsecure.push.ContactTokenDetails;
import org.whispersystems.textsecure.push.PushServiceSocket;
import org.whispersystems.textsecure.push.UnregisteredUserException;
import org.whispersystems.textsecure.storage.Session;
import org.whispersystems.textsecure.util.DirectoryUtil;
import org.whispersystems.textsecure.util.InvalidNumberException;
@@ -60,7 +62,8 @@ public class UniversalTransport {
}
public void deliver(SmsMessageRecord message)
throws UndeliverableMessageException, UntrustedIdentityException, RetryLaterException, UserInterventionRequiredException
throws UndeliverableMessageException, UntrustedIdentityException, RetryLaterException,
SecureFallbackApprovalException, InsecureFallbackApprovalException
{
if (!TextSecurePreferences.isPushRegistered(context)) {
smsTransport.deliver(message);
@@ -99,7 +102,8 @@ public class UniversalTransport {
}
public MmsSendResult deliver(SendReq mediaMessage, long threadId)
throws UndeliverableMessageException, RetryLaterException, UntrustedIdentityException, UserInterventionRequiredException
throws UndeliverableMessageException, RetryLaterException, UntrustedIdentityException,
SecureFallbackApprovalException, InsecureFallbackApprovalException
{
if (Util.isEmpty(mediaMessage.getTo())) {
return mmsTransport.deliver(mediaMessage);
@@ -155,28 +159,42 @@ public class UniversalTransport {
}
private MmsSendResult fallbackOrAskApproval(SendReq mediaMessage, String destination)
throws UserInterventionRequiredException, UndeliverableMessageException
throws SecureFallbackApprovalException, UndeliverableMessageException, InsecureFallbackApprovalException
{
boolean isSmsFallbackApprovalRequired = isSmsFallbackApprovalRequired(destination);
if (!isSmsFallbackApprovalRequired) {
Log.i("UniversalTransport", "Falling back to MMS without user intervention");
return mmsTransport.deliver(mediaMessage);
} else {
Log.i("UniversalTransport", "Marking message as pending user approval per their settings");
throw new UserInterventionRequiredException("Pending user approval for fallback to SMS");
try {
Recipient recipient = RecipientFactory.getRecipientsFromString(context, destination, false).getPrimaryRecipient();
boolean isSmsFallbackApprovalRequired = isSmsFallbackApprovalRequired(destination);
if (!isSmsFallbackApprovalRequired) {
Log.w("UniversalTransport", "Falling back to MMS");
return mmsTransport.deliver(mediaMessage);
} else if (!Session.hasEncryptCapableSession(context, masterSecret, recipient)) {
Log.w("UniversalTransport", "Marking message as pending insecure SMS fallback");
throw new InsecureFallbackApprovalException("Pending user approval for fallback to insecure SMS");
} else {
Log.w("UniversalTransport", "Marking message as pending secure SMS fallback");
throw new SecureFallbackApprovalException("Pending user approval for fallback secure to SMS");
}
} catch (RecipientFormattingException rfe) {
throw new UndeliverableMessageException(rfe);
}
}
private void fallbackOrAskApproval(SmsMessageRecord smsMessage, String destination)
throws UserInterventionRequiredException, UndeliverableMessageException
throws SecureFallbackApprovalException, UndeliverableMessageException, InsecureFallbackApprovalException
{
boolean isSmsFallbackApprovalRequired = isSmsFallbackApprovalRequired(destination);
Recipient recipient = smsMessage.getIndividualRecipient();
boolean isSmsFallbackApprovalRequired = isSmsFallbackApprovalRequired(destination);
if (!isSmsFallbackApprovalRequired) {
Log.i("UniversalTransport", "Falling back to SMS without user intervention");
Log.w("UniversalTransport", "Falling back to SMS");
smsTransport.deliver(smsMessage);
} else if (!Session.hasEncryptCapableSession(context, masterSecret, recipient)) {
Log.w("UniversalTransport", "Marking message as pending insecure fallback.");
throw new InsecureFallbackApprovalException("Pending user approval for fallback to insecure SMS");
} else {
Log.i("UniversalTransport", "Marking message as pending user approval per their settings");
throw new UserInterventionRequiredException("Pending user approval for fallback to SMS");
Log.w("UniversalTransport", "Marking message as pending secure fallback.");
throw new SecureFallbackApprovalException("Pending user approval for fallback to secure SMS");
}
}
@@ -218,7 +236,6 @@ public class UniversalTransport {
}
}
public boolean isMultipleRecipients(SendReq mediaMessage) {
int recipientCount = 0;
@@ -267,9 +284,9 @@ public class UniversalTransport {
return directory.isActiveNumber(destination);
} catch (NotInDirectoryException e) {
try {
PushServiceSocket socket = PushServiceSocketFactory.create(context);
String contactToken = DirectoryUtil.getDirectoryServerToken(destination);
ContactTokenDetails registeredUser = socket.getContactTokenDetails(contactToken);
PushServiceSocket socket = PushServiceSocketFactory.create(context);
String contactToken = DirectoryUtil.getDirectoryServerToken(destination);
ContactTokenDetails registeredUser = socket.getContactTokenDetails(contactToken);
if (registeredUser == null) {
registeredUser = new ContactTokenDetails();

View File

@@ -1,7 +0,0 @@
package org.thoughtcrime.securesms.transport;
public class UserInterventionRequiredException extends Exception {
public UserInterventionRequiredException(String detailMessage) {
super(detailMessage);
}
}