/rust/git/checkouts/nss-rs-71e20fe79ef91440/9b94ca3/src/aead/recprot.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 | | fmt, |
9 | | os::raw::{c_char, c_int, c_uint}, |
10 | | ptr::{null, null_mut}, |
11 | | }; |
12 | | |
13 | | use super::{COUNTER_LEN, NONCE_LEN, TAG_LEN, c_int_len, xor_nonce}; |
14 | | use crate::{ |
15 | | Cipher, Error, Res, SECItemBorrowed, SymKey, Version, |
16 | | constants::{TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256}, |
17 | | err::{sec::SEC_ERROR_BAD_DATA, secstatus_to_res}, |
18 | | hp::SSL_HkdfExpandLabelWithMech, |
19 | | p11::{ |
20 | | CK_ATTRIBUTE_TYPE, CK_GENERATOR_FUNCTION, CK_MECHANISM_TYPE, CKA_DECRYPT, CKA_ENCRYPT, |
21 | | CKA_NSS_MESSAGE, CKG_NO_GENERATE, CKM_AES_GCM, CKM_CHACHA20_POLY1305, CKM_HKDF_DATA, |
22 | | Context, PK11_AEADOp, PK11_CreateContextBySymKey, PK11SymKey, |
23 | | }, |
24 | | }; |
25 | | |
26 | 10.9k | fn cipher_mech_and_key_len(cipher: Cipher) -> Res<(CK_MECHANISM_TYPE, c_uint)> { |
27 | 10.9k | match cipher { |
28 | 10.9k | TLS_AES_128_GCM_SHA256 => Ok((CK_MECHANISM_TYPE::from(CKM_AES_GCM), 16)), |
29 | 0 | TLS_AES_256_GCM_SHA384 => Ok((CK_MECHANISM_TYPE::from(CKM_AES_GCM), 32)), |
30 | 0 | TLS_CHACHA20_POLY1305_SHA256 => Ok((CK_MECHANISM_TYPE::from(CKM_CHACHA20_POLY1305), 32)), |
31 | 0 | _ => Err(Error::UnsupportedCipher), |
32 | | } |
33 | 10.9k | } |
34 | | |
35 | 21.8k | fn expand_label( |
36 | 21.8k | version: Version, |
37 | 21.8k | cipher: Cipher, |
38 | 21.8k | secret: &SymKey, |
39 | 21.8k | label: &str, |
40 | 21.8k | mech: CK_MECHANISM_TYPE, |
41 | 21.8k | key_len: c_uint, |
42 | 21.8k | ) -> Res<SymKey> { |
43 | 21.8k | let mut ptr: *mut PK11SymKey = null_mut(); |
44 | | unsafe { |
45 | 21.8k | SSL_HkdfExpandLabelWithMech( |
46 | 21.8k | version, |
47 | 21.8k | cipher, |
48 | 21.8k | **secret, |
49 | 21.8k | null(), |
50 | | 0, |
51 | 21.8k | label.as_ptr().cast::<c_char>(), |
52 | 21.8k | c_uint::try_from(label.len())?, |
53 | 21.8k | mech, |
54 | 21.8k | key_len, |
55 | 21.8k | &raw mut ptr, |
56 | | ) |
57 | 0 | }?; |
58 | 21.8k | SymKey::from_ptr(ptr) |
59 | 21.8k | } |
60 | | |
61 | 21.8k | fn make_ctx( |
62 | 21.8k | mech: CK_MECHANISM_TYPE, |
63 | 21.8k | op: CK_ATTRIBUTE_TYPE, |
64 | 21.8k | key: &SymKey, |
65 | 21.8k | nonce_base: &[u8; NONCE_LEN], |
66 | 21.8k | ) -> Res<Context> { |
67 | 21.8k | let ptr = unsafe { |
68 | 21.8k | PK11_CreateContextBySymKey( |
69 | 21.8k | mech, |
70 | 21.8k | op, |
71 | 21.8k | **key, |
72 | 21.8k | SECItemBorrowed::wrap(nonce_base.as_slice())?.as_ref(), |
73 | | ) |
74 | | }; |
75 | 21.8k | Context::from_ptr(ptr) |
76 | 21.8k | } |
77 | | |
78 | | /// Calls `PK11_AEADOp` with the fixed parameters for this module (`CKG_NO_GENERATE`, no |
79 | | /// counter generation) and returns the number of output bytes written. |
80 | | /// |
81 | | /// # Safety |
82 | | /// |
83 | | /// `output`, `tag`, and `input` must be valid for `output_max`, `TAG_LEN`, and `input_len` |
84 | | /// bytes respectively. `output` and `input` may fully overlap (in-place operation); `tag` |
85 | | /// must not overlap with the `output` region. |
86 | | #[expect( |
87 | | clippy::too_many_arguments, |
88 | | reason = "Thin wrapper over a 14-argument C function." |
89 | | )] |
90 | 4.06k | unsafe fn aead_op( |
91 | 4.06k | ctx: &Context, |
92 | 4.06k | nonce_base: &[u8; NONCE_LEN], |
93 | 4.06k | count: u64, |
94 | 4.06k | aad: &[u8], |
95 | 4.06k | output: *mut u8, |
96 | 4.06k | output_max: usize, |
97 | 4.06k | tag: *mut u8, |
98 | 4.06k | input: *const u8, |
99 | 4.06k | input_len: usize, |
100 | 4.06k | ) -> Res<usize> { |
101 | 4.06k | let mut nonce = xor_nonce(nonce_base, count); |
102 | 4.06k | let mut out_len: c_int = 0; |
103 | 4.06k | secstatus_to_res(unsafe { |
104 | 4.06k | PK11_AEADOp( |
105 | 4.06k | **ctx, |
106 | 4.06k | CK_GENERATOR_FUNCTION::from(CKG_NO_GENERATE), |
107 | 4.06k | c_int_len(NONCE_LEN - COUNTER_LEN)?, |
108 | 4.06k | nonce.as_mut_ptr(), |
109 | 4.06k | c_int_len(NONCE_LEN)?, |
110 | 4.06k | aad.as_ptr(), |
111 | 4.06k | c_int_len(aad.len())?, |
112 | 4.06k | output, |
113 | 4.06k | &raw mut out_len, |
114 | 4.06k | c_int_len(output_max)?, |
115 | 4.06k | tag, |
116 | 4.06k | c_int_len(TAG_LEN)?, |
117 | 4.06k | input, |
118 | 4.06k | c_int_len(input_len)?, |
119 | | ) |
120 | 516 | })?; |
121 | 3.54k | Ok(usize::try_from(out_len)?) |
122 | 4.06k | } |
123 | | |
124 | | pub struct RecordProtection { |
125 | | ctx_encrypt: Context, |
126 | | ctx_decrypt: Context, |
127 | | nonce_base: [u8; NONCE_LEN], |
128 | | } |
129 | | |
130 | | impl RecordProtection { |
131 | | /// Create a new AEAD instance. |
132 | | /// |
133 | | /// # Errors |
134 | | /// |
135 | | /// Returns `Error` when the underlying crypto operations fail. |
136 | 10.9k | pub fn new(version: Version, cipher: Cipher, secret: &SymKey, prefix: &str) -> Res<Self> { |
137 | 10.9k | let (mech, key_len) = cipher_mech_and_key_len(cipher)?; |
138 | 10.9k | let key = expand_label( |
139 | 10.9k | version, |
140 | 10.9k | cipher, |
141 | 10.9k | secret, |
142 | 10.9k | &format!("{prefix}key"), |
143 | 10.9k | mech, |
144 | 10.9k | key_len, |
145 | 0 | )?; |
146 | 10.9k | let iv_key = expand_label( |
147 | 10.9k | version, |
148 | 10.9k | cipher, |
149 | 10.9k | secret, |
150 | 10.9k | &format!("{prefix}iv"), |
151 | 10.9k | CK_MECHANISM_TYPE::from(CKM_HKDF_DATA), |
152 | 10.9k | c_uint::try_from(NONCE_LEN)?, |
153 | 0 | )?; |
154 | 10.9k | let nonce_base: [u8; NONCE_LEN] = |
155 | 10.9k | iv_key.key_data()?.try_into().map_err(|_| Error::Internal)?; |
156 | 10.9k | let ctx_encrypt = make_ctx( |
157 | 10.9k | mech, |
158 | 10.9k | CK_ATTRIBUTE_TYPE::from(CKA_NSS_MESSAGE | CKA_ENCRYPT), |
159 | 10.9k | &key, |
160 | 10.9k | &nonce_base, |
161 | 0 | )?; |
162 | 10.9k | let ctx_decrypt = make_ctx( |
163 | 10.9k | mech, |
164 | 10.9k | CK_ATTRIBUTE_TYPE::from(CKA_NSS_MESSAGE | CKA_DECRYPT), |
165 | 10.9k | &key, |
166 | 10.9k | &nonce_base, |
167 | 0 | )?; |
168 | 10.9k | Ok(Self { |
169 | 10.9k | ctx_encrypt, |
170 | 10.9k | ctx_decrypt, |
171 | 10.9k | nonce_base, |
172 | 10.9k | }) |
173 | 10.9k | } |
174 | | |
175 | | /// Get the expansion size (authentication tag length) for this AEAD. |
176 | | #[must_use] |
177 | | #[expect(clippy::missing_const_for_fn, clippy::unused_self)] |
178 | 18.0k | pub fn expansion(&self) -> usize { |
179 | 18.0k | TAG_LEN |
180 | 18.0k | } |
181 | | |
182 | | /// Encrypt plaintext with associated data. |
183 | | /// |
184 | | /// # Errors |
185 | | /// |
186 | | /// Returns `Error` when encryption fails. |
187 | 805 | pub fn encrypt<'a>( |
188 | 805 | &self, |
189 | 805 | count: u64, |
190 | 805 | aad: &[u8], |
191 | 805 | input: &[u8], |
192 | 805 | output: &'a mut [u8], |
193 | 805 | ) -> Res<&'a [u8]> { |
194 | 805 | if output.len() |
195 | 805 | < input |
196 | 805 | .len() |
197 | 805 | .checked_add(TAG_LEN) |
198 | 805 | .ok_or(Error::IntegerOverflow)? |
199 | | { |
200 | 0 | return Err(Error::from(SEC_ERROR_BAD_DATA)); |
201 | 805 | } |
202 | 805 | let out_ptr = output.as_mut_ptr(); |
203 | 805 | let out_len = unsafe { |
204 | 805 | aead_op( |
205 | 805 | &self.ctx_encrypt, |
206 | 805 | &self.nonce_base, |
207 | 805 | count, |
208 | 805 | aad, |
209 | 805 | out_ptr, |
210 | 805 | input.len(), |
211 | 805 | out_ptr.add(input.len()), |
212 | 805 | input.as_ptr(), |
213 | 805 | input.len(), |
214 | | ) |
215 | 0 | }?; |
216 | 805 | if out_len != input.len() { |
217 | 0 | return Err(Error::Internal); |
218 | 805 | } |
219 | 805 | Ok(&output[..out_len + TAG_LEN]) |
220 | 805 | } |
221 | | |
222 | | /// Encrypt plaintext in place with associated data. |
223 | | /// |
224 | | /// # Errors |
225 | | /// |
226 | | /// Returns `Error` when encryption fails. |
227 | 2.25k | pub fn encrypt_in_place(&self, count: u64, aad: &[u8], data: &mut [u8]) -> Res<usize> { |
228 | 2.25k | if data.len() < self.expansion() { |
229 | 0 | return Err(Error::from(SEC_ERROR_BAD_DATA)); |
230 | 2.25k | } |
231 | 2.25k | let pt_len = data.len() - self.expansion(); |
232 | 2.25k | let data_ptr = data.as_mut_ptr(); |
233 | 2.25k | let out_len = unsafe { |
234 | 2.25k | aead_op( |
235 | 2.25k | &self.ctx_encrypt, |
236 | 2.25k | &self.nonce_base, |
237 | 2.25k | count, |
238 | 2.25k | aad, |
239 | 2.25k | data_ptr, |
240 | 2.25k | pt_len, |
241 | 2.25k | data_ptr.add(pt_len), |
242 | 2.25k | data_ptr.cast_const(), |
243 | 2.25k | pt_len, |
244 | | ) |
245 | 0 | }?; |
246 | 2.25k | if out_len != pt_len { |
247 | 0 | return Err(Error::Internal); |
248 | 2.25k | } |
249 | 2.25k | Ok(data.len()) |
250 | 2.25k | } |
251 | | |
252 | | /// Decrypt ciphertext with associated data. |
253 | | /// |
254 | | /// # Errors |
255 | | /// |
256 | | /// Returns `Error` when decryption or authentication fails. |
257 | 301 | pub fn decrypt<'a>( |
258 | 301 | &self, |
259 | 301 | count: u64, |
260 | 301 | aad: &[u8], |
261 | 301 | input: &[u8], |
262 | 301 | output: &'a mut [u8], |
263 | 301 | ) -> Res<&'a [u8]> { |
264 | 301 | let ct_len = input |
265 | 301 | .len() |
266 | 301 | .checked_sub(TAG_LEN) |
267 | 301 | .ok_or_else(|| Error::from(SEC_ERROR_BAD_DATA))?; |
268 | 164 | if output.len() < ct_len { |
269 | 0 | return Err(Error::from(SEC_ERROR_BAD_DATA)); |
270 | 164 | } |
271 | 164 | let mut tag = [0u8; TAG_LEN]; |
272 | 164 | tag.copy_from_slice(&input[ct_len..]); |
273 | 0 | let out_len = unsafe { |
274 | 164 | aead_op( |
275 | 164 | &self.ctx_decrypt, |
276 | 164 | &self.nonce_base, |
277 | 164 | count, |
278 | 164 | aad, |
279 | 164 | output.as_mut_ptr(), |
280 | 164 | output.len(), |
281 | 164 | tag.as_mut_ptr(), |
282 | 164 | input.as_ptr(), |
283 | 164 | ct_len, |
284 | | ) |
285 | 164 | }?; |
286 | 0 | Ok(&output[..out_len]) |
287 | 301 | } |
288 | | |
289 | | /// Decrypt ciphertext in place with associated data. |
290 | | /// |
291 | | /// # Errors |
292 | | /// |
293 | | /// Returns `Error` when decryption or authentication fails. |
294 | 836 | pub fn decrypt_in_place(&self, count: u64, aad: &[u8], data: &mut [u8]) -> Res<usize> { |
295 | 836 | let ct_len = data |
296 | 836 | .len() |
297 | 836 | .checked_sub(TAG_LEN) |
298 | 836 | .ok_or_else(|| Error::from(SEC_ERROR_BAD_DATA))?; |
299 | 836 | let mut tag = [0u8; TAG_LEN]; |
300 | 836 | tag.copy_from_slice(&data[ct_len..]); |
301 | 836 | let data_ptr = data.as_mut_ptr(); |
302 | 484 | let out_len = unsafe { |
303 | 836 | aead_op( |
304 | 836 | &self.ctx_decrypt, |
305 | 836 | &self.nonce_base, |
306 | 836 | count, |
307 | 836 | aad, |
308 | 836 | data_ptr, |
309 | 836 | data.len(), |
310 | 836 | tag.as_mut_ptr(), |
311 | 836 | data_ptr.cast_const(), |
312 | 836 | ct_len, |
313 | | ) |
314 | 352 | }?; |
315 | 484 | if out_len != ct_len { |
316 | 0 | return Err(Error::Internal); |
317 | 484 | } |
318 | 484 | Ok(ct_len) |
319 | 836 | } |
320 | | } |
321 | | |
322 | | impl fmt::Debug for RecordProtection { |
323 | 0 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
324 | 0 | write!(f, "[AEAD Context]") |
325 | 0 | } |
326 | | } |