mirror of
https://github.com/oxen-io/session-android.git
synced 2025-03-27 03:02:13 +00:00

1) Generate a Curve25519 identity key. 2) Use Curve25519 ephemerals and identities for v2 3DHE agreements. 3) Initiate v2 key exchange messages. 4) Accept v1 key exchange messages. 5) TOFU Curve25519 identities.
193 lines
9.2 KiB
Java
193 lines
9.2 KiB
Java
/**
|
|
* 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.transport;
|
|
|
|
import android.app.PendingIntent;
|
|
import android.content.Context;
|
|
import android.telephony.SmsManager;
|
|
import android.util.Log;
|
|
|
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
|
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
|
import org.thoughtcrime.securesms.service.SendReceiveService;
|
|
import org.thoughtcrime.securesms.service.SmsDeliveryListener;
|
|
import org.thoughtcrime.securesms.sms.MultipartSmsMessageHandler;
|
|
import org.thoughtcrime.securesms.sms.OutgoingPrekeyBundleMessage;
|
|
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
|
|
import org.thoughtcrime.securesms.sms.SmsTransportDetails;
|
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
|
import org.whispersystems.textsecure.crypto.IdentityKeyPair;
|
|
import org.whispersystems.textsecure.crypto.KeyUtil;
|
|
import org.whispersystems.textsecure.crypto.MasterSecret;
|
|
import org.whispersystems.textsecure.crypto.MessageCipher;
|
|
import org.whispersystems.textsecure.crypto.ecc.Curve;
|
|
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
|
|
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
|
|
import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage;
|
|
|
|
import java.util.ArrayList;
|
|
|
|
public class SmsTransport extends BaseTransport {
|
|
|
|
private final Context context;
|
|
private final MasterSecret masterSecret;
|
|
|
|
public SmsTransport(Context context, MasterSecret masterSecret) {
|
|
this.context = context.getApplicationContext();
|
|
this.masterSecret = masterSecret;
|
|
}
|
|
|
|
public void deliver(SmsMessageRecord message) throws UndeliverableMessageException {
|
|
if (message.isSecure() || message.isKeyExchange()) {
|
|
deliverSecureMessage(message);
|
|
} else {
|
|
deliverPlaintextMessage(message);
|
|
}
|
|
}
|
|
|
|
private void deliverSecureMessage(SmsMessageRecord message) throws UndeliverableMessageException {
|
|
MultipartSmsMessageHandler multipartMessageHandler = new MultipartSmsMessageHandler();
|
|
OutgoingTextMessage transportMessage = OutgoingTextMessage.from(message);
|
|
|
|
if (message.isSecure()) {
|
|
transportMessage = getAsymmetricEncrypt(masterSecret, transportMessage);
|
|
}
|
|
|
|
ArrayList<String> messages = multipartMessageHandler.divideMessage(transportMessage);
|
|
ArrayList<PendingIntent> sentIntents = constructSentIntents(message.getId(), message.getType(), messages);
|
|
ArrayList<PendingIntent> deliveredIntents = constructDeliveredIntents(message.getId(), message.getType(), messages);
|
|
|
|
Log.w("SmsTransport", "Secure divide into message parts: " + messages.size());
|
|
|
|
for (int i=0;i<messages.size();i++) {
|
|
// XXX moxie@thoughtcrime.org 1/7/11 -- There's apparently a bug where for some unknown recipients
|
|
// and messages, this will throw an NPE. I have no idea why, so I'm just catching it and marking
|
|
// the message as a failure. That way at least it doesn't repeatedly crash every time you start
|
|
// the app.
|
|
// d3sre 12/10/13 -- extended the log file to further analyse the problem
|
|
try {
|
|
SmsManager.getDefault().sendTextMessage(message.getIndividualRecipient().getNumber(), null, messages.get(i),
|
|
sentIntents.get(i),
|
|
deliveredIntents == null ? null : deliveredIntents.get(i));
|
|
} catch (NullPointerException npe) {
|
|
Log.w("SmsTransport", npe);
|
|
Log.w("SmsTransport", "Recipient: " + message.getIndividualRecipient().getNumber());
|
|
Log.w("SmsTransport", "Message Total Parts/Current: " + messages.size() + "/" + i);
|
|
Log.w("SmsTransport", "Message Part Length: " + messages.get(i).getBytes().length);
|
|
throw new UndeliverableMessageException(npe);
|
|
} catch (IllegalArgumentException iae) {
|
|
Log.w("SmsTransport", iae);
|
|
throw new UndeliverableMessageException(iae);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
private void deliverPlaintextMessage(SmsMessageRecord message)
|
|
throws UndeliverableMessageException
|
|
{
|
|
ArrayList<String> messages = SmsManager.getDefault().divideMessage(message.getBody().getBody());
|
|
ArrayList<PendingIntent> sentIntents = constructSentIntents(message.getId(), message.getType(), messages);
|
|
ArrayList<PendingIntent> deliveredIntents = constructDeliveredIntents(message.getId(), message.getType(), messages);
|
|
String recipient = message.getIndividualRecipient().getNumber();
|
|
|
|
// XXX moxie@thoughtcrime.org 1/7/11 -- There's apparently a bug where for some unknown recipients
|
|
// and messages, this will throw an NPE. I have no idea why, so I'm just catching it and marking
|
|
// the message as a failure. That way at least it doesn't repeatedly crash every time you start
|
|
// the app.
|
|
// d3sre 12/10/13 -- extended the log file to further analyse the problem
|
|
try {
|
|
SmsManager.getDefault().sendMultipartTextMessage(recipient, null, messages, sentIntents, deliveredIntents);
|
|
} catch (NullPointerException npe) {
|
|
Log.w("SmsTransport", npe);
|
|
Log.w("SmsTransport", "Recipient: " + recipient);
|
|
Log.w("SmsTransport", "Message Parts: " + messages.size());
|
|
|
|
try {
|
|
for (int i=0;i<messages.size();i++) {
|
|
SmsManager.getDefault().sendTextMessage(recipient, null, messages.get(i),
|
|
sentIntents.get(i),
|
|
deliveredIntents == null ? null : deliveredIntents.get(i));
|
|
}
|
|
} catch (NullPointerException npe2) {
|
|
Log.w("SmsTransport", npe);
|
|
throw new UndeliverableMessageException(npe2);
|
|
}
|
|
}
|
|
}
|
|
|
|
private ArrayList<PendingIntent> constructSentIntents(long messageId, long type, ArrayList<String> messages) {
|
|
ArrayList<PendingIntent> sentIntents = new ArrayList<PendingIntent>(messages.size());
|
|
|
|
for (String ignored : messages) {
|
|
sentIntents.add(PendingIntent.getBroadcast(context, 0,
|
|
constructSentIntent(context, messageId, type),
|
|
0));
|
|
}
|
|
|
|
return sentIntents;
|
|
}
|
|
|
|
private ArrayList<PendingIntent> constructDeliveredIntents(long messageId, long type, ArrayList<String> messages) {
|
|
if (!TextSecurePreferences.isSmsDeliveryReportsEnabled(context)) {
|
|
return null;
|
|
}
|
|
|
|
ArrayList<PendingIntent> deliveredIntents = new ArrayList<PendingIntent>(messages.size());
|
|
|
|
for (String ignored : messages) {
|
|
deliveredIntents.add(PendingIntent.getBroadcast(context, 0,
|
|
constructDeliveredIntent(context, messageId, type),
|
|
0));
|
|
}
|
|
|
|
return deliveredIntents;
|
|
}
|
|
|
|
private OutgoingTextMessage getAsymmetricEncrypt(MasterSecret masterSecret,
|
|
OutgoingTextMessage message)
|
|
{
|
|
Recipient recipient = message.getRecipients().getPrimaryRecipient();
|
|
String body = message.getMessageBody();
|
|
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret,
|
|
Curve.DJB_TYPE);
|
|
|
|
SmsTransportDetails transportDetails = new SmsTransportDetails();
|
|
|
|
if (KeyUtil.isNonPrekeySessionFor(context, masterSecret, recipient)) {
|
|
Log.w("SmsTransport", "Delivering standard ciphertext...");
|
|
|
|
MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey);
|
|
byte[] paddedPlaintext = transportDetails.getPaddedMessageBody(body.getBytes());
|
|
CiphertextMessage ciphertextMessage = messageCipher.encrypt(recipient, paddedPlaintext);
|
|
String ciphertxt = new String(transportDetails.getEncodedMessage(ciphertextMessage.serialize()));
|
|
|
|
return message.withBody(ciphertxt);
|
|
} else {
|
|
Log.w("SmsTransport", "Delivering prekeybundle ciphertext...");
|
|
MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey);
|
|
CiphertextMessage ciphertextMessage = messageCipher.encrypt(recipient, body.getBytes());
|
|
PreKeyBundleMessage preKeyBundleMessage = new PreKeyBundleMessage(ciphertextMessage, identityKey.getPublicKey());
|
|
byte[] cipherText = preKeyBundleMessage.serialize();
|
|
|
|
return new OutgoingPrekeyBundleMessage(message, new String(transportDetails.getEncodedMessage(cipherText)));
|
|
}
|
|
}
|
|
}
|