Skip to main content

libsignal_service/provisioning/
cipher.rs

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