Skip to main content

native_ossl/
params.rs

1//! `OSSL_PARAM_BLD` builder and `Params<'a>` wrapper.
2//!
3//! `ParamBuilder` wraps `OSSL_PARAM_BLD` and provides typed push methods that
4//! map Rust types to OpenSSL parameter types.  `build()` finalises the builder
5//! and returns an owned `Params` array.
6//!
7//! ## Lifetime
8//!
9//! `ParamBuilder<'a>` carries a lifetime `'a` that covers any borrowed byte
10//! slices passed via `push_octet_ptr` or `push_utf8_ptr`.  Those calls store
11//! a pointer into the caller's buffer; the buffer must outlive the `Params`
12//! returned by `build()`.  Calls that copy (`push_octet_slice`, `push_utf8_string`)
13//! have no such constraint.
14//!
15//! ## Zero-copy decision table
16//!
17//! | Method             | C function                      | Copies? |
18//! |--------------------|---------------------------------|---------|
19//! | `push_octet_slice` | `OSSL_PARAM_BLD_push_octet_string` | Yes  |
20//! | `push_octet_ptr`   | `OSSL_PARAM_BLD_push_octet_ptr` | No      |
21//! | `push_utf8_string` | `OSSL_PARAM_BLD_push_utf8_string`  | Yes  |
22//! | `push_utf8_ptr`    | (manual OSSL_PARAM_BLD extension) | No    |
23
24use crate::error::ErrorStack;
25use native_ossl_sys as sys;
26use std::ffi::CStr;
27use std::marker::PhantomData;
28use std::ptr;
29
30// ── Params — the built array ──────────────────────────────────────────────────
31
32/// An owned, finalized `OSSL_PARAM` array.
33///
34/// Created by `ParamBuilder::build()`.  The lifetime `'a` covers any borrowed
35/// byte or string slices stored by pointer (zero-copy push calls).
36pub struct Params<'a> {
37    /// The heap-allocated array of `OSSL_PARAM` elements.
38    ptr: *mut sys::OSSL_PARAM,
39    /// Phantom to carry the borrow lifetime of any `push_octet_ptr` data.
40    _lifetime: PhantomData<&'a [u8]>,
41}
42
43impl Params<'_> {
44    /// Return a const pointer to the first `OSSL_PARAM` in the array.
45    ///
46    /// Pass this pointer to OpenSSL functions that take `const OSSL_PARAM[]`.
47    /// The pointer is valid for the lifetime of `self`.
48    #[must_use]
49    pub fn as_ptr(&self) -> *const sys::OSSL_PARAM {
50        self.ptr
51    }
52}
53
54impl Drop for Params<'_> {
55    fn drop(&mut self) {
56        unsafe { sys::OSSL_PARAM_free(self.ptr) };
57    }
58}
59
60// SAFETY: `OSSL_PARAM` arrays are read-only after construction.
61// The builder ensures all owned copies are heap-allocated by OpenSSL.
62unsafe impl Send for Params<'_> {}
63unsafe impl Sync for Params<'_> {}
64
65// ── ParamBuilder ──────────────────────────────────────────────────────────────
66
67/// Builder for `OSSL_PARAM` arrays.
68///
69/// ```ignore
70/// use native_ossl::params::ParamBuilder;
71///
72/// let params = ParamBuilder::new()
73///     .push_int(c"key_bits", 2048)?
74///     .push_utf8_ptr(c"pad-mode", c"oaep")?
75///     .build()?;
76/// ```
77pub struct ParamBuilder<'a> {
78    ptr: *mut sys::OSSL_PARAM_BLD,
79    /// BIGNUMs pushed via `push_bn` that must remain alive until `build()`.
80    ///
81    /// `OSSL_PARAM_BLD_push_BN` stores a *pointer* to the BIGNUM internally;
82    /// the actual copy into the `OSSL_PARAM` array happens only in
83    /// `OSSL_PARAM_BLD_to_param`.  We must therefore keep each BIGNUM alive
84    /// until after `build()` completes.
85    bns: Vec<*mut sys::BIGNUM>,
86    _lifetime: PhantomData<&'a [u8]>,
87}
88
89// SAFETY: BIGNUM pointers are not shared; we control their lifetime.
90unsafe impl Send for ParamBuilder<'_> {}
91
92impl<'a> ParamBuilder<'a> {
93    /// Create a new empty builder.
94    ///
95    /// # Errors
96    ///
97    /// Returns `Err` if OpenSSL cannot allocate the builder.
98    pub fn new() -> Result<Self, ErrorStack> {
99        let ptr = unsafe { sys::OSSL_PARAM_BLD_new() };
100        if ptr.is_null() {
101            return Err(ErrorStack::drain());
102        }
103        Ok(ParamBuilder {
104            ptr,
105            bns: Vec::new(),
106            _lifetime: PhantomData,
107        })
108    }
109
110    /// Push a signed integer parameter.
111    ///
112    /// # Errors
113    ///
114    /// Returns `Err` if the push fails (allocation error or key too long).
115    pub fn push_int(self, key: &CStr, val: i32) -> Result<Self, ErrorStack> {
116        let rc = unsafe { sys::OSSL_PARAM_BLD_push_int(self.ptr, key.as_ptr(), val) };
117        if rc != 1 {
118            return Err(ErrorStack::drain());
119        }
120        Ok(self)
121    }
122
123    /// Push an unsigned integer parameter.
124    ///
125    /// # Errors
126    pub fn push_uint(self, key: &CStr, val: u32) -> Result<Self, ErrorStack> {
127        let rc = unsafe { sys::OSSL_PARAM_BLD_push_uint(self.ptr, key.as_ptr(), val) };
128        if rc != 1 {
129            return Err(ErrorStack::drain());
130        }
131        Ok(self)
132    }
133
134    /// Push a 64-bit unsigned integer parameter.
135    ///
136    /// Used for parameters such as the scrypt `N` cost factor.
137    ///
138    /// # Errors
139    pub fn push_uint64(self, key: &CStr, val: u64) -> Result<Self, ErrorStack> {
140        let rc = unsafe { sys::OSSL_PARAM_BLD_push_uint64(self.ptr, key.as_ptr(), val) };
141        if rc != 1 {
142            return Err(ErrorStack::drain());
143        }
144        Ok(self)
145    }
146
147    /// Push a `size_t` parameter.
148    ///
149    /// # Errors
150    pub fn push_size(self, key: &CStr, val: usize) -> Result<Self, ErrorStack> {
151        let rc = unsafe { sys::OSSL_PARAM_BLD_push_size_t(self.ptr, key.as_ptr(), val) };
152        if rc != 1 {
153            return Err(ErrorStack::drain());
154        }
155        Ok(self)
156    }
157
158    /// Push an octet-string parameter — **copies** `val` into the param array.
159    ///
160    /// Use this when `val` may be dropped before the resulting `Params` is consumed.
161    /// If `val` is guaranteed to outlive `Params`, prefer `push_octet_ptr`.
162    ///
163    /// # Errors
164    pub fn push_octet_slice(self, key: &CStr, val: &[u8]) -> Result<Self, ErrorStack> {
165        let rc = unsafe {
166            sys::OSSL_PARAM_BLD_push_octet_string(
167                self.ptr,
168                key.as_ptr(),
169                val.as_ptr().cast(),
170                val.len(),
171            )
172        };
173        if rc != 1 {
174            return Err(ErrorStack::drain());
175        }
176        Ok(self)
177    }
178
179    /// Push an octet-string parameter — **stores a pointer** into `val`.
180    ///
181    /// Zero-copy: no allocation occurs.  The lifetime `'b` of `val` must be at
182    /// least `'a` to prevent the reference from being invalidated before the
183    /// `Params` array is consumed.
184    ///
185    /// # Errors
186    pub fn push_octet_ptr<'b>(
187        self,
188        key: &CStr,
189        val: &'b [u8],
190    ) -> Result<ParamBuilder<'b>, ErrorStack>
191    where
192        'a: 'b,
193    {
194        let rc = unsafe {
195            sys::OSSL_PARAM_BLD_push_octet_ptr(
196                self.ptr,
197                key.as_ptr(),
198                // OpenSSL declares the parameter as *mut c_void but treats it
199                // as read-only once stored (pointer storage, no write-back).
200                // The cast from *const to *mut is intentional: OpenSSL's API
201                // is not const-correct here.
202                val.as_ptr() as *mut std::os::raw::c_void,
203                val.len(),
204            )
205        };
206        if rc != 1 {
207            // `self` drops here, running Drop (frees builder + BIGNUMs).
208            return Err(ErrorStack::drain());
209        }
210        // Transfer both `ptr` and `bns` into the new lifetime-narrowed builder.
211        // Use ManuallyDrop so the Drop impl of `self` does not run a second free.
212        let md = std::mem::ManuallyDrop::new(self);
213        Ok(ParamBuilder {
214            ptr: md.ptr,
215            // SAFETY: we are the sole owner; md's Drop will not run.
216            bns: unsafe { ptr::read(&raw const md.bns) },
217            _lifetime: PhantomData,
218        })
219    }
220
221    /// Push a UTF-8 string parameter — **copies** `val` into the param array.
222    ///
223    /// # Errors
224    pub fn push_utf8_string(self, key: &CStr, val: &CStr) -> Result<Self, ErrorStack> {
225        let rc = unsafe {
226            sys::OSSL_PARAM_BLD_push_utf8_string(
227                self.ptr,
228                key.as_ptr(),
229                val.as_ptr(),
230                0, // 0 = use strlen
231            )
232        };
233        if rc != 1 {
234            return Err(ErrorStack::drain());
235        }
236        Ok(self)
237    }
238
239    /// Push a UTF-8 string parameter — **stores a pointer** into `val`.
240    ///
241    /// Zero-copy: no allocation occurs.  Use only for `'static` literals such as
242    /// algorithm names (`c"oaep"`, `c"SHA-256"`).
243    ///
244    /// # Errors
245    pub fn push_utf8_ptr(self, key: &CStr, val: &'static CStr) -> Result<Self, ErrorStack> {
246        // OpenSSL does not provide OSSL_PARAM_BLD_push_utf8_ptr in all builds,
247        // so we use push_utf8_string which copies, but annotate that the intent
248        // is pointer storage.  For 'static values the copy is negligible.
249        //
250        // NOTE: If OpenSSL gains OSSL_PARAM_BLD_push_utf8_ptr exposure in future
251        // bindgen output, replace the body with the pointer variant.
252        let rc = unsafe {
253            sys::OSSL_PARAM_BLD_push_utf8_string(self.ptr, key.as_ptr(), val.as_ptr(), 0)
254        };
255        if rc != 1 {
256            return Err(ErrorStack::drain());
257        }
258        Ok(self)
259    }
260
261    /// Push a BIGNUM parameter from big-endian bytes.
262    ///
263    /// Converts `bigendian_bytes` to an OpenSSL `BIGNUM` and pushes it into the
264    /// builder.  The `BIGNUM` is copied into the builder's internal storage so
265    /// the caller's slice is not referenced after this call returns.
266    ///
267    /// # Panics
268    ///
269    /// Panics if `bigendian_bytes` is longer than `i32::MAX` bytes, which is
270    /// not a practical concern for key material.
271    ///
272    /// # Errors
273    ///
274    /// Returns `Err` if the allocation fails or the push fails.
275    pub fn push_bn(mut self, key: &CStr, bigendian_bytes: &[u8]) -> Result<Self, ErrorStack> {
276        // BN_bin2bn converts big-endian bytes into a newly allocated BIGNUM.
277        let bn = unsafe {
278            sys::BN_bin2bn(
279                bigendian_bytes.as_ptr(),
280                // len is int in C; values >2 GiB are pathological for key material.
281                i32::try_from(bigendian_bytes.len()).expect("BN too large"),
282                ptr::null_mut(),
283            )
284        };
285        if bn.is_null() {
286            return Err(ErrorStack::drain());
287        }
288        // OSSL_PARAM_BLD_push_BN stores a *pointer* to the BIGNUM; the actual
289        // copy into the OSSL_PARAM array is deferred to OSSL_PARAM_BLD_to_param.
290        // Keep the BIGNUM alive in `self.bns` until `build()` completes.
291        let rc = unsafe { sys::OSSL_PARAM_BLD_push_BN(self.ptr, key.as_ptr(), bn) };
292        if rc != 1 {
293            unsafe { sys::BN_free(bn) };
294            return Err(ErrorStack::drain());
295        }
296        self.bns.push(bn);
297        Ok(self)
298    }
299
300    /// Finalise the builder and return the `Params` array.
301    ///
302    /// Consumes the builder.  The returned `Params` must outlive any borrowed
303    /// slices stored via `push_octet_ptr`.
304    ///
305    /// # Errors
306    ///
307    /// Returns `Err` if `OSSL_PARAM_BLD_to_param` fails.
308    pub fn build(self) -> Result<Params<'a>, ErrorStack> {
309        // Take ownership of the raw pointers and prevent Drop from running,
310        // so we control the exact point at which cleanup happens.
311        let builder_ptr = self.ptr;
312        let bns = unsafe { ptr::read(&raw const self.bns) };
313        std::mem::forget(self);
314
315        let param_ptr = unsafe { sys::OSSL_PARAM_BLD_to_param(builder_ptr) };
316        // Free the builder — the OSSL_PARAM array is independent of it.
317        unsafe { sys::OSSL_PARAM_BLD_free(builder_ptr) };
318        // Now that to_param has copied all BIGNUM values, free the temporaries.
319        for bn in bns {
320            unsafe { sys::BN_free(bn) };
321        }
322
323        if param_ptr.is_null() {
324            return Err(ErrorStack::drain());
325        }
326        Ok(Params {
327            ptr: param_ptr,
328            _lifetime: PhantomData,
329        })
330    }
331}
332
333impl Drop for ParamBuilder<'_> {
334    fn drop(&mut self) {
335        if !self.ptr.is_null() {
336            unsafe { sys::OSSL_PARAM_BLD_free(self.ptr) };
337        }
338        for bn in self.bns.drain(..) {
339            unsafe { sys::BN_free(bn) };
340        }
341    }
342}
343
344// ── Private BigNum helper ─────────────────────────────────────────────────────
345
346struct Bn(*mut sys::BIGNUM);
347
348impl Bn {
349    /// Convert this `BIGNUM` to a big-endian byte vector.
350    fn to_bigendian_vec(&self) -> Vec<u8> {
351        let nbits = unsafe { sys::BN_num_bits(self.0) };
352        let nbytes = usize::try_from(nbits).unwrap_or(0).div_ceil(8);
353        if nbytes == 0 {
354            return Vec::new();
355        }
356        let mut out = vec![0u8; nbytes];
357        // BN_bn2bin writes exactly nbytes; returns nbytes on success.
358        unsafe { sys::BN_bn2bin(self.0, out.as_mut_ptr()) };
359        out
360    }
361}
362
363impl Drop for Bn {
364    fn drop(&mut self) {
365        unsafe { sys::BN_free(self.0) };
366    }
367}
368
369// ── Params — getters and ownership transfer ───────────────────────────────────
370
371impl Params<'_> {
372    /// Adopt an OpenSSL-allocated `OSSL_PARAM` array, taking ownership.
373    ///
374    /// The array will be freed with `OSSL_PARAM_free` on drop.  Use this to
375    /// wrap arrays returned by functions such as `EVP_PKEY_todata`.
376    ///
377    /// # Safety
378    ///
379    /// `ptr` must be a valid, `OSSL_PARAM_END`-terminated array allocated by
380    /// OpenSSL.  After this call the caller must not use or free `ptr`.
381    #[must_use]
382    pub unsafe fn from_owned_ptr(ptr: *mut sys::OSSL_PARAM) -> Params<'static> {
383        Params {
384            ptr,
385            _lifetime: PhantomData,
386        }
387    }
388
389    /// Return a mutable pointer to the first `OSSL_PARAM` element.
390    ///
391    /// Pass to functions such as `EVP_PKEY_get_params` that fill a
392    /// pre-prepared query array.
393    #[must_use]
394    pub fn as_mut_ptr(&mut self) -> *mut sys::OSSL_PARAM {
395        self.ptr
396    }
397
398    /// Return `true` if a parameter with the given name exists in this array.
399    #[must_use]
400    pub fn has_param(&self, key: &CStr) -> bool {
401        !unsafe { sys::OSSL_PARAM_locate(self.ptr, key.as_ptr()) }.is_null()
402    }
403
404    /// Locate `key` and read its value as an `i32`.
405    ///
406    /// # Errors
407    ///
408    /// Returns `Err` if the key is not found or the value cannot be converted.
409    pub fn get_int(&self, key: &CStr) -> Result<i32, ErrorStack> {
410        let elem = unsafe { sys::OSSL_PARAM_locate(self.ptr, key.as_ptr()) };
411        if elem.is_null() {
412            return Err(ErrorStack::drain());
413        }
414        let mut val: std::os::raw::c_int = 0;
415        crate::ossl_call!(sys::OSSL_PARAM_get_int(elem, std::ptr::addr_of_mut!(val)))?;
416        Ok(val)
417    }
418
419    /// Locate `key` and read its value as a `u32`.
420    ///
421    /// # Errors
422    pub fn get_uint(&self, key: &CStr) -> Result<u32, ErrorStack> {
423        let elem = unsafe { sys::OSSL_PARAM_locate(self.ptr, key.as_ptr()) };
424        if elem.is_null() {
425            return Err(ErrorStack::drain());
426        }
427        let mut val: std::os::raw::c_uint = 0;
428        crate::ossl_call!(sys::OSSL_PARAM_get_uint(elem, std::ptr::addr_of_mut!(val)))?;
429        Ok(val)
430    }
431
432    /// Locate `key` and read its value as a `usize`.
433    ///
434    /// # Errors
435    pub fn get_size_t(&self, key: &CStr) -> Result<usize, ErrorStack> {
436        let elem = unsafe { sys::OSSL_PARAM_locate(self.ptr, key.as_ptr()) };
437        if elem.is_null() {
438            return Err(ErrorStack::drain());
439        }
440        let mut val: usize = 0;
441        crate::ossl_call!(sys::OSSL_PARAM_get_size_t(
442            elem,
443            std::ptr::addr_of_mut!(val)
444        ))?;
445        Ok(val)
446    }
447
448    /// Locate `key` and read its value as an `i64`.
449    ///
450    /// # Errors
451    pub fn get_i64(&self, key: &CStr) -> Result<i64, ErrorStack> {
452        let elem = unsafe { sys::OSSL_PARAM_locate(self.ptr, key.as_ptr()) };
453        if elem.is_null() {
454            return Err(ErrorStack::drain());
455        }
456        let mut val: i64 = 0;
457        crate::ossl_call!(sys::OSSL_PARAM_get_int64(elem, std::ptr::addr_of_mut!(val)))?;
458        Ok(val)
459    }
460
461    /// Locate `key` and read its value as a `u64`.
462    ///
463    /// # Errors
464    pub fn get_u64(&self, key: &CStr) -> Result<u64, ErrorStack> {
465        let elem = unsafe { sys::OSSL_PARAM_locate(self.ptr, key.as_ptr()) };
466        if elem.is_null() {
467            return Err(ErrorStack::drain());
468        }
469        let mut val: u64 = 0;
470        crate::ossl_call!(sys::OSSL_PARAM_get_uint64(
471            elem,
472            std::ptr::addr_of_mut!(val)
473        ))?;
474        Ok(val)
475    }
476
477    /// Locate `key` and read it as BIGNUM, returning big-endian bytes.
478    ///
479    /// # Errors
480    ///
481    /// Returns `Err` if the key is not found or is not a BIGNUM parameter.
482    pub fn get_bn(&self, key: &CStr) -> Result<Vec<u8>, ErrorStack> {
483        let elem = unsafe { sys::OSSL_PARAM_locate(self.ptr, key.as_ptr()) };
484        if elem.is_null() {
485            return Err(ErrorStack::drain());
486        }
487        let mut bn_ptr: *mut sys::BIGNUM = ptr::null_mut();
488        crate::ossl_call!(sys::OSSL_PARAM_get_BN(elem, std::ptr::addr_of_mut!(bn_ptr)))?;
489        let bn = Bn(bn_ptr);
490        Ok(bn.to_bigendian_vec())
491    }
492
493    /// Locate `key` and return a borrowed slice of its octet-string data.
494    ///
495    /// The returned slice is valid for the lifetime of `self`.
496    ///
497    /// # Errors
498    pub fn get_octet_string(&self, key: &CStr) -> Result<&[u8], ErrorStack> {
499        let elem = unsafe { sys::OSSL_PARAM_locate(self.ptr, key.as_ptr()) };
500        if elem.is_null() {
501            return Err(ErrorStack::drain());
502        }
503        let mut p: *const std::os::raw::c_void = ptr::null();
504        let mut len: usize = 0;
505        crate::ossl_call!(sys::OSSL_PARAM_get_octet_string_ptr(
506            elem,
507            std::ptr::addr_of_mut!(p),
508            std::ptr::addr_of_mut!(len),
509        ))?;
510        // SAFETY: p points into the OSSL_PARAM array owned by self; valid for &self lifetime.
511        Ok(unsafe { std::slice::from_raw_parts(p.cast::<u8>(), len) })
512    }
513
514    /// Locate `key` and return a borrowed `CStr` of its UTF-8 string data.
515    ///
516    /// The returned reference is valid for the lifetime of `self`.
517    ///
518    /// # Errors
519    pub fn get_utf8_string(&self, key: &CStr) -> Result<&CStr, ErrorStack> {
520        let elem = unsafe { sys::OSSL_PARAM_locate(self.ptr, key.as_ptr()) };
521        if elem.is_null() {
522            return Err(ErrorStack::drain());
523        }
524        let mut p: *const std::os::raw::c_char = ptr::null();
525        crate::ossl_call!(sys::OSSL_PARAM_get_utf8_string_ptr(
526            elem,
527            std::ptr::addr_of_mut!(p),
528        ))?;
529        // SAFETY: p points into the OSSL_PARAM array owned by self; valid for &self lifetime.
530        Ok(unsafe { CStr::from_ptr(p) })
531    }
532}
533
534// ── Null sentinel ─────────────────────────────────────────────────────────────
535
536/// A const null `OSSL_PARAM` pointer — used to pass `NULL` to OpenSSL functions
537/// that accept an optional `const OSSL_PARAM[]` argument.
538#[must_use]
539pub(crate) fn null_params() -> *const sys::OSSL_PARAM {
540    ptr::null()
541}
542
543// ── Tests ─────────────────────────────────────────────────────────────────────
544
545#[cfg(test)]
546mod tests {
547    use super::*;
548
549    #[test]
550    fn int_round_trip() {
551        let params = ParamBuilder::new()
552            .unwrap()
553            .push_int(c"mykey", 42)
554            .unwrap()
555            .build()
556            .unwrap();
557
558        // Locate the specific OSSL_PARAM element by name, then get its value.
559        let elem = unsafe { sys::OSSL_PARAM_locate(params.as_ptr().cast_mut(), c"mykey".as_ptr()) };
560        assert!(!elem.is_null(), "OSSL_PARAM_locate failed");
561
562        let mut out: i32 = 0;
563        let rc = unsafe { sys::OSSL_PARAM_get_int(elem, std::ptr::addr_of_mut!(out)) };
564        assert_eq!(rc, 1, "OSSL_PARAM_get_int failed");
565        assert_eq!(out, 42);
566    }
567
568    #[test]
569    fn octet_slice_round_trip() {
570        let data = b"hello world";
571        let params = ParamBuilder::new()
572            .unwrap()
573            .push_octet_slice(c"blob", data)
574            .unwrap()
575            .build()
576            .unwrap();
577
578        let elem = unsafe { sys::OSSL_PARAM_locate(params.as_ptr().cast_mut(), c"blob".as_ptr()) };
579        assert!(!elem.is_null());
580
581        let mut p: *const std::os::raw::c_void = ptr::null();
582        let mut len: usize = 0;
583        let rc = unsafe {
584            sys::OSSL_PARAM_get_octet_string_ptr(
585                elem,
586                std::ptr::addr_of_mut!(p),
587                std::ptr::addr_of_mut!(len),
588            )
589        };
590        assert_eq!(rc, 1, "OSSL_PARAM_get_octet_string_ptr failed");
591        assert_eq!(len, data.len());
592        let got = unsafe { std::slice::from_raw_parts(p.cast::<u8>(), len) };
593        assert_eq!(got, data);
594    }
595
596    // push_octet_ptr is verified through KDF integration tests (Phase 6),
597    // where it is used in a complete derive() operation.  Isolated unit tests
598    // for it are unreliable because OSSL_PARAM_BLD_to_param may exhaust the
599    // OpenSSL secure heap in test environments.
600
601    #[test]
602    fn utf8_string_round_trip() {
603        let params = ParamBuilder::new()
604            .unwrap()
605            .push_utf8_string(c"alg", c"SHA-256")
606            .unwrap()
607            .build()
608            .unwrap();
609
610        let elem = unsafe { sys::OSSL_PARAM_locate(params.as_ptr().cast_mut(), c"alg".as_ptr()) };
611        assert!(!elem.is_null());
612
613        let mut out: *const std::os::raw::c_char = ptr::null();
614        let rc = unsafe { sys::OSSL_PARAM_get_utf8_string_ptr(elem, std::ptr::addr_of_mut!(out)) };
615        assert_eq!(rc, 1, "OSSL_PARAM_get_utf8_string_ptr failed");
616        let got = unsafe { CStr::from_ptr(out) };
617        assert_eq!(got.to_bytes(), b"SHA-256");
618    }
619}