libsignal_service/provisioning/
cipher.rs

1use std::fmt::{self, Debug};
2
3use aes::cipher::block_padding::Pkcs7;
4use aes::cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit};
5use aes::Aes256;
6use bytes::Bytes;
7use hmac::{Hmac, Mac};
8use libsignal_protocol::{KeyPair, PublicKey};
9use prost::Message;
10use rand::{CryptoRng, Rng};
11use sha2::Sha256;
12
13pub use crate::proto::{ProvisionEnvelope, ProvisionMessage};
14
15use crate::{
16    envelope::{CIPHER_KEY_SIZE, IV_LENGTH, IV_OFFSET},
17    provisioning::ProvisioningError,
18};
19
20enum CipherMode {
21    DecryptAndEncrypt(KeyPair),
22    EncryptOnly(PublicKey),
23}
24
25impl Debug for CipherMode {
26    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
27        match self {
28            CipherMode::DecryptAndEncrypt(key_pair) => f
29                .debug_tuple("CipherMode::DecryptAndEncrypt")
30                .field(&key_pair.public_key)
31                .finish(),
32            CipherMode::EncryptOnly(public) => f
33                .debug_tuple("CipherMode::EncryptOnly")
34                .field(&public)
35                .finish(),
36        }
37    }
38}
39
40impl CipherMode {
41    fn public(&self) -> &PublicKey {
42        match self {
43            CipherMode::DecryptAndEncrypt(pair) => &pair.public_key,
44            CipherMode::EncryptOnly(pub_key) => pub_key,
45        }
46    }
47}
48
49const VERSION: u8 = 1;
50
51#[derive(Debug)]
52pub struct ProvisioningCipher {
53    key_material: CipherMode,
54}
55
56impl ProvisioningCipher {
57    pub fn from_public(key: PublicKey) -> Self {
58        Self {
59            key_material: CipherMode::EncryptOnly(key),
60        }
61    }
62
63    pub fn from_key_pair(key_pair: KeyPair) -> Self {
64        Self {
65            key_material: CipherMode::DecryptAndEncrypt(key_pair),
66        }
67    }
68
69    pub fn public_key(&self) -> &PublicKey {
70        self.key_material.public()
71    }
72
73    pub fn encrypt<R: Rng + CryptoRng>(
74        &self,
75        csprng: &mut R,
76        msg: ProvisionMessage,
77    ) -> Result<ProvisionEnvelope, ProvisioningError> {
78        let msg = msg.encode_to_vec();
79
80        let our_key_pair = libsignal_protocol::KeyPair::generate(csprng);
81        let agreement = our_key_pair
82            .calculate_agreement(self.public_key())
83            .map_err(ProvisioningError::invalid_public_key)?;
84
85        let mut shared_secrets = [0; 64];
86        hkdf::Hkdf::<sha2::Sha256>::new(None, &agreement)
87            .expand(b"TextSecure Provisioning Message", &mut shared_secrets)
88            .expect("valid output length");
89
90        let aes_key = &shared_secrets[0..32];
91        let mac_key = &shared_secrets[32..];
92        let iv: [u8; IV_LENGTH] = csprng.random();
93
94        let cipher = cbc::Encryptor::<Aes256>::new(aes_key.into(), &iv.into());
95        let ciphertext = cipher.encrypt_padded_vec_mut::<Pkcs7>(&msg);
96        let mut mac = Hmac::<Sha256>::new_from_slice(mac_key)
97            .expect("HMAC can take any size key");
98        mac.update(&[VERSION]);
99        mac.update(&iv);
100        mac.update(&ciphertext);
101        let mac = mac.finalize().into_bytes();
102
103        let body: Vec<u8> = std::iter::once(VERSION)
104            .chain(iv.iter().cloned())
105            .chain(ciphertext)
106            .chain(mac)
107            .collect();
108
109        Ok(ProvisionEnvelope {
110            public_key: Some(our_key_pair.public_key.serialize().into()),
111            body: Some(body),
112        })
113    }
114
115    pub fn decrypt(
116        &self,
117        provision_envelope: ProvisionEnvelope,
118    ) -> Result<ProvisionMessage, ProvisioningError> {
119        let key_pair = match self.key_material {
120            CipherMode::DecryptAndEncrypt(ref key_pair) => key_pair,
121            CipherMode::EncryptOnly(_) => {
122                return Err(ProvisioningError::EncryptOnlyProvisioningCipher);
123            },
124        };
125        let master_ephemeral = PublicKey::deserialize(
126            &provision_envelope.public_key.expect("no public key"),
127        )
128        .map_err(ProvisioningError::invalid_public_key)?;
129        let body = provision_envelope
130            .body
131            .expect("no body in ProvisionMessage");
132        if body[0] != VERSION {
133            return Err(ProvisioningError::BadVersionNumber);
134        }
135
136        let iv = &body[IV_OFFSET..(IV_LENGTH + IV_OFFSET)];
137        let mac = &body[(body.len() - 32)..];
138        let cipher_text = &body[16 + 1..(body.len() - CIPHER_KEY_SIZE)];
139        let iv_and_cipher_text = &body[0..(body.len() - CIPHER_KEY_SIZE)];
140        debug_assert_eq!(iv.len(), IV_LENGTH);
141        debug_assert_eq!(mac.len(), 32);
142
143        let agreement = key_pair
144            .calculate_agreement(&master_ephemeral)
145            .map_err(ProvisioningError::invalid_private_key)?;
146
147        let mut shared_secrets = [0; 64];
148        hkdf::Hkdf::<sha2::Sha256>::new(None, &agreement)
149            .expand(b"TextSecure Provisioning Message", &mut shared_secrets)
150            .expect("valid output length");
151
152        let parts1 = &shared_secrets[0..32];
153        let parts2 = &shared_secrets[32..];
154
155        let mut verifier = Hmac::<Sha256>::new_from_slice(parts2)
156            .expect("HMAC can take any size key");
157        verifier.update(iv_and_cipher_text);
158        let our_mac = verifier.finalize().into_bytes();
159        debug_assert_eq!(our_mac.len(), mac.len());
160        if &our_mac[..32] != mac {
161            return Err(ProvisioningError::MismatchedMac);
162        }
163
164        // libsignal-service-java uses Pkcs5,
165        // but that should not matter.
166        // https://crypto.stackexchange.com/questions/9043/what-is-the-difference-between-pkcs5-padding-and-pkcs7-padding
167        let cipher = cbc::Decryptor::<Aes256>::new(parts1.into(), iv.into());
168        let input = cipher
169            .decrypt_padded_vec_mut::<Pkcs7>(cipher_text)
170            .map_err(ProvisioningError::AesPaddingError)?;
171
172        Ok(prost::Message::decode(Bytes::from(input))?)
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179
180    #[test]
181    fn encrypt_provisioning_roundtrip() -> anyhow::Result<()> {
182        let mut rng = rand::rng();
183        let key_pair = KeyPair::generate(&mut rng);
184        let cipher = ProvisioningCipher::from_key_pair(key_pair);
185        let encrypt_cipher: ProvisioningCipher =
186            ProvisioningCipher::from_public(*cipher.public_key());
187
188        assert_eq!(
189            cipher.public_key(),
190            encrypt_cipher.public_key(),
191            "copy public key"
192        );
193
194        let msg = ProvisionMessage::default();
195        let encrypted = encrypt_cipher.encrypt(&mut rng, msg.clone())?;
196
197        assert!(matches!(
198            encrypt_cipher.decrypt(encrypted.clone()),
199            Err(ProvisioningError::EncryptOnlyProvisioningCipher)
200        ));
201
202        let decrypted = cipher.decrypt(encrypted)?;
203        assert_eq!(msg, decrypted);
204
205        Ok(())
206    }
207}