1use super::aead_ctx::AeadCtx;
5use super::{
6 Algorithm, Nonce, Tag, AES_128_GCM, AES_128_GCM_SIV, AES_192_GCM, AES_256_GCM, AES_256_GCM_SIV,
7 CHACHA20_POLY1305, MAX_KEY_LEN, MAX_TAG_LEN, NONCE_LEN,
8};
9use crate::aws_lc::{
10 EVP_AEAD_CTX_open, EVP_AEAD_CTX_open_gather, EVP_AEAD_CTX_seal, EVP_AEAD_CTX_seal_scatter,
11};
12use crate::error::Unspecified;
13use crate::fips::indicator_check;
14use crate::hkdf;
15use crate::iv::FixedLength;
16use core::fmt::Debug;
17use core::mem::MaybeUninit;
18use core::ops::RangeFrom;
19use core::ptr::null;
20
21const MAX_NONCE_LEN: usize = NONCE_LEN;
23
24const MAX_TAG_NONCE_BUFFER_LEN: usize = MAX_TAG_LEN + MAX_NONCE_LEN;
26
27pub struct UnboundKey {
29 ctx: AeadCtx,
30 algorithm: &'static Algorithm,
31}
32
33#[allow(clippy::missing_fields_in_debug)]
34impl Debug for UnboundKey {
35 fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
36 f.debug_struct("UnboundKey")
37 .field("algorithm", &self.algorithm)
38 .finish()
39 }
40}
41
42impl UnboundKey {
43 pub fn new(algorithm: &'static Algorithm, key_bytes: &[u8]) -> Result<Self, Unspecified> {
47 Ok(Self {
48 ctx: (algorithm.init)(key_bytes, algorithm.tag_len())?,
49 algorithm,
50 })
51 }
52
53 #[inline]
54 pub(crate) fn open_within<'in_out>(
55 &self,
56 nonce: Nonce,
57 aad: &[u8],
58 in_out: &'in_out mut [u8],
59 ciphertext_and_tag: RangeFrom<usize>,
60 ) -> Result<&'in_out mut [u8], Unspecified> {
61 let in_prefix_len = ciphertext_and_tag.start;
62 let ciphertext_and_tag_len = in_out.len().checked_sub(in_prefix_len).ok_or(Unspecified)?;
63 let ciphertext_len = ciphertext_and_tag_len
64 .checked_sub(self.algorithm().tag_len())
65 .ok_or(Unspecified)?;
66 self.check_per_nonce_max_bytes(ciphertext_len)?;
67
68 match self.ctx {
69 AeadCtx::AES_128_GCM_RANDNONCE(_) | AeadCtx::AES_256_GCM_RANDNONCE(_) => {
70 self.open_combined_randnonce(nonce, aad, &mut in_out[in_prefix_len..])
71 }
72 _ => self.open_combined(nonce, aad.as_ref(), &mut in_out[in_prefix_len..]),
73 }?;
74
75 in_out.copy_within(in_prefix_len..in_prefix_len + ciphertext_len, 0);
77
78 Ok(&mut in_out[..ciphertext_len])
80 }
81
82 #[inline]
83 pub(crate) fn open_separate_gather(
84 &self,
85 nonce: &Nonce,
86 aad: &[u8],
87 in_ciphertext: &[u8],
88 in_tag: &[u8],
89 out_plaintext: &mut [u8],
90 ) -> Result<(), Unspecified> {
91 self.open_separate_gather_impl(
92 nonce,
93 aad,
94 in_ciphertext.as_ptr(),
95 in_ciphertext.len(),
96 in_tag,
97 out_plaintext.as_mut_ptr(),
98 out_plaintext.len(),
99 )
100 }
101
102 #[inline]
103 pub(crate) fn open_in_place_separate_tag(
104 &self,
105 nonce: &Nonce,
106 aad: &[u8],
107 in_tag: &[u8],
108 in_out: &mut [u8],
109 ) -> Result<(), Unspecified> {
110 let ptr = in_out.as_mut_ptr();
111 let len = in_out.len();
112 self.open_separate_gather_impl(nonce, aad, ptr.cast_const(), len, in_tag, ptr, len)
113 }
114
115 #[inline]
126 #[allow(clippy::too_many_arguments)]
127 fn open_separate_gather_impl(
128 &self,
129 nonce: &Nonce,
130 aad: &[u8],
131 in_ciphertext: *const u8,
132 in_ciphertext_len: usize,
133 in_tag: &[u8],
134 out_plaintext: *mut u8,
135 out_plaintext_len: usize,
136 ) -> Result<(), Unspecified> {
137 self.check_per_nonce_max_bytes(in_ciphertext_len)?;
138
139 if in_ciphertext_len != out_plaintext_len {
141 return Err(Unspecified);
142 }
143
144 unsafe {
145 let aead_ctx = self.ctx.as_ref();
146 let nonce = nonce.as_ref();
147
148 if 1 != EVP_AEAD_CTX_open_gather(
149 aead_ctx.as_const_ptr(),
150 out_plaintext,
151 nonce.as_ptr(),
152 nonce.len(),
153 in_ciphertext,
154 in_ciphertext_len,
155 in_tag.as_ptr(),
156 in_tag.len(),
157 aad.as_ptr(),
158 aad.len(),
159 ) {
160 return Err(Unspecified);
161 }
162 Ok(())
163 }
164 }
165
166 #[inline]
167 pub(crate) fn seal_in_place_append_tag<'a, InOut>(
168 &self,
169 nonce: Option<Nonce>,
170 aad: &[u8],
171 in_out: &'a mut InOut,
172 ) -> Result<Nonce, Unspecified>
173 where
174 InOut: AsMut<[u8]> + for<'in_out> Extend<&'in_out u8>,
175 {
176 self.check_per_nonce_max_bytes(in_out.as_mut().len())?;
177 match nonce {
178 Some(nonce) => self.seal_combined(nonce, aad, in_out),
179 None => self.seal_combined_randnonce(aad, in_out),
180 }
181 }
182
183 #[inline]
184 pub(crate) fn seal_in_place_separate_tag(
185 &self,
186 nonce: Option<Nonce>,
187 aad: &[u8],
188 in_out: &mut [u8],
189 ) -> Result<(Nonce, Tag), Unspecified> {
190 self.check_per_nonce_max_bytes(in_out.len())?;
191 match nonce {
192 Some(nonce) => self.seal_separate(nonce, aad, in_out),
193 None => self.seal_separate_randnonce(aad, in_out),
194 }
195 }
196
197 #[inline]
198 #[allow(clippy::needless_pass_by_value)]
199 pub(crate) fn seal_in_place_separate_scatter(
200 &self,
201 nonce: Nonce,
202 aad: &[u8],
203 in_out: &mut [u8],
204 extra_in: &[u8],
205 extra_out_and_tag: &mut [u8],
206 ) -> Result<(), Unspecified> {
207 self.check_per_nonce_max_bytes(in_out.len())?;
208 {
210 let actual = extra_in.len() + self.algorithm().tag_len();
211 let expected = extra_out_and_tag.len();
212
213 if actual != expected {
214 return Err(Unspecified);
215 }
216 }
217
218 let nonce = nonce.as_ref();
219 let mut out_tag_len = extra_out_and_tag.len();
220
221 if 1 != unsafe {
222 EVP_AEAD_CTX_seal_scatter(
223 self.ctx.as_ref().as_const_ptr(),
224 in_out.as_mut_ptr(),
225 extra_out_and_tag.as_mut_ptr(),
226 &mut out_tag_len,
227 extra_out_and_tag.len(),
228 nonce.as_ptr(),
229 nonce.len(),
230 in_out.as_ptr(),
231 in_out.len(),
232 extra_in.as_ptr(),
233 extra_in.len(),
234 aad.as_ptr(),
235 aad.len(),
236 )
237 } {
238 return Err(Unspecified);
239 }
240 Ok(())
241 }
242
243 #[inline]
245 #[must_use]
246 pub fn algorithm(&self) -> &'static Algorithm {
247 self.algorithm
248 }
249
250 #[inline]
251 pub(crate) fn check_per_nonce_max_bytes(&self, in_out_len: usize) -> Result<(), Unspecified> {
252 if in_out_len as u64 > self.algorithm().max_input_len {
253 return Err(Unspecified);
254 }
255 Ok(())
256 }
257
258 #[inline]
259 #[allow(clippy::needless_pass_by_value)]
260 fn open_combined(
261 &self,
262 nonce: Nonce,
263 aad: &[u8],
264 in_out: &mut [u8],
265 ) -> Result<(), Unspecified> {
266 let nonce = nonce.as_ref();
267
268 debug_assert_eq!(nonce.len(), self.algorithm().nonce_len());
269
270 let plaintext_len = in_out.len() - self.algorithm().tag_len();
271
272 let mut out_len = MaybeUninit::<usize>::uninit();
273 if 1 != indicator_check!(unsafe {
274 EVP_AEAD_CTX_open(
275 self.ctx.as_ref().as_const_ptr(),
276 in_out.as_mut_ptr(),
277 out_len.as_mut_ptr(),
278 plaintext_len,
279 nonce.as_ptr(),
280 nonce.len(),
281 in_out.as_ptr(),
282 plaintext_len + self.algorithm().tag_len(),
283 aad.as_ptr(),
284 aad.len(),
285 )
286 }) {
287 return Err(Unspecified);
288 }
289
290 Ok(())
291 }
292
293 #[inline]
294 #[allow(clippy::needless_pass_by_value)]
295 fn open_combined_randnonce(
296 &self,
297 nonce: Nonce,
298 aad: &[u8],
299 in_out: &mut [u8],
300 ) -> Result<(), Unspecified> {
301 let nonce = nonce.as_ref();
302
303 let alg_nonce_len = self.algorithm().nonce_len();
304 let alg_tag_len = self.algorithm().tag_len();
305
306 debug_assert_eq!(nonce.len(), alg_nonce_len);
307 debug_assert!(alg_tag_len + alg_nonce_len <= MAX_TAG_NONCE_BUFFER_LEN);
308
309 let plaintext_len = in_out.len() - alg_tag_len;
310
311 let mut tag_buffer = [0u8; MAX_TAG_NONCE_BUFFER_LEN];
312
313 tag_buffer[..alg_tag_len]
314 .copy_from_slice(&in_out[plaintext_len..plaintext_len + alg_tag_len]);
315 tag_buffer[alg_tag_len..alg_tag_len + alg_nonce_len].copy_from_slice(nonce);
316
317 let tag_slice = &tag_buffer[0..alg_tag_len + alg_nonce_len];
318
319 if 1 != indicator_check!(unsafe {
320 EVP_AEAD_CTX_open_gather(
321 self.ctx.as_ref().as_const_ptr(),
322 in_out.as_mut_ptr(),
323 null(),
324 0,
325 in_out.as_ptr(),
326 plaintext_len,
327 tag_slice.as_ptr(),
328 tag_slice.len(),
329 aad.as_ptr(),
330 aad.len(),
331 )
332 }) {
333 return Err(Unspecified);
334 }
335
336 Ok(())
337 }
338
339 #[inline]
340 fn seal_combined<InOut>(
341 &self,
342 nonce: Nonce,
343 aad: &[u8],
344 in_out: &mut InOut,
345 ) -> Result<Nonce, Unspecified>
346 where
347 InOut: AsMut<[u8]> + for<'in_out> Extend<&'in_out u8>,
348 {
349 let plaintext_len = in_out.as_mut().len();
350
351 let alg_tag_len = self.algorithm().tag_len();
352
353 debug_assert!(alg_tag_len <= MAX_TAG_LEN);
354
355 let tag_buffer = [0u8; MAX_TAG_LEN];
356
357 in_out.extend(tag_buffer[..alg_tag_len].iter());
358
359 let mut out_len = MaybeUninit::<usize>::uninit();
360 let mut_in_out = in_out.as_mut();
361
362 {
363 let nonce = nonce.as_ref();
364
365 debug_assert_eq!(nonce.len(), self.algorithm().nonce_len());
366
367 if 1 != indicator_check!(unsafe {
368 EVP_AEAD_CTX_seal(
369 self.ctx.as_ref().as_const_ptr(),
370 mut_in_out.as_mut_ptr(),
371 out_len.as_mut_ptr(),
372 plaintext_len + alg_tag_len,
373 nonce.as_ptr(),
374 nonce.len(),
375 mut_in_out.as_ptr(),
376 plaintext_len,
377 aad.as_ptr(),
378 aad.len(),
379 )
380 }) {
381 return Err(Unspecified);
382 }
383 }
384
385 Ok(nonce)
386 }
387
388 #[inline]
389 fn seal_combined_randnonce<InOut>(
390 &self,
391 aad: &[u8],
392 in_out: &mut InOut,
393 ) -> Result<Nonce, Unspecified>
394 where
395 InOut: AsMut<[u8]> + for<'in_out> Extend<&'in_out u8>,
396 {
397 let mut tag_buffer = [0u8; MAX_TAG_NONCE_BUFFER_LEN];
398
399 let mut out_tag_len = MaybeUninit::<usize>::uninit();
400
401 {
402 let plaintext_len = in_out.as_mut().len();
403 let in_out = in_out.as_mut();
404
405 if 1 != indicator_check!(unsafe {
406 EVP_AEAD_CTX_seal_scatter(
407 self.ctx.as_ref().as_const_ptr(),
408 in_out.as_mut_ptr(),
409 tag_buffer.as_mut_ptr(),
410 out_tag_len.as_mut_ptr(),
411 tag_buffer.len(),
412 null(),
413 0,
414 in_out.as_ptr(),
415 plaintext_len,
416 null(),
417 0,
418 aad.as_ptr(),
419 aad.len(),
420 )
421 }) {
422 return Err(Unspecified);
423 }
424 }
425
426 let tag_len = self.algorithm().tag_len();
427 let nonce_len = self.algorithm().nonce_len();
428
429 let nonce = Nonce(FixedLength::<NONCE_LEN>::try_from(
430 &tag_buffer[tag_len..tag_len + nonce_len],
431 )?);
432
433 in_out.extend(&tag_buffer[..tag_len]);
434
435 Ok(nonce)
436 }
437
438 #[inline]
439 fn seal_separate(
440 &self,
441 nonce: Nonce,
442 aad: &[u8],
443 in_out: &mut [u8],
444 ) -> Result<(Nonce, Tag), Unspecified> {
445 let mut tag = [0u8; MAX_TAG_LEN];
446 let mut out_tag_len = MaybeUninit::<usize>::uninit();
447 {
448 let nonce = nonce.as_ref();
449
450 debug_assert_eq!(nonce.len(), self.algorithm().nonce_len());
451
452 if 1 != indicator_check!(unsafe {
453 EVP_AEAD_CTX_seal_scatter(
454 self.ctx.as_ref().as_const_ptr(),
455 in_out.as_mut_ptr(),
456 tag.as_mut_ptr(),
457 out_tag_len.as_mut_ptr(),
458 tag.len(),
459 nonce.as_ptr(),
460 nonce.len(),
461 in_out.as_ptr(),
462 in_out.len(),
463 null(),
464 0usize,
465 aad.as_ptr(),
466 aad.len(),
467 )
468 }) {
469 return Err(Unspecified);
470 }
471 }
472 Ok((nonce, Tag(tag, unsafe { out_tag_len.assume_init() })))
473 }
474
475 #[inline]
476 fn seal_separate_randnonce(
477 &self,
478 aad: &[u8],
479 in_out: &mut [u8],
480 ) -> Result<(Nonce, Tag), Unspecified> {
481 let mut tag_buffer = [0u8; MAX_TAG_NONCE_BUFFER_LEN];
482
483 debug_assert!(
484 self.algorithm().tag_len() + self.algorithm().nonce_len() <= tag_buffer.len()
485 );
486
487 let mut out_tag_len = MaybeUninit::<usize>::uninit();
488
489 if 1 != indicator_check!(unsafe {
490 EVP_AEAD_CTX_seal_scatter(
491 self.ctx.as_ref().as_const_ptr(),
492 in_out.as_mut_ptr(),
493 tag_buffer.as_mut_ptr(),
494 out_tag_len.as_mut_ptr(),
495 tag_buffer.len(),
496 null(),
497 0,
498 in_out.as_ptr(),
499 in_out.len(),
500 null(),
501 0usize,
502 aad.as_ptr(),
503 aad.len(),
504 )
505 }) {
506 return Err(Unspecified);
507 }
508
509 let tag_len = self.algorithm().tag_len();
510 let nonce_len = self.algorithm().nonce_len();
511
512 let nonce = Nonce(FixedLength::<NONCE_LEN>::try_from(
513 &tag_buffer[tag_len..tag_len + nonce_len],
514 )?);
515
516 let mut tag = [0u8; MAX_TAG_LEN];
517 tag.copy_from_slice(&tag_buffer[..tag_len]);
518
519 Ok((nonce, Tag(tag, tag_len)))
520 }
521}
522
523impl From<AeadCtx> for UnboundKey {
524 fn from(value: AeadCtx) -> Self {
525 let algorithm = match value {
526 AeadCtx::AES_128_GCM(_)
527 | AeadCtx::AES_128_GCM_TLS12(_)
528 | AeadCtx::AES_128_GCM_TLS13(_)
529 | AeadCtx::AES_128_GCM_RANDNONCE(_) => &AES_128_GCM,
530 AeadCtx::AES_192_GCM(_) => &AES_192_GCM,
531 AeadCtx::AES_128_GCM_SIV(_) => &AES_128_GCM_SIV,
532 AeadCtx::AES_256_GCM(_)
533 | AeadCtx::AES_256_GCM_RANDNONCE(_)
534 | AeadCtx::AES_256_GCM_TLS12(_)
535 | AeadCtx::AES_256_GCM_TLS13(_) => &AES_256_GCM,
536 AeadCtx::AES_256_GCM_SIV(_) => &AES_256_GCM_SIV,
537 AeadCtx::CHACHA20_POLY1305(_) => &CHACHA20_POLY1305,
538 };
539 Self {
540 ctx: value,
541 algorithm,
542 }
543 }
544}
545
546impl From<hkdf::Okm<'_, &'static Algorithm>> for UnboundKey {
547 fn from(okm: hkdf::Okm<&'static Algorithm>) -> Self {
548 let mut key_bytes = [0; MAX_KEY_LEN];
549 let key_bytes = &mut key_bytes[..okm.len().key_len];
550 let algorithm = *okm.len();
551 okm.fill(key_bytes).unwrap();
552 Self::new(algorithm, key_bytes).unwrap()
553 }
554}