mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-23 18:15:22 +00:00
clean up session cipher, sealed session cipher, and old message version
This commit is contained in:
parent
f0020ea811
commit
1e1b3e02e1
@ -218,10 +218,8 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
private void handleMessage(@NonNull SignalServiceEnvelope envelope, @NonNull Optional<Long> smsMessageId, boolean isPushNotification) {
|
||||
try {
|
||||
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
|
||||
SignalProtocolStore axolotlStore = new SignalProtocolStoreImpl(context);
|
||||
SignalServiceAddress localAddress = new SignalServiceAddress(TextSecurePreferences.getLocalNumber(context));
|
||||
LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(context);
|
||||
SignalServiceCipher cipher = new SignalServiceCipher(localAddress, axolotlStore, new SessionProtocolImpl(context), apiDB);
|
||||
SignalServiceCipher cipher = new SignalServiceCipher(new SessionProtocolImpl(context), apiDB);
|
||||
|
||||
SignalServiceContent content = cipher.decrypt(envelope);
|
||||
|
||||
|
@ -1,212 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
package org.session.libsignal.libsignal;
|
||||
|
||||
|
||||
import org.session.libsignal.libsignal.ecc.Curve;
|
||||
import org.session.libsignal.libsignal.ecc.ECKeyPair;
|
||||
import org.session.libsignal.libsignal.ecc.ECPublicKey;
|
||||
import org.session.libsignal.utilities.logging.Log;
|
||||
import org.session.libsignal.libsignal.protocol.PreKeySignalMessage;
|
||||
import org.session.libsignal.libsignal.protocol.SignalMessage;
|
||||
import org.session.libsignal.libsignal.ratchet.AliceSignalProtocolParameters;
|
||||
import org.session.libsignal.libsignal.ratchet.BobSignalProtocolParameters;
|
||||
import org.session.libsignal.libsignal.ratchet.RatchetingSession;
|
||||
import org.session.libsignal.libsignal.state.IdentityKeyStore;
|
||||
import org.session.libsignal.libsignal.state.PreKeyBundle;
|
||||
import org.session.libsignal.libsignal.state.PreKeyStore;
|
||||
import org.session.libsignal.libsignal.state.SessionRecord;
|
||||
import org.session.libsignal.libsignal.state.SessionStore;
|
||||
import org.session.libsignal.libsignal.state.SignalProtocolStore;
|
||||
import org.session.libsignal.libsignal.state.SignedPreKeyStore;
|
||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||
|
||||
/**
|
||||
* SessionBuilder is responsible for setting up encrypted sessions.
|
||||
* Once a session has been established, {@link org.session.libsignal.libsignal.SessionCipher}
|
||||
* can be used to encrypt/decrypt messages in that session.
|
||||
* <p>
|
||||
* Sessions are built from one of three different possible vectors:
|
||||
* <ol>
|
||||
* <li>A {@link org.session.libsignal.libsignal.state.PreKeyBundle} retrieved from a server.</li>
|
||||
* <li>A {@link PreKeySignalMessage} received from a client.</li>
|
||||
* </ol>
|
||||
*
|
||||
* Sessions are constructed per recipientId + deviceId tuple. Remote logical users are identified
|
||||
* by their recipientId, and each logical recipientId can have multiple physical devices.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public class SessionBuilder {
|
||||
|
||||
private static final String TAG = SessionBuilder.class.getSimpleName();
|
||||
|
||||
private final SessionStore sessionStore;
|
||||
private final PreKeyStore preKeyStore;
|
||||
private final SignedPreKeyStore signedPreKeyStore;
|
||||
private final IdentityKeyStore identityKeyStore;
|
||||
private final SignalProtocolAddress remoteAddress;
|
||||
|
||||
/**
|
||||
* Constructs a SessionBuilder.
|
||||
*
|
||||
* @param sessionStore The {@link org.session.libsignal.libsignal.state.SessionStore} to store the constructed session in.
|
||||
* @param preKeyStore The {@link org.session.libsignal.libsignal.state.PreKeyStore} where the client's local {@link org.session.libsignal.libsignal.state.PreKeyRecord}s are stored.
|
||||
* @param identityKeyStore The {@link org.session.libsignal.libsignal.state.IdentityKeyStore} containing the client's identity key information.
|
||||
* @param remoteAddress The address of the remote user to build a session with.
|
||||
*/
|
||||
public SessionBuilder(SessionStore sessionStore,
|
||||
PreKeyStore preKeyStore,
|
||||
SignedPreKeyStore signedPreKeyStore,
|
||||
IdentityKeyStore identityKeyStore,
|
||||
SignalProtocolAddress remoteAddress)
|
||||
{
|
||||
this.sessionStore = sessionStore;
|
||||
this.preKeyStore = preKeyStore;
|
||||
this.signedPreKeyStore = signedPreKeyStore;
|
||||
this.identityKeyStore = identityKeyStore;
|
||||
this.remoteAddress = remoteAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a SessionBuilder
|
||||
* @param store The {@link SignalProtocolStore} to store all state information in.
|
||||
* @param remoteAddress The address of the remote user to build a session with.
|
||||
*/
|
||||
public SessionBuilder(SignalProtocolStore store, SignalProtocolAddress remoteAddress) {
|
||||
this(store, store, store, store, remoteAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a new session from a received {@link PreKeySignalMessage}.
|
||||
*
|
||||
* After a session is constructed in this way, the embedded {@link SignalMessage}
|
||||
* can be decrypted.
|
||||
*
|
||||
* @param message The received {@link PreKeySignalMessage}.
|
||||
* @throws org.session.libsignal.libsignal.InvalidKeyIdException when there is no local
|
||||
* {@link org.session.libsignal.libsignal.state.PreKeyRecord}
|
||||
* that corresponds to the PreKey ID in
|
||||
* the message.
|
||||
* @throws org.session.libsignal.libsignal.InvalidKeyException when the message is formatted incorrectly.
|
||||
* @throws org.session.libsignal.libsignal.UntrustedIdentityException when the {@link IdentityKey} of the sender is untrusted.
|
||||
*/
|
||||
/*package*/ Optional<Integer> process(SessionRecord sessionRecord, PreKeySignalMessage message)
|
||||
throws InvalidKeyIdException, InvalidKeyException, UntrustedIdentityException
|
||||
{
|
||||
IdentityKey theirIdentityKey = message.getIdentityKey();
|
||||
|
||||
if (!identityKeyStore.isTrustedIdentity(remoteAddress, theirIdentityKey, IdentityKeyStore.Direction.RECEIVING)) {
|
||||
throw new UntrustedIdentityException(remoteAddress.getName(), theirIdentityKey);
|
||||
}
|
||||
|
||||
Optional<Integer> unsignedPreKeyId = processV3(sessionRecord, message);
|
||||
|
||||
identityKeyStore.saveIdentity(remoteAddress, theirIdentityKey);
|
||||
|
||||
return unsignedPreKeyId;
|
||||
}
|
||||
|
||||
private Optional<Integer> processV3(SessionRecord sessionRecord, PreKeySignalMessage message)
|
||||
throws UntrustedIdentityException, InvalidKeyIdException, InvalidKeyException
|
||||
{
|
||||
|
||||
if (sessionRecord.hasSessionState(message.getMessageVersion(), message.getBaseKey().serialize())) {
|
||||
Log.w(TAG, "We've already setup a session for this V3 message, letting bundled message fall through...");
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
ECKeyPair ourSignedPreKey = signedPreKeyStore.loadSignedPreKey(message.getSignedPreKeyId()).getKeyPair();
|
||||
|
||||
BobSignalProtocolParameters.Builder parameters = BobSignalProtocolParameters.newBuilder();
|
||||
|
||||
parameters.setTheirBaseKey(message.getBaseKey())
|
||||
.setTheirIdentityKey(message.getIdentityKey())
|
||||
.setOurIdentityKey(identityKeyStore.getIdentityKeyPair())
|
||||
.setOurSignedPreKey(ourSignedPreKey)
|
||||
.setOurRatchetKey(ourSignedPreKey);
|
||||
|
||||
if (message.getPreKeyId().isPresent()) {
|
||||
parameters.setOurOneTimePreKey(Optional.of(preKeyStore.loadPreKey(message.getPreKeyId().get()).getKeyPair()));
|
||||
} else {
|
||||
parameters.setOurOneTimePreKey(Optional.<ECKeyPair>absent());
|
||||
}
|
||||
|
||||
if (!sessionRecord.isFresh()) sessionRecord.archiveCurrentState();
|
||||
|
||||
RatchetingSession.initializeSession(sessionRecord.getSessionState(), parameters.create());
|
||||
|
||||
sessionRecord.getSessionState().setLocalRegistrationId(identityKeyStore.getLocalRegistrationId());
|
||||
sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId());
|
||||
sessionRecord.getSessionState().setAliceBaseKey(message.getBaseKey().serialize());
|
||||
|
||||
if (message.getPreKeyId().isPresent()) {
|
||||
return message.getPreKeyId();
|
||||
} else {
|
||||
return Optional.absent();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a new session from a {@link org.session.libsignal.libsignal.state.PreKeyBundle} retrieved from
|
||||
* a server.
|
||||
*
|
||||
* @param preKey A PreKey for the destination recipient, retrieved from a server.
|
||||
* @throws InvalidKeyException when the {@link org.session.libsignal.libsignal.state.PreKeyBundle} is
|
||||
* badly formatted.
|
||||
* @throws org.session.libsignal.libsignal.UntrustedIdentityException when the sender's
|
||||
* {@link IdentityKey} is not
|
||||
* trusted.
|
||||
*/
|
||||
public void process(PreKeyBundle preKey) throws InvalidKeyException, UntrustedIdentityException {
|
||||
synchronized (SessionCipher.SESSION_LOCK) {
|
||||
if (!identityKeyStore.isTrustedIdentity(remoteAddress, preKey.getIdentityKey(), IdentityKeyStore.Direction.SENDING)) {
|
||||
throw new UntrustedIdentityException(remoteAddress.getName(), preKey.getIdentityKey());
|
||||
}
|
||||
|
||||
if (preKey.getSignedPreKey() != null &&
|
||||
!Curve.verifySignature(preKey.getIdentityKey().getPublicKey(),
|
||||
preKey.getSignedPreKey().serialize(),
|
||||
preKey.getSignedPreKeySignature()))
|
||||
{
|
||||
throw new InvalidKeyException("Invalid signature on device key!");
|
||||
}
|
||||
|
||||
if (preKey.getSignedPreKey() == null) {
|
||||
throw new InvalidKeyException("No signed prekey!");
|
||||
}
|
||||
|
||||
SessionRecord sessionRecord = sessionStore.loadSession(remoteAddress);
|
||||
ECKeyPair ourBaseKey = Curve.generateKeyPair();
|
||||
ECPublicKey theirSignedPreKey = preKey.getSignedPreKey();
|
||||
Optional<ECPublicKey> theirOneTimePreKey = Optional.fromNullable(preKey.getPreKey());
|
||||
Optional<Integer> theirOneTimePreKeyId = theirOneTimePreKey.isPresent() ? Optional.of(preKey.getPreKeyId()) :
|
||||
Optional.<Integer>absent();
|
||||
|
||||
AliceSignalProtocolParameters.Builder parameters = AliceSignalProtocolParameters.newBuilder();
|
||||
|
||||
parameters.setOurBaseKey(ourBaseKey)
|
||||
.setOurIdentityKey(identityKeyStore.getIdentityKeyPair())
|
||||
.setTheirIdentityKey(preKey.getIdentityKey())
|
||||
.setTheirSignedPreKey(theirSignedPreKey)
|
||||
.setTheirRatchetKey(theirSignedPreKey)
|
||||
.setTheirOneTimePreKey(theirOneTimePreKey);
|
||||
|
||||
if (!sessionRecord.isFresh()) sessionRecord.archiveCurrentState();
|
||||
|
||||
RatchetingSession.initializeSession(sessionRecord.getSessionState(), parameters.create());
|
||||
|
||||
sessionRecord.getSessionState().setUnacknowledgedPreKeyMessage(theirOneTimePreKeyId, preKey.getSignedPreKeyId(), ourBaseKey.getPublicKey());
|
||||
sessionRecord.getSessionState().setLocalRegistrationId(identityKeyStore.getLocalRegistrationId());
|
||||
sessionRecord.getSessionState().setRemoteRegistrationId(preKey.getRegistrationId());
|
||||
sessionRecord.getSessionState().setAliceBaseKey(ourBaseKey.getPublicKey().serialize());
|
||||
|
||||
identityKeyStore.saveIdentity(remoteAddress, preKey.getIdentityKey());
|
||||
sessionStore.storeSession(remoteAddress, sessionRecord);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,350 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
package org.session.libsignal.libsignal;
|
||||
|
||||
import org.session.libsignal.libsignal.ecc.Curve;
|
||||
import org.session.libsignal.libsignal.ecc.ECKeyPair;
|
||||
import org.session.libsignal.libsignal.ecc.ECPublicKey;
|
||||
import org.session.libsignal.libsignal.protocol.CiphertextMessage;
|
||||
import org.session.libsignal.libsignal.protocol.PreKeySignalMessage;
|
||||
import org.session.libsignal.libsignal.protocol.SignalMessage;
|
||||
import org.session.libsignal.libsignal.ratchet.ChainKey;
|
||||
import org.session.libsignal.libsignal.ratchet.MessageKeys;
|
||||
import org.session.libsignal.libsignal.ratchet.RootKey;
|
||||
import org.session.libsignal.libsignal.state.IdentityKeyStore;
|
||||
import org.session.libsignal.libsignal.state.PreKeyStore;
|
||||
import org.session.libsignal.libsignal.state.SessionRecord;
|
||||
import org.session.libsignal.libsignal.state.SessionState;
|
||||
import org.session.libsignal.libsignal.state.SessionStore;
|
||||
import org.session.libsignal.libsignal.state.SignalProtocolStore;
|
||||
import org.session.libsignal.libsignal.state.SignedPreKeyStore;
|
||||
import org.session.libsignal.libsignal.util.Pair;
|
||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import static org.session.libsignal.libsignal.state.SessionState.UnacknowledgedPreKeyMessageItems;
|
||||
|
||||
/**
|
||||
* The main entry point for Signal Protocol encrypt/decrypt operations.
|
||||
*
|
||||
* Once a session has been established with {@link SessionBuilder},
|
||||
* this class can be used for all encrypt/decrypt operations within
|
||||
* that session.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public class SessionCipher {
|
||||
|
||||
public static final Object SESSION_LOCK = new Object();
|
||||
|
||||
private final SessionStore sessionStore;
|
||||
private final IdentityKeyStore identityKeyStore;
|
||||
private final SessionBuilder sessionBuilder;
|
||||
private final PreKeyStore preKeyStore;
|
||||
private final SignalProtocolAddress remoteAddress;
|
||||
|
||||
/**
|
||||
* Construct a SessionCipher for encrypt/decrypt operations on a session.
|
||||
* In order to use SessionCipher, a session must have already been created
|
||||
* and stored using {@link SessionBuilder}.
|
||||
*
|
||||
* @param sessionStore The {@link SessionStore} that contains a session for this recipient.
|
||||
* @param remoteAddress The remote address that messages will be encrypted to or decrypted from.
|
||||
*/
|
||||
public SessionCipher(SessionStore sessionStore, PreKeyStore preKeyStore,
|
||||
SignedPreKeyStore signedPreKeyStore, IdentityKeyStore identityKeyStore,
|
||||
SignalProtocolAddress remoteAddress)
|
||||
{
|
||||
this.sessionStore = sessionStore;
|
||||
this.preKeyStore = preKeyStore;
|
||||
this.identityKeyStore = identityKeyStore;
|
||||
this.remoteAddress = remoteAddress;
|
||||
this.sessionBuilder = new SessionBuilder(sessionStore, preKeyStore, signedPreKeyStore,
|
||||
identityKeyStore, remoteAddress);
|
||||
}
|
||||
|
||||
public SessionCipher(SignalProtocolStore store, SignalProtocolAddress remoteAddress) {
|
||||
this(store, store, store, store, remoteAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt a message.
|
||||
*
|
||||
* @param paddedMessage The plaintext message bytes, optionally padded to a constant multiple.
|
||||
* @return A ciphertext message encrypted to the recipient+device tuple.
|
||||
*/
|
||||
public CiphertextMessage encrypt(byte[] paddedMessage) throws UntrustedIdentityException {
|
||||
synchronized (SESSION_LOCK) {
|
||||
SessionRecord sessionRecord = sessionStore.loadSession(remoteAddress);
|
||||
SessionState sessionState = sessionRecord.getSessionState();
|
||||
ChainKey chainKey = sessionState.getSenderChainKey();
|
||||
MessageKeys messageKeys = chainKey.getMessageKeys();
|
||||
ECPublicKey senderEphemeral = sessionState.getSenderRatchetKey();
|
||||
int previousCounter = sessionState.getPreviousCounter();
|
||||
int sessionVersion = sessionState.getSessionVersion();
|
||||
|
||||
byte[] ciphertextBody = getCiphertext(messageKeys, paddedMessage);
|
||||
CiphertextMessage ciphertextMessage = new SignalMessage(sessionVersion, messageKeys.getMacKey(),
|
||||
senderEphemeral, chainKey.getIndex(),
|
||||
previousCounter, ciphertextBody,
|
||||
sessionState.getLocalIdentityKey(),
|
||||
sessionState.getRemoteIdentityKey());
|
||||
|
||||
if (sessionState.hasUnacknowledgedPreKeyMessage()) {
|
||||
UnacknowledgedPreKeyMessageItems items = sessionState.getUnacknowledgedPreKeyMessageItems();
|
||||
int localRegistrationId = sessionState.getLocalRegistrationId();
|
||||
|
||||
ciphertextMessage = new PreKeySignalMessage(sessionVersion, localRegistrationId, items.getPreKeyId(),
|
||||
items.getSignedPreKeyId(), items.getBaseKey(),
|
||||
sessionState.getLocalIdentityKey(),
|
||||
(SignalMessage) ciphertextMessage);
|
||||
}
|
||||
|
||||
sessionState.setSenderChainKey(chainKey.getNextChainKey());
|
||||
|
||||
if (!identityKeyStore.isTrustedIdentity(remoteAddress, sessionState.getRemoteIdentityKey(), IdentityKeyStore.Direction.SENDING)) {
|
||||
throw new UntrustedIdentityException(remoteAddress.getName(), sessionState.getRemoteIdentityKey());
|
||||
}
|
||||
|
||||
identityKeyStore.saveIdentity(remoteAddress, sessionState.getRemoteIdentityKey());
|
||||
sessionStore.storeSession(remoteAddress, sessionRecord);
|
||||
return ciphertextMessage;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] decrypt(PreKeySignalMessage ciphertext)
|
||||
throws DuplicateMessageException, LegacyMessageException, InvalidMessageException,
|
||||
InvalidKeyIdException, InvalidKeyException, UntrustedIdentityException
|
||||
{
|
||||
synchronized (SESSION_LOCK) {
|
||||
SessionRecord sessionRecord = sessionStore.loadSession(remoteAddress);
|
||||
Optional<Integer> unsignedPreKeyId = sessionBuilder.process(sessionRecord, ciphertext);
|
||||
byte[] plaintext = decrypt(sessionRecord, ciphertext.getWhisperMessage());
|
||||
|
||||
sessionStore.storeSession(remoteAddress, sessionRecord);
|
||||
|
||||
if (unsignedPreKeyId.isPresent()) {
|
||||
preKeyStore.removePreKey(unsignedPreKeyId.get());
|
||||
}
|
||||
|
||||
return plaintext;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] decrypt(SignalMessage ciphertext)
|
||||
throws InvalidMessageException, DuplicateMessageException, LegacyMessageException,
|
||||
NoSessionException, UntrustedIdentityException
|
||||
{
|
||||
synchronized (SESSION_LOCK) {
|
||||
|
||||
if (!sessionStore.containsSession(remoteAddress)) {
|
||||
throw new NoSessionException("No session for: " + remoteAddress);
|
||||
}
|
||||
|
||||
SessionRecord sessionRecord = sessionStore.loadSession(remoteAddress);
|
||||
byte[] plaintext = decrypt(sessionRecord, ciphertext);
|
||||
|
||||
if (!identityKeyStore.isTrustedIdentity(remoteAddress, sessionRecord.getSessionState().getRemoteIdentityKey(), IdentityKeyStore.Direction.RECEIVING)) {
|
||||
throw new UntrustedIdentityException(remoteAddress.getName(), sessionRecord.getSessionState().getRemoteIdentityKey());
|
||||
}
|
||||
|
||||
identityKeyStore.saveIdentity(remoteAddress, sessionRecord.getSessionState().getRemoteIdentityKey());
|
||||
|
||||
sessionStore.storeSession(remoteAddress, sessionRecord);
|
||||
|
||||
return plaintext;
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] decrypt(SessionRecord sessionRecord, SignalMessage ciphertext)
|
||||
throws DuplicateMessageException, LegacyMessageException, InvalidMessageException
|
||||
{
|
||||
synchronized (SESSION_LOCK) {
|
||||
Iterator<SessionState> previousStates = sessionRecord.getPreviousSessionStates().iterator();
|
||||
List<Exception> exceptions = new LinkedList<Exception>();
|
||||
|
||||
try {
|
||||
SessionState sessionState = new SessionState(sessionRecord.getSessionState());
|
||||
byte[] plaintext = decrypt(sessionState, ciphertext);
|
||||
|
||||
sessionRecord.setState(sessionState);
|
||||
return plaintext;
|
||||
} catch (InvalidMessageException e) {
|
||||
exceptions.add(e);
|
||||
}
|
||||
|
||||
while (previousStates.hasNext()) {
|
||||
try {
|
||||
SessionState promotedState = new SessionState(previousStates.next());
|
||||
byte[] plaintext = decrypt(promotedState, ciphertext);
|
||||
|
||||
previousStates.remove();
|
||||
sessionRecord.promoteState(promotedState);
|
||||
|
||||
return plaintext;
|
||||
} catch (InvalidMessageException e) {
|
||||
exceptions.add(e);
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidMessageException("No valid sessions.", exceptions);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] decrypt(SessionState sessionState, SignalMessage ciphertextMessage)
|
||||
throws InvalidMessageException, DuplicateMessageException, LegacyMessageException
|
||||
{
|
||||
if (!sessionState.hasSenderChain()) {
|
||||
throw new InvalidMessageException("Uninitialized session!");
|
||||
}
|
||||
|
||||
if (ciphertextMessage.getMessageVersion() != sessionState.getSessionVersion()) {
|
||||
throw new InvalidMessageException(String.format("Message version %d, but session version %d",
|
||||
ciphertextMessage.getMessageVersion(),
|
||||
sessionState.getSessionVersion()));
|
||||
}
|
||||
|
||||
ECPublicKey theirEphemeral = ciphertextMessage.getSenderRatchetKey();
|
||||
int counter = ciphertextMessage.getCounter();
|
||||
ChainKey chainKey = getOrCreateChainKey(sessionState, theirEphemeral);
|
||||
MessageKeys messageKeys = getOrCreateMessageKeys(sessionState, theirEphemeral,
|
||||
chainKey, counter);
|
||||
|
||||
ciphertextMessage.verifyMac(sessionState.getRemoteIdentityKey(),
|
||||
sessionState.getLocalIdentityKey(),
|
||||
messageKeys.getMacKey());
|
||||
|
||||
byte[] plaintext = getPlaintext(messageKeys, ciphertextMessage.getBody());
|
||||
|
||||
sessionState.clearUnacknowledgedPreKeyMessage();
|
||||
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
public int getRemoteRegistrationId() {
|
||||
synchronized (SESSION_LOCK) {
|
||||
SessionRecord record = sessionStore.loadSession(remoteAddress);
|
||||
return record.getSessionState().getRemoteRegistrationId();
|
||||
}
|
||||
}
|
||||
|
||||
public int getSessionVersion() {
|
||||
synchronized (SESSION_LOCK) {
|
||||
if (!sessionStore.containsSession(remoteAddress)) {
|
||||
// Loki - If we have no session then we must be using the FallbackSessionCipher
|
||||
return 3;
|
||||
}
|
||||
|
||||
SessionRecord record = sessionStore.loadSession(remoteAddress);
|
||||
return record.getSessionState().getSessionVersion();
|
||||
}
|
||||
}
|
||||
|
||||
private ChainKey getOrCreateChainKey(SessionState sessionState, ECPublicKey theirEphemeral)
|
||||
throws InvalidMessageException
|
||||
{
|
||||
try {
|
||||
if (sessionState.hasReceiverChain(theirEphemeral)) {
|
||||
return sessionState.getReceiverChainKey(theirEphemeral);
|
||||
} else {
|
||||
RootKey rootKey = sessionState.getRootKey();
|
||||
ECKeyPair ourEphemeral = sessionState.getSenderRatchetKeyPair();
|
||||
Pair<RootKey, ChainKey> receiverChain = rootKey.createChain(theirEphemeral, ourEphemeral);
|
||||
ECKeyPair ourNewEphemeral = Curve.generateKeyPair();
|
||||
Pair<RootKey, ChainKey> senderChain = receiverChain.first().createChain(theirEphemeral, ourNewEphemeral);
|
||||
|
||||
sessionState.setRootKey(senderChain.first());
|
||||
sessionState.addReceiverChain(theirEphemeral, receiverChain.second());
|
||||
sessionState.setPreviousCounter(Math.max(sessionState.getSenderChainKey().getIndex()-1, 0));
|
||||
sessionState.setSenderChain(ourNewEphemeral, senderChain.second());
|
||||
|
||||
return receiverChain.second();
|
||||
}
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private MessageKeys getOrCreateMessageKeys(SessionState sessionState,
|
||||
ECPublicKey theirEphemeral,
|
||||
ChainKey chainKey, int counter)
|
||||
throws InvalidMessageException, DuplicateMessageException
|
||||
{
|
||||
if (chainKey.getIndex() > counter) {
|
||||
if (sessionState.hasMessageKeys(theirEphemeral, counter)) {
|
||||
return sessionState.removeMessageKeys(theirEphemeral, counter);
|
||||
} else {
|
||||
throw new DuplicateMessageException("Received message with old counter: " +
|
||||
chainKey.getIndex() + " , " + counter);
|
||||
}
|
||||
}
|
||||
|
||||
if (counter - chainKey.getIndex() > 2000) {
|
||||
throw new InvalidMessageException("Over 2000 messages into the future!");
|
||||
}
|
||||
|
||||
while (chainKey.getIndex() < counter) {
|
||||
MessageKeys messageKeys = chainKey.getMessageKeys();
|
||||
sessionState.setMessageKeys(theirEphemeral, messageKeys);
|
||||
chainKey = chainKey.getNextChainKey();
|
||||
}
|
||||
|
||||
sessionState.setReceiverChainKey(theirEphemeral, chainKey.getNextChainKey());
|
||||
return chainKey.getMessageKeys();
|
||||
}
|
||||
|
||||
private byte[] getCiphertext(MessageKeys messageKeys, byte[] plaintext) {
|
||||
try {
|
||||
Cipher cipher = getCipher(Cipher.ENCRYPT_MODE, messageKeys.getCipherKey(), messageKeys.getIv());
|
||||
return cipher.doFinal(plaintext);
|
||||
} catch (IllegalBlockSizeException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (BadPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] getPlaintext(MessageKeys messageKeys, byte[] cipherText)
|
||||
throws InvalidMessageException
|
||||
{
|
||||
try {
|
||||
Cipher cipher = getCipher(Cipher.DECRYPT_MODE, messageKeys.getCipherKey(), messageKeys.getIv());
|
||||
return cipher.doFinal(cipherText);
|
||||
} catch (IllegalBlockSizeException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
} catch (BadPaddingException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Cipher getCipher(int mode, SecretKeySpec key, IvParameterSpec iv) {
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
cipher.init(mode, key, iv);
|
||||
return cipher;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (NoSuchPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (java.security.InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -154,10 +154,4 @@ public class SignalMessage implements CiphertextMessage {
|
||||
public int getType() {
|
||||
return CiphertextMessage.WHISPER_TYPE;
|
||||
}
|
||||
|
||||
public static boolean isLegacy(byte[] message) {
|
||||
return message != null && message.length >= 1 &&
|
||||
ByteUtil.highBitsToInt(message[0]) != CiphertextMessage.CURRENT_VERSION;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,290 +0,0 @@
|
||||
package org.session.libsignal.metadata;
|
||||
|
||||
import org.session.libsignal.metadata.certificate.CertificateValidator;
|
||||
import org.session.libsignal.metadata.certificate.InvalidCertificateException;
|
||||
import org.session.libsignal.metadata.certificate.SenderCertificate;
|
||||
import org.session.libsignal.metadata.protocol.UnidentifiedSenderMessage;
|
||||
import org.session.libsignal.metadata.protocol.UnidentifiedSenderMessageContent;
|
||||
import org.session.libsignal.libsignal.DuplicateMessageException;
|
||||
import org.session.libsignal.libsignal.IdentityKey;
|
||||
import org.session.libsignal.libsignal.IdentityKeyPair;
|
||||
import org.session.libsignal.libsignal.InvalidKeyException;
|
||||
import org.session.libsignal.libsignal.InvalidKeyIdException;
|
||||
import org.session.libsignal.libsignal.InvalidMacException;
|
||||
import org.session.libsignal.libsignal.InvalidMessageException;
|
||||
import org.session.libsignal.libsignal.InvalidVersionException;
|
||||
import org.session.libsignal.libsignal.LegacyMessageException;
|
||||
import org.session.libsignal.libsignal.NoSessionException;
|
||||
import org.session.libsignal.libsignal.SessionCipher;
|
||||
import org.session.libsignal.libsignal.SignalProtocolAddress;
|
||||
import org.session.libsignal.libsignal.UntrustedIdentityException;
|
||||
import org.session.libsignal.libsignal.ecc.Curve;
|
||||
import org.session.libsignal.libsignal.ecc.ECKeyPair;
|
||||
import org.session.libsignal.libsignal.ecc.ECPrivateKey;
|
||||
import org.session.libsignal.libsignal.ecc.ECPublicKey;
|
||||
import org.session.libsignal.libsignal.kdf.HKDFv3;
|
||||
import org.session.libsignal.libsignal.protocol.CiphertextMessage;
|
||||
import org.session.libsignal.libsignal.protocol.PreKeySignalMessage;
|
||||
import org.session.libsignal.libsignal.protocol.SignalMessage;
|
||||
import org.session.libsignal.libsignal.state.SignalProtocolStore;
|
||||
import org.session.libsignal.libsignal.util.ByteUtil;
|
||||
import org.session.libsignal.utilities.Hex;
|
||||
import org.session.libsignal.libsignal.util.Pair;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.text.ParseException;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class SealedSessionCipher {
|
||||
|
||||
private final SignalProtocolStore signalProtocolStore;
|
||||
private final SignalProtocolAddress localAddress;
|
||||
|
||||
public SealedSessionCipher(SignalProtocolStore signalProtocolStore, SignalProtocolAddress localAddress)
|
||||
{
|
||||
this.signalProtocolStore = signalProtocolStore;
|
||||
this.localAddress = localAddress;
|
||||
}
|
||||
|
||||
public byte[] encrypt(SignalProtocolAddress destinationAddress, SenderCertificate senderCertificate, byte[] paddedPlaintext)
|
||||
throws InvalidKeyException, UntrustedIdentityException
|
||||
{
|
||||
CiphertextMessage message = new SessionCipher(signalProtocolStore, destinationAddress).encrypt(paddedPlaintext);
|
||||
return encrypt(destinationAddress, senderCertificate, message);
|
||||
}
|
||||
|
||||
public byte[] encrypt(SignalProtocolAddress destinationAddress, SenderCertificate senderCertificate, CiphertextMessage message)
|
||||
throws InvalidKeyException
|
||||
{
|
||||
try {
|
||||
IdentityKeyPair ourIdentity = signalProtocolStore.getIdentityKeyPair();
|
||||
byte[] theirPublicKey = Hex.fromStringCondensed(destinationAddress.getName());
|
||||
ECPublicKey theirIdentity = new IdentityKey(theirPublicKey, 0).getPublicKey();
|
||||
|
||||
ECKeyPair ephemeral = Curve.generateKeyPair();
|
||||
byte[] ephemeralSalt = ByteUtil.combine("UnidentifiedDelivery".getBytes(), theirIdentity.serialize(), ephemeral.getPublicKey().serialize());
|
||||
EphemeralKeys ephemeralKeys = calculateEphemeralKeys(theirIdentity, ephemeral.getPrivateKey(), ephemeralSalt);
|
||||
byte[] staticKeyCiphertext = encrypt(ephemeralKeys.cipherKey, ephemeralKeys.macKey, ourIdentity.getPublicKey().serialize());
|
||||
|
||||
byte[] staticSalt = ByteUtil.combine(ephemeralKeys.chainKey, staticKeyCiphertext);
|
||||
StaticKeys staticKeys = calculateStaticKeys(theirIdentity, ourIdentity.getPrivateKey(), staticSalt);
|
||||
UnidentifiedSenderMessageContent content = new UnidentifiedSenderMessageContent(message.getType(), senderCertificate, message.serialize());
|
||||
byte[] messageBytes = encrypt(staticKeys.cipherKey, staticKeys.macKey, content.getSerialized());
|
||||
|
||||
return new UnidentifiedSenderMessage(ephemeral.getPublicKey(), staticKeyCiphertext, messageBytes).getSerialized();
|
||||
} catch (IOException e) {
|
||||
throw new InvalidKeyException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt a sealed session message.
|
||||
* This will return a Pair<Integer, byte[]> which is the CipherTextMessage type and the decrypted message content
|
||||
*/
|
||||
public Pair<SignalProtocolAddress, Pair<Integer, byte[]>> decrypt(CertificateValidator validator, byte[] ciphertext, long timestamp, String prefixedPublicKey)
|
||||
throws
|
||||
InvalidMetadataMessageException, InvalidMetadataVersionException,
|
||||
ProtocolInvalidMessageException, ProtocolInvalidKeyException,
|
||||
ProtocolNoSessionException, ProtocolLegacyMessageException,
|
||||
ProtocolInvalidVersionException, ProtocolDuplicateMessageException,
|
||||
ProtocolInvalidKeyIdException, ProtocolUntrustedIdentityException,
|
||||
SelfSendException, IOException
|
||||
{
|
||||
UnidentifiedSenderMessageContent content;
|
||||
|
||||
try {
|
||||
IdentityKeyPair ourIdentity = signalProtocolStore.getIdentityKeyPair();
|
||||
UnidentifiedSenderMessage wrapper = new UnidentifiedSenderMessage(ciphertext);
|
||||
byte[] ephemeralSalt = ByteUtil.combine("UnidentifiedDelivery".getBytes(), ourIdentity.getPublicKey().serialize(), wrapper.getEphemeral().serialize());
|
||||
EphemeralKeys ephemeralKeys = calculateEphemeralKeys(wrapper.getEphemeral(), ourIdentity.getPrivateKey(), ephemeralSalt);
|
||||
byte[] staticKeyBytes = decrypt(ephemeralKeys.cipherKey, ephemeralKeys.macKey, wrapper.getEncryptedStatic());
|
||||
|
||||
ECPublicKey staticKey = Curve.decodePoint(staticKeyBytes, 0);
|
||||
byte[] staticSalt = ByteUtil.combine(ephemeralKeys.chainKey, wrapper.getEncryptedStatic());
|
||||
StaticKeys staticKeys = calculateStaticKeys(staticKey, ourIdentity.getPrivateKey(), staticSalt);
|
||||
byte[] messageBytes = decrypt(staticKeys.cipherKey, staticKeys.macKey, wrapper.getEncryptedMessage());
|
||||
|
||||
content = new UnidentifiedSenderMessageContent(messageBytes);
|
||||
validator.validate(content.getSenderCertificate(), timestamp);
|
||||
|
||||
if (content.getSenderCertificate().getSender().equals(localAddress.getName()) &&
|
||||
content.getSenderCertificate().getSenderDeviceId() == localAddress.getDeviceId())
|
||||
{
|
||||
throw new SelfSendException();
|
||||
}
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new InvalidMetadataMessageException(e);
|
||||
} catch (InvalidMacException e) {
|
||||
throw new InvalidMetadataMessageException(e);
|
||||
} catch (InvalidCertificateException e) {
|
||||
throw new InvalidMetadataMessageException(e);
|
||||
}
|
||||
|
||||
try {
|
||||
Pair<Integer, byte[]> dataPair = new Pair<>(content.getType(), decrypt(content));
|
||||
return new Pair<>(
|
||||
new SignalProtocolAddress(content.getSenderCertificate().getSender(), content.getSenderCertificate().getSenderDeviceId()),
|
||||
dataPair
|
||||
);
|
||||
} catch (InvalidMessageException e) {
|
||||
throw new ProtocolInvalidMessageException(e, content.getSenderCertificate().getSender(), content.getSenderCertificate().getSenderDeviceId());
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new ProtocolInvalidKeyException(e, content.getSenderCertificate().getSender(), content.getSenderCertificate().getSenderDeviceId());
|
||||
} catch (NoSessionException e) {
|
||||
throw new ProtocolNoSessionException(e, content.getSenderCertificate().getSender(), content.getSenderCertificate().getSenderDeviceId());
|
||||
} catch (LegacyMessageException e) {
|
||||
throw new ProtocolLegacyMessageException(e, content.getSenderCertificate().getSender(), content.getSenderCertificate().getSenderDeviceId());
|
||||
} catch (InvalidVersionException e) {
|
||||
throw new ProtocolInvalidVersionException(e, content.getSenderCertificate().getSender(), content.getSenderCertificate().getSenderDeviceId());
|
||||
} catch (DuplicateMessageException e) {
|
||||
throw new ProtocolDuplicateMessageException(e, content.getSenderCertificate().getSender(), content.getSenderCertificate().getSenderDeviceId());
|
||||
} catch (InvalidKeyIdException e) {
|
||||
throw new ProtocolInvalidKeyIdException(e, content.getSenderCertificate().getSender(), content.getSenderCertificate().getSenderDeviceId());
|
||||
} catch (UntrustedIdentityException e) {
|
||||
throw new ProtocolUntrustedIdentityException(e, content.getSenderCertificate().getSender(), content.getSenderCertificate().getSenderDeviceId());
|
||||
}
|
||||
}
|
||||
|
||||
public int getSessionVersion(SignalProtocolAddress remoteAddress) {
|
||||
return new SessionCipher(signalProtocolStore, remoteAddress).getSessionVersion();
|
||||
}
|
||||
|
||||
public int getRemoteRegistrationId(SignalProtocolAddress remoteAddress) {
|
||||
return new SessionCipher(signalProtocolStore, remoteAddress).getRemoteRegistrationId();
|
||||
}
|
||||
|
||||
private EphemeralKeys calculateEphemeralKeys(ECPublicKey ephemeralPublic, ECPrivateKey ephemeralPrivate, byte[] salt) throws InvalidKeyException {
|
||||
try {
|
||||
byte[] ephemeralSecret = Curve.calculateAgreement(ephemeralPublic, ephemeralPrivate);
|
||||
byte[] ephemeralDerived = new HKDFv3().deriveSecrets(ephemeralSecret, salt, new byte[0], 96);
|
||||
byte[][] ephemeralDerivedParts = ByteUtil.split(ephemeralDerived, 32, 32, 32);
|
||||
|
||||
return new EphemeralKeys(ephemeralDerivedParts[0], ephemeralDerivedParts[1], ephemeralDerivedParts[2]);
|
||||
} catch (ParseException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private StaticKeys calculateStaticKeys(ECPublicKey staticPublic, ECPrivateKey staticPrivate, byte[] salt) throws InvalidKeyException {
|
||||
try {
|
||||
byte[] staticSecret = Curve.calculateAgreement(staticPublic, staticPrivate);
|
||||
byte[] staticDerived = new HKDFv3().deriveSecrets(staticSecret, salt, new byte[0], 96);
|
||||
byte[][] staticDerivedParts = ByteUtil.split(staticDerived, 32, 32, 32);
|
||||
|
||||
return new StaticKeys(staticDerivedParts[1], staticDerivedParts[2]);
|
||||
} catch (ParseException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] decrypt(UnidentifiedSenderMessageContent message)
|
||||
throws InvalidVersionException, InvalidMessageException, InvalidKeyException, DuplicateMessageException, InvalidKeyIdException, UntrustedIdentityException, LegacyMessageException, NoSessionException
|
||||
{
|
||||
|
||||
SignalProtocolAddress sender = new SignalProtocolAddress(message.getSenderCertificate().getSender(), message.getSenderCertificate().getSenderDeviceId());
|
||||
|
||||
switch (message.getType()) {
|
||||
case CiphertextMessage.WHISPER_TYPE: return new SessionCipher(signalProtocolStore, sender).decrypt(new SignalMessage(message.getContent()));
|
||||
case CiphertextMessage.PREKEY_TYPE: return new SessionCipher(signalProtocolStore, sender).decrypt(new PreKeySignalMessage(message.getContent()));
|
||||
default: throw new InvalidMessageException("Unknown type: " + message.getType());
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] encrypt(SecretKeySpec cipherKey, SecretKeySpec macKey, byte[] plaintext) {
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, cipherKey, new IvParameterSpec(new byte[16]));
|
||||
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(macKey);
|
||||
|
||||
byte[] ciphertext = cipher.doFinal(plaintext);
|
||||
byte[] ourFullMac = mac.doFinal(ciphertext);
|
||||
byte[] ourMac = ByteUtil.trim(ourFullMac, 10);
|
||||
|
||||
return ByteUtil.combine(ciphertext, ourMac);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (NoSuchPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (java.security.InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (BadPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (IllegalBlockSizeException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] decrypt(SecretKeySpec cipherKey, SecretKeySpec macKey, byte[] ciphertext) throws InvalidMacException {
|
||||
try {
|
||||
if (ciphertext.length < 10) {
|
||||
throw new InvalidMacException("Ciphertext not long enough for MAC!");
|
||||
}
|
||||
|
||||
byte[][] ciphertextParts = ByteUtil.split(ciphertext, ciphertext.length - 10, 10);
|
||||
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(macKey);
|
||||
|
||||
byte[] digest = mac.doFinal(ciphertextParts[0]);
|
||||
byte[] ourMac = ByteUtil.trim(digest, 10);
|
||||
byte[] theirMac = ciphertextParts[1];
|
||||
|
||||
if (!MessageDigest.isEqual(ourMac, theirMac)) {
|
||||
throw new InvalidMacException("Bad mac!");
|
||||
}
|
||||
|
||||
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, cipherKey, new IvParameterSpec(new byte[16]));
|
||||
|
||||
return cipher.doFinal(ciphertextParts[0]);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (java.security.InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (NoSuchPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (IllegalBlockSizeException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (BadPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static class EphemeralKeys {
|
||||
private final byte[] chainKey;
|
||||
private final SecretKeySpec cipherKey;
|
||||
private final SecretKeySpec macKey;
|
||||
|
||||
private EphemeralKeys(byte[] chainKey, byte[] cipherKey, byte[] macKey) {
|
||||
this.chainKey = chainKey;
|
||||
this.cipherKey = new SecretKeySpec(cipherKey, "AES");
|
||||
this.macKey = new SecretKeySpec(macKey, "HmacSHA256");
|
||||
}
|
||||
}
|
||||
|
||||
private static class StaticKeys {
|
||||
private final SecretKeySpec cipherKey;
|
||||
private final SecretKeySpec macKey;
|
||||
|
||||
private StaticKeys(byte[] cipherKey, byte[] macKey) {
|
||||
this.cipherKey = new SecretKeySpec(cipherKey, "AES");
|
||||
this.macKey = new SecretKeySpec(macKey, "HmacSHA256");
|
||||
}
|
||||
}
|
||||
}
|
@ -878,7 +878,6 @@ public class SignalServiceMessageSender {
|
||||
{
|
||||
List<OutgoingPushMessage> messages = new LinkedList<>();
|
||||
|
||||
PushTransportDetails transportDetails = new PushTransportDetails(3);
|
||||
String publicKey = recipient.getNumber(); // Could be a contact's public key or the public key of a SSK group
|
||||
boolean isClosedGroup = apiDatabase.isClosedGroup(publicKey);
|
||||
String encryptionPublicKey;
|
||||
@ -888,7 +887,7 @@ public class SignalServiceMessageSender {
|
||||
} else {
|
||||
encryptionPublicKey = publicKey;
|
||||
}
|
||||
byte[] ciphertext = sessionProtocolImpl.encrypt(transportDetails.getPaddedMessageBody(plaintext), encryptionPublicKey);
|
||||
byte[] ciphertext = sessionProtocolImpl.encrypt(PushTransportDetails.getPaddedMessageBody(plaintext), encryptionPublicKey);
|
||||
String body = Base64.encodeBytes(ciphertext);
|
||||
int type = isClosedGroup ? SignalServiceProtos.Envelope.Type.CLOSED_GROUP_CIPHERTEXT_VALUE :
|
||||
SignalServiceProtos.Envelope.Type.UNIDENTIFIED_SENDER_VALUE;
|
||||
|
@ -11,11 +11,7 @@ import com.google.protobuf.InvalidProtocolBufferException;
|
||||
import org.session.libsignal.libsignal.ecc.ECKeyPair;
|
||||
import org.session.libsignal.metadata.InvalidMetadataMessageException;
|
||||
import org.session.libsignal.metadata.ProtocolInvalidMessageException;
|
||||
import org.session.libsignal.metadata.SealedSessionCipher;
|
||||
import org.session.libsignal.libsignal.InvalidMessageException;
|
||||
import org.session.libsignal.libsignal.SessionCipher;
|
||||
import org.session.libsignal.libsignal.SignalProtocolAddress;
|
||||
import org.session.libsignal.libsignal.state.SignalProtocolStore;
|
||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||
import org.session.libsignal.service.api.messages.SignalServiceAttachment;
|
||||
import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer;
|
||||
@ -55,18 +51,12 @@ public class SignalServiceCipher {
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = SignalServiceCipher.class.getSimpleName();
|
||||
|
||||
private final SignalProtocolStore signalProtocolStore;
|
||||
private final SignalServiceAddress localAddress;
|
||||
private final SessionProtocol sessionProtocolImpl;
|
||||
private final LokiAPIDatabaseProtocol apiDB;
|
||||
|
||||
public SignalServiceCipher(SignalServiceAddress localAddress,
|
||||
SignalProtocolStore signalProtocolStore,
|
||||
SessionProtocol sessionProtocolImpl,
|
||||
public SignalServiceCipher(SessionProtocol sessionProtocolImpl,
|
||||
LokiAPIDatabaseProtocol apiDB)
|
||||
{
|
||||
this.signalProtocolStore = signalProtocolStore;
|
||||
this.localAddress = localAddress;
|
||||
this.sessionProtocolImpl = sessionProtocolImpl;
|
||||
this.apiDB = apiDB;
|
||||
}
|
||||
@ -135,13 +125,8 @@ public class SignalServiceCipher {
|
||||
|
||||
protected Plaintext decrypt(SignalServiceEnvelope envelope, byte[] ciphertext) throws InvalidMetadataMessageException
|
||||
{
|
||||
SignalProtocolAddress sourceAddress = new SignalProtocolAddress(envelope.getSource(), envelope.getSourceDevice());
|
||||
SessionCipher sessionCipher = new SessionCipher(signalProtocolStore, sourceAddress);
|
||||
SealedSessionCipher sealedSessionCipher = new SealedSessionCipher(signalProtocolStore, new SignalProtocolAddress(localAddress.getNumber(), 1));
|
||||
|
||||
byte[] paddedMessage;
|
||||
Metadata metadata;
|
||||
int sessionVersion;
|
||||
|
||||
if (envelope.isClosedGroupCiphertext()) {
|
||||
String groupPublicKey = envelope.getSource();
|
||||
@ -149,20 +134,16 @@ public class SignalServiceCipher {
|
||||
paddedMessage = plaintextAndSenderPublicKey.getFirst();
|
||||
String senderPublicKey = plaintextAndSenderPublicKey.getSecond();
|
||||
metadata = new Metadata(senderPublicKey, 1, envelope.getTimestamp(), false);
|
||||
sessionVersion = sessionCipher.getSessionVersion();
|
||||
} else if (envelope.isUnidentifiedSender()) {
|
||||
ECKeyPair userX25519KeyPair = apiDB.getUserX25519KeyPair();
|
||||
kotlin.Pair<byte[], String> plaintextAndSenderPublicKey = sessionProtocolImpl.decrypt(ciphertext, userX25519KeyPair);
|
||||
paddedMessage = plaintextAndSenderPublicKey.getFirst();
|
||||
String senderPublicKey = plaintextAndSenderPublicKey.getSecond();
|
||||
metadata = new Metadata(senderPublicKey, 1, envelope.getTimestamp(), false);
|
||||
sessionVersion = sealedSessionCipher.getSessionVersion(new SignalProtocolAddress(metadata.getSender(), metadata.getSenderDevice()));
|
||||
} else {
|
||||
throw new InvalidMetadataMessageException("Unknown type: " + envelope.getType());
|
||||
}
|
||||
|
||||
PushTransportDetails transportDetails = new PushTransportDetails(sessionVersion);
|
||||
byte[] data = transportDetails.getStrippedPaddingMessageBody(paddedMessage);
|
||||
byte[] data = PushTransportDetails.getStrippedPaddingMessageBody(paddedMessage);
|
||||
|
||||
return new Plaintext(metadata, data);
|
||||
}
|
||||
|
@ -13,16 +13,7 @@ public class PushTransportDetails {
|
||||
|
||||
private static final String TAG = PushTransportDetails.class.getSimpleName();
|
||||
|
||||
private final int messageVersion;
|
||||
|
||||
public PushTransportDetails(int messageVersion) {
|
||||
this.messageVersion = messageVersion;
|
||||
}
|
||||
|
||||
public byte[] getStrippedPaddingMessageBody(byte[] messageWithPadding) {
|
||||
if (messageVersion < 2) throw new AssertionError("Unknown version: " + messageVersion);
|
||||
else if (messageVersion == 2) return messageWithPadding;
|
||||
|
||||
public static byte[] getStrippedPaddingMessageBody(byte[] messageWithPadding) {
|
||||
int paddingStart = 0;
|
||||
|
||||
for (int i=messageWithPadding.length-1;i>=0;i--) {
|
||||
@ -41,10 +32,7 @@ public class PushTransportDetails {
|
||||
return strippedMessage;
|
||||
}
|
||||
|
||||
public byte[] getPaddedMessageBody(byte[] messageBody) {
|
||||
if (messageVersion < 2) throw new AssertionError("Unknown version: " + messageVersion);
|
||||
else if (messageVersion == 2) return messageBody;
|
||||
|
||||
public static byte[] getPaddedMessageBody(byte[] messageBody) {
|
||||
// NOTE: This is dumb. We have our own padding scheme, but so does the cipher.
|
||||
// The +1 -1 here is to make sure the Cipher has room to add one padding byte,
|
||||
// otherwise it'll add a full 16 extra bytes.
|
||||
@ -55,7 +43,7 @@ public class PushTransportDetails {
|
||||
return paddedMessage;
|
||||
}
|
||||
|
||||
private int getPaddedMessageLength(int messageLength) {
|
||||
private static int getPaddedMessageLength(int messageLength) {
|
||||
int messageLengthWithTerminator = messageLength + 1;
|
||||
int messagePartCount = messageLengthWithTerminator / 160;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user