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 {
31 sys::EVP_CIPHER_fetch(std::ptr::null_mut(), name.as_ptr(), props_ptr)
32 };
33 if ptr.is_null() {
34 return Err(ErrorStack::drain());
35 }
36 Ok(CipherAlg { ptr, lib_ctx: None })
37 }
38
39 pub fn fetch_in(
43 ctx: &Arc<crate::lib_ctx::LibCtx>,
44 name: &CStr,
45 props: Option<&CStr>,
46 ) -> Result<Self, ErrorStack> {
47 let props_ptr = props.map_or(std::ptr::null(), CStr::as_ptr);
48 let ptr = unsafe {
49 sys::EVP_CIPHER_fetch(ctx.as_ptr(), name.as_ptr(), props_ptr)
50 };
51 if ptr.is_null() {
52 return Err(ErrorStack::drain());
53 }
54 Ok(CipherAlg {
55 ptr,
56 lib_ctx: Some(Arc::clone(ctx)),
57 })
58 }
59
60 #[must_use]
62 pub fn key_len(&self) -> usize {
63 usize::try_from(unsafe { sys::EVP_CIPHER_get_key_length(self.ptr) }).unwrap_or(0)
64 }
65
66 #[must_use]
68 pub fn iv_len(&self) -> usize {
69 usize::try_from(unsafe { sys::EVP_CIPHER_get_iv_length(self.ptr) }).unwrap_or(0)
70 }
71
72 #[must_use]
74 pub fn block_size(&self) -> usize {
75 usize::try_from(unsafe { sys::EVP_CIPHER_get_block_size(self.ptr) }).unwrap_or(0)
76 }
77
78 #[must_use]
80 pub fn flags(&self) -> u64 {
81 unsafe { sys::EVP_CIPHER_get_flags(self.ptr) }
82 }
83
84 #[must_use]
86 pub fn is_aead(&self) -> bool {
87 (self.flags() & 0x0020_0000) != 0
89 }
90
91 #[must_use]
93 pub fn as_ptr(&self) -> *const sys::EVP_CIPHER {
94 self.ptr
95 }
96}
97
98impl Clone for CipherAlg {
99 fn clone(&self) -> Self {
100 unsafe { sys::EVP_CIPHER_up_ref(self.ptr) };
101 CipherAlg {
102 ptr: self.ptr,
103 lib_ctx: self.lib_ctx.clone(),
104 }
105 }
106}
107
108impl Drop for CipherAlg {
109 fn drop(&mut self) {
110 unsafe { sys::EVP_CIPHER_free(self.ptr) };
111 }
112}
113
114unsafe impl Send for CipherAlg {}
116unsafe impl Sync for CipherAlg {}
117
118pub struct Encrypt;
122pub struct Decrypt;
124
125mod sealed {
126 pub trait Direction {}
127 impl Direction for super::Encrypt {}
128 impl Direction for super::Decrypt {}
129}
130
131pub struct CipherCtx<Dir> {
140 ptr: *mut sys::EVP_CIPHER_CTX,
141 _dir: std::marker::PhantomData<Dir>,
142}
143
144impl<Dir: sealed::Direction> CipherCtx<Dir> {
145 pub fn update(&mut self, input: &[u8], output: &mut [u8]) -> Result<usize, ErrorStack>
152 where
153 Dir: IsEncrypt,
154 {
155 unsafe { Dir::do_update(self.ptr, input, output) }
157 }
158
159 pub fn update_to_vec(&mut self, input: &[u8]) -> Result<Vec<u8>, ErrorStack>
167 where
168 Dir: IsEncrypt,
169 {
170 let block_size = usize::try_from(unsafe {
171 sys::EVP_CIPHER_CTX_get_block_size(self.ptr)
172 })
173 .unwrap_or(0);
174 let max = input.len() + block_size;
175 let mut out = vec![0u8; max];
176 let n = self.update(input, &mut out)?;
177 out.truncate(n);
178 Ok(out)
179 }
180
181 pub fn finalize(&mut self, output: &mut [u8]) -> Result<usize, ErrorStack>
185 where
186 Dir: IsEncrypt,
187 {
188 unsafe { Dir::do_finalize(self.ptr, output) }
190 }
191
192 pub fn set_params(&mut self, params: &crate::params::Params<'_>) -> Result<(), ErrorStack> {
196 crate::ossl_call!(sys::EVP_CIPHER_CTX_set_params(self.ptr, params.as_ptr()))
197 }
198
199 #[must_use]
203 pub fn as_ptr(&self) -> *mut sys::EVP_CIPHER_CTX {
204 self.ptr
205 }
206}
207
208impl<Dir> Drop for CipherCtx<Dir> {
209 fn drop(&mut self) {
210 unsafe { sys::EVP_CIPHER_CTX_free(self.ptr) };
211 }
212}
213
214unsafe impl<Dir: sealed::Direction> Send for CipherCtx<Dir> {}
215
216pub trait IsEncrypt: sealed::Direction {
220 unsafe fn do_update(
230 ctx: *mut sys::EVP_CIPHER_CTX,
231 input: &[u8],
232 output: &mut [u8],
233 ) -> Result<usize, ErrorStack>;
234
235 unsafe fn do_finalize(
245 ctx: *mut sys::EVP_CIPHER_CTX,
246 output: &mut [u8],
247 ) -> Result<usize, ErrorStack>;
248}
249
250impl IsEncrypt for Encrypt {
251 unsafe fn do_update(
252 ctx: *mut sys::EVP_CIPHER_CTX,
253 input: &[u8],
254 output: &mut [u8],
255 ) -> Result<usize, ErrorStack> {
256 let inl = i32::try_from(input.len()).map_err(|_| ErrorStack::drain())?;
257 let mut outl: i32 = 0;
258 crate::ossl_call!(sys::EVP_EncryptUpdate(
259 ctx,
260 output.as_mut_ptr(),
261 std::ptr::addr_of_mut!(outl),
262 input.as_ptr(),
263 inl
264 ))?;
265 Ok(usize::try_from(outl).unwrap_or(0))
266 }
267
268 unsafe fn do_finalize(
269 ctx: *mut sys::EVP_CIPHER_CTX,
270 output: &mut [u8],
271 ) -> Result<usize, ErrorStack> {
272 let mut outl: i32 = 0;
273 crate::ossl_call!(sys::EVP_EncryptFinal_ex(
274 ctx,
275 output.as_mut_ptr(),
276 std::ptr::addr_of_mut!(outl)
277 ))?;
278 Ok(usize::try_from(outl).unwrap_or(0))
279 }
280}
281
282impl IsEncrypt for Decrypt {
283 unsafe fn do_update(
284 ctx: *mut sys::EVP_CIPHER_CTX,
285 input: &[u8],
286 output: &mut [u8],
287 ) -> Result<usize, ErrorStack> {
288 let inl = i32::try_from(input.len()).map_err(|_| ErrorStack::drain())?;
289 let mut outl: i32 = 0;
290 crate::ossl_call!(sys::EVP_DecryptUpdate(
291 ctx,
292 output.as_mut_ptr(),
293 std::ptr::addr_of_mut!(outl),
294 input.as_ptr(),
295 inl
296 ))?;
297 Ok(usize::try_from(outl).unwrap_or(0))
298 }
299
300 unsafe fn do_finalize(
301 ctx: *mut sys::EVP_CIPHER_CTX,
302 output: &mut [u8],
303 ) -> Result<usize, ErrorStack> {
304 let mut outl: i32 = 0;
305 crate::ossl_call!(sys::EVP_DecryptFinal_ex(
306 ctx,
307 output.as_mut_ptr(),
308 std::ptr::addr_of_mut!(outl)
309 ))?;
310 Ok(usize::try_from(outl).unwrap_or(0))
311 }
312}
313
314impl CipherAlg {
315 pub fn encrypt(
322 &self,
323 key: &[u8],
324 iv: &[u8],
325 params: Option<&crate::params::Params<'_>>,
326 ) -> Result<CipherCtx<Encrypt>, ErrorStack> {
327 let ctx_ptr = unsafe { sys::EVP_CIPHER_CTX_new() };
328 if ctx_ptr.is_null() {
329 return Err(ErrorStack::drain());
330 }
331 let params_ptr = params.map_or(crate::params::null_params(), crate::params::Params::as_ptr);
332 crate::ossl_call!(sys::EVP_EncryptInit_ex2(
333 ctx_ptr,
334 self.ptr,
335 key.as_ptr(),
336 iv.as_ptr(),
337 params_ptr
338 ))
339 .map_err(|e| {
340 unsafe { sys::EVP_CIPHER_CTX_free(ctx_ptr) };
341 e
342 })?;
343 Ok(CipherCtx {
344 ptr: ctx_ptr,
345 _dir: std::marker::PhantomData,
346 })
347 }
348
349 pub fn decrypt(
355 &self,
356 key: &[u8],
357 iv: &[u8],
358 params: Option<&crate::params::Params<'_>>,
359 ) -> Result<CipherCtx<Decrypt>, ErrorStack> {
360 let ctx_ptr = unsafe { sys::EVP_CIPHER_CTX_new() };
361 if ctx_ptr.is_null() {
362 return Err(ErrorStack::drain());
363 }
364 let params_ptr = params.map_or(crate::params::null_params(), crate::params::Params::as_ptr);
365 crate::ossl_call!(sys::EVP_DecryptInit_ex2(
366 ctx_ptr,
367 self.ptr,
368 key.as_ptr(),
369 iv.as_ptr(),
370 params_ptr
371 ))
372 .map_err(|e| {
373 unsafe { sys::EVP_CIPHER_CTX_free(ctx_ptr) };
374 e
375 })?;
376 Ok(CipherCtx {
377 ptr: ctx_ptr,
378 _dir: std::marker::PhantomData,
379 })
380 }
381}
382
383pub struct AeadEncryptCtx(CipherCtx<Encrypt>);
387
388impl AeadEncryptCtx {
389 pub fn new(
397 alg: &CipherAlg,
398 key: &[u8],
399 iv: &[u8],
400 params: Option<&crate::params::Params<'_>>,
401 ) -> Result<Self, ErrorStack> {
402 assert!(alg.is_aead(), "CipherAlg is not an AEAD algorithm");
403 Ok(AeadEncryptCtx(alg.encrypt(key, iv, params)?))
404 }
405
406 pub fn set_aad(&mut self, aad: &[u8]) -> Result<(), ErrorStack> {
414 let alen = i32::try_from(aad.len()).expect("AAD too large for EVP_EncryptUpdate");
416 let mut outl: i32 = 0;
417 crate::ossl_call!(sys::EVP_EncryptUpdate(
418 self.0.ptr,
419 std::ptr::null_mut(),
420 std::ptr::addr_of_mut!(outl),
421 aad.as_ptr(),
422 alen
423 ))
424 }
425
426 pub fn update(&mut self, input: &[u8], output: &mut [u8]) -> Result<usize, ErrorStack> {
430 self.0.update(input, output)
431 }
432
433 pub fn finalize(&mut self, output: &mut [u8]) -> Result<usize, ErrorStack> {
437 self.0.finalize(output)
438 }
439
440 pub fn tag(&self, tag: &mut [u8]) -> Result<(), ErrorStack> {
450 let tlen = i32::try_from(tag.len()).expect("tag slice too large");
452 let rc = unsafe {
453 sys::EVP_CIPHER_CTX_ctrl(
454 self.0.ptr,
455 16, tlen,
457 tag.as_mut_ptr().cast(),
458 )
459 };
460 if rc != 1 {
461 return Err(ErrorStack::drain());
462 }
463 Ok(())
464 }
465}
466
467pub struct AeadDecryptCtx(CipherCtx<Decrypt>);
469
470impl AeadDecryptCtx {
471 pub fn new(
479 alg: &CipherAlg,
480 key: &[u8],
481 iv: &[u8],
482 params: Option<&crate::params::Params<'_>>,
483 ) -> Result<Self, ErrorStack> {
484 assert!(alg.is_aead(), "CipherAlg is not an AEAD algorithm");
485 Ok(AeadDecryptCtx(alg.decrypt(key, iv, params)?))
486 }
487
488 pub fn set_aad(&mut self, aad: &[u8]) -> Result<(), ErrorStack> {
496 let alen = i32::try_from(aad.len()).expect("AAD too large for EVP_DecryptUpdate");
497 let mut outl: i32 = 0;
498 crate::ossl_call!(sys::EVP_DecryptUpdate(
499 self.0.ptr,
500 std::ptr::null_mut(),
501 std::ptr::addr_of_mut!(outl),
502 aad.as_ptr(),
503 alen
504 ))
505 }
506
507 pub fn update(&mut self, input: &[u8], output: &mut [u8]) -> Result<usize, ErrorStack> {
511 self.0.update(input, output)
512 }
513
514 pub fn set_tag(&mut self, tag: &[u8]) -> Result<(), ErrorStack> {
522 let tlen = i32::try_from(tag.len()).expect("tag slice too large");
524 let rc = unsafe {
525 sys::EVP_CIPHER_CTX_ctrl(
526 self.0.ptr,
527 17, tlen,
529 tag.as_ptr().cast_mut().cast(),
531 )
532 };
533 if rc != 1 {
534 return Err(ErrorStack::drain());
535 }
536 Ok(())
537 }
538
539 pub fn finalize(&mut self, output: &mut [u8]) -> Result<usize, ErrorStack> {
543 self.0.finalize(output)
544 }
545}
546
547#[cfg(test)]
550mod tests {
551 use super::*;
552
553 #[test]
554 fn fetch_aes_256_gcm_properties() {
555 let alg = CipherAlg::fetch(c"AES-256-GCM", None).unwrap();
556 assert_eq!(alg.key_len(), 32);
557 assert_eq!(alg.iv_len(), 12);
558 assert_eq!(alg.block_size(), 1);
559 assert!(alg.is_aead());
560 }
561
562 #[test]
563 fn fetch_aes_256_cbc_properties() {
564 let alg = CipherAlg::fetch(c"AES-256-CBC", None).unwrap();
565 assert_eq!(alg.key_len(), 32);
566 assert_eq!(alg.iv_len(), 16);
567 assert_eq!(alg.block_size(), 16);
568 assert!(!alg.is_aead());
569 }
570
571 #[test]
572 fn fetch_nonexistent_fails() {
573 assert!(CipherAlg::fetch(c"NONEXISTENT_CIPHER_XYZ", None).is_err());
574 }
575
576 #[test]
577 fn clone_then_drop_both() {
578 let alg = CipherAlg::fetch(c"AES-256-GCM", None).unwrap();
579 let alg2 = alg.clone();
580 drop(alg);
581 drop(alg2);
582 }
583
584 #[test]
586 fn aes_256_cbc_round_trip() {
587 let alg = CipherAlg::fetch(c"AES-256-CBC", None).unwrap();
588 let key = [0x42u8; 32];
589 let iv = [0x24u8; 16];
590 let plaintext = b"Hello, cipher world!";
591
592 let mut enc = alg.encrypt(&key, &iv, None).unwrap();
594 let mut ciphertext = vec![0u8; plaintext.len() + alg.block_size()];
595 let n = enc.update(plaintext, &mut ciphertext).unwrap();
596 let m = enc.finalize(&mut ciphertext[n..]).unwrap();
597 ciphertext.truncate(n + m);
598
599 let mut dec = alg.decrypt(&key, &iv, None).unwrap();
601 let mut recovered = vec![0u8; ciphertext.len() + alg.block_size()];
602 let n2 = dec.update(&ciphertext, &mut recovered).unwrap();
603 let m2 = dec.finalize(&mut recovered[n2..]).unwrap();
604 recovered.truncate(n2 + m2);
605
606 assert_eq!(recovered, plaintext);
607 }
608
609 #[test]
611 fn aes_256_gcm_round_trip_and_tag_failure() {
612 let alg = CipherAlg::fetch(c"AES-256-GCM", None).unwrap();
613 let key = [0x11u8; 32];
614 let iv = [0x22u8; 12];
615 let aad = b"additional data";
616 let plaintext = b"secret message!";
617
618 let mut enc = AeadEncryptCtx::new(&alg, &key, &iv, None).unwrap();
620 enc.set_aad(aad).unwrap();
621 let mut ciphertext = vec![0u8; plaintext.len()];
622 let n = enc.update(plaintext, &mut ciphertext).unwrap();
623 enc.finalize(&mut ciphertext[n..]).unwrap();
624 let mut tag = [0u8; 16];
625 enc.tag(&mut tag).unwrap();
626
627 let mut dec = AeadDecryptCtx::new(&alg, &key, &iv, None).unwrap();
629 dec.set_aad(aad).unwrap();
630 let mut recovered = vec![0u8; ciphertext.len()];
631 let n2 = dec.update(&ciphertext, &mut recovered).unwrap();
632 dec.set_tag(&tag).unwrap();
633 dec.finalize(&mut recovered[n2..]).unwrap();
634 assert_eq!(&recovered[..n2], plaintext);
635
636 let mut bad_tag = tag;
638 bad_tag[0] ^= 0xff;
639 let mut dec2 = AeadDecryptCtx::new(&alg, &key, &iv, None).unwrap();
640 dec2.set_aad(aad).unwrap();
641 let mut dummy = vec![0u8; ciphertext.len()];
642 dec2.update(&ciphertext, &mut dummy).unwrap();
643 dec2.set_tag(&bad_tag).unwrap();
644 assert!(dec2.finalize(&mut dummy).is_err());
645 }
646}