Files
Android/src/org/thoughtcrime/securesms/crypto/KeyExchangeMessage.java
Moxie Marlinspike 8524cc5598 Resolved a few MMS issues.
1) Fixed the "Unsupported Encoding!" problem.

2) Workaround for the Sprint issue, where the MMSC is adding a single
   extra byte to the end of each encrypted message.

3) Fixed the "large blob of base64 text" on encrypted MMS problem.
2013-06-15 09:06:15 -07:00

141 lines
4.9 KiB
Java

/**
* 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.crypto;
import android.content.Context;
import android.preference.PreferenceManager;
import android.util.Log;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import org.thoughtcrime.securesms.database.keys.LocalKeyRecord;
import org.thoughtcrime.securesms.protocol.Message;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.Conversions;
import java.io.IOException;
/**
* A class for constructing and parsing key exchange messages.
*
* A key exchange message is basically represented by the following format:
*
* 1) 4 bits <protocol version number>
* 2) 4 bits <max supported protocol version number>
* 3) A serialized public key
* 4) (Optional) An identity key.
* 5) (if #4) A signature over the identity key, version bits, and serialized public key.
*
* A serialized public key is basically represented by the following format:
*
* 1) A 3 byte key ID.
* 2) An ECC key encoded with point compression.
*
* An initiating key ID is initialized with the bottom 12 bits of the ID set. A responding key
* ID does the same, but puts the initiating key ID's bottom 12 bits in the top 12 bits of its
* ID. This is used to correlate key exchange responses.
*
* @author Moxie Marlinspike
*
*/
public class KeyExchangeMessage {
private final int messageVersion;
private final int supportedVersion;
private final PublicKey publicKey;
private final String serialized;
private IdentityKey identityKey;
public KeyExchangeMessage(Context context, MasterSecret masterSecret, int messageVersion, LocalKeyRecord record, int highIdBits) {
this.publicKey = new PublicKey(record.getCurrentKeyPair().getPublicKey());
this.messageVersion = messageVersion;
this.supportedVersion = Message.SUPPORTED_VERSION;
publicKey.setId(publicKey.getId() | (highIdBits << 12));
byte[] publicKeyBytes = publicKey.serialize();
byte[] keyExchangeBytes = new byte[1 + publicKeyBytes.length];
keyExchangeBytes[0] = Conversions.intsToByteHighAndLow(messageVersion, supportedVersion);
System.arraycopy(publicKeyBytes, 0, keyExchangeBytes, 1, publicKeyBytes.length);
if (includeIdentitySignature(messageVersion, context))
keyExchangeBytes = IdentityKeyUtil.getSignedKeyExchange(context, masterSecret, keyExchangeBytes);
if (messageVersion < 1)
this.serialized = Base64.encodeBytes(keyExchangeBytes);
else
this.serialized = Base64.encodeBytesWithoutPadding(keyExchangeBytes);
}
public KeyExchangeMessage(String messageBody) throws InvalidVersionException, InvalidKeyException {
try {
byte[] keyBytes = Base64.decode(messageBody);
this.messageVersion = Conversions.highBitsToInt(keyBytes[0]);
this.supportedVersion = Conversions.lowBitsToInt(keyBytes[0]);
this.serialized = messageBody;
if (messageVersion > Message.SUPPORTED_VERSION)
throw new InvalidVersionException("Key exchange with version: " + messageVersion +
" but we only support: " + Message.SUPPORTED_VERSION);
if (messageVersion >= 1)
keyBytes = Base64.decodeWithoutPadding(messageBody);
this.publicKey = new PublicKey(keyBytes, 1);
if (keyBytes.length <= PublicKey.KEY_SIZE + 1) {
this.identityKey = null;
} else {
try {
this.identityKey = IdentityKeyUtil.verifySignedKeyExchange(keyBytes);
} catch (InvalidKeyException ike) {
Log.w("KeyUtil", ike);
this.identityKey = null;
}
}
} catch (IOException ioe) {
throw new InvalidKeyException(ioe);
}
}
private static boolean includeIdentitySignature(int messageVersion, Context context) {
return IdentityKeyUtil.hasIdentityKey(context) && (messageVersion >= 1);
}
public PublicKey getPublicKey() {
return publicKey;
}
public IdentityKey getIdentityKey() {
return identityKey;
}
public int getMaxVersion() {
return supportedVersion;
}
public boolean hasIdentityKey() {
return identityKey != null;
}
public String serialize() {
return serialized;
}
}