libsignal_service/provisioning/
cipher.rs1use 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 #[expect(clippy::result_large_err)]
74 pub fn encrypt<R: Rng + CryptoRng>(
75 &self,
76 csprng: &mut R,
77 msg: ProvisionMessage,
78 ) -> Result<ProvisionEnvelope, ProvisioningError> {
79 let msg = msg.encode_to_vec();
80
81 let our_key_pair = libsignal_protocol::KeyPair::generate(csprng);
82 let agreement = our_key_pair.calculate_agreement(self.public_key())?;
83
84 let mut shared_secrets = [0; 64];
85 hkdf::Hkdf::<sha2::Sha256>::new(None, &agreement)
86 .expand(b"TextSecure Provisioning Message", &mut shared_secrets)
87 .expect("valid output length");
88
89 let aes_key = &shared_secrets[0..32];
90 let mac_key = &shared_secrets[32..];
91 let iv: [u8; IV_LENGTH] = csprng.gen();
92
93 let cipher = cbc::Encryptor::<Aes256>::new(aes_key.into(), &iv.into());
94 let ciphertext = cipher.encrypt_padded_vec_mut::<Pkcs7>(&msg);
95 let mut mac = Hmac::<Sha256>::new_from_slice(mac_key)
96 .expect("HMAC can take any size key");
97 mac.update(&[VERSION]);
98 mac.update(&iv);
99 mac.update(&ciphertext);
100 let mac = mac.finalize().into_bytes();
101
102 let body: Vec<u8> = std::iter::once(VERSION)
103 .chain(iv.iter().cloned())
104 .chain(ciphertext)
105 .chain(mac)
106 .collect();
107
108 Ok(ProvisionEnvelope {
109 public_key: Some(our_key_pair.public_key.serialize().into()),
110 body: Some(body),
111 })
112 }
113
114 #[expect(clippy::result_large_err)]
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 let body = provision_envelope
129 .body
130 .expect("no body in ProvisionMessage");
131 if body[0] != VERSION {
132 return Err(ProvisioningError::BadVersionNumber);
133 }
134
135 let iv = &body[IV_OFFSET..(IV_LENGTH + IV_OFFSET)];
136 let mac = &body[(body.len() - 32)..];
137 let cipher_text = &body[16 + 1..(body.len() - CIPHER_KEY_SIZE)];
138 let iv_and_cipher_text = &body[0..(body.len() - CIPHER_KEY_SIZE)];
139 debug_assert_eq!(iv.len(), IV_LENGTH);
140 debug_assert_eq!(mac.len(), 32);
141
142 let agreement = key_pair.calculate_agreement(&master_ephemeral)?;
143
144 let mut shared_secrets = [0; 64];
145 hkdf::Hkdf::<sha2::Sha256>::new(None, &agreement)
146 .expand(b"TextSecure Provisioning Message", &mut shared_secrets)
147 .expect("valid output length");
148
149 let parts1 = &shared_secrets[0..32];
150 let parts2 = &shared_secrets[32..];
151
152 let mut verifier = Hmac::<Sha256>::new_from_slice(parts2)
153 .expect("HMAC can take any size key");
154 verifier.update(iv_and_cipher_text);
155 let our_mac = verifier.finalize().into_bytes();
156 debug_assert_eq!(our_mac.len(), mac.len());
157 if &our_mac[..32] != mac {
158 return Err(ProvisioningError::MismatchedMac);
159 }
160
161 let cipher = cbc::Decryptor::<Aes256>::new(parts1.into(), iv.into());
165 let input = cipher
166 .decrypt_padded_vec_mut::<Pkcs7>(cipher_text)
167 .map_err(ProvisioningError::AesPaddingError)?;
168
169 Ok(prost::Message::decode(Bytes::from(input))?)
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 #[test]
178 fn encrypt_provisioning_roundtrip() -> anyhow::Result<()> {
179 let mut rng = rand::thread_rng();
180 let key_pair = KeyPair::generate(&mut rng);
181 let cipher = ProvisioningCipher::from_key_pair(key_pair);
182 let encrypt_cipher: ProvisioningCipher =
183 ProvisioningCipher::from_public(*cipher.public_key());
184
185 assert_eq!(
186 cipher.public_key(),
187 encrypt_cipher.public_key(),
188 "copy public key"
189 );
190
191 let msg = ProvisionMessage::default();
192 let encrypted = encrypt_cipher.encrypt(&mut rng, msg.clone())?;
193
194 assert!(matches!(
195 encrypt_cipher.decrypt(encrypted.clone()),
196 Err(ProvisioningError::EncryptOnlyProvisioningCipher)
197 ));
198
199 let decrypted = cipher.decrypt(encrypted)?;
200 assert_eq!(msg, decrypted);
201
202 Ok(())
203 }
204}