Skip to content

Commit

Permalink
Secret storage support
Browse files Browse the repository at this point in the history
Co-authored-by: Ivan Enderlin <ivan@mnt.io>
  • Loading branch information
poljar and Hywan committed Sep 26, 2023
1 parent 1180c6a commit cda9ed1
Show file tree
Hide file tree
Showing 6 changed files with 906 additions and 11 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions crates/matrix-sdk-crypto/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# unreleased

- Add support for secret storage.

- Add initial support for MSC3814 - dehydrated devices.

- Mark our `OwnUserIdentity` as verified if we successfully import the matching
Expand Down
5 changes: 3 additions & 2 deletions crates/matrix-sdk-crypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ default = []
automatic-room-key-forwarding = []
js = ["ruma/js", "vodozemac/js"]
qrcode = ["dep:matrix-sdk-qrcode"]
backups_v1 = ["dep:bs58", "dep:cbc"]
backups_v1 = ["dep:cbc"]
message-ids = ["dep:ulid"]
experimental-algorithms = []

Expand All @@ -32,7 +32,7 @@ atomic = "0.5.1"
as_variant = { workspace = true }
async-std = { version = "1.12.0", features = ["unstable"] }
async-trait = { workspace = true }
bs58 = { version = "0.5.0", optional = true }
bs58 = { version = "0.5.0" }
byteorder = { workspace = true }
cbc = { version = "0.1.2", features = ["std"], optional = true }
cfg-if = "1.0"
Expand All @@ -54,6 +54,7 @@ ruma = { workspace = true, features = ["rand", "canonical-json", "unstable-msc38
serde = { workspace = true, features = ["derive", "rc"] }
serde_json = { workspace = true }
sha2 = "0.10.2"
subtle = "2.5.0"
tokio-stream = { version = "0.1.12", features = ["sync"] }
tokio = { workspace = true, default-features = false, features = ["sync"] }
thiserror = { workspace = true }
Expand Down
94 changes: 85 additions & 9 deletions crates/matrix-sdk-crypto/src/ciphers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use aes::{
Aes256,
};
use ctr::Ctr128BE;
use hkdf::Hkdf;
use hmac::{
digest::{FixedOutput, MacError},
Hmac, Mac as _,
Expand Down Expand Up @@ -48,6 +49,25 @@ impl HmacSha256Mac {
pub(crate) fn as_bytes(&self) -> &[u8; MAC_SIZE] {
&self.0
}

/// Return the underlying array of bytes of the authentication tag.
pub(crate) fn into_bytes(self) -> [u8; MAC_SIZE] {
self.0
}

/// Try to create a [`HmacSha256Mac`] from a slice of bytes.
///
/// Returns `None` if the length of the byte slice isn't 32 bytes.
pub(crate) fn from_slice(bytes: &[u8]) -> Option<Self> {
if bytes.len() != MAC_SIZE {
None
} else {
let mut mac = [0u8; MAC_SIZE];
mac.copy_from_slice(bytes);

Some(HmacSha256Mac(mac))
}
}
}

/// Keys used for our combination of AES-CTR-256 and HMAC-SHA-256.
Expand All @@ -74,6 +94,31 @@ impl AesHmacSha2Key {
/// "Key export" part of the [spec].
///
/// [spec]: https://spec.matrix.org/v1.8/client-server-api/#key-exports
const ZERO_SALT: &[u8; 32] = &[0u8; 32];

/// Create a per-secret specific [`AesHmacSha2Key`] from the secret storage
/// key.
///
/// The secret storage key will be expanded as described in the [spec].
///
/// [spec]: https://spec.matrix.org/v1.8/client-server-api/#msecret_storagev1aes-hmac-sha2
pub(crate) fn from_secret_storage_key(
secret_storage_key: &[u8; KEY_SIZE],
secret_name: &str,
) -> Self {
let mut expanded_keys = [0u8; KEY_SIZE * 2];
let hkdf: Hkdf<Sha256> = Hkdf::new(Some(Self::ZERO_SALT), secret_storage_key);

hkdf.expand(secret_name.as_bytes(), &mut expanded_keys)
.expect("We should be able to epxand 64 bytes of output key material.");

let (aes_key, mac_key) = Self::split_keys(&expanded_keys);

expanded_keys.zeroize();

Self { aes_key, mac_key }
}

pub(crate) fn from_passphrase(
passphrase: &str,
pbkdf_rounds: u32,
Expand All @@ -98,13 +143,33 @@ impl AesHmacSha2Key {
/// The method does not provide authenticity. You *must* call the
/// [`AesHmacSha2Key::create_mac_tag()`] method after the encryption step to
/// create a authentication tag.
pub(crate) fn encrypt(&self, mut plaintext: Vec<u8>) -> (Vec<u8>, [u8; IV_SIZE]) {
pub(crate) fn encrypt(&self, plaintext: Vec<u8>) -> (Vec<u8>, [u8; IV_SIZE]) {
let initialization_vector = Self::generate_iv();
let ciphertext = self.encrypt_with_iv(plaintext, &initialization_vector);

(ciphertext, initialization_vector)
}

/// Encrypt the given plaintext and return the ciphertext and the
/// initialization vector.
///
/// ⚠️ This method is a low level cryptographic primitive.
///
/// You *must* ensure that the initialization vector is unique.
///
/// The method does not provide authenticity. You *must* call the
/// [`AesHmacSha2Key::create_mac_tag()`] method after the encryption step to
/// create a authentication tag.
pub(crate) fn encrypt_with_iv(
&self,
mut plaintext: Vec<u8>,
initialization_vector: &[u8; IV_SIZE],
) -> Vec<u8> {
let mut cipher =
Aes256Ctr::new(self.aes_key(), Aes256Iv::from_slice(&initialization_vector));
Aes256Ctr::new(self.aes_key(), Aes256Iv::from_slice(initialization_vector));
cipher.apply_keystream(&mut plaintext);

(plaintext, initialization_vector)
plaintext
}

/// Create an authentication tag for the given ciphertext.
Expand Down Expand Up @@ -151,14 +216,10 @@ impl AesHmacSha2Key {
/// verify the authentication tag.
pub(crate) fn decrypt(
&self,
mut ciphertext: Vec<u8>,
ciphertext: Vec<u8>,
initialization_vector: &[u8; IV_SIZE],
) -> Vec<u8> {
let initialization_vector = Aes256Iv::from_slice(initialization_vector.as_slice());
let mut cipher = Aes256Ctr::new(self.aes_key(), initialization_vector);
cipher.apply_keystream(&mut ciphertext);

ciphertext
self.encrypt_with_iv(ciphertext, initialization_vector)
}

fn split_keys(
Expand Down Expand Up @@ -235,4 +296,19 @@ mod test {
"An encryption roundtrip should produce the same plaintext"
);
}

#[test]
fn mac_decoding() {
let invalid_mac = [0u8; 10];

assert!(
HmacSha256Mac::from_slice(&invalid_mac).is_none(),
"We should return an error if the MAC is too short"
);

let mac = [0u8; 32];

HmacSha256Mac::from_slice(&mac)
.expect("We should be able to create a MAC from a 32 byte long slice");
}
}
1 change: 1 addition & 0 deletions crates/matrix-sdk-crypto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ mod identities;
mod machine;
pub mod olm;
pub mod requests;
pub mod secret_storage;
mod session_manager;
pub mod store;
pub mod types;
Expand Down
Loading

0 comments on commit cda9ed1

Please sign in to comment.