From aff0d42686b92346365225d62c0e6a0f611df569 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 20 Feb 2026 11:21:44 +0100 Subject: [PATCH 01/36] Add bind 9.20.11 to the test matrix Depends on https://github.com/wolfSSL/osp/pull/316 --- .github/workflows/bind.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/bind.yml b/.github/workflows/bind.yml index a427ead431..e4d3635b6e 100644 --- a/.github/workflows/bind.yml +++ b/.github/workflows/bind.yml @@ -44,7 +44,7 @@ jobs: fail-fast: false matrix: # List of releases to test - ref: [ 9.18.0, 9.18.28, 9.18.33 ] + ref: [ 9.18.0, 9.18.28, 9.18.33, 9.20.11 ] name: ${{ matrix.ref }} if: github.repository_owner == 'wolfssl' runs-on: ubuntu-24.04 @@ -66,7 +66,7 @@ jobs: export DEBIAN_FRONTEND=noninteractive sudo apt-get update # hostap dependencies - sudo apt-get install -y libuv1-dev libnghttp2-dev libcap-dev libcmocka-dev + sudo apt-get install -y libuv1-dev libnghttp2-dev libcap-dev libcmocka-dev liburcu-dev - name: Checkout OSP uses: actions/checkout@v4 From 504617bbe96f2ee0dd4e12335ef0ea061f2b978f Mon Sep 17 00:00:00 2001 From: Josh Holtrop Date: Fri, 20 Feb 2026 16:20:09 -0500 Subject: [PATCH 02/36] Rust wrapper: add dilithium module --- wrapper/rust/wolfssl-wolfcrypt/build.rs | 16 + wrapper/rust/wolfssl-wolfcrypt/headers.h | 1 + .../rust/wolfssl-wolfcrypt/src/dilithium.rs | 1303 +++++++++++++++++ wrapper/rust/wolfssl-wolfcrypt/src/lib.rs | 1 + .../wolfssl-wolfcrypt/tests/common/mod.rs | 1 + .../wolfssl-wolfcrypt/tests/test_dilithium.rs | 437 ++++++ 6 files changed, 1759 insertions(+) create mode 100644 wrapper/rust/wolfssl-wolfcrypt/src/dilithium.rs create mode 100644 wrapper/rust/wolfssl-wolfcrypt/tests/test_dilithium.rs diff --git a/wrapper/rust/wolfssl-wolfcrypt/build.rs b/wrapper/rust/wolfssl-wolfcrypt/build.rs index 0f9985acba..1be13ec92e 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/build.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/build.rs @@ -312,6 +312,22 @@ fn scan_cfg() -> Result<()> { println!("cargo:rustc-cfg=rsa_const_api"); } + /* dilithium / ML-DSA */ + check_cfg(&binding, "wc_dilithium_init", "dilithium"); + check_cfg(&binding, "wc_dilithium_make_key", "dilithium_make_key"); + check_cfg(&binding, "wc_dilithium_make_key_from_seed", "dilithium_make_key_from_seed"); + check_cfg(&binding, "wc_dilithium_sign_msg", "dilithium_sign"); + check_cfg(&binding, "wc_dilithium_sign_msg_with_seed", "dilithium_sign_with_seed"); + check_cfg(&binding, "wc_dilithium_verify_msg", "dilithium_verify"); + check_cfg(&binding, "wc_dilithium_import_public", "dilithium_import"); + check_cfg(&binding, "wc_dilithium_export_public", "dilithium_export"); + check_cfg(&binding, "wc_dilithium_check_key", "dilithium_check_key"); + check_cfg(&binding, "DILITHIUM_LEVEL2_KEY_SIZE", "dilithium_level2"); + check_cfg(&binding, "DILITHIUM_LEVEL3_KEY_SIZE", "dilithium_level3"); + check_cfg(&binding, "DILITHIUM_LEVEL5_KEY_SIZE", "dilithium_level5"); + check_cfg(&binding, "DILITHIUM_PRIV_SEED_SZ", "dilithium_priv_seed_sz"); + check_cfg(&binding, "DILITHIUM_RND_SZ", "dilithium_rnd_sz"); + /* 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 da521fa703..ee1038bf80 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/headers.h +++ b/wrapper/rust/wolfssl-wolfcrypt/headers.h @@ -19,3 +19,4 @@ #include "wolfssl/wolfcrypt/logging.h" #include "wolfssl/wolfcrypt/aes.h" #include "wolfssl/wolfcrypt/pwdbased.h" +#include "wolfssl/wolfcrypt/dilithium.h" diff --git a/wrapper/rust/wolfssl-wolfcrypt/src/dilithium.rs b/wrapper/rust/wolfssl-wolfcrypt/src/dilithium.rs new file mode 100644 index 0000000000..d4146077a1 --- /dev/null +++ b/wrapper/rust/wolfssl-wolfcrypt/src/dilithium.rs @@ -0,0 +1,1303 @@ +/* + * 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 ML-DSA +(Dilithium) post-quantum digital signature functionality. + +The primary component is the [`Dilithium`] struct, which manages the lifecycle +of a wolfSSL `dilithium_key` object. It ensures proper initialization and +deallocation. + +Three security parameter sets are supported, selected via +[`Dilithium::set_level()`]: + +| Constant | Level | NIST PQC Level | +|-----------------|-------|----------------| +| [`Dilithium::LEVEL_44`] | 2 | 2 (ML-DSA-44) | +| [`Dilithium::LEVEL_65`] | 3 | 3 (ML-DSA-65) | +| [`Dilithium::LEVEL_87`] | 5 | 5 (ML-DSA-87) | + +# Examples + +```rust +#[cfg(all(dilithium, dilithium_make_key, dilithium_sign, dilithium_verify))] +{ +use wolfssl_wolfcrypt::random::RNG; +use wolfssl_wolfcrypt::dilithium::Dilithium; +let mut rng = RNG::new().expect("RNG creation failed"); +let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + .expect("Key generation failed"); +let message = b"Hello, ML-DSA!"; +let mut sig = vec![0u8; key.sig_size().expect("sig_size failed")]; +let sig_len = key.sign_msg(message, &mut sig, Some(&mut rng)) + .expect("Signing failed"); +let valid = key.verify_msg(&sig[..sig_len], message) + .expect("Verification failed"); +assert!(valid); +} +``` +*/ + +#![cfg(dilithium)] + +use crate::sys; +#[cfg(any(dilithium_make_key, dilithium_sign))] +use crate::random::RNG; +use core::mem::MaybeUninit; + +/// Rust wrapper for a wolfSSL `dilithium_key` object. +/// +/// Manages the lifecycle of the underlying key, including initialization and +/// deallocation via the [`Drop`] trait. +/// +/// An instance is created with [`Dilithium::generate()`], +/// [`Dilithium::generate_from_seed()`], or [`Dilithium::new()`]. +pub struct Dilithium { + ws_key: sys::dilithium_key, +} + +impl Dilithium { + /// ML-DSA-44 security parameter set (NIST Level 2). + pub const LEVEL_44: u8 = sys::WC_ML_DSA_44 as u8; + /// ML-DSA-65 security parameter set (NIST Level 3). + pub const LEVEL_65: u8 = sys::WC_ML_DSA_65 as u8; + /// ML-DSA-87 security parameter set (NIST Level 5). + pub const LEVEL_87: u8 = sys::WC_ML_DSA_87 as u8; + + /// Required size in bytes of the seed passed to + /// [`Dilithium::generate_from_seed()`] (`DILITHIUM_PRIV_SEED_SZ`). + #[cfg(dilithium_priv_seed_sz)] + pub const MAKE_KEY_SEED_SIZE: usize = sys::DILITHIUM_PRIV_SEED_SZ as usize; + + /// Required size in bytes of the seed passed to signing-with-seed + /// functions such as [`Dilithium::sign_msg_with_seed()`] + /// (`DILITHIUM_RND_SZ`). + #[cfg(dilithium_rnd_sz)] + pub const SIGN_SEED_SIZE: usize = sys::DILITHIUM_RND_SZ as usize; + + /// Private (secret) key size in bytes for ML-DSA-44. + #[cfg(dilithium_level2)] + pub const LEVEL2_KEY_SIZE: usize = sys::DILITHIUM_LEVEL2_KEY_SIZE as usize; + /// Signature size in bytes for ML-DSA-44. + #[cfg(dilithium_level2)] + pub const LEVEL2_SIG_SIZE: usize = sys::DILITHIUM_LEVEL2_SIG_SIZE as usize; + /// Public key size in bytes for ML-DSA-44. + #[cfg(dilithium_level2)] + pub const LEVEL2_PUB_KEY_SIZE: usize = sys::DILITHIUM_LEVEL2_PUB_KEY_SIZE as usize; + /// Combined private-plus-public key size in bytes for ML-DSA-44. + #[cfg(dilithium_level2)] + pub const LEVEL2_PRV_KEY_SIZE: usize = + sys::DILITHIUM_LEVEL2_PUB_KEY_SIZE as usize + sys::DILITHIUM_LEVEL2_KEY_SIZE as usize; + + /// Private (secret) key size in bytes for ML-DSA-65. + #[cfg(dilithium_level3)] + pub const LEVEL3_KEY_SIZE: usize = sys::DILITHIUM_LEVEL3_KEY_SIZE as usize; + /// Signature size in bytes for ML-DSA-65. + #[cfg(dilithium_level3)] + pub const LEVEL3_SIG_SIZE: usize = sys::DILITHIUM_LEVEL3_SIG_SIZE as usize; + /// Public key size in bytes for ML-DSA-65. + #[cfg(dilithium_level3)] + pub const LEVEL3_PUB_KEY_SIZE: usize = sys::DILITHIUM_LEVEL3_PUB_KEY_SIZE as usize; + /// Combined private-plus-public key size in bytes for ML-DSA-65. + #[cfg(dilithium_level3)] + pub const LEVEL3_PRV_KEY_SIZE: usize = + sys::DILITHIUM_LEVEL3_PUB_KEY_SIZE as usize + sys::DILITHIUM_LEVEL3_KEY_SIZE as usize; + + /// Private (secret) key size in bytes for ML-DSA-87. + #[cfg(dilithium_level5)] + pub const LEVEL5_KEY_SIZE: usize = sys::DILITHIUM_LEVEL5_KEY_SIZE as usize; + /// Signature size in bytes for ML-DSA-87. + #[cfg(dilithium_level5)] + pub const LEVEL5_SIG_SIZE: usize = sys::DILITHIUM_LEVEL5_SIG_SIZE as usize; + /// Public key size in bytes for ML-DSA-87. + #[cfg(dilithium_level5)] + pub const LEVEL5_PUB_KEY_SIZE: usize = sys::DILITHIUM_LEVEL5_PUB_KEY_SIZE as usize; + /// Combined private-plus-public key size in bytes for ML-DSA-87. + #[cfg(dilithium_level5)] + pub const LEVEL5_PRV_KEY_SIZE: usize = + sys::DILITHIUM_LEVEL5_PUB_KEY_SIZE as usize + sys::DILITHIUM_LEVEL5_KEY_SIZE as usize; + + /// Generate a new Dilithium key pair using a random number generator. + /// + /// # Parameters + /// + /// * `level`: Security parameter set. One of [`Dilithium::LEVEL_44`], + /// [`Dilithium::LEVEL_65`], or [`Dilithium::LEVEL_87`]. + /// * `rng`: `RNG` instance to use for random number generation. + /// + /// # Returns + /// + /// Returns either Ok(Dilithium) containing the key instance or Err(e) + /// containing the wolfSSL library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(dilithium, dilithium_make_key))] + /// { + /// use wolfssl_wolfcrypt::random::RNG; + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let mut rng = RNG::new().expect("Error creating RNG"); + /// let key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + /// .expect("Error with generate()"); + /// } + /// ``` + #[cfg(dilithium_make_key)] + pub fn generate(level: u8, rng: &mut RNG) -> Result { + Self::generate_ex(level, rng, None, None) + } + + /// Generate a new Dilithium key pair with optional heap hint and device ID. + /// + /// # Parameters + /// + /// * `level`: Security parameter set. One of [`Dilithium::LEVEL_44`], + /// [`Dilithium::LEVEL_65`], or [`Dilithium::LEVEL_87`]. + /// * `rng`: `RNG` instance to use for random number generation. + /// * `heap`: Optional heap hint. + /// * `dev_id`: Optional device ID for crypto callbacks or async hardware. + /// + /// # Returns + /// + /// Returns either Ok(Dilithium) containing the key instance or Err(e) + /// containing the wolfSSL library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(dilithium, dilithium_make_key))] + /// { + /// use wolfssl_wolfcrypt::random::RNG; + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let mut rng = RNG::new().expect("Error creating RNG"); + /// let key = Dilithium::generate_ex(Dilithium::LEVEL_44, &mut rng, None, None) + /// .expect("Error with generate_ex()"); + /// } + /// ``` + #[cfg(dilithium_make_key)] + pub fn generate_ex( + level: u8, + rng: &mut RNG, + heap: Option<*mut core::ffi::c_void>, + dev_id: Option, + ) -> Result { + let mut key = Self::new_ex(heap, dev_id)?; + let rc = unsafe { sys::wc_dilithium_set_level(&mut key.ws_key, level) }; + if rc != 0 { + return Err(rc); + } + let rc = unsafe { sys::wc_dilithium_make_key(&mut key.ws_key, &mut rng.wc_rng) }; + if rc != 0 { + return Err(rc); + } + Ok(key) + } + + /// Generate a Dilithium key pair from a fixed seed. + /// + /// Produces the same key pair for a given `(level, seed)` pair, enabling + /// deterministic key generation. + /// + /// # Parameters + /// + /// * `level`: Security parameter set. One of [`Dilithium::LEVEL_44`], + /// [`Dilithium::LEVEL_65`], or [`Dilithium::LEVEL_87`]. + /// * `seed`: Seed bytes. Must be `DILITHIUM_PRIV_SEED_SZ` (64) bytes. + /// + /// # Returns + /// + /// Returns either Ok(Dilithium) containing the key instance or Err(e) + /// containing the wolfSSL library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(dilithium, dilithium_make_key_from_seed))] + /// { + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let seed = [0x42u8; 64]; + /// let key = Dilithium::generate_from_seed(Dilithium::LEVEL_44, &seed) + /// .expect("Error with generate_from_seed()"); + /// } + /// ``` + #[cfg(dilithium_make_key_from_seed)] + pub fn generate_from_seed(level: u8, seed: &[u8]) -> Result { + Self::generate_from_seed_ex(level, seed, None, None) + } + + /// Generate a Dilithium key pair from a fixed seed with optional heap hint + /// and device ID. + /// + /// # Parameters + /// + /// * `level`: Security parameter set. One of [`Dilithium::LEVEL_44`], + /// [`Dilithium::LEVEL_65`], or [`Dilithium::LEVEL_87`]. + /// * `seed`: Seed bytes. Must be `DILITHIUM_PRIV_SEED_SZ` (64) bytes. + /// * `heap`: Optional heap hint. + /// * `dev_id`: Optional device ID for crypto callbacks or async hardware. + /// + /// # Returns + /// + /// Returns either Ok(Dilithium) containing the key instance or Err(e) + /// containing the wolfSSL library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(dilithium, dilithium_make_key_from_seed))] + /// { + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let seed = [0x42u8; 64]; + /// let key = Dilithium::generate_from_seed_ex(Dilithium::LEVEL_44, &seed, None, None) + /// .expect("Error with generate_from_seed_ex()"); + /// } + /// ``` + #[cfg(dilithium_make_key_from_seed)] + pub fn generate_from_seed_ex( + level: u8, + seed: &[u8], + heap: Option<*mut core::ffi::c_void>, + dev_id: Option, + ) -> Result { + let mut key = Self::new_ex(heap, dev_id)?; + let rc = unsafe { sys::wc_dilithium_set_level(&mut key.ws_key, level) }; + if rc != 0 { + return Err(rc); + } + let rc = unsafe { + sys::wc_dilithium_make_key_from_seed(&mut key.ws_key, seed.as_ptr()) + }; + if rc != 0 { + return Err(rc); + } + Ok(key) + } + + /// Create and initialize a new Dilithium key instance without a key. + /// + /// The security level and key material can be set afterwards using + /// [`Dilithium::set_level()`] and one of the import functions. + /// + /// # Returns + /// + /// Returns either Ok(Dilithium) containing the key instance or Err(e) + /// containing the wolfSSL library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(dilithium)] + /// { + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let key = Dilithium::new().expect("Error with new()"); + /// } + /// ``` + pub fn new() -> Result { + Self::new_ex(None, None) + } + + /// Create and initialize a new Dilithium 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(Dilithium) containing the key instance or Err(e) + /// containing the wolfSSL library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(dilithium)] + /// { + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let key = Dilithium::new_ex(None, None).expect("Error with new_ex()"); + /// } + /// ``` + pub fn new_ex( + heap: Option<*mut core::ffi::c_void>, + dev_id: Option, + ) -> Result { + let mut ws_key: MaybeUninit = MaybeUninit::uninit(); + 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 rc = unsafe { sys::wc_dilithium_init_ex(ws_key.as_mut_ptr(), heap, dev_id) }; + if rc != 0 { + return Err(rc); + } + let ws_key = unsafe { ws_key.assume_init() }; + Ok(Dilithium { ws_key }) + } + + /// Set the security parameter level for this key. + /// + /// Must be called before generating or importing key material. Use one of + /// the level constants: [`Dilithium::LEVEL_44`], [`Dilithium::LEVEL_65`], + /// or [`Dilithium::LEVEL_87`]. + /// + /// # Parameters + /// + /// * `level`: Security level (2 = ML-DSA-44, 3 = ML-DSA-65, 5 = ML-DSA-87). + /// + /// # Returns + /// + /// Returns either Ok(()) on success or Err(e) containing the wolfSSL + /// library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(dilithium)] + /// { + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let mut key = Dilithium::new().expect("Error with new()"); + /// key.set_level(Dilithium::LEVEL_65).expect("Error with set_level()"); + /// } + /// ``` + pub fn set_level(&mut self, level: u8) -> Result<(), i32> { + let rc = unsafe { sys::wc_dilithium_set_level(&mut self.ws_key, level) }; + if rc != 0 { + return Err(rc); + } + Ok(()) + } + + /// Get the security parameter level of this key. + /// + /// # Returns + /// + /// Returns either Ok(level) containing the current security level or + /// Err(e) containing the wolfSSL library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(dilithium)] + /// { + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let mut key = Dilithium::new().expect("Error with new()"); + /// key.set_level(Dilithium::LEVEL_87).expect("Error with set_level()"); + /// let level = key.get_level().expect("Error with get_level()"); + /// assert_eq!(level, Dilithium::LEVEL_87); + /// } + /// ``` + pub fn get_level(&mut self) -> Result { + let mut level = 0u8; + let rc = unsafe { sys::wc_dilithium_get_level(&mut self.ws_key, &mut level) }; + if rc != 0 { + return Err(rc); + } + Ok(level) + } + + /// Get the private (secret) key size in bytes for the current level. + /// + /// # Returns + /// + /// Returns either Ok(size) containing the private key size or Err(e) + /// containing the wolfSSL library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(dilithium, dilithium_make_key))] + /// { + /// use wolfssl_wolfcrypt::random::RNG; + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let mut rng = RNG::new().expect("Error creating RNG"); + /// let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + /// .expect("Error with generate()"); + /// let sz = key.size().expect("Error with size()"); + /// assert_eq!(sz, Dilithium::LEVEL2_KEY_SIZE); + /// } + /// ``` + pub fn size(&mut self) -> Result { + let rc = unsafe { sys::wc_dilithium_size(&mut self.ws_key) }; + if rc < 0 { + return Err(rc); + } + Ok(rc as usize) + } + + /// Get the combined private-plus-public key size in bytes for the current + /// level. + /// + /// # Returns + /// + /// Returns either Ok(size) or Err(e) containing the wolfSSL library error + /// code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(dilithium, dilithium_make_key))] + /// { + /// use wolfssl_wolfcrypt::random::RNG; + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let mut rng = RNG::new().expect("Error creating RNG"); + /// let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + /// .expect("Error with generate()"); + /// let sz = key.priv_size().expect("Error with priv_size()"); + /// assert_eq!(sz, Dilithium::LEVEL2_PRV_KEY_SIZE); + /// } + /// ``` + pub fn priv_size(&mut self) -> Result { + let rc = unsafe { sys::wc_dilithium_priv_size(&mut self.ws_key) }; + if rc < 0 { + return Err(rc); + } + Ok(rc as usize) + } + + /// Get the public key size in bytes for the current level. + /// + /// # Returns + /// + /// Returns either Ok(size) or Err(e) containing the wolfSSL library error + /// code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(dilithium, dilithium_make_key))] + /// { + /// use wolfssl_wolfcrypt::random::RNG; + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let mut rng = RNG::new().expect("Error creating RNG"); + /// let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + /// .expect("Error with generate()"); + /// let sz = key.pub_size().expect("Error with pub_size()"); + /// assert_eq!(sz, Dilithium::LEVEL2_PUB_KEY_SIZE); + /// } + /// ``` + pub fn pub_size(&mut self) -> Result { + let rc = unsafe { sys::wc_dilithium_pub_size(&mut self.ws_key) }; + if rc < 0 { + return Err(rc); + } + Ok(rc as usize) + } + + /// Get the signature size in bytes for the current level. + /// + /// # Returns + /// + /// Returns either Ok(size) or Err(e) containing the wolfSSL library error + /// code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(dilithium, dilithium_make_key))] + /// { + /// use wolfssl_wolfcrypt::random::RNG; + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let mut rng = RNG::new().expect("Error creating RNG"); + /// let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + /// .expect("Error with generate()"); + /// let sz = key.sig_size().expect("Error with sig_size()"); + /// assert_eq!(sz, Dilithium::LEVEL2_SIG_SIZE); + /// } + /// ``` + pub fn sig_size(&mut self) -> Result { + let rc = unsafe { sys::wc_dilithium_sig_size(&mut self.ws_key) }; + if rc < 0 { + return Err(rc); + } + Ok(rc as usize) + } + + /// Check that the key pair is valid (public key matches private key). + /// + /// # Returns + /// + /// Returns either Ok(()) on success or Err(e) containing the wolfSSL + /// library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(dilithium, dilithium_make_key, dilithium_check_key))] + /// { + /// use wolfssl_wolfcrypt::random::RNG; + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let mut rng = RNG::new().expect("Error creating RNG"); + /// let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + /// .expect("Error with generate()"); + /// key.check_key().expect("Error with check_key()"); + /// } + /// ``` + #[cfg(dilithium_check_key)] + pub fn check_key(&mut self) -> Result<(), i32> { + let rc = unsafe { sys::wc_dilithium_check_key(&mut self.ws_key) }; + if rc != 0 { + return Err(rc); + } + Ok(()) + } + + /// Import a public key from a raw byte buffer. + /// + /// # Parameters + /// + /// * `public`: Input 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 + /// #[cfg(all(dilithium, dilithium_make_key, dilithium_import, dilithium_export))] + /// { + /// use wolfssl_wolfcrypt::random::RNG; + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let mut rng = RNG::new().expect("Error creating RNG"); + /// let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + /// .expect("Error with generate()"); + /// let mut pub_buf = vec![0u8; key.pub_size().unwrap()]; + /// key.export_public(&mut pub_buf).expect("Error with export_public()"); + /// let mut key2 = Dilithium::new().expect("Error with new()"); + /// key2.set_level(Dilithium::LEVEL_44).expect("Error with set_level()"); + /// key2.import_public(&pub_buf).expect("Error with import_public()"); + /// } + /// ``` + #[cfg(dilithium_import)] + pub fn import_public(&mut self, public: &[u8]) -> Result<(), i32> { + let public_size = public.len() as u32; + let rc = unsafe { + sys::wc_dilithium_import_public(public.as_ptr(), public_size, &mut self.ws_key) + }; + if rc != 0 { + return Err(rc); + } + Ok(()) + } + + /// Import a private (secret) key from a raw byte buffer. + /// + /// The buffer should contain the raw private key bytes only + /// (size = `LEVEL*_KEY_SIZE`). + /// + /// # Parameters + /// + /// * `private`: Input buffer containing the raw private key bytes. + /// + /// # Returns + /// + /// Returns either Ok(()) on success or Err(e) containing the wolfSSL + /// library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(dilithium, dilithium_make_key, dilithium_import, dilithium_export))] + /// { + /// use wolfssl_wolfcrypt::random::RNG; + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let mut rng = RNG::new().expect("Error creating RNG"); + /// let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + /// .expect("Error with generate()"); + /// let mut priv_buf = vec![0u8; key.size().unwrap()]; + /// key.export_private(&mut priv_buf).expect("Error with export_private()"); + /// let mut key2 = Dilithium::new().expect("Error with new()"); + /// key2.set_level(Dilithium::LEVEL_44).expect("Error with set_level()"); + /// key2.import_private(&priv_buf).expect("Error with import_private()"); + /// } + /// ``` + #[cfg(dilithium_import)] + pub fn import_private(&mut self, private: &[u8]) -> Result<(), i32> { + let private_size = private.len() as u32; + let rc = unsafe { + sys::wc_dilithium_import_private(private.as_ptr(), private_size, &mut self.ws_key) + }; + if rc != 0 { + return Err(rc); + } + Ok(()) + } + + /// Import both private and public key material from raw byte buffers. + /// + /// # Parameters + /// + /// * `private`: Input buffer containing the raw private key bytes. + /// * `public`: Input 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 + /// #[cfg(all(dilithium, dilithium_make_key, dilithium_import, dilithium_export))] + /// { + /// use wolfssl_wolfcrypt::random::RNG; + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let mut rng = RNG::new().expect("Error creating RNG"); + /// let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + /// .expect("Error with generate()"); + /// let mut priv_buf = vec![0u8; key.size().unwrap()]; + /// let mut pub_buf = vec![0u8; key.pub_size().unwrap()]; + /// key.export_key(&mut priv_buf, &mut pub_buf).expect("Error with export_key()"); + /// let mut key2 = Dilithium::new().expect("Error with new()"); + /// key2.set_level(Dilithium::LEVEL_44).expect("Error with set_level()"); + /// key2.import_key(&priv_buf, &pub_buf).expect("Error with import_key()"); + /// } + /// ``` + #[cfg(dilithium_import)] + pub fn import_key(&mut self, private: &[u8], public: &[u8]) -> Result<(), i32> { + let private_size = private.len() as u32; + let public_size = public.len() as u32; + let rc = unsafe { + sys::wc_dilithium_import_key( + private.as_ptr(), private_size, + public.as_ptr(), public_size, + &mut self.ws_key, + ) + }; + if rc != 0 { + return Err(rc); + } + Ok(()) + } + + /// Export the public key to a raw byte buffer. + /// + /// # Parameters + /// + /// * `public`: Output buffer to receive the public key. Must be at least + /// `pub_size()` bytes. + /// + /// # Returns + /// + /// Returns either Ok(size) containing the number of bytes written or Err(e) + /// containing the wolfSSL library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(dilithium, dilithium_make_key, dilithium_export))] + /// { + /// use wolfssl_wolfcrypt::random::RNG; + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let mut rng = RNG::new().expect("Error creating RNG"); + /// let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + /// .expect("Error with generate()"); + /// let mut pub_buf = vec![0u8; key.pub_size().unwrap()]; + /// let written = key.export_public(&mut pub_buf).expect("Error with export_public()"); + /// assert_eq!(written, Dilithium::LEVEL2_PUB_KEY_SIZE); + /// } + /// ``` + #[cfg(dilithium_export)] + pub fn export_public(&mut self, public: &mut [u8]) -> Result { + let mut public_size = public.len() as u32; + let rc = unsafe { + sys::wc_dilithium_export_public(&mut self.ws_key, public.as_mut_ptr(), &mut public_size) + }; + if rc != 0 { + return Err(rc); + } + Ok(public_size as usize) + } + + /// Export the private (secret) key to a raw byte buffer. + /// + /// # Parameters + /// + /// * `private`: Output buffer to receive the private key. Must be at + /// least `size()` bytes. + /// + /// # Returns + /// + /// Returns either Ok(size) containing the number of bytes written or Err(e) + /// containing the wolfSSL library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(dilithium, dilithium_make_key, dilithium_export))] + /// { + /// use wolfssl_wolfcrypt::random::RNG; + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let mut rng = RNG::new().expect("Error creating RNG"); + /// let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + /// .expect("Error with generate()"); + /// let mut priv_buf = vec![0u8; key.size().unwrap()]; + /// let written = key.export_private(&mut priv_buf).expect("Error with export_private()"); + /// assert_eq!(written, Dilithium::LEVEL2_KEY_SIZE); + /// } + /// ``` + #[cfg(dilithium_export)] + pub fn export_private(&mut self, private: &mut [u8]) -> Result { + let mut private_size = private.len() as u32; + let rc = unsafe { + sys::wc_dilithium_export_private( + &mut self.ws_key, private.as_mut_ptr(), &mut private_size, + ) + }; + if rc != 0 { + return Err(rc); + } + Ok(private_size as usize) + } + + /// Export both private and public key material to separate raw byte + /// buffers. + /// + /// # Parameters + /// + /// * `private`: Output buffer for the private key. Must be at least + /// `size()` bytes. + /// * `public`: Output buffer for the public key. Must be at least + /// `pub_size()` bytes. + /// + /// # Returns + /// + /// Returns either Ok(()) on success or Err(e) containing the wolfSSL + /// library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(dilithium, dilithium_make_key, dilithium_export))] + /// { + /// use wolfssl_wolfcrypt::random::RNG; + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let mut rng = RNG::new().expect("Error creating RNG"); + /// let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + /// .expect("Error with generate()"); + /// let mut priv_buf = vec![0u8; key.size().unwrap()]; + /// let mut pub_buf = vec![0u8; key.pub_size().unwrap()]; + /// key.export_key(&mut priv_buf, &mut pub_buf).expect("Error with export_key()"); + /// } + /// ``` + #[cfg(dilithium_export)] + pub fn export_key(&mut self, private: &mut [u8], public: &mut [u8]) -> Result<(), i32> { + let mut private_size = private.len() as u32; + let mut public_size = public.len() as u32; + let rc = unsafe { + sys::wc_dilithium_export_key( + &mut self.ws_key, + private.as_mut_ptr(), &mut private_size, + public.as_mut_ptr(), &mut public_size, + ) + }; + if rc != 0 { + return Err(rc); + } + Ok(()) + } + + /// Sign a message and write the signature to `sig`. + /// + /// # Parameters + /// + /// * `msg`: Message to sign. + /// * `sig`: Output buffer to hold the signature. Must be at least + /// `sig_size()` bytes. + /// * `rng`: Optional RNG instance for hedged signing. Pass `None` for + /// deterministic signing. + /// + /// # Returns + /// + /// Returns either Ok(size) containing the number of bytes written to `sig` + /// on success or Err(e) containing the wolfSSL library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(dilithium, dilithium_make_key, dilithium_sign))] + /// { + /// use wolfssl_wolfcrypt::random::RNG; + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let mut rng = RNG::new().expect("Error creating RNG"); + /// let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + /// .expect("Error with generate()"); + /// let message = b"Hello, ML-DSA!"; + /// let mut sig = vec![0u8; key.sig_size().unwrap()]; + /// let sig_len = key.sign_msg(message, &mut sig, Some(&mut rng)) + /// .expect("Error with sign_msg()"); + /// assert_eq!(sig_len, Dilithium::LEVEL2_SIG_SIZE); + /// } + /// ``` + #[cfg(dilithium_sign)] + pub fn sign_msg( + &mut self, + msg: &[u8], + sig: &mut [u8], + rng: Option<&mut RNG>, + ) -> Result { + let msg_len = msg.len() as u32; + let mut sig_len = sig.len() as u32; + let rng_ptr = match rng { + Some(r) => &mut r.wc_rng as *mut sys::WC_RNG, + None => core::ptr::null_mut(), + }; + let rc = unsafe { + sys::wc_dilithium_sign_msg( + msg.as_ptr(), msg_len, + sig.as_mut_ptr(), &mut sig_len, + &mut self.ws_key, + rng_ptr, + ) + }; + if rc != 0 { + return Err(rc); + } + Ok(sig_len as usize) + } + + /// Sign a message with a context string and write the signature to `sig`. + /// + /// # Parameters + /// + /// * `ctx`: Context string (at most 255 bytes). + /// * `msg`: Message to sign. + /// * `sig`: Output buffer to hold the signature. Must be at least + /// `sig_size()` bytes. + /// * `rng`: Optional RNG instance for hedged signing. Pass `None` for + /// deterministic signing. + /// + /// # Returns + /// + /// Returns either Ok(size) containing the number of bytes written to `sig` + /// on success or Err(e) containing the wolfSSL library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(dilithium, dilithium_make_key, dilithium_sign))] + /// { + /// use wolfssl_wolfcrypt::random::RNG; + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let mut rng = RNG::new().expect("Error creating RNG"); + /// let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + /// .expect("Error with generate()"); + /// let message = b"Hello, ML-DSA!"; + /// let ctx = b"my context"; + /// let mut sig = vec![0u8; key.sig_size().unwrap()]; + /// key.sign_ctx_msg(ctx, message, &mut sig, Some(&mut rng)) + /// .expect("Error with sign_ctx_msg()"); + /// } + /// ``` + #[cfg(dilithium_sign)] + pub fn sign_ctx_msg( + &mut self, + ctx: &[u8], + msg: &[u8], + sig: &mut [u8], + rng: Option<&mut RNG>, + ) -> Result { + if ctx.len() > 255 { + return Err(sys::wolfCrypt_ErrorCodes_BUFFER_E); + } + let ctx_len = ctx.len() as u8; + let msg_len = msg.len() as u32; + let mut sig_len = sig.len() as u32; + let rng_ptr = match rng { + Some(r) => &mut r.wc_rng as *mut sys::WC_RNG, + None => core::ptr::null_mut(), + }; + let rc = unsafe { + sys::wc_dilithium_sign_ctx_msg( + ctx.as_ptr(), ctx_len, + msg.as_ptr(), msg_len, + sig.as_mut_ptr(), &mut sig_len, + &mut self.ws_key, + rng_ptr, + ) + }; + if rc != 0 { + return Err(rc); + } + Ok(sig_len as usize) + } + + /// Sign a pre-hashed message with a context string. + /// + /// This is the HashML-DSA variant: the message is supplied as a hash + /// digest along with the hash algorithm identifier. + /// + /// # Parameters + /// + /// * `ctx`: Context string (at most 255 bytes). + /// * `hash_alg`: Hash algorithm identifier (e.g. `WC_HASH_TYPE_SHA256`). + /// * `hash`: Hash digest of the message to sign. + /// * `sig`: Output buffer to hold the signature. Must be at least + /// `sig_size()` bytes. + /// * `rng`: Optional RNG instance for hedged signing. Pass `None` for + /// deterministic signing. + /// + /// # Returns + /// + /// Returns either Ok(size) containing the number of bytes written to `sig` + /// on success or Err(e) containing the wolfSSL library error code value. + #[cfg(dilithium_sign)] + pub fn sign_ctx_hash( + &mut self, + ctx: &[u8], + hash_alg: i32, + hash: &[u8], + sig: &mut [u8], + rng: Option<&mut RNG>, + ) -> Result { + if ctx.len() > 255 { + return Err(sys::wolfCrypt_ErrorCodes_BUFFER_E); + } + let ctx_len = ctx.len() as u8; + let hash_len = hash.len() as u32; + let mut sig_len = sig.len() as u32; + let rng_ptr = match rng { + Some(r) => &mut r.wc_rng as *mut sys::WC_RNG, + None => core::ptr::null_mut(), + }; + let rc = unsafe { + sys::wc_dilithium_sign_ctx_hash( + ctx.as_ptr(), ctx_len, + hash_alg, + hash.as_ptr(), hash_len, + sig.as_mut_ptr(), &mut sig_len, + &mut self.ws_key, + rng_ptr, + ) + }; + if rc != 0 { + return Err(rc); + } + Ok(sig_len as usize) + } + + /// Sign a message using a fixed random seed instead of an RNG. + /// + /// Produces a deterministic signature for a given `(key, msg, seed)` + /// triple. + /// + /// # Parameters + /// + /// * `msg`: Message to sign. + /// * `sig`: Output buffer to hold the signature. + /// * `seed`: Random seed bytes (`DILITHIUM_RND_SZ` = 32 bytes). + /// + /// # Returns + /// + /// Returns either Ok(size) containing the number of bytes written to `sig` + /// on success or Err(e) containing the wolfSSL library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(dilithium, dilithium_make_key_from_seed, dilithium_sign_with_seed))] + /// { + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let key_seed = [0x42u8; 64]; + /// let mut key = Dilithium::generate_from_seed(Dilithium::LEVEL_44, &key_seed) + /// .expect("Error with generate_from_seed()"); + /// let message = b"Hello, ML-DSA!"; + /// let sign_seed = [0x55u8; 32]; + /// let mut sig = vec![0u8; key.sig_size().unwrap()]; + /// key.sign_msg_with_seed(message, &mut sig, &sign_seed) + /// .expect("Error with sign_msg_with_seed()"); + /// } + /// ``` + #[cfg(dilithium_sign_with_seed)] + pub fn sign_msg_with_seed( + &mut self, + msg: &[u8], + sig: &mut [u8], + seed: &[u8], + ) -> Result { + let msg_len = msg.len() as u32; + let mut sig_len = sig.len() as u32; + let rc = unsafe { + sys::wc_dilithium_sign_msg_with_seed( + msg.as_ptr(), msg_len, + sig.as_mut_ptr(), &mut sig_len, + &mut self.ws_key, + seed.as_ptr(), + ) + }; + if rc != 0 { + return Err(rc); + } + Ok(sig_len as usize) + } + + /// Sign a message with a context string using a fixed random seed. + /// + /// # Parameters + /// + /// * `ctx`: Context string (at most 255 bytes). + /// * `msg`: Message to sign. + /// * `sig`: Output buffer to hold the signature. + /// * `seed`: Random seed bytes (`DILITHIUM_RND_SZ` = 32 bytes). + /// + /// # Returns + /// + /// Returns either Ok(size) containing the number of bytes written to `sig` + /// on success or Err(e) containing the wolfSSL library error code value. + #[cfg(dilithium_sign_with_seed)] + pub fn sign_ctx_msg_with_seed( + &mut self, + ctx: &[u8], + msg: &[u8], + sig: &mut [u8], + seed: &[u8], + ) -> Result { + if ctx.len() > 255 { + return Err(sys::wolfCrypt_ErrorCodes_BUFFER_E); + } + let ctx_len = ctx.len() as u8; + let msg_len = msg.len() as u32; + let mut sig_len = sig.len() as u32; + let rc = unsafe { + sys::wc_dilithium_sign_ctx_msg_with_seed( + ctx.as_ptr(), ctx_len, + msg.as_ptr(), msg_len, + sig.as_mut_ptr(), &mut sig_len, + &mut self.ws_key, + seed.as_ptr(), + ) + }; + if rc != 0 { + return Err(rc); + } + Ok(sig_len as usize) + } + + /// Sign a pre-hashed message with a context string using a fixed random + /// seed. + /// + /// # Parameters + /// + /// * `ctx`: Context string (at most 255 bytes). + /// * `hash_alg`: Hash algorithm identifier (e.g. `WC_HASH_TYPE_SHA256`). + /// * `hash`: Hash digest of the message to sign. + /// * `sig`: Output buffer to hold the signature. + /// * `seed`: Random seed bytes (`DILITHIUM_RND_SZ` = 32 bytes). + /// + /// # Returns + /// + /// Returns either Ok(size) containing the number of bytes written to `sig` + /// on success or Err(e) containing the wolfSSL library error code value. + #[cfg(dilithium_sign_with_seed)] + pub fn sign_ctx_hash_with_seed( + &mut self, + ctx: &[u8], + hash_alg: i32, + hash: &[u8], + sig: &mut [u8], + seed: &[u8], + ) -> Result { + if ctx.len() > 255 { + return Err(sys::wolfCrypt_ErrorCodes_BUFFER_E); + } + let ctx_len = ctx.len() as u8; + let hash_len = hash.len() as u32; + let mut sig_len = sig.len() as u32; + let rc = unsafe { + sys::wc_dilithium_sign_ctx_hash_with_seed( + ctx.as_ptr(), ctx_len, + hash_alg, + hash.as_ptr(), hash_len, + sig.as_mut_ptr(), &mut sig_len, + &mut self.ws_key, + seed.as_ptr(), + ) + }; + if rc != 0 { + return Err(rc); + } + Ok(sig_len as usize) + } + + /// Verify a message signature. + /// + /// # Parameters + /// + /// * `sig`: Signature to verify. + /// * `msg`: Message the signature was created over. + /// + /// # Returns + /// + /// Returns either Ok(true) if the signature is valid, Ok(false) if it is + /// invalid, or Err(e) containing the wolfSSL library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(dilithium, dilithium_make_key, dilithium_sign, dilithium_verify))] + /// { + /// use wolfssl_wolfcrypt::random::RNG; + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let mut rng = RNG::new().expect("Error creating RNG"); + /// let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + /// .expect("Error with generate()"); + /// let message = b"Hello, ML-DSA!"; + /// let mut sig = vec![0u8; key.sig_size().unwrap()]; + /// let sig_len = key.sign_msg(message, &mut sig, Some(&mut rng)) + /// .expect("Error with sign_msg()"); + /// let valid = key.verify_msg(&sig[..sig_len], message) + /// .expect("Error with verify_msg()"); + /// assert!(valid); + /// } + /// ``` + #[cfg(dilithium_verify)] + pub fn verify_msg(&mut self, sig: &[u8], msg: &[u8]) -> Result { + let sig_len = sig.len() as u32; + let msg_len = msg.len() as u32; + let mut res = 0i32; + let rc = unsafe { + sys::wc_dilithium_verify_msg( + sig.as_ptr(), sig_len, + msg.as_ptr(), msg_len, + &mut res, + &mut self.ws_key, + ) + }; + if rc != 0 { + return Err(rc); + } + Ok(res == 1) + } + + /// Verify a message signature with a context string. + /// + /// # Parameters + /// + /// * `sig`: Signature to verify. + /// * `ctx`: Context string used when signing. + /// * `msg`: Message the signature was created over. + /// + /// # Returns + /// + /// Returns either Ok(true) if the signature is valid, Ok(false) if it is + /// invalid, or Err(e) containing the wolfSSL library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(dilithium, dilithium_make_key, dilithium_sign, dilithium_verify))] + /// { + /// use wolfssl_wolfcrypt::random::RNG; + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let mut rng = RNG::new().expect("Error creating RNG"); + /// let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + /// .expect("Error with generate()"); + /// let message = b"Hello, ML-DSA!"; + /// let ctx = b"my context"; + /// let mut sig = vec![0u8; key.sig_size().unwrap()]; + /// let sig_len = key.sign_ctx_msg(ctx, message, &mut sig, Some(&mut rng)) + /// .expect("Error with sign_ctx_msg()"); + /// let valid = key.verify_ctx_msg(&sig[..sig_len], ctx, message) + /// .expect("Error with verify_ctx_msg()"); + /// assert!(valid); + /// } + /// ``` + #[cfg(dilithium_verify)] + pub fn verify_ctx_msg(&mut self, sig: &[u8], ctx: &[u8], msg: &[u8]) -> Result { + let sig_len = sig.len() as u32; + let ctx_len = ctx.len() as u32; + let msg_len = msg.len() as u32; + let mut res = 0i32; + let rc = unsafe { + sys::wc_dilithium_verify_ctx_msg( + sig.as_ptr(), sig_len, + ctx.as_ptr(), ctx_len, + msg.as_ptr(), msg_len, + &mut res, + &mut self.ws_key, + ) + }; + if rc != 0 { + return Err(rc); + } + Ok(res == 1) + } + + /// Verify a pre-hashed message signature with a context string. + /// + /// This is the HashML-DSA variant: the message is supplied as a hash + /// digest along with the hash algorithm identifier. + /// + /// # Parameters + /// + /// * `sig`: Signature to verify. + /// * `ctx`: Context string used when signing. + /// * `hash_alg`: Hash algorithm identifier (e.g. `WC_HASH_TYPE_SHA256`). + /// * `hash`: Hash digest of the message to verify. + /// + /// # Returns + /// + /// Returns either Ok(true) if the signature is valid, Ok(false) if it is + /// invalid, or Err(e) containing the wolfSSL library error code value. + #[cfg(dilithium_verify)] + pub fn verify_ctx_hash( + &mut self, + sig: &[u8], + ctx: &[u8], + hash_alg: i32, + hash: &[u8], + ) -> Result { + let sig_len = sig.len() as u32; + let ctx_len = ctx.len() as u32; + let hash_len = hash.len() as u32; + let mut res = 0i32; + let rc = unsafe { + sys::wc_dilithium_verify_ctx_hash( + sig.as_ptr(), sig_len, + ctx.as_ptr(), ctx_len, + hash_alg, + hash.as_ptr(), hash_len, + &mut res, + &mut self.ws_key, + ) + }; + if rc != 0 { + return Err(rc); + } + Ok(res == 1) + } +} + +impl Drop for Dilithium { + /// Safely free the underlying wolfSSL Dilithium key context. + /// + /// This calls `wc_dilithium_free()`. The Rust Drop trait guarantees this + /// is called when the `Dilithium` struct goes out of scope. + fn drop(&mut self) { + unsafe { sys::wc_dilithium_free(&mut self.ws_key); } + } +} diff --git a/wrapper/rust/wolfssl-wolfcrypt/src/lib.rs b/wrapper/rust/wolfssl-wolfcrypt/src/lib.rs index a073e8289e..9bbc7562f3 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/src/lib.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/src/lib.rs @@ -29,6 +29,7 @@ pub mod chacha20_poly1305; pub mod cmac; pub mod curve25519; pub mod dh; +pub mod dilithium; pub mod ecc; pub mod ed25519; pub mod ed448; diff --git a/wrapper/rust/wolfssl-wolfcrypt/tests/common/mod.rs b/wrapper/rust/wolfssl-wolfcrypt/tests/common/mod.rs index 8d191ed97b..38fbdd907b 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/tests/common/mod.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/tests/common/mod.rs @@ -5,6 +5,7 @@ fn setup_fips() fips::set_private_key_read_enable(1).expect("Error with set_private_key_read_enable()"); } +#[allow(dead_code)] pub fn setup() { #[cfg(fips)] diff --git a/wrapper/rust/wolfssl-wolfcrypt/tests/test_dilithium.rs b/wrapper/rust/wolfssl-wolfcrypt/tests/test_dilithium.rs new file mode 100644 index 0000000000..5261b60cf1 --- /dev/null +++ b/wrapper/rust/wolfssl-wolfcrypt/tests/test_dilithium.rs @@ -0,0 +1,437 @@ +/* + * 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(dilithium)] + +mod common; + +use wolfssl_wolfcrypt::dilithium::Dilithium; +#[cfg(any(dilithium_make_key, dilithium_sign))] +use wolfssl_wolfcrypt::random::RNG; + +/// Verify the level constants have the correct numeric values required by +/// the wolfCrypt API. +#[test] +fn test_level_constants() { + assert_eq!(Dilithium::LEVEL_44, 2); + assert_eq!(Dilithium::LEVEL_65, 3); + assert_eq!(Dilithium::LEVEL_87, 5); +} + +/// Verify `new()` + `set_level()` + `get_level()` for all three parameter sets. +#[test] +fn test_new_and_level() { + common::setup(); + + let mut key = Dilithium::new().expect("Error with new()"); + + key.set_level(Dilithium::LEVEL_44).expect("Error with set_level()"); + assert_eq!(key.get_level().expect("Error with get_level()"), Dilithium::LEVEL_44); + + key.set_level(Dilithium::LEVEL_65).expect("Error with set_level()"); + assert_eq!(key.get_level().expect("Error with get_level()"), Dilithium::LEVEL_65); + + key.set_level(Dilithium::LEVEL_87).expect("Error with set_level()"); + assert_eq!(key.get_level().expect("Error with get_level()"), Dilithium::LEVEL_87); +} + +/// Verify that `new_ex()` accepts the optional heap and device ID parameters. +#[test] +fn test_new_ex() { + common::setup(); + let mut key = Dilithium::new_ex(None, None).expect("Error with new_ex()"); + key.set_level(Dilithium::LEVEL_44).expect("Error with set_level()"); + assert_eq!(key.get_level().expect("Error with get_level()"), Dilithium::LEVEL_44); +} + +/// Verify the runtime size queries match the compile-time constants for +/// ML-DSA-44. +#[test] +#[cfg(all(dilithium_make_key, dilithium_level2))] +fn test_sizes_level44() { + common::setup(); + let mut rng = RNG::new().expect("Error creating RNG"); + let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + .expect("Error with generate()"); + assert_eq!(key.size().expect("Error with size()"), Dilithium::LEVEL2_KEY_SIZE); + assert_eq!(key.priv_size().expect("Error with priv_size()"), Dilithium::LEVEL2_PRV_KEY_SIZE); + assert_eq!(key.pub_size().expect("Error with pub_size()"), Dilithium::LEVEL2_PUB_KEY_SIZE); + assert_eq!(key.sig_size().expect("Error with sig_size()"), Dilithium::LEVEL2_SIG_SIZE); +} + +/// Verify the runtime size queries match the compile-time constants for +/// ML-DSA-65. +#[test] +#[cfg(all(dilithium_make_key, dilithium_level3))] +fn test_sizes_level65() { + common::setup(); + let mut rng = RNG::new().expect("Error creating RNG"); + let mut key = Dilithium::generate(Dilithium::LEVEL_65, &mut rng) + .expect("Error with generate()"); + assert_eq!(key.size().expect("Error with size()"), Dilithium::LEVEL3_KEY_SIZE); + assert_eq!(key.priv_size().expect("Error with priv_size()"), Dilithium::LEVEL3_PRV_KEY_SIZE); + assert_eq!(key.pub_size().expect("Error with pub_size()"), Dilithium::LEVEL3_PUB_KEY_SIZE); + assert_eq!(key.sig_size().expect("Error with sig_size()"), Dilithium::LEVEL3_SIG_SIZE); +} + +/// Verify the runtime size queries match the compile-time constants for +/// ML-DSA-87. +#[test] +#[cfg(all(dilithium_make_key, dilithium_level5))] +fn test_sizes_level87() { + common::setup(); + let mut rng = RNG::new().expect("Error creating RNG"); + let mut key = Dilithium::generate(Dilithium::LEVEL_87, &mut rng) + .expect("Error with generate()"); + assert_eq!(key.size().expect("Error with size()"), Dilithium::LEVEL5_KEY_SIZE); + assert_eq!(key.priv_size().expect("Error with priv_size()"), Dilithium::LEVEL5_PRV_KEY_SIZE); + assert_eq!(key.pub_size().expect("Error with pub_size()"), Dilithium::LEVEL5_PUB_KEY_SIZE); + assert_eq!(key.sig_size().expect("Error with sig_size()"), Dilithium::LEVEL5_SIG_SIZE); +} + +/// Verify that `check_key()` accepts a freshly generated ML-DSA-44 key pair. +#[test] +#[cfg(all(dilithium_make_key, dilithium_check_key))] +fn test_check_key_level44() { + common::setup(); + let mut rng = RNG::new().expect("Error creating RNG"); + let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + .expect("Error with generate()"); + key.check_key().expect("Error with check_key()"); +} + +/// Verify that `check_key()` accepts a freshly generated ML-DSA-65 key pair. +#[test] +#[cfg(all(dilithium_make_key, dilithium_check_key))] +fn test_check_key_level65() { + common::setup(); + let mut rng = RNG::new().expect("Error creating RNG"); + let mut key = Dilithium::generate(Dilithium::LEVEL_65, &mut rng) + .expect("Error with generate()"); + key.check_key().expect("Error with check_key()"); +} + +/// Verify that `check_key()` accepts a freshly generated ML-DSA-87 key pair. +#[test] +#[cfg(all(dilithium_make_key, dilithium_check_key))] +fn test_check_key_level87() { + common::setup(); + let mut rng = RNG::new().expect("Error creating RNG"); + let mut key = Dilithium::generate(Dilithium::LEVEL_87, &mut rng) + .expect("Error with generate()"); + key.check_key().expect("Error with check_key()"); +} + +/// Sign and verify a message round-trip using ML-DSA-44. +/// +/// Also verifies that a tampered message or signature produces a +/// verification failure rather than an error. +#[test] +#[cfg(all(dilithium_make_key, dilithium_sign, dilithium_verify))] +fn test_sign_verify_level44() { + common::setup(); + let mut rng = RNG::new().expect("Error creating RNG"); + let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + .expect("Error with generate()"); + let message = b"Hello, ML-DSA-44!"; + let mut sig = vec![0u8; key.sig_size().expect("Error with sig_size()")]; + + let sig_len = key.sign_msg(message, &mut sig, Some(&mut rng)) + .expect("Error with sign_msg()"); + assert_eq!(sig_len, sig.len()); + + let valid = key.verify_msg(&sig, message).expect("Error with verify_msg()"); + assert!(valid, "Valid signature should verify"); + + // A different message must not verify with the original signature. + let valid = key.verify_msg(&sig, b"Tampered message") + .expect("Error with verify_msg() on tampered message"); + assert!(!valid, "Tampered message should not verify"); +} + +/// Sign and verify a message round-trip using ML-DSA-65. +#[test] +#[cfg(all(dilithium_make_key, dilithium_sign, dilithium_verify))] +fn test_sign_verify_level65() { + common::setup(); + let mut rng = RNG::new().expect("Error creating RNG"); + let mut key = Dilithium::generate(Dilithium::LEVEL_65, &mut rng) + .expect("Error with generate()"); + let message = b"Hello, ML-DSA-65!"; + let mut sig = vec![0u8; key.sig_size().expect("Error with sig_size()")]; + + let sig_len = key.sign_msg(message, &mut sig, Some(&mut rng)) + .expect("Error with sign_msg()"); + assert_eq!(sig_len, sig.len()); + + let valid = key.verify_msg(&sig, message).expect("Error with verify_msg()"); + assert!(valid, "Valid signature should verify"); +} + +/// Sign and verify a message round-trip using ML-DSA-87. +#[test] +#[cfg(all(dilithium_make_key, dilithium_sign, dilithium_verify))] +fn test_sign_verify_level87() { + common::setup(); + let mut rng = RNG::new().expect("Error creating RNG"); + let mut key = Dilithium::generate(Dilithium::LEVEL_87, &mut rng) + .expect("Error with generate()"); + let message = b"Hello, ML-DSA-87!"; + let mut sig = vec![0u8; key.sig_size().expect("Error with sig_size()")]; + + let sig_len = key.sign_msg(message, &mut sig, Some(&mut rng)) + .expect("Error with sign_msg()"); + assert_eq!(sig_len, sig.len()); + + let valid = key.verify_msg(&sig, message).expect("Error with verify_msg()"); + assert!(valid, "Valid signature should verify"); +} + +/// Sign with a context string and verify using ML-DSA-44. +/// +/// Also verifies that a mismatched context causes verification to fail. +#[test] +#[cfg(all(dilithium_make_key, dilithium_sign, dilithium_verify))] +fn test_sign_ctx_verify_level44() { + common::setup(); + let mut rng = RNG::new().expect("Error creating RNG"); + let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + .expect("Error with generate()"); + let message = b"Context-bound message"; + let ctx = b"my context"; + let mut sig = vec![0u8; key.sig_size().expect("Error with sig_size()")]; + + let sig_len = key.sign_ctx_msg(ctx, message, &mut sig, Some(&mut rng)) + .expect("Error with sign_ctx_msg()"); + + let valid = key.verify_ctx_msg(&sig[..sig_len], ctx, message) + .expect("Error with verify_ctx_msg()"); + assert!(valid, "Valid context signature should verify"); + + // Wrong context must not verify. + let valid = key.verify_ctx_msg(&sig[..sig_len], b"wrong context", message) + .expect("Error with verify_ctx_msg() with wrong context"); + assert!(!valid, "Wrong context should not verify"); +} + +/// Export both keys, re-import them separately, and verify that: +/// - a signature from the original key is accepted by a public-key-only +/// import, and +/// - the re-imported private key can sign messages that verify with the +/// original public key. +#[test] +#[cfg(all(dilithium_make_key, dilithium_import, dilithium_export, dilithium_sign, dilithium_verify))] +fn test_import_export_level44() { + common::setup(); + let mut rng = RNG::new().expect("Error creating RNG"); + let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + .expect("Error with generate()"); + + let priv_size = key.size().expect("Error with size()"); + let pub_size = key.pub_size().expect("Error with pub_size()"); + let sig_size = key.sig_size().expect("Error with sig_size()"); + + let mut priv_buf = vec![0u8; priv_size]; + let mut pub_buf = vec![0u8; pub_size]; + key.export_key(&mut priv_buf, &mut pub_buf).expect("Error with export_key()"); + + // Verify export_public and export_private return the same bytes. + let mut pub_buf2 = vec![0u8; pub_size]; + let pub_written = key.export_public(&mut pub_buf2).expect("Error with export_public()"); + assert_eq!(pub_written, pub_size); + assert_eq!(pub_buf2, pub_buf); + + let mut priv_buf2 = vec![0u8; priv_size]; + let priv_written = key.export_private(&mut priv_buf2).expect("Error with export_private()"); + assert_eq!(priv_written, priv_size); + assert_eq!(priv_buf2, priv_buf); + + // Sign with the original key. + let message = b"Import/export test message"; + let mut sig = vec![0u8; sig_size]; + let sig_len = key.sign_msg(message, &mut sig, Some(&mut rng)) + .expect("Error with sign_msg()"); + + // Re-import public key only and verify. + let mut pub_key = Dilithium::new().expect("Error with new()"); + pub_key.set_level(Dilithium::LEVEL_44).expect("Error with set_level()"); + pub_key.import_public(&pub_buf).expect("Error with import_public()"); + let valid = pub_key.verify_msg(&sig[..sig_len], message) + .expect("Error with verify_msg() via imported public key"); + assert!(valid, "Imported public key should accept original signature"); + + // Re-import private key, sign a message, and verify with the original key. + let mut priv_key = Dilithium::new().expect("Error with new()"); + priv_key.set_level(Dilithium::LEVEL_44).expect("Error with set_level()"); + priv_key.import_private(&priv_buf).expect("Error with import_private()"); + let mut sig2 = vec![0u8; sig_size]; + let sig2_len = priv_key.sign_msg(message, &mut sig2, Some(&mut rng)) + .expect("Error with sign_msg() from imported private key"); + let valid = key.verify_msg(&sig2[..sig2_len], message) + .expect("Error with verify_msg() after import_private"); + assert!(valid, "Signature from re-imported private key should verify"); +} + +/// Export both keys, import them together via `import_key()`, then sign and +/// verify using the re-imported key pair. +#[test] +#[cfg(all(dilithium_make_key, dilithium_import, dilithium_export, dilithium_sign, dilithium_verify))] +fn test_import_key_level44() { + common::setup(); + let mut rng = RNG::new().expect("Error creating RNG"); + let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + .expect("Error with generate()"); + + let priv_size = key.size().expect("Error with size()"); + let pub_size = key.pub_size().expect("Error with pub_size()"); + let sig_size = key.sig_size().expect("Error with sig_size()"); + + let mut priv_buf = vec![0u8; priv_size]; + let mut pub_buf = vec![0u8; pub_size]; + key.export_key(&mut priv_buf, &mut pub_buf).expect("Error with export_key()"); + + let mut key2 = Dilithium::new().expect("Error with new()"); + key2.set_level(Dilithium::LEVEL_44).expect("Error with set_level()"); + key2.import_key(&priv_buf, &pub_buf).expect("Error with import_key()"); + + let message = b"import_key round-trip"; + let mut sig = vec![0u8; sig_size]; + let sig_len = key2.sign_msg(message, &mut sig, Some(&mut rng)) + .expect("Error with sign_msg() from imported key pair"); + let valid = key.verify_msg(&sig[..sig_len], message) + .expect("Error with verify_msg()"); + assert!(valid, "Imported key pair should produce valid signatures"); +} + +/// Verify that `generate_from_seed()` is deterministic: the same seed +/// produces the same key pair on repeated calls. +#[test] +#[cfg(all(dilithium_make_key_from_seed, dilithium_export))] +fn test_generate_from_seed_determinism() { + common::setup(); + // DILITHIUM_PRIV_SEED_SZ = 64 bytes + let seed = [0x42u8; 64]; + + let mut key1 = Dilithium::generate_from_seed(Dilithium::LEVEL_44, &seed) + .expect("Error with generate_from_seed() first call"); + let mut key2 = Dilithium::generate_from_seed(Dilithium::LEVEL_44, &seed) + .expect("Error with generate_from_seed() second call"); + + let pub_size = key1.pub_size().expect("Error with pub_size()"); + let mut pub1 = vec![0u8; pub_size]; + let mut pub2 = vec![0u8; pub_size]; + key1.export_public(&mut pub1).expect("Error with export_public() key1"); + key2.export_public(&mut pub2).expect("Error with export_public() key2"); + assert_eq!(pub1, pub2, "Same seed must yield same public key"); + + let priv_size = key1.size().expect("Error with size()"); + let mut priv1 = vec![0u8; priv_size]; + let mut priv2 = vec![0u8; priv_size]; + key1.export_private(&mut priv1).expect("Error with export_private() key1"); + key2.export_private(&mut priv2).expect("Error with export_private() key2"); + assert_eq!(priv1, priv2, "Same seed must yield same private key"); +} + +/// Verify that `sign_msg_with_seed()` is deterministic: the same key, +/// message, and signing seed always produce the same signature bytes, and +/// the signature verifies correctly. +#[test] +#[cfg(all(dilithium_make_key_from_seed, dilithium_sign_with_seed, dilithium_verify))] +fn test_sign_with_seed_determinism() { + common::setup(); + // DILITHIUM_PRIV_SEED_SZ = 64 bytes + let key_seed = [0x42u8; 64]; + // DILITHIUM_RND_SZ = 32 bytes + let sign_seed = [0x55u8; 32]; + let message = b"Deterministic ML-DSA signing test"; + + let mut key = Dilithium::generate_from_seed(Dilithium::LEVEL_44, &key_seed) + .expect("Error with generate_from_seed()"); + + let sig_size = key.sig_size().expect("Error with sig_size()"); + let mut sig1 = vec![0u8; sig_size]; + let mut sig2 = vec![0u8; sig_size]; + + let len1 = key.sign_msg_with_seed(message, &mut sig1, &sign_seed) + .expect("Error with sign_msg_with_seed() first call"); + let len2 = key.sign_msg_with_seed(message, &mut sig2, &sign_seed) + .expect("Error with sign_msg_with_seed() second call"); + + assert_eq!(len1, len2, "Signature lengths must match"); + assert_eq!(sig1[..len1], sig2[..len2], "Same inputs must yield same signature"); + + let valid = key.verify_msg(&sig1[..len1], message) + .expect("Error with verify_msg()"); + assert!(valid, "Deterministically signed message should verify"); +} + +/// Verify that `sign_ctx_msg_with_seed()` is deterministic and that the +/// produced signature verifies with `verify_ctx_msg()`. +#[test] +#[cfg(all(dilithium_make_key_from_seed, dilithium_sign_with_seed, dilithium_verify))] +fn test_sign_ctx_with_seed_determinism() { + common::setup(); + let key_seed = [0x11u8; 64]; + let sign_seed = [0x22u8; 32]; + let message = b"Context deterministic signing test"; + let ctx = b"test-context"; + + let mut key = Dilithium::generate_from_seed(Dilithium::LEVEL_44, &key_seed) + .expect("Error with generate_from_seed()"); + + let sig_size = key.sig_size().expect("Error with sig_size()"); + let mut sig1 = vec![0u8; sig_size]; + let mut sig2 = vec![0u8; sig_size]; + + let len1 = key.sign_ctx_msg_with_seed(ctx, message, &mut sig1, &sign_seed) + .expect("Error with sign_ctx_msg_with_seed() first call"); + let len2 = key.sign_ctx_msg_with_seed(ctx, message, &mut sig2, &sign_seed) + .expect("Error with sign_ctx_msg_with_seed() second call"); + + assert_eq!(len1, len2); + assert_eq!(sig1[..len1], sig2[..len2], "Same inputs must yield same signature"); + + let valid = key.verify_ctx_msg(&sig1[..len1], ctx, message) + .expect("Error with verify_ctx_msg()"); + assert!(valid, "Context-signed message should verify"); +} + +/// Verify that `generate_from_seed()` + `sign_msg_with_seed()` + +/// `verify_msg()` work across all three security levels. +#[test] +#[cfg(all(dilithium_make_key_from_seed, dilithium_sign_with_seed, dilithium_verify))] +fn test_seed_sign_verify_all_levels() { + common::setup(); + let key_seed = [0xABu8; 64]; + let sign_seed = [0xCDu8; 32]; + let message = b"All-levels seed sign/verify test"; + + for level in [Dilithium::LEVEL_44, Dilithium::LEVEL_65, Dilithium::LEVEL_87] { + let mut key = Dilithium::generate_from_seed(level, &key_seed) + .expect("Error with generate_from_seed()"); + let sig_size = key.sig_size().expect("Error with sig_size()"); + let mut sig = vec![0u8; sig_size]; + let sig_len = key.sign_msg_with_seed(message, &mut sig, &sign_seed) + .expect("Error with sign_msg_with_seed()"); + let valid = key.verify_msg(&sig[..sig_len], message) + .expect("Error with verify_msg()"); + assert!(valid, "Level {} seed-signed message should verify", level); + } +} From 558ae34f68a9dcae828963cbd7c28779b176e87c Mon Sep 17 00:00:00 2001 From: Andrew Hutchings Date: Mon, 23 Feb 2026 14:26:31 +0000 Subject: [PATCH 03/36] Fix CRL_STATIC_REVOKED_LIST binary search bugs in FindRevokedSerial The CRL_STATIC_REVOKED_LIST code path stored revoked certificates in a fixed array but never sorted it after parsing, causing binary search to silently miss revoked serials when entries arrived in non-sorted wire order. Additionally, comparisons used rc[0].serialSz instead of rc[mid].serialSz, omitted the length-equality check before XMEMCMP, and ignored the serialHash lookup path entirely (causing a NULL dereference when hash-based lookup was used). Fixes: - Sort the revoked cert array in InitCRL_Entry after populating it - Use rc[mid].serialSz instead of rc->serialSz in binary search - Add serialSz equality check before XMEMCMP, matching linked-list path - Implement serialHash-based linear scan for hash lookup callers Add unit test that loads a CRL with serials in unsorted wire order and verifies that a revoked certificate is correctly detected. --- src/crl.c | 93 ++++++++++++++++++++++++++------ tests/api/test_certman.c | 112 +++++++++++++++++++++++++++++++++++++++ tests/api/test_certman.h | 2 + 3 files changed, 190 insertions(+), 17 deletions(-) diff --git a/src/crl.c b/src/crl.c index 524adee47b..7836c83c88 100644 --- a/src/crl.c +++ b/src/crl.c @@ -98,6 +98,35 @@ int InitCRL(WOLFSSL_CRL* crl, WOLFSSL_CERT_MANAGER* cm) } +#ifdef CRL_STATIC_REVOKED_LIST +/* Compare two RevokedCert entries by (serialSz, serialNumber) for sorting. + * Returns < 0, 0, or > 0 like memcmp. */ +static int CompareRevokedCert(const RevokedCert* a, const RevokedCert* b) +{ + if (a->serialSz != b->serialSz) + return a->serialSz - b->serialSz; + return XMEMCMP(a->serialNumber, b->serialNumber, (size_t)a->serialSz); +} + +/* Sort revoked cert array in-place using insertion sort. The array is bounded + * by CRL_MAX_REVOKED_CERTS so O(n^2) is fine. */ +static void SortCRL_CertList(RevokedCert* certs, int totalCerts) +{ + int i, j; + RevokedCert tmp; + + for (i = 1; i < totalCerts; i++) { + XMEMCPY(&tmp, &certs[i], sizeof(RevokedCert)); + j = i - 1; + while (j >= 0 && CompareRevokedCert(&certs[j], &tmp) > 0) { + XMEMCPY(&certs[j + 1], &certs[j], sizeof(RevokedCert)); + j--; + } + XMEMCPY(&certs[j + 1], &tmp, sizeof(RevokedCert)); + } +} +#endif /* CRL_STATIC_REVOKED_LIST */ + /* Initialize CRL Entry */ static int InitCRL_Entry(CRL_Entry* crle, DecodedCRL* dcrl, const byte* buff, int verified, void* heap) @@ -132,12 +161,15 @@ static int InitCRL_Entry(CRL_Entry* crle, DecodedCRL* dcrl, const byte* buff, #endif #ifdef CRL_STATIC_REVOKED_LIST /* ParseCRL_CertList() has already cached the Revoked certs into - the crle->certs array */ + the crle->certs array. Sort it so binary search in + FindRevokedSerial works correctly. */ + crle->totalCerts = dcrl->totalCerts; + SortCRL_CertList(crle->certs, crle->totalCerts); #else crle->certs = dcrl->certs; /* take ownership */ + crle->totalCerts = dcrl->totalCerts; #endif dcrl->certs = NULL; - crle->totalCerts = dcrl->totalCerts; crle->crlNumberSet = dcrl->crlNumberSet; if (crle->crlNumberSet) { XMEMCPY(crle->crlNumber, dcrl->crlNumber, sizeof(crle->crlNumber)); @@ -313,25 +345,52 @@ static int FindRevokedSerial(RevokedCert* rc, byte* serial, int serialSz, int ret = 0; byte hash[SIGNER_DIGEST_SIZE]; #ifdef CRL_STATIC_REVOKED_LIST - /* do binary search */ - int low, high, mid; + if (serialHash == NULL) { + /* Binary search by (serialSz, serialNumber). The array was sorted in + * InitCRL_Entry by the same comparison key. */ + int low = 0; + int high = totalCerts - 1; - low = 0; - high = totalCerts - 1; + while (low <= high) { + int mid = (low + high) / 2; + int cmp; - while (low <= high) { - mid = (low + high) / 2; + /* Compare by serial size first, then by serial content. Shorter + * serials sort before longer ones. */ + if (rc[mid].serialSz != serialSz) { + cmp = rc[mid].serialSz - serialSz; + } + else { + cmp = XMEMCMP(rc[mid].serialNumber, serial, + (size_t)rc[mid].serialSz); + } - if (XMEMCMP(rc[mid].serialNumber, serial, rc->serialSz) < 0) { - low = mid + 1; + if (cmp < 0) { + low = mid + 1; + } + else if (cmp > 0) { + high = mid - 1; + } + else { + WOLFSSL_MSG("Cert revoked"); + ret = CRL_CERT_REVOKED; + break; + } } - else if (XMEMCMP(rc[mid].serialNumber, serial, rc->serialSz) > 0) { - high = mid - 1; - } - else { - WOLFSSL_MSG("Cert revoked"); - ret = CRL_CERT_REVOKED; - break; + } + else { + /* Hash-based lookup -- linear scan required since the array is sorted + * by serial number, not by hash. */ + int i; + for (i = 0; i < totalCerts; i++) { + ret = CalcHashId(rc[i].serialNumber, (word32)rc[i].serialSz, hash); + if (ret != 0) + break; + if (XMEMCMP(hash, serialHash, SIGNER_DIGEST_SIZE) == 0) { + WOLFSSL_MSG("Cert revoked"); + ret = CRL_CERT_REVOKED; + break; + } } } #else diff --git a/tests/api/test_certman.c b/tests/api/test_certman.c index 1e72ee4436..549b983945 100644 --- a/tests/api/test_certman.c +++ b/tests/api/test_certman.c @@ -1781,6 +1781,118 @@ int test_wolfSSL_CertManagerCRL(void) return EXPECT_RESULT(); } +int test_wolfSSL_CRL_static_revoked_list(void) +{ + EXPECT_DECLS; +#if defined(CRL_STATIC_REVOKED_LIST) && defined(HAVE_CRL) && \ + !defined(NO_RSA) && !defined(NO_CERTS) + /* CRL signed by certs/ca-cert.pem that revokes serials 05, 02, 01 in that + * (unsorted) wire order. The unsorted order exposes a binary search bug in + * FindRevokedSerial when CRL_STATIC_REVOKED_LIST is enabled: the revoked + * cert array is never sorted after parsing, so binary search misses entries + * that are out of order. + * + * Generated with Python cryptography library: + * builder.add_revoked_certificate(serial=5) + * builder.add_revoked_certificate(serial=2) + * builder.add_revoked_certificate(serial=1) + * crl = builder.sign(ca_key, hashes.SHA256()) + */ + static const unsigned char crl_multi_revoked[] = { + 0x30, 0x82, 0x02, 0x1D, 0x30, 0x82, 0x01, 0x05, + 0x02, 0x01, 0x01, 0x30, 0x0D, 0x06, 0x09, 0x2A, + 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B, + 0x05, 0x00, 0x30, 0x81, 0x94, 0x31, 0x0B, 0x30, + 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, + 0x55, 0x53, 0x31, 0x10, 0x30, 0x0E, 0x06, 0x03, + 0x55, 0x04, 0x08, 0x0C, 0x07, 0x4D, 0x6F, 0x6E, + 0x74, 0x61, 0x6E, 0x61, 0x31, 0x10, 0x30, 0x0E, + 0x06, 0x03, 0x55, 0x04, 0x07, 0x0C, 0x07, 0x42, + 0x6F, 0x7A, 0x65, 0x6D, 0x61, 0x6E, 0x31, 0x11, + 0x30, 0x0F, 0x06, 0x03, 0x55, 0x04, 0x0A, 0x0C, + 0x08, 0x53, 0x61, 0x77, 0x74, 0x6F, 0x6F, 0x74, + 0x68, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, + 0x04, 0x0B, 0x0C, 0x0A, 0x43, 0x6F, 0x6E, 0x73, + 0x75, 0x6C, 0x74, 0x69, 0x6E, 0x67, 0x31, 0x18, + 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0C, + 0x0F, 0x77, 0x77, 0x77, 0x2E, 0x77, 0x6F, 0x6C, + 0x66, 0x73, 0x73, 0x6C, 0x2E, 0x63, 0x6F, 0x6D, + 0x31, 0x1F, 0x30, 0x1D, 0x06, 0x09, 0x2A, 0x86, + 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x09, 0x01, 0x16, + 0x10, 0x69, 0x6E, 0x66, 0x6F, 0x40, 0x77, 0x6F, + 0x6C, 0x66, 0x73, 0x73, 0x6C, 0x2E, 0x63, 0x6F, + 0x6D, 0x17, 0x0D, 0x32, 0x36, 0x30, 0x31, 0x30, + 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5A, + 0x17, 0x0D, 0x33, 0x36, 0x30, 0x31, 0x30, 0x31, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5A, 0x30, + 0x3C, 0x30, 0x12, 0x02, 0x01, 0x05, 0x17, 0x0D, + 0x32, 0x33, 0x30, 0x31, 0x30, 0x31, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x5A, 0x30, 0x12, 0x02, + 0x01, 0x02, 0x17, 0x0D, 0x32, 0x33, 0x30, 0x32, + 0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x5A, 0x30, 0x12, 0x02, 0x01, 0x01, 0x17, 0x0D, + 0x32, 0x33, 0x30, 0x33, 0x30, 0x31, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x5A, 0x30, 0x0D, 0x06, + 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, + 0x01, 0x0B, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, + 0x00, 0x15, 0x9F, 0xC1, 0x9E, 0x17, 0xB3, 0x5A, + 0xF1, 0x48, 0xA5, 0x87, 0x2A, 0x84, 0xD1, 0x93, + 0x8D, 0x19, 0x24, 0xCB, 0xC5, 0x32, 0x56, 0x10, + 0x6C, 0x4D, 0xF5, 0xD1, 0x9A, 0xC0, 0x1A, 0x8B, + 0x1C, 0x84, 0x6B, 0x4B, 0x20, 0xA7, 0xA4, 0x2C, + 0x11, 0x5C, 0x23, 0xBD, 0x0C, 0xB1, 0x33, 0xBE, + 0x38, 0x1B, 0xCB, 0xDB, 0x8E, 0xD4, 0x0F, 0x62, + 0x0D, 0xB5, 0x18, 0x21, 0x28, 0x0B, 0x77, 0xB9, + 0xB4, 0xA8, 0xE9, 0xA0, 0x25, 0x00, 0x83, 0xED, + 0x64, 0x49, 0x8E, 0x52, 0xD9, 0x8D, 0xAF, 0xC2, + 0x16, 0x3E, 0xD3, 0x93, 0x09, 0xB9, 0x18, 0xBB, + 0x6C, 0x41, 0xDF, 0x59, 0x59, 0x53, 0x8C, 0x64, + 0x8B, 0xD1, 0x9D, 0xBB, 0x92, 0x8F, 0xB2, 0x26, + 0x27, 0x78, 0x41, 0xFB, 0xF8, 0xB1, 0x2F, 0x8F, + 0xA1, 0x85, 0xB6, 0xC7, 0x8E, 0x42, 0x72, 0xEF, + 0xF4, 0x3F, 0xC4, 0xAF, 0x40, 0x95, 0xCA, 0x94, + 0xE5, 0x88, 0x89, 0x18, 0x32, 0x54, 0xC3, 0xC4, + 0xBE, 0x7E, 0x48, 0x1B, 0x3D, 0xB3, 0x6C, 0x11, + 0x54, 0x6F, 0x9E, 0xFE, 0x09, 0x5B, 0x72, 0x3F, + 0xD7, 0xA0, 0x02, 0xFF, 0x43, 0x01, 0xFE, 0x23, + 0xF8, 0x72, 0xCD, 0xA9, 0x76, 0x36, 0x31, 0x78, + 0x21, 0xCB, 0x0E, 0xC2, 0x25, 0x8D, 0x0B, 0x4C, + 0x2C, 0xAA, 0x6A, 0x80, 0x6E, 0xE2, 0x1E, 0xAC, + 0x70, 0x5D, 0x4A, 0xAA, 0x56, 0x17, 0xF0, 0x2D, + 0xA2, 0x2A, 0x4E, 0x2B, 0xC8, 0xC9, 0x87, 0x8E, + 0x07, 0xEB, 0xD8, 0x36, 0x42, 0x39, 0xA0, 0xA4, + 0xF6, 0x34, 0xC2, 0x5F, 0xE1, 0x21, 0x07, 0x50, + 0x4B, 0x37, 0x15, 0x7D, 0xF9, 0x18, 0x54, 0x13, + 0xC0, 0x1D, 0x0A, 0x27, 0x3A, 0x63, 0xD2, 0xC3, + 0xD5, 0x57, 0x5E, 0x67, 0x56, 0x65, 0x9E, 0x2E, + 0x4D, 0xB4, 0x96, 0x54, 0x7A, 0x3D, 0xFD, 0xF9, + 0xCF, 0xCD, 0x10, 0x65, 0x05, 0x97, 0x53, 0x72, + 0x12 + }; + WOLFSSL_CERT_MANAGER* cm = NULL; + + /* Set up CertManager with the CA and CRL checking enabled */ + ExpectNotNull(cm = wolfSSL_CertManagerNew()); + ExpectIntEQ(wolfSSL_CertManagerLoadCA(cm, "./certs/ca-cert.pem", NULL), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CertManagerEnableCRL(cm, WOLFSSL_CRL_CHECKALL), + WOLFSSL_SUCCESS); + + /* Load the CRL that revokes serials {05, 02, 01} in unsorted wire order */ + ExpectIntEQ(wolfSSL_CertManagerLoadCRLBuffer(cm, crl_multi_revoked, + sizeof(crl_multi_revoked), WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + + /* server-cert.pem has serial 01, which is in the CRL but at the last + * position in the unsorted array. Binary search on unsorted data misses + * it, so this assertion fails before the bug fix. */ + ExpectIntEQ(wolfSSL_CertManagerCheckCRL(cm, server_cert_der_2048, + sizeof_server_cert_der_2048), WC_NO_ERR_TRACE(CRL_CERT_REVOKED)); + + wolfSSL_CertManagerFree(cm); +#endif + return EXPECT_RESULT(); +} + int test_wolfSSL_CRL_duplicate_extensions(void) { EXPECT_DECLS; diff --git a/tests/api/test_certman.h b/tests/api/test_certman.h index 6236cbb88d..b896475ce6 100644 --- a/tests/api/test_certman.h +++ b/tests/api/test_certman.h @@ -36,6 +36,7 @@ int test_wolfSSL_CertManagerNameConstraint3(void); int test_wolfSSL_CertManagerNameConstraint4(void); int test_wolfSSL_CertManagerNameConstraint5(void); int test_wolfSSL_CertManagerCRL(void); +int test_wolfSSL_CRL_static_revoked_list(void); int test_wolfSSL_CRL_duplicate_extensions(void); int test_wolfSSL_CertManagerCheckOCSPResponse(void); int test_various_pathlen_chains(void); @@ -53,6 +54,7 @@ int test_various_pathlen_chains(void); TEST_DECL_GROUP("certman", test_wolfSSL_CertManagerNameConstraint4), \ TEST_DECL_GROUP("certman", test_wolfSSL_CertManagerNameConstraint5), \ TEST_DECL_GROUP("certman", test_wolfSSL_CertManagerCRL), \ + TEST_DECL_GROUP("certman", test_wolfSSL_CRL_static_revoked_list), \ TEST_DECL_GROUP("certman", test_wolfSSL_CRL_duplicate_extensions), \ TEST_DECL_GROUP("certman", test_wolfSSL_CertManagerCheckOCSPResponse), \ TEST_DECL_GROUP("certman", test_various_pathlen_chains) From af329b38a8ad985a866edb4742d95e6bd90f382e Mon Sep 17 00:00:00 2001 From: Andrew Hutchings Date: Mon, 23 Feb 2026 14:55:31 +0000 Subject: [PATCH 04/36] Fix heap buffer over-read in wolfSSL_select_next_proto Add missing bounds validation in wolfSSL_select_next_proto. Three issues fixed: 1. Outer loop: no check that length byte + position stays within inLen, allowing XMEMCMP to read past the server protocol list buffer. 2. Inner loop: same missing check for clientNames/clientLen boundary. 3. No-overlap fallback unconditionally dereferences clientNames[0] even when clientLen is 0, and returns an outLen that may exceed the buffer. Also reject zero-length protocol entries (invalid per RFC 7301) to prevent infinite loops. Add unit test test_wolfSSL_select_next_proto with 8 cases covering NULL params, normal match, no overlap, malformed length overruns, zero-length entries, and empty client lists. --- src/ssl.c | 14 ++++++- tests/api.c | 119 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+), 2 deletions(-) diff --git a/src/ssl.c b/src/ssl.c index aa6b8e8515..4435a873eb 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -16662,8 +16662,12 @@ int wolfSSL_select_next_proto(unsigned char **out, unsigned char *outLen, for (i = 0; i < inLen; i += lenIn) { lenIn = in[i++]; + if (lenIn == 0 || i + lenIn > inLen) + break; for (j = 0; j < clientLen; j += lenClient) { lenClient = clientNames[j++]; + if (lenClient == 0 || j + lenClient > clientLen) + break; if (lenIn != lenClient) continue; @@ -16676,8 +16680,14 @@ int wolfSSL_select_next_proto(unsigned char **out, unsigned char *outLen, } } - *out = (unsigned char *)clientNames + 1; - *outLen = clientNames[0]; + if (clientLen > 0 && (unsigned int)clientNames[0] + 1 <= clientLen) { + *out = (unsigned char *)clientNames + 1; + *outLen = clientNames[0]; + } + else { + *out = (unsigned char *)clientNames; + *outLen = 0; + } return WOLFSSL_NPN_NO_OVERLAP; } diff --git a/tests/api.c b/tests/api.c index 3af30a5542..d431bf875b 100644 --- a/tests/api.c +++ b/tests/api.c @@ -9865,6 +9865,124 @@ static int test_wolfSSL_set_alpn_protos(void) return res; } +static int test_wolfSSL_select_next_proto(void) +{ + EXPECT_DECLS; + unsigned char *out = NULL; + unsigned char outLen = 0; + int ret; + + /* Wire format: length-prefixed protocol names */ + unsigned char serverList[] = { + 8, 'h','t','t','p','/','1','.','1', + 6, 's','p','d','y','/','2' + }; + unsigned int serverLen = sizeof(serverList); + + unsigned char clientList[] = { + 6, 's','p','d','y','/','2' + }; + unsigned int clientLen = sizeof(clientList); + + unsigned char clientListHttp[] = { + 8, 'h','t','t','p','/','1','.','1' + }; + unsigned int clientListHttpLen = sizeof(clientListHttp); + + unsigned char clientListNoMatch[] = { + 6, 's','p','d','y','/','3' + }; + unsigned int clientListNoMatchLen = sizeof(clientListNoMatch); + + /* Test 1: NULL parameters return UNSUPPORTED */ + ExpectIntEQ(wolfSSL_select_next_proto(NULL, &outLen, serverList, + serverLen, clientList, clientLen), WOLFSSL_NPN_UNSUPPORTED); + ExpectIntEQ(wolfSSL_select_next_proto(&out, NULL, serverList, + serverLen, clientList, clientLen), WOLFSSL_NPN_UNSUPPORTED); + ExpectIntEQ(wolfSSL_select_next_proto(&out, &outLen, NULL, + serverLen, clientList, clientLen), WOLFSSL_NPN_UNSUPPORTED); + ExpectIntEQ(wolfSSL_select_next_proto(&out, &outLen, serverList, + serverLen, NULL, clientLen), WOLFSSL_NPN_UNSUPPORTED); + + /* Test 2: Normal match - client wants "spdy/2", server offers it */ + out = NULL; + outLen = 0; + ret = wolfSSL_select_next_proto(&out, &outLen, serverList, serverLen, + clientList, clientLen); + ExpectIntEQ(ret, WOLFSSL_NPN_NEGOTIATED); + ExpectIntEQ(outLen, 6); + ExpectNotNull(out); + ExpectIntEQ(XMEMCMP(out, "spdy/2", 6), 0); + + /* Test 3: No overlap - server offers "http/1.1,spdy/2", client wants + * "spdy/3". Falls back to first client protocol. */ + out = NULL; + outLen = 0; + ret = wolfSSL_select_next_proto(&out, &outLen, serverList, serverLen, + clientListNoMatch, clientListNoMatchLen); + ExpectIntEQ(ret, WOLFSSL_NPN_NO_OVERLAP); + ExpectIntEQ(outLen, 6); + ExpectNotNull(out); + ExpectIntEQ(XMEMCMP(out, "spdy/3", 6), 0); + + /* Test 4: Malformed server list - length byte overruns buffer. + * Must NOT crash (heap over-read). */ + { + unsigned char malformedServer[] = { 200, 'h','t','t','p' }; + out = NULL; + outLen = 0; + ret = wolfSSL_select_next_proto(&out, &outLen, malformedServer, + sizeof(malformedServer), clientList, clientLen); + ExpectIntEQ(ret, WOLFSSL_NPN_NO_OVERLAP); + } + + /* Test 5: Malformed client list - length byte overruns buffer. + * Must NOT crash. */ + { + unsigned char malformedClient[] = { 200, 's','p','d','y' }; + out = NULL; + outLen = 0; + ret = wolfSSL_select_next_proto(&out, &outLen, serverList, serverLen, + malformedClient, sizeof(malformedClient)); + ExpectIntEQ(ret, WOLFSSL_NPN_NO_OVERLAP); + } + + /* Test 6: Zero-length entry in server list - must NOT infinite loop */ + { + unsigned char zeroLenServer[] = { 0, 6, 's','p','d','y','/','2' }; + out = NULL; + outLen = 0; + ret = wolfSSL_select_next_proto(&out, &outLen, zeroLenServer, + sizeof(zeroLenServer), clientList, clientLen); + /* Zero-length entry causes break, so no match found */ + ExpectIntEQ(ret, WOLFSSL_NPN_NO_OVERLAP); + } + + /* Test 7: Empty client list (clientLen == 0) - must NOT dereference + * clientNames[0]. */ + { + unsigned char emptyClient[] = { 0 }; + out = NULL; + outLen = 0; + ret = wolfSSL_select_next_proto(&out, &outLen, serverList, serverLen, + emptyClient, 0); + ExpectIntEQ(ret, WOLFSSL_NPN_NO_OVERLAP); + ExpectIntEQ(outLen, 0); + } + + /* Test 8: First protocol match - both start with "http/1.1" */ + out = NULL; + outLen = 0; + ret = wolfSSL_select_next_proto(&out, &outLen, serverList, serverLen, + clientListHttp, clientListHttpLen); + ExpectIntEQ(ret, WOLFSSL_NPN_NEGOTIATED); + ExpectIntEQ(outLen, 8); + ExpectNotNull(out); + ExpectIntEQ(XMEMCMP(out, "http/1.1", 8), 0); + + return EXPECT_RESULT(); +} + #endif /* HAVE_ALPN_PROTOS_SUPPORT */ static int test_wolfSSL_wolfSSL_UseSecureRenegotiation(void) @@ -32842,6 +32960,7 @@ TEST_CASE testCases[] = { #ifdef HAVE_ALPN_PROTOS_SUPPORT /* Uses Assert in handshake callback. */ TEST_DECL(test_wolfSSL_set_alpn_protos), + TEST_DECL(test_wolfSSL_select_next_proto), #endif TEST_DECL(test_tls_ems_downgrade), TEST_DECL(test_wolfSSL_DisableExtendedMasterSecret), From 10325b458770056cb96af1bcf493323629a48f4d Mon Sep 17 00:00:00 2001 From: Andrew Hutchings Date: Mon, 23 Feb 2026 15:03:50 +0000 Subject: [PATCH 05/36] Fix integer underflow in ECH innerClientHelloLen parsing Add bounds check before subtracting WC_AES_BLOCK_SIZE from the attacker-controlled innerClientHelloLen field in TLSX_ECH_Parse(). Values 0-15 caused a word16 underflow to ~65K, leading to a heap buffer overflow write via XMEMSET and heap buffer over-read via wc_AesGcmDecrypt. Return BAD_FUNC_ARG if the field is too small. --- src/tls.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tls.c b/src/tls.c index 207c873b46..a4f83e0867 100644 --- a/src/tls.c +++ b/src/tls.c @@ -13605,6 +13605,9 @@ static int TLSX_ECH_Parse(WOLFSSL* ssl, const byte* readBuf, word16 size, } /* read hello inner len */ ato16(readBuf_p, &ech->innerClientHelloLen); + if (ech->innerClientHelloLen < WC_AES_BLOCK_SIZE) { + return BAD_FUNC_ARG; + } ech->innerClientHelloLen -= WC_AES_BLOCK_SIZE; readBuf_p += 2; ech->outerClientPayload = readBuf_p; From 599eec673e1e26957775e0f2f92ab24f22127d46 Mon Sep 17 00:00:00 2001 From: Andrew Hutchings Date: Mon, 23 Feb 2026 15:30:33 +0000 Subject: [PATCH 06/36] Fix ImportKeyState wordAdj always-zero bug in DTLS session import In ImportKeyState(), wordAdj was always zero because it was computed after clamping wordCount, and the subtraction direction was reversed. This caused misaligned parsing of all subsequent fields when importing state from a peer compiled with a larger WOLFSSL_DTLS_WINDOW_WORDS. Fix both window and prevWindow blocks to compute the adjustment before clamping, with the correct subtraction direction. Add test that imports a state buffer with wordCount > WOLFSSL_DTLS_WINDOW_WORDS to verify the fix. --- src/internal.c | 4 +- tests/api.c | 112 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 2 deletions(-) diff --git a/src/internal.c b/src/internal.c index eb59f8133c..2017fc9977 100644 --- a/src/internal.c +++ b/src/internal.c @@ -1123,8 +1123,8 @@ static int ImportKeyState(WOLFSSL* ssl, const byte* exp, word32 len, byte ver, idx += OPAQUE16_LEN; if (wordCount > WOLFSSL_DTLS_WINDOW_WORDS) { + wordAdj = (wordCount - WOLFSSL_DTLS_WINDOW_WORDS) * sizeof(word32); wordCount = WOLFSSL_DTLS_WINDOW_WORDS; - wordAdj = (WOLFSSL_DTLS_WINDOW_WORDS - wordCount) * sizeof(word32); } XMEMSET(keys->peerSeq[0].window, 0xFF, DTLS_SEQ_SZ); @@ -1139,8 +1139,8 @@ static int ImportKeyState(WOLFSSL* ssl, const byte* exp, word32 len, byte ver, idx += OPAQUE16_LEN; if (wordCount > WOLFSSL_DTLS_WINDOW_WORDS) { + wordAdj = (wordCount - WOLFSSL_DTLS_WINDOW_WORDS) * sizeof(word32); wordCount = WOLFSSL_DTLS_WINDOW_WORDS; - wordAdj = (WOLFSSL_DTLS_WINDOW_WORDS - wordCount) * sizeof(word32); } XMEMSET(keys->peerSeq[0].prevWindow, 0xFF, DTLS_SEQ_SZ); diff --git a/tests/api.c b/tests/api.c index d431bf875b..730b3e5897 100644 --- a/tests/api.c +++ b/tests/api.c @@ -9172,6 +9172,117 @@ static int test_wolfSSL_dtls_export_peers(void) return EXPECT_RESULT(); } +/* Test that ImportKeyState correctly skips extra window words when importing + * state from a peer compiled with a larger WOLFSSL_DTLS_WINDOW_WORDS. */ +static int test_wolfSSL_dtls_import_state_extra_window_words(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_DTLS) && defined(WOLFSSL_SESSION_EXPORT) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + unsigned int stateSz = 0; + byte* state = NULL; + byte* modified = NULL; + unsigned int modifiedSz; + word16 origKeyLen; + word16 origTotalLen; + /* Offset from start of key state data to the first wordCount field. + * Layout: 4 sequence numbers (16 bytes) + DTLS-specific fields (42 bytes) + + * encryptSz(4) + padSz(4) + encryptionOn(1) + decryptedCur(1) = 68 */ + const int keyStateWindowOffset = 68; + /* Buffer header: 2 proto + 2 total_len + 2 key_len = 6 */ + const int headerSz = 6; + int idx, modIdx; + int extraPerWindow = 2 * (int)sizeof(word32); /* 8 bytes extra per window */ + int totalExtra = extraPerWindow * 2; /* 16 bytes extra total */ + + /* Create DTLS context and SSL object */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLSv1_2_server_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + + /* Get required buffer size and export state-only */ + ExpectIntEQ(wolfSSL_dtls_export_state_only(ssl, NULL, &stateSz), 0); + ExpectIntGT((int)stateSz, 0); + state = (byte*)XMALLOC(stateSz, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(state); + ExpectIntGT(wolfSSL_dtls_export_state_only(ssl, state, &stateSz), 0); + + /* Build a modified buffer that simulates a peer with + * WOLFSSL_DTLS_WINDOW_WORDS = WOLFSSL_DTLS_WINDOW_WORDS + 2. + * Each window section gets 2 extra word32 values (8 bytes). + * Two windows => 16 extra bytes total. */ + modifiedSz = stateSz + totalExtra; + modified = (byte*)XMALLOC(modifiedSz, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(modified); + + if (EXPECT_SUCCESS()) { + int windowWords = WOLFSSL_DTLS_WINDOW_WORDS; + int windowDataSz = windowWords * (int)sizeof(word32); + + XMEMSET(modified, 0, modifiedSz); + + /* Copy protocol/version bytes (first 2 bytes) */ + XMEMCPY(modified, state, 2); + + /* Read original total length and key state length */ + ato16(state + 2, &origTotalLen); + ato16(state + 4, &origKeyLen); + + /* Write updated total length and key state length */ + c16toa((word16)(origTotalLen + totalExtra), modified + 2); + c16toa((word16)(origKeyLen + totalExtra), modified + 4); + + /* Copy key state data up to first window section */ + idx = headerSz; + modIdx = headerSz; + XMEMCPY(modified + modIdx, state + idx, keyStateWindowOffset); + idx += keyStateWindowOffset; + modIdx += keyStateWindowOffset; + + /* First window: write increased wordCount */ + c16toa((word16)(windowWords + 2), modified + modIdx); + idx += OPAQUE16_LEN; + modIdx += OPAQUE16_LEN; + + /* Copy original window data */ + XMEMCPY(modified + modIdx, state + idx, windowDataSz); + idx += windowDataSz; + modIdx += windowDataSz; + + /* Insert 2 extra word32 padding values */ + XMEMSET(modified + modIdx, 0, extraPerWindow); + modIdx += extraPerWindow; + + /* Second window (prevWindow): same transformation */ + c16toa((word16)(windowWords + 2), modified + modIdx); + idx += OPAQUE16_LEN; + modIdx += OPAQUE16_LEN; + + XMEMCPY(modified + modIdx, state + idx, windowDataSz); + idx += windowDataSz; + modIdx += windowDataSz; + + XMEMSET(modified + modIdx, 0, extraPerWindow); + modIdx += extraPerWindow; + + /* Copy remainder of key state (after both windows) */ + XMEMCPY(modified + modIdx, state + idx, stateSz - idx); + } + + /* Import the modified state - should succeed with the fix */ + wolfSSL_free(ssl); + ssl = NULL; + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntGT(wolfSSL_dtls_import(ssl, modified, modifiedSz), 0); + + XFREE(state, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(modified, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + static int test_wolfSSL_UseTrustedCA(void) { EXPECT_DECLS; @@ -32913,6 +33024,7 @@ TEST_CASE testCases[] = { TEST_DECL(test_wolfSSL_tls_export), #endif TEST_DECL(test_wolfSSL_dtls_export_peers), + TEST_DECL(test_wolfSSL_dtls_import_state_extra_window_words), TEST_DECL(test_wolfSSL_SetMinVersion), TEST_DECL(test_wolfSSL_CTX_SetMinVersion), From 8a75e7d1c707ae34e0cd273dcf48e10a0c2e002c Mon Sep 17 00:00:00 2001 From: Sean Parkinson Date: Tue, 24 Feb 2026 09:13:30 +1000 Subject: [PATCH 07/36] ML-KEM decapsulate: check for H Decapsulation needs H, hash of public key, and it is not present if you have a new key made from a seed. Code changed to check for and create H in decapsulate. --- wolfcrypt/src/wc_mlkem.c | 92 ++++++++++++++++++++++++---------------- wolfcrypt/test/test.c | 23 ++++++++++ 2 files changed, 78 insertions(+), 37 deletions(-) diff --git a/wolfcrypt/src/wc_mlkem.c b/wolfcrypt/src/wc_mlkem.c index 60a0850dc5..4b2a6de68c 100644 --- a/wolfcrypt/src/wc_mlkem.c +++ b/wolfcrypt/src/wc_mlkem.c @@ -950,6 +950,55 @@ static int mlkemkey_encapsulate(MlKemKey* key, const byte* m, byte* r, byte* c) } #endif +#if !defined(WOLFSSL_MLKEM_NO_ENCAPSULATE) || \ + !defined(WOLFSSL_MLKEM_NO_DECAPSULATE) +static int wc_mlkemkey_check_h(MlKemKey* key) +{ + int ret = 0; + + /* If public hash (h) is not stored against key, calculate it + * (fields set explicitly instead of using decode). + * Step 1: ... H(ek)... + */ + if ((key->flags & MLKEM_FLAG_H_SET) == 0) { + #ifndef WOLFSSL_NO_MALLOC + byte* pubKey = NULL; + word32 pubKeyLen; + #else + byte pubKey[WC_ML_KEM_MAX_PUBLIC_KEY_SIZE]; + word32 pubKeyLen; + #endif + + /* Determine how big an encoded public key will be. */ + ret = wc_KyberKey_PublicKeySize(key, &pubKeyLen); + if (ret == 0) { + #ifndef WOLFSSL_NO_MALLOC + /* Allocate dynamic memory for encoded public key. */ + pubKey = (byte*)XMALLOC(pubKeyLen, key->heap, + DYNAMIC_TYPE_TMP_BUFFER); + if (pubKey == NULL) { + ret = MEMORY_E; + } + } + if (ret == 0) { + #endif + /* Encode public key - h is hash of encoded public key. */ + ret = wc_KyberKey_EncodePublicKey(key, pubKey, pubKeyLen); + } + #ifndef WOLFSSL_NO_MALLOC + /* Dispose of encoded public key. */ + XFREE(pubKey, key->heap, DYNAMIC_TYPE_TMP_BUFFER); + #endif + } + if ((ret == 0) && ((key->flags & MLKEM_FLAG_H_SET) == 0)) { + /* Implementation issue if h not cached and flag set. */ + ret = BAD_STATE_E; + } + + return ret; +} +#endif + #ifndef WOLFSSL_MLKEM_NO_ENCAPSULATE /** * Encapsulate with random number generator and derive secret. @@ -1084,43 +1133,8 @@ int wc_MlKemKey_EncapsulateWithRandom(MlKemKey* key, unsigned char* c, } #endif - /* If public hash (h) is not stored against key, calculate it - * (fields set explicitly instead of using decode). - * Step 1: ... H(ek)... - */ - if ((ret == 0) && ((key->flags & MLKEM_FLAG_H_SET) == 0)) { - #ifndef WOLFSSL_NO_MALLOC - byte* pubKey = NULL; - word32 pubKeyLen; - #else - byte pubKey[WC_ML_KEM_MAX_PUBLIC_KEY_SIZE]; - word32 pubKeyLen = WC_ML_KEM_MAX_PUBLIC_KEY_SIZE; - #endif - - #ifndef WOLFSSL_NO_MALLOC - /* Determine how big an encoded public key will be. */ - ret = wc_KyberKey_PublicKeySize(key, &pubKeyLen); - if (ret == 0) { - /* Allocate dynamic memory for encoded public key. */ - pubKey = (byte*)XMALLOC(pubKeyLen, key->heap, - DYNAMIC_TYPE_TMP_BUFFER); - if (pubKey == NULL) { - ret = MEMORY_E; - } - } - if (ret == 0) { - #endif - /* Encode public key - h is hash of encoded public key. */ - ret = wc_KyberKey_EncodePublicKey(key, pubKey, pubKeyLen); - #ifndef WOLFSSL_NO_MALLOC - } - /* Dispose of encoded public key. */ - XFREE(pubKey, key->heap, DYNAMIC_TYPE_TMP_BUFFER); - #endif - } - if ((ret == 0) && ((key->flags & MLKEM_FLAG_H_SET) == 0)) { - /* Implementation issue if h not cached and flag set. */ - ret = BAD_STATE_E; + if (ret == 0) { + ret = wc_mlkemkey_check_h(key); } #ifdef WOLFSSL_MLKEM_KYBER @@ -1487,6 +1501,10 @@ int wc_MlKemKey_Decapsulate(MlKemKey* key, unsigned char* ss, /* Decapsulate the cipher text. */ ret = mlkemkey_decapsulate(key, msg, ct); } + if (ret == 0) { + /* Check we have H, hash of public, set. */ + ret = wc_mlkemkey_check_h(key); + } if (ret == 0) { /* Hash message into seed buffer. */ ret = MLKEM_HASH_G(&key->hash, msg, WC_ML_KEM_SYM_SZ, key->h, diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index a98e826af2..eab586d3ec 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -42961,6 +42961,29 @@ static wc_test_ret_t mlkem512_kat(void) if (XMEMCMP(ss_dec, ml_kem_512_ss, sizeof(ml_kem_512_ss)) != 0) ERROR_OUT(WC_TEST_RET_ENC_NC, out); + +#ifndef WOLFSSL_MLKEM_NO_MAKE_KEY + wc_MlKemKey_Free(key); + XMEMSET(key, 0, sizeof(MlKemKey)); + key_inited = 0; + ret = wc_MlKemKey_Init(key, WC_ML_KEM_512, HEAP_HINT, devId); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + else + key_inited = 1; + ret = wc_MlKemKey_MakeKeyWithRandom(key, kyber512_rand, + sizeof(kyber512_rand)); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + + ret = wc_MlKemKey_Decapsulate(key, ss_dec, ml_kem_512_ct, + sizeof(ml_kem_512_ct)); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + + if (XMEMCMP(ss_dec, ml_kem_512_ss, sizeof(ml_kem_512_ss)) != 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); +#endif #else (void)ml_kem_512_ct; (void)ml_kem_512_ss; From 66566955db554ed187e6fbb8ced944bbbf8222ff Mon Sep 17 00:00:00 2001 From: Daniel Pouzzner Date: Mon, 23 Feb 2026 23:03:08 -0600 Subject: [PATCH 08/36] wolfssl/wolfcrypt/wc_port.h, wolfssl/wolfcrypt/sha256.h, wolfssl/wolfcrypt/sha512.h, wolfssl/wolfcrypt/sp.h, wolfssl/wolfcrypt/wc_mlkem.h: add WC_NO_INLINE. --- wolfssl/wolfcrypt/sha256.h | 8 +------- wolfssl/wolfcrypt/sha512.h | 8 +------- wolfssl/wolfcrypt/sp.h | 13 +------------ wolfssl/wolfcrypt/wc_mlkem.h | 10 +--------- wolfssl/wolfcrypt/wc_port.h | 14 ++++++++++++++ 5 files changed, 18 insertions(+), 35 deletions(-) diff --git a/wolfssl/wolfcrypt/sha256.h b/wolfssl/wolfcrypt/sha256.h index 398722955f..1cc27b070a 100644 --- a/wolfssl/wolfcrypt/sha256.h +++ b/wolfssl/wolfcrypt/sha256.h @@ -102,13 +102,7 @@ #define WOLFSSL_NO_HASH_RAW #endif -#if defined(_MSC_VER) - #define SHA256_NOINLINE __declspec(noinline) -#elif defined(__IAR_SYSTEMS_ICC__) || defined(__GNUC__) - #define SHA256_NOINLINE __attribute__((noinline)) -#else - #define SHA256_NOINLINE -#endif +#define SHA256_NOINLINE WC_NO_INLINE #if !defined(NO_OLD_SHA_NAMES) #define SHA256 WC_SHA256 diff --git a/wolfssl/wolfcrypt/sha512.h b/wolfssl/wolfcrypt/sha512.h index 7d9724e5f1..a6e906f1c8 100644 --- a/wolfssl/wolfcrypt/sha512.h +++ b/wolfssl/wolfcrypt/sha512.h @@ -80,13 +80,7 @@ #include #endif -#if defined(_MSC_VER) - #define SHA512_NOINLINE __declspec(noinline) -#elif defined(__IAR_SYSTEMS_ICC__) || defined(__GNUC__) - #define SHA512_NOINLINE __attribute__((noinline)) -#else - #define SHA512_NOINLINE -#endif +#define SHA512_NOINLINE WC_NO_INLINE #ifdef WOLFSSL_SHA512 diff --git a/wolfssl/wolfcrypt/sp.h b/wolfssl/wolfcrypt/sp.h index 1ebd39efad..c0f028a9b3 100644 --- a/wolfssl/wolfcrypt/sp.h +++ b/wolfssl/wolfcrypt/sp.h @@ -48,18 +48,7 @@ #undef WOLFSSL_HAVE_SP_ECC #endif -#ifdef noinline - #define SP_NOINLINE noinline -#elif defined(_MSC_VER) - #define SP_NOINLINE __declspec(noinline) -#elif defined(__ICCARM__) || defined(__IAR_SYSTEMS_ICC__) - #define SP_NOINLINE _Pragma("inline = never") -#elif defined(__GNUC__) || defined(__KEIL__) || defined(__DCC__) - #define SP_NOINLINE __attribute__((noinline)) -#else - #define SP_NOINLINE -#endif - +#define SP_NOINLINE WC_NO_INLINE #ifdef __cplusplus extern "C" { diff --git a/wolfssl/wolfcrypt/wc_mlkem.h b/wolfssl/wolfcrypt/wc_mlkem.h index 1ee4225787..27f12264c3 100644 --- a/wolfssl/wolfcrypt/wc_mlkem.h +++ b/wolfssl/wolfcrypt/wc_mlkem.h @@ -44,15 +44,7 @@ #define WOLFSSL_MLKEM_NO_DECAPSULATE #endif -#ifdef noinline - #define MLKEM_NOINLINE noinline -#elif defined(_MSC_VER) - #define MLKEM_NOINLINE __declspec(noinline) -#elif defined(__GNUC__) - #define MLKEM_NOINLINE __attribute__((noinline)) -#else - #define MLKEM_NOINLINE -#endif +#define MLKEM_NOINLINE WC_NO_INLINE enum { /* Flags of Kyber keys. */ diff --git a/wolfssl/wolfcrypt/wc_port.h b/wolfssl/wolfcrypt/wc_port.h index 76cb11b5b1..f58fd0e854 100644 --- a/wolfssl/wolfcrypt/wc_port.h +++ b/wolfssl/wolfcrypt/wc_port.h @@ -143,6 +143,20 @@ #endif #endif +#ifndef WC_NO_INLINE + #ifdef noinline + #define WC_NO_INLINE noinline + #elif defined(_MSC_VER) + #define WC_NO_INLINE __declspec(noinline) + #elif defined(__ICCARM__) || defined(__IAR_SYSTEMS_ICC__) + #define WC_NO_INLINE _Pragma("inline = never") + #elif defined(__GNUC__) || defined(__KEIL__) || defined(__DCC__) + #define WC_NO_INLINE __attribute__((noinline)) + #else + #define WC_NO_INLINE + #endif +#endif + #ifndef WC_OMIT_FRAME_POINTER #if defined(__GNUC__) #define WC_OMIT_FRAME_POINTER \ From 96fc89626590e2bbc5ce893494765e934187fbc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Tue, 24 Feb 2026 16:20:10 +0100 Subject: [PATCH 09/36] Wdeclaration-after-statement fixes --- .github/workflows/no-malloc.yml | 2 ++ tests/suites.c | 8 ++++---- wolfcrypt/test/test.c | 3 ++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/no-malloc.yml b/.github/workflows/no-malloc.yml index 1ed247122b..c04744e94b 100644 --- a/.github/workflows/no-malloc.yml +++ b/.github/workflows/no-malloc.yml @@ -19,6 +19,8 @@ jobs: config: [ # Add new configs here '--enable-rsa --enable-keygen --disable-dh CFLAGS="-DWOLFSSL_NO_MALLOC -DRSA_MIN_SIZE=1024 -pedantic -Wdeclaration-after-statement -DTEST_LIBWOLFSSL_SOURCES_INCLUSION_SEQUENCE"', + '--enable-ecc --enable-rsa --enable-keygen --enable-ed25519 --enable-curve25519 --enable-ed448 --enable-curve448 --enable-mlkem CFLAGS="-DWOLFSSL_NO_MALLOC -pedantic -Wdeclaration-after-statement -DTEST_LIBWOLFSSL_SOURCES_INCLUSION_SEQUENCE"', + '--enable-ecc --enable-rsa --enable-keygen --enable-ed25519 --enable-curve25519 --enable-ed448 --enable-curve448 --enable-mlkem --enable-staticmemory CFLAGS="-DWOLFSSL_NO_MALLOC -pedantic -Wdeclaration-after-statement -DTEST_LIBWOLFSSL_SOURCES_INCLUSION_SEQUENCE"', ] name: make check if: github.repository_owner == 'wolfssl' diff --git a/tests/suites.c b/tests/suites.c index 34fe772d27..b0cbca6ff7 100644 --- a/tests/suites.c +++ b/tests/suites.c @@ -909,6 +909,10 @@ int SuiteTest(int argc, char** argv) char argv0[3][80]; char* myArgv[3]; +#ifdef WOLFSSL_STATIC_MEMORY + byte memory[200000]; +#endif + printf(" Begin Cipher Suite Tests\n"); /* setup */ @@ -918,10 +922,6 @@ int SuiteTest(int argc, char** argv) args.argv = myArgv; XSTRLCPY(argv0[0], "SuiteTest", sizeof(argv0[0])); -#ifdef WOLFSSL_STATIC_MEMORY - byte memory[200000]; -#endif - cipherSuiteCtx = wolfSSL_CTX_new(wolfSSLv23_client_method()); if (cipherSuiteCtx == NULL) { printf("can't get cipher suite ctx\n"); diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index a98e826af2..49a9e06feb 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -21039,7 +21039,6 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t memory_test(void) { wc_test_ret_t ret = 0; word32 j = 0; /* used in embedded const pointer test */ - WOLFSSL_ENTER("memory_test"); #if defined(COMPLEX_MEM_TEST) || defined(WOLFSSL_STATIC_MEMORY) int i; @@ -21053,6 +21052,8 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t memory_test(void) * alignment when tests are ran */ #endif + WOLFSSL_ENTER("memory_test"); + #ifdef WOLFSSL_STATIC_MEMORY /* check macro settings */ if (sizeof(size)/sizeof(word32) != WOLFMEM_DEF_BUCKETS) { From 2ae3164c6f2db5fdd9f7a6be344e068cd3264bde Mon Sep 17 00:00:00 2001 From: Eric Blankenhorn Date: Tue, 24 Feb 2026 09:27:42 -0600 Subject: [PATCH 10/36] Fix cert chain size issue --- src/ssl_load.c | 8 +++++++- tests/api.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/src/ssl_load.c b/src/ssl_load.c index 3fc7b033e2..9203b52209 100644 --- a/src/ssl_load.c +++ b/src/ssl_load.c @@ -4831,7 +4831,13 @@ static int wolfssl_add_to_chain(DerBuffer** chain, int weOwn, const byte* cert, /* Get length of previous chain. */ len = oldChain->length; } - /* Allocate DER buffer bug enough to hold old and new certificates. */ + /* Check for integer overflow in size calculation. */ + if ((len > WOLFSSL_MAX_32BIT - CERT_HEADER_SZ) || + (certSz > WOLFSSL_MAX_32BIT - CERT_HEADER_SZ - len)) { + WOLFSSL_MSG("wolfssl_add_to_chain overflow"); + return 0; + } + /* Allocate DER buffer big enough to hold old and new certificates. */ ret = AllocDer(&newChain, len + CERT_HEADER_SZ + certSz, CERT_TYPE, heap); if (ret != 0) { WOLFSSL_MSG("AllocDer error"); diff --git a/tests/api.c b/tests/api.c index 2cdd81c79b..8b9d0a4ecd 100644 --- a/tests/api.c +++ b/tests/api.c @@ -3515,6 +3515,57 @@ static int test_wolfSSL_CTX_add1_chain_cert(void) return EXPECT_RESULT(); } +/* Test that wolfssl_add_to_chain rejects sizes that would overflow word32. + * ZD #21241 */ +static int test_wolfSSL_add_to_chain_overflow(void) +{ + EXPECT_DECLS; +#if !defined(NO_CERTS) && defined(OPENSSL_EXTRA) && \ + defined(KEEP_OUR_CERT) && !defined(NO_RSA) && !defined(NO_TLS) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_FILESYSTEM) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL_X509* x509 = NULL; + DerBuffer* fakeChain = NULL; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())); + + /* Load a real cert so ctx->certificate is set (first add goes there). */ + ExpectNotNull(x509 = wolfSSL_X509_load_certificate_file( + "./certs/intermediate/client-int-cert.pem", WOLFSSL_FILETYPE_PEM)); + ExpectIntEQ(SSL_CTX_add1_chain_cert(ctx, x509), 1); + wolfSSL_X509_free(x509); + x509 = NULL; + + /* Now ctx->certificate is set, next add goes to certChain via + * wolfssl_add_to_chain. Fake a chain whose length is near UINT32_MAX + * so the size calculation (len + CERT_HEADER_SZ + certSz) overflows. */ + fakeChain = (DerBuffer*)XMALLOC(sizeof(DerBuffer) + 16, ctx->heap, + DYNAMIC_TYPE_CERT); + ExpectNotNull(fakeChain); + if (EXPECT_SUCCESS()) { + XMEMSET(fakeChain, 0, sizeof(DerBuffer) + 16); + fakeChain->buffer = (byte*)(fakeChain + 1); + fakeChain->length = WOLFSSL_MAX_32BIT - 2; /* will overflow with any cert */ + fakeChain->type = CERT_TYPE; + fakeChain->dynType = DYNAMIC_TYPE_CERT; + /* Replace the real chain with our fake one. */ + if (ctx->certChain != NULL) { + XFREE(ctx->certChain, ctx->heap, DYNAMIC_TYPE_CERT); + } + ctx->certChain = fakeChain; + } + + /* Try to add another cert - this MUST fail due to overflow guard. */ + ExpectNotNull(x509 = wolfSSL_X509_load_certificate_file( + "./certs/intermediate/ca-int2-cert.pem", WOLFSSL_FILETYPE_PEM)); + ExpectIntEQ(SSL_CTX_add1_chain_cert(ctx, x509), 0); + wolfSSL_X509_free(x509); + + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + static int test_wolfSSL_CTX_use_certificate_chain_buffer_format(void) { EXPECT_DECLS; @@ -32759,6 +32810,7 @@ TEST_CASE testCases[] = { TEST_DECL(test_wolfSSL_CTX_load_verify_buffer_ex), TEST_DECL(test_wolfSSL_CTX_load_verify_chain_buffer_format), TEST_DECL(test_wolfSSL_CTX_add1_chain_cert), + TEST_DECL(test_wolfSSL_add_to_chain_overflow), TEST_DECL(test_wolfSSL_CTX_use_certificate_chain_buffer_format), TEST_DECL(test_wolfSSL_CTX_use_certificate_chain_file_format), TEST_DECL(test_wolfSSL_use_certificate_chain_file), From d72fcb1d2720e48fee0af70984fa03416c7faa22 Mon Sep 17 00:00:00 2001 From: Marco Oliverio Date: Tue, 24 Feb 2026 17:59:06 +0100 Subject: [PATCH 11/36] tls13: avoid to create a new suite in CertificateRequest This way the ssl object honour the HasSigAlgo list set by wolfSSL_set1_sigalgs_list. --- src/tls13.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/tls13.c b/src/tls13.c index 101b31541a..0bc741ad16 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -7791,7 +7791,6 @@ static int SendTls13CertificateRequest(WOLFSSL* ssl, byte* reqCtx, int sendSz; word32 i; word32 reqSz; - word16 hashSigAlgoSz = 0; SignatureAlgorithms* sa; WOLFSSL_START(WC_FUNC_CERTIFICATE_REQUEST_SEND); @@ -7802,14 +7801,11 @@ static int SendTls13CertificateRequest(WOLFSSL* ssl, byte* reqCtx, if (ssl->options.side != WOLFSSL_SERVER_END) return SIDE_ERROR; - /* Get the length of the hashSigAlgo buffer */ - InitSuitesHashSigAlgo(NULL, SIG_ALL, 1, 1, ssl->buffers.keySz, - &hashSigAlgoSz); - sa = TLSX_SignatureAlgorithms_New(ssl, hashSigAlgoSz, ssl->heap); + /* Use ssl->suites->hashSigAlgo so wolfSSL_set1_sigalgs_list() is honored. + * hashSigAlgoSz=0 makes GetSize/Write fall back to WOLFSSL_SUITES(ssl). */ + sa = TLSX_SignatureAlgorithms_New(ssl, 0, ssl->heap); if (sa == NULL) return MEMORY_ERROR; - InitSuitesHashSigAlgo(sa->hashSigAlgo, SIG_ALL, 1, 1, ssl->buffers.keySz, - &hashSigAlgoSz); ret = TLSX_Push(&ssl->extensions, TLSX_SIGNATURE_ALGORITHMS, sa, ssl->heap); if (ret != 0) { TLSX_SignatureAlgorithms_FreeAll(sa, ssl->heap); From 8f787909da890e5830a9a6f73d3c4ff0d9bd7da9 Mon Sep 17 00:00:00 2001 From: Eric Blankenhorn Date: Tue, 24 Feb 2026 11:17:42 -0600 Subject: [PATCH 12/36] Fix from review --- src/ssl_load.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/ssl_load.c b/src/ssl_load.c index 9203b52209..0a0fb9e467 100644 --- a/src/ssl_load.c +++ b/src/ssl_load.c @@ -4835,14 +4835,17 @@ static int wolfssl_add_to_chain(DerBuffer** chain, int weOwn, const byte* cert, if ((len > WOLFSSL_MAX_32BIT - CERT_HEADER_SZ) || (certSz > WOLFSSL_MAX_32BIT - CERT_HEADER_SZ - len)) { WOLFSSL_MSG("wolfssl_add_to_chain overflow"); - return 0; - } - /* Allocate DER buffer big enough to hold old and new certificates. */ - ret = AllocDer(&newChain, len + CERT_HEADER_SZ + certSz, CERT_TYPE, heap); - if (ret != 0) { - WOLFSSL_MSG("AllocDer error"); res = 0; } + if (res == 1) { + /* Allocate DER buffer big enough to hold old and new certificates. */ + ret = AllocDer(&newChain, len + CERT_HEADER_SZ + certSz, CERT_TYPE, + heap); + if (ret != 0) { + WOLFSSL_MSG("AllocDer error"); + res = 0; + } + } if (res == 1) { if (oldChain != NULL) { From 5536ecf026151f1cdc80f6908fe8820e798dcd58 Mon Sep 17 00:00:00 2001 From: Eric Blankenhorn Date: Tue, 24 Feb 2026 12:43:46 -0600 Subject: [PATCH 13/36] Fix issue from review --- tests/api.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/api.c b/tests/api.c index 8b9d0a4ecd..298d999dbf 100644 --- a/tests/api.c +++ b/tests/api.c @@ -3554,6 +3554,9 @@ static int test_wolfSSL_add_to_chain_overflow(void) } ctx->certChain = fakeChain; } + else { + XFREE(fakeChain, ctx ? ctx->heap : NULL, DYNAMIC_TYPE_CERT); + } /* Try to add another cert - this MUST fail due to overflow guard. */ ExpectNotNull(x509 = wolfSSL_X509_load_certificate_file( From 7af0fa497ab7c18a4ab6faa1908c01970beeb42d Mon Sep 17 00:00:00 2001 From: Josh Holtrop Date: Tue, 24 Feb 2026 14:23:59 -0500 Subject: [PATCH 14/36] Rust wrapper: update dilithium module after review --- .github/workflows/rust-wrapper.yml | 1 + wrapper/rust/wolfssl-wolfcrypt/build.rs | 2 +- .../rust/wolfssl-wolfcrypt/src/dilithium.rs | 132 ++++++++++-------- .../wolfssl-wolfcrypt/tests/test_dilithium.rs | 28 ++-- 4 files changed, 87 insertions(+), 76 deletions(-) diff --git a/.github/workflows/rust-wrapper.yml b/.github/workflows/rust-wrapper.yml index 218f7d8320..d83aaa3121 100644 --- a/.github/workflows/rust-wrapper.yml +++ b/.github/workflows/rust-wrapper.yml @@ -38,6 +38,7 @@ jobs: # Add new configs here '', '--enable-all', + '--enable-all --enable-dilithium', '--enable-cryptonly --disable-examples', '--enable-cryptonly --disable-examples --disable-aes --disable-aesgcm', '--enable-cryptonly --disable-examples --disable-aescbc', diff --git a/wrapper/rust/wolfssl-wolfcrypt/build.rs b/wrapper/rust/wolfssl-wolfcrypt/build.rs index 1be13ec92e..63e098b0ae 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/build.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/build.rs @@ -325,7 +325,7 @@ fn scan_cfg() -> Result<()> { check_cfg(&binding, "DILITHIUM_LEVEL2_KEY_SIZE", "dilithium_level2"); check_cfg(&binding, "DILITHIUM_LEVEL3_KEY_SIZE", "dilithium_level3"); check_cfg(&binding, "DILITHIUM_LEVEL5_KEY_SIZE", "dilithium_level5"); - check_cfg(&binding, "DILITHIUM_PRIV_SEED_SZ", "dilithium_priv_seed_sz"); + check_cfg(&binding, "DILITHIUM_SEED_SZ", "dilithium_make_key_seed_sz"); check_cfg(&binding, "DILITHIUM_RND_SZ", "dilithium_rnd_sz"); /* sha */ diff --git a/wrapper/rust/wolfssl-wolfcrypt/src/dilithium.rs b/wrapper/rust/wolfssl-wolfcrypt/src/dilithium.rs index d4146077a1..0dcf920043 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/src/dilithium.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/src/dilithium.rs @@ -38,7 +38,7 @@ Three security parameter sets are supported, selected via # Examples ```rust -#[cfg(all(dilithium, dilithium_make_key, dilithium_sign, dilithium_verify))] +#[cfg(all(dilithium, dilithium_make_key, dilithium_sign, dilithium_verify, random))] { use wolfssl_wolfcrypt::random::RNG; use wolfssl_wolfcrypt::dilithium::Dilithium; @@ -47,7 +47,7 @@ let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) .expect("Key generation failed"); let message = b"Hello, ML-DSA!"; let mut sig = vec![0u8; key.sig_size().expect("sig_size failed")]; -let sig_len = key.sign_msg(message, &mut sig, Some(&mut rng)) +let sig_len = key.sign_msg(message, &mut sig, &mut rng) .expect("Signing failed"); let valid = key.verify_msg(&sig[..sig_len], message) .expect("Verification failed"); @@ -59,7 +59,7 @@ assert!(valid); #![cfg(dilithium)] use crate::sys; -#[cfg(any(dilithium_make_key, dilithium_sign))] +#[cfg(all(random, any(dilithium_make_key, dilithium_sign)))] use crate::random::RNG; use core::mem::MaybeUninit; @@ -83,9 +83,9 @@ impl Dilithium { pub const LEVEL_87: u8 = sys::WC_ML_DSA_87 as u8; /// Required size in bytes of the seed passed to - /// [`Dilithium::generate_from_seed()`] (`DILITHIUM_PRIV_SEED_SZ`). - #[cfg(dilithium_priv_seed_sz)] - pub const MAKE_KEY_SEED_SIZE: usize = sys::DILITHIUM_PRIV_SEED_SZ as usize; + /// [`Dilithium::generate_from_seed()`] (`DILITHIUM_SEED_SZ`). + #[cfg(dilithium_make_key_seed_sz)] + pub const DILITHIUM_SEED_SZ: usize = sys::DILITHIUM_SEED_SZ as usize; /// Required size in bytes of the seed passed to signing-with-seed /// functions such as [`Dilithium::sign_msg_with_seed()`] @@ -151,7 +151,7 @@ impl Dilithium { /// # Example /// /// ```rust - /// #[cfg(all(dilithium, dilithium_make_key))] + /// #[cfg(all(dilithium, dilithium_make_key, random))] /// { /// use wolfssl_wolfcrypt::random::RNG; /// use wolfssl_wolfcrypt::dilithium::Dilithium; @@ -160,7 +160,7 @@ impl Dilithium { /// .expect("Error with generate()"); /// } /// ``` - #[cfg(dilithium_make_key)] + #[cfg(all(dilithium_make_key, random))] pub fn generate(level: u8, rng: &mut RNG) -> Result { Self::generate_ex(level, rng, None, None) } @@ -183,7 +183,7 @@ impl Dilithium { /// # Example /// /// ```rust - /// #[cfg(all(dilithium, dilithium_make_key))] + /// #[cfg(all(dilithium, dilithium_make_key, random))] /// { /// use wolfssl_wolfcrypt::random::RNG; /// use wolfssl_wolfcrypt::dilithium::Dilithium; @@ -192,7 +192,7 @@ impl Dilithium { /// .expect("Error with generate_ex()"); /// } /// ``` - #[cfg(dilithium_make_key)] + #[cfg(all(dilithium_make_key, random))] pub fn generate_ex( level: u8, rng: &mut RNG, @@ -220,7 +220,7 @@ impl Dilithium { /// /// * `level`: Security parameter set. One of [`Dilithium::LEVEL_44`], /// [`Dilithium::LEVEL_65`], or [`Dilithium::LEVEL_87`]. - /// * `seed`: Seed bytes. Must be `DILITHIUM_PRIV_SEED_SZ` (64) bytes. + /// * `seed`: Seed bytes. Must be `DILITHIUM_SEED_SZ` (32) bytes. /// /// # Returns /// @@ -233,7 +233,7 @@ impl Dilithium { /// #[cfg(all(dilithium, dilithium_make_key_from_seed))] /// { /// use wolfssl_wolfcrypt::dilithium::Dilithium; - /// let seed = [0x42u8; 64]; + /// let seed = [0x42u8; 32]; /// let key = Dilithium::generate_from_seed(Dilithium::LEVEL_44, &seed) /// .expect("Error with generate_from_seed()"); /// } @@ -250,7 +250,7 @@ impl Dilithium { /// /// * `level`: Security parameter set. One of [`Dilithium::LEVEL_44`], /// [`Dilithium::LEVEL_65`], or [`Dilithium::LEVEL_87`]. - /// * `seed`: Seed bytes. Must be `DILITHIUM_PRIV_SEED_SZ` (64) bytes. + /// * `seed`: Seed bytes. Must be `DILITHIUM_SEED_SZ` (32) bytes. /// * `heap`: Optional heap hint. /// * `dev_id`: Optional device ID for crypto callbacks or async hardware. /// @@ -265,7 +265,7 @@ impl Dilithium { /// #[cfg(all(dilithium, dilithium_make_key_from_seed))] /// { /// use wolfssl_wolfcrypt::dilithium::Dilithium; - /// let seed = [0x42u8; 64]; + /// let seed = [0x42u8; 32]; /// let key = Dilithium::generate_from_seed_ex(Dilithium::LEVEL_44, &seed, None, None) /// .expect("Error with generate_from_seed_ex()"); /// } @@ -277,6 +277,10 @@ impl Dilithium { heap: Option<*mut core::ffi::c_void>, dev_id: Option, ) -> Result { + #[cfg(dilithium_make_key_seed_sz)] + if seed.len() != Self::DILITHIUM_SEED_SZ { + return Err(sys::wolfCrypt_ErrorCodes_BUFFER_E); + } let mut key = Self::new_ex(heap, dev_id)?; let rc = unsafe { sys::wc_dilithium_set_level(&mut key.ws_key, level) }; if rc != 0 { @@ -428,7 +432,7 @@ impl Dilithium { /// # Example /// /// ```rust - /// #[cfg(all(dilithium, dilithium_make_key))] + /// #[cfg(all(dilithium, dilithium_make_key, random))] /// { /// use wolfssl_wolfcrypt::random::RNG; /// use wolfssl_wolfcrypt::dilithium::Dilithium; @@ -458,7 +462,7 @@ impl Dilithium { /// # Example /// /// ```rust - /// #[cfg(all(dilithium, dilithium_make_key))] + /// #[cfg(all(dilithium, dilithium_make_key, random))] /// { /// use wolfssl_wolfcrypt::random::RNG; /// use wolfssl_wolfcrypt::dilithium::Dilithium; @@ -487,7 +491,7 @@ impl Dilithium { /// # Example /// /// ```rust - /// #[cfg(all(dilithium, dilithium_make_key))] + /// #[cfg(all(dilithium, dilithium_make_key, random))] /// { /// use wolfssl_wolfcrypt::random::RNG; /// use wolfssl_wolfcrypt::dilithium::Dilithium; @@ -516,7 +520,7 @@ impl Dilithium { /// # Example /// /// ```rust - /// #[cfg(all(dilithium, dilithium_make_key))] + /// #[cfg(all(dilithium, dilithium_make_key, random))] /// { /// use wolfssl_wolfcrypt::random::RNG; /// use wolfssl_wolfcrypt::dilithium::Dilithium; @@ -545,7 +549,7 @@ impl Dilithium { /// # Example /// /// ```rust - /// #[cfg(all(dilithium, dilithium_make_key, dilithium_check_key))] + /// #[cfg(all(dilithium, dilithium_make_key, dilithium_check_key, random))] /// { /// use wolfssl_wolfcrypt::random::RNG; /// use wolfssl_wolfcrypt::dilithium::Dilithium; @@ -578,7 +582,7 @@ impl Dilithium { /// # Example /// /// ```rust - /// #[cfg(all(dilithium, dilithium_make_key, dilithium_import, dilithium_export))] + /// #[cfg(all(dilithium, dilithium_make_key, dilithium_import, dilithium_export, random))] /// { /// use wolfssl_wolfcrypt::random::RNG; /// use wolfssl_wolfcrypt::dilithium::Dilithium; @@ -621,7 +625,7 @@ impl Dilithium { /// # Example /// /// ```rust - /// #[cfg(all(dilithium, dilithium_make_key, dilithium_import, dilithium_export))] + /// #[cfg(all(dilithium, dilithium_make_key, dilithium_import, dilithium_export, random))] /// { /// use wolfssl_wolfcrypt::random::RNG; /// use wolfssl_wolfcrypt::dilithium::Dilithium; @@ -662,7 +666,7 @@ impl Dilithium { /// # Example /// /// ```rust - /// #[cfg(all(dilithium, dilithium_make_key, dilithium_import, dilithium_export))] + /// #[cfg(all(dilithium, dilithium_make_key, dilithium_import, dilithium_export, random))] /// { /// use wolfssl_wolfcrypt::random::RNG; /// use wolfssl_wolfcrypt::dilithium::Dilithium; @@ -709,7 +713,7 @@ impl Dilithium { /// # Example /// /// ```rust - /// #[cfg(all(dilithium, dilithium_make_key, dilithium_export))] + /// #[cfg(all(dilithium, dilithium_make_key, dilithium_export, random))] /// { /// use wolfssl_wolfcrypt::random::RNG; /// use wolfssl_wolfcrypt::dilithium::Dilithium; @@ -748,7 +752,7 @@ impl Dilithium { /// # Example /// /// ```rust - /// #[cfg(all(dilithium, dilithium_make_key, dilithium_export))] + /// #[cfg(all(dilithium, dilithium_make_key, dilithium_export, random))] /// { /// use wolfssl_wolfcrypt::random::RNG; /// use wolfssl_wolfcrypt::dilithium::Dilithium; @@ -792,7 +796,7 @@ impl Dilithium { /// # Example /// /// ```rust - /// #[cfg(all(dilithium, dilithium_make_key, dilithium_export))] + /// #[cfg(all(dilithium, dilithium_make_key, dilithium_export, random))] /// { /// use wolfssl_wolfcrypt::random::RNG; /// use wolfssl_wolfcrypt::dilithium::Dilithium; @@ -828,8 +832,8 @@ impl Dilithium { /// * `msg`: Message to sign. /// * `sig`: Output buffer to hold the signature. Must be at least /// `sig_size()` bytes. - /// * `rng`: Optional RNG instance for hedged signing. Pass `None` for - /// deterministic signing. + /// * `rng`: RNG instance for hedged signing. For deterministic signing, + /// use [`Dilithium::sign_msg_with_seed()`] instead. /// /// # Returns /// @@ -839,7 +843,7 @@ impl Dilithium { /// # Example /// /// ```rust - /// #[cfg(all(dilithium, dilithium_make_key, dilithium_sign))] + /// #[cfg(all(dilithium, dilithium_make_key, dilithium_sign, random))] /// { /// use wolfssl_wolfcrypt::random::RNG; /// use wolfssl_wolfcrypt::dilithium::Dilithium; @@ -848,30 +852,26 @@ impl Dilithium { /// .expect("Error with generate()"); /// let message = b"Hello, ML-DSA!"; /// let mut sig = vec![0u8; key.sig_size().unwrap()]; - /// let sig_len = key.sign_msg(message, &mut sig, Some(&mut rng)) + /// let sig_len = key.sign_msg(message, &mut sig, &mut rng) /// .expect("Error with sign_msg()"); /// assert_eq!(sig_len, Dilithium::LEVEL2_SIG_SIZE); /// } /// ``` - #[cfg(dilithium_sign)] + #[cfg(all(dilithium_sign, random))] pub fn sign_msg( &mut self, msg: &[u8], sig: &mut [u8], - rng: Option<&mut RNG>, + rng: &mut RNG, ) -> Result { let msg_len = msg.len() as u32; let mut sig_len = sig.len() as u32; - let rng_ptr = match rng { - Some(r) => &mut r.wc_rng as *mut sys::WC_RNG, - None => core::ptr::null_mut(), - }; let rc = unsafe { sys::wc_dilithium_sign_msg( msg.as_ptr(), msg_len, sig.as_mut_ptr(), &mut sig_len, &mut self.ws_key, - rng_ptr, + &mut rng.wc_rng, ) }; if rc != 0 { @@ -888,8 +888,8 @@ impl Dilithium { /// * `msg`: Message to sign. /// * `sig`: Output buffer to hold the signature. Must be at least /// `sig_size()` bytes. - /// * `rng`: Optional RNG instance for hedged signing. Pass `None` for - /// deterministic signing. + /// * `rng`: RNG instance for hedged signing. For deterministic signing, + /// use [`Dilithium::sign_ctx_msg_with_seed()`] instead. /// /// # Returns /// @@ -899,7 +899,7 @@ impl Dilithium { /// # Example /// /// ```rust - /// #[cfg(all(dilithium, dilithium_make_key, dilithium_sign))] + /// #[cfg(all(dilithium, dilithium_make_key, dilithium_sign, random))] /// { /// use wolfssl_wolfcrypt::random::RNG; /// use wolfssl_wolfcrypt::dilithium::Dilithium; @@ -909,17 +909,17 @@ impl Dilithium { /// let message = b"Hello, ML-DSA!"; /// let ctx = b"my context"; /// let mut sig = vec![0u8; key.sig_size().unwrap()]; - /// key.sign_ctx_msg(ctx, message, &mut sig, Some(&mut rng)) + /// key.sign_ctx_msg(ctx, message, &mut sig, &mut rng) /// .expect("Error with sign_ctx_msg()"); /// } /// ``` - #[cfg(dilithium_sign)] + #[cfg(all(dilithium_sign, random))] pub fn sign_ctx_msg( &mut self, ctx: &[u8], msg: &[u8], sig: &mut [u8], - rng: Option<&mut RNG>, + rng: &mut RNG, ) -> Result { if ctx.len() > 255 { return Err(sys::wolfCrypt_ErrorCodes_BUFFER_E); @@ -927,17 +927,13 @@ impl Dilithium { let ctx_len = ctx.len() as u8; let msg_len = msg.len() as u32; let mut sig_len = sig.len() as u32; - let rng_ptr = match rng { - Some(r) => &mut r.wc_rng as *mut sys::WC_RNG, - None => core::ptr::null_mut(), - }; let rc = unsafe { sys::wc_dilithium_sign_ctx_msg( ctx.as_ptr(), ctx_len, msg.as_ptr(), msg_len, sig.as_mut_ptr(), &mut sig_len, &mut self.ws_key, - rng_ptr, + &mut rng.wc_rng, ) }; if rc != 0 { @@ -958,21 +954,21 @@ impl Dilithium { /// * `hash`: Hash digest of the message to sign. /// * `sig`: Output buffer to hold the signature. Must be at least /// `sig_size()` bytes. - /// * `rng`: Optional RNG instance for hedged signing. Pass `None` for - /// deterministic signing. + /// * `rng`: RNG instance for hedged signing. For deterministic signing, + /// use [`Dilithium::sign_ctx_hash_with_seed()`] instead. /// /// # Returns /// /// Returns either Ok(size) containing the number of bytes written to `sig` /// on success or Err(e) containing the wolfSSL library error code value. - #[cfg(dilithium_sign)] + #[cfg(all(dilithium_sign, random))] pub fn sign_ctx_hash( &mut self, ctx: &[u8], hash_alg: i32, hash: &[u8], sig: &mut [u8], - rng: Option<&mut RNG>, + rng: &mut RNG, ) -> Result { if ctx.len() > 255 { return Err(sys::wolfCrypt_ErrorCodes_BUFFER_E); @@ -980,10 +976,6 @@ impl Dilithium { let ctx_len = ctx.len() as u8; let hash_len = hash.len() as u32; let mut sig_len = sig.len() as u32; - let rng_ptr = match rng { - Some(r) => &mut r.wc_rng as *mut sys::WC_RNG, - None => core::ptr::null_mut(), - }; let rc = unsafe { sys::wc_dilithium_sign_ctx_hash( ctx.as_ptr(), ctx_len, @@ -991,7 +983,7 @@ impl Dilithium { hash.as_ptr(), hash_len, sig.as_mut_ptr(), &mut sig_len, &mut self.ws_key, - rng_ptr, + &mut rng.wc_rng, ) }; if rc != 0 { @@ -1022,7 +1014,7 @@ impl Dilithium { /// #[cfg(all(dilithium, dilithium_make_key_from_seed, dilithium_sign_with_seed))] /// { /// use wolfssl_wolfcrypt::dilithium::Dilithium; - /// let key_seed = [0x42u8; 64]; + /// let key_seed = [0x42u8; 32]; /// let mut key = Dilithium::generate_from_seed(Dilithium::LEVEL_44, &key_seed) /// .expect("Error with generate_from_seed()"); /// let message = b"Hello, ML-DSA!"; @@ -1039,6 +1031,10 @@ impl Dilithium { sig: &mut [u8], seed: &[u8], ) -> Result { + #[cfg(dilithium_rnd_sz)] + if seed.len() != sys::DILITHIUM_RND_SZ as usize { + return Err(sys::wolfCrypt_ErrorCodes_BUFFER_E); + } let msg_len = msg.len() as u32; let mut sig_len = sig.len() as u32; let rc = unsafe { @@ -1079,6 +1075,10 @@ impl Dilithium { if ctx.len() > 255 { return Err(sys::wolfCrypt_ErrorCodes_BUFFER_E); } + #[cfg(dilithium_rnd_sz)] + if seed.len() != sys::DILITHIUM_RND_SZ as usize { + return Err(sys::wolfCrypt_ErrorCodes_BUFFER_E); + } let ctx_len = ctx.len() as u8; let msg_len = msg.len() as u32; let mut sig_len = sig.len() as u32; @@ -1124,6 +1124,10 @@ impl Dilithium { if ctx.len() > 255 { return Err(sys::wolfCrypt_ErrorCodes_BUFFER_E); } + #[cfg(dilithium_rnd_sz)] + if seed.len() != sys::DILITHIUM_RND_SZ as usize { + return Err(sys::wolfCrypt_ErrorCodes_BUFFER_E); + } let ctx_len = ctx.len() as u8; let hash_len = hash.len() as u32; let mut sig_len = sig.len() as u32; @@ -1158,7 +1162,7 @@ impl Dilithium { /// # Example /// /// ```rust - /// #[cfg(all(dilithium, dilithium_make_key, dilithium_sign, dilithium_verify))] + /// #[cfg(all(dilithium, dilithium_make_key, dilithium_sign, dilithium_verify, random))] /// { /// use wolfssl_wolfcrypt::random::RNG; /// use wolfssl_wolfcrypt::dilithium::Dilithium; @@ -1167,7 +1171,7 @@ impl Dilithium { /// .expect("Error with generate()"); /// let message = b"Hello, ML-DSA!"; /// let mut sig = vec![0u8; key.sig_size().unwrap()]; - /// let sig_len = key.sign_msg(message, &mut sig, Some(&mut rng)) + /// let sig_len = key.sign_msg(message, &mut sig, &mut rng) /// .expect("Error with sign_msg()"); /// let valid = key.verify_msg(&sig[..sig_len], message) /// .expect("Error with verify_msg()"); @@ -1209,7 +1213,7 @@ impl Dilithium { /// # Example /// /// ```rust - /// #[cfg(all(dilithium, dilithium_make_key, dilithium_sign, dilithium_verify))] + /// #[cfg(all(dilithium, dilithium_make_key, dilithium_sign, dilithium_verify, random))] /// { /// use wolfssl_wolfcrypt::random::RNG; /// use wolfssl_wolfcrypt::dilithium::Dilithium; @@ -1219,7 +1223,7 @@ impl Dilithium { /// let message = b"Hello, ML-DSA!"; /// let ctx = b"my context"; /// let mut sig = vec![0u8; key.sig_size().unwrap()]; - /// let sig_len = key.sign_ctx_msg(ctx, message, &mut sig, Some(&mut rng)) + /// let sig_len = key.sign_ctx_msg(ctx, message, &mut sig, &mut rng) /// .expect("Error with sign_ctx_msg()"); /// let valid = key.verify_ctx_msg(&sig[..sig_len], ctx, message) /// .expect("Error with verify_ctx_msg()"); @@ -1228,6 +1232,9 @@ impl Dilithium { /// ``` #[cfg(dilithium_verify)] pub fn verify_ctx_msg(&mut self, sig: &[u8], ctx: &[u8], msg: &[u8]) -> Result { + if ctx.len() > 255 { + return Err(sys::wolfCrypt_ErrorCodes_BUFFER_E); + } let sig_len = sig.len() as u32; let ctx_len = ctx.len() as u32; let msg_len = msg.len() as u32; @@ -1271,6 +1278,9 @@ impl Dilithium { hash_alg: i32, hash: &[u8], ) -> Result { + if ctx.len() > 255 { + return Err(sys::wolfCrypt_ErrorCodes_BUFFER_E); + } let sig_len = sig.len() as u32; let ctx_len = ctx.len() as u32; let hash_len = hash.len() as u32; diff --git a/wrapper/rust/wolfssl-wolfcrypt/tests/test_dilithium.rs b/wrapper/rust/wolfssl-wolfcrypt/tests/test_dilithium.rs index 5261b60cf1..d8b3b452d0 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/tests/test_dilithium.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/tests/test_dilithium.rs @@ -23,7 +23,7 @@ mod common; use wolfssl_wolfcrypt::dilithium::Dilithium; -#[cfg(any(dilithium_make_key, dilithium_sign))] +#[cfg(all(random, any(dilithium_make_key, dilithium_sign)))] use wolfssl_wolfcrypt::random::RNG; /// Verify the level constants have the correct numeric values required by @@ -153,7 +153,7 @@ fn test_sign_verify_level44() { let message = b"Hello, ML-DSA-44!"; let mut sig = vec![0u8; key.sig_size().expect("Error with sig_size()")]; - let sig_len = key.sign_msg(message, &mut sig, Some(&mut rng)) + let sig_len = key.sign_msg(message, &mut sig, &mut rng) .expect("Error with sign_msg()"); assert_eq!(sig_len, sig.len()); @@ -177,7 +177,7 @@ fn test_sign_verify_level65() { let message = b"Hello, ML-DSA-65!"; let mut sig = vec![0u8; key.sig_size().expect("Error with sig_size()")]; - let sig_len = key.sign_msg(message, &mut sig, Some(&mut rng)) + let sig_len = key.sign_msg(message, &mut sig, &mut rng) .expect("Error with sign_msg()"); assert_eq!(sig_len, sig.len()); @@ -196,7 +196,7 @@ fn test_sign_verify_level87() { let message = b"Hello, ML-DSA-87!"; let mut sig = vec![0u8; key.sig_size().expect("Error with sig_size()")]; - let sig_len = key.sign_msg(message, &mut sig, Some(&mut rng)) + let sig_len = key.sign_msg(message, &mut sig, &mut rng) .expect("Error with sign_msg()"); assert_eq!(sig_len, sig.len()); @@ -218,7 +218,7 @@ fn test_sign_ctx_verify_level44() { let ctx = b"my context"; let mut sig = vec![0u8; key.sig_size().expect("Error with sig_size()")]; - let sig_len = key.sign_ctx_msg(ctx, message, &mut sig, Some(&mut rng)) + let sig_len = key.sign_ctx_msg(ctx, message, &mut sig, &mut rng) .expect("Error with sign_ctx_msg()"); let valid = key.verify_ctx_msg(&sig[..sig_len], ctx, message) @@ -266,7 +266,7 @@ fn test_import_export_level44() { // Sign with the original key. let message = b"Import/export test message"; let mut sig = vec![0u8; sig_size]; - let sig_len = key.sign_msg(message, &mut sig, Some(&mut rng)) + let sig_len = key.sign_msg(message, &mut sig, &mut rng) .expect("Error with sign_msg()"); // Re-import public key only and verify. @@ -282,7 +282,7 @@ fn test_import_export_level44() { priv_key.set_level(Dilithium::LEVEL_44).expect("Error with set_level()"); priv_key.import_private(&priv_buf).expect("Error with import_private()"); let mut sig2 = vec![0u8; sig_size]; - let sig2_len = priv_key.sign_msg(message, &mut sig2, Some(&mut rng)) + let sig2_len = priv_key.sign_msg(message, &mut sig2, &mut rng) .expect("Error with sign_msg() from imported private key"); let valid = key.verify_msg(&sig2[..sig2_len], message) .expect("Error with verify_msg() after import_private"); @@ -313,7 +313,7 @@ fn test_import_key_level44() { let message = b"import_key round-trip"; let mut sig = vec![0u8; sig_size]; - let sig_len = key2.sign_msg(message, &mut sig, Some(&mut rng)) + let sig_len = key2.sign_msg(message, &mut sig, &mut rng) .expect("Error with sign_msg() from imported key pair"); let valid = key.verify_msg(&sig[..sig_len], message) .expect("Error with verify_msg()"); @@ -326,8 +326,8 @@ fn test_import_key_level44() { #[cfg(all(dilithium_make_key_from_seed, dilithium_export))] fn test_generate_from_seed_determinism() { common::setup(); - // DILITHIUM_PRIV_SEED_SZ = 64 bytes - let seed = [0x42u8; 64]; + // DILITHIUM_SEED_SZ = 32 bytes + let seed = [0x42u8; 32]; let mut key1 = Dilithium::generate_from_seed(Dilithium::LEVEL_44, &seed) .expect("Error with generate_from_seed() first call"); @@ -356,8 +356,8 @@ fn test_generate_from_seed_determinism() { #[cfg(all(dilithium_make_key_from_seed, dilithium_sign_with_seed, dilithium_verify))] fn test_sign_with_seed_determinism() { common::setup(); - // DILITHIUM_PRIV_SEED_SZ = 64 bytes - let key_seed = [0x42u8; 64]; + // DILITHIUM_SEED_SZ = 32 bytes + let key_seed = [0x42u8; 32]; // DILITHIUM_RND_SZ = 32 bytes let sign_seed = [0x55u8; 32]; let message = b"Deterministic ML-DSA signing test"; @@ -388,7 +388,7 @@ fn test_sign_with_seed_determinism() { #[cfg(all(dilithium_make_key_from_seed, dilithium_sign_with_seed, dilithium_verify))] fn test_sign_ctx_with_seed_determinism() { common::setup(); - let key_seed = [0x11u8; 64]; + let key_seed = [0x11u8; 32]; let sign_seed = [0x22u8; 32]; let message = b"Context deterministic signing test"; let ctx = b"test-context"; @@ -419,7 +419,7 @@ fn test_sign_ctx_with_seed_determinism() { #[cfg(all(dilithium_make_key_from_seed, dilithium_sign_with_seed, dilithium_verify))] fn test_seed_sign_verify_all_levels() { common::setup(); - let key_seed = [0xABu8; 64]; + let key_seed = [0xABu8; 32]; let sign_seed = [0xCDu8; 32]; let message = b"All-levels seed sign/verify test"; From 39987a9d53aaf4d0b267c9009f5288c6c10f9819 Mon Sep 17 00:00:00 2001 From: Daniel Pouzzner Date: Tue, 24 Feb 2026 13:59:12 -0600 Subject: [PATCH 15/36] wolfcrypt/src/aes.c, wolfcrypt/src/cmac.c, wolfssl/wolfcrypt/aes.h, wolfssl/wolfcrypt/types.h: optimizations to mitigate performance regressions from 299e7bd097 (#9783): * add prefetch_ptr flag argument to AesEncrypt_C() and AesDecrypt_C(), and call PreFetchTe() and PreFetchSBox() only if *prefetch_ptr is zero, whereupon it is set to 1; * when C implementations are available, add prefetch_ptr arg to wc_AesEncrypt() and wc_AesDecrypt(), and pass it through; * in functions that directly call the AES block encryption methods, opportunistically inhibit prefetch on all but the first call; * move AES-specific code in wc_CmacUpdate() in cmac.c to wc_local_CmacUpdateAes() in aes.c to let it use conditional prefetching; * add WC_ARG_NOT_NULL(), WC_ARGS_NOT_NULL(), and WC_ALL_ARGS_NOT_NULL attribute abstractions. --- .wolfssl_known_macro_extras | 1 + wolfcrypt/src/aes.c | 298 ++++++++++++++++++++++++++++++------ wolfcrypt/src/cmac.c | 14 +- wolfssl/wolfcrypt/aes.h | 7 + wolfssl/wolfcrypt/types.h | 25 +++ 5 files changed, 289 insertions(+), 56 deletions(-) diff --git a/.wolfssl_known_macro_extras b/.wolfssl_known_macro_extras index 97decd7acb..24552e1037 100644 --- a/.wolfssl_known_macro_extras +++ b/.wolfssl_known_macro_extras @@ -1094,6 +1094,7 @@ __clang_major__ __cplusplus __ghc__ __ghs__ +__has_attribute __hpux__ __i386 __i386__ diff --git a/wolfcrypt/src/aes.c b/wolfcrypt/src/aes.c index 29911ccfa2..4833803a56 100644 --- a/wolfcrypt/src/aes.c +++ b/wolfcrypt/src/aes.c @@ -70,9 +70,9 @@ block cipher mechanism that uses n-bit binary string parameter key with 128-bits #include #endif -#if defined(WOLFSSL_AES_SIV) +#ifdef WOLFSSL_CMAC #include -#endif /* WOLFSSL_AES_SIV */ +#endif #if defined(WOLFSSL_HAVE_PSA) && !defined(WOLFSSL_PSA_NO_AES) #include @@ -2142,8 +2142,12 @@ static word32 GetTable8_4(const byte* t, byte o0, byte o1, byte o2, byte o3) * @param [out] outBlock Encrypted block. * @param [in] r Rounds divided by 2. */ +#define WC_AES_HAVE_PREFETCH_ARG +static int always_prefetch = 0; +WC_MAYBE_UNUSED static int never_prefetch = 1; +WC_ARGS_NOT_NULL((1, 2, 3, 5)) static void AesEncrypt_C(Aes* aes, const byte* inBlock, byte* outBlock, - word32 r) + word32 r, int *prefetch_ptr) { word32 s0 = 0, s1 = 0, s2 = 0, s3 = 0; word32 t0 = 0, t1 = 0, t2 = 0, t3 = 0; @@ -2178,8 +2182,15 @@ static void AesEncrypt_C(Aes* aes, const byte* inBlock, byte* outBlock, s3 ^= rk[3]; #ifndef WOLFSSL_AES_SMALL_TABLES + #ifndef WC_NO_CACHE_RESISTANT - s0 |= PreFetchTe(); + if (*prefetch_ptr == 0) { + s0 |= PreFetchTe(); + if (prefetch_ptr != &always_prefetch) + *prefetch_ptr = 1; + } +#else + (void)prefetch_ptr; #endif #ifndef WOLFSSL_AES_TOUCH_LINES @@ -2320,9 +2331,17 @@ static void AesEncrypt_C(Aes* aes, const byte* inBlock, byte* outBlock, s2 ^= u2 & 0x000000ff; s3 ^= u3 & 0x000000ff; } #endif -#else + +#else /* WOLFSSL_AES_SMALL_TABLES */ + #ifndef WC_NO_CACHE_RESISTANT - s0 |= PreFetchSBox(); + if (*prefetch_ptr == 0) { + s0 |= PreFetchSBox(); + if (prefetch_ptr != &always_prefetch) + *prefetch_ptr = 1; + } +#else + (void)prefetch_ptr; #endif r *= 2; @@ -2399,7 +2418,8 @@ static void AesEncrypt_C(Aes* aes, const byte* inBlock, byte* outBlock, s1 = t1 ^ rk[1]; s2 = t2 ^ rk[2]; s3 = t3 ^ rk[3]; -#endif + +#endif /* WOLFSSL_AES_SMALL_TABLES */ /* write out */ #ifdef LITTLE_ENDIAN_ORDER @@ -2429,9 +2449,10 @@ static void AesEncrypt_C(Aes* aes, const byte* inBlock, byte* outBlock, static void AesEncryptBlocks_C(Aes* aes, const byte* in, byte* out, word32 sz) { word32 i; + int did_prefetches = 0; for (i = 0; i < sz; i += WC_AES_BLOCK_SIZE) { - AesEncrypt_C(aes, in, out, aes->rounds >> 1); + AesEncrypt_C(aes, in, out, aes->rounds >> 1, &did_prefetches); in += WC_AES_BLOCK_SIZE; out += WC_AES_BLOCK_SIZE; } @@ -2998,10 +3019,18 @@ extern void AesEncryptBlocks_C(Aes* aes, const byte* in, byte* out, word32 sz); #endif /* !WC_AES_BITSLICED */ -/* this section disabled with NO_AES_192 */ -/* calling this one when missing NO_AES_192 */ +#ifdef WC_AES_HAVE_PREFETCH_ARG +#define wc_AesEncrypt(aes, inBlock, outBlock) \ + AesEncrypt_preFetchOpt(aes, inBlock, outBlock, &always_prefetch) +WC_ALL_ARGS_NOT_NULL static WARN_UNUSED_RESULT int AesEncrypt_preFetchOpt( + Aes* aes, const byte* inBlock, byte* outBlock, int *prefetch_ptr) +#else +#define AesEncrypt_preFetchOpt(aes, inBlock, outBlock, prefetch_ptr) \ + wc_AesEncrypt(aes, inBlock, outBlock) static WARN_UNUSED_RESULT int wc_AesEncrypt( Aes* aes, const byte* inBlock, byte* outBlock) + WC_ALL_ARGS_NOT_NULL +#endif { #if defined(MAX3266X_AES) word32 keySize; @@ -3153,7 +3182,11 @@ static WARN_UNUSED_RESULT int wc_AesEncrypt( } #endif +#ifdef WC_AES_HAVE_PREFETCH_ARG + AesEncrypt_C(aes, inBlock, outBlock, r, prefetch_ptr); +#else AesEncrypt_C(aes, inBlock, outBlock, r); +#endif return 0; } /* wc_AesEncrypt */ @@ -3211,8 +3244,14 @@ static WARN_UNUSED_RESULT WC_INLINE word32 PreFetchTd4(void) * @param [out] outBlock Encrypted block. * @param [in] r Rounds divided by 2. */ +#ifndef WC_AES_HAVE_PREFETCH_ARG + #define WC_AES_HAVE_PREFETCH_ARG + static int always_prefetch = 0; + WC_MAYBE_UNUSED static int never_prefetch = 1; +#endif +WC_ARGS_NOT_NULL((1, 2, 3, 5)) static void AesDecrypt_C(Aes* aes, const byte* inBlock, byte* outBlock, - word32 r) + word32 r, int *prefetch_ptr) { word32 s0 = 0, s1 = 0, s2 = 0, s3 = 0; word32 t0 = 0, t1 = 0, t2 = 0, t3 = 0; @@ -3246,8 +3285,16 @@ static void AesDecrypt_C(Aes* aes, const byte* inBlock, byte* outBlock, s3 ^= rk[3]; #ifndef WOLFSSL_AES_SMALL_TABLES + #ifndef WC_NO_CACHE_RESISTANT - s0 |= PreFetchTd(); + if (*prefetch_ptr == 0) { + s0 |= PreFetchTd(); + /* don't set the prefetched flag here -- PreFetchTd4() is called + * below. + */ + } +#else + (void)prefetch_ptr; #endif #ifndef WOLFSSL_AES_TOUCH_LINES @@ -3330,7 +3377,13 @@ static void AesDecrypt_C(Aes* aes, const byte* inBlock, byte* outBlock, */ #ifndef WC_NO_CACHE_RESISTANT - t0 |= PreFetchTd4(); + if (*prefetch_ptr == 0) { + t0 |= PreFetchTd4(); + if (prefetch_ptr != &always_prefetch) + *prefetch_ptr = 1; + } +#else + (void)prefetch_ptr; #endif s0 = GetTable8_4(Td4, GETBYTE(t0, 3), GETBYTE(t3, 2), @@ -3341,9 +3394,17 @@ static void AesDecrypt_C(Aes* aes, const byte* inBlock, byte* outBlock, GETBYTE(t0, 1), GETBYTE(t3, 0)) ^ rk[2]; s3 = GetTable8_4(Td4, GETBYTE(t3, 3), GETBYTE(t2, 2), GETBYTE(t1, 1), GETBYTE(t0, 0)) ^ rk[3]; -#else + +#else /* WOLFSSL_AES_SMALL_TABLES */ + #ifndef WC_NO_CACHE_RESISTANT - s0 |= PreFetchTd4(); + if (*prefetch_ptr == 0) { + s0 |= PreFetchTd4(); + if (prefetch_ptr != &always_prefetch) + *prefetch_ptr = 1; + } +#else + (void)prefetch_ptr; #endif r *= 2; @@ -3419,7 +3480,8 @@ static void AesDecrypt_C(Aes* aes, const byte* inBlock, byte* outBlock, s1 = t1 ^ rk[1]; s2 = t2 ^ rk[2]; s3 = t3 ^ rk[3]; -#endif + +#endif /* WOLFSSL_AES_SMALL_TABLES */ /* write out */ #ifdef LITTLE_ENDIAN_ORDER @@ -3450,9 +3512,10 @@ static void AesDecrypt_C(Aes* aes, const byte* inBlock, byte* outBlock, static void AesDecryptBlocks_C(Aes* aes, const byte* in, byte* out, word32 sz) { word32 i; + int did_prefetches = 0; for (i = 0; i < sz; i += WC_AES_BLOCK_SIZE) { - AesDecrypt_C(aes, in, out, aes->rounds >> 1); + AesDecrypt_C(aes, in, out, aes->rounds >> 1, &did_prefetches); in += WC_AES_BLOCK_SIZE; out += WC_AES_BLOCK_SIZE; } @@ -3808,8 +3871,18 @@ static void AesDecryptBlocks_C(Aes* aes, const byte* in, byte* out, word32 sz) #if defined(__aarch64__) || !defined(WOLFSSL_ARMASM) #if !defined(WC_AES_BITSLICED) || defined(WOLFSSL_AES_DIRECT) /* Software AES - ECB Decrypt */ -static WARN_UNUSED_RESULT int wc_AesDecrypt( + +#ifdef WC_AES_HAVE_PREFETCH_ARG +#define wc_AesDecrypt(aes, inBlock, outBlock) \ + AesDecrypt_preFetchOpt(aes, inBlock, outBlock, &always_prefetch) +WC_ALL_ARGS_NOT_NULL static WARN_UNUSED_RESULT int AesDecrypt_preFetchOpt( + Aes* aes, const byte* inBlock, byte* outBlock, int *prefetch_ptr) +#else +#define AesDecrypt_preFetchOpt(aes, inBlock, outBlock, prefetch_ptr) \ + wc_AesDecrypt(aes, inBlock, outBlock) +WC_ALL_ARGS_NOT_NULL static WARN_UNUSED_RESULT int wc_AesDecrypt( Aes* aes, const byte* inBlock, byte* outBlock) +#endif { #if defined(MAX3266X_AES) word32 keySize; @@ -3935,7 +4008,11 @@ static WARN_UNUSED_RESULT int wc_AesDecrypt( } #endif +#ifdef WC_AES_HAVE_PREFETCH_ARG + AesDecrypt_C(aes, inBlock, outBlock, r, prefetch_ptr); +#else AesDecrypt_C(aes, inBlock, outBlock, r); +#endif return 0; } /* wc_AesDecrypt[_SW]() */ @@ -3946,7 +4023,16 @@ static WARN_UNUSED_RESULT int wc_AesDecrypt( #endif /* NEED_AES_TABLES */ - +#ifndef WC_AES_HAVE_PREFETCH_ARG + #ifndef AesEncrypt_preFetchOpt + #define AesEncrypt_preFetchOpt(aes, inBlock, outBlock, do_preFetch) \ + wc_AesEncrypt(aes, inBlock, outBlock) + #endif + #ifndef AesDecrypt_preFetchOpt + #define AesDecrypt_preFetchOpt(aes, inBlock, outBlock, do_preFetch) \ + wc_AesDecrypt(aes, inBlock, outBlock) + #endif +#endif /* wc_AesSetKey */ #if defined(STM32_CRYPTO) @@ -5335,6 +5421,7 @@ int wc_AesSetIV(Aes* aes, const byte* iv) #else /* Allow direct access to one block encrypt */ + /* Note, the in and out args are swapped compared to wc_AesEncrypt(). */ int wc_AesEncryptDirect(Aes* aes, byte* out, const byte* in) { int ret; @@ -5355,6 +5442,7 @@ int wc_AesSetIV(Aes* aes, const byte* iv) #ifdef HAVE_AES_DECRYPT /* Allow direct access to one block decrypt */ + /* Note, the in and out args are swapped compared to wc_AesDecrypt(). */ int wc_AesDecryptDirect(Aes* aes, byte* out, const byte* in) { int ret; @@ -6097,7 +6185,6 @@ int wc_AesSetIV(Aes* aes, const byte* iv) offset += WC_AES_BLOCK_SIZE; } - return 0; } #endif /* HAVE_AES_DECRYPT */ @@ -6471,10 +6558,15 @@ int wc_AesCbcEncrypt(Aes* aes, byte* out, const byte* in, word32 sz) else #endif { +#ifdef WC_AES_HAVE_PREFETCH_ARG + int did_prefetches = 0; +#endif ret = 0; while (blocks--) { xorbuf((byte*)aes->reg, in, WC_AES_BLOCK_SIZE); - ret = wc_AesEncrypt(aes, (byte*)aes->reg, (byte*)aes->reg); + ret = AesEncrypt_preFetchOpt(aes, (byte*)aes->reg, + (byte*)aes->reg, + &did_prefetches); if (ret != 0) break; XMEMCPY(out, aes->reg, WC_AES_BLOCK_SIZE); @@ -6713,9 +6805,13 @@ int wc_AesCbcEncrypt(Aes* aes, byte* out, const byte* in, word32 sz) } } #else +#ifdef WC_AES_HAVE_PREFETCH_ARG + { + int did_prefetches = 0; +#endif while (blocks--) { XMEMCPY(aes->tmp, in, WC_AES_BLOCK_SIZE); - ret = wc_AesDecrypt(aes, in, out); + ret = AesDecrypt_preFetchOpt(aes, in, out, &did_prefetches); if (ret != 0) return ret; xorbuf(out, (byte*)aes->reg, WC_AES_BLOCK_SIZE); @@ -6725,6 +6821,9 @@ int wc_AesCbcEncrypt(Aes* aes, byte* out, const byte* in, word32 sz) out += WC_AES_BLOCK_SIZE; in += WC_AES_BLOCK_SIZE; } +#ifdef WC_AES_HAVE_PREFETCH_ARG + } +#endif #endif } @@ -6967,6 +7066,9 @@ int wc_AesCbcEncrypt(Aes* aes, byte* out, const byte* in, word32 sz) int ret = 0; #endif word32 processed; +#ifdef WC_AES_HAVE_PREFETCH_ARG + int did_prefetches = 0; +#endif #if !(!defined(__aarch64__) && defined(WOLFSSL_ARMASM) && \ !defined(WOLFSSL_ARMASM_NO_HW_CRYPTO)) @@ -7118,7 +7220,9 @@ int wc_AesCbcEncrypt(Aes* aes, byte* out, const byte* in, word32 sz) #ifdef XTRANSFORM_AESCTRBLOCK XTRANSFORM_AESCTRBLOCK(aes, out, in); #else - ret = wc_AesEncrypt(aes, (byte*)aes->reg, scratch); + ret = AesEncrypt_preFetchOpt(aes, (byte*)aes->reg, + scratch, + &did_prefetches); if (ret != 0) break; xorbuf(scratch, in, WC_AES_BLOCK_SIZE); @@ -7136,7 +7240,9 @@ int wc_AesCbcEncrypt(Aes* aes, byte* out, const byte* in, word32 sz) /* handle non block size remaining and store unused byte count in left */ if ((ret == 0) && sz) { - ret = wc_AesEncrypt(aes, (byte*)aes->reg, (byte*)aes->tmp); + ret = AesEncrypt_preFetchOpt(aes, (byte*)aes->reg, + (byte*)aes->tmp, + &did_prefetches); if (ret == 0) { IncrementAesCounter((byte*)aes->reg); aes->left = WC_AES_BLOCK_SIZE - sz; @@ -7175,6 +7281,16 @@ int wc_AesCbcEncrypt(Aes* aes, byte* out, const byte* in, word32 sz) #endif /* WOLFSSL_AES_COUNTER */ #endif /* !WOLFSSL_RISCV_ASM */ +#ifndef WC_AES_HAVE_PREFETCH_ARG + #ifndef AesEncrypt_preFetchOpt + #define AesEncrypt_preFetchOpt(aes, inBlock, outBlock, do_preFetch) \ + wc_AesEncrypt(aes, inBlock, outBlock) + #endif + #ifndef AesDecrypt_preFetchOpt + #define AesDecrypt_preFetchOpt(aes, inBlock, outBlock, do_preFetch) \ + wc_AesDecrypt(aes, inBlock, outBlock) + #endif +#endif /* * The IV for AES GCM and CCM, stored in struct Aes's member reg, is comprised @@ -9616,6 +9732,9 @@ WARN_UNUSED_RESULT int AES_GCM_encrypt_C( ALIGN16 byte counter[WC_AES_BLOCK_SIZE]; ALIGN16 byte initialCounter[WC_AES_BLOCK_SIZE]; ALIGN16 byte scratch[WC_AES_BLOCK_SIZE]; +#ifdef WC_AES_HAVE_PREFETCH_ARG + int did_prefetches = 0; +#endif if (ivSz == GCM_NONCE_MID_SZ) { /* Counter is IV with bottom 4 bytes set to: 0x00,0x00,0x00,0x01. */ @@ -9674,7 +9793,8 @@ WARN_UNUSED_RESULT int AES_GCM_encrypt_C( while (blocks--) { IncrementGcmCounter(counter); #if !defined(WOLFSSL_PIC32MZ_CRYPT) - ret = wc_AesEncrypt(aes, counter, scratch); + ret = AesEncrypt_preFetchOpt(aes, counter, scratch, + &did_prefetches); if (ret != 0) return ret; xorbufout(c, scratch, p, WC_AES_BLOCK_SIZE); @@ -9686,14 +9806,15 @@ WARN_UNUSED_RESULT int AES_GCM_encrypt_C( if (partial != 0) { IncrementGcmCounter(counter); - ret = wc_AesEncrypt(aes, counter, scratch); + ret = AesEncrypt_preFetchOpt(aes, counter, scratch, &did_prefetches); if (ret != 0) return ret; xorbufout(c, scratch, p, partial); } if (authTag) { GHASH(&aes->gcm, authIn, authInSz, out, sz, authTag, authTagSz); - ret = wc_AesEncrypt(aes, initialCounter, scratch); + ret = AesEncrypt_preFetchOpt(aes, initialCounter, scratch, + &did_prefetches); if (ret != 0) return ret; xorbuf(authTag, scratch, authTagSz); @@ -12814,7 +12935,11 @@ static WARN_UNUSED_RESULT int roll_x( in += WC_AES_BLOCK_SIZE; inSz -= WC_AES_BLOCK_SIZE; - ret = wc_AesEncrypt(aes, out, out); + /* wc_AesCcmEncrypt(), wc_AesCcmDecrypt(), and roll_auth() only call + * roll_x() after the AES cache lines are already hot -- no need to + * absorb additional prefetch overhead here. + */ + ret = AesEncrypt_preFetchOpt(aes, out, out, &never_prefetch); if (ret != 0) return ret; } @@ -12822,7 +12947,11 @@ static WARN_UNUSED_RESULT int roll_x( /* process remainder of the data */ if (inSz > 0) { xorbuf(out, in, inSz); - ret = wc_AesEncrypt(aes, out, out); + /* wc_AesCcmEncrypt(), wc_AesCcmDecrypt(), and roll_auth() only call + * roll_x() after the AES cache lines are already hot -- no need to + * absorb additional prefetch overhead here. + */ + ret = AesEncrypt_preFetchOpt(aes, out, out, &never_prefetch); if (ret != 0) return ret; } @@ -12870,7 +12999,11 @@ static WARN_UNUSED_RESULT int roll_auth( xorbuf(out + authLenSz, in, inSz); inSz = 0; } - ret = wc_AesEncrypt(aes, out, out); + /* wc_AesCcmEncrypt() and wc_AesCcmDecrypt() only call roll_auth() after the + * AES cache lines are already hot -- no need to absorb additional prefetch + * overhead here. + */ + ret = AesEncrypt_preFetchOpt(aes, out, out, &never_prefetch); if ((ret == 0) && (inSz > 0)) { ret = roll_x(aes, in, inSz, out); @@ -12996,6 +13129,9 @@ int wc_AesCcmEncrypt(Aes* aes, byte* out, const byte* in, word32 inSz, #endif VECTOR_REGISTERS_PUSH; + /* note this wc_AesEncrypt() will perform cache prefetches if needed, so + * that the later encrypt ops don't need to. + */ ret = wc_AesEncrypt(aes, B, A); #ifdef WOLFSSL_CHECK_MEM_ZERO if (ret == 0) @@ -13014,7 +13150,7 @@ int wc_AesCcmEncrypt(Aes* aes, byte* out, const byte* in, word32 inSz, B[0] = (byte)(lenSz - 1U); for (i = 0; i < lenSz; i++) B[WC_AES_BLOCK_SIZE - 1 - i] = 0; - ret = wc_AesEncrypt(aes, B, A); + ret = AesEncrypt_preFetchOpt(aes, B, A, &never_prefetch); } if (ret == 0) { @@ -13042,7 +13178,7 @@ int wc_AesCcmEncrypt(Aes* aes, byte* out, const byte* in, word32 inSz, #endif if (ret == 0) { while (inSz >= WC_AES_BLOCK_SIZE) { - ret = wc_AesEncrypt(aes, B, A); + ret = AesEncrypt_preFetchOpt(aes, B, A, &never_prefetch); if (ret != 0) break; xorbuf(A, in, WC_AES_BLOCK_SIZE); @@ -13055,7 +13191,7 @@ int wc_AesCcmEncrypt(Aes* aes, byte* out, const byte* in, word32 inSz, } } if ((ret == 0) && (inSz > 0)) { - ret = wc_AesEncrypt(aes, B, A); + ret = AesEncrypt_preFetchOpt(aes, B, A, &never_prefetch); } if ((ret == 0) && (inSz > 0)) { xorbuf(A, in, inSz); @@ -13095,6 +13231,9 @@ int wc_AesCcmDecrypt(Aes* aes, byte* out, const byte* in, word32 inSz, byte mask = 0xFF; const word32 wordSz = (word32)sizeof(word32); int ret = 0; +#ifdef WC_AES_HAVE_PREFETCH_ARG + int did_prefetches = 0; +#endif /* sanity check on arguments */ if (aes == NULL || (inSz != 0 && (in == NULL || out == NULL)) || @@ -13165,7 +13304,7 @@ int wc_AesCcmDecrypt(Aes* aes, byte* out, const byte* in, word32 inSz, #endif while (oSz >= WC_AES_BLOCK_SIZE) { - ret = wc_AesEncrypt(aes, B, A); + ret = AesEncrypt_preFetchOpt(aes, B, A, &did_prefetches); if (ret != 0) break; xorbuf(A, in, WC_AES_BLOCK_SIZE); @@ -13177,14 +13316,14 @@ int wc_AesCcmDecrypt(Aes* aes, byte* out, const byte* in, word32 inSz, } if ((ret == 0) && (inSz > 0)) - ret = wc_AesEncrypt(aes, B, A); + ret = AesEncrypt_preFetchOpt(aes, B, A, &did_prefetches); if ((ret == 0) && (inSz > 0)) { xorbuf(A, in, oSz); XMEMCPY(o, A, oSz); for (i = 0; i < lenSz; i++) B[WC_AES_BLOCK_SIZE - 1 - i] = 0; - ret = wc_AesEncrypt(aes, B, A); + ret = AesEncrypt_preFetchOpt(aes, B, A, &did_prefetches); } if (ret == 0) { @@ -13200,7 +13339,7 @@ int wc_AesCcmDecrypt(Aes* aes, byte* out, const byte* in, word32 inSz, B[WC_AES_BLOCK_SIZE - 1 - i] = (byte)((inSz >> ((8 * i) & mask)) & mask); } - ret = wc_AesEncrypt(aes, B, A); + ret = AesEncrypt_preFetchOpt(aes, B, A, &did_prefetches); } if (ret == 0) { @@ -13214,7 +13353,7 @@ int wc_AesCcmDecrypt(Aes* aes, byte* out, const byte* in, word32 inSz, B[0] = (byte)(lenSz - 1U); for (i = 0; i < lenSz; i++) B[WC_AES_BLOCK_SIZE - 1 - i] = 0; - ret = wc_AesEncrypt(aes, B, B); + ret = AesEncrypt_preFetchOpt(aes, B, B, &did_prefetches); } if (ret == 0) @@ -13772,9 +13911,12 @@ static WARN_UNUSED_RESULT int _AesEcbEncrypt( AesEncryptBlocks_C(aes, in, out, sz); #else word32 i; +#ifdef WC_AES_HAVE_PREFETCH_ARG + int did_prefetches = 0; +#endif for (i = 0; i < sz; i += WC_AES_BLOCK_SIZE) { - ret = wc_AesEncryptDirect(aes, out, in); + ret = AesEncrypt_preFetchOpt(aes, in, out, &did_prefetches); if (ret != 0) break; in += WC_AES_BLOCK_SIZE; @@ -13936,6 +14078,9 @@ static WARN_UNUSED_RESULT int AesCfbEncrypt_C(Aes* aes, byte* out, { int ret = 0; word32 processed; +#ifdef WC_AES_HAVE_PREFETCH_ARG + int did_prefetches = 0; +#endif if ((aes == NULL) || (out == NULL) || (in == NULL)) { return BAD_FUNC_ARG; @@ -13960,7 +14105,8 @@ static WARN_UNUSED_RESULT int AesCfbEncrypt_C(Aes* aes, byte* out, VECTOR_REGISTERS_PUSH; while (sz >= WC_AES_BLOCK_SIZE) { - ret = wc_AesEncryptDirect(aes, (byte*)aes->reg, (byte*)aes->reg); + ret = AesEncrypt_preFetchOpt(aes, (byte*)aes->reg, (byte*)aes->reg, + &did_prefetches); if (ret != 0) { break; } @@ -13973,7 +14119,8 @@ static WARN_UNUSED_RESULT int AesCfbEncrypt_C(Aes* aes, byte* out, /* encrypt left over data */ if ((ret == 0) && sz) { - ret = wc_AesEncryptDirect(aes, (byte*)aes->tmp, (byte*)aes->reg); + ret = AesEncrypt_preFetchOpt(aes, (byte*)aes->reg, (byte*)aes->tmp, + &did_prefetches); if (ret == 0) { xorbufout(out, in, aes->tmp, sz); XMEMCPY(aes->reg, out, sz); @@ -14004,6 +14151,9 @@ static WARN_UNUSED_RESULT int AesCfbDecrypt_C(Aes* aes, byte* out, { int ret = 0; word32 processed; +#ifdef WC_AES_HAVE_PREFETCH_ARG + int did_prefetches = 0; +#endif (void)mode; @@ -14050,7 +14200,8 @@ static WARN_UNUSED_RESULT int AesCfbDecrypt_C(Aes* aes, byte* out, } #endif while (sz >= WC_AES_BLOCK_SIZE) { - ret = wc_AesEncryptDirect(aes, (byte*)aes->tmp, (byte*)aes->reg); + ret = AesEncrypt_preFetchOpt(aes, (byte*)aes->reg, (byte*)aes->tmp, + &did_prefetches); if (ret != 0) { break; } @@ -14063,7 +14214,8 @@ static WARN_UNUSED_RESULT int AesCfbDecrypt_C(Aes* aes, byte* out, /* decrypt left over data */ if ((ret == 0) && sz) { - ret = wc_AesEncryptDirect(aes, (byte*)aes->tmp, (byte*)aes->reg); + ret = AesEncrypt_preFetchOpt(aes, (byte*)aes->reg, (byte*)aes->tmp, + &did_prefetches); if (ret == 0) { XMEMCPY(aes->reg, in, sz); xorbufout(out, in, aes->tmp, sz); @@ -14144,6 +14296,9 @@ static WARN_UNUSED_RESULT int wc_AesFeedbackCFB8( { byte *pt; int ret = 0; +#ifdef WC_AES_HAVE_PREFETCH_ARG + int did_prefetches = 0; +#endif if (aes == NULL || out == NULL || in == NULL) { return BAD_FUNC_ARG; @@ -14156,7 +14311,8 @@ static WARN_UNUSED_RESULT int wc_AesFeedbackCFB8( VECTOR_REGISTERS_PUSH; while (sz > 0) { - ret = wc_AesEncryptDirect(aes, (byte*)aes->tmp, (byte*)aes->reg); + ret = AesEncrypt_preFetchOpt(aes, (byte*)aes->reg, (byte*)aes->tmp, + &did_prefetches); if (ret != 0) break; if (dir == AES_DECRYPTION) { @@ -14200,6 +14356,9 @@ static WARN_UNUSED_RESULT int wc_AesFeedbackCFB1( byte* pt; int bit = 7; int ret = 0; +#ifdef WC_AES_HAVE_PREFETCH_ARG + int did_prefetches = 0; +#endif if (aes == NULL || out == NULL || in == NULL) { return BAD_FUNC_ARG; @@ -14212,7 +14371,8 @@ static WARN_UNUSED_RESULT int wc_AesFeedbackCFB1( VECTOR_REGISTERS_PUSH; while (sz > 0) { - ret = wc_AesEncryptDirect(aes, (byte*)aes->tmp, (byte*)aes->reg); + ret = AesEncrypt_preFetchOpt(aes, (byte*)aes->reg, (byte*)aes->tmp, + &did_prefetches); if (ret != 0) break; if (dir == AES_DECRYPTION) { @@ -14351,6 +14511,9 @@ static WARN_UNUSED_RESULT int AesOfbCrypt_C(Aes* aes, byte* out, const byte* in, { int ret = 0; word32 processed; +#ifdef WC_AES_HAVE_PREFETCH_ARG + int did_prefetches = 0; +#endif if ((aes == NULL) || (out == NULL) || (in == NULL)) { return BAD_FUNC_ARG; @@ -14373,7 +14536,8 @@ static WARN_UNUSED_RESULT int AesOfbCrypt_C(Aes* aes, byte* out, const byte* in, VECTOR_REGISTERS_PUSH; while (sz >= WC_AES_BLOCK_SIZE) { - ret = wc_AesEncryptDirect(aes, (byte*)aes->reg, (byte*)aes->reg); + ret = AesEncrypt_preFetchOpt(aes, (byte*)aes->reg, (byte*)aes->reg, + &did_prefetches); if (ret != 0) { break; } @@ -14385,7 +14549,8 @@ static WARN_UNUSED_RESULT int AesOfbCrypt_C(Aes* aes, byte* out, const byte* in, /* encrypt left over data */ if ((ret == 0) && sz) { - ret = wc_AesEncryptDirect(aes, (byte*)aes->tmp, (byte*)aes->reg); + ret = AesEncrypt_preFetchOpt(aes, (byte*)aes->reg, (byte*)aes->tmp, + &did_prefetches); if (ret == 0) { XMEMCPY(aes->reg, aes->tmp, WC_AES_BLOCK_SIZE); xorbufout(out, in, aes->tmp, sz); @@ -16096,6 +16261,45 @@ int wc_AesXtsDecryptConsecutiveSectors(XtsAes* aes, byte* out, const byte* in, #endif /* HAVE_AES_DECRYPT */ #endif /* WOLFSSL_AES_XTS */ +#ifdef WOLFSSL_CMAC + +int wc_local_CmacUpdateAes(struct Cmac *cmac, const byte* in, word32 inSz) { + int ret = 0; + Aes *aes = &cmac->aes; +#ifdef WC_AES_HAVE_PREFETCH_ARG + int did_prefetches = 0; +#endif + + VECTOR_REGISTERS_PUSH; + + while ((ret == 0) && (inSz != 0)) { + word32 add = min(inSz, WC_AES_BLOCK_SIZE - cmac->bufferSz); + XMEMCPY(&cmac->buffer[cmac->bufferSz], in, add); + + cmac->bufferSz += add; + inSz -= add; + in += add; + + if (cmac->bufferSz == WC_AES_BLOCK_SIZE && inSz != 0) { + if (cmac->totalSz != 0) { + xorbuf(cmac->buffer, cmac->digest, WC_AES_BLOCK_SIZE); + } + ret = AesEncrypt_preFetchOpt(aes, cmac->buffer, + cmac->digest, &did_prefetches); + if (ret == 0) { + cmac->totalSz += WC_AES_BLOCK_SIZE; + cmac->bufferSz = 0; + } + } + } + + VECTOR_REGISTERS_POP; + + return ret; +} + +#endif /* WOLFSSL_CMAC */ + #ifdef WOLFSSL_AES_SIV /* diff --git a/wolfcrypt/src/cmac.c b/wolfcrypt/src/cmac.c index ac9a14811e..66e45f9247 100644 --- a/wolfcrypt/src/cmac.c +++ b/wolfcrypt/src/cmac.c @@ -228,6 +228,7 @@ int wc_CmacUpdate(Cmac* cmac, const byte* in, word32 inSz) #if !defined(NO_AES) && defined(WOLFSSL_AES_DIRECT) case WC_CMAC_AES: { +#ifdef HAVE_SELFTEST while ((ret == 0) && (inSz != 0)) { word32 add = min(inSz, WC_AES_BLOCK_SIZE - cmac->bufferSz); XMEMCPY(&cmac->buffer[cmac->bufferSz], in, add); @@ -240,21 +241,16 @@ int wc_CmacUpdate(Cmac* cmac, const byte* in, word32 inSz) if (cmac->totalSz != 0) { xorbuf(cmac->buffer, cmac->digest, WC_AES_BLOCK_SIZE); } -#ifndef HAVE_SELFTEST - ret = wc_AesEncryptDirect(&cmac->aes, cmac->digest, - cmac->buffer); - if (ret == 0) { - cmac->totalSz += WC_AES_BLOCK_SIZE; - cmac->bufferSz = 0; - } -#else wc_AesEncryptDirect(&cmac->aes, cmac->digest, cmac->buffer); cmac->totalSz += WC_AES_BLOCK_SIZE; cmac->bufferSz = 0; -#endif } } +#else + (void)ret; + ret = wc_local_CmacUpdateAes(cmac, in, inSz); +#endif }; break; #endif /* !NO_AES && WOLFSSL_AES_DIRECT */ default: diff --git a/wolfssl/wolfcrypt/aes.h b/wolfssl/wolfcrypt/aes.h index da27ccc709..3c4a7db5eb 100644 --- a/wolfssl/wolfcrypt/aes.h +++ b/wolfssl/wolfcrypt/aes.h @@ -806,6 +806,13 @@ int wc_AesSivDecrypt_ex(const byte* key, word32 keySz, const AesSivAssoc* assoc, const byte* in, word32 inSz, byte* siv, byte* out); #endif +#ifdef WOLFSSL_CMAC +/* forward declaration, in case aes.h is being included by cmac.h */ +struct Cmac; +WOLFSSL_LOCAL int wc_local_CmacUpdateAes(struct Cmac *cmac, const byte* in, + word32 inSz); +#endif + #ifdef WOLFSSL_AES_EAX /* Because of the circular dependency between AES and CMAC, we need to prevent diff --git a/wolfssl/wolfcrypt/types.h b/wolfssl/wolfcrypt/types.h index 28bb12fc17..4ba6dfbe75 100644 --- a/wolfssl/wolfcrypt/types.h +++ b/wolfssl/wolfcrypt/types.h @@ -2056,6 +2056,31 @@ WOLFSSL_API word32 CheckRunTimeSettings(void); #define WC_NORETURN #endif +#if defined(__has_attribute) && __has_attribute(nonnull) + #ifndef WC_ARG_NOT_NULL + #define WC_ARG_NOT_NULL(a) __attribute__((nonnull(a))) + #endif + #ifndef WC_ARGS_NOT_NULL + /* double-parenthesize, a la WC_ARGS_NOT_NULL((1, 2)) -- this approach + * maintains compatibility with WOLF_NO_VARIADIC_MACROS. + */ + #define WC_ARGS_NOT_NULL(p_a) __attribute__((nonnull p_a)) + #endif + #ifndef WC_ALL_ARGS_NOT_NULL + #define WC_ALL_ARGS_NOT_NULL __attribute__((nonnull)) + #endif +#else + #ifndef WC_ARG_NOT_NULL + #define WC_ARG_NOT_NULL(a) /* null expansion */ + #endif + #ifndef WC_ARGS_NOT_NULL + #define WC_ARGS_NOT_NULL(p_a) /* null expansion */ + #endif + #ifndef WC_ALL_ARGS_NOT_NULL + #define WC_ALL_ARGS_NOT_NULL + #endif +#endif + #if defined(WOLFSSL_KEY_GEN) || defined(HAVE_COMP_KEY) || \ defined(WOLFSSL_DEBUG_MATH) || defined(DEBUG_WOLFSSL) || \ defined(WOLFSSL_PUBLIC_MP) || defined(OPENSSL_EXTRA) || \ From 314da6d6bc1f388ae1f9b3e35c40dccab912a42c Mon Sep 17 00:00:00 2001 From: Daniel Pouzzner Date: Tue, 24 Feb 2026 15:41:11 -0600 Subject: [PATCH 16/36] wolfssl/wolfcrypt/types.h: work around limitations of Watcom and Windows preprocessors, re WC_ARG_NOT_NULL and friends. --- wolfssl/wolfcrypt/types.h | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/wolfssl/wolfcrypt/types.h b/wolfssl/wolfcrypt/types.h index 4ba6dfbe75..af7d6b6224 100644 --- a/wolfssl/wolfcrypt/types.h +++ b/wolfssl/wolfcrypt/types.h @@ -2056,7 +2056,8 @@ WOLFSSL_API word32 CheckRunTimeSettings(void); #define WC_NORETURN #endif -#if defined(__has_attribute) && __has_attribute(nonnull) +#ifdef __has_attribute +#if __has_attribute(nonnull) #ifndef WC_ARG_NOT_NULL #define WC_ARG_NOT_NULL(a) __attribute__((nonnull(a))) #endif @@ -2069,16 +2070,17 @@ WOLFSSL_API word32 CheckRunTimeSettings(void); #ifndef WC_ALL_ARGS_NOT_NULL #define WC_ALL_ARGS_NOT_NULL __attribute__((nonnull)) #endif -#else - #ifndef WC_ARG_NOT_NULL - #define WC_ARG_NOT_NULL(a) /* null expansion */ - #endif - #ifndef WC_ARGS_NOT_NULL - #define WC_ARGS_NOT_NULL(p_a) /* null expansion */ - #endif - #ifndef WC_ALL_ARGS_NOT_NULL - #define WC_ALL_ARGS_NOT_NULL - #endif +#endif /* __has_attribute(nonnull) */ +#endif /* defined(__has_attribute) */ + +#ifndef WC_ARG_NOT_NULL + #define WC_ARG_NOT_NULL(a) /* null expansion */ +#endif +#ifndef WC_ARGS_NOT_NULL + #define WC_ARGS_NOT_NULL(p_a) /* null expansion */ +#endif +#ifndef WC_ALL_ARGS_NOT_NULL + #define WC_ALL_ARGS_NOT_NULL #endif #if defined(WOLFSSL_KEY_GEN) || defined(HAVE_COMP_KEY) || \ From 3f3bf7501c7dcec2a3e691575d517564a91e359d Mon Sep 17 00:00:00 2001 From: JacobBarthelmeh Date: Tue, 24 Feb 2026 16:35:10 -0700 Subject: [PATCH 17/36] reduce arduino coverage to avoid tests failing from external changes --- .github/workflows/arduino.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/arduino.yml b/.github/workflows/arduino.yml index 4ed695b2fc..02291d8b51 100644 --- a/.github/workflows/arduino.yml +++ b/.github/workflows/arduino.yml @@ -94,11 +94,9 @@ jobs: - arduino:avr:nano - arduino:avr:uno - arduino:avr:yun - - arduino:samd:mkrwifi1010 - arduino:samd:mkr1000 - arduino:samd:mkrfox1200 - arduino:mbed_edge:edge_control - - arduino:mbed_nano:nanorp2040connect - arduino:mbed_portenta:envie_m7 - arduino:mbed_portenta:portenta_x8 - arduino:renesas_uno:unor4wifi From 3295a6521ce3ae81977e390ee4033781040bd582 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Tue, 24 Feb 2026 17:55:43 -0800 Subject: [PATCH 18/36] Fix Fenrir issues in wolfcrypt --- wolfcrypt/src/aes.c | 4 ++-- wolfcrypt/src/asn.c | 2 +- wolfcrypt/src/chacha20_poly1305.c | 17 ++++++++++++----- wolfcrypt/src/ecc.c | 8 ++++++-- wolfcrypt/src/evp.c | 4 ++-- wolfcrypt/src/hpke.c | 11 +++++++++++ wolfcrypt/src/pkcs12.c | 8 +++++++- wolfcrypt/src/pwdbased.c | 3 +++ wolfcrypt/src/sakke.c | 3 ++- wolfcrypt/src/srp.c | 6 ++++-- wolfcrypt/src/wc_mlkem.c | 5 +++++ 11 files changed, 55 insertions(+), 16 deletions(-) diff --git a/wolfcrypt/src/aes.c b/wolfcrypt/src/aes.c index 29911ccfa2..6f951bc676 100644 --- a/wolfcrypt/src/aes.c +++ b/wolfcrypt/src/aes.c @@ -14643,7 +14643,7 @@ int wc_AesKeyUnWrap_ex(Aes *aes, const byte* in, word32 inSz, byte* out, return ret; /* verify IV */ - if (XMEMCMP(tmp, expIv, KEYWRAP_BLOCK_SIZE) != 0) + if (ConstantCompare(tmp, expIv, KEYWRAP_BLOCK_SIZE) != 0) return BAD_KEYWRAP_IV_E; return (int)(inSz - KEYWRAP_BLOCK_SIZE); @@ -16303,7 +16303,7 @@ static WARN_UNUSED_RESULT int AesSivCipher( WOLFSSL_MSG("S2V failed."); } - if (XMEMCMP(siv, sivTmp, WC_AES_BLOCK_SIZE) != 0) { + if (ConstantCompare(siv, sivTmp, WC_AES_BLOCK_SIZE) != 0) { WOLFSSL_MSG("Computed SIV doesn't match received SIV."); ret = AES_SIV_AUTH_E; } diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index 3be5e33fb0..a0744e6ca8 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -487,7 +487,7 @@ static word32 SizeASNLength(word32 length) #define ALLOC_ASNSETDATA(name, cnt, err, heap) \ do { \ if ((err) == 0) { \ - (name) = (ASNSetData*)XMALLOC(sizeof(ASNGetData) * (cnt), (heap), \ + (name) = (ASNSetData*)XMALLOC(sizeof(ASNSetData) * (cnt), (heap), \ DYNAMIC_TYPE_TMP_BUFFER); \ if ((name) == NULL) { \ (err) = MEMORY_E; \ diff --git a/wolfcrypt/src/chacha20_poly1305.c b/wolfcrypt/src/chacha20_poly1305.c index f788c2ed64..8911b4d53d 100644 --- a/wolfcrypt/src/chacha20_poly1305.c +++ b/wolfcrypt/src/chacha20_poly1305.c @@ -187,6 +187,8 @@ int wc_ChaCha20Poly1305_Init(ChaChaPoly_Aead* aead, aead->state = CHACHA20_POLY1305_STATE_READY; } + ForceZero(authKey, sizeof(authKey)); + return ret; } @@ -332,25 +334,30 @@ int wc_XChaCha20Poly1305_Init( /* Create the Poly1305 key */ if ((ret = wc_Chacha_Process(&aead->chacha, authKey, authKey, (word32)sizeof authKey)) < 0) - return ret; + goto out; /* advance to start of the next ChaCha block. */ wc_Chacha_purge_current_block(&aead->chacha); /* Initialize Poly1305 context */ if ((ret = wc_Poly1305SetKey(&aead->poly, authKey, (word32)sizeof authKey)) < 0) - return ret; + goto out; if ((ret = wc_Poly1305Update(&aead->poly, ad, (word32)ad_len)) < 0) - return ret; + goto out; if ((ret = wc_Poly1305_Pad(&aead->poly, (word32)ad_len)) < 0) - return ret; + goto out; aead->isEncrypt = isEncrypt ? 1 : 0; aead->state = CHACHA20_POLY1305_STATE_AAD; - return 0; + ret = 0; + +out: + ForceZero(authKey, sizeof(authKey)); + + return ret; } static WC_INLINE int wc_XChaCha20Poly1305_crypt_oneshot( diff --git a/wolfcrypt/src/ecc.c b/wolfcrypt/src/ecc.c index d7acd0bdaf..721c52a1d5 100644 --- a/wolfcrypt/src/ecc.c +++ b/wolfcrypt/src/ecc.c @@ -14484,6 +14484,8 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, RESTORE_VECTOR_REGISTERS(); + ForceZero(sharedSecret, sharedSz); + ForceZero(keys, (word32)keysLen); WC_FREE_VAR_EX(sharedSecret, ctx->heap, DYNAMIC_TYPE_ECC_BUFFER); WC_FREE_VAR_EX(keys, ctx->heap, DYNAMIC_TYPE_ECC_BUFFER); @@ -14778,8 +14780,8 @@ int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, if (ret == 0) ret = wc_HmacFinal(hmac, verify); - if ((ret == 0) && (XMEMCMP(verify, msg + msgSz - digestSz, - digestSz) != 0)) { + if ((ret == 0) && (ConstantCompare(verify, msg + msgSz - digestSz, + (int)digestSz) != 0)) { ret = HASH_TYPE_E; WOLFSSL_MSG("ECC Decrypt HMAC Check failed!"); } @@ -14882,6 +14884,8 @@ int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, if (pubKey == peerKey) wc_ecc_free(peerKey); #endif + ForceZero(sharedSecret, sharedSz); + ForceZero(keys, (word32)keysLen); #ifdef WOLFSSL_SMALL_STACK #ifndef WOLFSSL_ECIES_OLD XFREE(peerKey, ctx->heap, DYNAMIC_TYPE_ECC_BUFFER); diff --git a/wolfcrypt/src/evp.c b/wolfcrypt/src/evp.c index dc14d4fe66..cc2bb3a73b 100644 --- a/wolfcrypt/src/evp.c +++ b/wolfcrypt/src/evp.c @@ -4952,7 +4952,7 @@ int wolfSSL_EVP_DigestVerifyFinal(WOLFSSL_EVP_MD_CTX *ctx, hashLen = wolfssl_mac_len(ctx->hash.hmac.macType); - if (siglen > hashLen) + if (siglen > hashLen || siglen > INT_MAX) return WOLFSSL_FAILURE; /* May be a truncated signature. */ } @@ -4962,7 +4962,7 @@ int wolfSSL_EVP_DigestVerifyFinal(WOLFSSL_EVP_MD_CTX *ctx, if (ctx->isHMAC) { /* Check HMAC result matches the signature. */ - if (XMEMCMP(sig, digest, (size_t)siglen) == 0) + if (ConstantCompare(sig, digest, (int)siglen) == 0) return WOLFSSL_SUCCESS; return WOLFSSL_FAILURE; } diff --git a/wolfcrypt/src/hpke.c b/wolfcrypt/src/hpke.c index 9f99f190ba..e7b15db0a4 100644 --- a/wolfcrypt/src/hpke.c +++ b/wolfcrypt/src/hpke.c @@ -796,6 +796,8 @@ static int wc_HpkeEncap(Hpke* hpke, void* ephemeralKey, void* receiverKey, hpke->Npk * 2, sharedSecret); } + ForceZero(dh, hpke->Ndh); + ForceZero(kemContext, hpke->Npk * 2); WC_FREE_VAR_EX(dh, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER); WC_FREE_VAR_EX(kemContext, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER); @@ -816,6 +818,9 @@ static int wc_HpkeSetupBaseSender(Hpke* hpke, HpkeBaseContext* context, #ifdef WOLFSSL_SMALL_STACK sharedSecret = (byte*)XMALLOC(hpke->Nsecret, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER); + if (sharedSecret == NULL) { + return MEMORY_E; + } #endif /* encap */ @@ -827,6 +832,7 @@ static int wc_HpkeSetupBaseSender(Hpke* hpke, HpkeBaseContext* context, infoSz); } + ForceZero(sharedSecret, hpke->Nsecret); WC_FREE_VAR_EX(sharedSecret, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER); return ret; @@ -914,6 +920,7 @@ int wc_HpkeSealBase(Hpke* hpke, void* ephemeralKey, void* receiverKey, PRIVATE_KEY_LOCK(); + ForceZero(context, sizeof(HpkeBaseContext)); WC_FREE_VAR_EX(context, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER); return ret; @@ -1032,6 +1039,8 @@ static int wc_HpkeDecap(Hpke* hpke, void* receiverKey, const byte* pubKey, hpke->Npk * 2, sharedSecret); } + ForceZero(dh, hpke->Ndh); + ForceZero(kemContext, hpke->Npk * 2); WC_FREE_VAR_EX(dh, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER); WC_FREE_VAR_EX(kemContext, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER); @@ -1058,6 +1067,7 @@ static int wc_HpkeSetupBaseReceiver(Hpke* hpke, HpkeBaseContext* context, infoSz); } + ForceZero(sharedSecret, hpke->Nsecret); WC_FREE_VAR_EX(sharedSecret, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER); return ret; @@ -1144,6 +1154,7 @@ int wc_HpkeOpenBase(Hpke* hpke, void* receiverKey, const byte* pubKey, PRIVATE_KEY_LOCK(); + ForceZero(context, sizeof(HpkeBaseContext)); WC_FREE_VAR_EX(context, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER); return ret; diff --git a/wolfcrypt/src/pkcs12.c b/wolfcrypt/src/pkcs12.c index 93066a5e3f..3edfd36830 100644 --- a/wolfcrypt/src/pkcs12.c +++ b/wolfcrypt/src/pkcs12.c @@ -637,7 +637,13 @@ static int wc_PKCS12_verify(WC_PKCS12* pkcs12, byte* data, word32 dataSz, } #endif - return XMEMCMP(digest, mac->digest, mac->digestSz); + if (ConstantCompare(digest, mac->digest, (int)mac->digestSz) != 0) { + ForceZero(digest, sizeof(digest)); + return MAC_CMP_FAILED_E; + } + + ForceZero(digest, sizeof(digest)); + return 0; } int wc_PKCS12_verify_ex(WC_PKCS12* pkcs12, const byte* psw, word32 pswSz) diff --git a/wolfcrypt/src/pwdbased.c b/wolfcrypt/src/pwdbased.c index d575eaa362..5e212b5d6c 100644 --- a/wolfcrypt/src/pwdbased.c +++ b/wolfcrypt/src/pwdbased.c @@ -152,6 +152,8 @@ int wc_PBKDF1_ex(byte* key, int keyLen, byte* iv, int ivLen, WC_FREE_VAR_EX(hash, heap, DYNAMIC_TYPE_HASHCTX); + ForceZero(digest, sizeof(digest)); + if (err != 0) return err; @@ -294,6 +296,7 @@ int wc_PBKDF2_ex(byte* output, const byte* passwd, int pLen, const byte* salt, wc_HmacFree(hmac); } + ForceZero(buffer, (word32)hLen); WC_FREE_VAR_EX(buffer, heap, DYNAMIC_TYPE_TMP_BUFFER); WC_FREE_VAR_EX(hmac, heap, DYNAMIC_TYPE_HMAC); diff --git a/wolfcrypt/src/sakke.c b/wolfcrypt/src/sakke.c index e59943506b..97de612b8e 100644 --- a/wolfcrypt/src/sakke.c +++ b/wolfcrypt/src/sakke.c @@ -6941,7 +6941,8 @@ int wc_DeriveSakkeSSV(SakkeKey* key, enum wc_HashType hashType, byte* ssv, err = sakke_compute_point_r(key, key->id, key->idSz, ri, n, test); } - if ((err == 0) && (XMEMCMP(auth, test, (size_t)(2 * n + 1)) != 0)) { + /* n is word16, so 2*n+1 always fits in int */ + if ((err == 0) && (ConstantCompare(auth, test, (int)(2 * n + 1)) != 0)) { err = SAKKE_VERIFY_FAIL_E; } diff --git a/wolfcrypt/src/srp.c b/wolfcrypt/src/srp.c index 9f2b416f13..5f51d4f5ee 100644 --- a/wolfcrypt/src/srp.c +++ b/wolfcrypt/src/srp.c @@ -982,7 +982,7 @@ int wc_SrpVerifyPeersProof(Srp* srp, byte* proof, word32 size) if (hashSize < 0) return ALGO_ID_E; - if (size != (word32)hashSize) + if (size != (word32)hashSize || size > INT_MAX) return BUFFER_E; r = SrpHashFinal(srp->side == SRP_CLIENT_SIDE ? &srp->server_proof @@ -994,9 +994,11 @@ int wc_SrpVerifyPeersProof(Srp* srp, byte* proof, word32 size) if (!r) r = SrpHashUpdate(&srp->server_proof, srp->key, srp->keySz); } - if (!r && XMEMCMP(proof, digest, size) != 0) + if (!r && ConstantCompare(proof, digest, (int)size) != 0) r = SRP_VERIFY_E; + ForceZero(digest, sizeof(digest)); + return r; } diff --git a/wolfcrypt/src/wc_mlkem.c b/wolfcrypt/src/wc_mlkem.c index 60a0850dc5..11b4515875 100644 --- a/wolfcrypt/src/wc_mlkem.c +++ b/wolfcrypt/src/wc_mlkem.c @@ -1205,6 +1205,8 @@ int wc_MlKemKey_EncapsulateWithRandom(MlKemKey* key, unsigned char* c, } #endif + ForceZero(kr, sizeof(kr)); + return ret; } #endif /* !WOLFSSL_MLKEM_NO_ENCAPSULATE */ @@ -1541,6 +1543,9 @@ int wc_MlKemKey_Decapsulate(MlKemKey* key, unsigned char* ss, } #endif + ForceZero(msg, sizeof(msg)); + ForceZero(kr, sizeof(kr)); + return ret; } #endif /* WOLFSSL_MLKEM_NO_DECAPSULATE */ From deb668ca4b8664b553a0894e7d52cefab28a4c77 Mon Sep 17 00:00:00 2001 From: Sameeh Jubran Date: Mon, 2 Feb 2026 11:30:20 +0200 Subject: [PATCH 19/36] pkcs7: add RSA-PSS support for SignedData Add full RSA-PSS (RSASSA-PSS) support to PKCS#7 SignedData encoding and verification. This change enables SignerInfo.signatureAlgorithm to use id-RSASSA-PSS with explicit RSASSA-PSS-params (hash, MGF1, salt length), as required by RFC 4055 and CMS profiles. Key changes: - Add RSA-PSS encode and verify paths for PKCS7 SignedData - Encode full RSASSA-PSS AlgorithmIdentifier parameters - Decode RSA-PSS parameters from SignerInfo for verification - Treat RSA-PSS like ECDSA (sign raw digest, not DigestInfo) - Fix certificate signatureAlgorithm parameter length handling - Add API test coverage for RSA-PSS SignedData This resolves failures when using RSA-PSS signer certificates (e.g. -173 invalid signature algorithm) and maintains backward compatibility with RSA PKCS#1 v1.5 and ECDSA. Signed-off-by: Sameeh Jubran --- .github/workflows/os-check.yml | 2 + doc/dox_comments/header_files/cryptocb.h | 11 + doc/dox_comments/header_files/doxygen_pages.h | 10 + doc/dox_comments/header_files/pkcs7.h | 2 +- examples/configs/README.md | 2 +- examples/configs/user_settings_pkcs7.h | 1 + tests/api/test_asn.c | 133 +++++ tests/api/test_asn.h | 4 +- tests/api/test_pkcs7.c | 92 ++++ tests/api/test_pkcs7.h | 13 + wolfcrypt/src/aes.c | 1 - wolfcrypt/src/asn.c | 486 ++++++++++++++---- wolfcrypt/src/pkcs7.c | 457 +++++++++++++++- wolfssl/wolfcrypt/asn.h | 6 + wolfssl/wolfcrypt/pkcs7.h | 7 + 15 files changed, 1093 insertions(+), 134 deletions(-) diff --git a/.github/workflows/os-check.yml b/.github/workflows/os-check.yml index 763716b027..724fa8191d 100644 --- a/.github/workflows/os-check.yml +++ b/.github/workflows/os-check.yml @@ -53,6 +53,8 @@ jobs: '--enable-opensslall --enable-opensslextra CPPFLAGS=-DWC_RNG_SEED_CB', '--enable-opensslall --enable-opensslextra CPPFLAGS=''-DWC_RNG_SEED_CB -DWOLFSSL_NO_GETPID'' ', + # PKCS#7 with RSA-PSS (CMS RSASSA-PSS signers) + '--enable-pkcs7 CPPFLAGS=-DWC_RSA_PSS', '--enable-opensslextra CPPFLAGS=''-DWOLFSSL_NO_CA_NAMES'' ', '--enable-opensslextra=x509small', 'CPPFLAGS=''-DWOLFSSL_EXTRA'' ', diff --git a/doc/dox_comments/header_files/cryptocb.h b/doc/dox_comments/header_files/cryptocb.h index b6a21922f3..e236e1271f 100644 --- a/doc/dox_comments/header_files/cryptocb.h +++ b/doc/dox_comments/header_files/cryptocb.h @@ -52,6 +52,17 @@ } } #endif + #if defined(WC_RSA_PSS) && !defined(NO_RSA) + if (info->pk.type == WC_PK_TYPE_RSA_PSS) { + // RSA-PSS sign/verify + ret = wc_RsaPSS_Sign_ex( + info->pk.rsa.in, info->pk.rsa.inLen, + info->pk.rsa.out, *info->pk.rsa.outLen, + WC_HASH_TYPE_SHA256, WC_MGF1SHA256, + RSA_PSS_SALT_LEN_DEFAULT, + info->pk.rsa.key, info->pk.rsa.rng); + } + #endif #ifdef HAVE_ECC if (info->pk.type == WC_PK_TYPE_ECDSA_SIGN) { // ECDSA diff --git a/doc/dox_comments/header_files/doxygen_pages.h b/doc/dox_comments/header_files/doxygen_pages.h index 58ae75852e..beee6c6241 100644 --- a/doc/dox_comments/header_files/doxygen_pages.h +++ b/doc/dox_comments/header_files/doxygen_pages.h @@ -51,6 +51,7 @@
  • \ref MD5
  • \ref Password
  • \ref PKCS7
  • +
  • \ref PKCS7_RSA_PSS
  • \ref PKCS11
  • \ref Poly1305
  • \ref RIPEMD
  • @@ -97,4 +98,13 @@ \sa wc_CryptoCb_AesSetKey \sa \ref Crypto Callbacks */ +/*! + \page PKCS7_RSA_PSS PKCS#7 RSA-PSS (CMS) + PKCS#7 SignedData supports RSA-PSS signers (CMS RSASSA-PSS). When WC_RSA_PSS + is defined, use wc_PKCS7_InitWithCert with a signer certificate that has + RSA-PSS (id-RSASSA-PSS) and set hashOID and optional rng; encode produces + full RSASSA-PSS-params (hashAlgorithm, mgfAlgorithm, saltLength, + trailerField). Verify accepts NULL, empty, or absent parameters with + RFC defaults. See \ref PKCS7 for the main API. +*/ diff --git a/doc/dox_comments/header_files/pkcs7.h b/doc/dox_comments/header_files/pkcs7.h index 69d927c692..d7f679fafd 100644 --- a/doc/dox_comments/header_files/pkcs7.h +++ b/doc/dox_comments/header_files/pkcs7.h @@ -147,7 +147,7 @@ int wc_PKCS7_EncodeData(wc_PKCS7* pkcs7, byte* output, \brief This function builds the PKCS7 signed data content type, encoding the PKCS7 structure into a buffer containing a parsable PKCS7 - signed data packet. + signed data packet. For RSA-PSS signers (WC_RSA_PSS), see \ref PKCS7_RSA_PSS. \return Success On successfully encoding the PKCS7 data into the buffer, returns the index parsed up to in the PKCS7 structure. This index also diff --git a/examples/configs/README.md b/examples/configs/README.md index bb87653953..0a2f3193fa 100644 --- a/examples/configs/README.md +++ b/examples/configs/README.md @@ -23,7 +23,7 @@ Example wolfSSL configuration file templates for use when autoconf is not availa * `user_settings_openssl_compat.h`: OpenSSL compatibility layer for drop-in replacement. Enables OPENSSL_ALL and related APIs. * `user_settings_baremetal.h`: Bare metal configuration. No filesystem, static memory only, minimal footprint. * `user_settings_rsa_only.h`: RSA-only configuration (no ECC). For legacy systems requiring RSA cipher suites. -* `user_settings_pkcs7.h`: PKCS#7/CMS configuration for signing and encryption. S/MIME, firmware signing. +* `user_settings_pkcs7.h`: PKCS#7/CMS configuration for signing and encryption. S/MIME, firmware signing. For RSA-PSS SignedData (CMS RSASSA-PSS), define `WC_RSA_PSS`; see doxygen \ref PKCS7_RSA_PSS. * `user_settings_ca.h`: Certificate Authority / PKI operations. Certificate generation, signing, CRL, OCSP. * `user_settings_wolfboot_keytools.h`: wolfBoot key generation and signing tool. Supports ECC, RSA, ED25519, ED448, and post-quantum (ML-DSA/Dilithium, LMS, XMSS). * `user_settings_wolfssh.h`: Minimum options for building wolfSSH. See comment at top for ./configure used to generate. diff --git a/examples/configs/user_settings_pkcs7.h b/examples/configs/user_settings_pkcs7.h index 5375e1fb2b..04aa77e074 100644 --- a/examples/configs/user_settings_pkcs7.h +++ b/examples/configs/user_settings_pkcs7.h @@ -115,6 +115,7 @@ extern "C" { #undef NO_RSA #define WOLFSSL_KEY_GEN #define WC_RSA_NO_PADDING + #define WC_RSA_PSS /* RSA-PSS SignedData (id-RSASSA-PSS); see PKCS7_RSA_PSS */ #else #define NO_RSA #endif diff --git a/tests/api/test_asn.c b/tests/api/test_asn.c index d7e0fd3322..ecfe4ede50 100644 --- a/tests/api/test_asn.c +++ b/tests/api/test_asn.c @@ -24,6 +24,7 @@ #include #include +#include #if defined(WC_ENABLE_ASYM_KEY_EXPORT) && defined(HAVE_ED25519) static int test_SetAsymKeyDer_once(byte* privKey, word32 privKeySz, byte* pubKey, @@ -787,3 +788,135 @@ int test_wolfssl_local_MatchBaseName(void) return EXPECT_RESULT(); } + +/* + * Testing wc_DecodeRsaPssParams with known DER byte arrays. + * Exercises both WOLFSSL_ASN_TEMPLATE and non-template paths. + */ +int test_wc_DecodeRsaPssParams(void) +{ + EXPECT_DECLS; +#if defined(WC_RSA_PSS) && !defined(NO_RSA) && !defined(NO_ASN) + enum wc_HashType hash; + int mgf; + int saltLen; + + /* SHA-256 / MGF1-SHA-256 / saltLen=32 */ + static const byte pssParamsSha256[] = { + 0x30, 0x34, + 0xA0, 0x0F, + 0x30, 0x0D, + 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, + 0x04, 0x02, 0x01, + 0x05, 0x00, + 0xA1, 0x1C, + 0x30, 0x1A, + 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, + 0x01, 0x01, 0x08, + 0x30, 0x0D, + 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, + 0x04, 0x02, 0x01, + 0x05, 0x00, + 0xA2, 0x03, + 0x02, 0x01, 0x20, + }; + + /* Hash-only: SHA-256 hash, defaults for MGF and salt */ + static const byte pssParamsHashOnly[] = { + 0x30, 0x11, + 0xA0, 0x0F, + 0x30, 0x0D, + 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, + 0x04, 0x02, 0x01, + 0x05, 0x00, + }; + + /* Salt-only: default hash/mgf, saltLen=48 */ + static const byte pssParamsSaltOnly[] = { + 0x30, 0x05, + 0xA2, 0x03, + 0x02, 0x01, 0x30, + }; + + /* NULL tag (05 00) means all defaults */ + static const byte pssParamsNull[] = { 0x05, 0x00 }; + + /* Empty SEQUENCE means all non-default fields omitted => defaults */ + static const byte pssParamsEmptySeq[] = { 0x30, 0x00 }; + + /* --- Test 1: sz=0 => all defaults --- */ + hash = WC_HASH_TYPE_NONE; + mgf = 0; + saltLen = 0; + ExpectIntEQ(wc_DecodeRsaPssParams((const byte*)"", 0, + &hash, &mgf, &saltLen), 0); + ExpectIntEQ((int)hash, (int)WC_HASH_TYPE_SHA); + ExpectIntEQ(mgf, WC_MGF1SHA1); + ExpectIntEQ(saltLen, 20); + + /* --- Test 2: NULL tag => all defaults --- */ + hash = WC_HASH_TYPE_NONE; + mgf = 0; + saltLen = 0; + ExpectIntEQ(wc_DecodeRsaPssParams(pssParamsNull, + (word32)sizeof(pssParamsNull), &hash, &mgf, &saltLen), 0); + ExpectIntEQ((int)hash, (int)WC_HASH_TYPE_SHA); + ExpectIntEQ(mgf, WC_MGF1SHA1); + ExpectIntEQ(saltLen, 20); + + /* --- Test 3: Empty SEQUENCE => all defaults --- */ + hash = WC_HASH_TYPE_NONE; + mgf = 0; + saltLen = 0; + ExpectIntEQ(wc_DecodeRsaPssParams(pssParamsEmptySeq, + (word32)sizeof(pssParamsEmptySeq), &hash, &mgf, &saltLen), 0); + ExpectIntEQ((int)hash, (int)WC_HASH_TYPE_SHA); + ExpectIntEQ(mgf, WC_MGF1SHA1); + ExpectIntEQ(saltLen, 20); + +#ifndef NO_SHA256 + /* --- Test 4: SHA-256 / MGF1-SHA-256 / salt=32 --- */ + hash = WC_HASH_TYPE_NONE; + mgf = 0; + saltLen = 0; + ExpectIntEQ(wc_DecodeRsaPssParams(pssParamsSha256, + (word32)sizeof(pssParamsSha256), &hash, &mgf, &saltLen), 0); + ExpectIntEQ((int)hash, (int)WC_HASH_TYPE_SHA256); + ExpectIntEQ(mgf, WC_MGF1SHA256); + ExpectIntEQ(saltLen, 32); + + /* --- Test 5: Hash only => SHA-256, default MGF/salt --- */ + hash = WC_HASH_TYPE_NONE; + mgf = 0; + saltLen = 0; + ExpectIntEQ(wc_DecodeRsaPssParams(pssParamsHashOnly, + (word32)sizeof(pssParamsHashOnly), &hash, &mgf, &saltLen), 0); + ExpectIntEQ((int)hash, (int)WC_HASH_TYPE_SHA256); + ExpectIntEQ(mgf, WC_MGF1SHA1); + ExpectIntEQ(saltLen, 20); +#endif + + /* --- Test 6: Salt only => default hash/MGF, salt=48 --- */ + hash = WC_HASH_TYPE_NONE; + mgf = 0; + saltLen = 0; + ExpectIntEQ(wc_DecodeRsaPssParams(pssParamsSaltOnly, + (word32)sizeof(pssParamsSaltOnly), &hash, &mgf, &saltLen), 0); + ExpectIntEQ((int)hash, (int)WC_HASH_TYPE_SHA); + ExpectIntEQ(mgf, WC_MGF1SHA1); + ExpectIntEQ(saltLen, 48); + + /* --- Test 7: NULL pointer -> BAD_FUNC_ARG --- */ + ExpectIntEQ(wc_DecodeRsaPssParams(NULL, 10, &hash, &mgf, &saltLen), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* --- Test 8: Bad leading tag => ASN_PARSE_E --- */ + { + static const byte badTag[] = { 0x01, 0x00 }; + ExpectIntEQ(wc_DecodeRsaPssParams(badTag, (word32)sizeof(badTag), + &hash, &mgf, &saltLen), WC_NO_ERR_TRACE(ASN_PARSE_E)); + } + +#endif /* WC_RSA_PSS && !NO_RSA && !NO_ASN */ + return EXPECT_RESULT(); +} diff --git a/tests/api/test_asn.h b/tests/api/test_asn.h index 7f2879dbce..2403bc81d1 100644 --- a/tests/api/test_asn.h +++ b/tests/api/test_asn.h @@ -28,11 +28,13 @@ int test_SetAsymKeyDer(void); int test_GetSetShortInt(void); int test_wc_IndexSequenceOf(void); int test_wolfssl_local_MatchBaseName(void); +int test_wc_DecodeRsaPssParams(void); #define TEST_ASN_DECLS \ TEST_DECL_GROUP("asn", test_SetAsymKeyDer), \ TEST_DECL_GROUP("asn", test_GetSetShortInt), \ TEST_DECL_GROUP("asn", test_wc_IndexSequenceOf), \ - TEST_DECL_GROUP("asn", test_wolfssl_local_MatchBaseName) + TEST_DECL_GROUP("asn", test_wolfssl_local_MatchBaseName), \ + TEST_DECL_GROUP("asn", test_wc_DecodeRsaPssParams) #endif /* WOLFCRYPT_TEST_ASN_H */ diff --git a/tests/api/test_pkcs7.c b/tests/api/test_pkcs7.c index edbbb2b4ef..5f3644869f 100644 --- a/tests/api/test_pkcs7.c +++ b/tests/api/test_pkcs7.c @@ -947,6 +947,98 @@ int test_wc_PKCS7_EncodeSignedData(void) } /* END test_wc_PKCS7_EncodeSignedData */ +/* + * Testing wc_PKCS7_EncodeSignedData() with RSA-PSS signer certificate. + * Uses certs/rsapss/client-rsapss.der and client-rsapss-priv.der. + * Requires both encode and round-trip verify to succeed. + */ +#if defined(HAVE_PKCS7) && defined(WC_RSA_PSS) && !defined(NO_RSA) && \ + !defined(NO_FILESYSTEM) && !defined(NO_SHA256) +int test_wc_PKCS7_EncodeSignedData_RSA_PSS(void) +{ + EXPECT_DECLS; + PKCS7* pkcs7 = NULL; + WC_RNG rng; + byte output[FOURK_BUF]; + byte cert[FOURK_BUF]; + byte key[FOURK_BUF]; + word32 outputSz = (word32)sizeof(output); + word32 certSz = 0; + word32 keySz = 0; + XFILE fp = XBADFILE; + byte data[] = "Test data for RSA-PSS SignedData."; + + XMEMSET(&rng, 0, sizeof(WC_RNG)); + XMEMSET(output, 0, outputSz); + XMEMSET(cert, 0, sizeof(cert)); + XMEMSET(key, 0, sizeof(key)); + + ExpectIntEQ(wc_InitRng(&rng), 0); + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + + ExpectTrue((fp = XFOPEN("./certs/rsapss/client-rsapss.der", "rb")) != XBADFILE); + if (fp != XBADFILE) { + ExpectIntGT(certSz = (word32)XFREAD(cert, 1, sizeof(cert), fp), 0); + XFCLOSE(fp); + fp = XBADFILE; + } + + ExpectTrue((fp = XFOPEN("./certs/rsapss/client-rsapss-priv.der", "rb")) != XBADFILE); + if (fp != XBADFILE) { + ExpectIntGT(keySz = (word32)XFREAD(key, 1, sizeof(key), fp), 0); + XFCLOSE(fp); + fp = XBADFILE; + } + + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, cert, certSz), 0); + + if (pkcs7 != NULL) { + /* Force RSA-PSS so SignerInfo uses id-RSASSA-PSS (cert may use RSA + * in subjectPublicKeyInfo). WC_RSA_PSS is guaranteed by outer guard. */ + pkcs7->publicKeyOID = RSAPSSk; + + pkcs7->content = data; + pkcs7->contentSz = (word32)sizeof(data); + pkcs7->contentOID = DATA; + pkcs7->hashOID = SHA256h; + pkcs7->encryptOID = RSAk; + pkcs7->privateKey = key; + pkcs7->privateKeySz = keySz; + pkcs7->rng = &rng; + pkcs7->signedAttribs = NULL; + pkcs7->signedAttribsSz = 0; + } + + /* EncodeSignedData with RSA-PSS cert: require encode and verify success */ + { + int outLen = wc_PKCS7_EncodeSignedData(pkcs7, output, outputSz); + ExpectIntGT(outLen, 0); + if (outLen > 0) { + int verifyRet = wc_PKCS7_VerifySignedData(pkcs7, output, + (word32)outLen); + ExpectIntEQ(verifyRet, 0); + + if (pkcs7 != NULL) { + /* Verify decoded RSASSA-PSS parameters match what we + * encoded: + * hashAlgorithm = SHA-256 + * maskGenAlgorithm = MGF1-SHA-256 + * saltLength = 32 (== SHA-256 digest length) */ + ExpectIntEQ(pkcs7->pssHashType, (int)WC_HASH_TYPE_SHA256); + ExpectIntEQ(pkcs7->pssMgf, WC_MGF1SHA256); + ExpectIntEQ(pkcs7->pssSaltLen, 32); + } + } + } + + wc_PKCS7_Free(pkcs7); + DoExpectIntEQ(wc_FreeRng(&rng), 0); + + return EXPECT_RESULT(); +} /* END test_wc_PKCS7_EncodeSignedData_RSA_PSS */ +#endif + + /* * Testing wc_PKCS7_EncodeSignedData_ex() and wc_PKCS7_VerifySignedData_ex() */ diff --git a/tests/api/test_pkcs7.h b/tests/api/test_pkcs7.h index 7bc6207880..c55307cacc 100644 --- a/tests/api/test_pkcs7.h +++ b/tests/api/test_pkcs7.h @@ -29,6 +29,10 @@ int test_wc_PKCS7_Init(void); int test_wc_PKCS7_InitWithCert(void); int test_wc_PKCS7_EncodeData(void); int test_wc_PKCS7_EncodeSignedData(void); +#if defined(HAVE_PKCS7) && defined(WC_RSA_PSS) && !defined(NO_RSA) && \ + !defined(NO_FILESYSTEM) && !defined(NO_SHA256) +int test_wc_PKCS7_EncodeSignedData_RSA_PSS(void); +#endif int test_wc_PKCS7_EncodeSignedData_ex(void); int test_wc_PKCS7_VerifySignedData_RSA(void); int test_wc_PKCS7_VerifySignedData_ECC(void); @@ -55,10 +59,19 @@ int test_wc_PKCS7_VerifySignedData_PKCS7ContentSeq(void); TEST_DECL_GROUP("pkcs7", test_wc_PKCS7_New), \ TEST_DECL_GROUP("pkcs7", test_wc_PKCS7_Init) +#if defined(HAVE_PKCS7) && defined(WC_RSA_PSS) && !defined(NO_RSA) && \ + !defined(NO_FILESYSTEM) && !defined(NO_SHA256) +#define TEST_PKCS7_RSA_PSS_SD_DECL \ + TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_EncodeSignedData_RSA_PSS), +#else +#define TEST_PKCS7_RSA_PSS_SD_DECL +#endif + #define TEST_PKCS7_SIGNED_DATA_DECLS \ TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_InitWithCert), \ TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_EncodeData), \ TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_EncodeSignedData), \ + TEST_PKCS7_RSA_PSS_SD_DECL \ TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_EncodeSignedData_ex), \ TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_VerifySignedData_RSA), \ TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_VerifySignedData_ECC), \ diff --git a/wolfcrypt/src/aes.c b/wolfcrypt/src/aes.c index 29911ccfa2..5c571dfa86 100644 --- a/wolfcrypt/src/aes.c +++ b/wolfcrypt/src/aes.c @@ -17147,5 +17147,4 @@ int wc_AesCtsDecryptFinal(Aes* aes, byte* out, word32* outSz) #endif /* WOLFSSL_AES_CTS */ - #endif /* !NO_AES */ diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index 3be5e33fb0..66bdddd599 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -2539,7 +2539,7 @@ int GetASNHeader(const byte* input, byte tag, word32* inOutIdx, int* len, return GetASNHeader_ex(input, tag, inOutIdx, len, maxIdx, 1); } -#ifndef WOLFSSL_ASN_TEMPLATE +#if !defined(WOLFSSL_ASN_TEMPLATE) static int GetHeader(const byte* input, byte* tag, word32* inOutIdx, int* len, word32 maxIdx, int check) { @@ -2894,7 +2894,9 @@ static int GetInteger7Bit(const byte* input, word32* inOutIdx, word32 maxIdx) return b; } #endif /* !NO_CERTS */ +#endif /* !WOLFSSL_ASN_TEMPLATE */ +#if !defined(WOLFSSL_ASN_TEMPLATE) #if ((defined(WC_RSA_PSS) && !defined(NO_RSA)) || !defined(NO_CERTS)) /* Get the DER/BER encoding of an ASN.1 INTEGER that has a value of no more than * 16 bits. @@ -2932,7 +2934,7 @@ static int GetInteger16Bit(const byte* input, word32* inOutIdx, word32 maxIdx) return ASN_PARSE_E; } n = input[idx++]; - n = (n << 8) | input[idx++]; + n = (word16)((n << 8) | input[idx++]); } else return ASN_PARSE_E; @@ -2940,7 +2942,7 @@ static int GetInteger16Bit(const byte* input, word32* inOutIdx, word32 maxIdx) *inOutIdx = idx; return n; } -#endif /* WC_RSA_PSS && !NO_RSA */ +#endif /* WC_RSA_PSS || !NO_CERTS */ #endif /* !WOLFSSL_ASN_TEMPLATE */ #if !defined(NO_DSA) && !defined(NO_SHA) @@ -3132,10 +3134,12 @@ const char* GetSigName(int oid) { #if !defined(WOLFSSL_ASN_TEMPLATE) || defined(HAVE_PKCS7) || \ - defined(OPENSSL_EXTRA) || defined(WOLFSSL_CERT_GEN) + defined(OPENSSL_EXTRA) || defined(WOLFSSL_CERT_GEN) || defined(WC_RSA_PSS) #if !defined(NO_DSA) || defined(HAVE_ECC) || !defined(NO_CERTS) || \ defined(WOLFSSL_CERT_GEN) || \ - (!defined(NO_RSA) && (defined(WOLFSSL_KEY_GEN) || defined(OPENSSL_EXTRA))) + (!defined(NO_RSA) && \ + (defined(WOLFSSL_KEY_GEN) || defined(OPENSSL_EXTRA))) || \ + (defined(WC_RSA_PSS) && !defined(NO_RSA)) /* Set the DER/BER encoding of the ASN.1 INTEGER header. * * When output is NULL, calculate the header length only. @@ -3146,7 +3150,7 @@ const char* GetSigName(int oid) { * @param [out] output Buffer to write into. * @return Number of bytes added to the buffer. */ -int SetASNInt(int len, byte firstByte, byte* output) +WOLFSSL_LOCAL int SetASNInt(int len, byte firstByte, byte* output) { int idx = 0; @@ -7452,6 +7456,9 @@ static int RsaPssHashOidToSigOid(word32 oid, word32* sigOid) } #endif +/* RSASSA-PSS-params context-specific tags. + * RFC 4055, 3.1 + */ #ifdef WOLFSSL_ASN_TEMPLATE /* ASN tag for hashAlgorithm. */ #define ASN_TAG_RSA_PSS_HASH (ASN_CONTEXT_SPECIFIC | 0) @@ -7498,7 +7505,7 @@ enum { RSAPSSPARAMSASN_IDX_TRAILERINT }; -/* Number of items in ASN.1 template for an algorithm identifier. */ +/* Number of items in ASN.1 template for RSA PSS parameters. */ #define rsaPssParamsASN_Length (sizeof(rsaPssParamsASN) / sizeof(ASNItem)) #else /* ASN tag for hashAlgorithm. */ @@ -7525,122 +7532,44 @@ enum { static int DecodeRsaPssParams(const byte* params, word32 sz, enum wc_HashType* hash, int* mgf, int* saltLen) { -#ifndef WOLFSSL_ASN_TEMPLATE - int ret = 0; - word32 idx = 0; - int len = 0; - word32 oid = 0; - byte tag; - int length; + if (params == NULL) + return BAD_FUNC_ARG; - if (params == NULL) { - ret = BAD_FUNC_ARG; + /* Empty or NULL-tag parameters mean all defaults per RFC 4055 */ + if (sz == 0) { + *hash = WC_HASH_TYPE_SHA; + *mgf = WC_MGF1SHA1; + *saltLen = 20; + return 0; } - if ((ret == 0) && (GetSequence_ex(params, &idx, &len, sz, 1) < 0)) { - ret = ASN_PARSE_E; - } - if (ret == 0) { - if ((idx < sz) && (params[idx] == ASN_TAG_RSA_PSS_HASH)) { - /* Hash algorithm to use on message. */ - if (GetHeader(params, &tag, &idx, &length, sz, 0) < 0) { - ret = ASN_PARSE_E; - } - if (ret == 0) { - if (GetAlgoId(params, &idx, &oid, oidHashType, sz) < 0) { - ret = ASN_PARSE_E; - } - } - if (ret == 0) { - ret = RsaPssHashOidToType(oid, hash); - } - } - else { - /* Default hash algorithm. */ + if (params[0] == ASN_TAG_NULL) { + if (sz >= 2 && params[1] == 0) { *hash = WC_HASH_TYPE_SHA; - } - } - if (ret == 0) { - if ((idx < sz) && (params[idx] == ASN_TAG_RSA_PSS_MGF)) { - /* MGF and hash algorithm to use with padding. */ - if (GetHeader(params, &tag, &idx, &length, sz, 0) < 0) { - ret = ASN_PARSE_E; - } - if (ret == 0) { - if (GetAlgoId(params, &idx, &oid, oidIgnoreType, sz) < 0) { - ret = ASN_PARSE_E; - } - } - if ((ret == 0) && (oid != MGF1_OID)) { - ret = ASN_PARSE_E; - } - if (ret == 0) { - ret = GetAlgoId(params, &idx, &oid, oidHashType, sz); - if (ret == 0) { - ret = RsaPssHashOidToMgf1(oid, mgf); - } - } - } - else { - /* Default MGF/Hash algorithm. */ *mgf = WC_MGF1SHA1; - } - } - if (ret == 0) { - if ((idx < sz) && (params[idx] == ASN_TAG_RSA_PSS_SALTLEN)) { - /* Salt length to use with padding. */ - if (GetHeader(params, &tag, &idx, &length, sz, 0) < 0) { - ret = ASN_PARSE_E; - } - if (ret == 0) { - ret = GetInteger16Bit(params, &idx, sz); - if (ret >= 0) { - *saltLen = ret; - ret = 0; - } - } - } - else { - /* Default salt length. */ *saltLen = 20; + return 0; } + return ASN_PARSE_E; } - if (ret == 0) { - if ((idx < sz) && (params[idx] == ASN_TAG_RSA_PSS_TRAILER)) { - /* Unused - trialerField. */ - if (GetHeader(params, &tag, &idx, &length, sz, 0) < 0) { - ret = ASN_PARSE_E; - } - if (ret == 0) { - ret = GetInteger16Bit(params, &idx, sz); - if (ret > 0) { - ret = 0; - } - } - } - } - if ((ret == 0) && (idx != sz)) { - ret = ASN_PARSE_E; - } + if (params[0] != (ASN_SEQUENCE | ASN_CONSTRUCTED)) + return ASN_PARSE_E; - return ret; -#else +#ifdef WOLFSSL_ASN_TEMPLATE +{ DECL_ASNGETDATA(dataASN, rsaPssParamsASN_Length); int ret = 0; word16 sLen = 20; - if (params == NULL) { - ret = BAD_FUNC_ARG; - } + /* Default values. */ + *hash = WC_HASH_TYPE_SHA; + *mgf = WC_MGF1SHA1; CALLOC_ASNGETDATA(dataASN, rsaPssParamsASN_Length, ret, NULL); if (ret == 0) { word32 inOutIdx = 0; - /* Default values. */ - *hash = WC_HASH_TYPE_SHA; - *mgf = WC_MGF1SHA1; - /* Set OID type expected. */ GetASN_OID(&dataASN[RSAPSSPARAMSASN_IDX_HASHOID], oidHashType); + GetASN_OID(&dataASN[RSAPSSPARAMSASN_IDX_MGFOID], oidIgnoreType); GetASN_OID(&dataASN[RSAPSSPARAMSASN_IDX_MGFHOID], oidHashType); /* Place the salt length into 16-bit var sLen. */ GetASN_Int16Bit(&dataASN[RSAPSSPARAMSASN_IDX_SALTLENINT], &sLen); @@ -7652,6 +7581,10 @@ static int DecodeRsaPssParams(const byte* params, word32 sz, word32 oid = dataASN[RSAPSSPARAMSASN_IDX_HASHOID].data.oid.sum; ret = RsaPssHashOidToType(oid, hash); } + if ((ret == 0) && (dataASN[RSAPSSPARAMSASN_IDX_MGFOID].tag != 0)) { + if (dataASN[RSAPSSPARAMSASN_IDX_MGFOID].data.oid.sum != MGF1_OID) + ret = ASN_PARSE_E; + } if ((ret == 0) && (dataASN[RSAPSSPARAMSASN_IDX_MGFHOID].tag != 0)) { word32 oid = dataASN[RSAPSSPARAMSASN_IDX_MGFHOID].data.oid.sum; ret = RsaPssHashOidToMgf1(oid, mgf); @@ -7662,8 +7595,317 @@ static int DecodeRsaPssParams(const byte* params, word32 sz, FREE_ASNGETDATA(dataASN, NULL); return ret; +} +#else /* !WOLFSSL_ASN_TEMPLATE */ +{ + int ret = 0; + word32 idx = 0; + int len = 0; + word32 oid = 0; + byte tag; + int length; + + /* Decode RSASSA-PSS-params SEQUENCE content. */ + if (GetSequence_ex(params, &idx, &len, sz, 1) < 0) { + WOLFSSL_MSG("DecodeRsaPssParams: fail at sequence"); + return ASN_PARSE_E; + } + + /* [0] hashAlgorithm */ + if (ret == 0) { + if ((idx < sz) && (params[idx] == ASN_TAG_RSA_PSS_HASH)) { + /* Hash algorithm to use on message. */ + if (GetHeader(params, &tag, &idx, &length, sz, 0) < 0) { + WOLFSSL_MSG("DecodeRsaPssParams: fail at hash_header"); + ret = ASN_PARSE_E; + } + if (ret == 0) { + if (GetAlgoId(params, &idx, &oid, oidHashType, sz) < 0) { + WOLFSSL_MSG("DecodeRsaPssParams: fail at hash_algo"); + ret = ASN_PARSE_E; + } + } + if (ret == 0) { + ret = RsaPssHashOidToType(oid, hash); + if (ret != 0) { + WOLFSSL_MSG("DecodeRsaPssParams: fail at hash_oid"); + } + } + } + else { + /* Default hash algorithm. */ + *hash = WC_HASH_TYPE_SHA; + } + } + + /* [1] maskGenAlgorithm -- AlgorithmIdentifier { OID id-mgf1, hash AlgoId } + * Parse manually: read the MGF SEQUENCE + OID, then the hash AlgoId + * parameter, because GetAlgoId consumes the entire AlgorithmIdentifier + * including the hash parameter inside it. */ + if (ret == 0) { + if ((idx < sz) && (params[idx] == ASN_TAG_RSA_PSS_MGF)) { + int mgfSeqLen = 0; + word32 mgfEnd; + + if (GetHeader(params, &tag, &idx, &length, sz, 0) < 0) { + WOLFSSL_MSG("DecodeRsaPssParams: fail at mgf_header"); + ret = ASN_PARSE_E; + } + /* Read MGF AlgorithmIdentifier SEQUENCE header */ + if (ret == 0) { + if (GetSequence(params, &idx, &mgfSeqLen, sz) < 0) { + WOLFSSL_MSG("DecodeRsaPssParams: fail at mgf_seq"); + ret = ASN_PARSE_E; + } + } + /* Bound subsequent reads to the MGF SEQUENCE content */ + mgfEnd = idx + (word32)mgfSeqLen; + if (mgfEnd > sz) { + WOLFSSL_MSG("DecodeRsaPssParams: mgf_seq overflows buffer"); + ret = ASN_PARSE_E; + } + /* Read MGF OID (id-mgf1) directly */ + if (ret == 0) { + if (GetObjectId(params, &idx, &oid, oidIgnoreType, + mgfEnd) < 0) { + WOLFSSL_MSG("DecodeRsaPssParams: fail at mgf_oid"); + ret = ASN_PARSE_E; + } + } + if ((ret == 0) && (oid != MGF1_OID)) { + WOLFSSL_MSG("DecodeRsaPssParams: fail at mgf_oid_value"); + ret = ASN_PARSE_E; + } + /* Read hash AlgorithmIdentifier (parameter of MGF1) */ + if (ret == 0) { + ret = GetAlgoId(params, &idx, &oid, oidHashType, mgfEnd); + if (ret == 0) { + ret = RsaPssHashOidToMgf1(oid, mgf); + if (ret != 0) { + WOLFSSL_MSG("DecodeRsaPssParams: fail at mgf_hash_oid"); + } + } + else { + WOLFSSL_MSG("DecodeRsaPssParams: fail at mgf_hash_algo"); + } + } + if ((ret == 0) && (idx != mgfEnd)) { + WOLFSSL_MSG("DecodeRsaPssParams: extra data in mgf_seq"); + ret = ASN_PARSE_E; + } + } + else { + /* Default MGF/Hash algorithm. */ + *mgf = WC_MGF1SHA1; + } + } + + /* [2] saltLength */ + if (ret == 0) { + if ((idx < sz) && (params[idx] == ASN_TAG_RSA_PSS_SALTLEN)) { + /* Salt length to use with padding. */ + if (GetHeader(params, &tag, &idx, &length, sz, 0) < 0) { + WOLFSSL_MSG("DecodeRsaPssParams: fail at saltlen_header"); + ret = ASN_PARSE_E; + } + if (ret == 0) { + ret = GetInteger16Bit(params, &idx, sz); + if (ret >= 0) { + *saltLen = ret; + ret = 0; + } + else { + WOLFSSL_MSG("DecodeRsaPssParams: fail at saltlen_value"); + } + } + } + else { + /* Default salt length. */ + *saltLen = 20; + } + } + + /* [3] trailerField */ + if (ret == 0) { + if ((idx < sz) && (params[idx] == ASN_TAG_RSA_PSS_TRAILER)) { + /* Unused - trailerField. */ + if (GetHeader(params, &tag, &idx, &length, sz, 0) < 0) { + WOLFSSL_MSG("DecodeRsaPssParams: fail at trailer_header"); + ret = ASN_PARSE_E; + } + if (ret == 0) { + ret = GetInteger16Bit(params, &idx, sz); + if (ret > 0) { + ret = 0; + } + else if (ret != 0) { + WOLFSSL_MSG("DecodeRsaPssParams: fail at trailer_value"); + } + } + } + } + + if ((ret == 0) && (idx != sz)) { + WOLFSSL_MSG("DecodeRsaPssParams: fail at extra_data"); + ret = ASN_PARSE_E; + } + + return ret; +} #endif /* WOLFSSL_ASN_TEMPLATE */ } + +WOLFSSL_TEST_VIS int wc_DecodeRsaPssParams(const byte* params, word32 sz, + enum wc_HashType* hash, int* mgf, int* saltLen) +{ + return DecodeRsaPssParams(params, sz, hash, mgf, saltLen); +} + +/* Encode AlgorithmIdentifier for id-RSASSA-PSS with full RSASSA-PSS-params. + * hashOID: e.g. SHA256h; saltLen: e.g. 32; trailerField is encoded as 1. + * When out is NULL returns required length only. + * Returns encoded length on success, 0 on error. + * Note: saltLength is encoded as a single-byte INTEGER; values >255 would + * require multi-byte encoding (not implemented; CMS profiles use hash-length + * salts, e.g. 20-64 bytes). */ +/* Scratch buffer for building RSA-PSS AlgorithmIdentifier sub-encodings. + * Must hold the largest single sub-encoding (hash AlgoId, typically ~15 bytes) + * plus room for integer TLVs. 64 bytes is sufficient; 128 gives margin. */ +#define RSA_PSS_ALGOID_TMPBUF_SZ 128 + +word32 wc_EncodeRsaPssAlgoId(int hashOID, int saltLen, byte* out, word32 outSz) +{ + word32 idx = 0; + word32 hashAlgSz, mgf1ParamSz, tag0Sz, tag1Sz, tag2Sz, tag3Sz; + word32 paramsSz, outerSz; + const byte* rsapssOid = sigRsaSsaPssOid; + word32 rsapssOidSz = sizeof(sigRsaSsaPssOid); + /* MGF1 OID 1.2.840.113549.1.1.8 */ + static const byte mgf1Oid[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x08 }; + int setIntRet; +#ifdef WOLFSSL_SMALL_STACK + byte* tmpBuf; +#else + byte tmpBuf[RSA_PSS_ALGOID_TMPBUF_SZ]; +#endif + + if (saltLen < 0 || saltLen > 255) { + WOLFSSL_MSG("Salt length must be 0-255 for single-byte encoding"); + return 0; + } + +#ifdef WOLFSSL_SMALL_STACK + tmpBuf = (byte*)XMALLOC(RSA_PSS_ALGOID_TMPBUF_SZ, NULL, + DYNAMIC_TYPE_TMP_BUFFER); + if (tmpBuf == NULL) + return 0; +#endif + + hashAlgSz = SetAlgoID(hashOID, out ? tmpBuf : NULL, oidHashType, 0); + if (hashAlgSz == 0) { + idx = 0; goto pss_algoid_done; + } + mgf1ParamSz = hashAlgSz; + tag0Sz = SetExplicit(0, hashAlgSz, NULL, 0) + hashAlgSz; + { + /* MGF AlgorithmIdentifier: SEQUENCE { OID id-mgf1, hash AlgoId } + * The hash AlgoId (from SetAlgoID) is the parameter of MGF1. */ + word32 mgf1OidLen = (word32)SetObjectId((int)sizeof(mgf1Oid), NULL) + (word32)sizeof(mgf1Oid); + word32 mgf1SeqContent = mgf1OidLen + mgf1ParamSz; + word32 mgf1SeqSz = SetSequence(mgf1SeqContent, NULL) + mgf1SeqContent; + tag1Sz = SetExplicit(1, mgf1SeqSz, NULL, 0) + mgf1SeqSz; + } + /* SetASNInt writes only tag+length (+ optional leading 0); value byte(s) appended by caller. */ + setIntRet = SetASNInt(1, (byte)(saltLen & 0xff), NULL); + if (setIntRet <= 0) { + idx = 0; goto pss_algoid_done; + } + { + word32 saltIntSz = (word32)setIntRet + 1; /* header + one value byte (two if high bit set) */ + tag2Sz = SetExplicit(2, saltIntSz, NULL, 0) + saltIntSz; + } + setIntRet = SetASNInt(1, 0x01, NULL); + if (setIntRet <= 0) { + idx = 0; goto pss_algoid_done; + } + { + word32 trailerIntSz = (word32)setIntRet + 1; + tag3Sz = SetExplicit(3, trailerIntSz, NULL, 0) + trailerIntSz; + } + + paramsSz = tag0Sz + tag1Sz + tag2Sz + tag3Sz; + { + word32 idPart = (word32)SetObjectId((int)rsapssOidSz, NULL) + rsapssOidSz; + word32 seqPart = SetSequence(paramsSz, NULL) + paramsSz; + outerSz = SetSequence(idPart + seqPart, NULL) + idPart + seqPart; + } + + if (out == NULL) { + idx = outerSz; goto pss_algoid_done; + } + if (outSz < outerSz) { + idx = 0; goto pss_algoid_done; + } + + { + word32 idPart = (word32)SetObjectId((int)rsapssOidSz, NULL) + rsapssOidSz; + word32 seqPart = SetSequence(paramsSz, NULL) + paramsSz; + idx += SetSequence(idPart + seqPart, out + idx); + } + idx += (word32)SetObjectId((int)rsapssOidSz, out + idx); + XMEMCPY(out + idx, rsapssOid, rsapssOidSz); + idx += rsapssOidSz; + idx += SetSequence(paramsSz, out + idx); + + idx += SetExplicit(0, hashAlgSz, out + idx, 0); + XMEMCPY(out + idx, tmpBuf, hashAlgSz); + idx += hashAlgSz; + + { + /* [1] EXPLICIT { SEQUENCE { OID id-mgf1, hash AlgoId } } */ + word32 mgf1OidLen = (word32)SetObjectId((int)sizeof(mgf1Oid), NULL) + (word32)sizeof(mgf1Oid); + word32 mgf1SeqContent = mgf1OidLen + mgf1ParamSz; + word32 mgf1SeqSz = SetSequence(mgf1SeqContent, NULL) + mgf1SeqContent; + idx += SetExplicit(1, mgf1SeqSz, out + idx, 0); + idx += SetSequence(mgf1SeqContent, out + idx); + idx += (word32)SetObjectId((int)sizeof(mgf1Oid), out + idx); + XMEMCPY(out + idx, mgf1Oid, sizeof(mgf1Oid)); + idx += (word32)sizeof(mgf1Oid); + XMEMCPY(out + idx, tmpBuf, hashAlgSz); + idx += hashAlgSz; + } + + /* INTEGER saltLength (single byte; see function comment for >255 limitation). */ + setIntRet = SetASNInt(1, (byte)(saltLen & 0xff), tmpBuf); + if (setIntRet > 0) { + tmpBuf[setIntRet] = (byte)(saltLen & 0xff); + } + { + word32 saltIntSz = (word32)setIntRet + 1; + idx += SetExplicit(2, saltIntSz, out + idx, 0); + XMEMCPY(out + idx, tmpBuf, saltIntSz); + idx += saltIntSz; + } + + /* INTEGER trailerField (1): same pattern. */ + setIntRet = SetASNInt(1, 0x01, tmpBuf); + if (setIntRet > 0) { + tmpBuf[setIntRet] = 0x01; + } + { + word32 trailerIntSz = (word32)setIntRet + 1; + idx += SetExplicit(3, trailerIntSz, out + idx, 0); + XMEMCPY(out + idx, tmpBuf, trailerIntSz); + idx += trailerIntSz; + } + +pss_algoid_done: +#ifdef WOLFSSL_SMALL_STACK + XFREE(tmpBuf, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + return idx; +} + #endif /* WC_RSA_PSS */ #if defined(WOLFSSL_ASN_TEMPLATE) || (!defined(NO_CERTS) && \ @@ -11113,6 +11355,7 @@ int wc_RsaPublicKeyDecode_ex(const byte* input, word32* inOutIdx, word32 inSz, #else DECL_ASNGETDATA(dataASN, rsaPublicKeyASN_Length); int ret = 0; + word32 startIdx = (inOutIdx != NULL) ? *inOutIdx : 0; #ifdef WC_RSA_PSS word32 oid = RSAk; #endif @@ -11131,7 +11374,10 @@ int wc_RsaPublicKeyDecode_ex(const byte* input, word32* inOutIdx, word32 inSz, (int)(rsaPublicKeyASN_Length - RSAPUBLICKEYASN_IDX_PUBKEY_RSA_SEQ), 0, input, inOutIdx, inSz); if (ret != 0) { - /* Didn't work - try whole SubjectKeyInfo instead. */ + /* Didn't work - try whole SubjectKeyInfo instead. Reset index + * to caller's start since the previous attempt advanced it. */ + if (inOutIdx != NULL) + *inOutIdx = startIdx; #ifdef WC_RSA_PSS /* Could be RSA or RSA PSS key. */ GetASN_OID(&dataASN[RSAPUBLICKEYASN_IDX_ALGOID_OID], oidKeyType); @@ -17041,8 +17287,17 @@ static int GetSigAlg(DecodedCert* cert, word32* sigOid, word32 maxIdx) if (cert->srcIdx != endSeqIdx) { #ifdef WC_RSA_PSS if (*sigOid == CTC_RSASSAPSS) { - cert->sigParamsIndex = cert->srcIdx; - cert->sigParamsLength = endSeqIdx - cert->srcIdx; + /* cert->srcIdx is at start of parameters TLV (NULL or SEQUENCE) */ + word32 tmpIdx = cert->srcIdx; + byte tag; + int len; + + WOLFSSL_MSG("Cert sigAlg is RSASSA-PSS; decoding params"); + if (GetHeader(cert->source, &tag, &tmpIdx, &len, endSeqIdx, 0) < 0) { + return ASN_PARSE_E; + } + cert->sigParamsIndex = cert->srcIdx; + cert->sigParamsLength = (word32)((tmpIdx - cert->srcIdx) + len); } else #endif @@ -24082,10 +24337,17 @@ static int DecodeCertInternal(DecodedCert* cert, int verify, int* criticalExt, } } if (ret == 0) { - /* Store parameters for use in signature verification. */ - cert->sigParamsIndex = - dataASN[X509CERTASN_IDX_SIGALGO_PARAMS].offset; - cert->sigParamsLength = sigAlgParamsSz; + /* Store parameters for use in signature verification. + * Use full TLV length: tag (1) + length bytes + content. + * GetASNItem_Length can be content-only when buffer.data unset. */ + word32 off = dataASN[X509CERTASN_IDX_SIGALGO_PARAMS].offset; + word32 contentLen = + (word32)dataASN[X509CERTASN_IDX_SIGALGO_PARAMS].length; + cert->sigParamsIndex = off; + cert->sigParamsLength = (contentLen <= 127) + ? (2 + contentLen) + : GetASNItem_Length( + dataASN[X509CERTASN_IDX_SIGALGO_PARAMS], cert->source); } } #endif diff --git a/wolfcrypt/src/pkcs7.c b/wolfcrypt/src/pkcs7.c index 75b53d4bfe..135163dd25 100644 --- a/wolfcrypt/src/pkcs7.c +++ b/wolfcrypt/src/pkcs7.c @@ -28,6 +28,26 @@ #ifndef NO_RSA #include #endif +#ifndef RSA_PSS_SALT_LEN_DEFAULT + #define RSA_PSS_SALT_LEN_DEFAULT (-1) +#endif +#if defined(WC_RSA_PSS) && !defined(NO_RSA) + #if (defined(HAVE_FIPS) && \ + (!defined(HAVE_FIPS_VERSION) || (HAVE_FIPS_VERSION < 2))) || \ + (defined(HAVE_SELFTEST) && \ + (!defined(HAVE_SELFTEST_VERSION) || (HAVE_SELFTEST_VERSION < 2))) + #ifndef WC_MGF1NONE + #define WC_MGF1NONE 0 + #define WC_MGF1SHA1 26 + #define WC_MGF1SHA224 4 + #define WC_MGF1SHA256 1 + #define WC_MGF1SHA384 2 + #define WC_MGF1SHA512 3 + #define WC_MGF1SHA512_224 5 + #define WC_MGF1SHA512_256 6 + #endif + #endif +#endif #ifdef HAVE_ECC #include #endif @@ -1019,6 +1039,11 @@ static int wc_PKCS7_CheckPublicKeyDer(wc_PKCS7* pkcs7, int keyOID, switch (keyOID) { #ifndef NO_RSA + #ifdef WC_RSA_PSS + case RSAPSSk: + /* RSA-PSS cert: public key is same RSA format as RSAk */ + FALL_THROUGH; + #endif case RSAk: ret = wc_InitRsaKey_ex(rsa, pkcs7->heap, pkcs7->devId); if (ret != 0) { @@ -1173,6 +1198,10 @@ int wc_PKCS7_InitWithCert(wc_PKCS7* pkcs7, byte* derCert, word32 derCertSz) XMEMCPY(pkcs7->publicKey, dCert->publicKey, dCert->pubKeySize); pkcs7->publicKeySz = dCert->pubKeySize; pkcs7->publicKeyOID = dCert->keyOID; + /* Do not derive publicKeyOID from cert signatureOID: the cert's + * signature is how the cert was signed by its issuer; the signer + * chooses digestEncryptionAlgorithm (e.g. RSASSA-PSS vs PKCS#1 v1.5) + * via API / pkcs7->publicKeyOID set by the application. */ XMEMCPY(pkcs7->issuerHash, dCert->issuerHash, KEYID_SIZE); pkcs7->issuer = dCert->issuerRaw; pkcs7->issuerSz = (word32)dCert->issuerRawLen; @@ -1533,7 +1562,11 @@ typedef struct ESD { byte issuerSKIDSeq[MAX_SEQ_SZ]; byte issuerSKID[MAX_OCTET_STR_SZ]; byte signerDigAlgoId[MAX_ALGO_SZ]; +#if defined(WC_RSA_PSS) + byte digEncAlgoId[128]; /* RSASSA-PSS needs full params */ +#else byte digEncAlgoId[MAX_ALGO_SZ]; +#endif byte signedAttribSet[MAX_SET_SZ]; EncodedAttrib signedAttribs[7]; byte signerDigest[MAX_OCTET_STR_SZ]; @@ -1945,6 +1978,131 @@ static int wc_PKCS7_EcdsaSign(wc_PKCS7* pkcs7, byte* in, word32 inSz, ESD* esd) #endif /* HAVE_ECC */ +#if defined(WC_RSA_PSS) && !defined(NO_RSA) +/* Map hash type to MGF1 identifier; local copy so PKCS7 does not depend on + * rsa.c when RSA is in a separate FIPS module (e.g. CAVP build). */ +static int pkcs7_hash2mgf(enum wc_HashType hType) +{ + switch (hType) { + case WC_HASH_TYPE_NONE: + return WC_MGF1NONE; + case WC_HASH_TYPE_SHA: +#ifndef NO_SHA + return WC_MGF1SHA1; +#else + break; +#endif + case WC_HASH_TYPE_SHA224: +#ifdef WOLFSSL_SHA224 + return WC_MGF1SHA224; +#else + break; +#endif + case WC_HASH_TYPE_SHA256: +#ifndef NO_SHA256 + return WC_MGF1SHA256; +#else + break; +#endif + case WC_HASH_TYPE_SHA384: +#ifdef WOLFSSL_SHA384 + return WC_MGF1SHA384; +#else + break; +#endif + case WC_HASH_TYPE_SHA512: +#ifdef WOLFSSL_SHA512 + return WC_MGF1SHA512; +#else + break; +#endif + /* MGF1 only supports SHA-1 and SHA-2; other hashes fall through to WC_MGF1NONE */ + case WC_HASH_TYPE_MD2: + case WC_HASH_TYPE_MD4: + case WC_HASH_TYPE_MD5: + case WC_HASH_TYPE_MD5_SHA: + case WC_HASH_TYPE_SHA3_224: + case WC_HASH_TYPE_SHA3_256: + case WC_HASH_TYPE_SHA3_384: + case WC_HASH_TYPE_SHA3_512: + case WC_HASH_TYPE_BLAKE2B: + case WC_HASH_TYPE_BLAKE2S: +#ifndef WOLFSSL_NOSHA512_224 + case WC_HASH_TYPE_SHA512_224: +#endif +#ifndef WOLFSSL_NOSHA512_256 + case WC_HASH_TYPE_SHA512_256: +#endif +#ifdef WOLFSSL_SHAKE128 + case WC_HASH_TYPE_SHAKE128: +#endif +#ifdef WOLFSSL_SHAKE256 + case WC_HASH_TYPE_SHAKE256: +#endif +#ifdef WOLFSSL_SM3 + case WC_HASH_TYPE_SM3: +#endif + default: + break; + } + return WC_MGF1NONE; +} + +/* returns size of signature put into esd->encContentDigest, negative on error. + * Signs the digest (contentAttribsDigest) with RSA-PSS padding, like ECDSA. */ +static int wc_PKCS7_RsaPssSign(wc_PKCS7* pkcs7, byte* digest, word32 digestSz, + ESD* esd) +{ + int ret; + word32 outSz; + WC_DECLARE_VAR(privKey, RsaKey, 1, 0); + + if (pkcs7 == NULL || pkcs7->rng == NULL || digest == NULL || esd == NULL) { + return BAD_FUNC_ARG; + } + + WC_ALLOC_VAR_EX(privKey, RsaKey, 1, pkcs7->heap, DYNAMIC_TYPE_TMP_BUFFER, + return MEMORY_E); + + ret = wc_PKCS7_ImportRSA(pkcs7, privKey); + if (ret == 0) { + outSz = sizeof(esd->encContentDigest); +#ifdef WOLFSSL_ASYNC_CRYPT + do { + ret = wc_AsyncWait(ret, &privKey->asyncDev, + WC_ASYNC_FLAG_CALL_AGAIN); + if (ret >= 0) +#endif + { + /* Salt length policy: use hash digest length (RFC 4055 typical). + * RFC 3447 allows arbitrary salt lengths, but hash-length is the + * most interoperable choice and matches OpenSSL's default. + * Must agree with the saltLen encoded in + * SignerInfo.signatureAlgorithm params above. */ + int saltLen = wc_HashGetDigestSize(wc_OidGetHash(pkcs7->hashOID)); + if (saltLen < 0) { + ret = saltLen; + } + else { + ret = wc_RsaPSS_Sign_ex(digest, digestSz, + esd->encContentDigest, outSz, + esd->hashType, pkcs7_hash2mgf(esd->hashType), + saltLen, privKey, pkcs7->rng); + } + } +#ifdef WOLFSSL_ASYNC_CRYPT + } while (ret == WC_NO_ERR_TRACE(WC_PENDING_E)); +#endif + /* wc_RsaPSS_Sign_ex returns signature length on success */ + } + + wc_FreeRsaKey(privKey); + WC_FREE_VAR_EX(privKey, pkcs7->heap, DYNAMIC_TYPE_TMP_BUFFER); + + return ret; +} +#endif /* WC_RSA_PSS && !NO_RSA */ + /* returns encContentDigestSz based on the signature set to be used */ static int wc_PKCS7_GetSignSize(wc_PKCS7* pkcs7) { @@ -1954,6 +2112,9 @@ static int wc_PKCS7_GetSignSize(wc_PKCS7* pkcs7) #ifndef NO_RSA case RSAk: + #ifdef WC_RSA_PSS + case RSAPSSk: + #endif { #ifndef WOLFSSL_SMALL_STACK RsaKey privKey[1]; @@ -2244,6 +2405,20 @@ static int wc_PKCS7_SignedDataGetEncAlgoId(wc_PKCS7* pkcs7, int* digEncAlgoId, } #endif /* HAVE_ECC */ +#ifdef WC_RSA_PSS + else if (pkcs7->publicKeyOID == RSAPSSk) { + algoType = oidSigType; + algoId = CTC_RSASSAPSS; + /* Hash/MGF/salt conveyed via PSS params in AlgorithmIdentifier */ + } +#endif +#ifndef WC_RSA_PSS + else if (pkcs7->publicKeyOID == RSAPSSk) { + WOLFSSL_MSG("RSA-PSS requested but WC_RSA_PSS not compiled in"); + return NOT_COMPILED_IN; + } +#endif + if (algoId == 0) { WOLFSSL_MSG("Invalid signature algorithm type"); return BAD_FUNC_ARG; @@ -2359,7 +2534,8 @@ static int wc_PKCS7_SignedDataBuildSignature(wc_PKCS7* pkcs7, { int ret = 0; #if defined(HAVE_ECC) || \ - (defined(HAVE_PKCS7_RSA_RAW_SIGN_CALLBACK) && !defined(NO_RSA)) + (defined(HAVE_PKCS7_RSA_RAW_SIGN_CALLBACK) && !defined(NO_RSA)) || \ + (defined(WC_RSA_PSS) && !defined(NO_RSA)) int hashSz = 0; #endif #if defined(HAVE_PKCS7_RSA_RAW_SIGN_CALLBACK) && !defined(NO_RSA) @@ -2384,7 +2560,8 @@ static int wc_PKCS7_SignedDataBuildSignature(wc_PKCS7* pkcs7, } #if defined(HAVE_ECC) || \ - (defined(HAVE_PKCS7_RSA_RAW_SIGN_CALLBACK) && !defined(NO_RSA)) + (defined(HAVE_PKCS7_RSA_RAW_SIGN_CALLBACK) && !defined(NO_RSA)) || \ + (defined(WC_RSA_PSS) && !defined(NO_RSA)) /* get digest size from hash type */ hashSz = wc_HashGetDigestSize(esd->hashType); if (hashSz < 0) { @@ -2447,6 +2624,14 @@ static int wc_PKCS7_SignedDataBuildSignature(wc_PKCS7* pkcs7, break; #endif +#if defined(WC_RSA_PSS) && !defined(NO_RSA) + case RSAPSSk: + /* RSA-PSS signs the digest directly (no DigestInfo), like ECDSA */ + ret = wc_PKCS7_RsaPssSign(pkcs7, esd->contentAttribsDigest, + (word32)hashSz, esd); + break; +#endif + default: WOLFSSL_MSG("Unsupported public key type"); ret = BAD_FUNC_ARG; @@ -2801,14 +2986,19 @@ static int PKCS7_EncodeSigned(wc_PKCS7* pkcs7, return BAD_FUNC_ARG; } - /* signature size varies with ECDSA, with a varying sign size the content - * hash must be known in order to create the surrounding ASN1 syntax - * properly before writing out the content and generating the hash on the - * fly and then creating the signature */ - if (hashBuf == NULL && pkcs7->publicKeyOID == ECDSAk) { + /* signature size varies with ECDSA; RSA-PSS signs digest directly like + * ECDSA. For both, content hash must be known to build ASN.1 before signing */ +#if defined(HAVE_ECC) || defined(WC_RSA_PSS) + if (hashBuf == NULL && + (pkcs7->publicKeyOID == ECDSAk +#ifdef WC_RSA_PSS + || pkcs7->publicKeyOID == RSAPSSk +#endif + )) { WOLFSSL_MSG("Pre-calculated content hash is needed in this case"); return BAD_FUNC_ARG; } +#endif #if defined(WOLFSSL_SM2) && defined(WOLFSSL_SM3) keyIdSize = wc_HashGetDigestSize(wc_HashTypeConvert(HashIdAlg( @@ -2965,8 +3155,31 @@ static int PKCS7_EncodeSigned(wc_PKCS7* pkcs7, idx = ret; goto out; } - esd->digEncAlgoIdSz = SetAlgoIDEx(digEncAlgoId, esd->digEncAlgoId, - digEncAlgoType, 0, pkcs7->hashParamsAbsent); +#if defined(WC_RSA_PSS) + if (digEncAlgoId == CTC_RSASSAPSS) { + /* Salt length policy: always encode as hash digest length. + * This is the common CMS/RFC 4055 profile and matches OpenSSL + * defaults. The decoder (pssSaltLen) handles arbitrary values + * from external blobs. A future pkcs7->pssSaltLen override for + * encode could be added here if custom salt lengths are needed. */ + int saltLen = wc_HashGetDigestSize(wc_OidGetHash(pkcs7->hashOID)); + if (saltLen < 0) { + idx = saltLen; + goto out; + } + esd->digEncAlgoIdSz = wc_EncodeRsaPssAlgoId(pkcs7->hashOID, + (int)(word32)saltLen, esd->digEncAlgoId, + (word32)sizeof(esd->digEncAlgoId)); + if (esd->digEncAlgoIdSz == 0) { + idx = ASN_PARSE_E; + goto out; + } + } else +#endif + { + esd->digEncAlgoIdSz = SetAlgoIDEx(digEncAlgoId, esd->digEncAlgoId, + digEncAlgoType, 0, pkcs7->hashParamsAbsent); + } signerInfoSz += esd->digEncAlgoIdSz; /* build up signed attributes, include contentType, signingTime, and @@ -3567,8 +3780,12 @@ int wc_PKCS7_EncodeSignedData(wc_PKCS7* pkcs7, byte* output, word32 outputSz) return BAD_FUNC_ARG; } - /* pre-calculate hash for ECC signatures */ - if (pkcs7->publicKeyOID == ECDSAk) { + /* pre-calculate content hash for ECDSA and RSA-PSS (both sign digest directly) */ + if (pkcs7->publicKeyOID == ECDSAk +#ifdef WC_RSA_PSS + || pkcs7->publicKeyOID == RSAPSSk +#endif + ) { int hashSz; enum wc_HashType hashType; byte hashBuf[WC_MAX_DIGEST_SIZE]; @@ -4071,8 +4288,7 @@ static int wc_PKCS7_RsaVerify(wc_PKCS7* pkcs7, byte* sig, int sigSz, byte digest[MAX_PKCS7_DIGEST_SZ]; #endif RsaKey key[1]; - DecodedCert stack_dCert; - DecodedCert* dCert = &stack_dCert; + DecodedCert dCert[1]; #endif if (pkcs7 == NULL || sig == NULL || hash == NULL) { @@ -4175,6 +4391,144 @@ static int wc_PKCS7_RsaVerify(wc_PKCS7* pkcs7, byte* sig, int sigSz, return ret; } +#if defined(WC_RSA_PSS) +/* returns 0 when signature verifies, negative on error */ +static int wc_PKCS7_RsaPssVerify(wc_PKCS7* pkcs7, byte* sig, int sigSz, + byte* hash, word32 hashSz) +{ + int ret = 0, i; + word32 scratch = 0, verified = 0; + word32 pkSz; + int saltLen, verify; + enum wc_HashType hashType; + int hashDigSz, mgf; + /* wc_RsaPSS_Verify_ex output is PSS-encoded message (RSA_PSS_PAD_SZ + + * saltLen + 2*hLen bytes), which can exceed MAX_PKCS7_DIGEST_SZ. + * Use MAX_ENCRYPTED_KEY_SZ (512) to cover RSA keys up to 4096-bit. */ + WC_DECLARE_VAR(digest, byte, MAX_ENCRYPTED_KEY_SZ, 0); + WC_DECLARE_VAR(key, RsaKey, 1, 0); + WC_DECLARE_VAR(dCert, DecodedCert, 1, 0); + + if (pkcs7 == NULL || sig == NULL || hash == NULL) + return BAD_FUNC_ARG; + + /* Use SignerInfo.signatureAlgorithm params when decoded; else digestAlgo */ + if (pkcs7->pssParamsPresent) + hashType = (enum wc_HashType)pkcs7->pssHashType; + else + hashType = wc_OidGetHash(pkcs7->hashOID); + hashDigSz = wc_HashGetDigestSize(hashType); + if (hashDigSz < 0) + return ASN_PARSE_E; + if (hashSz != (word32)hashDigSz) + return ASN_PARSE_E; + mgf = pkcs7->pssParamsPresent + ? pkcs7->pssMgf : pkcs7_hash2mgf(hashType); + if (mgf == WC_MGF1NONE) + return ASN_PARSE_E; + + WC_ALLOC_VAR_EX(digest, byte, MAX_ENCRYPTED_KEY_SZ, pkcs7->heap, + DYNAMIC_TYPE_TMP_BUFFER, return MEMORY_E); + WC_ALLOC_VAR_EX(key, RsaKey, 1, pkcs7->heap, DYNAMIC_TYPE_TMP_BUFFER, + { WC_FREE_VAR_EX(digest, pkcs7->heap, DYNAMIC_TYPE_TMP_BUFFER); + return MEMORY_E; }); + WC_ALLOC_VAR_EX(dCert, DecodedCert, 1, pkcs7->heap, DYNAMIC_TYPE_DCERT, + { WC_FREE_VAR_EX(digest, pkcs7->heap, DYNAMIC_TYPE_TMP_BUFFER); + WC_FREE_VAR_EX(key, pkcs7->heap, DYNAMIC_TYPE_TMP_BUFFER); + return MEMORY_E; }); + + XMEMSET(digest, 0, MAX_ENCRYPTED_KEY_SZ); + + for (i = 0; i < MAX_PKCS7_CERTS; i++) { + if (pkcs7->certSz[i] == 0) + continue; + + scratch = 0; + ret = wc_InitRsaKey_ex(key, pkcs7->heap, pkcs7->devId); + if (ret != 0) + break; + + InitDecodedCert(dCert, pkcs7->cert[i], pkcs7->certSz[i], pkcs7->heap); + ret = ParseCert(dCert, CA_TYPE, NO_VERIFY, 0); + if (ret < 0) { + FreeDecodedCert(dCert); + wc_FreeRsaKey(key); + continue; + } + + pkSz = dCert->pubKeySize; + if (pkSz > (MAX_RSA_INT_SZ + MAX_RSA_E_SZ)) + pkSz = (MAX_RSA_INT_SZ + MAX_RSA_E_SZ); + + if (wc_RsaPublicKeyDecode(dCert->publicKey, &scratch, key, + pkSz) < 0) { + FreeDecodedCert(dCert); + wc_FreeRsaKey(key); + continue; + } + + saltLen = pkcs7->pssParamsPresent + ? pkcs7->pssSaltLen : RSA_PSS_SALT_LEN_DEFAULT; + + /* wc_RsaPSS_Verify_ex returns PSS encoded message length, not + * the recovered hash. Must then call wc_RsaPSS_CheckPadding_ex + * to verify the PSS padding against the expected hash. */ + verify = wc_RsaPSS_Verify_ex(sig, (word32)sigSz, digest, + MAX_ENCRYPTED_KEY_SZ, + hashType, mgf, saltLen, key); + if (verify > 0) { + /* wc_RsaPSS_CheckPadding_ex signature varies across + * FIPS / selftest versions; match the pattern in asn.c */ + #if (defined(HAVE_SELFTEST) && \ + (!defined(HAVE_SELFTEST_VERSION) || \ + (HAVE_SELFTEST_VERSION < 2))) || \ + (defined(HAVE_FIPS) && defined(HAVE_FIPS_VERSION) && \ + (HAVE_FIPS_VERSION < 2)) + ret = wc_RsaPSS_CheckPadding_ex(hash, hashSz, digest, + (word32)verify, hashType, + saltLen); + #elif (defined(HAVE_SELFTEST) && \ + (HAVE_SELFTEST_VERSION == 2)) || \ + (defined(HAVE_FIPS) && defined(HAVE_FIPS_VERSION) && \ + (HAVE_FIPS_VERSION == 2)) + ret = wc_RsaPSS_CheckPadding_ex(hash, hashSz, digest, + (word32)verify, hashType, + saltLen, 0); + #else + ret = wc_RsaPSS_CheckPadding_ex2(hash, hashSz, digest, + (word32)verify, hashType, + saltLen, + mp_count_bits(&key->n), + pkcs7->heap); + #endif + } + else { + ret = verify; + } + + FreeDecodedCert(dCert); + wc_FreeRsaKey(key); + + if (ret == 0) { + verified = 1; + pkcs7->verifyCert = pkcs7->cert[i]; + pkcs7->verifyCertSz = pkcs7->certSz[i]; + break; + } + ret = 0; + } + + if (verified == 0) + ret = SIG_VERIFY_E; + + WC_FREE_VAR_EX(digest, pkcs7->heap, DYNAMIC_TYPE_TMP_BUFFER); + WC_FREE_VAR_EX(key, pkcs7->heap, DYNAMIC_TYPE_TMP_BUFFER); + WC_FREE_VAR_EX(dCert, pkcs7->heap, DYNAMIC_TYPE_DCERT); + + return ret; +} +#endif /* WC_RSA_PSS */ + #endif /* NO_RSA */ @@ -4194,8 +4548,7 @@ static int wc_PKCS7_EcdsaVerify(wc_PKCS7* pkcs7, byte* sig, int sigSz, #else byte digest[MAX_PKCS7_DIGEST_SZ]; ecc_key key[1]; - DecodedCert stack_dCert; - DecodedCert* dCert = &stack_dCert; + DecodedCert dCert[1]; #endif word32 idx = 0; @@ -4695,6 +5048,14 @@ static int wc_PKCS7_SignedDataVerifySignature(wc_PKCS7* pkcs7, byte* sig, plainDigestSz); } break; + + #ifdef WC_RSA_PSS + /* RSA-PSS signs the raw hash (plainDigest), not DigestInfo */ + case RSAPSSk: + ret = wc_PKCS7_RsaPssVerify(pkcs7, sig, (int)sigSz, plainDigest, + plainDigestSz); + break; + #endif #endif #ifdef HAVE_ECC @@ -4741,6 +5102,13 @@ static int wc_PKCS7_SetPublicKeyOID(wc_PKCS7* pkcs7, int sigOID) pkcs7->publicKeyOID = RSAk; break; + #ifdef WC_RSA_PSS + /* CTC_RSASSAPSS and RSAPSSk are the same OID value */ + case CTC_RSASSAPSS: + pkcs7->publicKeyOID = RSAPSSk; + break; + #endif + /* if sigOID is already RSAk */ case RSAk: pkcs7->publicKeyOID = (word32)sigOID; @@ -5050,9 +5418,62 @@ static int wc_PKCS7_ParseSignerInfo(wc_PKCS7* pkcs7, byte* in, word32 inSz, idx += (word32)length; } - /* Get digestEncryptionAlgorithm - key type or signature type */ - if (ret == 0 && GetAlgoId(in, &idx, &sigOID, oidIgnoreType, inSz) < 0) { - ret = ASN_PARSE_E; + /* Get digestEncryptionAlgorithm (signatureAlgorithm) - parse manually + * so we can decode id-RSASSA-PSS parameters in all builds. */ +#if defined(WC_RSA_PSS) && !defined(NO_RSA) + pkcs7->pssParamsPresent = 0; +#endif + if (ret == 0) { + int algoSeqLen = 0; + word32 algoContentStart = 0; + if (GetSequence(in, &idx, &algoSeqLen, (word32)inSz) < 0) { + ret = ASN_PARSE_E; + } + else { + algoContentStart = idx; /* first byte of AlgorithmIdentifier content */ + if (GetObjectId(in, &idx, &sigOID, oidSigType, inSz) < 0) { + ret = ASN_PARSE_E; + } + /* Only parse params when still inside the AlgorithmIdentifier; + * when optional params are absent, idx is already past the sequence. */ + else if (algoContentStart + (word32)algoSeqLen > idx) { + word32 paramsStart = idx; + byte paramTag; + int paramLen = 0; + if (GetASNTag(in, &idx, ¶mTag, inSz) != 0 || + GetLength(in, &idx, ¶mLen, inSz) < 0) { + ret = ASN_PARSE_E; + } + else { +#if defined(WC_RSA_PSS) && !defined(NO_RSA) + if ((word32)sigOID == (word32)CTC_RSASSAPSS && + paramTag == (ASN_SEQUENCE | ASN_CONSTRUCTED)) { + word32 tlvLen = (idx - paramsStart) + + (word32)paramLen; + enum wc_HashType pssHash = WC_HASH_TYPE_SHA; + int pssMgfVal = 0, pssSalt = 0; + if (paramsStart + tlvLen > (word32)inSz) { + return ASN_PARSE_E; + } + ret = wc_DecodeRsaPssParams(in + paramsStart, tlvLen, + &pssHash, &pssMgfVal, + &pssSalt); + if (ret == 0) { + pkcs7->pssSaltLen = pssSalt; + pkcs7->pssHashType = (int)pssHash; + pkcs7->pssMgf = pssMgfVal; + pkcs7->pssParamsPresent = 1; + } + else { + WOLFSSL_MSG("RSASSA-PSS parameters invalid - failing parse"); + return ASN_PARSE_E; + } + } +#endif + idx += (word32)paramLen; + } + } + } } /* store public key type based on digestEncryptionAlgorithm */ diff --git a/wolfssl/wolfcrypt/asn.h b/wolfssl/wolfcrypt/asn.h index 69e8f04bae..340f189ec5 100644 --- a/wolfssl/wolfcrypt/asn.h +++ b/wolfssl/wolfcrypt/asn.h @@ -2506,6 +2506,12 @@ WOLFSSL_LOCAL word32 SetSet(word32 len, byte* output); WOLFSSL_API word32 SetAlgoID(int algoOID, byte* output, int type, int curveSz); WOLFSSL_LOCAL word32 SetAlgoIDEx(int algoOID, byte* output, int type, int curveSz, byte absentParams); +#if defined(WC_RSA_PSS) && !defined(NO_RSA) +WOLFSSL_LOCAL word32 wc_EncodeRsaPssAlgoId(int hashOID, int saltLen, byte* out, + word32 outSz); +WOLFSSL_TEST_VIS int wc_DecodeRsaPssParams(const byte* params, word32 sz, + enum wc_HashType* hash, int* mgf, int* saltLen); +#endif WOLFSSL_LOCAL int SetMyVersion(word32 version, byte* output, int header); WOLFSSL_LOCAL int SetSerialNumber(const byte* sn, word32 snSz, byte* output, word32 outputSz, int maxSnSz); diff --git a/wolfssl/wolfcrypt/pkcs7.h b/wolfssl/wolfcrypt/pkcs7.h index 1a13ae0277..93c9c6a5d7 100644 --- a/wolfssl/wolfcrypt/pkcs7.h +++ b/wolfssl/wolfcrypt/pkcs7.h @@ -388,6 +388,13 @@ struct wc_PKCS7 { CallbackEccSignRawDigest eccSignRawDigestCb; #endif +#if defined(WC_RSA_PSS) && !defined(NO_RSA) + int pssSaltLen; /* RSASSA-PSS params from SignerInfo; valid when */ + int pssHashType; /* pssParamsPresent == 1; else verify path uses */ + int pssMgf; /* RSA_PSS_SALT_LEN_DEFAULT / digest algo defaults */ + byte pssParamsPresent; +#endif + /* !! NEW DATA MEMBERS MUST BE ADDED AT END !! */ }; From 20eeba3d89c3c9b2e539703f15597c855583cf8f Mon Sep 17 00:00:00 2001 From: Marco Oliverio Date: Tue, 24 Feb 2026 18:01:32 +0100 Subject: [PATCH 20/36] test: tls13: add wolfSSL_set1_sigalgs_list test --- tests/api/test_tls13.c | 86 ++++++++++++++++++++++++++++++++++++++++++ tests/api/test_tls13.h | 4 +- 2 files changed, 89 insertions(+), 1 deletion(-) diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index 9c77126f46..a6f411544b 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -3100,3 +3100,89 @@ int test_tls13_plaintext_alert(void) return EXPECT_RESULT(); } +/* Test that wolfSSL_set1_sigalgs_list() is honored in TLS 1.3 + */ +int test_tls13_cert_req_sigalgs(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_CERTS) && !defined(NO_RSA) && defined(WC_RSA_PSS) && \ + defined(HAVE_ECC) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) && defined(OPENSSL_EXTRA) && \ + !defined(NO_FILESYSTEM) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + + /* Server: require client cert and load ECC client cert for verification */ + if (EXPECT_SUCCESS()) { + wolfSSL_set_verify(ssl_s, + WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s, + cliEccCertFile, 0), WOLFSSL_SUCCESS); + } + + /* Server: restrict CertificateRequest to RSA-PSS+SHA256 only */ + if (EXPECT_SUCCESS()) { + ExpectIntEQ(wolfSSL_set1_sigalgs_list(ssl_s, "RSA-PSS+SHA256"), + WOLFSSL_SUCCESS); + } + + /* Client: load ECC cert/key */ + if (EXPECT_SUCCESS()) { + ExpectIntEQ(wolfSSL_use_certificate_file(ssl_c, cliEccCertFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_use_PrivateKey_file(ssl_c, cliEccKeyFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + } + + /* Handshake must fail: ECC client cannot match RSA-PSS+SHA256 */ + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); ctx_s = NULL; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + + /* Server: require client cert and load RSA client cert for verification */ + if (EXPECT_SUCCESS()) { + wolfSSL_set_verify(ssl_s, + WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s, + cliCertFile, 0), WOLFSSL_SUCCESS); + } + + /* Server: restrict CertificateRequest to RSA-PSS+SHA256 only */ + if (EXPECT_SUCCESS()) { + ExpectIntEQ(wolfSSL_set1_sigalgs_list(ssl_s, "RSA-PSS+SHA256"), + WOLFSSL_SUCCESS); + } + + /* Client: load RSA cert/key */ + if (EXPECT_SUCCESS()) { + ExpectIntEQ(wolfSSL_use_certificate_file(ssl_c, cliCertFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_use_PrivateKey_file(ssl_c, cliKeyFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + } + + /* Handshake must succeed: RSA client satisfies RSA-PSS+SHA256 */ + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); ctx_s = NULL; +#endif + + return EXPECT_RESULT(); +} + diff --git a/tests/api/test_tls13.h b/tests/api/test_tls13.h index 8b5f48ef4d..67b23d57fe 100644 --- a/tests/api/test_tls13.h +++ b/tests/api/test_tls13.h @@ -38,6 +38,7 @@ int test_tls13_duplicate_extension(void); int test_key_share_mismatch(void); int test_tls13_middlebox_compat_empty_session_id(void); int test_tls13_plaintext_alert(void); +int test_tls13_cert_req_sigalgs(void); #define TEST_TLS13_DECLS \ TEST_DECL_GROUP("tls13", test_tls13_apis), \ @@ -53,6 +54,7 @@ int test_tls13_plaintext_alert(void); TEST_DECL_GROUP("tls13", test_tls13_duplicate_extension), \ TEST_DECL_GROUP("tls13", test_key_share_mismatch), \ TEST_DECL_GROUP("tls13", test_tls13_middlebox_compat_empty_session_id), \ - TEST_DECL_GROUP("tls13", test_tls13_plaintext_alert) + TEST_DECL_GROUP("tls13", test_tls13_plaintext_alert), \ + TEST_DECL_GROUP("tls13", test_tls13_cert_req_sigalgs) #endif /* WOLFCRYPT_TEST_TLS13_H */ From 38b52d8079092675d3d71c622ba62928a8e4b91a Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Tue, 20 Jan 2026 13:51:44 +0100 Subject: [PATCH 21/36] nginx 1.28.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### `wolfssl/internal.h` - **`InternalTicket` struct gains a flexible array member**: A new `peerCert[]` field (with a preceding `peerCertLen[2]`) is added to `InternalTicket`. This allows the peer's DER-encoded certificate to be stored directly inside the session ticket. - **`ExternalTicket` struct becomes variable-length**: The `enc_ticket` field is changed from a fixed-size array to a flexible array member (`byte enc_ticket[]`). The `mac` field is removed from the struct — the MAC is now placed dynamically after the encrypted data in `enc_ticket`. ### `src/internal.c` - The `GetRecordHeader` function now only adds `MAX_COMP_EXTRA` to the maximum allowed record size when `ssl->options.usingCompression` is true, tightening the length validation. The max fragment length extension check is now much stricter. - **Peer certificate is serialized into the ticket**: During ticket creation, the code attempts to find the peer certificate from `ssl->peerCert` or from `ssl->session->chain` (fallback). If found and within `MAX_TICKET_PEER_CERT_SZ`, it's copied into `it->peerCert`. DTLS is explicitly excluded (peer cert length set to 0) to keep ticket size small for MTU constraints. If `HAVE_MAX_FRAGMENT` is defined and max fragment is not `MAX_RECORD_SIZE` for TLS 1.3, the cert is also skipped since `SendTls13NewSessionTicket` doesn't support fragmentation yet. - **Peer certificate restoration from ticket**: On successful ticket decryption, if the ticket contains a peer certificate (`peerCertLen > 0`), it is decoded back into `ssl->peerCert` via `ParseCertRelative`/`CopyDecodedToX509`, and also added to `ssl->session->chain` via `AddSessionCertToChain`. - The `CLEAR_ASN_NO_PEM_HEADER_ERROR` macro was rewritten to loop and remove all consecutive PEM no-start-line errors (not just the last one), wrapped in a `do { ... } while(0)` for safety. - The `SendTicket` function is simplified to use `SendHandshakeMsg` to support fragmenting the larger ticket. --- ### `src/x509.c` - `loadX509orX509REQFromPemBio` now accepts `TRUSTED_CERT_TYPE` in addition to `CERT_TYPE` and `CERTREQ_TYPE`. - **Streaming BIO support**: When `wolfSSL_BIO_get_len()` returns ≤ 0 (e.g., pipes/FIFOs), the function no longer returns an error. Instead, it sets an initial buffer of `MAX_X509_SIZE` and dynamically grows (doubling) up to `MAX_BIO_READ_BUFFER` (`MAX_X509_SIZE * 16`) as data is read byte-by-byte. - **Alternate footer detection**: For `TRUSTED_CERT_TYPE`, the PEM reader also checks for the regular `CERT_TYPE` footer (`-----END CERTIFICATE-----`) in addition to the trusted cert footer (`-----END TRUSTED CERTIFICATE-----`), so it can parse either format. - Removed two lines that set `cert->srcIdx` to `SIGALGO_SEQ` offset. This makes `cert->srcIdx` reflect the end of parsed certificate data. This is used by `loadX509orX509REQFromBuffer` to detect where auxiliary trust data begins in trusted certificates. --- ### `src/ssl_sk.c` - Added a `STACK_TYPE_X509_CRL` case to `wolfssl_sk_dup_data` that calls `wolfSSL_X509_CRL_dup` for deep-copying CRL stack elements. Previously, `STACK_TYPE_X509_CRL` fell through to the unsupported default case. --- ### `wolfssl/openssl/ssl.h` - `sk_X509_dup` now maps to `wolfSSL_shallow_sk_dup` (was `wolfSSL_sk_dup`/deep copy). This matches OpenSSL's behavior where `sk_X509_dup` does a shallow copy. - `sk_SSL_CIPHER_dup` similarly changed to `wolfSSL_shallow_sk_dup`. --- ### `src/ssl_api_cert.c` - When `ssl->ourCert` is `NULL` and the SSL owns its cert, the function now checks if `ssl->ctx->ourCert` points to the same certificate (by comparing DER buffers). If so, it returns the ctx's `X509` pointer directly. This maintains pointer compatibility for applications (like nginx OCSP stapling) that use the `X509*` from `SSL_CTX_use_certificate` as a lookup key. ### `src/bio.c` - When `wolfssl_file_len` returns `WOLFSSL_BAD_FILETYPE` (now returned for pipes/FIFOs), `wolfSSL_BIO_get_len` treats it as length 0 instead of propagating the error. --- ### `tests/test-maxfrag.conf` and `tests/test-maxfrag-dtls.conf` - Removed `DHE-RSA-AES256-GCM-SHA384` test entries because the ClientKeyExchange doesn't fit in the selected max fragment length. --- .github/workflows/nginx.yml | 85 +++++---- .wolfssl_known_macro_extras | 2 + configure.ac | 3 +- src/bio.c | 2 + src/crl.c | 22 +++ src/dtls.c | 5 +- src/internal.c | 330 +++++++++++++++++++++++------------ src/ssl.c | 2 + src/ssl_api_cert.c | 22 ++- src/ssl_misc.c | 10 +- src/ssl_sk.c | 31 +++- src/x509.c | 293 +++++++++++++++++++++++-------- tests/test-maxfrag-dtls.conf | 11 -- tests/test-maxfrag.conf | 9 - wolfcrypt/src/asn.c | 23 ++- wolfcrypt/test/test.c | 2 +- wolfssl/internal.h | 91 +++++++--- wolfssl/openssl/ssl.h | 7 +- wolfssl/ssl.h | 2 + wolfssl/wolfcrypt/wc_port.h | 2 +- 20 files changed, 670 insertions(+), 284 deletions(-) diff --git a/.github/workflows/nginx.yml b/.github/workflows/nginx.yml index cc67610f2b..b95b6e89c7 100644 --- a/.github/workflows/nginx.yml +++ b/.github/workflows/nginx.yml @@ -12,6 +12,10 @@ concurrency: cancel-in-progress: true # END OF COMMON SECTION +# clang has better sanitizer support +env: + CC: clang + jobs: build_wolfssl: name: Build wolfSSL @@ -31,7 +35,8 @@ jobs: uses: wolfSSL/actions-build-autotools-project@v1 with: path: wolfssl - configure: --enable-nginx ${{ env.wolf_debug_flags }} + configure: >- + --enable-nginx --enable-curve25519 --enable-ed25519 ${{ env.wolf_debug_flags }} install: true - name: tar build-dir @@ -50,6 +55,41 @@ jobs: matrix: include: # in general we want to pass all tests that match *ssl* + - ref: 1.28.1 + test-ref: 0fccfcef1278263416043e0bbb3e0116b84026e4 + # Following tests pass with sanitizer on + sanitize-ok: >- + h2_ssl_proxy_cache.t h2_ssl.t h2_ssl_variables.t + h2_ssl_verify_client.t mail_imap_ssl.t mail_ssl_session_reuse.t + mail_ssl.t proxy_ssl_certificate_cache.t + proxy_ssl_certificate_empty.t proxy_ssl_certificate.t + proxy_ssl_certificate_vars.t proxy_ssl_name.t ssl_cache_reload.t + ssl_certificate_aux.t ssl_certificate_cache.t + ssl_certificate_chain.t ssl_certificates.t ssl_certificate.t + ssl_client_escaped_cert.t ssl_crl.t ssl_curve.t ssl_ocsp.t + ssl_password_file.t ssl_proxy_upgrade.t ssl_reject_handshake.t + ssl_session_reuse.t ssl_session_ticket_key.t ssl_sni_protocols.t + ssl_sni_reneg.t ssl_sni_sessions.t ssl_sni.t ssl_stapling.t ssl.t + ssl_verify_client.t ssl_verify_client_trusted.t ssl_verify_depth.t + stream_proxy_ssl_certificate_cache.t stream_proxy_ssl_certificate.t + stream_proxy_ssl_certificate_vars.t + stream_proxy_ssl_name_complex.t stream_proxy_ssl_name.t + stream_ssl_alpn.t stream_ssl_certificate_cache.t + stream_ssl_certificate.t stream_ssl_ocsp.t stream_ssl_preread_alpn.t + stream_ssl_preread_protocol.t stream_ssl_preread.t + stream_ssl_reject_handshake.t stream_ssl_session_reuse.t + stream_ssl_sni_protocols.t stream_ssl_stapling.t stream_ssl.t + stream_ssl_variables.t stream_ssl_verify_client.t + stream_upstream_zone_ssl.t upstream_zone_ssl.t + uwsgi_ssl_certificate.t uwsgi_ssl_certificate_vars.t + # Following tests do not pass with sanitizer on (with OpenSSL too) + sanitize-not-ok: >- + grpc_ssl.t h2_proxy_request_buffering_ssl.t h2_proxy_ssl.t + proxy_request_buffering_ssl.t proxy_ssl_conf_command.t + proxy_ssl_keepalive.t proxy_ssl.t proxy_ssl_verify.t ssl_cache.t + stream_proxy_protocol_ssl.t stream_proxy_ssl_conf_command.t + stream_proxy_ssl.t stream_proxy_ssl_verify.t + - ref: 1.25.0 test-ref: 5b2894ea1afd01a26c589ce11f310df118e42592 # Following tests pass with sanitizer on @@ -120,30 +160,19 @@ jobs: - name: untar build-dir run: tar -xf build-dir.tgz - - name: Install dependencies - run: | - sudo cpan -iT Proc::Find + - name: Openssl version + run: openssl version -a - # Locking in the version of SSLeay used with testing - - name: Download and install Net::SSLeay 1.94 manually - run: | - curl -LO https://www.cpan.org/modules/by-module/Net/CHRISN/Net-SSLeay-1.94.tar.gz - tar -xzf Net-SSLeay-1.94.tar.gz - cd Net-SSLeay-1.94 - perl Makefile.PL - make - sudo make install + - name: Setup Perl environment + uses: shogo82148/actions-setup-perl@v1 + with: + perl-version: '5.38.2' # SSL version 2.091 changes '' return to undef causing test case to fail. # Locking in the test version to use as 2.090 - - name: Download and install IO::Socket::SSL 2.090 manually + - name: Install dependencies run: | - curl -LO https://www.cpan.org/modules/by-module/IO/IO-Socket-SSL-2.090.tar.gz - tar -xzf IO-Socket-SSL-2.090.tar.gz - cd IO-Socket-SSL-2.090 - perl Makefile.PL - make - sudo make install + cpanm --notest Proc::Find Net::SSLeay@1.94 IO::Socket::SSL@2.090 - name: Checkout wolfssl-nginx uses: actions/checkout@v4 @@ -211,17 +240,14 @@ jobs: run: | echo "nginx_c_flags=-O0" >> $GITHUB_ENV - - name: workaround high-entropy ASLR - # not needed after either an update to llvm or runner is done - run: sudo sysctl vm.mmap_rnd_bits=28 - - name: Build nginx with sanitizer working-directory: nginx run: | ./auto/configure --with-wolfssl=$GITHUB_WORKSPACE/build-dir --with-http_ssl_module \ --with-stream --with-stream_ssl_module --with-stream_ssl_preread_module \ --with-http_v2_module --with-mail --with-mail_ssl_module \ - --with-cc-opt='-fsanitize=address -DNGX_DEBUG_PALLOC=1 -g3 ${{ env.nginx_c_flags }}' \ + --with-cc-opt='-fsanitize=address -DNGX_DEBUG_PALLOC=1 -g3 \ + ${{ env.nginx_c_flags }}' \ --with-ld-opt='-fsanitize=address ${{ env.nginx_c_flags }}' make -j @@ -229,19 +255,16 @@ jobs: working-directory: nginx run: ldd objs/nginx | grep wolfssl - - if: ${{ runner.debug }} - name: Run nginx-tests with sanitizer (debug) + - name: Create LSAN suppression file working-directory: nginx-tests run: | - LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$GITHUB_WORKSPACE/build-dir/lib \ - TMPDIR=$GITHUB_WORKSPACE TEST_NGINX_VERBOSE=y TEST_NGINX_CATLOG=y \ - TEST_NGINX_BINARY=../nginx/objs/nginx prove -v ${{ matrix.sanitize-ok }} + echo "leak:ngx_worker_process_init" > lsan.supp - if: ${{ !runner.debug }} name: Run nginx-tests with sanitizer working-directory: nginx-tests run: | LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$GITHUB_WORKSPACE/build-dir/lib \ + LSAN_OPTIONS=suppressions=$GITHUB_WORKSPACE/nginx-tests/lsan.supp \ TMPDIR=$GITHUB_WORKSPACE TEST_NGINX_BINARY=../nginx/objs/nginx \ prove ${{ matrix.sanitize-ok }} - diff --git a/.wolfssl_known_macro_extras b/.wolfssl_known_macro_extras index 97decd7acb..8bd95f782b 100644 --- a/.wolfssl_known_macro_extras +++ b/.wolfssl_known_macro_extras @@ -220,6 +220,7 @@ ENABLED_BSDKM_REGISTER ENABLE_SECURE_SOCKETS_LOGS ESP32 ESP8266 +ESPIPE ESP_ENABLE_WOLFSSH ESP_IDF_VERSION ESP_IDF_VERSION_MAJOR @@ -367,6 +368,7 @@ NO_ASM NO_ASN_OLD_TYPE_NAMES NO_CAMELLIA_CBC NO_CERT +NO_CERT_IN_TICKET NO_CIPHER_SUITE_ALIASES NO_CLIENT_CACHE NO_CLOCK_SPEEDUP diff --git a/configure.ac b/configure.ac index fe4c830c34..c3cbbb5be6 100644 --- a/configure.ac +++ b/configure.ac @@ -2743,7 +2743,8 @@ if test "$ENABLED_LIBWEBSOCKETS" = "yes" || test "$ENABLED_OPENVPN" = "yes" || \ test "$ENABLED_OPENRESTY" = "yes" || test "$ENABLED_RSYSLOG" = "yes" || \ test "$ENABLED_KRB" = "yes" || test "$ENABLED_CHRONY" = "yes" || \ test "$ENABLED_FFMPEG" = "yes" || test "$ENABLED_STRONGSWAN" = "yes" || \ - test "$ENABLED_OPENLDAP" = "yes" || test "x$ENABLED_MOSQUITTO" = "xyes" || test "$ENABLED_HITCH" = "yes" + test "$ENABLED_OPENLDAP" = "yes" || test "x$ENABLED_MOSQUITTO" = "xyes" || \ + test "$ENABLED_HITCH" = "yes" || test "$ENABLED_NGINX" = "yes" then ENABLED_OPENSSLALL="yes" fi diff --git a/src/bio.c b/src/bio.c index e8f78e8187..02431dd38a 100644 --- a/src/bio.c +++ b/src/bio.c @@ -1938,6 +1938,8 @@ int wolfSSL_BIO_get_len(WOLFSSL_BIO *bio) len = BAD_FUNC_ARG; if (len == 0) { len = wolfssl_file_len(file, &memSz); + if (len == WC_NO_ERR_TRACE(WOLFSSL_BAD_FILETYPE)) + len = 0; } if (len == 0) { len = (int)memSz; diff --git a/src/crl.c b/src/crl.c index 524adee47b..a419741788 100644 --- a/src/crl.c +++ b/src/crl.c @@ -1369,6 +1369,28 @@ WOLFSSL_X509_CRL* wolfSSL_X509_CRL_dup(const WOLFSSL_X509_CRL* crl) return ret; } +#ifdef OPENSSL_ALL +int wolfSSL_X509_CRL_up_ref(WOLFSSL_X509_CRL* crl) +{ + int ret; + + if (crl == NULL) + return WOLFSSL_FAILURE; + + wolfSSL_RefInc(&crl->ref, &ret); +#ifdef WOLFSSL_REFCNT_ERROR_RETURN + if (ret != 0) { + WOLFSSL_MSG("Failed to lock x509 mutex"); + return WOLFSSL_FAILURE; + } +#else + (void)ret; +#endif + + return WOLFSSL_SUCCESS; +} +#endif + /* returns WOLFSSL_SUCCESS on success. Does not take ownership of newcrl */ int wolfSSL_X509_STORE_add_crl(WOLFSSL_X509_STORE *store, WOLFSSL_X509_CRL *newcrl) { diff --git a/src/dtls.c b/src/dtls.c index 59bcc2046d..17e8b1b4c2 100644 --- a/src/dtls.c +++ b/src/dtls.c @@ -403,8 +403,9 @@ static int TlsTicketIsValid(const WOLFSSL* ssl, WolfSSL_ConstVector exts, if (!IsAtLeastTLSv1_3(it->pv)) *resume = TRUE; } - if (it != NULL) - ForceZero(it, sizeof(InternalTicket)); + /* `it` points into tempTicket on successful decryption so clearing it will + * also satisfy the WOLFSSL_CHECK_MEM_ZERO check. */ + ForceZero(tempTicket, SESSION_TICKET_LEN); return 0; } #endif /* HAVE_SESSION_TICKET */ diff --git a/src/internal.c b/src/internal.c index eb59f8133c..0d3468d8e1 100644 --- a/src/internal.c +++ b/src/internal.c @@ -10756,7 +10756,9 @@ static void AddRecordHeader(byte* output, word32 length, byte type, #if !defined(WOLFSSL_NO_TLS12) || (defined(HAVE_SESSION_TICKET) && \ - !defined(NO_WOLFSSL_SERVER)) + !defined(NO_WOLFSSL_SERVER)) || \ + defined(HAVE_CERTIFICATE_STATUS_REQUEST) || \ + defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2) || !defined(NO_CERTS) /* add handshake header for message */ static void AddHandShakeHeader(byte* output, word32 length, word32 fragOffset, word32 fragLength, @@ -10791,7 +10793,7 @@ static void AddHandShakeHeader(byte* output, word32 length, } /* add both headers for handshake message */ -static void AddHeaders(byte* output, word32 length, byte type, WOLFSSL* ssl) +WC_MAYBE_UNUSED static void AddHeaders(byte* output, word32 length, byte type, WOLFSSL* ssl) { word32 lengthAdj = HANDSHAKE_HEADER_SZ; word32 outputAdj = RECORD_HEADER_SZ; @@ -10806,17 +10808,14 @@ static void AddHeaders(byte* output, word32 length, byte type, WOLFSSL* ssl) AddRecordHeader(output, length + lengthAdj, handshake, ssl, CUR_ORDER); AddHandShakeHeader(output + outputAdj, length, 0, length, type, ssl); } -#endif /* !WOLFSSL_NO_TLS12 || (HAVE_SESSION_TICKET && !NO_WOLFSSL_SERVER) */ +#endif /* !WOLFSSL_NO_TLS12 || (HAVE_SESSION_TICKET && !NO_WOLFSSL_SERVER) || + * HAVE_CERTIFICATE_STATUS_REQUEST || + * HAVE_CERTIFICATE_STATUS_REQUEST_V2 || !NO_CERTS */ -#ifndef WOLFSSL_NO_TLS12 -#if (!defined(NO_CERTS) && (!defined(NO_WOLFSSL_SERVER) || \ - !defined(WOLFSSL_NO_CLIENT_AUTH))) || \ - ((!defined(NO_WOLFSSL_SERVER) || \ - (!defined(NO_WOLFSSL_CLIENT) && !defined(NO_CERTS) && \ - !defined(WOLFSSL_NO_CLIENT_AUTH))) && defined(WOLFSSL_DTLS)) -static void AddFragHeaders(byte* output, word32 fragSz, word32 fragOffset, - word32 length, byte type, WOLFSSL* ssl) +#if !defined(WOLFSSL_NO_TLS12) || (defined(WOLFSSL_DTLS) && defined(WOLFSSL_TLS13)) +WC_MAYBE_UNUSED static void AddFragHeaders(byte* output, word32 fragSz, + word32 fragOffset, word32 length, byte type, WOLFSSL* ssl) { word32 lengthAdj = HANDSHAKE_HEADER_SZ; word32 outputAdj = RECORD_HEADER_SZ; @@ -10832,11 +10831,8 @@ static void AddFragHeaders(byte* output, word32 fragSz, word32 fragOffset, AddRecordHeader(output, fragSz + lengthAdj, handshake, ssl, CUR_ORDER); AddHandShakeHeader(output + outputAdj, length, fragOffset, fragSz, type, ssl); } -#endif +#endif /* !WOLFSSL_NO_TLS12 || (WOLFSSL_DTLS && WOLFSSL_TLS13) */ -#if !defined(NO_WOLFSSL_SERVER) || \ - (!defined(NO_WOLFSSL_CLIENT) && !defined(NO_CERTS) && \ - !defined(WOLFSSL_NO_CLIENT_AUTH)) /** * Send the handshake message. This function handles fragmenting the message * so that it will fit into the desired MTU or the max fragment size. @@ -10850,8 +10846,8 @@ static void AddFragHeaders(byte* output, word32 fragSz, word32 fragOffset, * @param type Type of message being sent * @return 0 on success and negative otherwise */ -static int SendHandshakeMsg(WOLFSSL* ssl, byte* input, word32 inputSz, - enum HandShakeType type, const char* packetName) +WC_MAYBE_UNUSED static int SendHandshakeMsg(WOLFSSL* ssl, byte* input, + word32 inputSz, enum HandShakeType type, const char* packetName) { int maxFrag; int ret = 0; @@ -11012,10 +11008,6 @@ static int SendHandshakeMsg(WOLFSSL* ssl, byte* input, word32 inputSz, ssl->options.buildingMsg = 0; return ret; } -#endif /* !NO_WOLFSSL_SERVER || (!NO_WOLFSSL_CLIENT && !NO_CERTS && - * !WOLFSSL_NO_CLIENT_AUTH) */ - -#endif /* !WOLFSSL_NO_TLS12 */ /* return bytes received, WOLFSSL_FATAL_ERROR on error, @@ -12232,12 +12224,16 @@ static int GetRecordHeader(WOLFSSL* ssl, word32* inOutIdx, /* record layer length check */ #ifdef HAVE_MAX_FRAGMENT - if (*size > (ssl->max_fragment + MAX_COMP_EXTRA + MAX_MSG_EXTRA)) { + if (*size > (ssl->max_fragment + MAX_MSG_EXTRA + + (ssl->options.usingCompression ? MAX_COMP_EXTRA : 0))) { + WOLFSSL_MSG_EX("Record length %d exceeds max fragment size", *size); WOLFSSL_ERROR_VERBOSE(LENGTH_ERROR); return LENGTH_ERROR; } #else - if (*size > (MAX_RECORD_SIZE + MAX_COMP_EXTRA + MAX_MSG_EXTRA)) { + if (*size > (MAX_RECORD_SIZE + MAX_MSG_EXTRA + + (ssl->options.usingCompression ? MAX_COMP_EXTRA : 0))) { + WOLFSSL_MSG_EX("Record length %d exceeds max record size", *size); WOLFSSL_ERROR_VERBOSE(LENGTH_ERROR); return LENGTH_ERROR; } @@ -13393,9 +13389,7 @@ int CheckIPAddr(DecodedCert* dCert, const char* ipasc) } -#if defined(SESSION_CERTS) && (!defined(NO_WOLFSSL_CLIENT) || \ - !defined(WOLFSSL_NO_CLIENT_AUTH)) -static void AddSessionCertToChain(WOLFSSL_X509_CHAIN* chain, +WC_MAYBE_UNUSED static void AddSessionCertToChain(WOLFSSL_X509_CHAIN* chain, byte* certBuf, word32 certSz) { if (chain->count < MAX_CHAIN_DEPTH && @@ -13408,7 +13402,6 @@ static void AddSessionCertToChain(WOLFSSL_X509_CHAIN* chain, WOLFSSL_MSG("Couldn't store chain cert for session"); } } -#endif #if defined(KEEP_PEER_CERT) || defined(SESSION_CERTS) || \ defined(OPENSSL_EXTRA) || defined(OPENSSL_EXTRA_X509_SMALL) || \ @@ -38892,9 +38885,15 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) int error; word32 itHash = 0; byte zeros[WOLFSSL_TICKET_MAC_SZ]; /* biggest cmp size */ + int internalTicketSz; + byte* mac; +#if defined(OPENSSL_ALL) && defined(KEEP_PEER_CERT) && \ + !defined(NO_CERT_IN_TICKET) + word16 peerCertSz = 0; +#endif + WOLFSSL_ASSERT_GE(sizeof(ssl->session->staticTicket), WOLFSSL_TICKET_ENC_SZ); WOLFSSL_ASSERT_SIZEOF_GE(ssl->session->staticTicket, *et); - WOLFSSL_ASSERT_SIZEOF_GE(et->enc_ticket, *it); if (ssl->session->ticket != ssl->session->staticTicket) { /* Always use the static ticket buffer */ @@ -38910,7 +38909,7 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) if (ssl->error != WC_NO_ERR_TRACE(WC_PENDING_E)) #endif { - XMEMSET(et, 0, sizeof(*et)); + XMEMSET(ssl->session->ticket, 0, SESSION_TICKET_LEN); } /* build internal */ @@ -38974,6 +38973,55 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) XMEMCPY(it->sessionCtx, ssl->sessionCtx, ID_LEN); #endif +#if defined(OPENSSL_ALL) && defined(KEEP_PEER_CERT) && \ + !defined(NO_CERT_IN_TICKET) + /* Store peer certificate in ticket for session resumption. + * Try ssl->peerCert first, then ssl->session->chain as fallback. + * Skip for DTLS to keep ticket size small for MTU constraints. */ + if (ssl->options.dtls) { + c16toa(0, it->peerCertLen); + peerCertSz = 0; + } + else { + const byte* certDer = NULL; + word32 certDerSz = 0; + + if (ssl->peerCert.derCert != NULL && + ssl->peerCert.derCert->length > 0) { + /* Use current peer certificate */ + certDer = ssl->peerCert.derCert->buffer; + certDerSz = ssl->peerCert.derCert->length; + } +#ifdef SESSION_CERTS + else if (ssl->session->chain.count > 0) { + /* Use peer certificate from session chain */ + certDer = ssl->session->chain.certs[0].buffer; + certDerSz = ssl->session->chain.certs[0].length; + } +#endif + + if (certDer != NULL && certDerSz > 0 && + certDerSz <= MAX_TICKET_PEER_CERT_SZ +#ifdef HAVE_MAX_FRAGMENT + /* We don't support fragmentation in + * SendTls13NewSessionTicket yet. */ + && (!IsAtLeastTLSv1_3(ssl->version) || + ssl->max_fragment == MAX_RECORD_SIZE) +#endif + ) { + c16toa((word16)certDerSz, it->peerCertLen); + XMEMCPY(it->peerCert, certDer, certDerSz); + peerCertSz = (word16)certDerSz; + } + else { + if (certDerSz > MAX_TICKET_PEER_CERT_SZ) + WOLFSSL_MSG("Peer cert too large for ticket, skipping"); + c16toa(0, it->peerCertLen); + peerCertSz = 0; + } + } +#endif + #ifdef WOLFSSL_TICKET_HAVE_ID { const byte* id = NULL; @@ -38986,6 +39034,16 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) } #endif + /* Calculate actual internal ticket size */ +#if defined(OPENSSL_ALL) && defined(KEEP_PEER_CERT) && \ + !defined(NO_CERT_IN_TICKET) + internalTicketSz = (int)(WOLFSSL_INTERNAL_TICKET_BASE_SZ + peerCertSz); +#else + internalTicketSz = (int)WOLFSSL_INTERNAL_TICKET_BASE_SZ; +#endif + /* MAC is placed after the encrypted data */ + mac = et->enc_ticket + WOLFSSL_TICKET_ENC_SZ; + /* encrypt */ encLen = WOLFSSL_TICKET_ENC_SZ; /* max size user can use */ if (ssl->ctx->ticketEncCb == NULL @@ -39002,10 +39060,10 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) ret = BAD_TICKET_ENCRYPT; } else { - itHash = HashObject((byte*)it, sizeof(*it), &error); + itHash = HashObject((byte*)it, (word32)internalTicketSz, &error); if (error == 0) { - ret = ssl->ctx->ticketEncCb(ssl, et->key_name, et->iv, et->mac, - 1, et->enc_ticket, WOLFSSL_INTERNAL_TICKET_LEN, &encLen, + ret = ssl->ctx->ticketEncCb(ssl, et->key_name, et->iv, mac, + 1, et->enc_ticket, internalTicketSz, &encLen, SSL_TICKET_CTX(ssl)); } else { @@ -39020,7 +39078,7 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) #endif goto error; } - if (encLen < (int)WOLFSSL_INTERNAL_TICKET_LEN || + if (encLen < internalTicketSz || encLen > (int)WOLFSSL_TICKET_ENC_SZ) { WOLFSSL_MSG("Bad user ticket encrypt size"); ret = BAD_TICKET_KEY_CB_SZ; @@ -39029,7 +39087,8 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) /* sanity checks on encrypt callback */ /* internal ticket can't be the same if encrypted */ - if (itHash == HashObject((byte*)it, sizeof(*it), &error) || error != 0) + if (itHash == HashObject((byte*)it, (word32)internalTicketSz, &error) || + error != 0) { WOLFSSL_MSG("User ticket encrypt didn't encrypt or hash failed"); ret = BAD_TICKET_ENCRYPT; @@ -39053,7 +39112,7 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) } /* mac */ - if (XMEMCMP(et->mac, zeros, WOLFSSL_TICKET_MAC_SZ) == 0) { + if (XMEMCMP(mac, zeros, WOLFSSL_TICKET_MAC_SZ) == 0) { WOLFSSL_MSG("User ticket encrypt didn't set mac"); ret = BAD_TICKET_ENCRYPT; goto error; @@ -39063,7 +39122,7 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) c16toa((word16)encLen, et->enc_len); if (encLen < (int)WOLFSSL_TICKET_ENC_SZ) { /* move mac up since whole enc buffer not used */ - XMEMMOVE(et->enc_ticket + encLen, et->mac, + XMEMMOVE(et->enc_ticket + encLen, mac, WOLFSSL_TICKET_MAC_SZ); } ssl->session->ticketLen = @@ -39073,11 +39132,12 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) error: #ifdef WOLFSSL_CHECK_MEM_ZERO /* Ticket has sensitive data in it now. */ - wc_MemZero_Add("Create Ticket internal", it, sizeof(InternalTicket)); + wc_MemZero_Add("Create Ticket internal", it, + WOLFSSL_INTERNAL_TICKET_MAX_SZ); #endif - ForceZero(it, sizeof(*it)); + ForceZero(it, WOLFSSL_INTERNAL_TICKET_MAX_SZ); #ifdef WOLFSSL_CHECK_MEM_ZERO - wc_MemZero_Check(it, sizeof(InternalTicket)); + wc_MemZero_Check(it, WOLFSSL_INTERNAL_TICKET_MAX_SZ); #endif WOLFSSL_ERROR_VERBOSE(ret); return ret; @@ -39128,7 +39188,7 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) } else { /* Callback uses ssl without const but for DTLS, it really shouldn't - * modify its state. */ + * modify its state. MAC is located after encrypted data. */ ret = ssl->ctx->ticketEncCb((WOLFSSL*)ssl, et->key_name, et->iv, et->enc_ticket + inLen, 0, et->enc_ticket, inLen, &outLen, @@ -39353,6 +39413,49 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) ato16(it->namedGroup, &ssl->session->namedGroup); #endif } + +#if defined(OPENSSL_ALL) && defined(KEEP_PEER_CERT) && \ + !defined(NO_CERT_IN_TICKET) + /* Restore peer certificate from ticket to session chain and peerCert */ + { + word16 peerCertLen = 0; + ato16(it->peerCertLen, &peerCertLen); + + if (peerCertLen > 0 && peerCertLen <= MAX_TICKET_PEER_CERT_SZ) { +#ifdef SESSION_CERTS + /* Clear existing chain and add the peer certificate */ + ssl->session->chain.count = 0; + AddSessionCertToChain(&ssl->session->chain, + it->peerCert, peerCertLen); +#endif + /* Also decode into ssl->peerCert for direct access */ + { + int ret; + DecodedCert* dCert; + + dCert = (DecodedCert*)XMALLOC(sizeof(DecodedCert), ssl->heap, + DYNAMIC_TYPE_DCERT); + if (dCert != NULL) { + InitDecodedCert(dCert, it->peerCert, peerCertLen, ssl->heap); + ret = ParseCertRelative(dCert, CERT_TYPE, 0, NULL, NULL); + if (ret == 0) { + FreeX509(&ssl->peerCert); + InitX509(&ssl->peerCert, 0, ssl->heap); + ret = CopyDecodedToX509(&ssl->peerCert, dCert); + if (ret != 0) { + /* Failed to copy - clear peerCert */ + FreeX509(&ssl->peerCert); + InitX509(&ssl->peerCert, 0, ssl->heap); + } + } + FreeDecodedCert(dCert); + XFREE(dCert, ssl->heap, DYNAMIC_TYPE_DCERT); + } + } + } + } +#endif + ssl->version.minor = it->pv.minor; } @@ -39397,6 +39500,23 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) #ifdef OPENSSL_EXTRA it->sessionCtxSz = sess->sessionCtxSz; XMEMCPY(it->sessionCtx, sess->sessionCtx, sess->sessionCtxSz); +#endif +#if defined(OPENSSL_ALL) && defined(KEEP_PEER_CERT) && \ + defined(SESSION_CERTS) && !defined(NO_CERT_IN_TICKET) + /* Store peer certificate from session chain */ + if (sess->chain.count > 0) { + word32 certLen = sess->chain.certs[0].length; + if (certLen <= MAX_TICKET_PEER_CERT_SZ) { + c16toa((word16)certLen, it->peerCertLen); + XMEMCPY(it->peerCert, sess->chain.certs[0].buffer, certLen); + } + else { + c16toa(0, it->peerCertLen); + } + } + else { + c16toa(0, it->peerCertLen); + } #endif } @@ -39467,9 +39587,10 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) byte* tmp; WOLFSSL_MSG("Found session matching the session id" " found in the ticket"); - /* Allocate and populate an InternalTicket */ + /* Allocate and populate an InternalTicket. Need max size + * because PopulateInternalTicketFromSession may write peer cert */ #ifdef WOLFSSL_NO_REALLOC - tmp = (byte*)XMALLOC(sizeof(InternalTicket), ssl->heap, + tmp = (byte*)XMALLOC(WOLFSSL_INTERNAL_TICKET_MAX_SZ, ssl->heap, DYNAMIC_TYPE_TLSX); if (tmp != NULL && psk->identity != NULL) { @@ -39478,13 +39599,13 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) psk->identity = NULL; } #else - tmp = (byte*)XREALLOC(psk->identity, sizeof(InternalTicket), + tmp = (byte*)XREALLOC(psk->identity, WOLFSSL_INTERNAL_TICKET_MAX_SZ, ssl->heap, DYNAMIC_TYPE_TLSX); #endif if (tmp != NULL) { - XMEMSET(tmp, 0, sizeof(InternalTicket)); + XMEMSET(tmp, 0, WOLFSSL_INTERNAL_TICKET_MAX_SZ); psk->identity = tmp; - psk->identityLen = sizeof(InternalTicket); + psk->identityLen = WOLFSSL_INTERNAL_TICKET_MAX_SZ; psk->it = (InternalTicket*)tmp; PopulateInternalTicketFromSession(sess, psk->it); decryptRet = WOLFSSL_TICKET_RET_OK; @@ -39519,8 +39640,8 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) } #ifdef WOLFSSL_CHECK_MEM_ZERO /* Internal ticket successfully decrypted. */ - wc_MemZero_Add("Do Client Ticket internal", psk->it, - sizeof(InternalTicket)); + wc_MemZero_Add("Do Client Ticket internal", psk->identity, + psk->identityLen); #endif ret = DoClientTicketCheckVersion(ssl, psk->it); @@ -39528,7 +39649,7 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) psk->decryptRet = PSK_DECRYPT_FAIL; ForceZero(psk->identity, psk->identityLen); #ifdef WOLFSSL_CHECK_MEM_ZERO - wc_MemZero_Check(psk->it, sizeof(InternalTicket)); + wc_MemZero_Check(psk->it, psk->identityLen); #endif WOLFSSL_LEAVE("DoClientTicket_ex", ret); return ret; @@ -39545,7 +39666,13 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) int ret; InternalTicket* it = NULL; #ifdef WOLFSSL_TLS13 - InternalTicket staticIt; + /* Static buffer for stateful tickets - need max size for peer cert */ + #ifdef WOLFSSL_SMALL_STACK + byte* staticItBuf = NULL; + #else + byte staticItBuf[WOLFSSL_INTERNAL_TICKET_MAX_SZ]; + #endif + InternalTicket* staticIt = NULL; const WOLFSSL_SESSION* sess = NULL; psk_sess_free_cb_ctx freeCtx; @@ -39577,18 +39704,27 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) * stateless tickets are much longer. */ sess = GetSesionFromCacheOrExt(ssl, input, &freeCtx); if (sess != NULL) { - it = &staticIt; - XMEMSET(it, 0, sizeof(InternalTicket)); + #ifdef WOLFSSL_SMALL_STACK + staticItBuf = (byte*)XMALLOC(WOLFSSL_INTERNAL_TICKET_MAX_SZ, + ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + if (staticItBuf == NULL) { + decryptRet = WOLFSSL_TICKET_RET_FATAL; + goto cleanup; + } + #endif + staticIt = (InternalTicket*)staticItBuf; + it = staticIt; + XMEMSET(it, 0, WOLFSSL_INTERNAL_TICKET_MAX_SZ); PopulateInternalTicketFromSession(sess, it); decryptRet = WOLFSSL_TICKET_RET_OK; } } else #endif - if (len >= sizeof(*it)) + if (len >= WOLFSSL_INTERNAL_TICKET_LEN + WOLFSSL_TICKET_FIXED_SZ) decryptRet = DoDecryptTicket(ssl, input, len, &it); else - WOLFSSL_MSG("Ticket is smaller than InternalTicket. Rejecting."); + WOLFSSL_MSG("Ticket is smaller than minimum size. Rejecting."); if (decryptRet != WOLFSSL_TICKET_RET_OK && @@ -39597,8 +39733,10 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) goto cleanup; } #ifdef WOLFSSL_CHECK_MEM_ZERO - /* Internal ticket successfully decrypted. */ - wc_MemZero_Add("Do Client Ticket internal", it, sizeof(InternalTicket)); + /* Internal ticket successfully decrypted. Zero at least the minimum + * internal ticket size (contains master secret). */ + wc_MemZero_Add("Do Client Ticket internal", it, + WOLFSSL_INTERNAL_TICKET_LEN); #endif ret = DoClientTicketCheckVersion(ssl, it); @@ -39611,12 +39749,19 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) cleanup: if (it != NULL) { - ForceZero(it, sizeof(*it)); + /* Zero the minimum internal ticket size. The it pointer points into + * the input buffer which may be smaller than MAX_SZ. We use the + * minimum length (WOLFSSL_INTERNAL_TICKET_LEN) which is guaranteed + * to fit and contains the sensitive master secret. */ + ForceZero(it, WOLFSSL_INTERNAL_TICKET_LEN); #ifdef WOLFSSL_CHECK_MEM_ZERO - wc_MemZero_Check(it, sizeof(InternalTicket)); + wc_MemZero_Check(it, WOLFSSL_INTERNAL_TICKET_LEN); #endif } #ifdef WOLFSSL_TLS13 + #ifdef WOLFSSL_SMALL_STACK + XFREE(staticItBuf, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + #endif if (sess != NULL) FreeSessionFromCacheOrExt(ssl, sess, &freeCtx); #endif @@ -39632,10 +39777,7 @@ cleanup: psk->decryptRet = PSK_DECRYPT_NONE; ForceZero(psk->identity, psk->identityLen); #ifdef WOLFSSL_CHECK_MEM_ZERO - /* We want to check the InternalTicket area since that is what - * we registered in DoClientTicket_ex */ - wc_MemZero_Check((((ExternalTicket*)psk->identity)->enc_ticket), - sizeof(InternalTicket)); + wc_MemZero_Check(psk->identity, psk->identityLen); #endif } } @@ -39651,11 +39793,15 @@ cleanup: int sendSz; word32 length = SESSION_HINT_SZ + LENGTH_SZ; word32 idx = RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ; + word32 headerSz = 0; WOLFSSL_START(WC_FUNC_TICKET_SEND); WOLFSSL_ENTER("SendTicket"); - if (ssl->options.createTicket) { + if (ssl->options.createTicket && + /* This will be set when SendHandshakeMsg returns WANT_WRITE. Create + * a new ticket only once. */ + !ssl->options.buildingMsg) { ret = SetupTicket(ssl); if (ret != 0) return ret; @@ -39677,20 +39823,13 @@ cleanup: idx += DTLS_RECORD_EXTRA + DTLS_HANDSHAKE_EXTRA; #endif } + headerSz = idx; - if (IsEncryptionOn(ssl, 1) && ssl->options.handShakeDone) - sendSz += cipherExtraData(ssl); - - /* Set this in case CheckAvailableSize returns a WANT_WRITE so that state - * is not advanced yet */ - ssl->options.buildingMsg = 1; - - /* check for available size */ - if ((ret = CheckAvailableSize(ssl, sendSz)) != 0) - return ret; + output = (byte*)XMALLOC(sendSz, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + if (output == NULL) + return MEMORY_E; /* get output buffer */ - output = GetOutputBuffer(ssl); AddHeaders(output, length, session_ticket, ssl); /* hint */ @@ -39705,45 +39844,9 @@ cleanup: XMEMCPY(output + idx, ssl->session->ticket, ssl->session->ticketLen); idx += ssl->session->ticketLen; - if (IsEncryptionOn(ssl, 1) && ssl->options.handShakeDone) { - byte* input; - int inputSz = (int)idx; /* build msg adds rec hdr */ - int recordHeaderSz = RECORD_HEADER_SZ; - - if (ssl->options.dtls) - recordHeaderSz += DTLS_RECORD_EXTRA; - inputSz -= recordHeaderSz; - input = (byte*)XMALLOC(inputSz, ssl->heap, DYNAMIC_TYPE_IN_BUFFER); - if (input == NULL) - return MEMORY_E; - - XMEMCPY(input, output + recordHeaderSz, inputSz); - sendSz = BuildMessage(ssl, output, sendSz, input, inputSz, - handshake, 1, 0, 0, CUR_ORDER); - XFREE(input, ssl->heap, DYNAMIC_TYPE_IN_BUFFER); - - if (sendSz < 0) - return sendSz; - } - else { - #ifdef WOLFSSL_DTLS - if (ssl->options.dtls) { - if ((ret = DtlsMsgPoolSave(ssl, output, (word32)sendSz, session_ticket)) != 0) - return ret; - - DtlsSEQIncrement(ssl, CUR_ORDER); - } - #endif - ret = HashOutput(ssl, output, sendSz, 0); - if (ret != 0) - return ret; - } - - ssl->buffers.outputBuffer.length += sendSz; - ssl->options.buildingMsg = 0; - - if (!ssl->options.groupMessages) - ret = SendBuffered(ssl); + ret = SendHandshakeMsg(ssl, output, idx - headerSz, session_ticket, + "Session Ticket"); + XFREE(output, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); WOLFSSL_LEAVE("SendTicket", ret); WOLFSSL_END(WC_FUNC_TICKET_SEND); @@ -40241,7 +40344,8 @@ static int DefTicketEncCb(WOLFSSL* ssl, byte key_name[WOLFSSL_TICKET_NAME_SZ], WOLFSSL_ENTER("DefTicketEncCb"); - if ((!enc) && (inLen != WOLFSSL_INTERNAL_TICKET_LEN)) { + /* For decryption, check minimum internal ticket size */ + if ((!enc) && (inLen < (int)WOLFSSL_INTERNAL_TICKET_LEN)) { return BUFFER_E; } diff --git a/src/ssl.c b/src/ssl.c index 10fd73ca60..72f78c1b32 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -15523,6 +15523,8 @@ WOLFSSL_CTX* wolfSSL_set_SSL_CTX(WOLFSSL* ssl, WOLFSSL_CTX* ctx) ssl->buffers.weOwnKey = 1; } else { + if (ssl->buffers.key != NULL && ssl->buffers.weOwnKey) + FreeDer(&ssl->buffers.key); ssl->buffers.key = ctx->privateKey; } #else diff --git a/src/ssl_api_cert.c b/src/ssl_api_cert.c index 82a2cbff39..231bf0c385 100644 --- a/src/ssl_api_cert.c +++ b/src/ssl_api_cert.c @@ -869,7 +869,8 @@ int wolfSSL_UnloadCertsKeys(WOLFSSL* ssl) if (ssl->buffers.weOwnKey) { WOLFSSL_MSG("Unloading key"); - ForceZero(ssl->buffers.key->buffer, ssl->buffers.key->length); + if (ssl->buffers.key != NULL && ssl->buffers.key->buffer != NULL) + ForceZero(ssl->buffers.key->buffer, ssl->buffers.key->length); FreeDer(&ssl->buffers.key); #ifdef WOLFSSL_BLIND_PRIVATE_KEY FreeDer(&ssl->buffers.keyMask); @@ -880,7 +881,8 @@ int wolfSSL_UnloadCertsKeys(WOLFSSL* ssl) #ifdef WOLFSSL_DUAL_ALG_CERTS if (ssl->buffers.weOwnAltKey) { WOLFSSL_MSG("Unloading alt key"); - ForceZero(ssl->buffers.altKey->buffer, ssl->buffers.altKey->length); + if (ssl->buffers.altKey != NULL && ssl->buffers.altKey->buffer != NULL) + ForceZero(ssl->buffers.altKey->buffer, ssl->buffers.altKey->length); FreeDer(&ssl->buffers.altKey); #ifdef WOLFSSL_BLIND_PRIVATE_KEY FreeDer(&ssl->buffers.altKeyMask); @@ -1710,6 +1712,22 @@ WOLFSSL_X509* wolfSSL_get_certificate(WOLFSSL* ssl) else if (ssl->buffers.weOwnCert) { /* Check if we already have a certificate allocated. */ if (ssl->ourCert == NULL) { + /* Check if ctx has ourCert set - if so, use it instead of creating + * a new X509. This maintains pointer compatibility with + * applications (like nginx OCSP stapling) that use the X509 pointer + * from SSL_CTX_use_certificate as a lookup key. */ + if (ssl->ctx != NULL && ssl->ctx->ourCert != NULL) { + /* Compare cert buffers to make sure they are the same */ + if (ssl->buffers.certificate == NULL || + ssl->buffers.certificate->buffer == NULL || + (ssl->buffers.certificate->length == + ssl->ctx->certificate->length && + XMEMCMP(ssl->buffers.certificate->buffer, + ssl->ctx->certificate->buffer, + ssl->buffers.certificate->length) == 0)) { + return ssl->ctx->ourCert; + } + } /* We own certificate so this should never happen. */ if (ssl->buffers.certificate == NULL) { WOLFSSL_MSG("Certificate buffer not set!"); diff --git a/src/ssl_misc.c b/src/ssl_misc.c index bf10255fd4..072f2cc133 100644 --- a/src/ssl_misc.c +++ b/src/ssl_misc.c @@ -230,7 +230,15 @@ static int wolfssl_file_len(XFILE fp, long* fileSz) /* Get file offset at end of file. */ curr = (long)XFTELL(fp); if (curr < 0) { - ret = WOLFSSL_BAD_FILE; +#ifdef ESPIPE + if (errno == ESPIPE) { + WOLFSSL_ERROR_MSG("wolfssl_file_len: file is a pipe"); + *fileSz = 0; + ret = WOLFSSL_BAD_FILETYPE; + } + else +#endif + ret = WOLFSSL_BAD_FILE; } } /* Move to end of file. */ diff --git a/src/ssl_sk.c b/src/ssl_sk.c index c78a56aab5..a8cf68d52e 100644 --- a/src/ssl_sk.c +++ b/src/ssl_sk.c @@ -448,8 +448,24 @@ static int wolfssl_sk_dup_data(WOLFSSL_STACK* dst, WOLFSSL_STACK* src) break; } break; + case STACK_TYPE_X509_CRL: +#if defined(OPENSSL_EXTRA) && defined(HAVE_CRL) + if (src->data.crl == NULL) { + break; + } + dst->data.crl = wolfSSL_X509_CRL_dup(src->data.crl); + if (dst->data.crl == NULL) { + WOLFSSL_MSG("wolfSSL_X509_CRL_dup error"); + err = 1; + break; + } +#else + WOLFSSL_MSG("CRL support not enabled"); + err = 1; +#endif + break; case STACK_TYPE_X509_OBJ: - #if defined(OPENSSL_ALL) +#if defined(OPENSSL_ALL) if (src->data.x509_obj == NULL) { break; } @@ -460,8 +476,11 @@ static int wolfssl_sk_dup_data(WOLFSSL_STACK* dst, WOLFSSL_STACK* src) err = 1; break; } +#else + WOLFSSL_MSG("OPENSSL_ALL support not enabled"); + err = 1; +#endif break; - #endif case STACK_TYPE_BIO: case STACK_TYPE_STRING: case STACK_TYPE_ACCESS_DESCRIPTION: @@ -475,7 +494,6 @@ static int wolfssl_sk_dup_data(WOLFSSL_STACK* dst, WOLFSSL_STACK* src) case STACK_TYPE_BY_DIR_entry: case STACK_TYPE_BY_DIR_hash: case STACK_TYPE_DIST_POINT: - case STACK_TYPE_X509_CRL: case STACK_TYPE_GENERAL_SUBTREE: default: WOLFSSL_MSG("Unsupported stack type"); @@ -488,7 +506,8 @@ static int wolfssl_sk_dup_data(WOLFSSL_STACK* dst, WOLFSSL_STACK* src) /* Duplicate the stack of nodes. * - * TODO: OpenSSL does a shallow copy but we have wolfSSL_shallow_sk_dup(). + * OpenSSL does a shallow copy but we map to wolfSSL_shallow_sk_dup() + * when we want a shallow copy. * * Data is copied/duplicated - deep copy. * @@ -683,7 +702,7 @@ void* wolfSSL_sk_value(const WOLFSSL_STACK* sk, int i) #if (!defined(NO_CERTS) && (defined(OPENSSL_EXTRA) || \ defined(WOLFSSL_WPAS_SMALL))) || defined(WOLFSSL_QT) || \ defined(OPENSSL_ALL) -/* Put the data into a node at the top of the stack. +/* Put the data into a node at the end of the list. * * @param [in, out] stack Stack of objects. * @param [in] data Data to store in stack. @@ -698,7 +717,7 @@ int wolfSSL_sk_push(WOLFSSL_STACK* stack, const void *data) return wolfSSL_sk_insert(stack, data, -1); } -/* Put the data into a node at an index in the stack. +/* Put the data into a node at an index in the list. * * @param [in, out] stack Stack of objects. * @param [in] data Data to store in stack. diff --git a/src/x509.c b/src/x509.c index 6b42fa2d21..843166b235 100644 --- a/src/x509.c +++ b/src/x509.c @@ -38,6 +38,8 @@ #include #endif +#define MAX_BIO_READ_BUFFER (MAX_X509_SIZE * 16) + #if defined(OPENSSL_ALL) || defined(OPENSSL_EXTRA) unsigned int wolfSSL_X509_get_extension_flags(WOLFSSL_X509* x509) { @@ -4672,6 +4674,14 @@ WOLFSSL_STACK* wolfSSL_sk_X509_CRL_new(void) return s; } +WOLFSSL_STACK* wolfSSL_sk_X509_CRL_new_null(void) +{ + WOLFSSL_STACK* s = wolfSSL_sk_new_null(); + if (s != NULL) + s->type = STACK_TYPE_X509_CRL; + return s; +} + void wolfSSL_sk_X509_CRL_pop_free(WOLF_STACK_OF(WOLFSSL_X509_CRL)* sk, void (*f) (WOLFSSL_X509_CRL*)) { @@ -6056,14 +6066,25 @@ static WOLFSSL_X509* loadX509orX509REQFromBuffer( /* ready to be decoded. */ if (der != NULL && der->buffer != NULL) { WC_DECLARE_VAR(cert, DecodedCert, 1, 0); + /* For TRUSTED_CERT_TYPE, the DER buffer contains the certificate + * followed by auxiliary trust info. ParseCertRelative expects CERT_TYPE + * and will parse only the certificate portion, ignoring the rest. */ + int parseType = (type == TRUSTED_CERT_TYPE) ? CERT_TYPE : type; WC_ALLOC_VAR_EX(cert, DecodedCert, 1, NULL, DYNAMIC_TYPE_DCERT, ret=MEMORY_ERROR); if (WC_VAR_OK(cert)) { InitDecodedCert(cert, der->buffer, der->length, NULL); - ret = ParseCertRelative(cert, type, 0, NULL, NULL); + ret = ParseCertRelative(cert, parseType, 0, NULL, NULL); if (ret == 0) { + /* For TRUSTED_CERT_TYPE, truncate the DER buffer to exclude + * auxiliary trust data. ParseCertRelative sets srcIdx to the + * end of the certificate, so we adjust cert->maxIdx accordingly. */ + if (type == TRUSTED_CERT_TYPE && cert->srcIdx < cert->maxIdx) { + cert->maxIdx = cert->srcIdx; + } + x509 = (WOLFSSL_X509*)XMALLOC(sizeof(WOLFSSL_X509), NULL, DYNAMIC_TYPE_X509); if (x509 != NULL) { @@ -12922,22 +12943,23 @@ static WOLFSSL_X509 *loadX509orX509REQFromPemBio(WOLFSSL_BIO *bp, int pemSz; long i = 0, l, footerSz; const char* footer = NULL; + int streaming = 0; /* Flag to indicate if source is streaming (FIFO) */ + const char* altFooter = NULL; + long altFooterSz = 0; WOLFSSL_ENTER("loadX509orX509REQFromPemBio"); - if (bp == NULL || (type != CERT_TYPE && type != CERTREQ_TYPE)) { + if (bp == NULL || (type != CERT_TYPE && type != CERTREQ_TYPE && + type != TRUSTED_CERT_TYPE)) { WOLFSSL_LEAVE("wolfSSL_PEM_read_bio_X509", BAD_FUNC_ARG); return NULL; } if ((l = wolfSSL_BIO_get_len(bp)) <= 0) { - /* No certificate in buffer */ -#if defined (WOLFSSL_HAPROXY) - WOLFSSL_ERROR(PEM_R_NO_START_LINE); -#else - WOLFSSL_ERROR(ASN_NO_PEM_HEADER); -#endif - return NULL; + /* No certificate size available - could be FIFO or other streaming + * source. Use MAX_X509_SIZE as initial buffer, will resize if needed. */ + l = MAX_X509_SIZE; + streaming = 1; } pemSz = (int)l; @@ -12953,27 +12975,79 @@ static WOLFSSL_X509 *loadX509orX509REQFromPemBio(WOLFSSL_BIO *bp, } footerSz = (long)XSTRLEN(footer); + /* For TRUSTED_CERT_TYPE, also prepare to check for regular CERT footer + * as the file might contain regular certificates instead of TRUSTED + * format */ + if (type == TRUSTED_CERT_TYPE) { + wc_PemGetHeaderFooter(CERT_TYPE, NULL, &altFooter); + if (altFooter != NULL) { + altFooterSz = (long)XSTRLEN(altFooter); + } + } + /* TODO: Inefficient * reading in one byte at a time until see the footer */ while ((l = wolfSSL_BIO_read(bp, (char *)&pem[i], 1)) == 1) { + int foundFooter = 0; i++; - if (i > footerSz && XMEMCMP((char *)&pem[i-footerSz], footer, - footerSz) == 0) { - if (wolfSSL_BIO_read(bp, (char *)&pem[i], 1) == 1) { + /* Check if buffer is full and we're reading from streaming source */ + if (i >= pemSz && streaming) { + /* Double the buffer size for streaming sources */ + int newSz = pemSz * 2; + unsigned char* newPem; + + /* Sanity check: don't grow beyond reasonable limit */ + if (newSz > MAX_BIO_READ_BUFFER) { + WOLFSSL_MSG("PEM data too large for streaming source"); + XFREE(pem, 0, DYNAMIC_TYPE_PEM); + return NULL; + } + + newPem = (unsigned char*)XREALLOC(pem, newSz, 0, DYNAMIC_TYPE_PEM); + if (newPem == NULL) { + WOLFSSL_MSG("Failed to resize PEM buffer"); + XFREE(pem, 0, DYNAMIC_TYPE_PEM); + return NULL; + } + + pem = newPem; + pemSz = newSz; + } + else if (i > pemSz) { + /* Buffer full for non-streaming source - this shouldn't happen */ + break; + } + + /* Check for the expected footer OR alternate footer (for + * TRUSTED_CERT_TYPE) */ + if (i > footerSz && + XMEMCMP((char *)&pem[i-footerSz], footer, footerSz) == 0) { + foundFooter = 1; + } + else if (i > altFooterSz && altFooter != NULL && + XMEMCMP((char *)&pem[i-altFooterSz], altFooter, altFooterSz) == 0) { + foundFooter = 1; + } + + if (foundFooter) { + if (i < pemSz && wolfSSL_BIO_read(bp, (char *)&pem[i], 1) == 1) { /* attempt to read newline following footer */ i++; - if (pem[i-1] == '\r') { + if (i < pemSz && pem[i-1] == '\r') { /* found \r , Windows line ending is \r\n so try to read one * more byte for \n, ignoring return value */ - (void)wolfSSL_BIO_read(bp, (char *)&pem[i++], 1); + if (i < pemSz) { + (void)wolfSSL_BIO_read(bp, (char *)&pem[i++], 1); + } } } break; } } - if (l == 0) + if (l == 0 && i == 0) { WOLFSSL_ERROR(ASN_NO_PEM_HEADER); + } if (i > pemSz) { WOLFSSL_MSG("Error parsing PEM"); } @@ -12985,6 +13059,12 @@ static WOLFSSL_X509 *loadX509orX509REQFromPemBio(WOLFSSL_BIO *bp, CERTREQ_TYPE, cb, u); else #endif + /* Use TRUSTED_CERT_TYPE if input was TRUSTED CERTIFICATE format, + * otherwise use CERT_TYPE for regular certificates */ + if (type == TRUSTED_CERT_TYPE) + x509 = loadX509orX509REQFromBuffer(pem, pemSz, WOLFSSL_FILETYPE_PEM, + TRUSTED_CERT_TYPE, cb, u); + else x509 = loadX509orX509REQFromBuffer(pem, pemSz, WOLFSSL_FILETYPE_PEM, CERT_TYPE, cb, u); } @@ -13005,6 +13085,86 @@ static WOLFSSL_X509 *loadX509orX509REQFromPemBio(WOLFSSL_BIO *bp, } +WC_MAYBE_UNUSED +static unsigned char* ReadPemFromBioToBuffer(WOLFSSL_BIO *bp, int *pemSz) +{ + unsigned char* pem = NULL; + + WOLFSSL_ENTER("ReadPemFromBioToBuffer"); + + if (bp == NULL || pemSz == NULL) { + WOLFSSL_LEAVE("ReadPemFromBioToBuffer", BAD_FUNC_ARG); + return NULL; + } + + if ((*pemSz = wolfSSL_BIO_get_len(bp)) <= 0) { + /* No certificate size available - could be FIFO or other streaming + * source. Use MAX_X509_SIZE as initial buffer, read in loop. */ + int totalRead = 0; + int readSz; + + *pemSz = MAX_X509_SIZE; + pem = (unsigned char*)XMALLOC(*pemSz, 0, DYNAMIC_TYPE_PEM); + if (pem == NULL) { + return NULL; + } + + /* Read from streaming source until EOF or buffer full */ + while ((readSz = wolfSSL_BIO_read(bp, pem + totalRead, + *pemSz - totalRead)) > 0) { + totalRead += readSz; + + /* If buffer is full, try to grow it */ + if (totalRead >= *pemSz) { + int newSz = *pemSz * 2; + unsigned char* newPem; + + /* Sanity check */ + if (newSz > MAX_BIO_READ_BUFFER) { + WOLFSSL_MSG("PEM data too large for streaming source"); + XFREE(pem, NULL, DYNAMIC_TYPE_PEM); + return NULL; + } + + newPem = (unsigned char*)XREALLOC(pem, newSz, 0, + DYNAMIC_TYPE_PEM); + if (newPem == NULL) { + XFREE(pem, NULL, DYNAMIC_TYPE_PEM); + return NULL; + } + pem = newPem; + *pemSz = newSz; + } + } + + *pemSz = totalRead; + if (*pemSz <= 0) { + XFREE(pem, NULL, DYNAMIC_TYPE_PEM); + return NULL; + } + } + else { + /* Known size - allocate and read */ + pem = (unsigned char*)XMALLOC(*pemSz, 0, DYNAMIC_TYPE_PEM); + if (pem == NULL) { + return NULL; + } + + XMEMSET(pem, 0, *pemSz); + + *pemSz = wolfSSL_BIO_read(bp, pem, *pemSz); + if (*pemSz <= 0) { + XFREE(pem, NULL, DYNAMIC_TYPE_PEM); + return NULL; + } + } + + WOLFSSL_LEAVE("ReadPemFromBioToBuffer", 0); + return pem; +} + + + #if defined(WOLFSSL_ACERT) WOLFSSL_X509_ACERT *wolfSSL_PEM_read_bio_X509_ACERT(WOLFSSL_BIO *bp, WOLFSSL_X509_ACERT **x, @@ -13019,29 +13179,14 @@ static WOLFSSL_X509 *loadX509orX509REQFromPemBio(WOLFSSL_BIO *bp, WOLFSSL_ENTER("wolfSSL_PEM_read_bio_X509_ACERT"); if (bp == NULL) { - WOLFSSL_LEAVE("wolfSSL_PEM_read_bio_X509_ACERT", BAD_FUNC_ARG); return NULL; } - if ((pemSz = wolfSSL_BIO_get_len(bp)) <= 0) { - /* No certificate in buffer */ - WOLFSSL_ERROR(ASN_NO_PEM_HEADER); - return NULL; - } - - pem = (unsigned char*)XMALLOC(pemSz, 0, DYNAMIC_TYPE_PEM); - + pem = ReadPemFromBioToBuffer(bp, &pemSz); if (pem == NULL) { return NULL; } - XMEMSET(pem, 0, pemSz); - - if (wolfSSL_BIO_read(bp, pem, pemSz) != pemSz) { - XFREE(pem, NULL, DYNAMIC_TYPE_PEM); - return NULL; - } - x509 = wolfSSL_X509_ACERT_load_certificate_buffer(pem, pemSz, WOLFSSL_FILETYPE_PEM); @@ -13079,14 +13224,15 @@ static WOLFSSL_X509 *loadX509orX509REQFromPemBio(WOLFSSL_BIO *bp, WOLFSSL_X509 **x, wc_pem_password_cb *cb, void *u) { - WOLFSSL_ENTER("wolfSSL_PEM_read_bio_X509"); + WOLFSSL_ENTER("wolfSSL_PEM_read_bio_X509_AUX"); /* AUX info is; trusted/rejected uses, friendly name, private key id, * and potentially a stack of "other" info. wolfSSL does not store * friendly name or private key id yet in WOLFSSL_X509 for human * readability and does not support extra trusted/rejected uses for - * root CA. */ - return wolfSSL_PEM_read_bio_X509(bp, x, cb, u); + * root CA. Use TRUSTED_CERT_TYPE to properly parse TRUSTED CERTIFICATE + * format and strip auxiliary data. */ + return loadX509orX509REQFromPemBio(bp, x, cb, u, TRUSTED_CERT_TYPE); } #ifdef WOLFSSL_CERT_REQ @@ -13139,21 +13285,14 @@ WOLFSSL_X509 *wolfSSL_PEM_read_bio_X509_REQ(WOLFSSL_BIO *bp, WOLFSSL_X509 **x, { #if defined(WOLFSSL_PEM_TO_DER) && defined(HAVE_CRL) unsigned char* pem = NULL; - int pemSz; - int derSz; + int pemSz = 0; + int derSz = 0; DerBuffer* der = NULL; WOLFSSL_X509_CRL* crl = NULL; - if ((pemSz = wolfSSL_BIO_get_len(bp)) <= 0) { - goto err; - } + WOLFSSL_ENTER("wolfSSL_PEM_read_bio_X509_CRL"); - pem = (unsigned char*)XMALLOC(pemSz, 0, DYNAMIC_TYPE_PEM); - if (pem == NULL) { - goto err; - } - - if (wolfSSL_BIO_read(bp, pem, pemSz) != pemSz) { + if ((pem = ReadPemFromBioToBuffer(bp, &pemSz)) == NULL) { goto err; } @@ -13166,6 +13305,9 @@ WOLFSSL_X509 *wolfSSL_PEM_read_bio_X509_REQ(WOLFSSL_BIO *bp, WOLFSSL_X509 **x, } err: + if (pemSz == 0) { + WOLFSSL_ERROR(ASN_NO_PEM_HEADER); + } XFREE(pem, 0, DYNAMIC_TYPE_PEM); if (der != NULL) { FreeDer(&der); @@ -15393,48 +15535,39 @@ void wolfSSL_X509_email_free(WOLF_STACK_OF(WOLFSSL_STRING) *sk) wolfSSL_sk_pop_free(sk, NULL); } -static int x509_aia_append_string(WOLFSSL_STACK** head, - const byte* uri, word32 uriSz) +static int x509_aia_append_string(WOLFSSL_STACK* list, const byte* uri, + word32 uriSz) { - WOLFSSL_STACK* node; - char* url; - - url = (char*)XMALLOC(uriSz + 1, NULL, DYNAMIC_TYPE_OPENSSL); + WOLFSSL_STRING url = (WOLFSSL_STRING)XMALLOC(uriSz + 1, NULL, + DYNAMIC_TYPE_OPENSSL); if (url == NULL) - return WOLFSSL_FAILURE; - + return -1; XMEMCPY(url, uri, uriSz); url[uriSz] = '\0'; - node = wolfSSL_sk_new_node(*head != NULL ? (*head)->heap : NULL); - if (node == NULL) { + if (wolfSSL_sk_push(list, url) <= 0) { XFREE(url, NULL, DYNAMIC_TYPE_OPENSSL); - return WOLFSSL_FAILURE; + return -1; } - node->type = STACK_TYPE_STRING; - node->data.string = url; - - if (wolfSSL_sk_push_back_node(head, node) != WOLFSSL_SUCCESS) { - XFREE(url, NULL, DYNAMIC_TYPE_OPENSSL); - wolfSSL_sk_free_node(node); - return WOLFSSL_FAILURE; - } - - return WOLFSSL_SUCCESS; + return 0; } static WOLFSSL_STACK* x509_get1_aia_by_method(WOLFSSL_X509* x, word32 method, const byte* fallback, int fallbackSz) { - WOLFSSL_STACK* head = NULL; + WOLFSSL_STACK* ret = NULL; int i; if (x == NULL) return NULL; - /* Collect matching URIs from the multi-entry list into a new stack; - * fall back to the legacy single-entry field for compatibility. */ + ret = wolfSSL_sk_WOLFSSL_STRING_new(); + if (ret == NULL) + return NULL; + + /* Build from multi-entry list when available; otherwise fall back to the + * legacy single-entry fields to preserve previous behavior. */ if (x->authInfoListSz > 0) { for (i = 0; i < x->authInfoListSz; i++) { if (x->authInfoList[i].method != method || @@ -15443,22 +15576,28 @@ static WOLFSSL_STACK* x509_get1_aia_by_method(WOLFSSL_X509* x, word32 method, continue; } - if (x509_aia_append_string(&head, x->authInfoList[i].uri, - x->authInfoList[i].uriSz) != WOLFSSL_SUCCESS) { - wolfSSL_sk_pop_free(head, NULL); + if (x509_aia_append_string(ret, x->authInfoList[i].uri, + x->authInfoList[i].uriSz) != 0) { + wolfSSL_X509_email_free(ret); return NULL; } } } - if (head == NULL && fallback != NULL && fallbackSz > 0) { - if (x509_aia_append_string(&head, fallback, (word32)fallbackSz) - != WOLFSSL_SUCCESS) { - wolfSSL_sk_pop_free(head, NULL); + /* Only use fallback when nothing was found in the list */ + if (wolfSSL_sk_num(ret) == 0 && fallback != NULL && fallbackSz > 0) { + if (x509_aia_append_string(ret, fallback, (word32)fallbackSz) != 0) { + wolfSSL_X509_email_free(ret); return NULL; } } - return head; + /* Return NULL when empty */ + if (wolfSSL_sk_num(ret) == 0) { + wolfSSL_X509_email_free(ret); + ret = NULL; + } + + return ret; } WOLF_STACK_OF(WOLFSSL_STRING) *wolfSSL_X509_get1_ocsp(WOLFSSL_X509 *x) diff --git a/tests/test-maxfrag-dtls.conf b/tests/test-maxfrag-dtls.conf index 67aef1776e..f41419ed0c 100644 --- a/tests/test-maxfrag-dtls.conf +++ b/tests/test-maxfrag-dtls.conf @@ -202,14 +202,3 @@ -v 3 -l ECDHE-RSA-AES256-GCM-SHA384 -F 6 - -# server DTLSv1.2 DHE-RSA-AES256-GCM-SHA384 --u --v 3 --l DHE-RSA-AES256-GCM-SHA384 - -# client DTLSv1.2 DHE-RSA-AES256-GCM-SHA384 --u --v 3 --l DHE-RSA-AES256-GCM-SHA384 --F 6 diff --git a/tests/test-maxfrag.conf b/tests/test-maxfrag.conf index 32cdfe2047..2253ab3c58 100644 --- a/tests/test-maxfrag.conf +++ b/tests/test-maxfrag.conf @@ -169,15 +169,6 @@ -l ECDHE-RSA-AES256-GCM-SHA384 -F 6 -# server TLSv1.2 DHE-RSA-AES256-GCM-SHA384 --v 3 --l DHE-RSA-AES256-GCM-SHA384 - -# client TLSv1.2 DHE-RSA-AES256-GCM-SHA384 --v 3 --l DHE-RSA-AES256-GCM-SHA384 --F 6 - # server TLSv1.2 DHE-RSA-AES256-GCM-SHA384 -v 3 -l DHE-RSA-AES256-GCM-SHA384 diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index 3be5e33fb0..8ad0896331 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -24119,8 +24119,6 @@ static int DecodeCertInternal(DecodedCert* cert, int verify, int* criticalExt, cert->extensionsSz = (int)GetASNItem_Length( dataASN[X509CERTASN_IDX_TBS_EXT], cert->source); cert->extensionsIdx = dataASN[X509CERTASN_IDX_TBS_EXT].offset; - /* Advance past extensions. */ - cert->srcIdx = dataASN[X509CERTASN_IDX_SIGALGO_SEQ].offset; } } @@ -27452,6 +27450,27 @@ int PemToDer(const unsigned char* buff, long longSz, int type, footer = END_X509_CRL; } #endif + else if (type == CERT_TYPE + || type == CA_TYPE + || type == CHAIN_CERT_TYPE + || type == TRUSTED_PEER_TYPE) { + if (header == BEGIN_CERT) { + header = BEGIN_TRUSTED_CERT; + footer = END_TRUSTED_CERT; + } + else { + break; + } + } + else if (type == TRUSTED_CERT_TYPE) { + if (header == BEGIN_TRUSTED_CERT) { + header = BEGIN_CERT; + footer = END_CERT; + } + else { + break; + } + } else { break; } diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 49a9e06feb..1c28238059 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -20574,7 +20574,7 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_bank_test(void) #ifdef WC_DRBG_BANKREF WC_ALLOC_VAR_EX(rng, WC_RNG, 1, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER, - return WC_TEST_RET_ENC_EC(MEMORY_E)); + ERROR_OUT(WC_TEST_RET_ENC_EC(MEMORY_E), out)); XMEMSET(rng, 0, sizeof(*rng)); #endif diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 7ebe3d7b6c..c920c13b30 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -1279,16 +1279,6 @@ enum { #endif #endif /* NO_DH */ -#ifndef MAX_PSK_ID_LEN - /* max psk identity/hint supported */ - #if defined(WOLFSSL_TLS13) - /* OpenSSL has a 1472 byte session ticket */ - #define MAX_PSK_ID_LEN 1536 - #else - #define MAX_PSK_ID_LEN 128 - #endif -#endif - #ifndef MAX_PSK_KEY_LEN #define MAX_PSK_KEY_LEN 64 #endif @@ -2046,6 +2036,7 @@ WOLFSSL_LOCAL int NamedGroupIsPqcHybrid(int group); #define MAX_ENCRYPT_SZ ENCRYPT_LEN #define WOLFSSL_ASSERT_EQ(x, y) wc_static_assert((x) == (y)) +#define WOLFSSL_ASSERT_GE(x, y) wc_static_assert((x) >= (y)) #define WOLFSSL_ASSERT_SIZEOF_GE(x, y) wc_static_assert(sizeof(x) >= sizeof(y)) @@ -3463,6 +3454,11 @@ WOLFSSL_LOCAL int TLSX_AddEmptyRenegotiationInfo(TLSX** extensions, void* heap); #endif /* HAVE_SECURE_RENEGOTIATION */ #ifdef HAVE_SESSION_TICKET +/* Max peer cert size for ticket: 2KB is reasonable for most RSA/ECC certs */ +#ifndef MAX_TICKET_PEER_CERT_SZ +#define MAX_TICKET_PEER_CERT_SZ 2048 +#endif + /* Our ticket format. All members need to be a byte or array of byte to * avoid alignment issues */ typedef struct InternalTicket { @@ -3488,21 +3484,40 @@ typedef struct InternalTicket { byte sessionCtxSz; /* sessionCtx length */ byte sessionCtx[ID_LEN]; /* app specific context id */ #endif /* OPENSSL_EXTRA */ +#if defined(OPENSSL_ALL) && defined(KEEP_PEER_CERT) && \ + !defined(NO_CERT_IN_TICKET) + byte peerCertLen[OPAQUE16_LEN]; /* peer cert length */ + byte peerCert[]; /* peer certificate DER - variable length */ +#endif } InternalTicket; +/* Base size of InternalTicket without the variable-length peerCert field */ +#define WOLFSSL_INTERNAL_TICKET_BASE_SZ (sizeof(InternalTicket)) + +/* Minimum internal ticket length (no peer cert) */ #ifndef WOLFSSL_TICKET_ENC_CBC_HMAC - #define WOLFSSL_INTERNAL_TICKET_LEN sizeof(InternalTicket) + #define WOLFSSL_INTERNAL_TICKET_LEN WOLFSSL_INTERNAL_TICKET_BASE_SZ #else #define WOLFSSL_INTERNAL_TICKET_LEN \ - (((sizeof(InternalTicket) + 15) / 16) * 16) + (((WOLFSSL_INTERNAL_TICKET_BASE_SZ + 15) / 16) * 16) +#endif + +/* Maximum internal ticket length (with max peer cert) */ +#if defined(OPENSSL_ALL) && defined(KEEP_PEER_CERT) && \ + !defined(NO_CERT_IN_TICKET) + #define WOLFSSL_INTERNAL_TICKET_MAX_SZ \ + (WOLFSSL_INTERNAL_TICKET_BASE_SZ + MAX_TICKET_PEER_CERT_SZ) +#else + #define WOLFSSL_INTERNAL_TICKET_MAX_SZ WOLFSSL_INTERNAL_TICKET_BASE_SZ #endif #ifndef WOLFSSL_TICKET_EXTRA_PADDING_SZ #define WOLFSSL_TICKET_EXTRA_PADDING_SZ 32 #endif +/* Maximum encrypted ticket size */ #define WOLFSSL_TICKET_ENC_SZ \ - (sizeof(InternalTicket) + WOLFSSL_TICKET_EXTRA_PADDING_SZ) + (WOLFSSL_INTERNAL_TICKET_MAX_SZ + WOLFSSL_TICKET_EXTRA_PADDING_SZ) /* RFC 5077 defines this for session tickets. All members need to be a byte or * array of byte to avoid alignment issues */ @@ -3510,14 +3525,18 @@ typedef struct ExternalTicket { byte key_name[WOLFSSL_TICKET_NAME_SZ]; /* key context name - 16 */ byte iv[WOLFSSL_TICKET_IV_SZ]; /* this ticket's iv - 16 */ byte enc_len[OPAQUE16_LEN]; /* encrypted length - 2 */ - byte enc_ticket[WOLFSSL_TICKET_ENC_SZ]; - /* encrypted internal ticket */ - byte mac[WOLFSSL_TICKET_MAC_SZ]; /* total mac - 32 */ + byte enc_ticket[]; /* encrypted ticket - var length + * + total mac - 32 */ } ExternalTicket; -/* Cast to int to reduce amount of casts in code */ -#define SESSION_TICKET_LEN ((int)sizeof(ExternalTicket)) -#define WOLFSSL_TICKET_FIXED_SZ (SESSION_TICKET_LEN - WOLFSSL_TICKET_ENC_SZ) +/* Fixed portion of external ticket (key_name + iv + enc_len) */ +#define WOLFSSL_TICKET_FIXED_SZ \ + (WOLFSSL_TICKET_NAME_SZ + WOLFSSL_TICKET_IV_SZ + OPAQUE16_LEN + \ + WOLFSSL_TICKET_MAC_SZ) + +/* Maximum session ticket length */ +#define SESSION_TICKET_LEN \ + ((int)(WOLFSSL_TICKET_FIXED_SZ + WOLFSSL_TICKET_ENC_SZ)) typedef struct SessionTicket { word32 lifetime; @@ -3559,6 +3578,20 @@ WOLFSSL_LOCAL void TLSX_SessionTicket_Free(SessionTicket* ticket, void* heap); #endif /* HAVE_SESSION_TICKET */ +#ifndef MAX_PSK_ID_LEN + /* max psk identity/hint supported */ + #if defined(WOLFSSL_TLS13) + #ifdef SESSION_TICKET_LEN + #define MAX_PSK_ID_LEN SESSION_TICKET_LEN + #else + /* Previous value. Use as fallback for when tickets are disabled. */ + #define MAX_PSK_ID_LEN 1536 + #endif + #else + #define MAX_PSK_ID_LEN 128 + #endif +#endif + #if defined(HAVE_ENCRYPT_THEN_MAC) && !defined(WOLFSSL_AEAD_ONLY) int TLSX_EncryptThenMac_Respond(WOLFSSL* ssl); #endif @@ -6411,12 +6444,20 @@ struct SystemCryptoPolicy { * for the caller to find so we clear the last error. */ #if defined(OPENSSL_EXTRA) && defined(WOLFSSL_HAVE_ERROR_QUEUE) -#define CLEAR_ASN_NO_PEM_HEADER_ERROR(err) \ - (err) = wolfSSL_ERR_peek_last_error(); \ - if (wolfSSL_ERR_GET_LIB(err) == WOLFSSL_ERR_LIB_PEM && \ - wolfSSL_ERR_GET_REASON(err) == -WOLFSSL_PEM_R_NO_START_LINE_E) { \ - wc_RemoveErrorNode(-1); \ - } +#define CLEAR_ASN_NO_PEM_HEADER_ERROR(err) \ +do { \ + (err) = wolfSSL_ERR_peek_last_error(); \ + if (wolfSSL_ERR_GET_LIB(err) == WOLFSSL_ERR_LIB_PEM && \ + wolfSSL_ERR_GET_REASON(err) == -WOLFSSL_PEM_R_NO_START_LINE_E) { \ + unsigned long peekErr; \ + do { \ + wc_RemoveErrorNode(-1); \ + peekErr = wolfSSL_ERR_peek_last_error(); \ + } while (wolfSSL_ERR_GET_LIB(peekErr) == WOLFSSL_ERR_LIB_PEM && \ + wolfSSL_ERR_GET_REASON(peekErr) == \ + -WOLFSSL_PEM_R_NO_START_LINE_E); \ + } \ +} while(0) #else #define CLEAR_ASN_NO_PEM_HEADER_ERROR(err) (void)(err); #endif diff --git a/wolfssl/openssl/ssl.h b/wolfssl/openssl/ssl.h index 2222430839..6676c26d62 100644 --- a/wolfssl/openssl/ssl.h +++ b/wolfssl/openssl/ssl.h @@ -632,16 +632,18 @@ typedef STACK_OF(ACCESS_DESCRIPTION) AUTHORITY_INFO_ACCESS; #define sk_X509_push wolfSSL_sk_X509_push #define sk_X509_pop wolfSSL_sk_X509_pop #define sk_X509_pop_free wolfSSL_sk_X509_pop_free -#define sk_X509_dup wolfSSL_sk_dup +#define sk_X509_dup wolfSSL_shallow_sk_dup #define sk_X509_free wolfSSL_sk_X509_free #define X509_chain_up_ref wolfSSL_X509_chain_up_ref #define sk_X509_CRL_new wolfSSL_sk_X509_CRL_new +#define sk_X509_CRL_new_null wolfSSL_sk_X509_CRL_new_null #define sk_X509_CRL_pop_free wolfSSL_sk_X509_CRL_pop_free #define sk_X509_CRL_free wolfSSL_sk_X509_CRL_free #define sk_X509_CRL_push wolfSSL_sk_X509_CRL_push #define sk_X509_CRL_value wolfSSL_sk_X509_CRL_value #define sk_X509_CRL_num wolfSSL_sk_X509_CRL_num +#define sk_X509_CRL_dup wolfSSL_shallow_sk_dup #define sk_X509_OBJECT_new wolfSSL_sk_X509_OBJECT_new #define sk_X509_OBJECT_free wolfSSL_sk_X509_OBJECT_free @@ -824,6 +826,7 @@ wolfSSL_X509_STORE_set_verify_cb((WOLFSSL_X509_STORE *)(s), (WOLFSSL_X509_STORE_ #define X509_CRL_new wolfSSL_X509_CRL_new #define X509_CRL_dup wolfSSL_X509_CRL_dup +#define X509_CRL_up_ref wolfSSL_X509_CRL_up_ref #define X509_CRL_free wolfSSL_X509_CRL_free #define X509_CRL_sign wolfSSL_X509_CRL_sign #define X509_CRL_get_lastUpdate wolfSSL_X509_CRL_get_lastUpdate @@ -1337,7 +1340,7 @@ typedef WOLFSSL_SRTP_PROTECTION_PROFILE SRTP_PROTECTION_PROFILE; #define sk_SSL_COMP_zero wolfSSL_sk_SSL_COMP_zero #define sk_SSL_CIPHER_value wolfSSL_sk_SSL_CIPHER_value #endif /* OPENSSL_ALL || WOLFSSL_HAPROXY */ -#define sk_SSL_CIPHER_dup wolfSSL_sk_dup +#define sk_SSL_CIPHER_dup wolfSSL_shallow_sk_dup #define sk_SSL_CIPHER_free wolfSSL_sk_SSL_CIPHER_free #define sk_SSL_CIPHER_find wolfSSL_sk_SSL_CIPHER_find diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index 8dd8acde1c..2fdbfa4a94 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -1952,6 +1952,7 @@ WOLFSSL_API WOLFSSL_X509* wolfSSL_sk_X509_pop(WOLF_STACK_OF(WOLFSSL_X509)* sk); WOLFSSL_API void wolfSSL_sk_X509_free(WOLF_STACK_OF(WOLFSSL_X509)* sk); WOLFSSL_API WOLFSSL_STACK* wolfSSL_sk_X509_CRL_new(void); +WOLFSSL_API WOLFSSL_STACK* wolfSSL_sk_X509_CRL_new_null(void); WOLFSSL_API void wolfSSL_sk_X509_CRL_pop_free(WOLF_STACK_OF(WOLFSSL_X509_CRL)* sk, void (*f) (WOLFSSL_X509_CRL*)); WOLFSSL_API void wolfSSL_sk_X509_CRL_free(WOLF_STACK_OF(WOLFSSL_X509_CRL)* sk); @@ -3517,6 +3518,7 @@ WOLFSSL_API int wolfSSL_X509_REVOKED_get_serial_number(RevokedCert* rev, #endif #if defined(HAVE_CRL) && (defined(OPENSSL_EXTRA) || defined(WOLFSSL_WPAS_SMALL)) WOLFSSL_API WOLFSSL_X509_CRL* wolfSSL_X509_CRL_dup(const WOLFSSL_X509_CRL* crl); +WOLFSSL_API int wolfSSL_X509_CRL_up_ref(WOLFSSL_X509_CRL* crl); WOLFSSL_API void wolfSSL_X509_CRL_free(WOLFSSL_X509_CRL *crl); #endif #if defined(HAVE_CRL) && defined(OPENSSL_EXTRA) diff --git a/wolfssl/wolfcrypt/wc_port.h b/wolfssl/wolfcrypt/wc_port.h index 76cb11b5b1..adff8eb929 100644 --- a/wolfssl/wolfcrypt/wc_port.h +++ b/wolfssl/wolfcrypt/wc_port.h @@ -104,7 +104,7 @@ #else #define WC_DEPRECATED(msg) /* null expansion */ #endif -#endif /* !WC_MAYBE_UNUSED */ +#endif /* !WC_DEPRECATED */ /* use inlining if compiler allows */ #ifndef WC_INLINE From e9a2f27b2cb68b2b5421af1e0d23120171e84608 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Wed, 25 Feb 2026 15:35:36 +0100 Subject: [PATCH 22/36] Address peer review --- .github/workflows/nginx.yml | 3 +- src/internal.c | 87 +++++++++++++++++++------------------ src/ssl.c | 9 ++-- src/ssl_api_cert.c | 7 ++- src/x509.c | 2 + 5 files changed, 56 insertions(+), 52 deletions(-) diff --git a/.github/workflows/nginx.yml b/.github/workflows/nginx.yml index b95b6e89c7..d457e111c2 100644 --- a/.github/workflows/nginx.yml +++ b/.github/workflows/nginx.yml @@ -246,8 +246,7 @@ jobs: ./auto/configure --with-wolfssl=$GITHUB_WORKSPACE/build-dir --with-http_ssl_module \ --with-stream --with-stream_ssl_module --with-stream_ssl_preread_module \ --with-http_v2_module --with-mail --with-mail_ssl_module \ - --with-cc-opt='-fsanitize=address -DNGX_DEBUG_PALLOC=1 -g3 \ - ${{ env.nginx_c_flags }}' \ + --with-cc-opt='-fsanitize=address -DNGX_DEBUG_PALLOC=1 -g3 ${{ env.nginx_c_flags }}' \ --with-ld-opt='-fsanitize=address ${{ env.nginx_c_flags }}' make -j diff --git a/src/internal.c b/src/internal.c index 0d3468d8e1..8eadd585e5 100644 --- a/src/internal.c +++ b/src/internal.c @@ -39034,12 +39034,10 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) } #endif - /* Calculate actual internal ticket size */ + internalTicketSz = (int)WOLFSSL_INTERNAL_TICKET_BASE_SZ; #if defined(OPENSSL_ALL) && defined(KEEP_PEER_CERT) && \ !defined(NO_CERT_IN_TICKET) - internalTicketSz = (int)(WOLFSSL_INTERNAL_TICKET_BASE_SZ + peerCertSz); -#else - internalTicketSz = (int)WOLFSSL_INTERNAL_TICKET_BASE_SZ; + internalTicketSz += peerCertSz; #endif /* MAC is placed after the encrypted data */ mac = et->enc_ticket + WOLFSSL_TICKET_ENC_SZ; @@ -39326,6 +39324,48 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) } #endif /* WOLFSSL_SLT13 */ +#if defined(OPENSSL_ALL) && defined(KEEP_PEER_CERT) && \ + !defined(NO_CERT_IN_TICKET) + static void RestorePeerCertFromTicket(WOLFSSL* ssl, InternalTicket* it) + { + word16 peerCertLen = 0; + ato16(it->peerCertLen, &peerCertLen); + + if (peerCertLen > 0 && peerCertLen <= MAX_TICKET_PEER_CERT_SZ) { +#ifdef SESSION_CERTS + /* Clear existing chain and add the peer certificate */ + ssl->session->chain.count = 0; + AddSessionCertToChain(&ssl->session->chain, + it->peerCert, peerCertLen); +#endif + /* Also decode into ssl->peerCert for direct access */ + { + int ret; + DecodedCert* dCert; + + dCert = (DecodedCert*)XMALLOC(sizeof(DecodedCert), ssl->heap, + DYNAMIC_TYPE_DCERT); + if (dCert != NULL) { + InitDecodedCert(dCert, it->peerCert, peerCertLen, ssl->heap); + ret = ParseCertRelative(dCert, CERT_TYPE, 0, NULL, NULL); + if (ret == 0) { + FreeX509(&ssl->peerCert); + InitX509(&ssl->peerCert, 0, ssl->heap); + ret = CopyDecodedToX509(&ssl->peerCert, dCert); + if (ret != 0) { + /* Failed to copy - clear peerCert */ + FreeX509(&ssl->peerCert); + InitX509(&ssl->peerCert, 0, ssl->heap); + } + } + FreeDecodedCert(dCert); + XFREE(dCert, ssl->heap, DYNAMIC_TYPE_DCERT); + } + } + } + } +#endif /* OPENSSL_ALL && KEEP_PEER_CERT && !NO_CERT_IN_TICKET */ + void DoClientTicketFinalize(WOLFSSL* ssl, InternalTicket* it, const WOLFSSL_SESSION* sess) { @@ -39416,44 +39456,7 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) #if defined(OPENSSL_ALL) && defined(KEEP_PEER_CERT) && \ !defined(NO_CERT_IN_TICKET) - /* Restore peer certificate from ticket to session chain and peerCert */ - { - word16 peerCertLen = 0; - ato16(it->peerCertLen, &peerCertLen); - - if (peerCertLen > 0 && peerCertLen <= MAX_TICKET_PEER_CERT_SZ) { -#ifdef SESSION_CERTS - /* Clear existing chain and add the peer certificate */ - ssl->session->chain.count = 0; - AddSessionCertToChain(&ssl->session->chain, - it->peerCert, peerCertLen); -#endif - /* Also decode into ssl->peerCert for direct access */ - { - int ret; - DecodedCert* dCert; - - dCert = (DecodedCert*)XMALLOC(sizeof(DecodedCert), ssl->heap, - DYNAMIC_TYPE_DCERT); - if (dCert != NULL) { - InitDecodedCert(dCert, it->peerCert, peerCertLen, ssl->heap); - ret = ParseCertRelative(dCert, CERT_TYPE, 0, NULL, NULL); - if (ret == 0) { - FreeX509(&ssl->peerCert); - InitX509(&ssl->peerCert, 0, ssl->heap); - ret = CopyDecodedToX509(&ssl->peerCert, dCert); - if (ret != 0) { - /* Failed to copy - clear peerCert */ - FreeX509(&ssl->peerCert); - InitX509(&ssl->peerCert, 0, ssl->heap); - } - } - FreeDecodedCert(dCert); - XFREE(dCert, ssl->heap, DYNAMIC_TYPE_DCERT); - } - } - } - } + RestorePeerCertFromTicket(ssl, it); #endif ssl->version.minor = it->pv.minor; diff --git a/src/ssl.c b/src/ssl.c index 72f78c1b32..47056d8770 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -15508,11 +15508,10 @@ WOLFSSL_CTX* wolfSSL_set_SSL_CTX(WOLFSSL* ssl, WOLFSSL_CTX* ctx) #endif #ifndef WOLFSSL_BLIND_PRIVATE_KEY #ifdef WOLFSSL_COPY_KEY + if (ssl->buffers.key != NULL && ssl->buffers.weOwnKey) { + FreeDer(&ssl->buffers.key); + } if (ctx->privateKey != NULL) { - if (ssl->buffers.key != NULL) { - FreeDer(&ssl->buffers.key); - ssl->buffers.key = NULL; - } ret = AllocCopyDer(&ssl->buffers.key, ctx->privateKey->buffer, ctx->privateKey->length, ctx->privateKey->type, ctx->privateKey->heap); @@ -15523,8 +15522,6 @@ WOLFSSL_CTX* wolfSSL_set_SSL_CTX(WOLFSSL* ssl, WOLFSSL_CTX* ctx) ssl->buffers.weOwnKey = 1; } else { - if (ssl->buffers.key != NULL && ssl->buffers.weOwnKey) - FreeDer(&ssl->buffers.key); ssl->buffers.key = ctx->privateKey; } #else diff --git a/src/ssl_api_cert.c b/src/ssl_api_cert.c index 231bf0c385..f54edcd8e4 100644 --- a/src/ssl_api_cert.c +++ b/src/ssl_api_cert.c @@ -881,8 +881,11 @@ int wolfSSL_UnloadCertsKeys(WOLFSSL* ssl) #ifdef WOLFSSL_DUAL_ALG_CERTS if (ssl->buffers.weOwnAltKey) { WOLFSSL_MSG("Unloading alt key"); - if (ssl->buffers.altKey != NULL && ssl->buffers.altKey->buffer != NULL) - ForceZero(ssl->buffers.altKey->buffer, ssl->buffers.altKey->length); + if (ssl->buffers.altKey != NULL && + ssl->buffers.altKey->buffer != NULL) { + ForceZero(ssl->buffers.altKey->buffer, + ssl->buffers.altKey->length); + } FreeDer(&ssl->buffers.altKey); #ifdef WOLFSSL_BLIND_PRIVATE_KEY FreeDer(&ssl->buffers.altKeyMask); diff --git a/src/x509.c b/src/x509.c index 843166b235..fd46e89c40 100644 --- a/src/x509.c +++ b/src/x509.c @@ -38,6 +38,8 @@ #include #endif +/* 16 times MAX_X509_SIZE should be more than enough to read any X509 + * certificate file */ #define MAX_BIO_READ_BUFFER (MAX_X509_SIZE * 16) #if defined(OPENSSL_ALL) || defined(OPENSSL_EXTRA) From 67de2349da968f915c72c929b61a5d1bb76195e4 Mon Sep 17 00:00:00 2001 From: Eric Blankenhorn Date: Tue, 24 Feb 2026 07:39:17 -0600 Subject: [PATCH 23/36] Add sanity checks in key export --- src/ssl.c | 7 +++++++ src/tls13.c | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/src/ssl.c b/src/ssl.c index 10fd73ca60..ba03ed4418 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -5726,6 +5726,13 @@ int wolfSSL_export_keying_material(WOLFSSL *ssl, return WOLFSSL_FAILURE; } + /* Sanity check contextLen to prevent integer overflow when cast to word32 + * and to ensure it fits in the 2-byte length encoding (max 65535). */ + if (use_context && contextLen > UINT16_MAX) { + WOLFSSL_MSG("contextLen too large"); + return WOLFSSL_FAILURE; + } + /* clientRandom + serverRandom * OR * clientRandom + serverRandom + ctx len encoding + ctx */ diff --git a/src/tls13.c b/src/tls13.c index 101b31541a..c1d31c8187 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -1023,6 +1023,11 @@ int Tls13_Exporter(WOLFSSL* ssl, unsigned char *out, size_t outLen, if (ret != 0) return ret; + /* Sanity check contextLen to prevent truncation when cast to word32. */ + if (contextLen > UINT32_MAX) { + return BAD_FUNC_ARG; + } + /* Hash(context_value) */ ret = wc_Hash(hashType, context, (word32)contextLen, hashOut, WC_MAX_DIGEST_SIZE); if (ret != 0) From 4f8f11bcba81ceee92ae400c82862ca9a91744aa Mon Sep 17 00:00:00 2001 From: Eric Blankenhorn Date: Tue, 24 Feb 2026 07:50:44 -0600 Subject: [PATCH 24/36] Add test case --- tests/api.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/api.c b/tests/api.c index 2cdd81c79b..4511b8d37d 100644 --- a/tests/api.c +++ b/tests/api.c @@ -24070,6 +24070,11 @@ static int test_export_keying_material_cb(WOLFSSL_CTX *ctx, WOLFSSL *ssl) NULL, 0, 0), 0); ExpectIntEQ(wolfSSL_export_keying_material(ssl, ekm, sizeof(ekm), "key expansion", XSTR_SIZEOF("key expansion"), NULL, 0, 0), 0); + /* contextLen overflow: values exceeding UINT16_MAX must be rejected to + * prevent integer overflow in seedLen calculation (ZD #21242). */ + ExpectIntEQ(wolfSSL_export_keying_material(ssl, ekm, sizeof(ekm), + "Test label", XSTR_SIZEOF("Test label"), ekm, + (size_t)UINT16_MAX + 1, 1), 0); return EXPECT_RESULT(); } From 41ebc92fa5f58aaadd745c2c6cd76eadba610f4e Mon Sep 17 00:00:00 2001 From: Eric Blankenhorn Date: Tue, 24 Feb 2026 08:02:26 -0600 Subject: [PATCH 25/36] Replace macros from stdint.h with literals to make code more generic --- src/ssl.c | 2 +- src/tls13.c | 2 +- tests/api.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ssl.c b/src/ssl.c index ba03ed4418..92789e7b3a 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -5728,7 +5728,7 @@ int wolfSSL_export_keying_material(WOLFSSL *ssl, /* Sanity check contextLen to prevent integer overflow when cast to word32 * and to ensure it fits in the 2-byte length encoding (max 65535). */ - if (use_context && contextLen > UINT16_MAX) { + if (use_context && contextLen > 0xFFFF) { WOLFSSL_MSG("contextLen too large"); return WOLFSSL_FAILURE; } diff --git a/src/tls13.c b/src/tls13.c index c1d31c8187..d9e5aebca8 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -1024,7 +1024,7 @@ int Tls13_Exporter(WOLFSSL* ssl, unsigned char *out, size_t outLen, return ret; /* Sanity check contextLen to prevent truncation when cast to word32. */ - if (contextLen > UINT32_MAX) { + if (contextLen > 0xFFFFFFFFU) { return BAD_FUNC_ARG; } diff --git a/tests/api.c b/tests/api.c index 4511b8d37d..d8b0677007 100644 --- a/tests/api.c +++ b/tests/api.c @@ -24074,7 +24074,7 @@ static int test_export_keying_material_cb(WOLFSSL_CTX *ctx, WOLFSSL *ssl) * prevent integer overflow in seedLen calculation (ZD #21242). */ ExpectIntEQ(wolfSSL_export_keying_material(ssl, ekm, sizeof(ekm), "Test label", XSTR_SIZEOF("Test label"), ekm, - (size_t)UINT16_MAX + 1, 1), 0); + (size_t)0xFFFF + 1, 1), 0); return EXPECT_RESULT(); } From 75b0808fe5886585756cd336e7f873f0130a3073 Mon Sep 17 00:00:00 2001 From: Eric Blankenhorn Date: Wed, 25 Feb 2026 09:02:55 -0600 Subject: [PATCH 26/36] Update from review --- src/ssl.c | 2 +- src/tls13.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ssl.c b/src/ssl.c index 92789e7b3a..869af20011 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -5728,7 +5728,7 @@ int wolfSSL_export_keying_material(WOLFSSL *ssl, /* Sanity check contextLen to prevent integer overflow when cast to word32 * and to ensure it fits in the 2-byte length encoding (max 65535). */ - if (use_context && contextLen > 0xFFFF) { + if (use_context && contextLen > WOLFSSL_MAX_16BIT) { WOLFSSL_MSG("contextLen too large"); return WOLFSSL_FAILURE; } diff --git a/src/tls13.c b/src/tls13.c index d9e5aebca8..025819bd9f 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -1024,7 +1024,7 @@ int Tls13_Exporter(WOLFSSL* ssl, unsigned char *out, size_t outLen, return ret; /* Sanity check contextLen to prevent truncation when cast to word32. */ - if (contextLen > 0xFFFFFFFFU) { + if (contextLen > WOLFSSL_MAX_32BIT) { return BAD_FUNC_ARG; } From ef325bbed895569d2ad8bb85162cc6e7316b4688 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Mon, 23 Feb 2026 12:43:07 +0100 Subject: [PATCH 27/36] Changes for socat 1.8.0.3 --- .github/workflows/socat.yml | 20 ++++++++++++++------ wolfssl/openssl/ssl.h | 2 ++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/workflows/socat.yml b/.github/workflows/socat.yml index 1484027ece..89e4fcc788 100644 --- a/.github/workflows/socat.yml +++ b/.github/workflows/socat.yml @@ -23,7 +23,7 @@ jobs: uses: wolfSSL/actions-build-autotools-project@v1 with: path: wolfssl - configure: --enable-maxfragment --enable-opensslall --enable-opensslextra --enable-dtls --enable-oldtls --enable-tlsv10 --enable-ipv6 'CPPFLAGS=-DWOLFSSL_NO_DTLS_SIZE_CHECK -DOPENSSL_COMPATIBLE_DEFAULTS' + configure: --enable-all --enable-oldtls --enable-tlsv10 --enable-ipv6 'CPPFLAGS=-DWOLFSSL_NO_DTLS_SIZE_CHECK -DOPENSSL_COMPATIBLE_DEFAULTS' install: true - name: tar build-dir @@ -43,6 +43,14 @@ jobs: # This should be a safe limit for the tests to run. timeout-minutes: 30 needs: build_wolfssl + strategy: + fail-fast: false + matrix: + include: + - socat_version: "1.8.0.0" + expect_fail: "36,64,146,216,309,310,386,399,402,403,459,460,467,468,475,478,491,492,528" + - socat_version: "1.8.0.3" + expect_fail: "146,386,399,402,459,460,467,468,475,478,491,492,495,528" steps: - name: Install prereqs run: @@ -57,7 +65,7 @@ jobs: run: tar -xf build-dir.tgz - name: Download socat - run: curl -O http://www.dest-unreach.org/socat/download/socat-1.8.0.0.tar.gz && tar xvf socat-1.8.0.0.tar.gz + run: curl -O http://www.dest-unreach.org/socat/download/socat-${{ matrix.socat_version }}.tar.gz && tar xvf socat-${{ matrix.socat_version }}.tar.gz - name: Checkout OSP uses: actions/checkout@v4 @@ -66,16 +74,16 @@ jobs: path: osp - name: Build socat - working-directory: ./socat-1.8.0.0 + working-directory: ./socat-${{ matrix.socat_version }} run: | - patch -p1 < ../osp/socat/1.8.0.0/socat-1.8.0.0.patch + patch -p1 < ../osp/socat/${{ matrix.socat_version }}/socat-${{ matrix.socat_version }}.patch autoreconf -vfi ./configure --with-wolfssl=$GITHUB_WORKSPACE/build-dir --enable-default-ipv=4 make - name: Run socat tests - working-directory: ./socat-1.8.0.0 + working-directory: ./socat-${{ matrix.socat_version }} run: | export LD_LIBRARY_PATH=$GITHUB_WORKSPACE/build-dir/lib:$LD_LIBRARY_PATH export SHELL=/bin/bash - SOCAT=$GITHUB_WORKSPACE/socat-1.8.0.0/socat ./test.sh -t 0.5 --expect-fail 36,64,146,214,216,217,309,310,386,399,402,403,459,460,467,468,475,478,492,528,530 + SOCAT=$GITHUB_WORKSPACE/socat-${{ matrix.socat_version }}/socat ./test.sh -t 0.5 --expect-fail ${{ matrix.expect_fail }} diff --git a/wolfssl/openssl/ssl.h b/wolfssl/openssl/ssl.h index 2222430839..26b0a627a1 100644 --- a/wolfssl/openssl/ssl.h +++ b/wolfssl/openssl/ssl.h @@ -1333,6 +1333,8 @@ typedef WOLFSSL_SRTP_PROTECTION_PROFILE SRTP_PROTECTION_PROFILE; #define SSL_SESSION_get_id wolfSSL_SESSION_get_id #define SSL_get_cipher_bits(s,np) \ wolfSSL_CIPHER_get_bits(SSL_get_current_cipher(s),np) +#define SSL_get_cipher_version(s) \ + wolfSSL_CIPHER_get_version(SSL_get_current_cipher(s)) #define sk_SSL_CIPHER_num wolfSSL_sk_SSL_CIPHER_num #define sk_SSL_COMP_zero wolfSSL_sk_SSL_COMP_zero #define sk_SSL_CIPHER_value wolfSSL_sk_SSL_CIPHER_value From 5c38f440fada1635bc032f3ad3ae0f2244861053 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 20 Feb 2026 11:40:00 +0100 Subject: [PATCH 28/36] Add msmtp action Depends on https://github.com/wolfSSL/osp/pull/317 --- .github/workflows/msmtp.yml | 108 ++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 .github/workflows/msmtp.yml diff --git a/.github/workflows/msmtp.yml b/.github/workflows/msmtp.yml new file mode 100644 index 0000000000..2b1fa7885c --- /dev/null +++ b/.github/workflows/msmtp.yml @@ -0,0 +1,108 @@ +name: msmtp Tests + +# START OF COMMON SECTION +on: + push: + branches: [ 'master', 'main', 'release/**' ] + pull_request: + branches: [ '*' ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +# END OF COMMON SECTION + +jobs: + build_wolfssl: + name: Build wolfSSL + # Just to keep it the same as the testing target + if: github.repository_owner == 'wolfssl' + runs-on: ubuntu-24.04 + # This should be a safe limit for the tests to run. + timeout-minutes: 4 + steps: + - name: Build wolfSSL + uses: wolfSSL/actions-build-autotools-project@v1 + with: + path: wolfssl + configure: --enable-opensslextra --enable-opensslall + install: true + + - name: tar build-dir + run: tar -zcf build-dir.tgz build-dir + + - name: Upload built lib + uses: actions/upload-artifact@v4 + with: + name: wolf-install-msmtp + path: build-dir.tgz + retention-days: 5 + + msmtp_check: + strategy: + fail-fast: false + matrix: + ref: [ 1.8.28 ] + name: ${{ matrix.ref }} + if: github.repository_owner == 'wolfssl' + runs-on: ubuntu-24.04 + # This should be a safe limit for the tests to run. + timeout-minutes: 10 + needs: build_wolfssl + steps: + - name: Download lib + uses: actions/download-artifact@v4 + with: + name: wolf-install-msmtp + + - name: untar build-dir + run: tar -xf build-dir.tgz + + - name: Checkout OSP + uses: actions/checkout@v4 + with: + repository: wolfssl/osp + path: osp + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + autoconf automake libtool pkg-config gettext \ + libidn2-dev libsecret-1-dev autopoint + + - name: Checkout msmtp + uses: actions/checkout@v4 + with: + repository: marlam/msmtp + ref: msmtp-${{ matrix.ref }} + path: msmtp-${{ matrix.ref }} + + - name: Apply wolfSSL patch + working-directory: msmtp-${{ matrix.ref }} + run: patch -p1 < $GITHUB_WORKSPACE/osp/msmtp/${{ matrix.ref }}/wolfssl-msmtp-${{ matrix.ref }}.patch + + - name: Regenerate build system + working-directory: msmtp-${{ matrix.ref }} + run: autoreconf -ivf + + - name: Configure msmtp with wolfSSL + working-directory: msmtp-${{ matrix.ref }} + run: | + PKG_CONFIG_PATH=$GITHUB_WORKSPACE/build-dir/lib/pkgconfig \ + ./configure --with-tls=wolfssl + + - name: Build msmtp + working-directory: msmtp-${{ matrix.ref }} + run: make -j$(nproc) + + - name: Run msmtp tests + working-directory: msmtp-${{ matrix.ref }} + run: LD_LIBRARY_PATH=$GITHUB_WORKSPACE/build-dir/lib make check + + - name: Confirm msmtp built with wolfSSL + run: ldd msmtp-${{ matrix.ref }}/src/msmtp | grep wolfssl + + - name: Print test logs on failure + if: ${{ failure() }} + run: tail -n +1 msmtp-${{ matrix.ref }}/tests/*.log From 110f5cb4427de0471b4bccaca29a29823aa6fa8e Mon Sep 17 00:00:00 2001 From: Andrew Hutchings Date: Thu, 26 Feb 2026 14:09:01 +0000 Subject: [PATCH 29/36] Fix ECH error code: use BUFFER_ERROR for malformed peer input Change innerClientHelloLen underflow guard in TLSX_ECH_Parse from BAD_FUNC_ARG to BUFFER_ERROR to match the convention used throughout tls.c for wire-protocol length/bounds validation. --- src/tls.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tls.c b/src/tls.c index a4f83e0867..d0f7c9ea2a 100644 --- a/src/tls.c +++ b/src/tls.c @@ -13606,7 +13606,7 @@ static int TLSX_ECH_Parse(WOLFSSL* ssl, const byte* readBuf, word16 size, /* read hello inner len */ ato16(readBuf_p, &ech->innerClientHelloLen); if (ech->innerClientHelloLen < WC_AES_BLOCK_SIZE) { - return BAD_FUNC_ARG; + return BUFFER_ERROR; } ech->innerClientHelloLen -= WC_AES_BLOCK_SIZE; readBuf_p += 2; From fc0ec06e7266c39989781f38367be891181178a1 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Mon, 23 Feb 2026 16:52:43 +0100 Subject: [PATCH 30/36] sssd 2.10.2 changes --- .github/workflows/sssd.yml | 5 +++-- src/pk_ec.c | 31 +++++++++++++++++++++++++++++++ wolfssl/openssl/ec.h | 4 ++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sssd.yml b/.github/workflows/sssd.yml index b160bf29d7..797a1f4fbf 100644 --- a/.github/workflows/sssd.yml +++ b/.github/workflows/sssd.yml @@ -44,7 +44,7 @@ jobs: fail-fast: false matrix: # List of releases to test - ref: [ 2.9.1 ] + ref: [ 2.9.1, 2.10.2 ] name: ${{ matrix.ref }} if: github.repository_owner == 'wolfssl' runs-on: ubuntu-24.04 @@ -61,7 +61,8 @@ jobs: # Don't prompt for anything export DEBIAN_FRONTEND=noninteractive sudo apt-get update - sudo apt-get install -y build-essential autoconf libldb-dev libldb2 python3-ldb bc + sudo apt-get install -y build-essential autoconf libldb-dev \ + libldb2 python3-ldb bc libcap-dev - name: Setup env run: | diff --git a/src/pk_ec.c b/src/pk_ec.c index e0300cd0a8..0c66482db6 100644 --- a/src/pk_ec.c +++ b/src/pk_ec.c @@ -2824,6 +2824,37 @@ int wolfSSL_EC_POINT_copy(WOLFSSL_EC_POINT *dest, const WOLFSSL_EC_POINT *src) return ret; } +/* Duplicates an EC point. + * + * @param [in] src EC point to duplicate. + * @param [in] group EC group for the new point. + * @return New EC point on success. + * @return NULL on failure. + */ +WOLFSSL_EC_POINT *wolfSSL_EC_POINT_dup(const WOLFSSL_EC_POINT *src, + const WOLFSSL_EC_GROUP *group) +{ + WOLFSSL_EC_POINT *dest; + + WOLFSSL_ENTER("wolfSSL_EC_POINT_dup"); + + if ((src == NULL) || (group == NULL)) { + return NULL; + } + + dest = wolfSSL_EC_POINT_new(group); + if (dest == NULL) { + return NULL; + } + + if (wolfSSL_EC_POINT_copy(dest, src) != 1) { + wolfSSL_EC_POINT_free(dest); + return NULL; + } + + return dest; +} + /* Checks whether point is at infinity. * * Return code compliant with OpenSSL. diff --git a/wolfssl/openssl/ec.h b/wolfssl/openssl/ec.h index 80674e1a0b..c706761e08 100644 --- a/wolfssl/openssl/ec.h +++ b/wolfssl/openssl/ec.h @@ -379,6 +379,9 @@ int wolfSSL_EC_POINT_cmp(const WOLFSSL_EC_GROUP *group, WOLFSSL_API int wolfSSL_EC_POINT_copy(WOLFSSL_EC_POINT *dest, const WOLFSSL_EC_POINT *src); WOLFSSL_API +WOLFSSL_EC_POINT *wolfSSL_EC_POINT_dup(const WOLFSSL_EC_POINT *src, + const WOLFSSL_EC_GROUP *group); +WOLFSSL_API void wolfSSL_EC_POINT_free(WOLFSSL_EC_POINT *point); WOLFSSL_API int wolfSSL_EC_POINT_is_at_infinity(const WOLFSSL_EC_GROUP *group, @@ -479,6 +482,7 @@ typedef WOLFSSL_EC_KEY_METHOD EC_KEY_METHOD; #define EC_POINT_clear_free wolfSSL_EC_POINT_clear_free #define EC_POINT_cmp wolfSSL_EC_POINT_cmp #define EC_POINT_copy wolfSSL_EC_POINT_copy +#define EC_POINT_dup wolfSSL_EC_POINT_dup #define EC_POINT_is_at_infinity wolfSSL_EC_POINT_is_at_infinity #define EC_get_builtin_curves wolfSSL_EC_get_builtin_curves From fe85ca643a11d751dad7ec7e0c8d6b731fbf5eb3 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Thu, 26 Feb 2026 15:18:24 +0100 Subject: [PATCH 31/36] Add test for EC_POINT_dup --- tests/api/test_ossl_ec.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/api/test_ossl_ec.c b/tests/api/test_ossl_ec.c index c715f343b7..7f788984cd 100644 --- a/tests/api/test_ossl_ec.c +++ b/tests/api/test_ossl_ec.c @@ -314,6 +314,7 @@ int test_wolfSSL_EC_POINT(void) EC_POINT* set_point = NULL; EC_POINT* get_point = NULL; EC_POINT* infinity = NULL; + EC_POINT* dup_point = NULL; BIGNUM* k = NULL; BIGNUM* Gx = NULL; BIGNUM* Gy = NULL; @@ -507,6 +508,12 @@ int test_wolfSSL_EC_POINT(void) ExpectIntEQ(EC_POINT_copy(new_point, NULL), 0); ExpectIntEQ(EC_POINT_copy(new_point, set_point), 1); + /* Test duplicating */ + ExpectNull(EC_POINT_dup(NULL, group)); + ExpectNull(EC_POINT_dup(set_point, NULL)); + ExpectNotNull(dup_point = EC_POINT_dup(set_point, group)); + ExpectIntEQ(EC_POINT_cmp(group, dup_point, set_point, ctx), 0); + /* Test inverting */ ExpectIntEQ(EC_POINT_invert(NULL, NULL, ctx), 0); ExpectIntEQ(EC_POINT_invert(NULL, new_point, ctx), 0); @@ -526,6 +533,12 @@ int test_wolfSSL_EC_POINT(void) ExpectIntEQ(EC_POINT_add(group, orig_point, orig_point, new_point, NULL), 1); ExpectIntEQ(EC_POINT_cmp(group, orig_point, set_point, NULL), 0); + /* dup_point equals set_point so let's test with that too */ + ExpectIntEQ(EC_POINT_add(group, orig_point, dup_point, dup_point, NULL), + 1); + ExpectIntEQ(EC_POINT_add(group, orig_point, orig_point, new_point, + NULL), 1); + ExpectIntEQ(EC_POINT_cmp(group, orig_point, set_point, NULL), 0); EC_POINT_free(orig_point); } #endif @@ -769,6 +782,7 @@ int test_wolfSSL_EC_POINT(void) BN_free(k); BN_free(set_point_bn); EC_POINT_free(infinity); + EC_POINT_free(dup_point); EC_POINT_free(new_point); EC_POINT_free(set_point); EC_POINT_clear_free(Gxy); From 100e79f9e5ce370303fafa07a42d973fda200935 Mon Sep 17 00:00:00 2001 From: Daniel Pouzzner Date: Thu, 26 Feb 2026 09:24:10 -0600 Subject: [PATCH 32/36] wolfcrypt/src/aes.c: add _TI_CRYPT and _RISCV_ASM fallthrough definitions for Aes{En,de}crypt_preFetchOpt. --- wolfcrypt/src/aes.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/wolfcrypt/src/aes.c b/wolfcrypt/src/aes.c index 7c4b3277ce..91b67917f1 100644 --- a/wolfcrypt/src/aes.c +++ b/wolfcrypt/src/aes.c @@ -91,6 +91,11 @@ block cipher mechanism that uses n-bit binary string parameter key with 128-bits #if defined(WOLFSSL_TI_CRYPT) #include + + #define AesEncrypt_preFetchOpt(aes, inBlock, outBlock, do_preFetch) \ + wc_AesEncryptDirect(aes, outBlock, inBlock) + #define AesDecrypt_preFetchOpt(aes, inBlock, outBlock, do_preFetch) \ + wc_AesDecryptDirect(aes, outBlock, inBlock) #else @@ -7279,7 +7284,6 @@ int wc_AesCbcEncrypt(Aes* aes, byte* out, const byte* in, word32 sz) #endif /* NEED_AES_CTR_SOFT */ #endif /* WOLFSSL_AES_COUNTER */ -#endif /* !WOLFSSL_RISCV_ASM */ #ifndef WC_AES_HAVE_PREFETCH_ARG #ifndef AesEncrypt_preFetchOpt @@ -7292,6 +7296,15 @@ int wc_AesCbcEncrypt(Aes* aes, byte* out, const byte* in, word32 sz) #endif #endif +#else /* WOLFSSL_RISCV_ASM */ + +#define AesEncrypt_preFetchOpt(aes, inBlock, outBlock, do_preFetch) \ + wc_AesEncryptDirect(aes, outBlock, inBlock) +#define AesDecrypt_preFetchOpt(aes, inBlock, outBlock, do_preFetch) \ + wc_AesDecryptDirect(aes, outBlock, inBlock) + +#endif /* WOLFSSL_RISCV_ASM */ + /* * The IV for AES GCM and CCM, stored in struct Aes's member reg, is comprised * of two parts in order: From 187534855d7e1c6b1b72100ad666d99f72e06439 Mon Sep 17 00:00:00 2001 From: Eric Blankenhorn Date: Tue, 24 Feb 2026 08:27:42 -0600 Subject: [PATCH 33/36] Fix issues in TLS Extension size calculations --- src/tls.c | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/src/tls.c b/src/tls.c index 207c873b46..6313385c01 100644 --- a/src/tls.c +++ b/src/tls.c @@ -2138,7 +2138,7 @@ static void TLSX_SNI_FreeAll(SNI* list, void* heap) static word16 TLSX_SNI_GetSize(SNI* list) { SNI* sni; - word16 length = OPAQUE16_LEN; /* list length */ + word32 length = OPAQUE16_LEN; /* list length */ while ((sni = list)) { list = sni->next; @@ -2150,9 +2150,13 @@ static word16 TLSX_SNI_GetSize(SNI* list) length += (word16)XSTRLEN((char*)sni->data.host_name); break; } + + if (length > WOLFSSL_MAX_16BIT) { + return 0; + } } - return length; + return (word16)length; } /** Writes the SNI objects of a list in a buffer. */ @@ -3216,7 +3220,7 @@ static void TLSX_CSR_Free(CertificateStatusRequest* csr, void* heap) word16 TLSX_CSR_GetSize_ex(CertificateStatusRequest* csr, byte isRequest, int idx) { - word16 size = 0; + word32 size = 0; /* shut up compiler warnings */ (void) csr; (void) isRequest; @@ -3237,15 +3241,21 @@ word16 TLSX_CSR_GetSize_ex(CertificateStatusRequest* csr, byte isRequest, if (csr->ssl != NULL && SSL_CM(csr->ssl) != NULL && SSL_CM(csr->ssl)->ocsp_stapling != NULL && SSL_CM(csr->ssl)->ocsp_stapling->statusCb != NULL) { - return OPAQUE8_LEN + OPAQUE24_LEN + csr->ssl->ocspCsrResp[idx].length; + size = OPAQUE8_LEN + OPAQUE24_LEN + + csr->ssl->ocspCsrResp[idx].length; + if (size > WOLFSSL_MAX_16BIT) + return 0; + return (word16)size; } - return (word16)(OPAQUE8_LEN + OPAQUE24_LEN + - csr->responses[idx].length); + size = OPAQUE8_LEN + OPAQUE24_LEN + csr->responses[idx].length; + if (size > WOLFSSL_MAX_16BIT) + return 0; + return (word16)size; } #else (void)idx; #endif - return size; + return (word16)size; } #if (defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_SERVER)) @@ -3855,7 +3865,7 @@ static void TLSX_CSR2_FreeAll(CertificateStatusRequestItemV2* csr2, void* heap) static word16 TLSX_CSR2_GetSize(CertificateStatusRequestItemV2* csr2, byte isRequest) { - word16 size = 0; + word32 size = 0; /* shut up compiler warnings */ (void) csr2; (void) isRequest; @@ -3876,11 +3886,15 @@ static word16 TLSX_CSR2_GetSize(CertificateStatusRequestItemV2* csr2, size += OCSP_NONCE_EXT_SZ; break; } + + if (size > WOLFSSL_MAX_16BIT) { + return 0; + } } } #endif - return size; + return (word16)size; } static int TLSX_CSR2_Write(CertificateStatusRequestItemV2* csr2, From be7f9341576bc7033b4c515cd42c7efa8b1f8f6b Mon Sep 17 00:00:00 2001 From: Eric Blankenhorn Date: Tue, 24 Feb 2026 08:35:51 -0600 Subject: [PATCH 34/36] Add test case --- src/tls.c | 14 +++++---- tests/api.c | 1 + tests/api/test_tls_ext.c | 64 ++++++++++++++++++++++++++++++++++++++++ tests/api/test_tls_ext.h | 1 + wolfssl/internal.h | 3 +- 5 files changed, 77 insertions(+), 6 deletions(-) diff --git a/src/tls.c b/src/tls.c index 6313385c01..7e724681af 100644 --- a/src/tls.c +++ b/src/tls.c @@ -2135,7 +2135,7 @@ static void TLSX_SNI_FreeAll(SNI* list, void* heap) } /** Tells the buffered size of the SNI objects in a list. */ -static word16 TLSX_SNI_GetSize(SNI* list) +WOLFSSL_TEST_VIS word16 TLSX_SNI_GetSize(SNI* list) { SNI* sni; word32 length = OPAQUE16_LEN; /* list length */ @@ -3241,15 +3241,19 @@ word16 TLSX_CSR_GetSize_ex(CertificateStatusRequest* csr, byte isRequest, if (csr->ssl != NULL && SSL_CM(csr->ssl) != NULL && SSL_CM(csr->ssl)->ocsp_stapling != NULL && SSL_CM(csr->ssl)->ocsp_stapling->statusCb != NULL) { + if (WOLFSSL_MAX_16BIT - OPAQUE8_LEN - OPAQUE24_LEN < + csr->ssl->ocspCsrResp[idx].length) { + return 0; + } size = OPAQUE8_LEN + OPAQUE24_LEN + csr->ssl->ocspCsrResp[idx].length; - if (size > WOLFSSL_MAX_16BIT) - return 0; return (word16)size; } - size = OPAQUE8_LEN + OPAQUE24_LEN + csr->responses[idx].length; - if (size > WOLFSSL_MAX_16BIT) + if (WOLFSSL_MAX_16BIT - OPAQUE8_LEN - OPAQUE24_LEN < + csr->responses[idx].length) { return 0; + } + size = OPAQUE8_LEN + OPAQUE24_LEN + csr->responses[idx].length; return (word16)size; } #else diff --git a/tests/api.c b/tests/api.c index b0861a5b48..f1e6e1780d 100644 --- a/tests/api.c +++ b/tests/api.c @@ -32914,6 +32914,7 @@ TEST_CASE testCases[] = { TEST_DECL(test_certificate_authorities_certificate_request), TEST_DECL(test_certificate_authorities_client_hello), TEST_DECL(test_TLSX_TCA_Find), + TEST_DECL(test_TLSX_SNI_GetSize_overflow), TEST_DECL(test_wolfSSL_wolfSSL_UseSecureRenegotiation), TEST_DECL(test_wolfSSL_SCR_Reconnect), TEST_DECL(test_wolfSSL_SCR_check_enabled), diff --git a/tests/api/test_tls_ext.c b/tests/api/test_tls_ext.c index 4c89d7edf6..ad6053f727 100644 --- a/tests/api/test_tls_ext.c +++ b/tests/api/test_tls_ext.c @@ -545,3 +545,67 @@ int test_certificate_authorities_client_hello(void) { #endif return EXPECT_RESULT(); } + +/* Test that the SNI size calculation returns 0 on overflow instead of + * wrapping around to a small value (integer overflow vulnerability). */ +int test_TLSX_SNI_GetSize_overflow(void) +{ + EXPECT_DECLS; +#if defined(HAVE_SNI) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + TLSX* sni_ext = NULL; + SNI* head = NULL; + SNI* sni = NULL; + int i; + /* Each SNI adds ENUM_LEN(1) + OPAQUE16_LEN(2) + hostname_len to the size. + * With a 1-byte hostname, each entry adds 4 bytes. Starting from + * OPAQUE16_LEN(2) base, we need enough entries to exceed UINT16_MAX. */ + const int num_sni = (0xFFFF / 4) + 2; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + + /* Add initial SNI via public API */ + ExpectIntEQ(WOLFSSL_SUCCESS, + wolfSSL_UseSNI(ssl, WOLFSSL_SNI_HOST_NAME, "a", 1)); + + /* Find the SNI extension and manually build a long chain */ + if (EXPECT_SUCCESS()) { + sni_ext = TLSX_Find(ssl->extensions, TLSX_SERVER_NAME); + ExpectNotNull(sni_ext); + } + + if (EXPECT_SUCCESS()) { + head = (SNI*)sni_ext->data; + ExpectNotNull(head); + } + + /* Append many SNI nodes to force overflow in the size calculation */ + for (i = 1; EXPECT_SUCCESS() && i < num_sni; i++) { + sni = (SNI*)XMALLOC(sizeof(SNI), NULL, DYNAMIC_TYPE_TLSX); + ExpectNotNull(sni); + if (sni != NULL) { + XMEMSET(sni, 0, sizeof(SNI)); + sni->type = WOLFSSL_SNI_HOST_NAME; + sni->data.host_name = (char*)XMALLOC(2, NULL, DYNAMIC_TYPE_TLSX); + ExpectNotNull(sni->data.host_name); + if (sni->data.host_name != NULL) { + sni->data.host_name[0] = 'a'; + sni->data.host_name[1] = '\0'; + } + sni->next = head->next; + head->next = sni; + } + } + + if (EXPECT_SUCCESS()) { + /* The fixed calculation should return 0 (overflow detected) */ + ExpectIntEQ(TLSX_SNI_GetSize((SNI*)sni_ext->data), 0); + } + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_tls_ext.h b/tests/api/test_tls_ext.h index c02067522d..ec7a5223be 100644 --- a/tests/api/test_tls_ext.h +++ b/tests/api/test_tls_ext.h @@ -27,5 +27,6 @@ int test_wolfSSL_DisableExtendedMasterSecret(void); int test_certificate_authorities_certificate_request(void); int test_certificate_authorities_client_hello(void); int test_TLSX_TCA_Find(void); +int test_TLSX_SNI_GetSize_overflow(void); #endif /* TESTS_API_TEST_TLS_EMS_H */ diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 7ebe3d7b6c..12c09a219b 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -3169,7 +3169,7 @@ struct TLSX { struct TLSX* next; /* List Behavior */ }; -WOLFSSL_LOCAL TLSX* TLSX_Find(TLSX* list, TLSX_Type type); +WOLFSSL_TEST_VIS TLSX* TLSX_Find(TLSX* list, TLSX_Type type); WOLFSSL_LOCAL void TLSX_Remove(TLSX** list, TLSX_Type type, void* heap); WOLFSSL_LOCAL void TLSX_FreeAll(TLSX* list, void* heap); WOLFSSL_LOCAL int TLSX_SupportExtensions(WOLFSSL* ssl); @@ -3237,6 +3237,7 @@ WOLFSSL_LOCAL int TLSX_UseSNI(TLSX** extensions, byte type, const void* data, WOLFSSL_LOCAL byte TLSX_SNI_Status(TLSX* extensions, byte type); WOLFSSL_LOCAL word16 TLSX_SNI_GetRequest(TLSX* extensions, byte type, void** data, byte ignoreStatus); +WOLFSSL_TEST_VIS word16 TLSX_SNI_GetSize(SNI* list); #ifndef NO_WOLFSSL_SERVER WOLFSSL_LOCAL void TLSX_SNI_SetOptions(TLSX* extensions, byte type, From edd943e1158d51f226ce1cf19dedf4d28adf8ee5 Mon Sep 17 00:00:00 2001 From: Eric Blankenhorn Date: Thu, 26 Feb 2026 08:39:49 -0600 Subject: [PATCH 35/36] Fix prefix map issues --- wolfssl/internal.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 12c09a219b..942a926fb7 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -3169,6 +3169,9 @@ struct TLSX { struct TLSX* next; /* List Behavior */ }; +#ifdef WOLFSSL_API_PREFIX_MAP + #define TLSX_Find wolfSSL_TLSX_Find +#endif WOLFSSL_TEST_VIS TLSX* TLSX_Find(TLSX* list, TLSX_Type type); WOLFSSL_LOCAL void TLSX_Remove(TLSX** list, TLSX_Type type, void* heap); WOLFSSL_LOCAL void TLSX_FreeAll(TLSX* list, void* heap); @@ -3237,6 +3240,9 @@ WOLFSSL_LOCAL int TLSX_UseSNI(TLSX** extensions, byte type, const void* data, WOLFSSL_LOCAL byte TLSX_SNI_Status(TLSX* extensions, byte type); WOLFSSL_LOCAL word16 TLSX_SNI_GetRequest(TLSX* extensions, byte type, void** data, byte ignoreStatus); +#ifdef WOLFSSL_API_PREFIX_MAP + #define TLSX_SNI_GetSize wolfSSL_TLSX_SNI_GetSize +#endif WOLFSSL_TEST_VIS word16 TLSX_SNI_GetSize(SNI* list); #ifndef NO_WOLFSSL_SERVER From f53ce49694bfc2b1b236b7d20c42ba5b8d75f64d Mon Sep 17 00:00:00 2001 From: Eric Blankenhorn Date: Thu, 26 Feb 2026 10:46:03 -0600 Subject: [PATCH 36/36] Fix from review --- src/tls.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tls.c b/src/tls.c index 7e724681af..46783fb3bf 100644 --- a/src/tls.c +++ b/src/tls.c @@ -2147,7 +2147,7 @@ WOLFSSL_TEST_VIS word16 TLSX_SNI_GetSize(SNI* list) switch (sni->type) { case WOLFSSL_SNI_HOST_NAME: - length += (word16)XSTRLEN((char*)sni->data.host_name); + length += (word32)XSTRLEN((char*)sni->data.host_name); break; }