1// Copyright 2023 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
1415use aes::{
16 cipher::{generic_array::GenericArray, IvSizeUser, KeyIvInit, KeySizeUser, StreamCipher},
17 Aes256,
18};
19use ctr::Ctr128BE;
20use hkdf::Hkdf;
21use hmac::{
22 digest::{FixedOutput, MacError},
23 Hmac, Mac as _,
24};
25use pbkdf2::pbkdf2;
26use rand::{thread_rng, RngCore};
27use sha2::{Sha256, Sha512};
28use zeroize::{Zeroize, ZeroizeOnDrop};
2930// We could use the `keysize()` method Aes256Ctr as KeySize exposes, but it's
31// not const (yet?), same for the IV size.
32pub(crate) const IV_SIZE: usize = 16;
33pub(crate) const KEY_SIZE: usize = 32;
34pub(crate) const SALT_SIZE: usize = 16;
35pub(crate) const MAC_SIZE: usize = 32;
3637type Aes256Ctr = Ctr128BE<Aes256>;
3839type Aes256Key = GenericArray<u8, <Aes256Ctr as KeySizeUser>::KeySize>;
40type Aes256Iv = GenericArray<u8, <Aes256Ctr as IvSizeUser>::IvSize>;
41type HmacSha256Key = [u8; KEY_SIZE];
4243/// An authentication tag for the HMAC-SHA-256 message authentication algorithm.
44#[derive(Debug)]
45pub(crate) struct HmacSha256Mac([u8; MAC_SIZE]);
4647impl HmacSha256Mac {
48/// Represent the MAC tag as an array of bytes.
49pub(crate) fn as_bytes(&self) -> &[u8; MAC_SIZE] {
50&self.0
51}
5253/// Return the underlying array of bytes of the authentication tag.
54pub(crate) fn into_bytes(self) -> [u8; MAC_SIZE] {
55self.0
56}
5758/// Try to create a [`HmacSha256Mac`] from a slice of bytes.
59 ///
60 /// Returns `None` if the length of the byte slice isn't 32 bytes.
61pub(crate) fn from_slice(bytes: &[u8]) -> Option<Self> {
62if bytes.len() != MAC_SIZE {
63None
64} else {
65let mut mac = [0u8; MAC_SIZE];
66 mac.copy_from_slice(bytes);
6768Some(HmacSha256Mac(mac))
69 }
70 }
71}
7273/// Keys used for our combination of AES-CTR-256 and HMAC-SHA-256.
74///
75/// ⚠️ This struct provides low-level cryptographic primitives.
76///
77/// This combination is, as of now, used in the following places:
78///
79/// 1. Secret storage[1]
80/// 2. File-based key exports[2]
81///
82/// [1]: https://spec.matrix.org/v1.8/client-server-api/#msecret_storagev1aes-hmac-sha2
83/// [2]: https://spec.matrix.org/v1.8/client-server-api/#key-exports
84#[derive(Zeroize, ZeroizeOnDrop)]
85pub(crate) struct AesHmacSha2Key {
86 aes_key: Box<[u8; KEY_SIZE]>,
87 mac_key: Box<[u8; KEY_SIZE]>,
88}
8990impl AesHmacSha2Key {
91/// Create a [`AesHmacSha2Key`] from a passphrase.
92 ///
93 /// The passphrase will be expanded using the algorithm described in the
94 /// "Key export" part of the [spec].
95 ///
96 /// [spec]: https://spec.matrix.org/v1.8/client-server-api/#key-exports
97const ZERO_SALT: &'static [u8; 32] = &[0u8; 32];
9899/// Create a per-secret specific [`AesHmacSha2Key`] from the secret storage
100 /// key.
101 ///
102 /// The secret storage key will be expanded as described in the [spec].
103 ///
104 /// [spec]: https://spec.matrix.org/v1.8/client-server-api/#msecret_storagev1aes-hmac-sha2
105pub(crate) fn from_secret_storage_key(
106 secret_storage_key: &[u8; KEY_SIZE],
107 secret_name: &str,
108 ) -> Self {
109let mut expanded_keys = [0u8; KEY_SIZE * 2];
110let hkdf: Hkdf<Sha256> = Hkdf::new(Some(Self::ZERO_SALT), secret_storage_key);
111112 hkdf.expand(secret_name.as_bytes(), &mut expanded_keys)
113 .expect("We should be able to expand 64 bytes of output key material.");
114115let (aes_key, mac_key) = Self::split_keys(&expanded_keys);
116117 expanded_keys.zeroize();
118119Self { aes_key, mac_key }
120 }
121122pub(crate) fn from_passphrase(
123 passphrase: &str,
124 pbkdf_rounds: u32,
125 salt: &[u8; SALT_SIZE],
126 ) -> Self {
127let mut expanded_keys = [0u8; KEY_SIZE * 2];
128129 pbkdf2::<Hmac<Sha512>>(passphrase.as_bytes(), salt, pbkdf_rounds, &mut expanded_keys)
130 .expect(
131"We should be able to expand a passphrase of any length due to \
132 HMAC being able to be initialized with any input size",
133 );
134135let (aes_key, mac_key) = Self::split_keys(&expanded_keys);
136137 expanded_keys.zeroize();
138139Self { aes_key, mac_key }
140 }
141142/// Encrypt the given plaintext and return the ciphertext and the
143 /// initialization vector.
144 ///
145 /// ⚠️ This method is a low-level cryptographic primitive.
146 ///
147 /// This method does not provide authenticity. You *must* call the
148 /// [`AesHmacSha2Key::create_mac_tag()`] method after the encryption step to
149 /// create a authentication tag.
150pub(crate) fn encrypt(&self, plaintext: Vec<u8>) -> (Vec<u8>, [u8; IV_SIZE]) {
151let initialization_vector = Self::generate_iv();
152let ciphertext = self.apply_keystream(plaintext, &initialization_vector);
153154 (ciphertext, initialization_vector)
155 }
156157/// Apply the keystream to the data stream, producing either the plaintext
158 /// or the ciphertext depending on whether the data stream is the ciphertext
159 /// or the plaintext, respectively.
160 ///
161 /// ⚠️ This method is a low-level cryptographic primitive.
162 ///
163 /// If this method is encrypting a plaintext, you *must* ensure that the
164 /// initialization vector is unique across all calls to this method for
165 /// a given key.
166 ///
167 /// This method does not provide authenticity. You *must* call the
168 /// [`AesHmacSha2Key::create_mac_tag()`] method after the encryption step to
169 /// create a authentication tag or the [`AesHmacSha2Key::verify_mac()`]
170 /// method before decrypting.
171pub(crate) fn apply_keystream(
172&self,
173mut plaintext: Vec<u8>,
174 initialization_vector: &[u8; IV_SIZE],
175 ) -> Vec<u8> {
176let mut cipher =
177 Aes256Ctr::new(self.aes_key(), Aes256Iv::from_slice(initialization_vector));
178 cipher.apply_keystream(&mut plaintext);
179180 plaintext
181 }
182183/// Create an authentication tag for the given ciphertext.
184 ///
185 /// ⚠️ This method is a low-level cryptographic primitive.
186 ///
187 /// This method *must* be called after a call to
188 /// [`AesHmacSha2Key::encrypt()`]. The authentication tag must be
189 /// provided besides the ciphertext for a decryption attempt.
190pub(crate) fn create_mac_tag(&self, ciphertext: &[u8]) -> HmacSha256Mac {
191let mut mac = [0u8; 32];
192let mac_array = GenericArray::from_mut_slice(&mut mac);
193194let mut hmac = Hmac::<Sha256>::new_from_slice(self.mac_key())
195 .expect("We should be able to create a new HMAC object from our 32 byte MAC key");
196197 hmac.update(ciphertext);
198 hmac.finalize_into(mac_array);
199200 HmacSha256Mac(mac)
201 }
202203/// Verify an authentication tag for the given, encrypted, message.
204 ///
205 /// You *must* use this method to compare the authentication tags. This
206 /// method provides a constant-time comparison for the authentication tags.
207 ///
208 /// This method *must* be called before a call to
209 /// [`AesHmacSha2Key::decrypt()`].
210pub(crate) fn verify_mac(&self, message: &[u8], mac: &[u8; MAC_SIZE]) -> Result<(), MacError> {
211let mac_array = GenericArray::from_slice(mac);
212213let mut hmac = Hmac::<Sha256>::new_from_slice(self.mac_key())
214 .expect("We should be able to create a new HMAC object from our 32 byte MAC key");
215216 hmac.update(message);
217 hmac.verify(mac_array)
218 }
219220/// Decrypt the given ciphertext and return the decrypted plaintext.
221 ///
222 /// The method does not provide authenticity. You *must* call the
223 /// [`AesHmacSha2Key::verify_mac()`] method before the decryption step to
224 /// verify the authentication tag.
225pub(crate) fn decrypt(
226&self,
227 ciphertext: Vec<u8>,
228 initialization_vector: &[u8; IV_SIZE],
229 ) -> Vec<u8> {
230self.apply_keystream(ciphertext, initialization_vector)
231 }
232233fn split_keys(
234 expanded_keys: &[u8; KEY_SIZE * 2],
235 ) -> (Box<[u8; KEY_SIZE]>, Box<[u8; KEY_SIZE]>) {
236let mut aes_key = Box::new([0u8; KEY_SIZE]);
237let mut mac_key = Box::new([0u8; KEY_SIZE]);
238239 aes_key.copy_from_slice(&expanded_keys[0..32]);
240 mac_key.copy_from_slice(&expanded_keys[32..64]);
241242 (aes_key, mac_key)
243 }
244245/// Generate a new, random initialization vector.
246 ///
247 /// The initialization vector will be clamped and will be used to encrypt
248 /// the ciphertext.
249fn generate_iv() -> [u8; IV_SIZE] {
250let mut rng = thread_rng();
251let mut iv = [0u8; IV_SIZE];
252253 rng.fill_bytes(&mut iv);
254255Self::clamp_iv(iv)
256 }
257258/// The spec tells us to set bit 63 to 0 in some cases for some reason, I'm
259 /// not sure why, but fine:
260 /// Generate 16 random bytes, set bit 63 to 0 (in order to work around
261 /// differences in AES-CTR implementations), and use this as the AES
262 /// initialization vector. This becomes the iv property, encoded using
263 /// base64[1].
264 ///
265 /// [1]: https://spec.matrix.org/v1.8/client-server-api/#msecret_storagev1aes-hmac-sha2
266fn clamp_iv(iv: [u8; 16]) -> [u8; IV_SIZE] {
267let mut iv = u128::from_be_bytes(iv);
268 iv &= !(1 << 63);
269 iv.to_be_bytes()
270 }
271272/// Get the encryption key.
273fn aes_key(&self) -> &Aes256Key {
274 Aes256Key::from_slice(self.aes_key.as_slice())
275 }
276277/// Get the authentication key.
278fn mac_key(&self) -> &HmacSha256Key {
279&self.mac_key
280 }
281}
282283#[cfg(test)]
284mod test {
285use super::*;
286287#[test]
288fn encryption_roundtrip() {
289let plaintext = "It's a secret to everybody";
290291let salt = [0u8; SALT_SIZE];
292let key = AesHmacSha2Key::from_passphrase("My passphrase", 10, &salt);
293294let (ciphertext, iv) = key.encrypt(plaintext.as_bytes().to_vec());
295let mac = key.create_mac_tag(&ciphertext);
296297 key.verify_mac(&ciphertext, mac.as_bytes())
298 .expect("The MAC tag should be successfully verified");
299let decrypted = key.decrypt(ciphertext, &iv);
300301assert_eq!(
302 plaintext.as_bytes(),
303 decrypted,
304"An encryption roundtrip should produce the same plaintext"
305);
306 }
307308#[test]
309fn mac_decoding() {
310let invalid_mac = [0u8; 10];
311312assert!(
313 HmacSha256Mac::from_slice(&invalid_mac).is_none(),
314"We should return an error if the MAC is too short"
315);
316317let mac = [0u8; 32];
318319 HmacSha256Mac::from_slice(&mac)
320 .expect("We should be able to create a MAC from a 32 byte long slice");
321 }
322}