Skip to content

Commit 421675b

Browse files
authored
Merge branch 'main' into better-sign-verify-tests
2 parents 3145241 + 8607867 commit 421675b

File tree

7 files changed

+546
-22
lines changed

7 files changed

+546
-22
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ For more details about the supported curves, verification/signing methods, and a
3838

3939
1. Clone the repository:
4040
```
41-
git clone --depth=1 git@github.com:gasbytes/rustls-wolfcrypt-provider.git
41+
git clone --depth=1 git@github.com:wolfssl/rustls-wolfcrypt-provider.git
4242
cd rustls-wolfcrypt-provider/
4343
```
4444

rustls-wolfcrypt-provider/Cargo.lock

Lines changed: 13 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rustls-wolfcrypt-provider/Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ version = "0.1.0"
44
edition = "2021"
55

66
[dependencies]
7-
rustls = { version = "0.23.17", default-features = false, features = ["tls12"] }
7+
rustls = { version = "0.23.18", default-features = false, features = ["tls12"] }
88
chacha20poly1305 = { version = "0.10", default-features = false, features = ["alloc"] }
99
der = { version = "0.7", default-features = false }
1010
ecdsa = { version = "0.16.9", default-features = false, features = ["alloc"] }
@@ -28,12 +28,13 @@ anyhow = "1.0.95"
2828
num_cpus = "1.16.0"
2929
lazy_static = "1.5.0"
3030

31+
3132
[dev-dependencies]
3233
rcgen = { version = "0.13" }
3334
serial_test = { version = "3.2.0", default-features = false }
3435
tokio = { version = "1.41", features = ["macros", "rt", "net", "io-util", "io-std"], default-features = false }
3536
webpki-roots = { version = "0.26", default-features = false }
36-
rustls = { version = "0.23.17", features = ["std", "tls12"] }
37+
rustls = { version = "0.23.18", features = ["std", "tls12"] }
3738
rustls-pemfile = { version = "2.2.0", default-features = false, features = ["std"]}
3839

