1use crate::error::ErrorStack;
6use native_ossl_sys as sys;
7use std::ffi::CStr;
8use std::sync::Arc;
9
10#[derive(Debug)]
16pub struct DigestAlg {
17 ptr: *mut sys::EVP_MD,
18 lib_ctx: Option<Arc<crate::lib_ctx::LibCtx>>,
20}
21
22impl DigestAlg {
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_MD_fetch(std::ptr::null_mut(), name.as_ptr(), props_ptr) };
31 if ptr.is_null() {
32 return Err(ErrorStack::drain());
33 }
34 Ok(DigestAlg { ptr, lib_ctx: None })
35 }
36
37 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 { sys::EVP_MD_fetch(ctx.as_ptr(), name.as_ptr(), props_ptr) };
49 if ptr.is_null() {
50 return Err(ErrorStack::drain());
51 }
52 Ok(DigestAlg {
53 ptr,
54 lib_ctx: Some(Arc::clone(ctx)),
55 })
56 }
57
58 #[must_use]
60 pub fn output_len(&self) -> usize {
61 usize::try_from(unsafe { sys::EVP_MD_get_size(self.ptr) }).unwrap_or(0)
62 }
63
64 #[must_use]
66 pub fn block_size(&self) -> usize {
67 usize::try_from(unsafe { sys::EVP_MD_get_block_size(self.ptr) }).unwrap_or(0)
68 }
69
70 #[must_use]
72 pub fn nid(&self) -> i32 {
73 unsafe { sys::EVP_MD_get_type(self.ptr) }
74 }
75
76 #[must_use]
78 pub fn as_ptr(&self) -> *const sys::EVP_MD {
79 self.ptr
80 }
81}
82
83impl Clone for DigestAlg {
84 fn clone(&self) -> Self {
85 unsafe { sys::EVP_MD_up_ref(self.ptr) };
86 DigestAlg {
87 ptr: self.ptr,
88 lib_ctx: self.lib_ctx.clone(),
89 }
90 }
91}
92
93impl Drop for DigestAlg {
94 fn drop(&mut self) {
95 unsafe { sys::EVP_MD_free(self.ptr) };
96 }
97}
98
99unsafe impl Send for DigestAlg {}
101unsafe impl Sync for DigestAlg {}
102
103#[derive(Debug)]
110pub struct DigestCtx {
111 ptr: *mut sys::EVP_MD_CTX,
112}
113
114impl DigestCtx {
115 pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack> {
121 crate::ossl_call!(sys::EVP_DigestUpdate(
122 self.ptr,
123 data.as_ptr().cast(),
124 data.len()
125 ))
126 }
127
128 pub fn finish(&mut self, out: &mut [u8]) -> Result<usize, ErrorStack> {
137 let mut len: u32 = 0;
138 crate::ossl_call!(sys::EVP_DigestFinal_ex(
139 self.ptr,
140 out.as_mut_ptr(),
141 std::ptr::addr_of_mut!(len)
142 ))?;
143 Ok(usize::try_from(len).unwrap_or(0))
144 }
145
146 pub fn finish_xof(&mut self, out: &mut [u8]) -> Result<(), ErrorStack> {
152 crate::ossl_call!(sys::EVP_DigestFinalXOF(
153 self.ptr,
154 out.as_mut_ptr(),
155 out.len()
156 ))
157 }
158
159 pub fn fork(&self) -> Result<DigestCtx, ErrorStack> {
166 let new_ctx = unsafe { sys::EVP_MD_CTX_new() };
167 if new_ctx.is_null() {
168 return Err(ErrorStack::drain());
169 }
170 crate::ossl_call!(sys::EVP_MD_CTX_copy_ex(new_ctx, self.ptr))?;
171 Ok(DigestCtx { ptr: new_ctx })
172 }
173
174 #[cfg(ossl_v400)]
185 pub fn serialize_size(&self) -> Result<usize, ErrorStack> {
186 let mut outlen: usize = 0;
187 crate::ossl_call!(sys::EVP_MD_CTX_serialize(
188 self.ptr,
189 std::ptr::null_mut(),
190 std::ptr::addr_of_mut!(outlen)
191 ))?;
192 Ok(outlen)
193 }
194
195 #[cfg(ossl_v400)]
208 pub fn serialize(&self, out: &mut [u8]) -> Result<usize, ErrorStack> {
209 let mut outlen: usize = out.len();
210 crate::ossl_call!(sys::EVP_MD_CTX_serialize(
211 self.ptr,
212 out.as_mut_ptr(),
213 std::ptr::addr_of_mut!(outlen)
214 ))?;
215 Ok(outlen)
216 }
217
218 #[cfg(ossl_v400)]
231 pub fn deserialize(&mut self, data: &[u8]) -> Result<(), ErrorStack> {
232 crate::ossl_call!(sys::EVP_MD_CTX_deserialize(
233 self.ptr,
234 data.as_ptr(),
235 data.len()
236 ))
237 }
238
239 pub fn new_empty() -> Result<Self, ErrorStack> {
249 let ptr = unsafe { sys::EVP_MD_CTX_new() };
250 if ptr.is_null() {
251 return Err(ErrorStack::drain());
252 }
253 Ok(DigestCtx { ptr })
254 }
255
256 pub fn reinit(
265 &mut self,
266 alg: &DigestAlg,
267 params: Option<&crate::params::Params<'_>>,
268 ) -> Result<(), ErrorStack> {
269 crate::ossl_call!(sys::EVP_DigestInit_ex2(
270 self.ptr,
271 alg.ptr,
272 params.map_or(std::ptr::null(), super::params::Params::as_ptr),
273 ))
274 }
275
276 pub unsafe fn from_ptr(ptr: *mut sys::EVP_MD_CTX) -> Self {
283 DigestCtx { ptr }
284 }
285
286 #[must_use]
293 pub fn as_ptr(&self) -> *mut sys::EVP_MD_CTX {
294 self.ptr
295 }
296}
297
298impl Drop for DigestCtx {
299 fn drop(&mut self) {
300 unsafe { sys::EVP_MD_CTX_free(self.ptr) };
301 }
302}
303
304unsafe impl Send for DigestCtx {}
309unsafe impl Sync for DigestCtx {}
310
311impl DigestAlg {
312 pub fn new_context(&self) -> Result<DigestCtx, ErrorStack> {
318 let ctx_ptr = unsafe { sys::EVP_MD_CTX_new() };
319 if ctx_ptr.is_null() {
320 return Err(ErrorStack::drain());
321 }
322 crate::ossl_call!(sys::EVP_DigestInit_ex2(ctx_ptr, self.ptr, std::ptr::null())).map_err(
323 |e| {
324 unsafe { sys::EVP_MD_CTX_free(ctx_ptr) };
325 e
326 },
327 )?;
328 Ok(DigestCtx { ptr: ctx_ptr })
329 }
330
331 pub fn digest(&self, data: &[u8], out: &mut [u8]) -> Result<usize, ErrorStack> {
338 let mut len: u32 = 0;
339 crate::ossl_call!(sys::EVP_Digest(
340 data.as_ptr().cast(),
341 data.len(),
342 out.as_mut_ptr(),
343 std::ptr::addr_of_mut!(len),
344 self.ptr,
345 std::ptr::null_mut()
346 ))?;
347 Ok(usize::try_from(len).unwrap_or(0))
348 }
349
350 pub fn digest_to_vec(&self, data: &[u8]) -> Result<Vec<u8>, ErrorStack> {
354 let mut out = vec![0u8; self.output_len()];
355 let len = self.digest(data, &mut out)?;
356 out.truncate(len);
357 Ok(out)
358 }
359}
360
361#[cfg(test)]
364mod tests {
365 use super::*;
366
367 #[test]
368 fn fetch_sha256_properties() {
369 let alg = DigestAlg::fetch(c"SHA2-256", None).unwrap();
370 assert_eq!(alg.output_len(), 32);
371 assert_eq!(alg.block_size(), 64);
372 }
373
374 #[test]
375 fn fetch_nonexistent_fails() {
376 assert!(DigestAlg::fetch(c"NONEXISTENT_DIGEST_XYZ", None).is_err());
377 }
378
379 #[test]
380 fn clone_then_drop_both() {
381 let alg = DigestAlg::fetch(c"SHA2-256", None).unwrap();
382 let alg2 = alg.clone();
383 drop(alg);
385 drop(alg2);
386 }
387
388 #[test]
390 fn sha256_known_answer() {
391 let alg = DigestAlg::fetch(c"SHA2-256", None).unwrap();
392 let mut ctx = alg.new_context().unwrap();
393 ctx.update(b"abc").unwrap();
394 let mut out = [0u8; 32];
395 let n = ctx.finish(&mut out).unwrap();
396 assert_eq!(n, 32);
397 assert_eq!(
398 hex::encode(out),
399 "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
400 );
401 }
402
403 #[test]
405 fn sha256_oneshot() {
406 let alg = DigestAlg::fetch(c"SHA2-256", None).unwrap();
407 let got = alg.digest_to_vec(b"abc").unwrap();
408 let expected =
409 hex::decode("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad")
410 .unwrap();
411 assert_eq!(got, expected);
412 }
413
414 #[test]
416 fn fork_mid_stream() {
417 let alg = DigestAlg::fetch(c"SHA2-256", None).unwrap();
418 let mut ctx = alg.new_context().unwrap();
419 ctx.update(b"common prefix").unwrap();
420
421 let mut fork = ctx.fork().unwrap();
422
423 ctx.update(b" A").unwrap();
424 fork.update(b" B").unwrap();
425
426 let mut out_a = [0u8; 32];
427 let mut out_b = [0u8; 32];
428 ctx.finish(&mut out_a).unwrap();
429 fork.finish(&mut out_b).unwrap();
430
431 assert_ne!(out_a, out_b);
433 }
434
435 #[cfg(ossl_v400)]
440 #[test]
441 fn serialize_deserialize_roundtrip() {
442 let alg = DigestAlg::fetch(c"SHA2-256", None).unwrap();
443
444 let mut ctx_a = alg.new_context().unwrap();
445 ctx_a.update(b"hello").unwrap();
446
447 let size = ctx_a.serialize_size().unwrap();
449 assert!(size > 0, "serialized state must be non-empty");
450
451 let mut state = vec![0u8; size];
452 let written = ctx_a.serialize(&mut state).unwrap();
453 assert_eq!(written, size, "serialize wrote unexpected byte count");
454
455 ctx_a.update(b" world").unwrap();
457 let mut out_a = [0u8; 32];
458 ctx_a.finish(&mut out_a).unwrap();
459
460 let mut ctx_b = alg.new_context().unwrap();
462 ctx_b.deserialize(&state).unwrap();
463 ctx_b.update(b" world").unwrap();
464 let mut out_b = [0u8; 32];
465 ctx_b.finish(&mut out_b).unwrap();
466
467 assert_eq!(out_a, out_b, "restored context produced different digest");
468 }
469
470 #[cfg(ossl_v400)]
472 #[test]
473 fn serialize_different_suffix_differs() {
474 let alg = DigestAlg::fetch(c"SHA2-256", None).unwrap();
475 let mut ctx = alg.new_context().unwrap();
476 ctx.update(b"hello").unwrap();
477
478 let size = ctx.serialize_size().unwrap();
479 let mut state = vec![0u8; size];
480 ctx.serialize(&mut state).unwrap();
481
482 let mut ctx_a = alg.new_context().unwrap();
483 ctx_a.deserialize(&state).unwrap();
484 ctx_a.update(b" world").unwrap();
485 let mut out_a = [0u8; 32];
486 ctx_a.finish(&mut out_a).unwrap();
487
488 let mut ctx_b = alg.new_context().unwrap();
489 ctx_b.deserialize(&state).unwrap();
490 ctx_b.update(b" WORLD").unwrap();
491 let mut out_b = [0u8; 32];
492 ctx_b.finish(&mut out_b).unwrap();
493
494 assert_ne!(
495 out_a, out_b,
496 "different suffixes must produce different digests"
497 );
498 }
499}