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 #[must_use]
197 pub fn as_ptr(&self) -> *mut sys::EVP_CIPHER_CTX {
198 self.ptr
199 }
200}
201
202impl<Dir> Drop for CipherCtx<Dir> {
203 fn drop(&mut self) {
204 unsafe { sys::EVP_CIPHER_CTX_free(self.ptr) };
205 }
206}
207
208unsafe impl<Dir: sealed::Direction> Send for CipherCtx<Dir> {}
209
210pub trait IsEncrypt: sealed::Direction {
214 unsafe fn do_update(
224 ctx: *mut sys::EVP_CIPHER_CTX,
225 input: &[u8],
226 output: &mut [u8],
227 ) -> Result<usize, ErrorStack>;
228
229 unsafe fn do_finalize(
239 ctx: *mut sys::EVP_CIPHER_CTX,
240 output: &mut [u8],
241 ) -> Result<usize, ErrorStack>;
242}
243
244impl IsEncrypt for Encrypt {
245 unsafe fn do_update(
246 ctx: *mut sys::EVP_CIPHER_CTX,
247 input: &[u8],
248 output: &mut [u8],
249 ) -> Result<usize, ErrorStack> {
250 let inl = i32::try_from(input.len()).map_err(|_| ErrorStack::drain())?;
251 let mut outl: i32 = 0;
252 crate::ossl_call!(sys::EVP_EncryptUpdate(
253 ctx,
254 output.as_mut_ptr(),
255 std::ptr::addr_of_mut!(outl),
256 input.as_ptr(),
257 inl
258 ))?;
259 Ok(usize::try_from(outl).unwrap_or(0))
260 }
261
262 unsafe fn do_finalize(
263 ctx: *mut sys::EVP_CIPHER_CTX,
264 output: &mut [u8],
265 ) -> Result<usize, ErrorStack> {
266 let mut outl: i32 = 0;
267 crate::ossl_call!(sys::EVP_EncryptFinal_ex(
268 ctx,
269 output.as_mut_ptr(),
270 std::ptr::addr_of_mut!(outl)
271 ))?;
272 Ok(usize::try_from(outl).unwrap_or(0))
273 }
274}
275
276impl IsEncrypt for Decrypt {
277 unsafe fn do_update(
278 ctx: *mut sys::EVP_CIPHER_CTX,
279 input: &[u8],
280 output: &mut [u8],
281 ) -> Result<usize, ErrorStack> {
282 let inl = i32::try_from(input.len()).map_err(|_| ErrorStack::drain())?;
283 let mut outl: i32 = 0;
284 crate::ossl_call!(sys::EVP_DecryptUpdate(
285 ctx,
286 output.as_mut_ptr(),
287 std::ptr::addr_of_mut!(outl),
288 input.as_ptr(),
289 inl
290 ))?;
291 Ok(usize::try_from(outl).unwrap_or(0))
292 }
293
294 unsafe fn do_finalize(
295 ctx: *mut sys::EVP_CIPHER_CTX,
296 output: &mut [u8],
297 ) -> Result<usize, ErrorStack> {
298 let mut outl: i32 = 0;
299 crate::ossl_call!(sys::EVP_DecryptFinal_ex(
300 ctx,
301 output.as_mut_ptr(),
302 std::ptr::addr_of_mut!(outl)
303 ))?;
304 Ok(usize::try_from(outl).unwrap_or(0))
305 }
306}
307
308impl CipherAlg {
309 pub fn encrypt(
316 &self,
317 key: &[u8],
318 iv: &[u8],
319 params: Option<&crate::params::Params<'_>>,
320 ) -> Result<CipherCtx<Encrypt>, ErrorStack> {
321 let ctx_ptr = unsafe { sys::EVP_CIPHER_CTX_new() };
322 if ctx_ptr.is_null() {
323 return Err(ErrorStack::drain());
324 }
325 let params_ptr = params.map_or(crate::params::null_params(), crate::params::Params::as_ptr);
326 crate::ossl_call!(sys::EVP_EncryptInit_ex2(
327 ctx_ptr,
328 self.ptr,
329 key.as_ptr(),
330 iv.as_ptr(),
331 params_ptr
332 ))
333 .map_err(|e| {
334 unsafe { sys::EVP_CIPHER_CTX_free(ctx_ptr) };
335 e
336 })?;
337 Ok(CipherCtx {
338 ptr: ctx_ptr,
339 _dir: std::marker::PhantomData,
340 })
341 }
342
343 pub fn decrypt(
349 &self,
350 key: &[u8],
351 iv: &[u8],
352 params: Option<&crate::params::Params<'_>>,
353 ) -> Result<CipherCtx<Decrypt>, ErrorStack> {
354 let ctx_ptr = unsafe { sys::EVP_CIPHER_CTX_new() };
355 if ctx_ptr.is_null() {
356 return Err(ErrorStack::drain());
357 }
358 let params_ptr = params.map_or(crate::params::null_params(), crate::params::Params::as_ptr);
359 crate::ossl_call!(sys::EVP_DecryptInit_ex2(
360 ctx_ptr,
361 self.ptr,
362 key.as_ptr(),
363 iv.as_ptr(),
364 params_ptr
365 ))
366 .map_err(|e| {
367 unsafe { sys::EVP_CIPHER_CTX_free(ctx_ptr) };
368 e
369 })?;
370 Ok(CipherCtx {
371 ptr: ctx_ptr,
372 _dir: std::marker::PhantomData,
373 })
374 }
375}
376
377pub struct AeadEncryptCtx(CipherCtx<Encrypt>);
381
382impl AeadEncryptCtx {
383 pub fn new(
391 alg: &CipherAlg,
392 key: &[u8],
393 iv: &[u8],
394 params: Option<&crate::params::Params<'_>>,
395 ) -> Result<Self, ErrorStack> {
396 assert!(alg.is_aead(), "CipherAlg is not an AEAD algorithm");
397 Ok(AeadEncryptCtx(alg.encrypt(key, iv, params)?))
398 }
399
400 pub fn set_aad(&mut self, aad: &[u8]) -> Result<(), ErrorStack> {
408 let alen = i32::try_from(aad.len()).expect("AAD too large for EVP_EncryptUpdate");
410 let mut outl: i32 = 0;
411 crate::ossl_call!(sys::EVP_EncryptUpdate(
412 self.0.ptr,
413 std::ptr::null_mut(),
414 std::ptr::addr_of_mut!(outl),
415 aad.as_ptr(),
416 alen
417 ))
418 }
419
420 pub fn update(&mut self, input: &[u8], output: &mut [u8]) -> Result<usize, ErrorStack> {
424 self.0.update(input, output)
425 }
426
427 pub fn finalize(&mut self, output: &mut [u8]) -> Result<usize, ErrorStack> {
431 self.0.finalize(output)
432 }
433
434 pub fn tag(&self, tag: &mut [u8]) -> Result<(), ErrorStack> {
444 let tlen = i32::try_from(tag.len()).expect("tag slice too large");
446 let rc = unsafe {
447 sys::EVP_CIPHER_CTX_ctrl(
448 self.0.ptr,
449 16, tlen,
451 tag.as_mut_ptr().cast(),
452 )
453 };
454 if rc != 1 {
455 return Err(ErrorStack::drain());
456 }
457 Ok(())
458 }
459}
460
461pub struct AeadDecryptCtx(CipherCtx<Decrypt>);
463
464impl AeadDecryptCtx {
465 pub fn new(
473 alg: &CipherAlg,
474 key: &[u8],
475 iv: &[u8],
476 params: Option<&crate::params::Params<'_>>,
477 ) -> Result<Self, ErrorStack> {
478 assert!(alg.is_aead(), "CipherAlg is not an AEAD algorithm");
479 Ok(AeadDecryptCtx(alg.decrypt(key, iv, params)?))
480 }
481
482 pub fn set_aad(&mut self, aad: &[u8]) -> Result<(), ErrorStack> {
490 let alen = i32::try_from(aad.len()).expect("AAD too large for EVP_DecryptUpdate");
491 let mut outl: i32 = 0;
492 crate::ossl_call!(sys::EVP_DecryptUpdate(
493 self.0.ptr,
494 std::ptr::null_mut(),
495 std::ptr::addr_of_mut!(outl),
496 aad.as_ptr(),
497 alen
498 ))
499 }
500
501 pub fn update(&mut self, input: &[u8], output: &mut [u8]) -> Result<usize, ErrorStack> {
505 self.0.update(input, output)
506 }
507
508 pub fn set_tag(&mut self, tag: &[u8]) -> Result<(), ErrorStack> {
516 let tlen = i32::try_from(tag.len()).expect("tag slice too large");
518 let rc = unsafe {
519 sys::EVP_CIPHER_CTX_ctrl(
520 self.0.ptr,
521 17, tlen,
523 tag.as_ptr().cast_mut().cast(),
525 )
526 };
527 if rc != 1 {
528 return Err(ErrorStack::drain());
529 }
530 Ok(())
531 }
532
533 pub fn finalize(&mut self, output: &mut [u8]) -> Result<usize, ErrorStack> {
537 self.0.finalize(output)
538 }
539}
540
541#[cfg(test)]
544mod tests {
545 use super::*;
546
547 #[test]
548 fn fetch_aes_256_gcm_properties() {
549 let alg = CipherAlg::fetch(c"AES-256-GCM", None).unwrap();
550 assert_eq!(alg.key_len(), 32);
551 assert_eq!(alg.iv_len(), 12);
552 assert_eq!(alg.block_size(), 1);
553 assert!(alg.is_aead());
554 }
555
556 #[test]
557 fn fetch_aes_256_cbc_properties() {
558 let alg = CipherAlg::fetch(c"AES-256-CBC", None).unwrap();
559 assert_eq!(alg.key_len(), 32);
560 assert_eq!(alg.iv_len(), 16);
561 assert_eq!(alg.block_size(), 16);
562 assert!(!alg.is_aead());
563 }
564
565 #[test]
566 fn fetch_nonexistent_fails() {
567 assert!(CipherAlg::fetch(c"NONEXISTENT_CIPHER_XYZ", None).is_err());
568 }
569
570 #[test]
571 fn clone_then_drop_both() {
572 let alg = CipherAlg::fetch(c"AES-256-GCM", None).unwrap();
573 let alg2 = alg.clone();
574 drop(alg);
575 drop(alg2);
576 }
577
578 #[test]
580 fn aes_256_cbc_round_trip() {
581 let alg = CipherAlg::fetch(c"AES-256-CBC", None).unwrap();
582 let key = [0x42u8; 32];
583 let iv = [0x24u8; 16];
584 let plaintext = b"Hello, cipher world!";
585
586 let mut enc = alg.encrypt(&key, &iv, None).unwrap();
588 let mut ciphertext = vec![0u8; plaintext.len() + alg.block_size()];
589 let n = enc.update(plaintext, &mut ciphertext).unwrap();
590 let m = enc.finalize(&mut ciphertext[n..]).unwrap();
591 ciphertext.truncate(n + m);
592
593 let mut dec = alg.decrypt(&key, &iv, None).unwrap();
595 let mut recovered = vec![0u8; ciphertext.len() + alg.block_size()];
596 let n2 = dec.update(&ciphertext, &mut recovered).unwrap();
597 let m2 = dec.finalize(&mut recovered[n2..]).unwrap();
598 recovered.truncate(n2 + m2);
599
600 assert_eq!(recovered, plaintext);
601 }
602
603 #[test]
605 fn aes_256_gcm_round_trip_and_tag_failure() {
606 let alg = CipherAlg::fetch(c"AES-256-GCM", None).unwrap();
607 let key = [0x11u8; 32];
608 let iv = [0x22u8; 12];
609 let aad = b"additional data";
610 let plaintext = b"secret message!";
611
612 let mut enc = AeadEncryptCtx::new(&alg, &key, &iv, None).unwrap();
614 enc.set_aad(aad).unwrap();
615 let mut ciphertext = vec![0u8; plaintext.len()];
616 let n = enc.update(plaintext, &mut ciphertext).unwrap();
617 enc.finalize(&mut ciphertext[n..]).unwrap();
618 let mut tag = [0u8; 16];
619 enc.tag(&mut tag).unwrap();
620
621 let mut dec = AeadDecryptCtx::new(&alg, &key, &iv, None).unwrap();
623 dec.set_aad(aad).unwrap();
624 let mut recovered = vec![0u8; ciphertext.len()];
625 let n2 = dec.update(&ciphertext, &mut recovered).unwrap();
626 dec.set_tag(&tag).unwrap();
627 dec.finalize(&mut recovered[n2..]).unwrap();
628 assert_eq!(&recovered[..n2], plaintext);
629
630 let mut bad_tag = tag;
632 bad_tag[0] ^= 0xff;
633 let mut dec2 = AeadDecryptCtx::new(&alg, &key, &iv, None).unwrap();
634 dec2.set_aad(aad).unwrap();
635 let mut dummy = vec![0u8; ciphertext.len()];
636 dec2.update(&ciphertext, &mut dummy).unwrap();
637 dec2.set_tag(&bad_tag).unwrap();
638 assert!(dec2.finalize(&mut dummy).is_err());
639 }
640}