3940
[features]
Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
use rustls::crypto::tls13::{self, Hkdf as RustlsHkdf};
2+
use alloc::boxed::Box;
3+
use alloc::vec;
4+
use core::mem;
5+
use alloc::vec::Vec;
6+
use wolfcrypt_rs::*;
7+
8+
use crate::error::check_if_zero;
9+
use crate::hmac::hmac::WCShaHmac;
10+
11+
pub struct WCHkdfUsingHmac(pub WCShaHmac);
12+
13+
impl RustlsHkdf for WCHkdfUsingHmac {
14+
fn extract_from_zero_ikm(
15+
&self,
16+
salt: Option<&[u8]>,
17+
) -> Box<dyn rustls::crypto::tls13::HkdfExpander> {
18+
let hash_len = self.0.hash_len();
19+
let ikm = vec![0u8; hash_len];
20+
self.extract_from_secret(salt, &ikm)
21+
}
22+
23+
fn extract_from_secret(
24+
&self,
25+
salt: Option<&[u8]>,
26+
ikm: &[u8],
27+
) -> Box<dyn rustls::crypto::tls13::HkdfExpander> {
28+
let hash_len = self.0.hash_len();
29+
let mut extracted_key = vec![0u8; hash_len];
30+
let zero_salt = vec![0u8; hash_len];
31+
let salt_bytes = salt.unwrap_or(&zero_salt);
32+
33+
let ret = unsafe {
34+
wc_HKDF_Extract(
35+
self.0.hash_type().try_into().unwrap(),
36+
salt_bytes.as_ptr(),
37+
salt_bytes.len() as u32,
38+
ikm.as_ptr(),
39+
ikm.len() as u32,
40+
extracted_key.as_mut_ptr(),
41+
)
42+
};
43+
check_if_zero(ret).unwrap();
44+
45+
Box::new(WolfHkdfExpander::new(extracted_key, self.0.hash_type().try_into().unwrap(), self.0.hash_len()))
46+
}
47+
48+
fn expander_for_okm(
49+
&self,
50+
okm: &rustls::crypto::tls13::OkmBlock,
51+
) -> Box<dyn rustls::crypto::tls13::HkdfExpander> {
52+
Box::new(WolfHkdfExpander {
53+
extracted_key: okm.as_ref().to_vec(),
54+
hash_type: self.0.hash_type().try_into().unwrap(),
55+
hash_len: self.0.hash_len(),
56+
})
57+
}
58+
59+
fn hmac_sign(
60+
&self,
61+
key: &rustls::crypto::tls13::OkmBlock,
62+
message: &[u8],
63+
) -> rustls::crypto::hmac::Tag {
64+
let mut hmac = vec![0u8; self.0.hash_len()];
65+
let mut hmac_ctx = unsafe { mem::zeroed() };
66+
67+
let mut ret = unsafe {
68+
wc_HmacSetKey(
69+
&mut hmac_ctx,
70+
self.0.hash_type().try_into().unwrap(),
71+
key.as_ref().as_ptr(),
72+
key.as_ref().len() as u32,
73+
)
74+
};
75+
check_if_zero(ret).unwrap();
76+
77+
ret = unsafe {
78+
wc_HmacUpdate(
79+
&mut hmac_ctx,
80+
message.as_ptr(),
81+
message.len() as u32,
82+
)
83+
};
84+
check_if_zero(ret).unwrap();
85+
86+
ret = unsafe {
87+
wc_HmacFinal(
88+
&mut hmac_ctx,
89+
hmac.as_mut_ptr(),
90+
)
91+
};
92+
check_if_zero(ret).unwrap();
93+
94+
unsafe {
95+
wc_HmacFree(
96+
&mut hmac_ctx,
97+
)
98+
};
99+
check_if_zero(ret).unwrap();
100+
101+
rustls::crypto::hmac::Tag::new(&hmac)
102+
}
103+
}
104+
105+
/// Expander implementation that holds the extracted key material from HKDF extract phase
106+
struct WolfHkdfExpander {
107+
extracted_key: Vec<u8>, // The pseudorandom key (PRK) output from HKDF-Extract
108+
hash_type: i32, // The wolfSSL hash algorithm identifier
109+
hash_len: usize, // Length of the hash function output
110+
}
111+
112+
impl WolfHkdfExpander {
113+
fn new(extracted_key: Vec<u8>, hash_type: i32, hash_len: usize) -> Self {
114+
Self {
115+
extracted_key,
116+
hash_type,
117+
hash_len,
118+
}
119+
}
120+
}
121+
122+
impl tls13::HkdfExpander for WolfHkdfExpander {
123+
fn expand_slice(
124+
&self,
125+
info: &[&[u8]],
126+
output: &mut [u8],
127+
) -> Result<(), tls13::OutputLengthError> {
128+
let info_concat = info.concat();
129+
130+
if output.len() > 255 * self.hash_len {
131+
return Err(tls13::OutputLengthError);
132+
}
133+
134+
unsafe {
135+
wc_HKDF_Expand(
136+
self.hash_type,
137+
self.extracted_key.as_ptr(),
138+
self.extracted_key.len() as u32,
139+
info_concat.as_ptr(),
140+
info_concat.len() as u32,
141+
output.as_mut_ptr(),
142+
output.len() as u32,
143+
);
144+
}
145+
146+
Ok(())
147+
}
148+
149+
fn expand_block(&self, info: &[&[u8]]) -> tls13::OkmBlock {
150+
let mut output = vec![0u8; self.hash_len];
151+
self.expand_slice(info, &mut output)
152+
.expect("expand_block failed");
153+
tls13::OkmBlock::new(&output)
154+
}
155+
156+
fn hash_len(&self) -> usize {
157+
self.hash_len
158+
}
159+
}
160+
161+
#[cfg(test)]
162+
mod tests {
163+
use super::*;
164+
use hex_literal::hex;
165+
166+
/// Tests the HKDF implementation against RFC 5869 test vector A.1
167+
/// This is the primary compliance test using SHA-256
168+
#[test]
169+
fn test_hkdf_sha256() {
170+
// Test vectors from RFC 5869 Appendix A.1
171+
let ikm = hex!("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
172+
let salt = hex!("000102030405060708090a0b0c");
173+
let info = hex!("f0f1f2f3f4f5f6f7f8f9");
174+
let expected_okm = hex!(
175+
"3cb25f25faacd57a90434f64d0362f2a"
176+
"2d2d0a90cf1a5a4c5db02d56ecc4c5bf"
177+
"34007208d5b887185865"
178+
);
179+
180+
let hkdf = WCHkdfUsingHmac(WCShaHmac::new(wc_HashType_WC_HASH_TYPE_SHA256));
181+
let expander = hkdf.extract_from_secret(Some(&salt), &ikm);
182+
183+
let mut okm = vec![0u8; 42]; // Length from test vector
184+
expander.expand_slice(&[&info], &mut okm).unwrap();
185+
186+
assert_eq!(&okm[..], &expected_okm[..]);
187+
}
188+
189+
/// Tests HKDF with SHA-384 to ensure it works with different hash functions
190+
/// Note: This test doesn't verify against RFC test vectors
191+
#[test]
192+
fn test_hkdf_sha384() {
193+
// Test with SHA384
194+
let ikm = hex!("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
195+
let salt = hex!("000102030405060708090a0b0c");
196+
let info = hex!("f0f1f2f3f4f5f6f7f8f9");
197+
198+
let hkdf = WCHkdfUsingHmac(WCShaHmac::new(wc_HashType_WC_HASH_TYPE_SHA384));
199+
let expander = hkdf.extract_from_secret(Some(&salt), &ikm);
200+
201+
let mut okm = vec![0u8; 48]; // SHA384 output length
202+
expander.expand_slice(&[&info], &mut okm).unwrap();
203+
204+
// Just verify we can generate output - actual value would need a verified test vector
205+
assert!(!okm.iter().all(|&x| x == 0));
206+
}
207+
208+
/// Verifies that the HKDF implementation correctly enforces the output length limit
209+
/// The limit is 255 times the hash length as specified in RFC 5869
210+
#[test]
211+
fn test_hkdf_output_length_limit() {
212+
let hkdf = WCHkdfUsingHmac(WCShaHmac::new(wc_HashType_WC_HASH_TYPE_SHA256));
213+
let expander = hkdf.extract_from_zero_ikm(None);
214+
215+
// Maximum allowed length (255 * hash_len)
216+
let max_len = 255 * 32;
217+
let mut okm = vec![0u8; max_len];
218+
assert!(expander.expand_slice(&[&[]], &mut okm).is_ok());
219+
220+
// Exceeding maximum length should fail
221+
let mut okm = vec![0u8; max_len + 1];
222+
assert!(expander.expand_slice(&[&[]], &mut okm).is_err());
223+
}
224+
225+
/// Tests the special case of zero input key material
226+
/// This is important for TLS 1.3 which sometimes requires derivation from zero IKM
227+
#[test]
228+
fn test_hkdf_zero_ikm() {
229+
let hkdf = WCHkdfUsingHmac(WCShaHmac::new(wc_HashType_WC_HASH_TYPE_SHA256));
230+
let salt = hex!("000102030405060708090a0b0c");
231+
let info = hex!("f0f1f2f3f4f5f6f7f8f9");
232+
233+
let expander = hkdf.extract_from_zero_ikm(Some(&salt));
234+
235+
let mut okm1 = vec![0u8; 32];
236+
expander.expand_slice(&[&info], &mut okm1).unwrap();
237+
238+
// Verify that zero IKM produces consistent output
239+
let expander2 = hkdf.extract_from_zero_ikm(Some(&salt));
240+
let mut okm2 = vec![0u8; 32];
241+
expander2.expand_slice(&[&info], &mut okm2).unwrap();
242+
243+
assert_eq!(okm1, okm2);
244+
}
245+
246+
/// Tests that the implementation correctly handles multiple info components
247+
/// Verifies that passing multiple info slices produces the same result as their concatenation
248+
#[test]
249+
fn test_hkdf_multiple_info_components() {
250+
let hkdf = WCHkdfUsingHmac(WCShaHmac::new(wc_HashType_WC_HASH_TYPE_SHA256));
251+
let salt = hex!("000102030405060708090a0b0c");
252+
let info1 = hex!("f0f1f2f3");
253+
let info2 = hex!("f4f5f6f7");
254+
let info3 = hex!("f8f9");
255+
256+
let expander = hkdf.extract_from_zero_ikm(Some(&salt));
257+
258+
// Test with multiple info components
259+
let mut okm1 = vec![0u8; 32];
260+
expander.expand_slice(&[&info1, &info2, &info3], &mut okm1).unwrap();
261+
262+
// Test with concatenated info
263+
let mut info_concat = Vec::new();
264+
info_concat.extend_from_slice(&info1);
265+
info_concat.extend_from_slice(&info2);
266+
info_concat.extend_from_slice(&info3);
267+
268+
let mut okm2 = vec![0u8; 32];
269+
expander.expand_slice(&[&info_concat], &mut okm2).unwrap();
270+
271+
// Results should be identical
272+
assert_eq!(okm1, okm2);
273+
}
274+
}

0 commit comments

Comments
 (0)