diff --git a/Cargo.lock b/Cargo.lock index 435ae66..26bb172 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -414,9 +414,9 @@ checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "mbedtls" -version = "0.12.0-alpha.2" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "228976da3acdebe818892aff63978494dc84104a8b3a250b0554f24436143a93" +checksum = "80ecfbadfdbaa354a1cb85c03317ae679ef15363e15aa251ada8364fa364e6c3" dependencies = [ "bitflags 1.3.2", "byteorder", @@ -680,7 +680,7 @@ dependencies = [ [[package]] name = "rustls-mbedcrypto-provider" -version = "0.0.1-alpha.1" +version = "0.0.1" dependencies = [ "bencher", "bit-vec 0.6.3", @@ -697,7 +697,7 @@ dependencies = [ [[package]] name = "rustls-mbedpki-provider" -version = "0.1.0-alpha.1" +version = "0.0.1" dependencies = [ "chrono", "mbedtls", @@ -721,7 +721,7 @@ dependencies = [ [[package]] name = "rustls-mbedtls-provider-utils" -version = "0.1.0-alpha.1" +version = "0.1.0" dependencies = [ "mbedtls", "rustls", diff --git a/Cargo.toml b/Cargo.toml index fd64931..6acc2e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,5 +5,9 @@ members = [ "rustls-mbedpki-provider", "rustls-mbedtls-provider-utils", ] -default-members = ["rustls-mbedcrypto-provider", "rustls-mbedpki-provider", "rustls-mbedtls-provider-utils"] +default-members = [ + "rustls-mbedcrypto-provider", + "rustls-mbedpki-provider", + "rustls-mbedtls-provider-utils", +] resolver = "2" diff --git a/rustls-mbedcrypto-provider/Cargo.toml b/rustls-mbedcrypto-provider/Cargo.toml index 37c6376..b218ffa 100644 --- a/rustls-mbedcrypto-provider/Cargo.toml +++ b/rustls-mbedcrypto-provider/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustls-mbedcrypto-provider" -version = "0.0.1-alpha.1" +version = "0.0.1" edition = "2021" license = "MPL-2.0" description = "Mbedtls based crypto provider for rustls." @@ -13,29 +13,19 @@ resolver = "2" [dependencies] rustls = { version = "0.22.1", default-features = false } -mbedtls = { version = "0.12.0-alpha.2", default-features = false, features = [ - "std", -] } +mbedtls = { version = "0.12.1", default-features = false, features = ["std"] } log = { version = "0.4.4", optional = true } webpki = { package = "rustls-webpki", version = "0.102.0", features = [ "alloc", "std", ], default-features = false } -utils = { package = "rustls-mbedtls-provider-utils", path = "../rustls-mbedtls-provider-utils", version = "0.1.0-alpha.1" } +utils = { package = "rustls-mbedtls-provider-utils", path = "../rustls-mbedtls-provider-utils", version = "0.1.0" } yasna = { version = "0.3", default-features = false, features = ["bit-vec"] } bit-vec = "0.6.3" -[target.'cfg(target_env = "msvc")'.dependencies] -# mbedtls need feature `time` to build when targeting msvc -mbedtls = { version = "0.12.0-alpha.2", default-features = false, features = [ - "std", - "time", -] } [dev-dependencies] -rustls = { version = "0.22.1", default-features = false, features = [ - "ring", -] } +rustls = { version = "0.22.1", default-features = false, features = ["ring"] } webpki-roots = "0.26.0" rustls-pemfile = "2" env_logger = "0.10" @@ -59,6 +49,3 @@ path = "examples/internal/bench.rs" [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] - -[package.metadata.cargo_check_external_types] -allowed_external_types = ["rustls_pki_types::*"] diff --git a/rustls-mbedcrypto-provider/src/hmac.rs b/rustls-mbedcrypto-provider/src/hmac.rs index 0bb6e0c..16e5fb1 100644 --- a/rustls-mbedcrypto-provider/src/hmac.rs +++ b/rustls-mbedcrypto-provider/src/hmac.rs @@ -7,8 +7,6 @@ use crate::log::error; use alloc::boxed::Box; -use alloc::vec; -use alloc::vec::Vec; use rustls::crypto; /// HMAC using SHA-256. @@ -17,7 +15,7 @@ pub(crate) static HMAC_SHA256: Hmac = Hmac(&super::hash::MBED_SHA_256); /// HMAC using SHA-384. pub(crate) static HMAC_SHA384: Hmac = Hmac(&super::hash::MBED_SHA_384); -pub(crate) struct Hmac(&'static super::hash::Algorithm); +pub(crate) struct Hmac(pub(crate) &'static super::hash::Algorithm); impl crypto::hmac::Hmac for Hmac { fn with_key(&self, key: &[u8]) -> Box { @@ -29,17 +27,24 @@ impl crypto::hmac::Hmac for Hmac { } } +impl Hmac { + #[inline] + pub(crate) fn hash_algorithm(&self) -> &'static super::hash::Algorithm { + self.0 + } +} + struct HmacKey(MbedHmacKey); impl crypto::hmac::Key for HmacKey { - fn sign_concat(&self, first: &[u8], middle: &[&[u8]], last: &[u8]) -> crypto::hmac::Tag { + fn sign_concat(&self, first: &[u8], middle: &[&[u8]], last: &[u8]) -> rustls::crypto::hmac::Tag { let mut ctx = self.0.starts(); ctx.update(first); for m in middle { ctx.update(m); } ctx.update(last); - crypto::hmac::Tag::new(&ctx.finish()) + ctx.finish().into() } fn tag_len(&self) -> usize { @@ -49,13 +54,12 @@ impl crypto::hmac::Key for HmacKey { struct MbedHmacKey { hmac_algo: &'static super::hash::Algorithm, - /// use [`crypto::hmac::Tag`] for saving key material, since they have same max size. - key: crypto::hmac::Tag, + key: Tag, } impl MbedHmacKey { pub(crate) fn new(hmac_algo: &'static super::hash::Algorithm, key: &[u8]) -> Self { - Self { hmac_algo, key: crypto::hmac::Tag::new(key) } + Self { hmac_algo, key: Tag::new(key) } } pub(crate) fn starts(&self) -> MbedHmacContext { @@ -73,13 +77,13 @@ struct MbedHmacContext { impl MbedHmacContext { /// Since the trait does not provider a way to return error, empty vector is returned when getting error from `mbedtls`. - pub(crate) fn finish(self) -> Vec { - let mut out = vec![0u8; self.hmac_algo.output_len]; - match self.ctx.finish(&mut out) { + pub(crate) fn finish(self) -> Tag { + let mut out = Tag::with_len(self.hmac_algo.output_len); + match self.ctx.finish(out.as_mut()) { Ok(_) => out, Err(_err) => { error!("Failed to finish hmac, mbedtls error: {:?}", _err); - vec![] + Tag::with_len(0) } } } @@ -93,3 +97,97 @@ impl MbedHmacContext { } } } + +/// A HMAC tag, stored as a value. +#[derive(Clone, PartialEq, Eq, Debug)] +pub(crate) struct Tag { + buf: [u8; Self::MAX_LEN], + used: usize, +} + +impl Tag { + /// Build a tag by copying a byte slice. + /// + /// The slice can be up to [`Tag::MAX_LEN`] bytes in length. + pub(crate) fn new(bytes: &[u8]) -> Self { + let mut tag = Self { buf: [0u8; Self::MAX_LEN], used: bytes.len() }; + tag.buf[..tag.used].copy_from_slice(bytes); + tag + } + + /// Build a tag with given capacity. + /// + /// The slice can be up to [`Tag::MAX_LEN`] bytes in length. + pub(crate) fn with_len(len: usize) -> Self { + Self { buf: [0u8; Self::MAX_LEN], used: len } + } + + /// Maximum supported HMAC tag size: supports up to SHA512. + pub(crate) const MAX_LEN: usize = 64; +} + +impl Drop for Tag { + fn drop(&mut self) { + mbedtls::zeroize(&mut self.buf) + } +} + +impl AsRef<[u8]> for Tag { + fn as_ref(&self) -> &[u8] { + &self.buf[..self.used] + } +} + +impl AsMut<[u8]> for Tag { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.buf[..self.used] + } +} + +impl From for rustls::crypto::hmac::Tag { + fn from(val: Tag) -> Self { + Self::new(&val.buf[..val.used]) + } +} + +#[cfg(test)] +mod tests { + use rustls::crypto::hmac::Hmac; + + use super::*; + + #[test] + fn test_hmac_sha256_tag_length() { + let hmac = &HMAC_SHA256; + let key_len = 256 / 8; + let key = vec![0u8; key_len]; + test_hmac_tag_length_helper(hmac, &key, key_len); + } + + #[test] + fn test_hmac_sha384_tag_length() { + let hmac = &HMAC_SHA384; + let key_len = 384 / 8; + let key = vec![0u8; key_len]; + test_hmac_tag_length_helper(hmac, &key, key_len); + } + + fn test_hmac_tag_length_helper(hmac: &super::Hmac, key: &[u8], key_len: usize) { + let hmac_key = hmac.with_key(key); + assert_eq!(hmac.hash_output_len(), hmac_key.tag_len()); + assert_eq!(hmac.hash_output_len(), key_len); + } + + #[test] + fn test_mbed_hmac_context_error() { + let key_len = 256 / 8; + let key = vec![0u8; key_len]; + let mut bad_ctx = MbedHmacContext { + hmac_algo: &crate::hash::MBED_SHA_256, + ctx: mbedtls::hash::Hmac::new(crate::hash::MBED_SHA_384.hash_type, &key).unwrap(), + }; + bad_ctx.update(&[]); + let tag = bad_ctx.finish(); + assert_eq!(tag, Tag::with_len(0)); + } +} diff --git a/rustls-mbedcrypto-provider/src/tls13.rs b/rustls-mbedcrypto-provider/src/tls13.rs index 0ed1966..83a4635 100644 --- a/rustls-mbedcrypto-provider/src/tls13.rs +++ b/rustls-mbedcrypto-provider/src/tls13.rs @@ -7,6 +7,7 @@ use super::aead; use crate::error::mbedtls_err_to_rustls_error; +use crate::log::error; use alloc::boxed::Box; use alloc::string::String; use alloc::vec::Vec; @@ -16,7 +17,8 @@ use rustls::crypto::cipher::{ make_tls13_aad, AeadKey, BorrowedPlainMessage, Iv, MessageDecrypter, MessageEncrypter, Nonce, OpaqueMessage, PlainMessage, Tls13AeadAlgorithm, UnsupportedOperationError, }; -use rustls::crypto::tls13::HkdfUsingHmac; +use rustls::crypto::hmac::Hmac; +use rustls::crypto::tls13::{Hkdf, HkdfExpander, OkmBlock, OutputLengthError}; use rustls::crypto::CipherSuiteCommon; use rustls::internal::msgs::codec::Codec; use rustls::{ @@ -34,7 +36,7 @@ pub(crate) static TLS13_CHACHA20_POLY1305_SHA256_INTERNAL: &Tls13CipherSuite = & confidentiality_limit: u64::MAX, integrity_limit: 1 << 36, }, - hkdf_provider: &HkdfUsingHmac(&super::hmac::HMAC_SHA256), + hkdf_provider: &MbedHkdfUsingHmac(&super::hmac::HMAC_SHA256), aead_alg: &AeadAlgorithm(&aead::CHACHA20_POLY1305), quic: None, }; @@ -47,7 +49,7 @@ pub static TLS13_AES_256_GCM_SHA384: SupportedCipherSuite = SupportedCipherSuite confidentiality_limit: 1 << 23, integrity_limit: 1 << 52, }, - hkdf_provider: &HkdfUsingHmac(&super::hmac::HMAC_SHA384), + hkdf_provider: &MbedHkdfUsingHmac(&super::hmac::HMAC_SHA384), aead_alg: &AeadAlgorithm(&aead::AES256_GCM), quic: None, }); @@ -60,7 +62,7 @@ pub static TLS13_AES_128_GCM_SHA256: SupportedCipherSuite = SupportedCipherSuite confidentiality_limit: 1 << 23, integrity_limit: 1 << 52, }, - hkdf_provider: &HkdfUsingHmac(&super::hmac::HMAC_SHA256), + hkdf_provider: &MbedHkdfUsingHmac(&super::hmac::HMAC_SHA256), aead_alg: &AeadAlgorithm(&aead::AES128_GCM), quic: None, }); @@ -191,3 +193,197 @@ impl MessageDecrypter for Tls13MessageDecrypter { msg.into_tls13_unpadded_message() } } + +struct MbedHkdfUsingHmac<'a>(&'a super::hmac::Hmac); + +const ZERO_IKM: [u8; crate::hmac::Tag::MAX_LEN] = [0u8; crate::hmac::Tag::MAX_LEN]; + +impl<'a> Hkdf for MbedHkdfUsingHmac<'a> { + fn extract_from_zero_ikm(&self, salt: Option<&[u8]>) -> Box { + let md = self.0.hash_algorithm().hash_type; + let capacity = self.0.hash_algorithm().output_len; + let mut prf = crate::hmac::Tag::with_len(capacity); + let prf_res = mbedtls::hash::Hkdf::hkdf_extract(md, salt, &ZERO_IKM[..capacity], prf.as_mut()).map(|_| prf); + Box::new(MbedHkdfHmacExpander { hash_alg: self.0.hash_algorithm(), prf_res }) + } + + fn extract_from_secret(&self, salt: Option<&[u8]>, secret: &[u8]) -> Box { + let md = self.0.hash_algorithm().hash_type; + let mut prf = crate::hmac::Tag::with_len(self.0.hash_algorithm().output_len); + let prf_res = mbedtls::hash::Hkdf::hkdf_extract(md, salt, secret, prf.as_mut()).map(|_| prf); + Box::new(MbedHkdfHmacExpander { hash_alg: self.0.hash_algorithm(), prf_res }) + } + + fn expander_for_okm(&self, okm: &OkmBlock) -> Box { + let mut prf = crate::hmac::Tag::with_len(okm.as_ref().len()); + prf.as_mut() + .copy_from_slice(okm.as_ref()); + Box::new(MbedHkdfHmacExpander { hash_alg: self.0.hash_algorithm(), prf_res: Ok(prf) }) + } + + fn hmac_sign(&self, key: &OkmBlock, message: &[u8]) -> rustls::crypto::hmac::Tag { + self.0 + .with_key(key.as_ref()) + .sign(&[message]) + } +} + +struct MbedHkdfHmacExpander { + hash_alg: &'static super::hash::Algorithm, + prf_res: Result, +} + +impl HkdfExpander for MbedHkdfHmacExpander { + fn expand_slice(&self, info: &[&[u8]], output: &mut [u8]) -> Result<(), OutputLengthError> { + let prf = self.prf_res.as_ref().map_err(|_err| { + error!( + "MbedHkdfExpander::expand_slice got mbedtls error from creation call in Hkdf trait: {:?}", + _err + ); + OutputLengthError + })?; + let info: Vec = info + .iter() + .flat_map(|&slice| slice) + .cloned() + .collect(); + mbedtls::hash::Hkdf::hkdf_expand(self.hash_alg.hash_type, prf.as_ref(), &info, output).map_err(|_err| { + error!("MbedHkdfExpander::expand_slice got mbedtls error: {:?}", _err); + OutputLengthError + }) + } + + fn expand_block(&self, info: &[&[u8]]) -> OkmBlock { + if let Err(_err) = self.prf_res.as_ref() { + error!( + "MbedHkdfExpander::expand_block got mbedtls error from creation call in Hkdf trait: {:?}", + _err + ); + return OkmBlock::new(&[]); + } + let prf = self + .prf_res + .as_ref() + .expect("validated"); + let mut tag = crate::hmac::Tag::with_len(self.hash_alg.output_len); + let info: Vec = info + .iter() + .flat_map(|&slice| slice) + .cloned() + .collect(); + let _ = mbedtls::hash::Hkdf::hkdf_expand(self.hash_alg.hash_type, prf.as_ref(), &info, tag.as_mut()) + .map_err(|_err| error!("MbedHkdfExpander::expand_block got mbedtls error: {:?}", _err)); + OkmBlock::new(tag.as_ref()) + } + + fn hash_len(&self) -> usize { + self.hash_alg.output_len + } +} + +#[cfg(test)] +mod tests { + use crate::aead::Algorithm; + + use super::*; + const NONCE_LEN: usize = 12; + const MAX_LEN: usize = 32; + + static UNSUPPORTED_ALGORITHM: Algorithm = Algorithm { + key_length: 256 / 8, + cipher_type: CipherType::Aes256Cbc, + cipher_id: mbedtls::cipher::raw::CipherId::Aes, + cipher_mode: mbedtls::cipher::raw::CipherMode::CBC, + }; + + #[test] + fn test_extract_keys() { + let algorithm = AeadAlgorithm(&aead::AES128_GCM); + let key_bytes: [u8; MAX_LEN] = [1; MAX_LEN]; + let key = key_bytes.into(); + let iv_bytes: [u8; NONCE_LEN] = [2; NONCE_LEN]; + let iv = Iv::new(iv_bytes); + let _result = algorithm.extract_keys(key, iv).unwrap(); + + let algorithm = AeadAlgorithm(&aead::AES256_GCM); + let key_bytes: [u8; MAX_LEN] = [3; MAX_LEN]; + let key = key_bytes.into(); + let iv_bytes: [u8; NONCE_LEN] = [4; NONCE_LEN]; + let iv = Iv::new(iv_bytes); + let _result = algorithm.extract_keys(key, iv).unwrap(); + + let algorithm = AeadAlgorithm(&aead::CHACHA20_POLY1305); + let key_bytes: [u8; MAX_LEN] = [5; MAX_LEN]; + let key = key_bytes.into(); + let iv_bytes: [u8; NONCE_LEN] = [6; NONCE_LEN]; + let iv = Iv::new(iv_bytes); + let _result = algorithm.extract_keys(key, iv).unwrap(); + + let algorithm = AeadAlgorithm(&UNSUPPORTED_ALGORITHM); + let key_bytes: [u8; MAX_LEN] = [7; MAX_LEN]; + let key = key_bytes.into(); + let iv_bytes: [u8; NONCE_LEN] = [8; NONCE_LEN]; + let iv = Iv::new(iv_bytes); + let result = algorithm.extract_keys(key, iv).is_err(); + assert!(result); + } + + #[test] + fn test_expander_error() { + let hash_alg = &crate::hash::MBED_SHA_256; + let expander = MbedHkdfHmacExpander { hash_alg, prf_res: Err(mbedtls::Error::AesBadInputData) }; + assert!(expander + .expand_slice(&[&[]], &mut []) + .is_err()); + let okm_block = &OkmBlock::new(&[]); + let expected: &[u8] = okm_block.as_ref(); + assert_eq!(expander.expand_block(&[&[]]).as_ref(), expected); + assert_eq!(expander.hash_len(), hash_alg.output_len); + } +} + +#[cfg(bench)] +mod benchmarks { + use rustls::crypto::tls13::{expand, Hkdf}; + + use crate::hmac::HMAC_SHA256; + + struct ByteArray([u8; N]); + + impl From<[u8; N]> for ByteArray { + fn from(array: [u8; N]) -> Self { + Self(array) + } + } + + #[bench] + fn bench_mbedtls_hkdf(b: &mut test::Bencher) { + bench_hkdf(b, &rustls::crypto::tls13::HkdfUsingHmac(&HMAC_SHA256)); + } + + #[bench] + fn bench_rustls_hkdf_mbedtls_hmac(b: &mut test::Bencher) { + bench_hkdf(b, &super::MbedHkdfUsingHmac(&HMAC_SHA256)); + } + + fn bench_hkdf(b: &mut test::Bencher, hkdf: &dyn Hkdf) { + let ikm = &[0x0b; 22]; + let salt = &[0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c]; + let info: &[&[u8]] = &[&[0xf0, 0xf1, 0xf2], &[0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9]]; + b.iter(|| { + let output: ByteArray<42> = expand( + hkdf.extract_from_secret(Some(salt), ikm) + .as_ref(), + info, + ); + assert_eq!( + &output.0, + &[ + 0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a, 0x90, 0x43, 0x4f, 0x64, 0xd0, 0x36, 0x2f, 0x2a, 0x2d, 0x2d, + 0x0a, 0x90, 0xcf, 0x1a, 0x5a, 0x4c, 0x5d, 0xb0, 0x2d, 0x56, 0xec, 0xc4, 0xc5, 0xbf, 0x34, 0x00, 0x72, 0x08, + 0xd5, 0xb8, 0x87, 0x18, 0x58, 0x65 + ] + ); + }); + } +} diff --git a/rustls-mbedpki-provider/Cargo.toml b/rustls-mbedpki-provider/Cargo.toml index cabeb0d..97a06d5 100644 --- a/rustls-mbedpki-provider/Cargo.toml +++ b/rustls-mbedpki-provider/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustls-mbedpki-provider" -version = "0.1.0-alpha.1" +version = "0.0.1" edition = "2021" license = "MPL-2.0" description = "Implements rustls PKI traits using mbedtls" @@ -12,7 +12,7 @@ resolver = "2" [dependencies] rustls = { version = "0.22.1", default_features = false } -mbedtls = { version = "0.12.0-alpha.2", features = [ +mbedtls = { version = "0.12.1", features = [ "x509", "chrono", "std", @@ -20,18 +20,8 @@ mbedtls = { version = "0.12.0-alpha.2", features = [ x509-parser = "0.15" chrono = "0.4" -utils = { package = "rustls-mbedtls-provider-utils", path = "../rustls-mbedtls-provider-utils", version = "0.1.0-alpha.1" } - -[target.'cfg(target_env = "msvc")'.dependencies] -# mbedtls need feature `time` to build when targeting msvc -mbedtls = { version = "0.12.0-alpha.2", default-features = false, features = [ - "x509", - "chrono", - "std", - "time", -] } +utils = { package = "rustls-mbedtls-provider-utils", path = "../rustls-mbedtls-provider-utils", version = "0.1.0" } [dev-dependencies] rustls-pemfile = "2" rustls = { version = "0.22.1" } - diff --git a/rustls-mbedtls-provider-utils/Cargo.toml b/rustls-mbedtls-provider-utils/Cargo.toml index 3a04c4d..9cbafa7 100644 --- a/rustls-mbedtls-provider-utils/Cargo.toml +++ b/rustls-mbedtls-provider-utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustls-mbedtls-provider-utils" -version = "0.1.0-alpha.1" +version = "0.1.0" edition = "2021" license = "MPL-2.0" description = "Utility code used in mbedtls based provider for rustls." @@ -13,13 +13,4 @@ resolver = "2" [dependencies] rustls = { version = "0.22.1", default-features = false } -mbedtls = { version = "0.12.0-alpha.2", default-features = false, features = [ - "std", -] } - -[target.'cfg(target_env = "msvc")'.dependencies] -# mbedtls need feature `time` to build when targeting msvc -mbedtls = { version = "0.12.0-alpha.2", default-features = false, features = [ - "std", - "time", -] } +mbedtls = { version = "0.12.1", default-features = false, features = ["std"] }