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 /// Locate `key` and overwrite its value in place with a new `i32`.
534 ///
535 /// The `Params` array must have been built with `ParamBuilder::push_int` for
536 /// this key so that the element already has storage of the right type and size.
537 /// This is useful after a `get_params` call to propagate changes back, or to
538 /// update a query template before re-issuing it.
539 ///
540 /// # Errors
541 ///
542 /// Returns `Err` if the key is not found or the type is incompatible.
543 pub fn set_int(&mut self, key: &CStr, val: i32) -> Result<(), ErrorStack> {
544 let elem = unsafe { sys::OSSL_PARAM_locate(self.ptr, key.as_ptr()) };
545 if elem.is_null() {
546 return Err(ErrorStack::drain());
547 }
548 crate::ossl_call!(sys::OSSL_PARAM_set_int(elem, val))
549 }
550
551 /// Locate `key` and overwrite its value in place with a new `u32`.
552 ///
553 /// See [`set_int`](Self::set_int) for usage notes.
554 ///
555 /// # Errors
556 ///
557 /// Returns `Err` if the key is not found or the type is incompatible.
558 pub fn set_uint(&mut self, key: &CStr, val: u32) -> Result<(), ErrorStack> {
559 let elem = unsafe { sys::OSSL_PARAM_locate(self.ptr, key.as_ptr()) };
560 if elem.is_null() {
561 return Err(ErrorStack::drain());
562 }
563 crate::ossl_call!(sys::OSSL_PARAM_set_uint(elem, val))
564 }
565
566 /// Locate `key` and overwrite its value in place with a new `usize`.
567 ///
568 /// See [`set_int`](Self::set_int) for usage notes.
569 ///
570 /// # Errors
571 ///
572 /// Returns `Err` if the key is not found or the type is incompatible.
573 pub fn set_size(&mut self, key: &CStr, val: usize) -> Result<(), ErrorStack> {
574 let elem = unsafe { sys::OSSL_PARAM_locate(self.ptr, key.as_ptr()) };
575 if elem.is_null() {
576 return Err(ErrorStack::drain());
577 }
578 crate::ossl_call!(sys::OSSL_PARAM_set_size_t(elem, val))
579 }
580
581 /// Locate `key` and read its value as an `i64`.
582 ///
583 /// Wraps `OSSL_PARAM_get_long`. On LP64 platforms (Linux x86-64) C `long`
584 /// is 64 bits; on LLP64 platforms (Windows) it is 32 bits. The result is
585 /// always widened to `i64`.
586 ///
587 /// # Errors
588 ///
589 /// Returns `Err` if the key is not found or the value cannot be converted.
590 pub fn get_long(&self, key: &CStr) -> Result<i64, ErrorStack> {
591 let elem = unsafe { sys::OSSL_PARAM_locate(self.ptr, key.as_ptr()) };
592 if elem.is_null() {
593 return Err(ErrorStack::drain());
594 }
595 let mut val: std::os::raw::c_long = 0;
596 crate::ossl_call!(sys::OSSL_PARAM_get_long(elem, std::ptr::addr_of_mut!(val)))?;
597 Ok(val as i64)
598 }
599
600 /// Return `true` if the parameter named `key` has been populated by a
601 /// `get_params` call (i.e. `OSSL_PARAM_modified` returns 1).
602 ///
603 /// Returns `false` if the key does not exist in the array or has not been
604 /// written by OpenSSL yet. This is typically used after passing a query
605 /// array to `EVP_PKEY_get_params` or similar to confirm that a particular
606 /// field was actually filled.
607 #[must_use]
608 pub fn was_modified(&self, key: &CStr) -> bool {
609 let elem = unsafe { sys::OSSL_PARAM_locate(self.ptr, key.as_ptr()) };
610 if elem.is_null() {
611 return false;
612 }
613 unsafe { sys::OSSL_PARAM_modified(elem) == 1 }
614 }
615}
616
617// ── Null sentinel ─────────────────────────────────────────────────────────────
618
619/// A const null `OSSL_PARAM` pointer — used to pass `NULL` to OpenSSL functions
620/// that accept an optional `const OSSL_PARAM[]` argument.
621#[must_use]
622pub(crate) fn null_params() -> *const sys::OSSL_PARAM {
623 ptr::null()
624}
625
626// ── Tests ─────────────────────────────────────────────────────────────────────
627
628#[cfg(test)]
629mod tests {
630 use super::*;
631
632 #[test]
633 fn int_round_trip() {
634 let params = ParamBuilder::new()
635 .unwrap()
636 .push_int(c"mykey", 42)
637 .unwrap()
638 .build()
639 .unwrap();
640
641 // Locate the specific OSSL_PARAM element by name, then get its value.
642 let elem = unsafe { sys::OSSL_PARAM_locate(params.as_ptr().cast_mut(), c"mykey".as_ptr()) };
643 assert!(!elem.is_null(), "OSSL_PARAM_locate failed");
644
645 let mut out: i32 = 0;
646 let rc = unsafe { sys::OSSL_PARAM_get_int(elem, std::ptr::addr_of_mut!(out)) };
647 assert_eq!(rc, 1, "OSSL_PARAM_get_int failed");
648 assert_eq!(out, 42);
649 }
650
651 #[test]
652 fn octet_slice_round_trip() {
653 let data = b"hello world";
654 let params = ParamBuilder::new()
655 .unwrap()
656 .push_octet_slice(c"blob", data)
657 .unwrap()
658 .build()
659 .unwrap();
660
661 let elem = unsafe { sys::OSSL_PARAM_locate(params.as_ptr().cast_mut(), c"blob".as_ptr()) };
662 assert!(!elem.is_null());
663
664 let mut p: *const std::os::raw::c_void = ptr::null();
665 let mut len: usize = 0;
666 let rc = unsafe {
667 sys::OSSL_PARAM_get_octet_string_ptr(
668 elem,
669 std::ptr::addr_of_mut!(p),
670 std::ptr::addr_of_mut!(len),
671 )
672 };
673 assert_eq!(rc, 1, "OSSL_PARAM_get_octet_string_ptr failed");
674 assert_eq!(len, data.len());
675 let got = unsafe { std::slice::from_raw_parts(p.cast::<u8>(), len) };
676 assert_eq!(got, data);
677 }
678
679 // push_octet_ptr is verified through KDF integration tests (Phase 6),
680 // where it is used in a complete derive() operation. Isolated unit tests
681 // for it are unreliable because OSSL_PARAM_BLD_to_param may exhaust the
682 // OpenSSL secure heap in test environments.
683
684 #[test]
685 fn params_set_int_roundtrip() {
686 // Build a Params array with an initial int value.
687 let mut params = ParamBuilder::new()
688 .unwrap()
689 .push_int(c"myint", 10)
690 .unwrap()
691 .build()
692 .unwrap();
693
694 // Verify the initial value via the get_int getter.
695 assert_eq!(params.get_int(c"myint").unwrap(), 10);
696
697 // Overwrite the value in place via set_int.
698 params.set_int(c"myint", 99).expect("set_int failed");
699
700 // Confirm the new value is visible.
701 assert_eq!(params.get_int(c"myint").unwrap(), 99);
702 }
703
704 #[test]
705 fn params_was_modified() {
706 use crate::pkey::{KeygenCtx, Pkey, Private};
707
708 // Generate a small RSA key so we have a real Pkey to query against.
709 let key: Pkey<Private> = {
710 let mut ctx = KeygenCtx::new(c"RSA").expect("KeygenCtx::new");
711 let bits = ParamBuilder::new()
712 .unwrap()
713 .push_uint(c"bits", 512)
714 .unwrap()
715 .build()
716 .unwrap();
717 ctx.set_params(&bits).expect("set_params bits");
718 ctx.generate().expect("generate")
719 };
720
721 // Query "bits" (key size as uint) — a uint param avoids the BN type
722 // mismatch that octet_slice causes for BN params like "e" or "n".
723 let mut query = ParamBuilder::new()
724 .unwrap()
725 .push_uint(c"bits", 0)
726 .unwrap()
727 .build()
728 .unwrap();
729
730 assert!(!query.was_modified(c"bits"), "not yet modified");
731
732 // After get_params, OpenSSL fills the element: was_modified returns true.
733 key.get_params(&mut query).expect("get_params");
734 assert!(
735 query.was_modified(c"bits"),
736 "should be modified after get_params"
737 );
738
739 // A key that does not exist returns false regardless.
740 assert!(!query.was_modified(c"no_such_key"));
741 }
742
743 #[test]
744 fn utf8_string_round_trip() {
745 let params = ParamBuilder::new()
746 .unwrap()
747 .push_utf8_string(c"alg", c"SHA-256")
748 .unwrap()
749 .build()
750 .unwrap();
751
752 let elem = unsafe { sys::OSSL_PARAM_locate(params.as_ptr().cast_mut(), c"alg".as_ptr()) };
753 assert!(!elem.is_null());
754
755 let mut out: *const std::os::raw::c_char = ptr::null();
756 let rc = unsafe { sys::OSSL_PARAM_get_utf8_string_ptr(elem, std::ptr::addr_of_mut!(out)) };
757 assert_eq!(rc, 1, "OSSL_PARAM_get_utf8_string_ptr failed");
758 let got = unsafe { CStr::from_ptr(out) };
759 assert_eq!(got.to_bytes(), b"SHA-256");
760 }
761}