mirror of
https://github.com/signalapp/libsignal.git
synced 2024-09-20 03:52:17 +02:00
SealedSessionCipher decrypt
This commit is contained in:
parent
405d071ead
commit
786b9b5792
@ -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);
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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)) {
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -1687,6 +1687,23 @@ jni_fn_get_new_boxed_obj!(Java_org_signal_client_internal_Native_SenderCertifica
|
||||
jni_fn_get_optional_jstring!(Java_org_signal_client_internal_Native_SenderCertificate_1GetSenderUuid(SenderCertificate) using SenderCertificate::sender_uuid);
|
||||
jni_fn_get_optional_jstring!(Java_org_signal_client_internal_Native_SenderCertificate_1GetSenderE164(SenderCertificate) using SenderCertificate::sender_e164);
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn Java_org_signal_client_internal_Native_SenderCertificate_1PreferredAddress(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
cert: ObjectHandle,
|
||||
session_store: JavaSessionStore,
|
||||
) -> ObjectHandle {
|
||||
run_ffi_safe(&env, || {
|
||||
let cert = native_handle_cast::<SenderCertificate>(cert)?;
|
||||
let session_store = JniSessionStore::new(&env, session_store)?;
|
||||
|
||||
let address = expect_ready(cert.preferred_address(&session_store, None))?;
|
||||
box_object::<ProtocolAddress>(Ok(address))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn Java_org_signal_client_internal_Native_SenderCertificate_1Validate(
|
||||
env: JNIEnv,
|
||||
@ -1792,7 +1809,7 @@ pub unsafe extern "C" fn Java_org_signal_client_internal_Native_SealedSessionCip
|
||||
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
|
||||
let ctext = block_on(sealed_sender_encrypt(
|
||||
let ctext = expect_ready(sealed_sender_encrypt(
|
||||
destination,
|
||||
sender_cert,
|
||||
&ptext,
|
||||
@ -1804,3 +1821,30 @@ pub unsafe extern "C" fn Java_org_signal_client_internal_Native_SealedSessionCip
|
||||
to_jbytearray(&env, Ok(ctext))
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn Java_org_signal_client_internal_Native_SealedSessionCipher_1DecryptToUsmc(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
ctext: jbyteArray,
|
||||
trust_root: ObjectHandle,
|
||||
timestamp: jlong,
|
||||
identity_store: JavaIdentityKeyStore,
|
||||
) -> ObjectHandle {
|
||||
run_ffi_safe(&env, || {
|
||||
let ctext = env.convert_byte_array(ctext)?;
|
||||
let trust_root = native_handle_cast::<PublicKey>(trust_root)?;
|
||||
let timestamp = jlong_to_u64(timestamp)?;
|
||||
let mut identity_store = JniIdentityKeyStore::new(&env, identity_store)?;
|
||||
|
||||
let usmc = expect_ready(sealed_sender_decrypt_to_usmc(
|
||||
&ctext,
|
||||
trust_root,
|
||||
timestamp,
|
||||
&mut identity_store,
|
||||
None,
|
||||
))?;
|
||||
|
||||
box_object::<UnidentifiedSenderMessageContent>(Ok(usmc))
|
||||
})
|
||||
}
|
||||
|
@ -142,6 +142,10 @@ pub fn throw_error(env: &JNIEnv, error: SignalJniError) {
|
||||
"java/lang/IllegalStateException"
|
||||
}
|
||||
|
||||
SignalJniError::Signal(SignalProtocolError::SealedSenderSelfSend) => {
|
||||
"org/signal/libsignal/metadata/SelfSendException"
|
||||
}
|
||||
|
||||
SignalJniError::Signal(SignalProtocolError::InvalidArgument(_)) => {
|
||||
"java/lang/IllegalArgumentException"
|
||||
}
|
||||
@ -322,6 +326,16 @@ pub fn jlong_from_u64(value: Result<u64, SignalProtocolError>) -> Result<jlong,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
pub fn convert_optional_jstring(env: &JNIEnv, value: JString) -> Result<Option<String>, SignalJniError> {
|
||||
if value.is_null() {
|
||||
return Ok(None)
|
||||
}
|
||||
|
||||
let rstr = env.get_string(value)?.into();
|
||||
Ok(Some(rstr))
|
||||
}
|
||||
*/
|
||||
pub fn exception_check(env: &JNIEnv, fn_name: &'static str) -> Result<(), SignalJniError> {
|
||||
fn exception_class_name(env: &JNIEnv, exn: JThrowable) -> Result<String, SignalJniError> {
|
||||
let class_type = env.call_method(exn, "getClass", "()Ljava/lang/Class;", &[])?;
|
||||
|
@ -11,6 +11,7 @@ use block_modes::{block_padding::Pkcs7, BlockMode, Cbc};
|
||||
use ctr::Ctr128;
|
||||
use hmac::{Hmac, Mac, NewMac};
|
||||
use sha2::Sha256;
|
||||
use subtle::ConstantTimeEq;
|
||||
|
||||
pub fn aes_256_ctr_encrypt(ptext: &[u8], key: &[u8]) -> Result<Vec<u8>> {
|
||||
if key.len() != 32 {
|
||||
@ -66,6 +67,28 @@ pub fn hmac_sha256(key: &[u8], input: &[u8]) -> Result<[u8; 32]> {
|
||||
Ok(hmac.finalize().into_bytes().into())
|
||||
}
|
||||
|
||||
pub fn aes256_ctr_hmacsha256_encrypt(msg: &[u8], cipher_key: &[u8], mac_key: &[u8]) -> Result<Vec<u8>> {
|
||||
let ctext = aes_256_ctr_encrypt(msg, cipher_key)?;
|
||||
let mac = hmac_sha256(mac_key, &ctext)?;
|
||||
let mut result = Vec::with_capacity(ctext.len() + 10);
|
||||
result.extend_from_slice(&ctext);
|
||||
result.extend_from_slice(&mac[..10]);
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn aes256_ctr_hmacsha256_decrypt(ctext: &[u8], cipher_key: &[u8], mac_key: &[u8]) -> Result<Vec<u8>> {
|
||||
if ctext.len() < 10 {
|
||||
return Err(SignalProtocolError::InvalidCiphertext);
|
||||
}
|
||||
let ptext_len = ctext.len() - 10;
|
||||
let our_mac = hmac_sha256(mac_key, &ctext[..ptext_len])?;
|
||||
let same : bool = our_mac[..10].ct_eq(&ctext[ptext_len..]).into();
|
||||
if !same {
|
||||
return Err(SignalProtocolError::InvalidCiphertext);
|
||||
}
|
||||
aes_256_ctr_decrypt(&ctext[..ptext_len], cipher_key)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
|
@ -66,6 +66,7 @@ pub enum SignalProtocolError {
|
||||
|
||||
InvalidSealedSenderMessage(String),
|
||||
UnknownSealedSenderVersion(u8),
|
||||
SealedSenderSelfSend,
|
||||
}
|
||||
|
||||
impl Error for SignalProtocolError {
|
||||
@ -202,6 +203,9 @@ impl fmt::Display for SignalProtocolError {
|
||||
SignalProtocolError::UnknownSealedSenderVersion(v) => {
|
||||
write!(f, "unknown sealed sender message version {}", v)
|
||||
}
|
||||
SignalProtocolError::SealedSenderSelfSend => {
|
||||
write!(f, "self send of a sealed sender message")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -46,8 +46,9 @@ pub use {
|
||||
AliceSignalProtocolParameters, BobSignalProtocolParameters, ChainKey, MessageKeys, RootKey,
|
||||
},
|
||||
sealed_sender::{
|
||||
sealed_sender_encrypt, SenderCertificate, ServerCertificate, UnidentifiedSenderMessage,
|
||||
UnidentifiedSenderMessageContent,
|
||||
sealed_sender_encrypt, sealed_sender_decrypt, sealed_sender_decrypt_to_usmc,
|
||||
SenderCertificate, ServerCertificate, UnidentifiedSenderMessage,
|
||||
UnidentifiedSenderMessageContent, SealedSenderDecryptionResult,
|
||||
},
|
||||
sender_keys::{
|
||||
SenderChainKey, SenderKeyName, SenderKeyRecord, SenderKeyState, SenderMessageKey,
|
||||
|
@ -7,12 +7,15 @@ use crate::crypto;
|
||||
use crate::error::{Result, SignalProtocolError};
|
||||
use crate::kdf::HKDF;
|
||||
use crate::proto;
|
||||
use crate::session_cipher;
|
||||
use crate::{
|
||||
message_encrypt, Context, IdentityKeyStore, KeyPair, PrivateKey, ProtocolAddress, PublicKey,
|
||||
SessionStore,
|
||||
SessionStore, PreKeyStore, SignedPreKeyStore, SignalMessage, PreKeySignalMessage,
|
||||
};
|
||||
use prost::Message;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use subtle::ConstantTimeEq;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ServerCertificate {
|
||||
@ -292,6 +295,33 @@ impl SenderCertificate {
|
||||
pub fn signature(&self) -> Result<&[u8]> {
|
||||
Ok(&self.signature)
|
||||
}
|
||||
|
||||
pub async fn preferred_address(&self, session_store: &dyn SessionStore, ctx: Context) -> Result<ProtocolAddress> {
|
||||
if let Some(uuid) = self.sender_uuid()? {
|
||||
let uuid_address = ProtocolAddress::new(uuid.to_owned(), self.sender_device_id()?);
|
||||
if session_store.load_session(&uuid_address, ctx).await?.is_some() {
|
||||
return Ok(uuid_address);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(e164) = self.sender_e164()? {
|
||||
let e164_address = ProtocolAddress::new(e164.to_owned(), self.sender_device_id()?);
|
||||
if session_store.load_session(&e164_address, ctx).await?.is_some() {
|
||||
return Ok(e164_address);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This logic of preferring e164 over uuid comes from Java, but seems incorrect.
|
||||
*/
|
||||
let best_address = match (self.sender_e164()?, self.sender_uuid()?) {
|
||||
(Some(e164),_) => e164.to_owned(),
|
||||
(None,Some(uuid)) => uuid.to_owned(),
|
||||
(None,None) => return Err(SignalProtocolError::InvalidSealedSenderMessage("No sender in sender cert".to_owned())),
|
||||
};
|
||||
|
||||
Ok(ProtocolAddress::new(best_address, self.sender_device_id()?))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UnidentifiedSenderMessageContent {
|
||||
@ -382,7 +412,7 @@ const SEALED_SENDER_VERSION: u8 = 1;
|
||||
|
||||
impl UnidentifiedSenderMessage {
|
||||
pub fn deserialize(data: &[u8]) -> Result<Self> {
|
||||
if data.len() == 0 {
|
||||
if data.is_empty() {
|
||||
return Err(SignalProtocolError::InvalidSealedSenderMessage(
|
||||
"Message was empty".to_owned(),
|
||||
));
|
||||
@ -464,15 +494,6 @@ impl UnidentifiedSenderMessage {
|
||||
}
|
||||
}
|
||||
|
||||
fn aes256_ctr_hmacsha256_encrypt(msg: &[u8], cipher_key: &[u8], mac_key: &[u8]) -> Result<Vec<u8>> {
|
||||
let ctext = crypto::aes_256_ctr_encrypt(msg, cipher_key)?;
|
||||
let mac = crypto::hmac_sha256(mac_key, &ctext)?;
|
||||
let mut result = Vec::with_capacity(ctext.len() + 10);
|
||||
result.extend_from_slice(&ctext);
|
||||
result.extend_from_slice(&mac[..10]);
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
struct EphemeralKeys {
|
||||
derived_values: Box<[u8]>,
|
||||
}
|
||||
@ -571,7 +592,7 @@ pub async fn sealed_sender_encrypt<R: Rng + CryptoRng>(
|
||||
true,
|
||||
)?;
|
||||
|
||||
let static_key_ctext = aes256_ctr_hmacsha256_encrypt(
|
||||
let static_key_ctext = crypto::aes256_ctr_hmacsha256_encrypt(
|
||||
&our_identity.public_key().serialize(),
|
||||
&eph_keys.cipher_key()?,
|
||||
&eph_keys.mac_key()?,
|
||||
@ -589,7 +610,7 @@ pub async fn sealed_sender_encrypt<R: Rng + CryptoRng>(
|
||||
sender_cert.clone(),
|
||||
message.serialize().to_vec(),
|
||||
)?;
|
||||
let message_data = aes256_ctr_hmacsha256_encrypt(
|
||||
let message_data = crypto::aes256_ctr_hmacsha256_encrypt(
|
||||
usmc.serialized()?,
|
||||
&static_keys.cipher_key()?,
|
||||
&static_keys.mac_key()?,
|
||||
@ -601,3 +622,116 @@ pub async fn sealed_sender_encrypt<R: Rng + CryptoRng>(
|
||||
.to_vec(),
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn sealed_sender_decrypt_to_usmc(ciphertext: &[u8],
|
||||
trust_root: &PublicKey,
|
||||
timestamp: u64,
|
||||
identity_store: &mut dyn IdentityKeyStore,
|
||||
ctx: Context) -> Result<UnidentifiedSenderMessageContent> {
|
||||
|
||||
let our_identity = identity_store.get_identity_key_pair(ctx).await?;
|
||||
let usm = UnidentifiedSenderMessage::deserialize(ciphertext)?;
|
||||
|
||||
let eph_keys = EphemeralKeys::calculate(
|
||||
&usm.ephemeral_public()?,
|
||||
&our_identity.public_key(),
|
||||
&our_identity.private_key(),
|
||||
false,
|
||||
)?;
|
||||
|
||||
let static_key_bytes = crypto::aes256_ctr_hmacsha256_decrypt(usm.encrypted_static()?, &eph_keys.cipher_key()?, &eph_keys.mac_key()?)?;
|
||||
|
||||
let static_key = PublicKey::deserialize(&static_key_bytes)?;
|
||||
|
||||
let static_keys = StaticKeys::calculate(
|
||||
&static_key,
|
||||
our_identity.private_key(),
|
||||
eph_keys.chain_key()?,
|
||||
usm.encrypted_static()?,
|
||||
)?;
|
||||
|
||||
let message_bytes = crypto::aes256_ctr_hmacsha256_decrypt(usm.encrypted_message()?, &static_keys.cipher_key()?, &static_keys.mac_key()?)?;
|
||||
|
||||
let usmc = UnidentifiedSenderMessageContent::deserialize(&message_bytes)?;
|
||||
if !usmc.sender()?.validate(trust_root, timestamp)? {
|
||||
return Err(SignalProtocolError::InvalidSealedSenderMessage("trust root validation failed".to_string()));
|
||||
}
|
||||
|
||||
if ! bool::from(static_key_bytes.ct_eq(&usmc.sender()?.key()?.serialize())) {
|
||||
return Err(SignalProtocolError::InvalidSealedSenderMessage("sender certificate key does not match message key".to_string()));
|
||||
}
|
||||
|
||||
Ok(usmc)
|
||||
}
|
||||
|
||||
pub struct SealedSenderDecryptionResult {
|
||||
pub sender_uuid: Option<String>,
|
||||
pub sender_e164: Option<String>,
|
||||
pub device_id: u32,
|
||||
pub message: Vec<u8>
|
||||
}
|
||||
|
||||
pub async fn sealed_sender_decrypt(ciphertext: &[u8],
|
||||
trust_root: &PublicKey,
|
||||
timestamp: u64,
|
||||
local_e164: Option<String>,
|
||||
local_uuid: Option<String>,
|
||||
local_device_id: u32,
|
||||
identity_store: &mut dyn IdentityKeyStore,
|
||||
session_store: &mut dyn SessionStore,
|
||||
pre_key_store: &mut dyn PreKeyStore,
|
||||
signed_pre_key_store: &mut dyn SignedPreKeyStore,
|
||||
ctx: Context) -> Result<SealedSenderDecryptionResult> {
|
||||
|
||||
let usmc = sealed_sender_decrypt_to_usmc(
|
||||
ciphertext, trust_root, timestamp, identity_store, ctx).await?;
|
||||
|
||||
let is_local_e164 = match (local_e164, usmc.sender()?.sender_e164()?) {
|
||||
(Some(l),Some(s)) => l == s,
|
||||
(_,_) => false
|
||||
};
|
||||
|
||||
let is_local_uuid = match (local_uuid, usmc.sender()?.sender_uuid()?) {
|
||||
(Some(l),Some(s)) => l == s,
|
||||
(_,_) => false
|
||||
};
|
||||
|
||||
if (is_local_e164 || is_local_uuid) && usmc.sender()?.sender_device_id()? == local_device_id {
|
||||
return Err(SignalProtocolError::SealedSenderSelfSend);
|
||||
}
|
||||
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
|
||||
let remote_address = usmc.sender()?.preferred_address(session_store, ctx).await?;
|
||||
|
||||
let message = match usmc.msg_type()? {
|
||||
2 => {
|
||||
let ctext = SignalMessage::try_from(usmc.contents()?)?;
|
||||
session_cipher::message_decrypt_signal(&ctext,
|
||||
&remote_address,
|
||||
session_store,
|
||||
identity_store,
|
||||
&mut rng,
|
||||
ctx).await?
|
||||
}
|
||||
3 => {
|
||||
let ctext = PreKeySignalMessage::try_from(usmc.contents()?)?;
|
||||
session_cipher::message_decrypt_prekey(&ctext,
|
||||
&remote_address,
|
||||
session_store,
|
||||
identity_store,
|
||||
pre_key_store,
|
||||
signed_pre_key_store,
|
||||
&mut rng,
|
||||
ctx).await?
|
||||
}
|
||||
_ => return Err(SignalProtocolError::InvalidSealedSenderMessage("Unknown message type".to_owned()))
|
||||
};
|
||||
|
||||
Ok(SealedSenderDecryptionResult {
|
||||
sender_uuid: usmc.sender()?.sender_uuid()?.map(|s| s.to_string()),
|
||||
sender_e164: usmc.sender()?.sender_e164()?.map(|s| s.to_string()),
|
||||
device_id: usmc.sender()?.sender_device_id()?,
|
||||
message,
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user