7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 1 | // Copyright 2023 The Matrix.org Foundation C.I.C. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 2 | // |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 4 | // you may not use this file except in compliance with the License. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 5 | // You may obtain a copy of the License at |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 6 | // |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 8 | // |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 9 | // Unless required by applicable law or agreed to in writing, software |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 12 | // See the License for the specific language governing permissions and |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 13 | // limitations under the License. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 14 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 15 | use aes::{ |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 16 | Aes256, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 17 | cipher::{IvSizeUser, KeyIvInit, KeySizeUser, StreamCipher, generic_array::GenericArray}, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 18 | }; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 19 | use ctr::Ctr128BE; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 20 | use hkdf::Hkdf; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 21 | use hmac::{ |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 22 | Hmac, Mac as _, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 23 | digest::{FixedOutput, MacError}, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 24 | }; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 25 | use pbkdf2::pbkdf2; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 26 | use rand::{Rng, rng}; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 27 | use sha2::{Sha256, Sha512}; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 28 | use zeroize::{Zeroize, ZeroizeOnDrop}; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 29 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 30 | // We could use the `keysize()` method Aes256Ctr as KeySize exposes, but it's |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 31 | // not const (yet?), same for the IV size. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 32 | pub(crate) const IV_SIZE: usize = 16; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 33 | pub(crate) const KEY_SIZE: usize = 32; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 34 | pub(crate) const SALT_SIZE: usize = 16; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 35 | pub(crate) const MAC_SIZE: usize = 32; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 36 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 37 | type Aes256Ctr = Ctr128BE<Aes256>; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 38 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 39 | type Aes256Key = GenericArray<u8, <Aes256Ctr as KeySizeUser>::KeySize>; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 40 | type Aes256Iv = GenericArray<u8, <Aes256Ctr as IvSizeUser>::IvSize>; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 41 | type HmacSha256Key = [u8; KEY_SIZE]; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 42 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 43 | /// An authentication tag for the HMAC-SHA-256 message authentication algorithm. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 44 | #[derive(Debug)] |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 45 | pub(crate) struct HmacSha256Mac([u8; MAC_SIZE]); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 46 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 47 | impl HmacSha256Mac { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 48 | /// Represent the MAC tag as an array of bytes. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 49 | pub(crate) fn as_bytes(&self) -> &[u8; MAC_SIZE] { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 50 | &self.0 |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 51 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 52 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 53 | /// Return the underlying array of bytes of the authentication tag. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 54 | pub(crate) fn into_bytes(self) -> [u8; MAC_SIZE] { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 55 | self.0 |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 56 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 57 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 58 | /// Try to create a [`HmacSha256Mac`] from a slice of bytes. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 59 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 60 | /// Returns `None` if the length of the byte slice isn't 32 bytes. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 61 | pub(crate) fn from_slice(bytes: &[u8]) -> Option<Self> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 62 | if bytes.len() != MAC_SIZE { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 63 | None |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 64 | } else { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 65 | let mut mac = [0u8; MAC_SIZE]; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 66 | mac.copy_from_slice(bytes); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 67 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 68 | Some(HmacSha256Mac(mac)) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 69 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 70 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 71 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 72 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 73 | /// Keys used for our combination of AES-CTR-256 and HMAC-SHA-256. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 74 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 75 | /// ⚠️ This struct provides low-level cryptographic primitives. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 76 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 77 | /// This combination is, as of now, used in the following places: |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 78 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 79 | /// 1. Secret storage[1] |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 80 | /// 2. File-based key exports[2] |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 81 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 82 | /// [1]: https://spec.matrix.org/v1.8/client-server-api/#msecret_storagev1aes-hmac-sha2 |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 83 | /// [2]: https://spec.matrix.org/v1.8/client-server-api/#key-exports |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 84 | #[derive(Zeroize, ZeroizeOnDrop)] |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 85 | pub(crate) struct AesHmacSha2Key { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 86 | aes_key: Box<[u8; KEY_SIZE]>, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 87 | mac_key: Box<[u8; KEY_SIZE]>, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 88 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 89 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 90 | impl AesHmacSha2Key { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 91 | /// Create a [`AesHmacSha2Key`] from a passphrase. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 92 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 93 | /// The passphrase will be expanded using the algorithm described in the |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 94 | /// "Key export" part of the [spec]. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 95 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 96 | /// [spec]: https://spec.matrix.org/v1.8/client-server-api/#key-exports |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 97 | const ZERO_SALT: &'static [u8; 32] = &[0u8; 32]; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 98 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 99 | /// Create a per-secret specific [`AesHmacSha2Key`] from the secret storage |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 100 | /// key. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 101 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 102 | /// The secret storage key will be expanded as described in the [spec]. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 103 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 104 | /// [spec]: https://spec.matrix.org/v1.8/client-server-api/#msecret_storagev1aes-hmac-sha2 |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 105 | pub(crate) fn from_secret_storage_key( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 106 | secret_storage_key: &[u8; KEY_SIZE], |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 107 | secret_name: &str, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 108 | ) -> Self { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 109 | let mut expanded_keys = [0u8; KEY_SIZE * 2]; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 110 | let hkdf: Hkdf<Sha256> = Hkdf::new(Some(Self::ZERO_SALT), secret_storage_key); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 111 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 112 | hkdf.expand(secret_name.as_bytes(), &mut expanded_keys) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 113 | .expect("We should be able to expand 64 bytes of output key material."); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 114 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 115 | let (aes_key, mac_key) = Self::split_keys(&expanded_keys); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 116 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 117 | expanded_keys.zeroize(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 118 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 119 | Self { aes_key, mac_key } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 120 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 121 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 122 | pub(crate) fn from_passphrase( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 123 | passphrase: &str, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 124 | pbkdf_rounds: u32, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 125 | salt: &[u8; SALT_SIZE], |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 126 | ) -> Self { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 127 | let mut expanded_keys = [0u8; KEY_SIZE * 2]; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 128 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 129 | pbkdf2::<Hmac<Sha512>>(passphrase.as_bytes(), salt, pbkdf_rounds, &mut expanded_keys) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 130 | .expect( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 131 | "We should be able to expand a passphrase of any length due to \ |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 132 | HMAC being able to be initialized with any input size", |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 133 | ); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 134 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 135 | let (aes_key, mac_key) = Self::split_keys(&expanded_keys); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 136 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 137 | expanded_keys.zeroize(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 138 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 139 | Self { aes_key, mac_key } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 140 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 141 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 142 | /// Encrypt the given plaintext and return the ciphertext and the |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 143 | /// initialization vector. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 144 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 145 | /// ⚠️ This method is a low-level cryptographic primitive. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 146 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 147 | /// This method does not provide authenticity. You *must* call the |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 148 | /// [`AesHmacSha2Key::create_mac_tag()`] method after the encryption step to |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 149 | /// create a authentication tag. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 150 | pub(crate) fn encrypt(&self, plaintext: Vec<u8>) -> (Vec<u8>, [u8; IV_SIZE]) { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 151 | let initialization_vector = Self::generate_iv(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 152 | let ciphertext = self.apply_keystream(plaintext, &initialization_vector); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 153 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 154 | (ciphertext, initialization_vector) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 155 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 156 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 157 | /// Apply the keystream to the data stream, producing either the plaintext |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 158 | /// or the ciphertext depending on whether the data stream is the ciphertext |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 159 | /// or the plaintext, respectively. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 160 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 161 | /// ⚠️ This method is a low-level cryptographic primitive. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 162 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 163 | /// If this method is encrypting a plaintext, you *must* ensure that the |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 164 | /// initialization vector is unique across all calls to this method for |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 165 | /// a given key. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 166 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 167 | /// This method does not provide authenticity. You *must* call the |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 168 | /// [`AesHmacSha2Key::create_mac_tag()`] method after the encryption step to |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 169 | /// create a authentication tag or the [`AesHmacSha2Key::verify_mac()`] |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 170 | /// method before decrypting. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 171 | pub(crate) fn apply_keystream( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 172 | &self, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 173 | mut plaintext: Vec<u8>, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 174 | initialization_vector: &[u8; IV_SIZE], |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 175 | ) -> Vec<u8> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 176 | let mut cipher = |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 177 | Aes256Ctr::new(self.aes_key(), Aes256Iv::from_slice(initialization_vector)); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 178 | cipher.apply_keystream(&mut plaintext); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 179 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 180 | plaintext |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 181 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 182 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 183 | /// Create an authentication tag for the given ciphertext. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 184 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 185 | /// ⚠️ This method is a low-level cryptographic primitive. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 186 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 187 | /// This method *must* be called after a call to |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 188 | /// [`AesHmacSha2Key::encrypt()`]. The authentication tag must be |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 189 | /// provided besides the ciphertext for a decryption attempt. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 190 | pub(crate) fn create_mac_tag(&self, ciphertext: &[u8]) -> HmacSha256Mac { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 191 | let mut mac = [0u8; 32]; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 192 | let mac_array = GenericArray::from_mut_slice(&mut mac); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 193 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 194 | let mut hmac = Hmac::<Sha256>::new_from_slice(self.mac_key()) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 195 | .expect("We should be able to create a new HMAC object from our 32 byte MAC key"); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 196 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 197 | hmac.update(ciphertext); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 198 | hmac.finalize_into(mac_array); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 199 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 200 | HmacSha256Mac(mac) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 201 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 202 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 203 | /// Verify an authentication tag for the given, encrypted, message. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 204 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 205 | /// You *must* use this method to compare the authentication tags. This |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 206 | /// method provides a constant-time comparison for the authentication tags. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 207 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 208 | /// This method *must* be called before a call to |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 209 | /// [`AesHmacSha2Key::decrypt()`]. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 210 | pub(crate) fn verify_mac(&self, message: &[u8], mac: &[u8; MAC_SIZE]) -> Result<(), MacError> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 211 | let mac_array = GenericArray::from_slice(mac); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 212 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 213 | let mut hmac = Hmac::<Sha256>::new_from_slice(self.mac_key()) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 214 | .expect("We should be able to create a new HMAC object from our 32 byte MAC key"); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 215 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 216 | hmac.update(message); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 217 | hmac.verify(mac_array) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 218 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 219 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 220 | /// Decrypt the given ciphertext and return the decrypted plaintext. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 221 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 222 | /// The method does not provide authenticity. You *must* call the |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 223 | /// [`AesHmacSha2Key::verify_mac()`] method before the decryption step to |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 224 | /// verify the authentication tag. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 225 | pub(crate) fn decrypt( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 226 | &self, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 227 | ciphertext: Vec<u8>, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 228 | initialization_vector: &[u8; IV_SIZE], |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 229 | ) -> Vec<u8> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 230 | self.apply_keystream(ciphertext, initialization_vector) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 231 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 232 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 233 | fn split_keys( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 234 | expanded_keys: &[u8; KEY_SIZE * 2], |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 235 | ) -> (Box<[u8; KEY_SIZE]>, Box<[u8; KEY_SIZE]>) { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 236 | let mut aes_key = Box::new([0u8; KEY_SIZE]); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 237 | let mut mac_key = Box::new([0u8; KEY_SIZE]); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 238 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 239 | aes_key.copy_from_slice(&expanded_keys[0..32]); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 240 | mac_key.copy_from_slice(&expanded_keys[32..64]); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 241 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 242 | (aes_key, mac_key) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 243 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 244 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 245 | /// Generate a new, random initialization vector. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 246 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 247 | /// The initialization vector will be clamped and will be used to encrypt |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 248 | /// the ciphertext. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 249 | fn generate_iv() -> [u8; IV_SIZE] { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 250 | let mut rng = rng(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 251 | let mut iv = [0u8; IV_SIZE]; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 252 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 253 | rng.fill_bytes(&mut iv); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 254 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 255 | Self::clamp_iv(iv) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 256 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 257 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 258 | /// The spec tells us to set bit 63 to 0 in some cases for some reason, I'm |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 259 | /// not sure why, but fine: |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 260 | /// Generate 16 random bytes, set bit 63 to 0 (in order to work around |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 261 | /// differences in AES-CTR implementations), and use this as the AES |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 262 | /// initialization vector. This becomes the iv property, encoded using |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 263 | /// base64[1]. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 264 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 265 | /// [1]: https://spec.matrix.org/v1.8/client-server-api/#msecret_storagev1aes-hmac-sha2 |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 266 | fn clamp_iv(iv: [u8; 16]) -> [u8; IV_SIZE] { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 267 | let mut iv = u128::from_be_bytes(iv); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 268 | iv &= !(1 << 63); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 269 | iv.to_be_bytes() |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 270 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 271 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 272 | /// Get the encryption key. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 273 | fn aes_key(&self) -> &Aes256Key { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 274 | Aes256Key::from_slice(self.aes_key.as_slice()) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 275 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 276 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 277 | /// Get the authentication key. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 278 | fn mac_key(&self) -> &HmacSha256Key { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 279 | &self.mac_key |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 280 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 281 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 282 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 283 | #[cfg(test)] |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 284 | mod test { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 285 | use super::*; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 286 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 287 | #[test] |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 288 | fn encryption_roundtrip() { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 289 | let plaintext = "It's a secret to everybody"; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 290 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 291 | let salt = [0u8; SALT_SIZE]; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 292 | let key = AesHmacSha2Key::from_passphrase("My passphrase", 10, &salt); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 293 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 294 | let (ciphertext, iv) = key.encrypt(plaintext.as_bytes().to_vec()); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 295 | let mac = key.create_mac_tag(&ciphertext); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 296 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 297 | key.verify_mac(&ciphertext, mac.as_bytes()) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 298 | .expect("The MAC tag should be successfully verified"); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 299 | let decrypted = key.decrypt(ciphertext, &iv); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 300 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 301 | assert_eq!( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 302 | plaintext.as_bytes(), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 303 | decrypted, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 304 | "An encryption roundtrip should produce the same plaintext" |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 305 | ); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 306 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 307 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 308 | #[test] |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 309 | fn mac_decoding() { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 310 | let invalid_mac = [0u8; 10]; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 311 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 312 | assert!( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 313 | HmacSha256Mac::from_slice(&invalid_mac).is_none(), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 314 | "We should return an error if the MAC is too short" |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 315 | ); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 316 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 317 | let mac = [0u8; 32]; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 318 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 319 | HmacSha256Mac::from_slice(&mac) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 320 | .expect("We should be able to create a MAC from a 32 byte long slice"); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 321 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 322 | } |