/rust/git/checkouts/nss-rs-71e20fe79ef91440/9b94ca3/src/hp.rs
Line | Count | Source |
1 | | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
2 | | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
3 | | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
4 | | // option. This file may not be copied, modified, or distributed |
5 | | // except according to those terms. |
6 | | |
7 | | use std::{ |
8 | | convert::TryFrom as _, |
9 | | fmt::{self, Debug}, |
10 | | os::raw::{c_char, c_int, c_uint}, |
11 | | ptr::{null, null_mut}, |
12 | | }; |
13 | | |
14 | | use pkcs11_bindings::{CKA_ENCRYPT, CKM_AES_ECB, CKM_CHACHA20}; |
15 | | |
16 | | use crate::{ |
17 | | SECItemBorrowed, |
18 | | constants::{ |
19 | | Cipher, TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256, |
20 | | Version, |
21 | | }, |
22 | | err::{Error, Res, secstatus_to_res}, |
23 | | p11::{ |
24 | | CK_ATTRIBUTE_TYPE, CK_CHACHA20_PARAMS, CK_MECHANISM_TYPE, Context, PK11_CipherOp, |
25 | | PK11_CreateContextBySymKey, PK11_Encrypt, PK11_GetBlockSize, PK11SymKey, SymKey, |
26 | | }, |
27 | | }; |
28 | | |
29 | | experimental_api!(SSL_HkdfExpandLabelWithMech( |
30 | | version: Version, |
31 | | cipher: Cipher, |
32 | | prk: *mut PK11SymKey, |
33 | | handshake_hash: *const u8, |
34 | | handshake_hash_len: c_uint, |
35 | | label: *const c_char, |
36 | | label_len: c_uint, |
37 | | mech: CK_MECHANISM_TYPE, |
38 | | key_size: c_uint, |
39 | | secret: *mut *mut PK11SymKey, |
40 | | )); |
41 | | |
42 | | /// Creates an AES-ECB `PK11Context` from a `SymKey`. |
43 | 10.6k | fn make_aes_ctx(key: &SymKey) -> Res<Context> { |
44 | 10.6k | Context::from_ptr(unsafe { |
45 | 10.6k | PK11_CreateContextBySymKey( |
46 | 10.6k | CK_MECHANISM_TYPE::from(CKM_AES_ECB), |
47 | 10.6k | CK_ATTRIBUTE_TYPE::from(CKA_ENCRYPT), |
48 | 10.6k | **key, |
49 | 10.6k | SECItemBorrowed::make_empty().as_ref(), |
50 | | ) |
51 | | }) |
52 | 10.6k | .map_err(|_| Error::CipherInit) |
53 | 10.6k | } |
54 | | |
55 | | pub enum Key { |
56 | | /// AES-ECB header-protection context. `PK11_CloneContext` is not supported for |
57 | | /// AES-ECB, so the `SymKey` is stored alongside `ctx` to enable duplication via |
58 | | /// `try_clone`. |
59 | | #[non_exhaustive] |
60 | | Aes { ctx: Context, key: SymKey }, |
61 | | /// The `ChaCha20` mask invokes `PK11_Encrypt` on each call because the counter |
62 | | /// and nonce change per invocation. |
63 | | #[non_exhaustive] |
64 | | Chacha(SymKey), |
65 | | } |
66 | | |
67 | | impl Debug for Key { |
68 | 0 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
69 | 0 | write!(f, "hp::Key") |
70 | 0 | } |
71 | | } |
72 | | |
73 | | impl Key { |
74 | | pub const SAMPLE_SIZE: usize = 16; |
75 | | |
76 | | /// QUIC-specific API for extracting a header-protection key. |
77 | | /// |
78 | | /// # Errors |
79 | | /// |
80 | | /// Errors if HKDF fails or if the label is too long to fit in a `c_uint`. |
81 | | /// |
82 | | /// # Panics |
83 | | /// |
84 | | /// When `cipher` is not known to this code. |
85 | 10.6k | pub fn extract(version: Version, cipher: Cipher, prk: &SymKey, label: &str) -> Res<Self> { |
86 | 10.6k | let l = label.as_bytes(); |
87 | 10.6k | let mut secret: *mut PK11SymKey = null_mut(); |
88 | | |
89 | 10.6k | let (mech, key_size) = match cipher { |
90 | 10.6k | TLS_AES_128_GCM_SHA256 => (CK_MECHANISM_TYPE::from(CKM_AES_ECB), 16), |
91 | 0 | TLS_AES_256_GCM_SHA384 => (CK_MECHANISM_TYPE::from(CKM_AES_ECB), 32), |
92 | 0 | TLS_CHACHA20_POLY1305_SHA256 => (CK_MECHANISM_TYPE::from(CKM_CHACHA20), 32), |
93 | 0 | _ => unreachable!(), |
94 | | }; |
95 | | |
96 | | // Note that this doesn't allow for passing null() for the handshake hash. |
97 | | // A zero-length slice produces an identical result. |
98 | | unsafe { |
99 | 10.6k | SSL_HkdfExpandLabelWithMech( |
100 | 10.6k | version, |
101 | 10.6k | cipher, |
102 | 10.6k | **prk, |
103 | 10.6k | null(), |
104 | | 0, |
105 | 10.6k | l.as_ptr().cast(), |
106 | 10.6k | c_uint::try_from(l.len())?, |
107 | 10.6k | mech, |
108 | 10.6k | key_size, |
109 | 10.6k | &raw mut secret, |
110 | | ) |
111 | 0 | }?; |
112 | 10.6k | let key = SymKey::from_ptr(secret).or(Err(Error::Hkdf))?; |
113 | | |
114 | 10.6k | let res = match cipher { |
115 | | TLS_AES_128_GCM_SHA256 | TLS_AES_256_GCM_SHA384 => { |
116 | 10.6k | let ctx = make_aes_ctx(&key)?; |
117 | 10.6k | Self::Aes { ctx, key } |
118 | | } |
119 | 0 | TLS_CHACHA20_POLY1305_SHA256 => Self::Chacha(key), |
120 | 0 | _ => unreachable!(), |
121 | | }; |
122 | | |
123 | 10.6k | debug_assert_eq!( |
124 | 10.6k | res.block_size(), |
125 | 10.6k | usize::try_from(unsafe { PK11_GetBlockSize(mech, null_mut()) })? |
126 | | ); |
127 | 10.6k | Ok(res) |
128 | 10.6k | } |
129 | | |
130 | 10.6k | const fn block_size(&self) -> usize { |
131 | 10.6k | match self { |
132 | 10.6k | Self::Aes { .. } => 16, |
133 | 0 | Self::Chacha(_) => 64, |
134 | | } |
135 | 10.6k | } |
136 | | |
137 | | /// Duplicate this key, creating a new independent instance. |
138 | | /// |
139 | | /// # Errors |
140 | | /// |
141 | | /// Errors if NSS context creation fails for AES keys. |
142 | 0 | pub fn try_clone(&self) -> Res<Self> { |
143 | 0 | match self { |
144 | 0 | Self::Aes { key, .. } => { |
145 | 0 | let key = key.clone(); |
146 | 0 | let ctx = make_aes_ctx(&key)?; |
147 | 0 | Ok(Self::Aes { ctx, key }) |
148 | | } |
149 | 0 | Self::Chacha(k) => Ok(Self::Chacha(k.clone())), |
150 | | } |
151 | 0 | } |
152 | | |
153 | | /// Generate a header protection mask for QUIC. |
154 | | /// |
155 | | /// # Errors |
156 | | /// |
157 | | /// An error is returned if the NSS functions fail. |
158 | | /// |
159 | | /// # Panics |
160 | | /// |
161 | | /// In debug builds, if NSS returns an unexpected output length. |
162 | 4.70k | pub fn mask(&self, sample: &[u8; Self::SAMPLE_SIZE]) -> Res<[u8; Self::SAMPLE_SIZE]> { |
163 | 4.70k | let mut output = [0; Self::SAMPLE_SIZE]; |
164 | | |
165 | 4.70k | match self { |
166 | 4.70k | Self::Aes { ctx, .. } => { |
167 | 4.70k | let mut output_len: c_int = 0; |
168 | | // SAFETY: `Deref` on `Context` copies the raw `*mut PK11Context` pointer |
169 | | // value; no Rust reference to the pointee is created. `Key` contains raw |
170 | | // pointers (`!Sync`), so concurrent invocations are impossible, and |
171 | | // AES-ECB full-block operations retain no inter-call state in the context. |
172 | 4.70k | secstatus_to_res(unsafe { |
173 | 4.70k | PK11_CipherOp( |
174 | 4.70k | **ctx, |
175 | 4.70k | output.as_mut_ptr(), |
176 | 4.70k | &raw mut output_len, |
177 | 4.70k | c_int::try_from(output.len())?, |
178 | 4.70k | sample.as_ptr().cast(), |
179 | 4.70k | c_int::try_from(Self::SAMPLE_SIZE)?, |
180 | | ) |
181 | 0 | })?; |
182 | 4.70k | debug_assert_eq!(usize::try_from(output_len)?, output.len()); |
183 | 4.70k | Ok(output) |
184 | | } |
185 | | |
186 | 0 | Self::Chacha(key) => { |
187 | 0 | let params: CK_CHACHA20_PARAMS = CK_CHACHA20_PARAMS { |
188 | 0 | pBlockCounter: sample.as_ptr().cast_mut(), |
189 | 0 | blockCounterBits: 32, |
190 | 0 | pNonce: sample[4..].as_ptr().cast_mut(), |
191 | 0 | ulNonceBits: 96, |
192 | 0 | }; |
193 | 0 | let mut output_len: c_uint = 0; |
194 | 0 | let mut param_item = SECItemBorrowed::wrap_struct(¶ms)?; |
195 | 0 | secstatus_to_res(unsafe { |
196 | 0 | PK11_Encrypt( |
197 | 0 | **key, |
198 | 0 | CK_MECHANISM_TYPE::from(CKM_CHACHA20), |
199 | 0 | std::ptr::from_mut(param_item.as_mut()), |
200 | 0 | output[..].as_mut_ptr(), |
201 | 0 | &raw mut output_len, |
202 | 0 | c_uint::try_from(output.len())?, |
203 | 0 | [0; Self::SAMPLE_SIZE].as_ptr(), |
204 | 0 | c_uint::try_from(Self::SAMPLE_SIZE)?, |
205 | | ) |
206 | 0 | })?; |
207 | 0 | debug_assert_eq!(usize::try_from(output_len)?, output.len()); |
208 | 0 | Ok(output) |
209 | | } |
210 | | } |
211 | 4.70k | } |
212 | | } |
213 | | |
214 | | #[cfg(test)] |
215 | | #[cfg_attr(coverage_nightly, coverage(off))] |
216 | | mod tests { |
217 | | use crate::{ |
218 | | constants::{TLS_AES_128_GCM_SHA256, TLS_VERSION_1_3}, |
219 | | hkdf, |
220 | | hp::Key, |
221 | | }; |
222 | | |
223 | | #[test] |
224 | | fn debug_format() { |
225 | | test_fixture::fixture_init(); |
226 | | let prk = hkdf::import_key(TLS_VERSION_1_3, &[0; 32]).unwrap(); |
227 | | let key = Key::extract(TLS_VERSION_1_3, TLS_AES_128_GCM_SHA256, &prk, "test").unwrap(); |
228 | | assert_eq!(format!("{key:?}"), "hp::Key"); |
229 | | } |
230 | | } |