1use crate::error::ErrorStack;
7use native_ossl_sys as sys;
8use std::ffi::CStr;
9use std::sync::Arc;
10
11pub struct CipherAlg {
17 ptr: *mut sys::EVP_CIPHER,
18 lib_ctx: Option<Arc<crate::lib_ctx::LibCtx>>,
20}
21
22impl CipherAlg {
23 pub fn fetch(name: &CStr, props: Option<&CStr>) -> Result<Self, ErrorStack> {
29 let props_ptr = props.map_or(std::ptr::null(), CStr::as_ptr);
30 let ptr = unsafe { sys::EVP_CIPHER_fetch(std::ptr::null_mut(), name.as_ptr(), props_ptr) };
31 if ptr.is_null() {
32 return Err(ErrorStack::drain());
33 }
34 Ok(CipherAlg { ptr, lib_ctx: None })
35 }
36
37 pub fn fetch_in(
41 ctx: &Arc<crate::lib_ctx::LibCtx>,
42 name: &CStr,
43 props: Option<&CStr>,
44 ) -> Result<Self, ErrorStack> {
45 let props_ptr = props.map_or(std::ptr::null(), CStr::as_ptr);
46 let ptr = unsafe { sys::EVP_CIPHER_fetch(ctx.as_ptr(), name.as_ptr(), props_ptr) };
47 if ptr.is_null() {
48 return Err(ErrorStack::drain());
49 }
50 Ok(CipherAlg {
51 ptr,
52 lib_ctx: Some(Arc::clone(ctx)),
53 })
54 }
55
56 #[must_use]
58 pub fn key_len(&self) -> usize {
59 usize::try_from(unsafe { sys::EVP_CIPHER_get_key_length(self.ptr) }).unwrap_or(0)
60 }
61
62 #[must_use]
64 pub fn iv_len(&self) -> usize {
65 usize::try_from(unsafe { sys::EVP_CIPHER_get_iv_length(self.ptr) }).unwrap_or(0)
66 }
67
68 #[must_use]
70 pub fn block_size(&self) -> usize {
71 usize::try_from(unsafe { sys::EVP_CIPHER_get_block_size(self.ptr) }).unwrap_or(0)
72 }
73
74 #[must_use]
76 pub fn flags(&self) -> u64 {
77 unsafe { sys::EVP_CIPHER_get_flags(self.ptr) }
78 }
79
80 #[must_use]
82 pub fn is_aead(&self) -> bool {
83 (self.flags() & 0x0020_0000) != 0
85 }
86
87 #[must_use]
89 pub fn as_ptr(&self) -> *const sys::EVP_CIPHER {
90 self.ptr
91 }
92}
93
94impl Clone for CipherAlg {
95 fn clone(&self) -> Self {
96 unsafe { sys::EVP_CIPHER_up_ref(self.ptr) };
97 CipherAlg {
98 ptr: self.ptr,
99 lib_ctx: self.lib_ctx.clone(),
100 }
101 }
102}
103
104impl Drop for CipherAlg {
105 fn drop(&mut self) {
106 unsafe { sys::EVP_CIPHER_free(self.ptr) };
107 }
108}
109
110unsafe impl Send for CipherAlg {}
112unsafe impl Sync for CipherAlg {}
113
114pub struct Encrypt;
118pub struct Decrypt;
120
121mod sealed {
122 pub trait Direction {}
123 impl Direction for super::Encrypt {}
124 impl Direction for super::Decrypt {}
125}
126
127pub struct CipherCtx<Dir> {
136 ptr: *mut sys::EVP_CIPHER_CTX,
137 _dir: std::marker::PhantomData<Dir>,
138}
139
140impl<Dir: sealed::Direction> CipherCtx<Dir> {
141 pub fn update(&mut self, input: &[u8], output: &mut [u8]) -> Result<usize, ErrorStack>
148 where
149 Dir: IsEncrypt,
150 {
151 unsafe { Dir::do_update(self.ptr, input, output) }
153 }
154
155 pub fn update_to_vec(&mut self, input: &[u8]) -> Result<Vec<u8>, ErrorStack>
163 where
164 Dir: IsEncrypt,
165 {
166 let block_size =
167 usize::try_from(unsafe { sys::EVP_CIPHER_CTX_get_block_size(self.ptr) }).unwrap_or(0);
168 let max = input.len() + block_size;
169 let mut out = vec![0u8; max];
170 let n = self.update(input, &mut out)?;
171 out.truncate(n);
172 Ok(out)
173 }
174
175 pub fn finalize(&mut self, output: &mut [u8]) -> Result<usize, ErrorStack>
179 where
180 Dir: IsEncrypt,
181 {
182 unsafe { Dir::do_finalize(self.ptr, output) }
184 }
185
186 pub fn set_params(&mut self, params: &crate::params::Params<'_>) -> Result<(), ErrorStack> {
190 crate::ossl_call!(sys::EVP_CIPHER_CTX_set_params(self.ptr, params.as_ptr()))
191 }
192
193 pub fn get_params(&self, params: &mut crate::params::Params<'_>) -> Result<(), ErrorStack> {
210 crate::ossl_call!(sys::EVP_CIPHER_CTX_get_params(
215 self.ptr,
216 params.as_mut_ptr()
217 ))
218 }
219
220 pub fn aead_tag_len(&self) -> Result<usize, ErrorStack> {
234 let mut params = crate::params::ParamBuilder::new()?
235 .push_size(c"taglen", 0)?
236 .build()?;
237 crate::ossl_call!(sys::EVP_CIPHER_CTX_get_params(
242 self.ptr,
243 params.as_mut_ptr()
244 ))?;
245 params
246 .get_size_t(c"taglen")
247 .map_err(|_| crate::error::ErrorStack::drain())
248 }
249
250 pub fn key_len(&self) -> Result<usize, ErrorStack> {
260 let mut params = crate::params::ParamBuilder::new()?
261 .push_size(c"keylen", 0)?
262 .build()?;
263 crate::ossl_call!(sys::EVP_CIPHER_CTX_get_params(
265 self.ptr,
266 params.as_mut_ptr()
267 ))?;
268 params
269 .get_size_t(c"keylen")
270 .map_err(|_| crate::error::ErrorStack::drain())
271 }
272
273 pub fn iv_len(&self) -> Result<usize, ErrorStack> {
283 let mut params = crate::params::ParamBuilder::new()?
284 .push_size(c"ivlen", 0)?
285 .build()?;
286 crate::ossl_call!(sys::EVP_CIPHER_CTX_get_params(
288 self.ptr,
289 params.as_mut_ptr()
290 ))?;
291 params
292 .get_size_t(c"ivlen")
293 .map_err(|_| crate::error::ErrorStack::drain())
294 }
295
296 #[must_use]
300 pub fn as_ptr(&self) -> *mut sys::EVP_CIPHER_CTX {
301 self.ptr
302 }
303}
304
305impl<Dir> Drop for CipherCtx<Dir> {
306 fn drop(&mut self) {
307 unsafe { sys::EVP_CIPHER_CTX_free(self.ptr) };
308 }
309}
310
311unsafe impl<Dir: sealed::Direction> Send for CipherCtx<Dir> {}
312
313pub trait IsEncrypt: sealed::Direction {
317 unsafe fn do_update(
327 ctx: *mut sys::EVP_CIPHER_CTX,
328 input: &[u8],
329 output: &mut [u8],
330 ) -> Result<usize, ErrorStack>;
331
332 unsafe fn do_finalize(
342 ctx: *mut sys::EVP_CIPHER_CTX,
343 output: &mut [u8],
344 ) -> Result<usize, ErrorStack>;
345}
346
347impl IsEncrypt for Encrypt {
348 unsafe fn do_update(
349 ctx: *mut sys::EVP_CIPHER_CTX,
350 input: &[u8],
351 output: &mut [u8],
352 ) -> Result<usize, ErrorStack> {
353 let inl = i32::try_from(input.len()).map_err(|_| ErrorStack::drain())?;
354 let mut outl: i32 = 0;
355 crate::ossl_call!(sys::EVP_EncryptUpdate(
356 ctx,
357 output.as_mut_ptr(),
358 std::ptr::addr_of_mut!(outl),
359 input.as_ptr(),
360 inl
361 ))?;
362 Ok(usize::try_from(outl).unwrap_or(0))
363 }
364
365 unsafe fn do_finalize(
366 ctx: *mut sys::EVP_CIPHER_CTX,
367 output: &mut [u8],
368 ) -> Result<usize, ErrorStack> {
369 let mut outl: i32 = 0;
370 crate::ossl_call!(sys::EVP_EncryptFinal_ex(
371 ctx,
372 output.as_mut_ptr(),
373 std::ptr::addr_of_mut!(outl)
374 ))?;
375 Ok(usize::try_from(outl).unwrap_or(0))
376 }
377}
378
379impl IsEncrypt for Decrypt {
380 unsafe fn do_update(
381 ctx: *mut sys::EVP_CIPHER_CTX,
382 input: &[u8],
383 output: &mut [u8],
384 ) -> Result<usize, ErrorStack> {
385 let inl = i32::try_from(input.len()).map_err(|_| ErrorStack::drain())?;
386 let mut outl: i32 = 0;
387 crate::ossl_call!(sys::EVP_DecryptUpdate(
388 ctx,
389 output.as_mut_ptr(),
390 std::ptr::addr_of_mut!(outl),
391 input.as_ptr(),
392 inl
393 ))?;
394 Ok(usize::try_from(outl).unwrap_or(0))
395 }
396
397 unsafe fn do_finalize(
398 ctx: *mut sys::EVP_CIPHER_CTX,
399 output: &mut [u8],
400 ) -> Result<usize, ErrorStack> {
401 let mut outl: i32 = 0;
402 crate::ossl_call!(sys::EVP_DecryptFinal_ex(
403 ctx,
404 output.as_mut_ptr(),
405 std::ptr::addr_of_mut!(outl)
406 ))?;
407 Ok(usize::try_from(outl).unwrap_or(0))
408 }
409}
410
411impl CipherAlg {
412 pub fn encrypt(
419 &self,
420 key: &[u8],
421 iv: &[u8],
422 params: Option<&crate::params::Params<'_>>,
423 ) -> Result<CipherCtx<Encrypt>, ErrorStack> {
424 let ctx_ptr = unsafe { sys::EVP_CIPHER_CTX_new() };
425 if ctx_ptr.is_null() {
426 return Err(ErrorStack::drain());
427 }
428 let params_ptr = params.map_or(crate::params::null_params(), crate::params::Params::as_ptr);
429 crate::ossl_call!(sys::EVP_EncryptInit_ex2(
430 ctx_ptr,
431 self.ptr,
432 key.as_ptr(),
433 iv.as_ptr(),
434 params_ptr
435 ))
436 .map_err(|e| {
437 unsafe { sys::EVP_CIPHER_CTX_free(ctx_ptr) };
438 e
439 })?;
440 Ok(CipherCtx {
441 ptr: ctx_ptr,
442 _dir: std::marker::PhantomData,
443 })
444 }
445
446 pub fn decrypt(
452 &self,
453 key: &[u8],
454 iv: &[u8],
455 params: Option<&crate::params::Params<'_>>,
456 ) -> Result<CipherCtx<Decrypt>, ErrorStack> {
457 let ctx_ptr = unsafe { sys::EVP_CIPHER_CTX_new() };
458 if ctx_ptr.is_null() {
459 return Err(ErrorStack::drain());
460 }
461 let params_ptr = params.map_or(crate::params::null_params(), crate::params::Params::as_ptr);
462 crate::ossl_call!(sys::EVP_DecryptInit_ex2(
463 ctx_ptr,
464 self.ptr,
465 key.as_ptr(),
466 iv.as_ptr(),
467 params_ptr
468 ))
469 .map_err(|e| {
470 unsafe { sys::EVP_CIPHER_CTX_free(ctx_ptr) };
471 e
472 })?;
473 Ok(CipherCtx {
474 ptr: ctx_ptr,
475 _dir: std::marker::PhantomData,
476 })
477 }
478}
479
480pub struct AeadEncryptCtx(CipherCtx<Encrypt>);
484
485impl AeadEncryptCtx {
486 pub fn new(
494 alg: &CipherAlg,
495 key: &[u8],
496 iv: &[u8],
497 params: Option<&crate::params::Params<'_>>,
498 ) -> Result<Self, ErrorStack> {
499 assert!(alg.is_aead(), "CipherAlg is not an AEAD algorithm");
500 Ok(AeadEncryptCtx(alg.encrypt(key, iv, params)?))
501 }
502
503 pub fn set_aad(&mut self, aad: &[u8]) -> Result<(), ErrorStack> {
511 let alen = i32::try_from(aad.len()).expect("AAD too large for EVP_EncryptUpdate");
513 let mut outl: i32 = 0;
514 crate::ossl_call!(sys::EVP_EncryptUpdate(
515 self.0.ptr,
516 std::ptr::null_mut(),
517 std::ptr::addr_of_mut!(outl),
518 aad.as_ptr(),
519 alen
520 ))
521 }
522
523 pub fn update(&mut self, input: &[u8], output: &mut [u8]) -> Result<usize, ErrorStack> {
527 self.0.update(input, output)
528 }
529
530 pub fn finalize(&mut self, output: &mut [u8]) -> Result<usize, ErrorStack> {
534 self.0.finalize(output)
535 }
536
537 pub fn tag(&self, tag: &mut [u8]) -> Result<(), ErrorStack> {
547 let tlen = i32::try_from(tag.len()).expect("tag slice too large");
549 let rc = unsafe {
550 sys::EVP_CIPHER_CTX_ctrl(
551 self.0.ptr,
552 16, tlen,
554 tag.as_mut_ptr().cast(),
555 )
556 };
557 if rc != 1 {
558 return Err(ErrorStack::drain());
559 }
560 Ok(())
561 }
562}
563
564pub struct AeadDecryptCtx(CipherCtx<Decrypt>);
566
567impl AeadDecryptCtx {
568 pub fn new(
576 alg: &CipherAlg,
577 key: &[u8],
578 iv: &[u8],
579 params: Option<&crate::params::Params<'_>>,
580 ) -> Result<Self, ErrorStack> {
581 assert!(alg.is_aead(), "CipherAlg is not an AEAD algorithm");
582 Ok(AeadDecryptCtx(alg.decrypt(key, iv, params)?))
583 }
584
585 pub fn set_aad(&mut self, aad: &[u8]) -> Result<(), ErrorStack> {
593 let alen = i32::try_from(aad.len()).expect("AAD too large for EVP_DecryptUpdate");
594 let mut outl: i32 = 0;
595 crate::ossl_call!(sys::EVP_DecryptUpdate(
596 self.0.ptr,
597 std::ptr::null_mut(),
598 std::ptr::addr_of_mut!(outl),
599 aad.as_ptr(),
600 alen
601 ))
602 }
603
604 pub fn update(&mut self, input: &[u8], output: &mut [u8]) -> Result<usize, ErrorStack> {
608 self.0.update(input, output)
609 }
610
611 pub fn set_tag(&mut self, tag: &[u8]) -> Result<(), ErrorStack> {
619 let tlen = i32::try_from(tag.len()).expect("tag slice too large");
621 let rc = unsafe {
622 sys::EVP_CIPHER_CTX_ctrl(
623 self.0.ptr,
624 17, tlen,
626 tag.as_ptr().cast_mut().cast(),
628 )
629 };
630 if rc != 1 {
631 return Err(ErrorStack::drain());
632 }
633 Ok(())
634 }
635
636 pub fn finalize(&mut self, output: &mut [u8]) -> Result<usize, ErrorStack> {
640 self.0.finalize(output)
641 }
642}
643
644#[cfg(test)]
647mod tests {
648 use super::*;
649
650 #[test]
651 fn fetch_aes_256_gcm_properties() {
652 let alg = CipherAlg::fetch(c"AES-256-GCM", None).unwrap();
653 assert_eq!(alg.key_len(), 32);
654 assert_eq!(alg.iv_len(), 12);
655 assert_eq!(alg.block_size(), 1);
656 assert!(alg.is_aead());
657 }
658
659 #[test]
660 fn fetch_aes_256_cbc_properties() {
661 let alg = CipherAlg::fetch(c"AES-256-CBC", None).unwrap();
662 assert_eq!(alg.key_len(), 32);
663 assert_eq!(alg.iv_len(), 16);
664 assert_eq!(alg.block_size(), 16);
665 assert!(!alg.is_aead());
666 }
667
668 #[test]
669 fn fetch_nonexistent_fails() {
670 assert!(CipherAlg::fetch(c"NONEXISTENT_CIPHER_XYZ", None).is_err());
671 }
672
673 #[test]
674 fn clone_then_drop_both() {
675 let alg = CipherAlg::fetch(c"AES-256-GCM", None).unwrap();
676 let alg2 = alg.clone();
677 drop(alg);
678 drop(alg2);
679 }
680
681 #[test]
683 fn aes_256_cbc_round_trip() {
684 let alg = CipherAlg::fetch(c"AES-256-CBC", None).unwrap();
685 let key = [0x42u8; 32];
686 let iv = [0x24u8; 16];
687 let plaintext = b"Hello, cipher world!";
688
689 let mut enc = alg.encrypt(&key, &iv, None).unwrap();
691 let mut ciphertext = vec![0u8; plaintext.len() + alg.block_size()];
692 let n = enc.update(plaintext, &mut ciphertext).unwrap();
693 let m = enc.finalize(&mut ciphertext[n..]).unwrap();
694 ciphertext.truncate(n + m);
695
696 let mut dec = alg.decrypt(&key, &iv, None).unwrap();
698 let mut recovered = vec![0u8; ciphertext.len() + alg.block_size()];
699 let n2 = dec.update(&ciphertext, &mut recovered).unwrap();
700 let m2 = dec.finalize(&mut recovered[n2..]).unwrap();
701 recovered.truncate(n2 + m2);
702
703 assert_eq!(recovered, plaintext);
704 }
705
706 #[test]
708 fn aes_256_gcm_round_trip_and_tag_failure() {
709 let alg = CipherAlg::fetch(c"AES-256-GCM", None).unwrap();
710 let key = [0x11u8; 32];
711 let iv = [0x22u8; 12];
712 let aad = b"additional data";
713 let plaintext = b"secret message!";
714
715 let mut enc = AeadEncryptCtx::new(&alg, &key, &iv, None).unwrap();
717 enc.set_aad(aad).unwrap();
718 let mut ciphertext = vec![0u8; plaintext.len()];
719 let n = enc.update(plaintext, &mut ciphertext).unwrap();
720 enc.finalize(&mut ciphertext[n..]).unwrap();
721 let mut tag = [0u8; 16];
722 enc.tag(&mut tag).unwrap();
723
724 let mut dec = AeadDecryptCtx::new(&alg, &key, &iv, None).unwrap();
726 dec.set_aad(aad).unwrap();
727 let mut recovered = vec![0u8; ciphertext.len()];
728 let n2 = dec.update(&ciphertext, &mut recovered).unwrap();
729 dec.set_tag(&tag).unwrap();
730 dec.finalize(&mut recovered[n2..]).unwrap();
731 assert_eq!(&recovered[..n2], plaintext);
732
733 let mut bad_tag = tag;
735 bad_tag[0] ^= 0xff;
736 let mut dec2 = AeadDecryptCtx::new(&alg, &key, &iv, None).unwrap();
737 dec2.set_aad(aad).unwrap();
738 let mut dummy = vec![0u8; ciphertext.len()];
739 dec2.update(&ciphertext, &mut dummy).unwrap();
740 dec2.set_tag(&bad_tag).unwrap();
741 assert!(dec2.finalize(&mut dummy).is_err());
742 }
743
744 #[test]
746 fn cipher_ctx_aead_tag_len_aes_gcm() {
747 let alg = CipherAlg::fetch(c"AES-256-GCM", None).unwrap();
748 let key = [0x11u8; 32];
749 let iv = [0x22u8; 12];
750 let ctx = alg.encrypt(&key, &iv, None).unwrap();
751 assert_eq!(ctx.aead_tag_len().unwrap(), 16);
752 }
753
754 #[test]
756 fn cipher_ctx_key_len_aes256() {
757 let alg = CipherAlg::fetch(c"AES-256-GCM", None).unwrap();
758 let key = [0x11u8; 32];
759 let iv = [0x22u8; 12];
760 let ctx = alg.encrypt(&key, &iv, None).unwrap();
761 assert_eq!(ctx.key_len().unwrap(), 32);
762 }
763
764 #[test]
766 fn cipher_ctx_iv_len_aes256_gcm() {
767 let alg = CipherAlg::fetch(c"AES-256-GCM", None).unwrap();
768 let key = [0x11u8; 32];
769 let iv = [0x22u8; 12];
770 let ctx = alg.encrypt(&key, &iv, None).unwrap();
771 assert_eq!(ctx.iv_len().unwrap(), 12);
772 }
773
774 #[test]
776 fn cipher_ctx_get_params_generic() {
777 let alg = CipherAlg::fetch(c"AES-256-GCM", None).unwrap();
778 let key = [0x11u8; 32];
779 let iv = [0x22u8; 12];
780 let ctx = alg.encrypt(&key, &iv, None).unwrap();
781
782 let mut params = crate::params::ParamBuilder::new()
783 .unwrap()
784 .push_size(c"keylen", 0)
785 .unwrap()
786 .push_size(c"ivlen", 0)
787 .unwrap()
788 .build()
789 .unwrap();
790 ctx.get_params(&mut params).unwrap();
791 assert_eq!(params.get_size_t(c"keylen").unwrap(), 32);
792 assert_eq!(params.get_size_t(c"ivlen").unwrap(), 12);
793 }
794}