Collapse KeyExchangeMessage and KeyExchangeProcessor interfaces.
parent
14b8f97de2
commit
a1db221caf
@ -1,254 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.crypto;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
|
|
||||||
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessageV2;
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
|
||||||
import org.thoughtcrime.securesms.service.PreKeyService;
|
|
||||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
|
||||||
import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage;
|
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
|
||||||
import org.whispersystems.libaxolotl.IdentityKey;
|
|
||||||
import org.whispersystems.libaxolotl.IdentityKeyPair;
|
|
||||||
import org.whispersystems.libaxolotl.InvalidKeyException;
|
|
||||||
import org.whispersystems.libaxolotl.InvalidMessageException;
|
|
||||||
import org.whispersystems.libaxolotl.ecc.Curve;
|
|
||||||
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
|
|
||||||
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
|
|
||||||
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
|
|
||||||
import org.whispersystems.libaxolotl.ratchet.RatchetingSession;
|
|
||||||
import org.whispersystems.libaxolotl.state.SessionRecord;
|
|
||||||
import org.whispersystems.libaxolotl.state.SessionStore;
|
|
||||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
|
||||||
import org.whispersystems.textsecure.push.PreKeyEntity;
|
|
||||||
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
|
|
||||||
import org.whispersystems.textsecure.storage.PreKeyRecord;
|
|
||||||
import org.whispersystems.textsecure.storage.RecipientDevice;
|
|
||||||
import org.whispersystems.textsecure.storage.TextSecureSessionStore;
|
|
||||||
import org.whispersystems.textsecure.util.Medium;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class processes key exchange interactions.
|
|
||||||
*
|
|
||||||
* @author Moxie Marlinspike
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
|
|
||||||
|
|
||||||
private Context context;
|
|
||||||
private RecipientDevice recipientDevice;
|
|
||||||
private MasterSecret masterSecret;
|
|
||||||
private SessionStore sessionStore;
|
|
||||||
|
|
||||||
public KeyExchangeProcessorV2(Context context, MasterSecret masterSecret, RecipientDevice recipientDevice)
|
|
||||||
{
|
|
||||||
this.context = context;
|
|
||||||
this.recipientDevice = recipientDevice;
|
|
||||||
this.masterSecret = masterSecret;
|
|
||||||
this.sessionStore = new TextSecureSessionStore(context, masterSecret);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isTrusted(PreKeyWhisperMessage message) {
|
|
||||||
return isTrusted(message.getIdentityKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isTrusted(PreKeyEntity entity) {
|
|
||||||
return isTrusted(entity.getIdentityKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isTrusted(KeyExchangeMessage message) {
|
|
||||||
return message.hasIdentityKey() && isTrusted(message.getIdentityKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isTrusted(IdentityKey identityKey) {
|
|
||||||
return DatabaseFactory.getIdentityDatabase(context).isValidIdentity(masterSecret,
|
|
||||||
recipientDevice.getRecipientId(),
|
|
||||||
identityKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isStale(KeyExchangeMessage m) {
|
|
||||||
KeyExchangeMessageV2 message = (KeyExchangeMessageV2) m;
|
|
||||||
SessionRecord sessionRecord = sessionStore.get(recipientDevice.getRecipientId(),
|
|
||||||
recipientDevice.getDeviceId());
|
|
||||||
|
|
||||||
return
|
|
||||||
message.isResponse() &&
|
|
||||||
(!sessionRecord.getSessionState().hasPendingKeyExchange() ||
|
|
||||||
sessionRecord.getSessionState().getPendingKeyExchangeSequence() != message.getSequence()) &&
|
|
||||||
!message.isResponseForSimultaneousInitiate();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void processKeyExchangeMessage(PreKeyWhisperMessage message)
|
|
||||||
throws InvalidKeyIdException, InvalidKeyException
|
|
||||||
{
|
|
||||||
int preKeyId = message.getPreKeyId();
|
|
||||||
ECPublicKey theirBaseKey = message.getBaseKey();
|
|
||||||
ECPublicKey theirEphemeralKey = message.getWhisperMessage().getSenderEphemeral();
|
|
||||||
IdentityKey theirIdentityKey = message.getIdentityKey();
|
|
||||||
|
|
||||||
Log.w("KeyExchangeProcessor", "Received pre-key with local key ID: " + preKeyId);
|
|
||||||
|
|
||||||
if (!PreKeyRecord.hasRecord(context, preKeyId) &&
|
|
||||||
sessionStore.contains(recipientDevice.getRecipientId(), recipientDevice.getDeviceId()))
|
|
||||||
{
|
|
||||||
Log.w("KeyExchangeProcessor", "We've already processed the prekey part, letting bundled message fall through...");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!PreKeyRecord.hasRecord(context, preKeyId))
|
|
||||||
throw new InvalidKeyIdException("No such prekey: " + preKeyId);
|
|
||||||
|
|
||||||
SessionRecord sessionRecord = sessionStore.get(recipientDevice.getRecipientId(),
|
|
||||||
recipientDevice.getDeviceId());
|
|
||||||
PreKeyRecord preKeyRecord = new PreKeyRecord(context, masterSecret, preKeyId);
|
|
||||||
ECKeyPair ourBaseKey = preKeyRecord.getKeyPair();
|
|
||||||
ECKeyPair ourEphemeralKey = ourBaseKey;
|
|
||||||
IdentityKeyPair ourIdentityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
|
|
||||||
boolean simultaneousInitiate = sessionRecord.getSessionState().hasPendingPreKey();
|
|
||||||
|
|
||||||
if (!simultaneousInitiate) sessionRecord.reset();
|
|
||||||
else sessionRecord.archiveCurrentState();
|
|
||||||
|
|
||||||
RatchetingSession.initializeSession(sessionRecord.getSessionState(),
|
|
||||||
ourBaseKey, theirBaseKey,
|
|
||||||
ourEphemeralKey, theirEphemeralKey,
|
|
||||||
ourIdentityKey, theirIdentityKey);
|
|
||||||
|
|
||||||
sessionRecord.getSessionState().setLocalRegistrationId(TextSecurePreferences.getLocalRegistrationId(context));
|
|
||||||
sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId());
|
|
||||||
|
|
||||||
if (simultaneousInitiate) sessionRecord.getSessionState().setNeedsRefresh(true);
|
|
||||||
|
|
||||||
sessionStore.put(recipientDevice.getRecipientId(), recipientDevice.getDeviceId(), sessionRecord);
|
|
||||||
|
|
||||||
if (preKeyId != Medium.MAX_VALUE) {
|
|
||||||
PreKeyRecord.delete(context, preKeyId);
|
|
||||||
}
|
|
||||||
|
|
||||||
PreKeyService.initiateRefresh(context, masterSecret);
|
|
||||||
|
|
||||||
DatabaseFactory.getIdentityDatabase(context)
|
|
||||||
.saveIdentity(masterSecret, recipientDevice.getRecipientId(), theirIdentityKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void processKeyExchangeMessage(PreKeyEntity message, long threadId)
|
|
||||||
throws InvalidKeyException
|
|
||||||
{
|
|
||||||
SessionRecord sessionRecord = sessionStore.get(recipientDevice.getRecipientId(),
|
|
||||||
recipientDevice.getDeviceId());
|
|
||||||
ECKeyPair ourBaseKey = Curve.generateKeyPair(true);
|
|
||||||
ECKeyPair ourEphemeralKey = Curve.generateKeyPair(true);
|
|
||||||
ECPublicKey theirBaseKey = message.getPublicKey();
|
|
||||||
ECPublicKey theirEphemeralKey = theirBaseKey;
|
|
||||||
IdentityKey theirIdentityKey = message.getIdentityKey();
|
|
||||||
IdentityKeyPair ourIdentityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
|
|
||||||
|
|
||||||
if (sessionRecord.getSessionState().getNeedsRefresh()) sessionRecord.archiveCurrentState();
|
|
||||||
else sessionRecord.reset();
|
|
||||||
|
|
||||||
RatchetingSession.initializeSession(sessionRecord.getSessionState(),
|
|
||||||
ourBaseKey, theirBaseKey, ourEphemeralKey,
|
|
||||||
theirEphemeralKey, ourIdentityKey, theirIdentityKey);
|
|
||||||
|
|
||||||
sessionRecord.getSessionState().setPendingPreKey(message.getKeyId(), ourBaseKey.getPublicKey());
|
|
||||||
sessionRecord.getSessionState().setLocalRegistrationId(TextSecurePreferences.getLocalRegistrationId(context));
|
|
||||||
sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId());
|
|
||||||
|
|
||||||
sessionStore.put(recipientDevice.getRecipientId(), recipientDevice.getDeviceId(), sessionRecord);
|
|
||||||
|
|
||||||
DatabaseFactory.getIdentityDatabase(context)
|
|
||||||
.saveIdentity(masterSecret, recipientDevice.getRecipientId(), message.getIdentityKey());
|
|
||||||
|
|
||||||
if (threadId != -1) {
|
|
||||||
broadcastSecurityUpdateEvent(context, threadId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void processKeyExchangeMessage(KeyExchangeMessage _message, long threadId)
|
|
||||||
throws InvalidMessageException
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
KeyExchangeMessageV2 message = (KeyExchangeMessageV2) _message;
|
|
||||||
SessionRecord sessionRecord = sessionStore.get(recipientDevice.getRecipientId(),
|
|
||||||
recipientDevice.getDeviceId());
|
|
||||||
Recipient recipient = RecipientFactory.getRecipientsForIds(context,
|
|
||||||
String.valueOf(recipientDevice.getRecipientId()),
|
|
||||||
false)
|
|
||||||
.getPrimaryRecipient();
|
|
||||||
|
|
||||||
Log.w("KeyExchangeProcessorV2", "Received key exchange with sequence: " + message.getSequence());
|
|
||||||
|
|
||||||
if (message.isInitiate()) {
|
|
||||||
ECKeyPair ourBaseKey, ourEphemeralKey;
|
|
||||||
IdentityKeyPair ourIdentityKey;
|
|
||||||
|
|
||||||
int flags = KeyExchangeMessageV2.RESPONSE_FLAG;
|
|
||||||
|
|
||||||
Log.w("KeyExchangeProcessorV2", "KeyExchange is an initiate.");
|
|
||||||
|
|
||||||
if (!sessionRecord.getSessionState().hasPendingKeyExchange()) {
|
|
||||||
Log.w("KeyExchangeProcessorV2", "We don't have a pending initiate...");
|
|
||||||
ourBaseKey = Curve.generateKeyPair(true);
|
|
||||||
ourEphemeralKey = Curve.generateKeyPair(true);
|
|
||||||
ourIdentityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
|
|
||||||
|
|
||||||
sessionRecord.getSessionState().setPendingKeyExchange(message.getSequence(), ourBaseKey,
|
|
||||||
ourEphemeralKey, ourIdentityKey);
|
|
||||||
} else {
|
|
||||||
Log.w("KeyExchangeProcessorV2", "We alredy have a pending initiate, responding as simultaneous initiate...");
|
|
||||||
ourBaseKey = sessionRecord.getSessionState().getPendingKeyExchangeBaseKey();
|
|
||||||
ourEphemeralKey = sessionRecord.getSessionState().getPendingKeyExchangeEphemeralKey();
|
|
||||||
ourIdentityKey = sessionRecord.getSessionState().getPendingKeyExchangeIdentityKey();
|
|
||||||
flags |= KeyExchangeMessageV2.SIMULTAENOUS_INITIATE_FLAG;
|
|
||||||
|
|
||||||
sessionRecord.getSessionState().setPendingKeyExchange(message.getSequence(), ourBaseKey,
|
|
||||||
ourEphemeralKey, ourIdentityKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
KeyExchangeMessageV2 ourMessage = new KeyExchangeMessageV2(message.getSequence(),
|
|
||||||
flags, ourBaseKey.getPublicKey(),
|
|
||||||
ourEphemeralKey.getPublicKey(),
|
|
||||||
ourIdentityKey.getPublicKey());
|
|
||||||
|
|
||||||
OutgoingKeyExchangeMessage textMessage = new OutgoingKeyExchangeMessage(recipient,
|
|
||||||
ourMessage.serialize());
|
|
||||||
MessageSender.send(context, masterSecret, textMessage, threadId, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.getSequence() != sessionRecord.getSessionState().getPendingKeyExchangeSequence()) {
|
|
||||||
Log.w("KeyExchangeProcessorV2", "No matching sequence for response. " +
|
|
||||||
"Is simultaneous initiate response: " + message.isResponseForSimultaneousInitiate());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ECKeyPair ourBaseKey = sessionRecord.getSessionState().getPendingKeyExchangeBaseKey();
|
|
||||||
ECKeyPair ourEphemeralKey = sessionRecord.getSessionState().getPendingKeyExchangeEphemeralKey();
|
|
||||||
IdentityKeyPair ourIdentityKey = sessionRecord.getSessionState().getPendingKeyExchangeIdentityKey();
|
|
||||||
|
|
||||||
sessionRecord.reset();
|
|
||||||
|
|
||||||
RatchetingSession.initializeSession(sessionRecord.getSessionState(),
|
|
||||||
ourBaseKey, message.getBaseKey(),
|
|
||||||
ourEphemeralKey, message.getEphemeralKey(),
|
|
||||||
ourIdentityKey, message.getIdentityKey());
|
|
||||||
|
|
||||||
sessionRecord.getSessionState().setSessionVersion(message.getVersion());
|
|
||||||
sessionStore.put(recipientDevice.getRecipientId(), recipientDevice.getDeviceId(), sessionRecord);
|
|
||||||
|
|
||||||
DatabaseFactory.getIdentityDatabase(context)
|
|
||||||
.saveIdentity(masterSecret, recipientDevice.getRecipientId(), message.getIdentityKey());
|
|
||||||
|
|
||||||
DecryptingQueue.scheduleRogueMessages(context, masterSecret, recipient);
|
|
||||||
|
|
||||||
broadcastSecurityUpdateEvent(context, threadId);
|
|
||||||
} catch (InvalidKeyException e) {
|
|
||||||
throw new InvalidMessageException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,38 +1,144 @@
|
|||||||
/**
|
|
||||||
* 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.protocol;
|
package org.thoughtcrime.securesms.crypto.protocol;
|
||||||
|
|
||||||
|
import com.google.protobuf.ByteString;
|
||||||
|
import com.google.protobuf.InvalidProtocolBufferException;
|
||||||
|
|
||||||
import org.whispersystems.libaxolotl.IdentityKey;
|
import org.whispersystems.libaxolotl.IdentityKey;
|
||||||
import org.whispersystems.libaxolotl.InvalidKeyException;
|
import org.whispersystems.libaxolotl.InvalidKeyException;
|
||||||
import org.whispersystems.libaxolotl.InvalidMessageException;
|
import org.whispersystems.libaxolotl.InvalidMessageException;
|
||||||
import org.whispersystems.libaxolotl.InvalidVersionException;
|
import org.whispersystems.libaxolotl.InvalidVersionException;
|
||||||
import org.whispersystems.libaxolotl.LegacyMessageException;
|
import org.whispersystems.libaxolotl.LegacyMessageException;
|
||||||
|
import org.whispersystems.libaxolotl.ecc.Curve;
|
||||||
|
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
|
||||||
|
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
|
||||||
|
import org.whispersystems.libaxolotl.protocol.WhisperProtos;
|
||||||
|
import org.whispersystems.textsecure.util.Base64;
|
||||||
|
import org.whispersystems.textsecure.util.Conversions;
|
||||||
|
import org.whispersystems.textsecure.util.Util;
|
||||||
|
|
||||||
public abstract class KeyExchangeMessage {
|
import java.io.IOException;
|
||||||
public abstract IdentityKey getIdentityKey();
|
|
||||||
public abstract boolean hasIdentityKey();
|
|
||||||
public abstract int getMaxVersion();
|
|
||||||
public abstract int getVersion();
|
|
||||||
|
|
||||||
public static KeyExchangeMessage createFor(String rawMessage)
|
public class KeyExchangeMessage {
|
||||||
throws InvalidMessageException, InvalidKeyException, InvalidVersionException, LegacyMessageException
|
|
||||||
|
public static final int INITIATE_FLAG = 0x01;
|
||||||
|
public static final int RESPONSE_FLAG = 0X02;
|
||||||
|
public static final int SIMULTAENOUS_INITIATE_FLAG = 0x04;
|
||||||
|
|
||||||
|
private final int version;
|
||||||
|
private final int supportedVersion;
|
||||||
|
private final int sequence;
|
||||||
|
private final int flags;
|
||||||
|
|
||||||
|
private final ECPublicKey baseKey;
|
||||||
|
private final ECPublicKey ephemeralKey;
|
||||||
|
private final IdentityKey identityKey;
|
||||||
|
private final byte[] serialized;
|
||||||
|
|
||||||
|
public KeyExchangeMessage(int sequence, int flags,
|
||||||
|
ECPublicKey baseKey, ECPublicKey ephemeralKey,
|
||||||
|
IdentityKey identityKey)
|
||||||
|
{
|
||||||
|
this.supportedVersion = CiphertextMessage.CURRENT_VERSION;
|
||||||
|
this.version = CiphertextMessage.CURRENT_VERSION;
|
||||||
|
this.sequence = sequence;
|
||||||
|
this.flags = flags;
|
||||||
|
this.baseKey = baseKey;
|
||||||
|
this.ephemeralKey = ephemeralKey;
|
||||||
|
this.identityKey = identityKey;
|
||||||
|
|
||||||
|
byte[] version = {Conversions.intsToByteHighAndLow(this.version, this.supportedVersion)};
|
||||||
|
byte[] message = WhisperProtos.KeyExchangeMessage.newBuilder()
|
||||||
|
.setId((sequence << 5) | flags)
|
||||||
|
.setBaseKey(ByteString.copyFrom(baseKey.serialize()))
|
||||||
|
.setEphemeralKey(ByteString.copyFrom(ephemeralKey.serialize()))
|
||||||
|
.setIdentityKey(ByteString.copyFrom(identityKey.serialize()))
|
||||||
|
.build().toByteArray();
|
||||||
|
|
||||||
|
this.serialized = Util.combine(version, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyExchangeMessage(String serializedAndEncoded)
|
||||||
|
throws InvalidMessageException, InvalidVersionException, LegacyMessageException
|
||||||
{
|
{
|
||||||
return new KeyExchangeMessageV2(rawMessage);
|
try {
|
||||||
|
byte[] serialized = Base64.decodeWithoutPadding(serializedAndEncoded);
|
||||||
|
byte[][] parts = Util.split(serialized, 1, serialized.length - 1);
|
||||||
|
|
||||||
|
this.version = Conversions.highBitsToInt(parts[0][0]);
|
||||||
|
this.supportedVersion = Conversions.lowBitsToInt(parts[0][0]);
|
||||||
|
|
||||||
|
if (this.version <= CiphertextMessage.UNSUPPORTED_VERSION) {
|
||||||
|
throw new LegacyMessageException("Unsupported legacy version: " + this.version);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.version > CiphertextMessage.CURRENT_VERSION) {
|
||||||
|
throw new InvalidVersionException("Unknown version: " + this.version);
|
||||||
|
}
|
||||||
|
|
||||||
|
WhisperProtos.KeyExchangeMessage message = WhisperProtos.KeyExchangeMessage.parseFrom(parts[1]);
|
||||||
|
|
||||||
|
if (!message.hasId() || !message.hasBaseKey() ||
|
||||||
|
!message.hasEphemeralKey() || !message.hasIdentityKey())
|
||||||
|
{
|
||||||
|
throw new InvalidMessageException("Some required fields missing!");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sequence = message.getId() >> 5;
|
||||||
|
this.flags = message.getId() & 0x1f;
|
||||||
|
this.serialized = serialized;
|
||||||
|
this.baseKey = Curve.decodePoint(message.getBaseKey().toByteArray(), 0);
|
||||||
|
this.ephemeralKey = Curve.decodePoint(message.getEphemeralKey().toByteArray(), 0);
|
||||||
|
this.identityKey = new IdentityKey(message.getIdentityKey().toByteArray(), 0);
|
||||||
|
} catch (InvalidKeyException | IOException e) {
|
||||||
|
throw new InvalidMessageException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getVersion() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECPublicKey getBaseKey() {
|
||||||
|
return baseKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECPublicKey getEphemeralKey() {
|
||||||
|
return ephemeralKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IdentityKey getIdentityKey() {
|
||||||
|
return identityKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasIdentityKey() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxVersion() {
|
||||||
|
return supportedVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isResponse() {
|
||||||
|
return ((flags & RESPONSE_FLAG) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isInitiate() {
|
||||||
|
return (flags & INITIATE_FLAG) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isResponseForSimultaneousInitiate() {
|
||||||
|
return (flags & SIMULTAENOUS_INITIATE_FLAG) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFlags() {
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSequence() {
|
||||||
|
return sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String serialize() {
|
||||||
|
return Base64.encodeBytesWithoutPadding(serialized);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,152 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.crypto.protocol;
|
|
||||||
|
|
||||||
import com.google.protobuf.ByteString;
|
|
||||||
import com.google.protobuf.InvalidProtocolBufferException;
|
|
||||||
|
|
||||||
import org.whispersystems.libaxolotl.IdentityKey;
|
|
||||||
import org.whispersystems.libaxolotl.InvalidKeyException;
|
|
||||||
import org.whispersystems.libaxolotl.InvalidMessageException;
|
|
||||||
import org.whispersystems.libaxolotl.InvalidVersionException;
|
|
||||||
import org.whispersystems.libaxolotl.LegacyMessageException;
|
|
||||||
import org.whispersystems.libaxolotl.ecc.Curve;
|
|
||||||
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
|
|
||||||
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
|
|
||||||
import org.whispersystems.libaxolotl.protocol.WhisperProtos;
|
|
||||||
import org.whispersystems.textsecure.util.Base64;
|
|
||||||
import org.whispersystems.textsecure.util.Conversions;
|
|
||||||
import org.whispersystems.textsecure.util.Util;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public class KeyExchangeMessageV2 extends KeyExchangeMessage {
|
|
||||||
|
|
||||||
public static final int INITIATE_FLAG = 0x01;
|
|
||||||
public static final int RESPONSE_FLAG = 0X02;
|
|
||||||
public static final int SIMULTAENOUS_INITIATE_FLAG = 0x04;
|
|
||||||
|
|
||||||
private final int version;
|
|
||||||
private final int supportedVersion;
|
|
||||||
private final int sequence;
|
|
||||||
private final int flags;
|
|
||||||
|
|
||||||
private final ECPublicKey baseKey;
|
|
||||||
private final ECPublicKey ephemeralKey;
|
|
||||||
private final IdentityKey identityKey;
|
|
||||||
private final byte[] serialized;
|
|
||||||
|
|
||||||
public KeyExchangeMessageV2(int sequence, int flags,
|
|
||||||
ECPublicKey baseKey, ECPublicKey ephemeralKey,
|
|
||||||
IdentityKey identityKey)
|
|
||||||
{
|
|
||||||
this.supportedVersion = CiphertextMessage.CURRENT_VERSION;
|
|
||||||
this.version = CiphertextMessage.CURRENT_VERSION;
|
|
||||||
this.sequence = sequence;
|
|
||||||
this.flags = flags;
|
|
||||||
this.baseKey = baseKey;
|
|
||||||
this.ephemeralKey = ephemeralKey;
|
|
||||||
this.identityKey = identityKey;
|
|
||||||
|
|
||||||
byte[] version = {Conversions.intsToByteHighAndLow(this.version, this.supportedVersion)};
|
|
||||||
byte[] message = WhisperProtos.KeyExchangeMessage.newBuilder()
|
|
||||||
.setId((sequence << 5) | flags)
|
|
||||||
.setBaseKey(ByteString.copyFrom(baseKey.serialize()))
|
|
||||||
.setEphemeralKey(ByteString.copyFrom(ephemeralKey.serialize()))
|
|
||||||
.setIdentityKey(ByteString.copyFrom(identityKey.serialize()))
|
|
||||||
.build().toByteArray();
|
|
||||||
|
|
||||||
this.serialized = Util.combine(version, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public KeyExchangeMessageV2(String serializedAndEncoded)
|
|
||||||
throws InvalidMessageException, InvalidVersionException, LegacyMessageException
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
byte[] serialized = Base64.decodeWithoutPadding(serializedAndEncoded);
|
|
||||||
byte[][] parts = Util.split(serialized, 1, serialized.length - 1);
|
|
||||||
|
|
||||||
this.version = Conversions.highBitsToInt(parts[0][0]);
|
|
||||||
this.supportedVersion = Conversions.lowBitsToInt(parts[0][0]);
|
|
||||||
|
|
||||||
if (this.version <= CiphertextMessage.UNSUPPORTED_VERSION) {
|
|
||||||
throw new LegacyMessageException("Unsupported legacy version: " + this.version);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.version > CiphertextMessage.CURRENT_VERSION) {
|
|
||||||
throw new InvalidVersionException("Unknown version: " + this.version);
|
|
||||||
}
|
|
||||||
|
|
||||||
WhisperProtos.KeyExchangeMessage message = WhisperProtos.KeyExchangeMessage.parseFrom(parts[1]);
|
|
||||||
|
|
||||||
if (!message.hasId() || !message.hasBaseKey() ||
|
|
||||||
!message.hasEphemeralKey() || !message.hasIdentityKey())
|
|
||||||
{
|
|
||||||
throw new InvalidMessageException("Some required fields missing!");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.sequence = message.getId() >> 5;
|
|
||||||
this.flags = message.getId() & 0x1f;
|
|
||||||
this.serialized = serialized;
|
|
||||||
this.baseKey = Curve.decodePoint(message.getBaseKey().toByteArray(), 0);
|
|
||||||
this.ephemeralKey = Curve.decodePoint(message.getEphemeralKey().toByteArray(), 0);
|
|
||||||
this.identityKey = new IdentityKey(message.getIdentityKey().toByteArray(), 0);
|
|
||||||
} catch (InvalidProtocolBufferException e) {
|
|
||||||
throw new InvalidMessageException(e);
|
|
||||||
} catch (InvalidKeyException e) {
|
|
||||||
throw new InvalidMessageException(e);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new InvalidMessageException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getVersion() {
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ECPublicKey getBaseKey() {
|
|
||||||
return baseKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ECPublicKey getEphemeralKey() {
|
|
||||||
return ephemeralKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public IdentityKey getIdentityKey() {
|
|
||||||
return identityKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasIdentityKey() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMaxVersion() {
|
|
||||||
return supportedVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isResponse() {
|
|
||||||
return ((flags & RESPONSE_FLAG) != 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isInitiate() {
|
|
||||||
return (flags & INITIATE_FLAG) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isResponseForSimultaneousInitiate() {
|
|
||||||
return (flags & SIMULTAENOUS_INITIATE_FLAG) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getFlags() {
|
|
||||||
return flags;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSequence() {
|
|
||||||
return sequence;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String serialize() {
|
|
||||||
return Base64.encodeBytesWithoutPadding(serialized);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue