From fa07db2cc67881b49cf7450ca75080fa9cca50c7 Mon Sep 17 00:00:00 2001 From: Josh Holtrop Date: Thu, 26 Feb 2026 14:54:49 -0500 Subject: [PATCH] Rust wrapper: add lms module --- wrapper/rust/wolfssl-wolfcrypt/build.rs | 6 + wrapper/rust/wolfssl-wolfcrypt/headers.h | 1 + wrapper/rust/wolfssl-wolfcrypt/src/lib.rs | 1 + wrapper/rust/wolfssl-wolfcrypt/src/lms.rs | 785 ++++++++++++++++++ .../rust/wolfssl-wolfcrypt/tests/test_lms.rs | 470 +++++++++++ 5 files changed, 1263 insertions(+) create mode 100644 wrapper/rust/wolfssl-wolfcrypt/src/lms.rs create mode 100644 wrapper/rust/wolfssl-wolfcrypt/tests/test_lms.rs diff --git a/wrapper/rust/wolfssl-wolfcrypt/build.rs b/wrapper/rust/wolfssl-wolfcrypt/build.rs index 5212682573..f4949451ee 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/build.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/build.rs @@ -331,6 +331,12 @@ fn scan_cfg() -> Result<()> { /* mlkem / ML-KEM */ check_cfg(&binding, "wc_MlKemKey_Init", "mlkem"); + /* lms / HSS */ + check_cfg(&binding, "wc_LmsKey_Init", "lms"); + check_cfg(&binding, "wc_LmsKey_MakeKey", "lms_make_key"); + check_cfg(&binding, "wc_LmsParm_WC_LMS_PARM_L1_H5_W1", "lms_sha256_256"); + check_cfg(&binding, "wc_LmsParm_WC_LMS_PARM_SHA256_192_L1_H5_W1", "lms_sha256_192"); + /* sha */ check_cfg(&binding, "wc_InitSha", "sha"); check_cfg(&binding, "wc_InitSha224", "sha224"); diff --git a/wrapper/rust/wolfssl-wolfcrypt/headers.h b/wrapper/rust/wolfssl-wolfcrypt/headers.h index 9ba97a48d9..6db82daa5e 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/headers.h +++ b/wrapper/rust/wolfssl-wolfcrypt/headers.h @@ -21,3 +21,4 @@ #include "wolfssl/wolfcrypt/pwdbased.h" #include "wolfssl/wolfcrypt/dilithium.h" #include "wolfssl/wolfcrypt/mlkem.h" +#include "wolfssl/wolfcrypt/wc_lms.h" diff --git a/wrapper/rust/wolfssl-wolfcrypt/src/lib.rs b/wrapper/rust/wolfssl-wolfcrypt/src/lib.rs index a2c4f4dd6a..7f1d1e3f96 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/src/lib.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/src/lib.rs @@ -37,6 +37,7 @@ pub mod fips; pub mod hkdf; pub mod hmac; pub mod kdf; +pub mod lms; pub mod mlkem; pub mod prf; pub mod random; diff --git a/wrapper/rust/wolfssl-wolfcrypt/src/lms.rs b/wrapper/rust/wolfssl-wolfcrypt/src/lms.rs new file mode 100644 index 0000000000..dd12c2409e --- /dev/null +++ b/wrapper/rust/wolfssl-wolfcrypt/src/lms.rs @@ -0,0 +1,785 @@ +/* + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/*! +This module provides a Rust wrapper for the wolfCrypt library's LMS/HSS +(Leighton-Micali Signature / Hierarchical Signature System) post-quantum +hash-based signature functionality (RFC 8554). + +The primary component is the [`Lms`] struct, which manages the lifecycle of +a wolfSSL `LmsKey` object. It ensures proper initialization and deallocation. + +LMS/HSS signatures are controlled by three parameters: + +| Parameter | Valid Values | Effect | +|--------------|-----------------------|---------------------------------| +| `levels` | 1..8 | Number of Merkle tree levels | +| `height` | 5, 10, 15, 20, 25 | Height of each Merkle tree | +| `winternitz` | 1, 2, 4, 8 | Bits per Winternitz chain step | + +The total number of available signatures is `2^(levels * height)`. +Larger `winternitz` values reduce signature size at the cost of slower +key generation and signing. + +Predefined parameter sets are available as `Lms::PARM_*` constants +(e.g., `Lms::PARM_L1_H5_W4`), or parameters can be set directly via +[`Lms::set_parameters()`]. + +Signing requires private key I/O callbacks to persist the evolving private +key state. Register callbacks with [`Lms::set_write_cb()`] and +[`Lms::set_read_cb()`] before calling [`Lms::make_key()`] or +[`Lms::reload()`]. These methods are absent when wolfCrypt is built with +`WOLFSSL_LMS_VERIFY_ONLY`. + +# Examples + +```rust,no_run +#[cfg(all(lms, lms_make_key, random))] +{ +use wolfssl_wolfcrypt::random::RNG; +use wolfssl_wolfcrypt::lms::Lms; +use wolfssl_wolfcrypt::sys; + +let mut rng = RNG::new().expect("RNG creation failed"); +let mut key = Lms::new().expect("Lms::new() failed"); + +// Use a small, fast parameter set for demonstration. +key.set_parm(Lms::PARM_L1_H5_W8).expect("set_parm failed"); + +// The private key I/O callbacks must be registered before making a key. +// (Omitted here for brevity; see set_write_cb / set_read_cb.) + +let sig_len = key.get_sig_len().expect("get_sig_len failed"); +let pub_len = key.get_pub_len().expect("get_pub_len failed"); +let mut sig = vec![0u8; sig_len]; +let mut pub_buf = vec![0u8; pub_len]; + +key.make_key(&mut rng).expect("make_key failed"); +key.sign(b"hello", &mut sig).expect("sign failed"); +key.export_pub_raw(&mut pub_buf).expect("export_pub_raw failed"); + +let mut vkey = Lms::new().expect("Lms::new() for verify failed"); +vkey.set_parm(Lms::PARM_L1_H5_W8).expect("set_parm failed"); +vkey.import_pub_raw(&pub_buf).expect("import_pub_raw failed"); +vkey.verify(&sig, b"hello").expect("verify failed"); +} +``` +*/ + +#![cfg(lms)] + +use crate::sys; +use core::mem::MaybeUninit; +#[cfg(all(lms_make_key, random))] +use crate::random::RNG; + +/// Rust wrapper for a wolfSSL `LmsKey` object (LMS/HSS, RFC 8554). +/// +/// Manages the lifecycle of the underlying key, including initialization and +/// deallocation via the [`Drop`] trait. +/// +/// An instance is created with [`Lms::new()`] or [`Lms::new_ex()`]. +/// Parameters must be set with [`Lms::set_parm()`] or +/// [`Lms::set_parameters()`] before generating or importing a key. +/// +/// When `WOLFSSL_LMS_VERIFY_ONLY` is **not** set, private-key callbacks must +/// be registered (see [`Lms::set_write_cb()`] and [`Lms::set_read_cb()`]) +/// before calling [`Lms::make_key()`] or [`Lms::reload()`]. +pub struct Lms { + ws_key: sys::LmsKey, +} + +#[cfg(lms_sha256_256)] +impl Lms { + pub const PARM_NONE: u32 = sys::wc_LmsParm_WC_LMS_PARM_NONE; + pub const PARM_L1_H5_W1: u32 = sys::wc_LmsParm_WC_LMS_PARM_L1_H5_W1; + pub const PARM_L1_H5_W2: u32 = sys::wc_LmsParm_WC_LMS_PARM_L1_H5_W2; + pub const PARM_L1_H5_W4: u32 = sys::wc_LmsParm_WC_LMS_PARM_L1_H5_W4; + pub const PARM_L1_H5_W8: u32 = sys::wc_LmsParm_WC_LMS_PARM_L1_H5_W8; + pub const PARM_L1_H10_W2: u32 = sys::wc_LmsParm_WC_LMS_PARM_L1_H10_W2; + pub const PARM_L1_H10_W4: u32 = sys::wc_LmsParm_WC_LMS_PARM_L1_H10_W4; + pub const PARM_L1_H10_W8: u32 = sys::wc_LmsParm_WC_LMS_PARM_L1_H10_W8; + pub const PARM_L1_H15_W2: u32 = sys::wc_LmsParm_WC_LMS_PARM_L1_H15_W2; + pub const PARM_L1_H15_W4: u32 = sys::wc_LmsParm_WC_LMS_PARM_L1_H15_W4; + pub const PARM_L1_H15_W8: u32 = sys::wc_LmsParm_WC_LMS_PARM_L1_H15_W8; + pub const PARM_L1_H20_W2: u32 = sys::wc_LmsParm_WC_LMS_PARM_L1_H20_W2; + pub const PARM_L1_H20_W4: u32 = sys::wc_LmsParm_WC_LMS_PARM_L1_H20_W4; + pub const PARM_L1_H20_W8: u32 = sys::wc_LmsParm_WC_LMS_PARM_L1_H20_W8; + pub const PARM_L2_H5_W2: u32 = sys::wc_LmsParm_WC_LMS_PARM_L2_H5_W2; + pub const PARM_L2_H5_W4: u32 = sys::wc_LmsParm_WC_LMS_PARM_L2_H5_W4; + pub const PARM_L2_H5_W8: u32 = sys::wc_LmsParm_WC_LMS_PARM_L2_H5_W8; + pub const PARM_L2_H10_W2: u32 = sys::wc_LmsParm_WC_LMS_PARM_L2_H10_W2; + pub const PARM_L2_H10_W4: u32 = sys::wc_LmsParm_WC_LMS_PARM_L2_H10_W4; + pub const PARM_L2_H10_W8: u32 = sys::wc_LmsParm_WC_LMS_PARM_L2_H10_W8; + pub const PARM_L2_H15_W2: u32 = sys::wc_LmsParm_WC_LMS_PARM_L2_H15_W2; + pub const PARM_L2_H15_W4: u32 = sys::wc_LmsParm_WC_LMS_PARM_L2_H15_W4; + pub const PARM_L2_H15_W8: u32 = sys::wc_LmsParm_WC_LMS_PARM_L2_H15_W8; + pub const PARM_L2_H20_W2: u32 = sys::wc_LmsParm_WC_LMS_PARM_L2_H20_W2; + pub const PARM_L2_H20_W4: u32 = sys::wc_LmsParm_WC_LMS_PARM_L2_H20_W4; + pub const PARM_L2_H20_W8: u32 = sys::wc_LmsParm_WC_LMS_PARM_L2_H20_W8; + pub const PARM_L3_H5_W2: u32 = sys::wc_LmsParm_WC_LMS_PARM_L3_H5_W2; + pub const PARM_L3_H5_W4: u32 = sys::wc_LmsParm_WC_LMS_PARM_L3_H5_W4; + pub const PARM_L3_H5_W8: u32 = sys::wc_LmsParm_WC_LMS_PARM_L3_H5_W8; + pub const PARM_L3_H10_W4: u32 = sys::wc_LmsParm_WC_LMS_PARM_L3_H10_W4; + pub const PARM_L3_H10_W8: u32 = sys::wc_LmsParm_WC_LMS_PARM_L3_H10_W8; + pub const PARM_L4_H5_W2: u32 = sys::wc_LmsParm_WC_LMS_PARM_L4_H5_W2; + pub const PARM_L4_H5_W4: u32 = sys::wc_LmsParm_WC_LMS_PARM_L4_H5_W4; + pub const PARM_L4_H5_W8: u32 = sys::wc_LmsParm_WC_LMS_PARM_L4_H5_W8; + pub const PARM_L4_H10_W4: u32 = sys::wc_LmsParm_WC_LMS_PARM_L4_H10_W4; + pub const PARM_L4_H10_W8: u32 = sys::wc_LmsParm_WC_LMS_PARM_L4_H10_W8; +} + +#[cfg(lms_sha256_192)] +impl Lms { + pub const PARM_SHA256_192_L1_H5_W1 : u32 = sys::wc_LmsParm_WC_LMS_PARM_SHA256_192_L1_H5_W1; + pub const PARM_SHA256_192_L1_H5_W2 : u32 = sys::wc_LmsParm_WC_LMS_PARM_SHA256_192_L1_H5_W2; + pub const PARM_SHA256_192_L1_H5_W4 : u32 = sys::wc_LmsParm_WC_LMS_PARM_SHA256_192_L1_H5_W4; + pub const PARM_SHA256_192_L1_H5_W8 : u32 = sys::wc_LmsParm_WC_LMS_PARM_SHA256_192_L1_H5_W8; + pub const PARM_SHA256_192_L1_H10_W2: u32 = sys::wc_LmsParm_WC_LMS_PARM_SHA256_192_L1_H10_W2; + pub const PARM_SHA256_192_L1_H10_W4: u32 = sys::wc_LmsParm_WC_LMS_PARM_SHA256_192_L1_H10_W4; + pub const PARM_SHA256_192_L1_H10_W8: u32 = sys::wc_LmsParm_WC_LMS_PARM_SHA256_192_L1_H10_W8; + pub const PARM_SHA256_192_L1_H15_W2: u32 = sys::wc_LmsParm_WC_LMS_PARM_SHA256_192_L1_H15_W2; + pub const PARM_SHA256_192_L1_H15_W4: u32 = sys::wc_LmsParm_WC_LMS_PARM_SHA256_192_L1_H15_W4; + pub const PARM_SHA256_192_L1_H20_W2: u32 = sys::wc_LmsParm_WC_LMS_PARM_SHA256_192_L1_H20_W2; + pub const PARM_SHA256_192_L1_H20_W4: u32 = sys::wc_LmsParm_WC_LMS_PARM_SHA256_192_L1_H20_W4; + pub const PARM_SHA256_192_L1_H20_W8: u32 = sys::wc_LmsParm_WC_LMS_PARM_SHA256_192_L1_H20_W8; + pub const PARM_SHA256_192_L2_H10_W2: u32 = sys::wc_LmsParm_WC_LMS_PARM_SHA256_192_L2_H10_W2; + pub const PARM_SHA256_192_L2_H10_W4: u32 = sys::wc_LmsParm_WC_LMS_PARM_SHA256_192_L2_H10_W4; + pub const PARM_SHA256_192_L2_H10_W8: u32 = sys::wc_LmsParm_WC_LMS_PARM_SHA256_192_L2_H10_W8; + pub const PARM_SHA256_192_L3_H5_W2 : u32 = sys::wc_LmsParm_WC_LMS_PARM_SHA256_192_L3_H5_W2; + pub const PARM_SHA256_192_L3_H5_W4 : u32 = sys::wc_LmsParm_WC_LMS_PARM_SHA256_192_L3_H5_W4; + pub const PARM_SHA256_192_L3_H5_W8 : u32 = sys::wc_LmsParm_WC_LMS_PARM_SHA256_192_L3_H5_W8; + pub const PARM_SHA256_192_L3_H10_W4: u32 = sys::wc_LmsParm_WC_LMS_PARM_SHA256_192_L3_H10_W4; + pub const PARM_SHA256_192_L4_H5_W8 : u32 = sys::wc_LmsParm_WC_LMS_PARM_SHA256_192_L4_H5_W8; +} + +impl Lms { + /// Length of the LMS Key ID (`WC_LMS_I_LEN` = 16 bytes). + pub const KEY_ID_LEN: usize = sys::WC_LMS_I_LEN as usize; + + /// Create and initialize a new `Lms` key instance. + /// + /// # Returns + /// + /// Returns either Ok(Lms) containing the key instance or Err(e) + /// containing the wolfSSL library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(lms)] + /// { + /// use wolfssl_wolfcrypt::lms::Lms; + /// let key = Lms::new().expect("Error with Lms::new()"); + /// } + /// ``` + pub fn new() -> Result { + Self::new_ex(None, None) + } + + /// Create and initialize a new `Lms` key instance with optional heap hint + /// and device ID. + /// + /// # Parameters + /// + /// * `heap`: Optional heap hint. + /// * `dev_id`: Optional device ID for crypto callbacks or async hardware. + /// + /// # Returns + /// + /// Returns either Ok(Lms) containing the key instance or Err(e) + /// containing the wolfSSL library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(lms)] + /// { + /// use wolfssl_wolfcrypt::lms::Lms; + /// let key = Lms::new_ex(None, None).expect("Error with Lms::new_ex()"); + /// } + /// ``` + pub fn new_ex( + heap: Option<*mut core::ffi::c_void>, + dev_id: Option, + ) -> Result { + let heap = match heap { + Some(h) => h, + None => core::ptr::null_mut(), + }; + let dev_id = match dev_id { + Some(id) => id, + None => sys::INVALID_DEVID, + }; + let mut ws_key: MaybeUninit = MaybeUninit::uninit(); + let rc = unsafe { sys::wc_LmsKey_Init(ws_key.as_mut_ptr(), heap, dev_id) }; + if rc != 0 { + return Err(rc); + } + let ws_key = unsafe { ws_key.assume_init() }; + let lms = Lms { ws_key }; + Ok(lms) + } + + /// Set parameters using a predefined `wc_LmsParm` parameter set. + /// + /// Use `Lms::PARM_*` constants for the `parm` value (e.g., + /// `Lms::PARM_L1_H5_W8`). + /// + /// # Parameters + /// + /// * `parm`: A `Lms::PARM_*` value identifying the parameter set. + /// + /// # Returns + /// + /// Returns either Ok(()) on success or Err(e) containing the wolfSSL + /// library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(lms)] + /// { + /// use wolfssl_wolfcrypt::lms::Lms; + /// let mut key = Lms::new().expect("Error with Lms::new()"); + /// key.set_parm(Lms::PARM_L1_H5_W8).expect("Error with set_parm()"); + /// } + /// ``` + pub fn set_parm(&mut self, parm: u32) -> Result<(), i32> { + let rc = unsafe { sys::wc_LmsKey_SetLmsParm(&mut self.ws_key, parm) }; + if rc != 0 { + return Err(rc); + } + Ok(()) + } + + /// Set LMS/HSS parameters directly by levels, height, and Winternitz factor. + /// + /// # Parameters + /// + /// * `levels`: Number of Merkle tree levels (1..8). + /// * `height`: Height of each Merkle tree (5, 10, 15, 20, or 25). + /// * `winternitz`: Winternitz factor (1, 2, 4, or 8). + /// + /// # Returns + /// + /// Returns either Ok(()) on success or Err(e) containing the wolfSSL + /// library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(lms)] + /// { + /// use wolfssl_wolfcrypt::lms::Lms; + /// let mut key = Lms::new().expect("Error with Lms::new()"); + /// key.set_parameters(1, 5, 8).expect("Error with set_parameters()"); + /// } + /// ``` + pub fn set_parameters(&mut self, levels: i32, height: i32, winternitz: i32) -> Result<(), i32> { + let rc = unsafe { + sys::wc_LmsKey_SetParameters(&mut self.ws_key, levels, height, winternitz) + }; + if rc != 0 { + return Err(rc); + } + Ok(()) + } + + /// Get the current LMS/HSS parameter values. + /// + /// # Returns + /// + /// Returns either Ok((levels, height, winternitz)) on success or Err(e) + /// containing the wolfSSL library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(lms)] + /// { + /// use wolfssl_wolfcrypt::lms::Lms; + /// let mut key = Lms::new().expect("Error with Lms::new()"); + /// key.set_parameters(1, 5, 8).expect("Error with set_parameters()"); + /// let (levels, height, winternitz) = key.get_parameters() + /// .expect("Error with get_parameters()"); + /// assert_eq!((levels, height, winternitz), (1, 5, 8)); + /// } + /// ``` + pub fn get_parameters(&self) -> Result<(i32, i32, i32), i32> { + let mut levels = 0i32; + let mut height = 0i32; + let mut winternitz = 0i32; + let rc = unsafe { + sys::wc_LmsKey_GetParameters( + &self.ws_key, + &mut levels, + &mut height, + &mut winternitz, + ) + }; + if rc != 0 { + return Err(rc); + } + Ok((levels, height, winternitz)) + } + + /// Register a callback to write (persist) the private key. + /// + /// The callback is called by [`Lms::make_key()`] and [`Lms::sign()`] + /// whenever the private key state must be saved to non-volatile storage. + /// + /// # Parameters + /// + /// * `write_cb`: Callback function of type + /// `wc_lms_write_private_key_cb`. + /// + /// # Returns + /// + /// Returns either Ok(()) on success or Err(e) containing the wolfSSL + /// library error code value. + #[cfg(lms_make_key)] + pub fn set_write_cb(&mut self, write_cb: sys::wc_lms_write_private_key_cb) -> Result<(), i32> { + let rc = unsafe { sys::wc_LmsKey_SetWriteCb(&mut self.ws_key, write_cb) }; + if rc != 0 { + return Err(rc); + } + Ok(()) + } + + /// Register a callback to read (restore) the private key. + /// + /// The callback is called by [`Lms::reload()`] and [`Lms::sign()`] + /// whenever the private key must be read from non-volatile storage. + /// + /// # Parameters + /// + /// * `read_cb`: Callback function of type `wc_lms_read_private_key_cb`. + /// + /// # Returns + /// + /// Returns either Ok(()) on success or Err(e) containing the wolfSSL + /// library error code value. + #[cfg(lms_make_key)] + pub fn set_read_cb(&mut self, read_cb: sys::wc_lms_read_private_key_cb) -> Result<(), i32> { + let rc = unsafe { sys::wc_LmsKey_SetReadCb(&mut self.ws_key, read_cb) }; + if rc != 0 { + return Err(rc); + } + Ok(()) + } + + /// Set the context pointer passed to the private key I/O callbacks. + /// + /// # Parameters + /// + /// * `context`: An arbitrary pointer passed unchanged to the write and + /// read callbacks. + /// + /// # Returns + /// + /// Returns either Ok(()) on success or Err(e) containing the wolfSSL + /// library error code value. + /// + /// # Safety + /// + /// The caller must ensure `context` remains valid for as long as the key + /// may invoke the registered write or read callbacks (i.e., across calls + /// to [`Lms::make_key()`], [`Lms::reload()`], and [`Lms::sign()`]). + #[cfg(lms_make_key)] + pub unsafe fn set_context(&mut self, context: *mut core::ffi::c_void) -> Result<(), i32> { + let rc = unsafe { sys::wc_LmsKey_SetContext(&mut self.ws_key, context) }; + if rc != 0 { + return Err(rc); + } + Ok(()) + } + + /// Generate a new LMS/HSS key pair using an RNG. + /// + /// Parameters must be set with [`Lms::set_parm()`] or + /// [`Lms::set_parameters()`] and private key callbacks must be + /// registered with [`Lms::set_write_cb()`] and [`Lms::set_read_cb()`] + /// before calling this function. + /// + /// # Parameters + /// + /// * `rng`: `RNG` instance to use for random number generation. + /// + /// # Returns + /// + /// Returns either Ok(()) on success or Err(e) containing the wolfSSL + /// library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(lms, lms_make_key, random))] + /// { + /// use wolfssl_wolfcrypt::random::RNG; + /// use wolfssl_wolfcrypt::lms::Lms; + /// let mut rng = RNG::new().expect("Error creating RNG"); + /// let mut key = Lms::new().expect("Error with Lms::new()"); + /// key.set_parm(Lms::PARM_L1_H5_W8).expect("Error with set_parm()"); + /// // Register write/read callbacks before calling make_key(). + /// // key.make_key(&mut rng).expect("Error with make_key()"); + /// } + /// ``` + #[cfg(all(lms_make_key, random))] + pub fn make_key(&mut self, rng: &mut RNG) -> Result<(), i32> { + let rc = unsafe { sys::wc_LmsKey_MakeKey(&mut self.ws_key, &mut rng.wc_rng) }; + if rc != 0 { + return Err(rc); + } + Ok(()) + } + + /// Reload a previously generated LMS/HSS key from non-volatile storage. + /// + /// The read callback registered with [`Lms::set_read_cb()`] is called + /// to restore the private key state. Parameters must have been set and + /// the read callback registered before calling this function. + /// + /// # Returns + /// + /// Returns either Ok(()) on success or Err(e) containing the wolfSSL + /// library error code value. + #[cfg(lms_make_key)] + pub fn reload(&mut self) -> Result<(), i32> { + let rc = unsafe { sys::wc_LmsKey_Reload(&mut self.ws_key) }; + if rc != 0 { + return Err(rc); + } + Ok(()) + } + + /// Get the encoded private key length in bytes. + /// + /// # Returns + /// + /// Returns either Ok(length) on success or Err(e) containing the wolfSSL + /// library error code value. + #[cfg(lms_make_key)] + pub fn get_priv_len(&self) -> Result { + let mut len = 0u32; + let rc = unsafe { sys::wc_LmsKey_GetPrivLen(&self.ws_key, &mut len) }; + if rc != 0 { + return Err(rc); + } + Ok(len as usize) + } + + /// Sign a message with this LMS/HSS private key. + /// + /// The `sig` buffer must be at least `get_sig_len()` bytes. The write + /// callback is invoked to persist the updated private key state after + /// signing. + /// + /// # Parameters + /// + /// * `msg`: Message bytes to sign. + /// * `sig`: Output buffer for the signature. Must be at least + /// `get_sig_len()` bytes. + /// + /// # Returns + /// + /// Returns either Ok(sig_len) containing the number of bytes written to + /// `sig` on success, or Err(e) containing the wolfSSL library error code. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(lms, lms_make_key, random))] + /// { + /// use wolfssl_wolfcrypt::random::RNG; + /// use wolfssl_wolfcrypt::lms::Lms; + /// let mut rng = RNG::new().expect("Error creating RNG"); + /// let mut key = Lms::new().expect("Error with Lms::new()"); + /// key.set_parm(Lms::PARM_L1_H5_W8).expect("Error with set_parm()"); + /// // Register callbacks and make key first (omitted for brevity). + /// // let sig_len = key.get_sig_len().unwrap(); + /// // let mut sig = vec![0u8; sig_len]; + /// // let written = key.sign(b"hello", &mut sig).expect("Error with sign()"); + /// // assert_eq!(written, sig_len); + /// } + /// ``` + #[cfg(lms_make_key)] + pub fn sign(&mut self, msg: &[u8], sig: &mut [u8]) -> Result { + // wc_LmsKey_Sign treats sigSz as write-only: it writes sig_len bytes + // to sig without reading or checking *sigSz beforehand. Validate here. + let expected_sig_len = self.get_sig_len()?; + if sig.len() < expected_sig_len { + return Err(sys::wolfCrypt_ErrorCodes_BUFFER_E); + } + let mut sig_sz = sig.len() as u32; + let rc = unsafe { + sys::wc_LmsKey_Sign( + &mut self.ws_key, + sig.as_mut_ptr(), + &mut sig_sz, + msg.as_ptr(), + msg.len() as core::ffi::c_int, + ) + }; + if rc != 0 { + return Err(rc); + } + Ok(sig_sz as usize) + } + + /// Return the number of signatures remaining for this key. + /// + /// Returns `Ok(true)` if at least one signature remains, `Ok(false)` if + /// exhausted, or `Err(e)` on error. This is a conservative check only. + /// + /// # Returns + /// + /// Returns either Ok(count) on success or Err(e) containing the wolfSSL + /// library error code value. + #[cfg(lms_make_key)] + pub fn sigs_left(&mut self) -> Result { + let rc = unsafe { sys::wc_LmsKey_SigsLeft(&mut self.ws_key) }; + if rc < 0 { + return Err(rc); + } + Ok(rc != 0) + } + + /// Get the signature length in bytes for this key's parameters. + /// + /// # Returns + /// + /// Returns either Ok(length) on success or Err(e) containing the wolfSSL + /// library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(lms)] + /// { + /// use wolfssl_wolfcrypt::lms::Lms; + /// let mut key = Lms::new().expect("Error with Lms::new()"); + /// key.set_parm(Lms::PARM_L1_H5_W8).expect("Error with set_parm()"); + /// let sig_len = key.get_sig_len().expect("Error with get_sig_len()"); + /// assert!(sig_len > 0); + /// } + /// ``` + pub fn get_sig_len(&self) -> Result { + let mut len = 0u32; + let rc = unsafe { sys::wc_LmsKey_GetSigLen(&self.ws_key, &mut len) }; + if rc != 0 { + return Err(rc); + } + Ok(len as usize) + } + + /// Get the public key length in bytes for this key's parameters. + /// + /// # Returns + /// + /// Returns either Ok(length) on success or Err(e) containing the wolfSSL + /// library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(lms)] + /// { + /// use wolfssl_wolfcrypt::lms::Lms; + /// let mut key = Lms::new().expect("Error with Lms::new()"); + /// key.set_parm(Lms::PARM_L1_H5_W8).expect("Error with set_parm()"); + /// let pub_len = key.get_pub_len().expect("Error with get_pub_len()"); + /// assert!(pub_len > 0); + /// } + /// ``` + pub fn get_pub_len(&self) -> Result { + let mut len = 0u32; + let rc = unsafe { sys::wc_LmsKey_GetPubLen(&self.ws_key, &mut len) }; + if rc != 0 { + return Err(rc); + } + Ok(len as usize) + } + + /// Copy the public key from `src` into this key instance. + /// + /// Both keys must have matching parameters. After a successful call, + /// this key can be used for verification. + /// + /// # Parameters + /// + /// * `src`: Source key to copy the public portion from. + /// + /// # Returns + /// + /// Returns either Ok(()) on success or Err(e) containing the wolfSSL + /// library error code value. + pub fn export_pub_from(&mut self, src: &Lms) -> Result<(), i32> { + let rc = unsafe { + sys::wc_LmsKey_ExportPub(&mut self.ws_key, &src.ws_key) + }; + if rc != 0 { + return Err(rc); + } + Ok(()) + } + + /// Export the raw public key bytes into `out`. + /// + /// The `out` buffer must be at least `get_pub_len()` bytes. + /// + /// # Parameters + /// + /// * `out`: Output buffer for the raw public key. Must be at least + /// `get_pub_len()` bytes. + /// + /// # Returns + /// + /// Returns either Ok(length) containing the number of bytes written on + /// success, or Err(e) containing the wolfSSL library error code. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(lms, lms_make_key, random))] + /// { + /// use wolfssl_wolfcrypt::lms::Lms; + /// let mut key = Lms::new().expect("Error with Lms::new()"); + /// key.set_parm(Lms::PARM_L1_H5_W8).expect("Error with set_parm()"); + /// // After make_key(): + /// // let pub_len = key.get_pub_len().unwrap(); + /// // let mut pub_buf = vec![0u8; pub_len]; + /// // let written = key.export_pub_raw(&mut pub_buf).unwrap(); + /// } + /// ``` + pub fn export_pub_raw(&self, out: &mut [u8]) -> Result { + let mut out_len = out.len() as u32; + let rc = unsafe { + sys::wc_LmsKey_ExportPubRaw(&self.ws_key, out.as_mut_ptr(), &mut out_len) + }; + if rc != 0 { + return Err(rc); + } + Ok(out_len as usize) + } + + /// Import a raw public key from `data` into this key instance. + /// + /// Parameters **must** be set with [`Lms::set_parm()`] or + /// [`Lms::set_parameters()`] before calling this function, and the + /// length of `data` must match `get_pub_len()`. After a successful + /// import the key can be used for verification. + /// + /// # Parameters + /// + /// * `data`: Buffer containing the raw public key bytes. + /// + /// # Returns + /// + /// Returns either Ok(()) on success or Err(e) containing the wolfSSL + /// library error code value. + /// + /// # Example + /// + /// ```rust,ignore + /// let mut key = Lms::new().expect("Error with Lms::new()"); + /// key.set_parm(Lms::PARM_L1_H5_W8).expect("set_parm failed"); + /// key.import_pub_raw(&pub_buf).expect("Error with import_pub_raw()"); + /// ``` + pub fn import_pub_raw(&mut self, data: &[u8]) -> Result<(), i32> { + let rc = unsafe { + sys::wc_LmsKey_ImportPubRaw(&mut self.ws_key, data.as_ptr(), data.len() as u32) + }; + if rc != 0 { + return Err(rc); + } + Ok(()) + } + + /// Verify an LMS/HSS signature over a message. + /// + /// The key must have a public key loaded (via [`Lms::make_key()`], + /// [`Lms::reload()`], or [`Lms::import_pub_raw()`]). + /// + /// # Parameters + /// + /// * `sig`: Signature bytes to verify. + /// * `msg`: Message bytes that were signed. + /// + /// # Returns + /// + /// Returns either Ok(()) if the signature is valid, or Err(e) containing + /// the wolfSSL library error code value (including a verification failure + /// code). + pub fn verify(&mut self, sig: &[u8], msg: &[u8]) -> Result<(), i32> { + // wc_lms.c validates sigSz, but ext_lms.c passes sigSz through to + // hss_validate_signature without checking it. Validate here for both. + let expected_sig_len = self.get_sig_len()?; + if sig.len() != expected_sig_len { + return Err(sys::wolfCrypt_ErrorCodes_BUFFER_E); + } + let rc = unsafe { + sys::wc_LmsKey_Verify( + &mut self.ws_key, + sig.as_ptr(), + sig.len() as u32, + msg.as_ptr(), + msg.len() as core::ffi::c_int, + ) + }; + if rc != 0 { + return Err(rc); + } + Ok(()) + } + + /// Get the Key ID (I value) for this LMS/HSS key. + /// + /// Returns a slice pointing into the key's internal storage. + /// + /// # Returns + /// + /// Returns either Ok(&[u8]) containing the key ID on success, or Err(e) + /// containing the wolfSSL library error code value. + pub fn get_kid(&mut self) -> Result<&[u8], i32> { + let mut kid_ptr: *const u8 = core::ptr::null(); + let mut kid_sz: u32 = 0; + let rc = unsafe { + sys::wc_LmsKey_GetKid(&mut self.ws_key, &mut kid_ptr, &mut kid_sz) + }; + if rc != 0 { + return Err(rc); + } + let slice = unsafe { core::slice::from_raw_parts(kid_ptr, kid_sz as usize) }; + Ok(slice) + } +} + +impl Drop for Lms { + /// Safely free the underlying wolfSSL LMS/HSS key context. + /// + /// This calls `wc_LmsKey_Free()`. The Rust Drop trait guarantees this + /// is called when the `Lms` struct goes out of scope. + fn drop(&mut self) { + unsafe { + sys::wc_LmsKey_Free(&mut self.ws_key); + } + } +} diff --git a/wrapper/rust/wolfssl-wolfcrypt/tests/test_lms.rs b/wrapper/rust/wolfssl-wolfcrypt/tests/test_lms.rs new file mode 100644 index 0000000000..16a51c3c9e --- /dev/null +++ b/wrapper/rust/wolfssl-wolfcrypt/tests/test_lms.rs @@ -0,0 +1,470 @@ +/* + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#![cfg(lms)] + +mod common; + +use wolfssl_wolfcrypt::lms::Lms; +use wolfssl_wolfcrypt::sys; +#[cfg(all(lms_make_key, random))] +use wolfssl_wolfcrypt::random::RNG; + +/// Private key NV storage for tests that require make_key / sign / reload. +/// +/// The write and read callbacks use a raw pointer to this struct as the +/// context argument. Each test that needs persistent NV storage creates its +/// own instance and boxes it to keep it alive for the duration of the test. +#[cfg(lms_make_key)] +struct KeyStore { + buf: [u8; 16384], +} + +#[cfg(lms_make_key)] +unsafe extern "C" fn write_key_cb( + priv_data: *const u8, + priv_sz: u32, + ctx: *mut core::ffi::c_void, +) -> i32 { + let store = unsafe { &mut *(ctx as *mut KeyStore) }; + let priv_sz = priv_sz as usize; + store.buf[..priv_sz] + .copy_from_slice(unsafe { core::slice::from_raw_parts(priv_data, priv_sz) }); + sys::wc_LmsRc_WC_LMS_RC_SAVED_TO_NV_MEMORY as i32 +} + +#[cfg(lms_make_key)] +unsafe extern "C" fn read_key_cb( + priv_data: *mut u8, + priv_sz: u32, + ctx: *mut core::ffi::c_void, +) -> i32 { + let store = unsafe { &*(ctx as *mut KeyStore) }; + let priv_sz = priv_sz as usize; + unsafe { core::slice::from_raw_parts_mut(priv_data, priv_sz) } + .copy_from_slice(&store.buf[..priv_sz]); + sys::wc_LmsRc_WC_LMS_RC_READ_TO_MEMORY as i32 +} + +/// Register the write and read callbacks and a context pointer on `key`. +#[cfg(lms_make_key)] +fn setup_callbacks(key: &mut Lms, ctx: *mut core::ffi::c_void) { + key.set_write_cb(Some(write_key_cb)).expect("Error with set_write_cb()"); + key.set_read_cb(Some(read_key_cb)).expect("Error with set_read_cb()"); + // Safety: ctx points to a BoxedKeyStore that outlives all callback invocations. + unsafe { key.set_context(ctx).expect("Error with set_context()") }; +} + +// --------------------------------------------------------------------------- +// Constant and construction tests (no keygen required) +// --------------------------------------------------------------------------- + +/// Verify the KEY_ID_LEN constant matches the C `WC_LMS_I_LEN` value. +#[test] +fn test_key_id_len_constant() { + assert_eq!(Lms::KEY_ID_LEN, 16); +} + +/// Verify that `new()` succeeds. +#[test] +fn test_new() { + common::setup(); + Lms::new().expect("Error with Lms::new()"); +} + +/// Verify that `new_ex()` accepts the optional heap and device ID parameters. +#[test] +fn test_new_ex() { + common::setup(); + Lms::new_ex(None, None).expect("Error with Lms::new_ex()"); +} + +/// Verify that `set_parm()` accepts a predefined wc_LmsParm value. +#[test] +fn test_set_parm() { + common::setup(); + let mut key = Lms::new().expect("Error with Lms::new()"); + key.set_parm(Lms::PARM_L1_H5_W8).expect("Error with set_parm()"); +} + +/// Verify that `set_parameters()` accepts explicit L/H/W values and that +/// `get_parameters()` returns them unchanged. +#[test] +fn test_set_get_parameters() { + common::setup(); + + for &(levels, height, winternitz) in &[(1, 5, 8), (1, 10, 4), (2, 5, 8)] { + // Use a fresh key for each parameter set; wc_LmsKey_SetParameters does + // not allow re-setting parameters on an already-configured key. + let mut k = Lms::new().expect("Error with Lms::new()"); + k.set_parameters(levels, height, winternitz) + .expect("Error with set_parameters()"); + let (l, h, w) = k.get_parameters().expect("Error with get_parameters()"); + assert_eq!(l, levels, "levels mismatch for ({},{},{})", levels, height, winternitz); + assert_eq!(h, height, "height mismatch for ({},{},{})", levels, height, winternitz); + assert_eq!(w, winternitz, "winternitz mismatch for ({},{},{})", levels, height, winternitz); + } +} + +/// Verify that `get_sig_len()` and `get_pub_len()` return positive values +/// after setting a predefined parameter set. +#[test] +fn test_size_queries_after_set_parm() { + common::setup(); + for &parm in &[ + Lms::PARM_L1_H5_W8, + Lms::PARM_L1_H5_W4, + Lms::PARM_L1_H10_W8, + ] { + let mut key = Lms::new().expect("Error with Lms::new()"); + key.set_parm(parm).expect("Error with set_parm()"); + let sig_len = key.get_sig_len().expect("Error with get_sig_len()"); + let pub_len = key.get_pub_len().expect("Error with get_pub_len()"); + assert!(sig_len > 0, "sig_len must be positive for parm {}", parm); + assert!(pub_len > 0, "pub_len must be positive for parm {}", parm); + } +} + +/// Verify that `get_sig_len()` grows with the number of levels (higher +/// level count increases signature size significantly). +#[test] +fn test_sig_len_increases_with_levels() { + common::setup(); + let mut key1 = Lms::new().expect("Error with Lms::new()"); + key1.set_parm(Lms::PARM_L1_H5_W8).expect("Error with set_parm()"); + let sig_len_l1 = key1.get_sig_len().expect("Error with get_sig_len() L1"); + + let mut key2 = Lms::new().expect("Error with Lms::new()"); + key2.set_parm(Lms::PARM_L2_H5_W8).expect("Error with set_parm()"); + let sig_len_l2 = key2.get_sig_len().expect("Error with get_sig_len() L2"); + + assert!( + sig_len_l2 > sig_len_l1, + "L2 sig_len ({}) must exceed L1 sig_len ({})", + sig_len_l2, + sig_len_l1 + ); +} + +// --------------------------------------------------------------------------- +// Private-key-length query (needs lms_make_key for the API) +// --------------------------------------------------------------------------- + +/// Verify that `get_priv_len()` returns a positive value after parameters +/// are set. No key generation is required for this query. +#[test] +#[cfg(lms_make_key)] +fn test_get_priv_len() { + common::setup(); + let mut key = Lms::new().expect("Error with Lms::new()"); + key.set_parm(Lms::PARM_L1_H5_W8).expect("Error with set_parm()"); + let priv_len = key.get_priv_len().expect("Error with get_priv_len()"); + assert!(priv_len > 0, "priv_len must be positive"); +} + +// --------------------------------------------------------------------------- +// Key generation and signing tests +// --------------------------------------------------------------------------- + +/// Verify that `make_key()` completes without error for the test parameter set. +#[test] +#[cfg(all(lms_make_key, random))] +fn test_make_key() { + common::setup(); + let mut rng = RNG::new().expect("Error creating RNG"); + let mut store = Box::new(KeyStore { buf: [0u8; 16384] }); + let ctx = store.as_mut() as *mut KeyStore as *mut core::ffi::c_void; + + let mut key = Lms::new().expect("Error with Lms::new()"); + key.set_parm(Lms::PARM_L1_H5_W8).expect("Error with set_parm()"); + setup_callbacks(&mut key, ctx); + key.make_key(&mut rng).expect("Error with make_key()"); + + let _ = store; // keep alive +} + +/// Sign a message and verify it with the same key. +#[test] +#[cfg(all(lms_make_key, random))] +fn test_sign_verify() { + common::setup(); + let mut rng = RNG::new().expect("Error creating RNG"); + let mut store = Box::new(KeyStore { buf: [0u8; 16384] }); + let ctx = store.as_mut() as *mut KeyStore as *mut core::ffi::c_void; + + let mut key = Lms::new().expect("Error with Lms::new()"); + key.set_parm(Lms::PARM_L1_H5_W8).expect("Error with set_parm()"); + setup_callbacks(&mut key, ctx); + key.make_key(&mut rng).expect("Error with make_key()"); + + let message = b"Hello, LMS/HSS!"; + let sig_len = key.get_sig_len().expect("Error with get_sig_len()"); + let mut sig = vec![0u8; sig_len]; + + let written = key.sign(message, &mut sig).expect("Error with sign()"); + assert_eq!(written, sig_len, "sign() must fill the entire signature buffer"); + + key.verify(&sig, message).expect("Valid signature must verify"); + + let _ = store; +} + +/// Verify that a signature does not verify for a different message. +#[test] +#[cfg(all(lms_make_key, random))] +fn test_sign_tampered_message() { + common::setup(); + let mut rng = RNG::new().expect("Error creating RNG"); + let mut store = Box::new(KeyStore { buf: [0u8; 16384] }); + let ctx = store.as_mut() as *mut KeyStore as *mut core::ffi::c_void; + + let mut key = Lms::new().expect("Error with Lms::new()"); + key.set_parm(Lms::PARM_L1_H5_W8).expect("Error with set_parm()"); + setup_callbacks(&mut key, ctx); + key.make_key(&mut rng).expect("Error with make_key()"); + + let message = b"Authentic message"; + let sig_len = key.get_sig_len().expect("Error with get_sig_len()"); + let mut sig = vec![0u8; sig_len]; + key.sign(message, &mut sig).expect("Error with sign()"); + + let result = key.verify(&sig, b"Tampered message"); + assert!(result.is_err(), "Signature must not verify for a different message"); + + let _ = store; +} + +/// Verify that a tampered signature is rejected. +#[test] +#[cfg(all(lms_make_key, random))] +fn test_sign_tampered_signature() { + common::setup(); + let mut rng = RNG::new().expect("Error creating RNG"); + let mut store = Box::new(KeyStore { buf: [0u8; 16384] }); + let ctx = store.as_mut() as *mut KeyStore as *mut core::ffi::c_void; + + let mut key = Lms::new().expect("Error with Lms::new()"); + key.set_parm(Lms::PARM_L1_H5_W8).expect("Error with set_parm()"); + setup_callbacks(&mut key, ctx); + key.make_key(&mut rng).expect("Error with make_key()"); + + let message = b"Message under test"; + let sig_len = key.get_sig_len().expect("Error with get_sig_len()"); + let mut sig = vec![0u8; sig_len]; + key.sign(message, &mut sig).expect("Error with sign()"); + + // Flip a byte in the signature body. + sig[sig_len / 2] ^= 0xFF; + + let result = key.verify(&sig, message); + assert!(result.is_err(), "Tampered signature must be rejected"); + + let _ = store; +} + +/// Export the raw public key, import it into a fresh key, and verify a +/// signature produced by the original key. +#[test] +#[cfg(all(lms_make_key, random))] +fn test_export_pub_raw_import_verify() { + common::setup(); + let mut rng = RNG::new().expect("Error creating RNG"); + let mut store = Box::new(KeyStore { buf: [0u8; 16384] }); + let ctx = store.as_mut() as *mut KeyStore as *mut core::ffi::c_void; + + let mut sign_key = Lms::new().expect("Error with Lms::new()"); + sign_key.set_parm(Lms::PARM_L1_H5_W8).expect("Error with set_parm()"); + setup_callbacks(&mut sign_key, ctx); + sign_key.make_key(&mut rng).expect("Error with make_key()"); + + let pub_len = sign_key.get_pub_len().expect("Error with get_pub_len()"); + let sig_len = sign_key.get_sig_len().expect("Error with get_sig_len()"); + + let mut pub_buf = vec![0u8; pub_len]; + let written = sign_key.export_pub_raw(&mut pub_buf) + .expect("Error with export_pub_raw()"); + assert_eq!(written, pub_len, "export_pub_raw must fill the entire buffer"); + + let message = b"Public key export/import test"; + let mut sig = vec![0u8; sig_len]; + sign_key.sign(message, &mut sig).expect("Error with sign()"); + + // Import the raw public key into a new key and verify. + // wc_LmsKey_ImportPubRaw requires params to be set first. + let mut verify_key = Lms::new().expect("Error with Lms::new() for verify"); + verify_key.set_parm(Lms::PARM_L1_H5_W8).expect("Error with set_parm() for verify key"); + verify_key.import_pub_raw(&pub_buf) + .expect("Error with import_pub_raw()"); + verify_key.verify(&sig, message) + .expect("Signature must verify against imported public key"); + + let _ = store; +} + +/// Verify that `export_pub_from()` copies the public key into a destination +/// key and that signatures from the source verify against that destination. +#[test] +#[cfg(all(lms_make_key, random))] +fn test_export_pub_from() { + common::setup(); + let mut rng = RNG::new().expect("Error creating RNG"); + let mut store = Box::new(KeyStore { buf: [0u8; 16384] }); + let ctx = store.as_mut() as *mut KeyStore as *mut core::ffi::c_void; + + let mut sign_key = Lms::new().expect("Error with Lms::new()"); + sign_key.set_parm(Lms::PARM_L1_H5_W8).expect("Error with set_parm()"); + setup_callbacks(&mut sign_key, ctx); + sign_key.make_key(&mut rng).expect("Error with make_key()"); + + let message = b"export_pub_from test message"; + let sig_len = sign_key.get_sig_len().expect("Error with get_sig_len()"); + let mut sig = vec![0u8; sig_len]; + sign_key.sign(message, &mut sig).expect("Error with sign()"); + + // Copy the public portion into a fresh key. + let mut verify_key = Lms::new().expect("Error with Lms::new() for verify"); + verify_key.set_parm(Lms::PARM_L1_H5_W8).expect("Error with set_parm() for verify key"); + verify_key.export_pub_from(&sign_key) + .expect("Error with export_pub_from()"); + + verify_key.verify(&sig, message) + .expect("Signature must verify against export_pub_from() key"); + + let _ = store; +} + +/// Verify that `sigs_left()` indicates signatures are available immediately +/// after `make_key()`. +#[test] +#[cfg(all(lms_make_key, random))] +fn test_sigs_left_after_make_key() { + common::setup(); + let mut rng = RNG::new().expect("Error creating RNG"); + let mut store = Box::new(KeyStore { buf: [0u8; 16384] }); + let ctx = store.as_mut() as *mut KeyStore as *mut core::ffi::c_void; + + let mut key = Lms::new().expect("Error with Lms::new()"); + key.set_parm(Lms::PARM_L1_H5_W8).expect("Error with set_parm()"); + setup_callbacks(&mut key, ctx); + key.make_key(&mut rng).expect("Error with make_key()"); + + let remaining = key.sigs_left().expect("Error with sigs_left()"); + assert!(remaining, "sigs_left must be true immediately after make_key()"); + + let _ = store; +} + +/// Verify that `get_kid()` returns a slice of exactly `KEY_ID_LEN` bytes +/// after key generation. +#[test] +#[cfg(all(lms_make_key, random))] +fn test_get_kid() { + common::setup(); + let mut rng = RNG::new().expect("Error creating RNG"); + let mut store = Box::new(KeyStore { buf: [0u8; 16384] }); + let ctx = store.as_mut() as *mut KeyStore as *mut core::ffi::c_void; + + let mut key = Lms::new().expect("Error with Lms::new()"); + key.set_parm(Lms::PARM_L1_H5_W8).expect("Error with set_parm()"); + setup_callbacks(&mut key, ctx); + key.make_key(&mut rng).expect("Error with make_key()"); + + let kid = key.get_kid().expect("Error with get_kid()"); + assert_eq!(kid.len(), Lms::KEY_ID_LEN, "kid must be KEY_ID_LEN bytes"); + + let _ = store; +} + +/// Verify that `reload()` restores a key to a usable signing state and that +/// a signature produced by the reloaded key verifies correctly. +#[test] +#[cfg(all(lms_make_key, random))] +fn test_reload() { + common::setup(); + let mut rng = RNG::new().expect("Error creating RNG"); + let mut store = Box::new(KeyStore { buf: [0u8; 16384] }); + let ctx = store.as_mut() as *mut KeyStore as *mut core::ffi::c_void; + + // Generate a key pair and export the public key. + let mut key = Lms::new().expect("Error with Lms::new()"); + key.set_parm(Lms::PARM_L1_H5_W8).expect("Error with set_parm()"); + setup_callbacks(&mut key, ctx); + key.make_key(&mut rng).expect("Error with make_key()"); + + let pub_len = key.get_pub_len().expect("Error with get_pub_len()"); + let mut pub_buf = vec![0u8; pub_len]; + key.export_pub_raw(&mut pub_buf).expect("Error with export_pub_raw()"); + + // Create a new key, reload from NV storage (written by make_key above). + let mut reloaded = Lms::new().expect("Error with Lms::new() for reload"); + reloaded.set_parm(Lms::PARM_L1_H5_W8).expect("Error with set_parm() for reload"); + setup_callbacks(&mut reloaded, ctx); + reloaded.reload().expect("Error with reload()"); + + // Sign with the reloaded key and verify against the exported public key. + let message = b"Reload round-trip message"; + let sig_len = reloaded.get_sig_len().expect("Error with get_sig_len()"); + let mut sig = vec![0u8; sig_len]; + reloaded.sign(message, &mut sig).expect("Error with sign() after reload"); + + let mut verify_key = Lms::new().expect("Error with Lms::new() for verify"); + // wc_LmsKey_ImportPubRaw requires params to be set first. + verify_key.set_parm(Lms::PARM_L1_H5_W8).expect("Error with set_parm() for verify key"); + verify_key.import_pub_raw(&pub_buf).expect("Error with import_pub_raw()"); + verify_key.verify(&sig, message) + .expect("Signature from reloaded key must verify"); + + let _ = store; +} + +/// Sign and verify round-trips across several predefined parameter sets, +/// confirming the implementation is parameter-agnostic. +#[test] +#[cfg(all(lms_make_key, random))] +fn test_sign_verify_multiple_parms() { + common::setup(); + let mut rng = RNG::new().expect("Error creating RNG"); + + for &parm in &[ + Lms::PARM_L1_H5_W8, + Lms::PARM_L1_H5_W4, + Lms::PARM_L1_H5_W2, + ] { + let mut store = Box::new(KeyStore { buf: [0u8; 16384] }); + let ctx = store.as_mut() as *mut KeyStore as *mut core::ffi::c_void; + + let mut key = Lms::new().expect("Error with Lms::new()"); + key.set_parm(parm).expect("Error with set_parm()"); + setup_callbacks(&mut key, ctx); + key.make_key(&mut rng) + .unwrap_or_else(|e| panic!("make_key failed for parm {}: {}", parm, e)); + + let message = b"Parameter set round-trip"; + let sig_len = key.get_sig_len().expect("Error with get_sig_len()"); + let mut sig = vec![0u8; sig_len]; + + key.sign(message, &mut sig) + .unwrap_or_else(|e| panic!("sign failed for parm {}: {}", parm, e)); + key.verify(&sig, message) + .unwrap_or_else(|e| panic!("verify failed for parm {}: {}", parm, e)); + + let _ = store; + } +}