SealedSessionCipher decrypt

This commit is contained in:
Jack Lloyd
2020-11-18 17:17:53 -05:00
parent 405d071ead
commit 786b9b5792
11 changed files with 264 additions and 150 deletions

View File

@@ -136,6 +136,7 @@ public final class Native {
public static native boolean ScannableFingerprint_Compare(byte[] fprint1, byte[] fprint2);
public static native long SealedSessionCipher_DecryptToUsmc(byte[] ctext, long trustRoot, long timestamp, IdentityKeyStore identityStore);
public static native byte[] SealedSessionCipher_Encrypt(long destination, long senderCert, byte[] ptext, SessionStore sessionStore, IdentityKeyStore identityStore);
public static native long SenderCertificate_Deserialize(byte[] data);
@@ -149,6 +150,7 @@ public final class Native {
public static native byte[] SenderCertificate_GetSerialized(long handle);
public static native long SenderCertificate_GetServerCertificate(long handle);
public static native byte[] SenderCertificate_GetSignature(long handle);
public static native long SenderCertificate_PreferredAddress(long cert, SessionStore sessionStore);
public static native boolean SenderCertificate_Validate(long cert, long key, long time);
public static native long SenderKeyDistributionMessage_Deserialize(byte[] data);

View File

@@ -1,6 +1,5 @@
package org.signal.libsignal.metadata;
import org.signal.libsignal.metadata.certificate.CertificateValidator;
import org.signal.libsignal.metadata.certificate.InvalidCertificateException;
import org.signal.libsignal.metadata.certificate.SenderCertificate;
@@ -18,34 +17,16 @@ import org.whispersystems.libsignal.NoSessionException;
import org.whispersystems.libsignal.SessionCipher;
import org.whispersystems.libsignal.SignalProtocolAddress;
import org.whispersystems.libsignal.UntrustedIdentityException;
import org.whispersystems.libsignal.ecc.Curve;
import org.whispersystems.libsignal.ecc.ECKeyPair;
import org.whispersystems.libsignal.ecc.ECPrivateKey;
import org.whispersystems.libsignal.ecc.ECPublicKey;
import org.whispersystems.libsignal.kdf.HKDFv3;
import org.whispersystems.libsignal.protocol.CiphertextMessage;
import org.whispersystems.libsignal.protocol.PreKeySignalMessage;
import org.whispersystems.libsignal.protocol.SignalMessage;
import org.whispersystems.libsignal.state.SignalProtocolStore;
import org.whispersystems.libsignal.util.ByteUtil;
import org.whispersystems.libsignal.util.guava.Optional;
import org.signal.client.internal.Native;
import java.security.InvalidAlgorithmParameterException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.util.UUID;
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 static final String TAG = SealedSessionCipher.class.getSimpleName();
@@ -87,36 +68,23 @@ public class SealedSessionCipher {
SelfSendException
{
UnidentifiedSenderMessageContent content;
try {
IdentityKeyPair ourIdentity = signalProtocolStore.getIdentityKeyPair();
UnidentifiedSenderMessage wrapper = new UnidentifiedSenderMessage(ciphertext);
byte[] ephemeralSalt = ByteUtil.combine("UnidentifiedDelivery".getBytes(), ourIdentity.getPublicKey().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 (!MessageDigest.isEqual(content.getSenderCertificate().getKey().serialize(), staticKeyBytes)) {
throw new InvalidKeyException("Sender's certificate key does not match key used in message");
}
boolean isLocalE164 = localE164Address != null && localE164Address.equals(content.getSenderCertificate().getSenderE164().orNull());
boolean isLocalUuid = localUuidAddress != null && localUuidAddress.equals(content.getSenderCertificate().getSenderUuid().orNull());
if ((isLocalE164 || isLocalUuid) && content.getSenderCertificate().getSenderDeviceId() == localDeviceId) {
throw new SelfSendException();
}
} catch (InvalidKeyException | InvalidMacException | InvalidCertificateException e) {
content = new UnidentifiedSenderMessageContent(
Native.SealedSessionCipher_DecryptToUsmc(ciphertext,
validator.getTrustRoot().nativeHandle(),
timestamp,
this.signalProtocolStore));
} catch (Exception e) {
throw new InvalidMetadataMessageException(e);
}
boolean isLocalE164 = localE164Address != null && localE164Address.equals(content.getSenderCertificate().getSenderE164().orNull());
boolean isLocalUuid = localUuidAddress != null && localUuidAddress.equals(content.getSenderCertificate().getSenderUuid().orNull());
if ((isLocalE164 || isLocalUuid) && content.getSenderCertificate().getSenderDeviceId() == localDeviceId) {
throw new SelfSendException();
}
try {
return new DecryptionResult(content.getSenderCertificate().getSenderUuid(),
content.getSenderCertificate().getSenderE164(),
@@ -149,34 +117,10 @@ public class SealedSessionCipher {
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, null, 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, null, 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 = getPreferredAddress(signalProtocolStore, message.getSenderCertificate());
SignalProtocolAddress sender = new SignalProtocolAddress(Native.SenderCertificate_PreferredAddress(message.getSenderCertificate().nativeHandle(), signalProtocolStore));
switch (message.getType()) {
case CiphertextMessage.WHISPER_TYPE: return new SessionCipher(signalProtocolStore, sender).decrypt(new SignalMessage(message.getContent()));
@@ -185,47 +129,6 @@ public class SealedSessionCipher {
}
}
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 | java.security.InvalidKeyException | NoSuchPaddingException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
throw new AssertionError(e);
}
}
private static SignalProtocolAddress getPreferredAddress(SignalProtocolStore store, SenderCertificate certificate) {
SignalProtocolAddress uuidAddress = certificate.getSenderUuid().isPresent() ? new SignalProtocolAddress(certificate.getSenderUuid().get(), certificate.getSenderDeviceId()) : null;
SignalProtocolAddress e164Address = certificate.getSenderE164().isPresent() ? new SignalProtocolAddress(certificate.getSenderE164().get(), certificate.getSenderDeviceId()) : null;
if (uuidAddress != null && store.containsSession(uuidAddress)) {
return uuidAddress;
} else if (e164Address != null && store.containsSession(e164Address)) {
return e164Address;
} else {
return new SignalProtocolAddress(certificate.getSender(), certificate.getSenderDeviceId());
}
}
public static class DecryptionResult {
private final Optional<String> senderUuid;
private final Optional<String> senderE164;
@@ -255,27 +158,4 @@ public class SealedSessionCipher {
return paddedMessage;
}
}
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");
}
}
}

View File

@@ -13,6 +13,10 @@ public class CertificateValidator {
this.trustRoot = trustRoot;
}
public ECPublicKey getTrustRoot() {
return this.trustRoot;
}
public void validate(SenderCertificate certificate, long validationTime) throws InvalidCertificateException {
try {
if (!Native.SenderCertificate_Validate(certificate.nativeHandle(), trustRoot.nativeHandle(), validationTime)) {

View File

@@ -14,6 +14,10 @@ public class UnidentifiedSenderMessageContent {
Native.UnidentifiedSenderMessageContent_Destroy(this.handle);
}
public UnidentifiedSenderMessageContent(long nativeHandle) {
this.handle = nativeHandle;
}
public UnidentifiedSenderMessageContent(byte[] serialized) throws InvalidMetadataMessageException, InvalidCertificateException {
try {
this.handle = Native.UnidentifiedSenderMessageContent_Deserialize(serialized);

View File

@@ -14,6 +14,10 @@ public class SignalProtocolAddress {
this.handle = Native.ProtocolAddress_New(name, deviceId);
}
public SignalProtocolAddress(long handle) {
this.handle = handle;
}
@Override
protected void finalize() {
Native.ProtocolAddress_Destroy(this.handle);