Initial Project Import

This commit is contained in:
Moxie Marlinspike
2011-12-20 10:20:44 -08:00
commit bbea3fe1b1
397 changed files with 48065 additions and 0 deletions

View File

@@ -0,0 +1,95 @@
/**
* Copyright (C) 2011 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.sms;
import java.util.Iterator;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.mms.TextSlide;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.service.MmsSender;
import org.thoughtcrime.securesms.service.SendReceiveService;
import org.thoughtcrime.securesms.service.SmsSender;
import ws.com.google.android.mms.ContentType;
import ws.com.google.android.mms.MmsException;
import ws.com.google.android.mms.pdu.EncodedStringValue;
import ws.com.google.android.mms.pdu.PduBody;
import ws.com.google.android.mms.pdu.SendReq;
import android.content.Context;
import android.content.Intent;
import android.telephony.PhoneNumberUtils;
import android.util.Log;
public class MessageSender {
public static long sendMms(Context context, MasterSecret masterSecret, Recipients recipients,
long threadId, SlideDeck slideDeck, String message, boolean isSecure) throws MmsException
{
if (threadId == -1)
threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients);
if (message.trim().length() > 0)
slideDeck.addSlide(new TextSlide(context, message));
SendReq sendRequest = new SendReq();
String[] recipientsArray = recipients.toNumberStringArray();
EncodedStringValue[] encodedNumbers = EncodedStringValue.encodeStrings(recipientsArray);
PduBody body = slideDeck.toPduBody();
sendRequest.setTo(encodedNumbers);
sendRequest.setDate(System.currentTimeMillis() / 1000L);
sendRequest.setBody(body);
sendRequest.setContentType(ContentType.MULTIPART_MIXED.getBytes());
long messageId = DatabaseFactory.getEncryptingMmsDatabase(context, masterSecret).insertMessageSent(sendRequest, threadId, isSecure);
Intent intent = new Intent(SendReceiveService.SEND_MMS_ACTION, null, context, SendReceiveService.class);
intent.putExtra("message_id", messageId);
context.startService(intent);
return threadId;
}
public static long send(Context context, MasterSecret masterSecret, Recipients recipients,
long threadId, String message, boolean isSecure)
{
if (threadId == -1)
threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients);
Iterator<Recipient> i = recipients.getRecipientsList().iterator();
while (i.hasNext()) {
Recipient recipient = i.next();
long messageId;
if (!isSecure) messageId = DatabaseFactory.getEncryptingSmsDatabase(context).insertMessageSent(masterSecret, PhoneNumberUtils.formatNumber(recipient.getNumber()), threadId, message, System.currentTimeMillis());
else messageId = DatabaseFactory.getEncryptingSmsDatabase(context).insertSecureMessageSent(masterSecret, PhoneNumberUtils.formatNumber(recipient.getNumber()), threadId, message, System.currentTimeMillis());
Log.w("SMSSender", "Got message id for new message: " + messageId);
Intent intent = new Intent(SendReceiveService.SEND_SMS_ACTION, null, context, SendReceiveService.class);
intent.putExtra("message_id", messageId);
context.startService(intent);
}
return threadId;
}
}

View File

@@ -0,0 +1,237 @@
/**
* Copyright (C) 2011 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.sms;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import org.thoughtcrime.securesms.protocol.Message;
import org.thoughtcrime.securesms.protocol.Prefix;
import org.thoughtcrime.securesms.protocol.WirePrefix;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.Conversions;
import org.thoughtcrime.securesms.util.Hex;
import android.telephony.SmsManager;
import android.util.Log;
public class MultipartMessageHandler {
private static final int VERSION_OFFSET = 0;
private static final int MULTIPART_OFFSET = 1;
private static final int IDENTIFIER_OFFSET = 2;
private static final int MULTIPART_SUPPORTED_AFTER_VERSION = 1;
private final HashMap<String, byte[][]> partialMessages = new HashMap<String, byte[][]>();
private final HashMap<String, Integer> idMap = new HashMap<String, Integer>();
private String spliceMessage(String prefix, byte[][] messageParts) {
Log.w("MultipartMessageHandler", "Have complete message fragments, splicing...");
int totalMessageLength = 0;
for (int i=0;i<messageParts.length;i++) {
totalMessageLength += messageParts[i].length;
}
byte[] totalMessage = new byte[totalMessageLength];
int totalMessageOffset = 0;
for (int i=0;i<messageParts.length;i++) {
System.arraycopy(messageParts[i], 0, totalMessage, totalMessageOffset, messageParts[i].length);
totalMessageOffset += messageParts[i].length;
}
return prefix + Base64.encodeBytesWithoutPadding(totalMessage);
}
private boolean isComplete(byte[][] partialMessages) {
for (int i=0;i<partialMessages.length;i++)
if (partialMessages[i] == null) return false;
Log.w("MultipartMessageHandler", "Buffer complete!");
return true;
}
private byte[][] findOrAllocateMultipartBuffer(String sender, int identifier, int count) {
String key = sender + identifier;
Log.w("MultipartMessageHandler", "Getting multipart buffer...");
if (partialMessages.containsKey(key)) {
Log.w("MultipartMessageHandler", "Returning existing multipart buffer...");
return partialMessages.get(key);
} else {
Log.w("MultipartMessageHandler", "Creating new multipart buffer: " + count);
byte[][] multipartBuffer = new byte[count][];
partialMessages.put(key, multipartBuffer);
return multipartBuffer;
}
}
private byte[] stripMultipartTransportLayer(int index, byte[] decodedMessage) {
byte[] strippedMessage = new byte[decodedMessage.length - (index == 0 ? 2 : 3)];
int copyDestinationIndex = 0;
int copyDestinationLength = strippedMessage.length;
if (index == 0) {
strippedMessage[0] = decodedMessage[0];
copyDestinationIndex++;
copyDestinationLength--;
}
System.arraycopy(decodedMessage, 3, strippedMessage, copyDestinationIndex, copyDestinationLength);
return strippedMessage;
}
private String processMultipartMessage(String prefix, int index, int count, String sender, int identifier, byte[] decodedMessage) {
Log.w("MultipartMessageHandler", "Processing multipart message...");
decodedMessage = stripMultipartTransportLayer(index, decodedMessage);
byte[][] messageParts = findOrAllocateMultipartBuffer(sender, identifier, count);
messageParts[index] = decodedMessage;
Log.w("MultipartMessageHandler", "Filled buffer at index: " + index);
if (!isComplete(messageParts))
return null;
partialMessages.remove(sender+identifier);
return spliceMessage(prefix, messageParts);
}
private String processSinglePartMessage(String prefix, byte[] decodedMessage) {
Log.w("MultipartMessageHandler", "Processing single part message...");
decodedMessage[MULTIPART_OFFSET] = decodedMessage[VERSION_OFFSET];
return prefix + Base64.encodeBytesWithoutPadding(decodedMessage, 1, decodedMessage.length-1);
}
public String processPotentialMultipartMessage(String prefix, String sender, String message) {
try {
byte[] decodedMessage = Base64.decodeWithoutPadding(message);
int currentVersion = Conversions.highBitsToInt(decodedMessage[VERSION_OFFSET]);
Log.w("MultipartMessageHandler", "Decoded message with version: " + currentVersion);
Log.w("MultipartMessageHandler", "Decoded message: " + Hex.toString(decodedMessage));
if (currentVersion < MULTIPART_SUPPORTED_AFTER_VERSION)
throw new AssertionError("Caller should have checked this.");
int multipartIndex = Conversions.highBitsToInt(decodedMessage[MULTIPART_OFFSET]);
int multipartCount = Conversions.lowBitsToInt(decodedMessage[MULTIPART_OFFSET]);
int identifier = decodedMessage[IDENTIFIER_OFFSET] & 0xFF;
Log.w("MultipartMessageHandler", "Multipart Info: (" + multipartIndex + "/" + multipartCount + ") ID: " + identifier);
if (multipartIndex >= multipartCount)
return message;
if (multipartCount == 1) return processSinglePartMessage(prefix, decodedMessage);
else return processMultipartMessage(prefix, multipartIndex, multipartCount, sender, identifier, decodedMessage);
} catch (IOException e) {
return message;
}
}
private ArrayList<String> buildSingleMessage(byte[] decodedMessage, WirePrefix prefix) {
Log.w("MultipartMessageHandler", "Adding transport info to single-part message...");
ArrayList<String> list = new ArrayList<String>();
byte[] messageWithMultipartHeader = new byte[decodedMessage.length + 1];
System.arraycopy(decodedMessage, 0, messageWithMultipartHeader, 1, decodedMessage.length);
messageWithMultipartHeader[0] = decodedMessage[0];
messageWithMultipartHeader[1] = Conversions.intsToByteHighAndLow(0, 1);
String encodedMessage = Base64.encodeBytesWithoutPadding(messageWithMultipartHeader);
list.add(prefix.calculatePrefix(encodedMessage) + encodedMessage);
Log.w("MultipartMessageHandler", "Complete fragment size: " + list.get(list.size()-1).length());
return list;
}
private byte getIdForRecipient(String recipient) {
Integer currentId;
if (idMap.containsKey(recipient)) {
currentId = idMap.get(recipient);
idMap.remove(recipient);
} else {
currentId = new Integer(0);
}
byte id = currentId.byteValue();
idMap.put(recipient, new Integer((currentId.intValue() + 1) % 255));
return id;
}
private ArrayList<String> buildMultipartMessage(String recipient, byte[] decodedMessage, WirePrefix prefix) {
Log.w("MultipartMessageHandler", "Building multipart message...");
ArrayList<String> list = new ArrayList<String>();
byte versionByte = decodedMessage[0];
int messageOffset = 1;
int segmentIndex = 0;
int segmentCount = SmsTransportDetails.getMessageCountForBytes(decodedMessage.length);
byte id = getIdForRecipient(recipient);
while (messageOffset < decodedMessage.length-1) {
int segmentSize = Math.min(SmsTransportDetails.BASE_MAX_BYTES, decodedMessage.length-messageOffset+3);
byte[] segment = new byte[segmentSize];
segment[0] = versionByte;
segment[1] = Conversions.intsToByteHighAndLow(segmentIndex++, segmentCount);
segment[2] = id;
Log.w("MultipartMessageHandler", "Fragment: (" + segmentIndex + "/" + segmentCount +") -- ID: " + id);
System.arraycopy(decodedMessage, messageOffset, segment, 3, segmentSize-3);
messageOffset += segmentSize-3;
String encodedSegment = Base64.encodeBytesWithoutPadding(segment);
list.add(prefix.calculatePrefix(encodedSegment) + encodedSegment);
Log.w("MultipartMessageHandler", "Complete fragment size: " + list.get(list.size()-1).length());
}
return list;
}
public boolean isManualTransport(String message) {
try {
byte[] decodedMessage = Base64.decodeWithoutPadding(message);
return Conversions.highBitsToInt(decodedMessage[0]) >= MULTIPART_SUPPORTED_AFTER_VERSION;
} catch (IOException ioe) {
throw new AssertionError(ioe);
}
}
public ArrayList<String> divideMessage(String recipient, String message, WirePrefix prefix) {
try {
byte[] decodedMessage = Base64.decodeWithoutPadding(message);
if (decodedMessage.length <= SmsTransportDetails.SINGLE_MESSAGE_MAX_BYTES)
return buildSingleMessage(decodedMessage, prefix);
else
return buildMultipartMessage(recipient, decodedMessage, prefix);
} catch (IOException ioe) {
throw new AssertionError(ioe);
}
}
}

View File

@@ -0,0 +1,135 @@
/**
* Copyright (C) 2011 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.sms;
import java.io.IOException;
import org.thoughtcrime.securesms.crypto.SessionCipher;
import org.thoughtcrime.securesms.crypto.TransportDetails;
import org.thoughtcrime.securesms.protocol.Prefix;
import org.thoughtcrime.securesms.protocol.WirePrefix;
import org.thoughtcrime.securesms.util.Base64;
import android.util.Log;
public class SmsTransportDetails implements TransportDetails {
public static final int SMS_SIZE = 160;
public static final int MULTIPART_SMS_SIZE = 153;
private static final int SINGLE_MESSAGE_MULTIPART_OVERHEAD = 1;
private static final int MULTI_MESSAGE_MULTIPART_OVERHEAD = 3;
private static final int FIRST_MULTI_MESSAGE_MULTIPART_OVERHEAD = 2;
public static final int BASE_MAX_BYTES = Base64.getEncodedBytesForTarget(SMS_SIZE - WirePrefix.PREFIX_SIZE);
public static final int SINGLE_MESSAGE_MAX_BYTES = BASE_MAX_BYTES - SINGLE_MESSAGE_MULTIPART_OVERHEAD;
public static final int MULTI_MESSAGE_MAX_BYTES = BASE_MAX_BYTES - MULTI_MESSAGE_MULTIPART_OVERHEAD;
public static final int FIRST_MULTI_MESSAGE_MAX_BYTES = BASE_MAX_BYTES - FIRST_MULTI_MESSAGE_MULTIPART_OVERHEAD;
public static final int ENCRYPTED_SINGLE_MESSAGE_BODY_MAX_SIZE = SINGLE_MESSAGE_MAX_BYTES - SessionCipher.ENCRYPTED_MESSAGE_OVERHEAD;
// private final int encryptedMessageOverhead;
// private final int encryptedSingleMessageBodyMaxSize;
// public SmsTransportDetails(int encryptedMessageOverhead) {
// this.encryptedMessageOverhead = encryptedMessageOverhead;
// this.encryptedSingleMessageBodyMaxSize = SINGLE_MESSAGE_MAX_BYTES - encryptedMessageOverhead;
// }
public byte[] encodeMessage(byte[] messageWithMac) {
String encodedMessage = Base64.encodeBytesWithoutPadding(messageWithMac);
Log.w("SmsTransportDetails", "Encoded Message Length: " + encodedMessage.length());
return (Prefix.ASYMMETRIC_ENCRYPT + encodedMessage).getBytes();
}
public byte[] decodeMessage(byte[] encodedMessageBytes) throws IOException {
String encodedMessage = new String(encodedMessageBytes);
encodedMessage = encodedMessage.substring(Prefix.ASYMMETRIC_ENCRYPT.length());
return Base64.decodeWithoutPadding(encodedMessage);
}
public byte[] stripPaddedMessage(byte[] messageWithPadding) {
int paddingBeginsIndex = 0;
for (int i=1;i<messageWithPadding.length;i++) {
if (messageWithPadding[i] == (byte)0x00) {
paddingBeginsIndex = i;
break;
}
}
if (paddingBeginsIndex == 0)
return messageWithPadding;
byte[] message = new byte[paddingBeginsIndex];
System.arraycopy(messageWithPadding, 0, message, 0, message.length);
return message;
}
public byte[] getPaddedMessageBody(byte[] messageBody) {
int paddedBodySize;
if (messageBody.length <= ENCRYPTED_SINGLE_MESSAGE_BODY_MAX_SIZE) {
paddedBodySize = ENCRYPTED_SINGLE_MESSAGE_BODY_MAX_SIZE;
} else {
paddedBodySize = getMaxBodySizeForCurrentRecordCount(messageBody.length);
}
Log.w("SessionCipher", "Padding message body out to: " + paddedBodySize);
byte[] paddedBody = new byte[paddedBodySize];
// byte[] bodyBytes = messageBody.getBytes();
assert(messageBody.length <= paddedBody.length);
System.arraycopy(messageBody, 0, paddedBody, 0, messageBody.length);
return paddedBody;
}
public static final int getMessageCountForBytes(int bytes) {
if (bytes <= SINGLE_MESSAGE_MAX_BYTES)
return 1;
bytes = Math.max(bytes - FIRST_MULTI_MESSAGE_MAX_BYTES, 0);
int messageCount = 1 + (bytes / MULTI_MESSAGE_MAX_BYTES);
int remainder = bytes % MULTI_MESSAGE_MAX_BYTES;
if (remainder > 0)
messageCount++;
return messageCount;
}
private int getMaxBodySizeForCurrentRecordCount(int bodyLength) {
int messageRecordsForBody = getMessageCountForBytes(bodyLength + SessionCipher.ENCRYPTED_MESSAGE_OVERHEAD);
if (messageRecordsForBody == 1)
return ENCRYPTED_SINGLE_MESSAGE_BODY_MAX_SIZE;
else
return SmsTransportDetails.FIRST_MULTI_MESSAGE_MAX_BYTES +
(SmsTransportDetails.MULTI_MESSAGE_MAX_BYTES * (messageRecordsForBody-1)) -
SessionCipher.ENCRYPTED_MESSAGE_OVERHEAD;
}
}