Merge pull request #10556 from holtrop-wolfssl/rust-crate-updates-3

Rust wrapper: add scrypt KDF and RSA-OAEP support
This commit is contained in:
Daniel Pouzzner
2026-06-05 16:24:29 -05:00
committed by GitHub
18 changed files with 1391 additions and 14 deletions
+22
View File
@@ -13,44 +13,66 @@ EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/build.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/headers.h
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/src/aes.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/src/blake2.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/src/blake2_digest.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/src/blake2_mac.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/src/chacha20_poly1305.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/src/cmac.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/src/cmac_mac.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/src/curve25519.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/src/dh.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/src/dilithium.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/src/ecc.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/src/ecdsa.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/src/ed25519.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/src/ed448.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/src/fips.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/src/hkdf.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/src/hmac.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/src/hmac_mac.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/src/kdf.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/src/lib.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/src/lms.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/src/mlkem.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/src/mlkem_kem.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/src/pbkdf2_password_hash.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/src/prf.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/src/random.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/src/rsa.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/src/rsa_oaep.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/src/rsa_pkcs1v15.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/src/scrypt_password_hash.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/src/sha.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/src/sha_digest.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/src/sys.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/common/mod.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/test_aes.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/test_blake2.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/test_blake2_digest.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/test_blake2_mac.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/test_chacha20_poly1305.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/test_cmac.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/test_cmac_mac.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/test_curve25519.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/test_dh.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/test_dilithium.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/test_ecc.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/test_ecdsa.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/test_ed25519.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/test_ed448.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/test_hkdf.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/test_hmac.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/test_hmac_mac.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/test_kdf.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/test_lms.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/test_mlkem.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/test_mlkem_kem.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/test_pbkdf2_password_hash.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/test_prf.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/test_random.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/test_rsa.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/test_rsa_oaep.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/test_rsa_pkcs1v15.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/test_scrypt_password_hash.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/test_sha.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/test_sha_digest.rs
EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/test_wolfcrypt.rs
+2
View File
@@ -425,6 +425,7 @@ fn scan_cfg() -> Result<()> {
/* kdf */
check_cfg(&binding, "wc_PBKDF2", "kdf_pbkdf2");
check_cfg(&binding, "wc_PKCS12_PBKDF_ex", "kdf_pkcs12");
check_cfg(&binding, "wc_scrypt", "kdf_scrypt");
check_cfg(&binding, "wc_SRTP_KDF", "kdf_srtp");
check_cfg(&binding, "wc_SSH_KDF", "kdf_ssh");
check_cfg(&binding, "wc_Tls13_HKDF_Extract_ex", "kdf_tls13");
@@ -457,6 +458,7 @@ fn scan_cfg() -> Result<()> {
check_cfg(&binding, "wc_RsaDirect", "rsa_direct");
check_cfg(&binding, "wc_MakeRsaKey", "rsa_keygen");
check_cfg(&binding, "wc_RsaPSS_Sign", "rsa_pss");
check_cfg(&binding, "wc_RsaPublicEncrypt_ex", "rsa_oaep");
check_cfg(&binding, "wc_RsaSetRNG", "rsa_setrng");
check_cfg(&binding, "WC_MGF1SHA512_224", "rsa_mgf1sha512_224");
check_cfg(&binding, "WC_MGF1SHA512_256", "rsa_mgf1sha512_256");
+22 -11
View File
@@ -806,19 +806,23 @@ impl CFB {
/// * `din`: Data to encrypt.
/// * `dout`: Buffer in which to store the encrypted data. The size of
/// the buffer must match that of the `din` buffer.
/// * `size`: Number of bits to encrypt. The `din` and `dout` buffers must
/// each be large enough to hold this number of bits.
///
/// # Returns
///
/// A Result which is Ok(()) on success or an Err containing the wolfSSL
/// library return code on failure.
pub fn encrypt1(&mut self, din: &[u8], dout: &mut [u8]) -> Result<(), i32> {
let in_size = crate::buffer_len_to_u32(din.len())?;
let out_size = crate::buffer_len_to_u32(dout.len())?;
if in_size != out_size {
pub fn encrypt1(&mut self, din: &[u8], dout: &mut [u8], size: usize) -> Result<(), i32> {
if din.len() != dout.len() {
return Err(sys::wolfCrypt_ErrorCodes_BAD_FUNC_ARG);
}
if din.len() < size.div_ceil(8) {
return Err(sys::wolfCrypt_ErrorCodes_BAD_FUNC_ARG);
}
let bit_size = crate::buffer_len_to_u32(size)?;
let rc = unsafe {
sys::wc_AesCfb1Encrypt(&mut self.ws_aes, dout.as_mut_ptr(), din.as_ptr(), in_size)
sys::wc_AesCfb1Encrypt(&mut self.ws_aes, dout.as_mut_ptr(), din.as_ptr(), bit_size)
};
if rc != 0 {
return Err(rc);
@@ -894,20 +898,24 @@ impl CFB {
/// * `din`: Data to decrypt.
/// * `dout`: Buffer in which to store the decrypted data. The size of
/// the buffer must match that of the `din` buffer.
/// * `size`: Number of bits to decrypt. The `din` and `dout` buffers must
/// each be large enough to hold this number of bits.
///
/// # Returns
///
/// A Result which is Ok(()) on success or an Err containing the wolfSSL
/// library return code on failure.
#[cfg(aes_decrypt)]
pub fn decrypt1(&mut self, din: &[u8], dout: &mut [u8]) -> Result<(), i32> {
let in_size = crate::buffer_len_to_u32(din.len())?;
let out_size = crate::buffer_len_to_u32(dout.len())?;
if in_size != out_size {
pub fn decrypt1(&mut self, din: &[u8], dout: &mut [u8], size: usize) -> Result<(), i32> {
if din.len() != dout.len() {
return Err(sys::wolfCrypt_ErrorCodes_BAD_FUNC_ARG);
}
if din.len() < size.div_ceil(8) {
return Err(sys::wolfCrypt_ErrorCodes_BAD_FUNC_ARG);
}
let bit_size = crate::buffer_len_to_u32(size)?;
let rc = unsafe {
sys::wc_AesCfb1Decrypt(&mut self.ws_aes, dout.as_mut_ptr(), din.as_ptr(), in_size)
sys::wc_AesCfb1Decrypt(&mut self.ws_aes, dout.as_mut_ptr(), din.as_ptr(), bit_size)
};
if rc != 0 {
return Err(rc);
@@ -2993,7 +3001,10 @@ impl XTSStream {
#[cfg(aes_xts_stream)]
impl XTSStream {
fn zeroize(&mut self) {
unsafe { crate::zeroize_raw(&mut self.ws_xtsaes); }
unsafe {
crate::zeroize_raw(&mut self.ws_xtsaes);
crate::zeroize_raw(&mut self.ws_xtsaesstreamdata);
}
}
}
#[cfg(aes_xts_stream)]
@@ -201,6 +201,9 @@ impl CMAC {
data.as_ptr(), data_size,
key.as_ptr(), key_size)
};
if rc == sys::wolfCrypt_ErrorCodes_MAC_CMP_FAILED_E {
return Ok(false);
}
if rc < 0 {
return Err(rc);
}
@@ -402,6 +405,9 @@ impl CMAC {
data.as_ptr(), data_size,
key.as_ptr(), key_size, heap, dev_id)
};
if rc == sys::wolfCrypt_ErrorCodes_MAC_CMP_FAILED_E {
return Ok(false);
}
if rc < 0 {
return Err(rc);
}
+108
View File
@@ -269,6 +269,114 @@ pub fn pkcs12_pbkdf_ex(password: &[u8], salt: &[u8], iterations: i32, typ: i32,
Ok(())
}
/// Implement the scrypt password-based key derivation function as defined
/// in RFC 7914.
///
/// # Parameters
///
/// * `password`: Password to use for key derivation.
/// * `salt`: Salt value to use for key derivation.
/// * `cost`: log base 2 of the iteration count (`N = 1 << cost`). Must
/// satisfy `1 <= cost < 128 * block_size / 8`.
/// * `block_size`: Number of 128-byte octets in a working block (the `r`
/// parameter from RFC 7914). Must be in `1..=8`.
/// * `parallel`: Number of parallel mix operations to perform (the `p`
/// parameter from RFC 7914). This implementation does not use threads.
/// * `out`: Output buffer in which to store the derived key.
///
/// # Returns
///
/// Returns either Ok(()) on success or Err(e) containing the wolfSSL
/// library error code value.
///
/// # Example
///
/// ```rust
/// #[cfg(kdf_scrypt)]
/// {
/// use wolfssl_wolfcrypt::kdf::scrypt;
/// let password = b"password";
/// let salt = b"NaCl";
/// let expected_key = [
/// 0xfdu8, 0xba, 0xbe, 0x1c, 0x9d, 0x34, 0x72, 0x00,
/// 0x78, 0x56, 0xe7, 0x19, 0x0d, 0x01, 0xe9, 0xfe,
/// 0x7c, 0x6a, 0xd7, 0xcb, 0xc8, 0x23, 0x78, 0x30,
/// 0xe7, 0x73, 0x76, 0x63, 0x4b, 0x37, 0x31, 0x62,
/// 0x2e, 0xaf, 0x30, 0xd9, 0x2e, 0x22, 0xa3, 0x88,
/// 0x6f, 0xf1, 0x09, 0x27, 0x9d, 0x98, 0x30, 0xda,
/// 0xc7, 0x27, 0xaf, 0xb9, 0x4a, 0x83, 0xee, 0x6d,
/// 0x83, 0x60, 0xcb, 0xdf, 0xa2, 0xcc, 0x06, 0x40
/// ];
/// let mut keyout = [0u8; 64];
/// scrypt(password, salt, 10, 8, 16, &mut keyout).expect("Error with scrypt()");
/// assert_eq!(keyout, expected_key);
/// }
/// ```
#[cfg(kdf_scrypt)]
pub fn scrypt(password: &[u8], salt: &[u8], cost: i32, block_size: i32,
parallel: i32, out: &mut [u8]) -> Result<(), i32> {
let password_size = crate::buffer_len_to_i32(password.len())?;
let salt_size = crate::buffer_len_to_i32(salt.len())?;
let out_size = crate::buffer_len_to_i32(out.len())?;
let rc = unsafe {
sys::wc_scrypt(out.as_mut_ptr(), password.as_ptr(), password_size,
salt.as_ptr(), salt_size, cost, block_size, parallel, out_size)
};
if rc != 0 {
return Err(rc);
}
Ok(())
}
/// Implement the scrypt password-based key derivation function as defined
/// in RFC 7914. This variant takes the iteration count `N` directly
/// instead of `log2(N)`.
///
/// # Parameters
///
/// * `password`: Password to use for key derivation.
/// * `salt`: Salt value to use for key derivation.
/// * `iterations`: Iteration count (`N`). Must be a power of two greater
/// than 1.
/// * `block_size`: Number of 128-byte octets in a working block (the `r`
/// parameter from RFC 7914). Must be in `1..=8`.
/// * `parallel`: Number of parallel mix operations to perform (the `p`
/// parameter from RFC 7914). This implementation does not use threads.
/// * `out`: Output buffer in which to store the derived key.
///
/// # Returns
///
/// Returns either Ok(()) on success or Err(e) containing the wolfSSL
/// library error code value.
///
/// # Example
///
/// ```rust
/// #[cfg(kdf_scrypt)]
/// {
/// use wolfssl_wolfcrypt::kdf::scrypt_ex;
/// let password = b"password";
/// let salt = b"NaCl";
/// let mut keyout = [0u8; 64];
/// scrypt_ex(password, salt, 1024, 8, 16, &mut keyout).expect("Error with scrypt_ex()");
/// }
/// ```
#[cfg(kdf_scrypt)]
pub fn scrypt_ex(password: &[u8], salt: &[u8], iterations: u32,
block_size: i32, parallel: i32, out: &mut [u8]) -> Result<(), i32> {
let password_size = crate::buffer_len_to_i32(password.len())?;
let salt_size = crate::buffer_len_to_i32(salt.len())?;
let out_size = crate::buffer_len_to_i32(out.len())?;
let rc = unsafe {
sys::wc_scrypt_ex(out.as_mut_ptr(), password.as_ptr(), password_size,
salt.as_ptr(), salt_size, iterations, block_size, parallel, out_size)
};
if rc != 0 {
return Err(rc);
}
Ok(())
}
/// Perform RFC 5869 HKDF-Extract operation for TLS v1.3 key derivation.
///
/// # Parameters
@@ -74,11 +74,15 @@ pub mod mlkem_kem;
pub mod prf;
pub mod random;
pub mod rsa;
#[cfg(rsa_oaep)]
pub mod rsa_oaep;
#[cfg(feature = "signature")]
pub mod rsa_pkcs1v15;
pub mod sha;
#[cfg(all(feature = "password-hash", hmac, kdf_pbkdf2))]
pub mod pbkdf2_password_hash;
#[cfg(all(feature = "password-hash", kdf_scrypt))]
pub mod scrypt_password_hash;
#[cfg(feature = "digest")]
pub mod sha_digest;
@@ -774,6 +774,9 @@ impl Lms {
if rc != 0 {
return Err(rc);
}
if kid_ptr.is_null() {
return Err(sys::wolfCrypt_ErrorCodes_BAD_FUNC_ARG);
}
let src = unsafe { core::slice::from_raw_parts(kid_ptr, kid_sz as usize) };
if kid.len() < src.len() {
return Err(sys::wolfCrypt_ErrorCodes_BUFFER_E);
@@ -135,6 +135,7 @@ macro_rules! impl_mlkem_kem {
let mut ss = [0u8; crate::mlkem::MlKem::SHARED_SECRET_SIZE];
wc_key.encapsulate_with_random(&mut ct, &mut ss, &rand)
.expect("encapsulate_with_random failed");
zeroize::Zeroize::zeroize(&mut rand[..]);
(ct.into(), ss.into())
}
@@ -184,6 +185,7 @@ macro_rules! impl_mlkem_kem {
let wc_key = crate::mlkem::MlKem::generate_with_random(
$key_type, &rand,
).expect("generate_with_random failed");
zeroize::Zeroize::zeroize(&mut rand[..]);
let mut pk = [0u8; $pk_len];
let mut sk = [0u8; $sk_len];
@@ -217,10 +217,14 @@ impl password_hash::CustomizedPasswordHasher<PasswordHash> for Pbkdf2 {
None => self.algorithm,
};
if params.rounds < MIN_ROUNDS || params.output_len > Output::MAX_LENGTH {
if params.rounds < MIN_ROUNDS {
return Err(Error::ParamInvalid { name: "i" });
}
if params.output_len > Output::MAX_LENGTH {
return Err(Error::ParamInvalid { name: "l" });
}
let iterations = i32::try_from(params.rounds)
.map_err(|_| Error::ParamInvalid { name: "i" })?;
+162
View File
@@ -847,6 +847,168 @@ impl RSA {
Ok(rc as usize)
}
/// Encrypt data using an RSA public key with RSAES-OAEP padding (PKCS#1
/// v2.2).
///
/// # Parameters
///
/// * `din`: Data to encrypt.
/// * `dout`: Buffer in which to store encrypted data.
/// * `hash_algo`: Hash algorithm type used by OAEP, one of
/// `RSA::HASH_TYPE_*`.
/// * `mgf`: Mask generation function to use, one of `RSA::MGF*`.
/// * `rng`: Reference to a `RNG` struct to use for random number
/// generation while encrypting.
///
/// # Returns
///
/// Returns Ok(size) on success or Err(e) containing the wolfSSL library
/// error code value.
/// The size returned specifies the number of bytes written to the `dout`
/// buffer.
///
/// # Example
///
/// ```rust
/// # extern crate std;
/// #[cfg(all(random, sha256, rsa_oaep, feature = "alloc"))]
/// {
/// use std::fs;
/// use wolfssl_wolfcrypt::random::RNG;
/// use wolfssl_wolfcrypt::rsa::RSA;
///
/// let rng = std::rc::Rc::new(RNG::new().expect("Error creating RNG"));
/// let key_path = "../../../certs/client-keyPub.der";
/// let der: Vec<u8> = fs::read(key_path).expect("Error reading key file");
/// let mut rsa = RSA::new_public_from_der(&der).expect("Error with new_public_from_der()");
/// rsa.set_shared_rng(std::rc::Rc::clone(&rng)).expect("Error with set_shared_rng()");
/// let plain: &[u8] = b"Test message";
/// let mut enc: [u8; 512] = [0; 512];
/// let enc_len = rsa.public_encrypt_oaep(plain, &mut enc, RSA::HASH_TYPE_SHA256, RSA::MGF1SHA256, &rng).expect("Error with public_encrypt_oaep()");
/// assert!(enc_len > 0 && enc_len <= 512);
///
/// let key_path = "../../../certs/client-key.der";
/// let der: Vec<u8> = fs::read(key_path).expect("Error reading key file");
/// let mut rsa = RSA::new_from_der(&der).expect("Error with new_from_der()");
/// rsa.set_shared_rng(std::rc::Rc::clone(&rng)).expect("Error with set_shared_rng()");
/// let mut plain_out: [u8; 512] = [0; 512];
/// let dec_len = rsa.private_decrypt_oaep(&enc[0..enc_len], &mut plain_out, RSA::HASH_TYPE_SHA256, RSA::MGF1SHA256).expect("Error with private_decrypt_oaep()");
/// assert_eq!(dec_len, plain.len());
/// assert_eq!(plain_out[0..dec_len], *plain);
/// }
/// ```
#[cfg(all(random, rsa_oaep))]
pub fn public_encrypt_oaep(&mut self, din: &[u8], dout: &mut [u8], hash_algo: u32, mgf: i32, rng: &RNG) -> Result<usize, i32> {
self.public_encrypt_oaep_ex(din, dout, hash_algo, mgf, None, rng)
}
/// Encrypt data using an RSA public key with RSAES-OAEP padding and an
/// optional label.
///
/// # Parameters
///
/// * `din`: Data to encrypt.
/// * `dout`: Buffer in which to store encrypted data.
/// * `hash_algo`: Hash algorithm type used by OAEP, one of
/// `RSA::HASH_TYPE_*`.
/// * `mgf`: Mask generation function to use, one of `RSA::MGF*`.
/// * `label`: Optional OAEP label (must be supplied identically when
/// decrypting).
/// * `rng`: Reference to a `RNG` struct to use for random number
/// generation while encrypting.
///
/// # Returns
///
/// Returns Ok(size) on success or Err(e) containing the wolfSSL library
/// error code value.
/// The size returned specifies the number of bytes written to the `dout`
/// buffer.
#[cfg(all(random, rsa_oaep))]
pub fn public_encrypt_oaep_ex(&mut self, din: &[u8], dout: &mut [u8], hash_algo: u32, mgf: i32, label: Option<&[u8]>, rng: &RNG) -> Result<usize, i32> {
let din_size = crate::buffer_len_to_u32(din.len())?;
let dout_size = crate::buffer_len_to_u32(dout.len())?;
let (label_ptr, label_size) = match label {
// wolfSSL C API takes label as `byte *` but only reads from it.
Some(label) => (label.as_ptr() as *mut u8, crate::buffer_len_to_u32(label.len())?),
None => (core::ptr::null_mut(), 0),
};
let rc = unsafe {
// SAFETY: label_ptr is declared as a mutable pointer but is only
// read from.
sys::wc_RsaPublicEncrypt_ex(din.as_ptr(), din_size,
dout.as_mut_ptr(), dout_size, &mut self.wc_rsakey,
rng.wc_rng, sys::WC_RSA_OAEP_PAD as i32,
hash_algo, mgf, label_ptr, label_size)
};
if rc < 0 {
return Err(rc);
}
Ok(rc as usize)
}
/// Decrypt data using an RSA private key with RSAES-OAEP padding (PKCS#1
/// v2.2).
///
/// # Parameters
///
/// * `din`: Data to decrypt.
/// * `dout`: Buffer in which to store decrypted data.
/// * `hash_algo`: Hash algorithm type used by OAEP, one of
/// `RSA::HASH_TYPE_*`.
/// * `mgf`: Mask generation function to use, one of `RSA::MGF*`.
///
/// # Returns
///
/// Returns Ok(size) on success or Err(e) containing the wolfSSL library
/// error code value.
/// The size returned specifies the number of bytes written to the `dout`
/// buffer.
#[cfg(rsa_oaep)]
pub fn private_decrypt_oaep(&mut self, din: &[u8], dout: &mut [u8], hash_algo: u32, mgf: i32) -> Result<usize, i32> {
self.private_decrypt_oaep_ex(din, dout, hash_algo, mgf, None)
}
/// Decrypt data using an RSA private key with RSAES-OAEP padding and an
/// optional label.
///
/// # Parameters
///
/// * `din`: Data to decrypt.
/// * `dout`: Buffer in which to store decrypted data.
/// * `hash_algo`: Hash algorithm type used by OAEP, one of
/// `RSA::HASH_TYPE_*`.
/// * `mgf`: Mask generation function to use, one of `RSA::MGF*`.
/// * `label`: Optional OAEP label that was supplied when encrypting.
///
/// # Returns
///
/// Returns Ok(size) on success or Err(e) containing the wolfSSL library
/// error code value.
/// The size returned specifies the number of bytes written to the `dout`
/// buffer.
#[cfg(rsa_oaep)]
pub fn private_decrypt_oaep_ex(&mut self, din: &[u8], dout: &mut [u8], hash_algo: u32, mgf: i32, label: Option<&[u8]>) -> Result<usize, i32> {
let din_size = crate::buffer_len_to_u32(din.len())?;
let dout_size = crate::buffer_len_to_u32(dout.len())?;
let (label_ptr, label_size) = match label {
// wolfSSL C API takes label as `byte *` but only reads from it.
Some(label) => (label.as_ptr() as *mut u8, crate::buffer_len_to_u32(label.len())?),
None => (core::ptr::null_mut(), 0),
};
let rc = unsafe {
// SAFETY: label_ptr is declared as a mutable pointer but is only
// read from.
sys::wc_RsaPrivateDecrypt_ex(din.as_ptr(), din_size,
dout.as_mut_ptr(), dout_size, &mut self.wc_rsakey,
sys::WC_RSA_OAEP_PAD as i32,
hash_algo, mgf, label_ptr, label_size)
};
if rc < 0 {
return Err(rc);
}
Ok(rc as usize)
}
/// Sign the provided data with the private key using RSA-PSS signature
/// scheme.
///
@@ -0,0 +1,386 @@
/*
* 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
*/
/*!
RSA OAEP (PKCS#1 v2.2) encryption / decryption with const-generic wrapper
types over [`crate::rsa::RSA`].
This module mirrors the style of [`crate::rsa_pkcs1v15`]: a [`Hash`] marker
selects the digest algorithm used by OAEP and its MGF1, and `N` is the
modulus size in bytes (e.g. `256` for RSA-2048, `384` for RSA-3072).
- [`EncryptingKey<H, N>`] / [`DecryptingKey<H, N>`] — RSA public/private key
wrappers parameterised by the OAEP hash and modulus size.
- [`Ciphertext<N>`] — fixed-size `[u8; N]` ciphertext wrapper.
# RustCrypto traits
The widely-used encryption / decryption traits in the RustCrypto ecosystem
(`rsa::traits::RandomizedEncryptor`, `rsa::traits::Decryptor`) live in the
`rsa` crate and are sealed, so external crates cannot implement them. This
module therefore provides only the natural inherent-method API, matching the
shape of those traits without claiming conformance.
# Example
```rust
#[cfg(all(random, sha256, rsa_keygen, rsa_oaep))]
{
use wolfssl_wolfcrypt::random::RNG;
use wolfssl_wolfcrypt::rsa_oaep::{Sha256, EncryptingKey, DecryptingKey};
let pad_rng = RNG::new().expect("RNG");
let mut dk: DecryptingKey<Sha256, 256> = DecryptingKey::generate(RNG::new().expect("RNG")).expect("dk");
let ek: EncryptingKey<Sha256, 256> = dk.encrypting_key().expect("ek");
let msg = b"hello, OAEP";
let ct = ek.encrypt(&pad_rng, msg).expect("encrypt");
let mut buf = [0u8; 256];
let n = dk.decrypt(&ct, &mut buf).expect("decrypt");
assert_eq!(&buf[..n], msg);
}
```
*/
#![cfg(all(rsa, rsa_oaep))]
use core::marker::PhantomData;
use crate::rsa::RSA;
use crate::sys;
#[cfg(random)]
use crate::random::RNG;
mod private {
pub trait Sealed {}
}
/// Marker trait selecting the digest algorithm used by OAEP (both the label
/// hash and the MGF1 hash).
pub trait Hash: private::Sealed {
/// wolfCrypt hash algorithm identifier (one of `WC_HASH_TYPE_*`).
const HASH_TYPE: u32;
/// wolfCrypt MGF1 identifier matching `HASH_TYPE`.
const MGF: i32;
}
/// SHA-1 digest selection for OAEP / MGF1.
///
/// SHA-1 is included for interoperability only and is **not recommended** for
/// new designs.
#[cfg(sha)]
pub enum Sha1 {}
#[cfg(sha)]
impl private::Sealed for Sha1 {}
#[cfg(sha)]
impl Hash for Sha1 {
const HASH_TYPE: u32 = sys::wc_HashType_WC_HASH_TYPE_SHA;
const MGF: i32 = sys::WC_MGF1SHA1 as i32;
}
/// SHA-224 digest selection for OAEP / MGF1.
#[cfg(sha224)]
pub enum Sha224 {}
#[cfg(sha224)]
impl private::Sealed for Sha224 {}
#[cfg(sha224)]
impl Hash for Sha224 {
const HASH_TYPE: u32 = sys::wc_HashType_WC_HASH_TYPE_SHA224;
const MGF: i32 = sys::WC_MGF1SHA224 as i32;
}
/// SHA-256 digest selection for OAEP / MGF1.
#[cfg(sha256)]
pub enum Sha256 {}
#[cfg(sha256)]
impl private::Sealed for Sha256 {}
#[cfg(sha256)]
impl Hash for Sha256 {
const HASH_TYPE: u32 = sys::wc_HashType_WC_HASH_TYPE_SHA256;
const MGF: i32 = sys::WC_MGF1SHA256 as i32;
}
/// SHA-384 digest selection for OAEP / MGF1.
#[cfg(sha384)]
pub enum Sha384 {}
#[cfg(sha384)]
impl private::Sealed for Sha384 {}
#[cfg(sha384)]
impl Hash for Sha384 {
const HASH_TYPE: u32 = sys::wc_HashType_WC_HASH_TYPE_SHA384;
const MGF: i32 = sys::WC_MGF1SHA384 as i32;
}
/// SHA-512 digest selection for OAEP / MGF1.
#[cfg(sha512)]
pub enum Sha512 {}
#[cfg(sha512)]
impl private::Sealed for Sha512 {}
#[cfg(sha512)]
impl Hash for Sha512 {
const HASH_TYPE: u32 = sys::wc_HashType_WC_HASH_TYPE_SHA512;
const MGF: i32 = sys::WC_MGF1SHA512 as i32;
}
/// Fixed-size RSAES-OAEP ciphertext. `N` is the modulus size in bytes.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Ciphertext<const N: usize>([u8; N]);
impl<const N: usize> Ciphertext<N> {
/// Construct a ciphertext from its raw bytes.
pub const fn from_bytes(bytes: [u8; N]) -> Self {
Self(bytes)
}
/// Return the raw ciphertext bytes.
pub const fn to_bytes(&self) -> [u8; N] {
self.0
}
}
impl<const N: usize> AsRef<[u8]> for Ciphertext<N> {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl<const N: usize> TryFrom<&[u8]> for Ciphertext<N> {
type Error = i32;
fn try_from(bytes: &[u8]) -> Result<Self, i32> {
let arr: [u8; N] = bytes.try_into()
.map_err(|_| sys::wolfCrypt_ErrorCodes_BAD_FUNC_ARG)?;
Ok(Self(arr))
}
}
impl<const N: usize> From<Ciphertext<N>> for [u8; N] {
fn from(ct: Ciphertext<N>) -> Self {
ct.0
}
}
fn check_modulus_size(rsa: &RSA, expected: usize) -> Result<(), i32> {
let actual = rsa.get_encrypt_size()?;
if actual != expected {
return Err(sys::wolfCrypt_ErrorCodes_BAD_FUNC_ARG);
}
Ok(())
}
/// Maximum number of bytes that the public exponent `e` can occupy.
const MAX_E_LEN: usize = 8;
/// RSA OAEP encrypting (public) key.
///
/// Owns a copy of the public key as raw `(n, e)` bytes and instantiates a
/// short-lived [`RSA`] on each encryption. `H` selects the OAEP hash; `N` is
/// the modulus size in bytes.
pub struct EncryptingKey<H: Hash, const N: usize> {
n: [u8; N],
e: [u8; MAX_E_LEN],
e_len: u8,
_hash: PhantomData<H>,
}
impl<H: Hash, const N: usize> Clone for EncryptingKey<H, N> {
fn clone(&self) -> Self { *self }
}
impl<H: Hash, const N: usize> Copy for EncryptingKey<H, N> {}
impl<H: Hash, const N: usize> core::fmt::Debug for EncryptingKey<H, N> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("EncryptingKey")
.field("n", &&self.n[..])
.field("e", &self.exponent())
.finish()
}
}
impl<H: Hash, const N: usize> PartialEq for EncryptingKey<H, N> {
fn eq(&self, other: &Self) -> bool {
self.n == other.n && self.exponent() == other.exponent()
}
}
impl<H: Hash, const N: usize> Eq for EncryptingKey<H, N> {}
impl<H: Hash, const N: usize> EncryptingKey<H, N> {
/// Construct an encrypting key from raw big-endian modulus (`n`) and
/// public exponent (`e`) bytes.
pub fn from_components(n: &[u8], e: &[u8]) -> Result<Self, i32> {
if n.len() != N || e.is_empty() || e.len() > MAX_E_LEN {
return Err(sys::wolfCrypt_ErrorCodes_BAD_FUNC_ARG);
}
let mut n_arr = [0u8; N];
n_arr.copy_from_slice(n);
let mut e_arr = [0u8; MAX_E_LEN];
e_arr[..e.len()].copy_from_slice(e);
Ok(Self {
n: n_arr,
e: e_arr,
e_len: e.len() as u8,
_hash: PhantomData,
})
}
/// Adopt the public part of an existing [`RSA`] key, verifying its
/// modulus size in bytes matches `N`.
pub fn from_rsa(rsa: &RSA) -> Result<Self, i32> {
check_modulus_size(rsa, N)?;
let mut n = [0u8; N];
let mut e = [0u8; MAX_E_LEN];
let mut n_len: u32 = n.len() as u32;
let mut e_len: u32 = e.len() as u32;
#[cfg(rsa_const_api)]
let key = &rsa.wc_rsakey;
// SAFETY: older wolfSSL declared the first arg as non-const, but the
// function only reads from the key (newer versions declare it const).
#[cfg(not(rsa_const_api))]
let key = core::ptr::addr_of!(rsa.wc_rsakey) as *mut sys::RsaKey;
let rc = unsafe {
sys::wc_RsaFlattenPublicKey(
key,
e.as_mut_ptr(), &mut e_len,
n.as_mut_ptr(), &mut n_len,
)
};
if rc != 0 {
return Err(rc);
}
if (n_len as usize) != N || e_len == 0 || (e_len as usize) > MAX_E_LEN {
return Err(sys::wolfCrypt_ErrorCodes_BAD_FUNC_ARG);
}
Ok(Self {
n,
e,
e_len: e_len as u8,
_hash: PhantomData,
})
}
/// Construct an encrypting key from a DER-encoded `SubjectPublicKeyInfo`
/// / PKCS#1 public key.
pub fn from_public_der(der: &[u8]) -> Result<Self, i32> {
let rsa = RSA::new_public_from_der(der)?;
Self::from_rsa(&rsa)
}
/// Return the raw modulus bytes.
pub const fn modulus(&self) -> &[u8; N] {
&self.n
}
/// Return the raw public exponent bytes.
pub fn exponent(&self) -> &[u8] {
&self.e[..self.e_len as usize]
}
/// Encrypt `msg` with RSAES-OAEP, returning the fixed-size ciphertext.
#[cfg(random)]
pub fn encrypt(&self, rng: &RNG, msg: &[u8]) -> Result<Ciphertext<N>, i32> {
self.encrypt_inner(rng, msg, None)
}
/// Encrypt `msg` with RSAES-OAEP using an associated `label`, returning
/// the fixed-size ciphertext.
#[cfg(random)]
pub fn encrypt_with_label(&self, rng: &RNG, msg: &[u8], label: &[u8]) -> Result<Ciphertext<N>, i32> {
self.encrypt_inner(rng, msg, Some(label))
}
#[cfg(random)]
fn encrypt_inner(&self, rng: &RNG, msg: &[u8], label: Option<&[u8]>) -> Result<Ciphertext<N>, i32> {
let mut rsa = RSA::new_public_from_raw(&self.n, self.exponent())?;
let mut out = [0u8; N];
let len = rsa.public_encrypt_oaep_ex(msg, &mut out, H::HASH_TYPE, H::MGF, label, rng)?;
if len != N {
return Err(sys::wolfCrypt_ErrorCodes_BAD_FUNC_ARG);
}
Ok(Ciphertext(out))
}
}
/// RSA OAEP decrypting (private) key.
///
/// `H` selects the OAEP hash; `N` is the expected modulus size in bytes
/// (e.g. `256` for RSA-2048, `384` for RSA-3072).
pub struct DecryptingKey<H: Hash, const N: usize> {
inner: RSA,
_hash: PhantomData<H>,
}
impl<H: Hash, const N: usize> DecryptingKey<H, N> {
/// Generate a fresh `N * 8`-bit RSA key with public exponent 65537. The
/// `rng` is consumed and bound to the key for blinding during decryption.
#[cfg(all(random, rsa_keygen))]
pub fn generate(rng: RNG) -> Result<Self, i32> {
let bits: i32 = (N * 8).try_into().map_err(|_| sys::wolfCrypt_ErrorCodes_BAD_FUNC_ARG)?;
let mut rsa = RSA::generate(bits, 65537, &rng)?;
rsa.set_rng(rng)?;
Ok(Self { inner: rsa, _hash: PhantomData })
}
/// Adopt an existing [`RSA`] key, verifying its modulus size in bytes
/// matches `N`. The `rng` is consumed and bound to the key for blinding
/// during decryption.
#[cfg(random)]
pub fn from_rsa(rsa: RSA, rng: RNG) -> Result<Self, i32> {
check_modulus_size(&rsa, N)?;
let mut rsa = rsa;
rsa.set_rng(rng)?;
Ok(Self { inner: rsa, _hash: PhantomData })
}
/// Construct a decrypting key from a DER-encoded PKCS#1 private key. The
/// `rng` is consumed and bound to the key for blinding during decryption.
#[cfg(random)]
pub fn from_private_der(der: &[u8], rng: RNG) -> Result<Self, i32> {
let rsa = RSA::new_from_der(der)?;
Self::from_rsa(rsa, rng)
}
/// Borrow the inner [`RSA`] key.
pub fn as_rsa(&self) -> &RSA {
&self.inner
}
/// Consume the decrypting key and return its inner [`RSA`].
pub fn into_rsa(self) -> RSA {
self.inner
}
/// Derive the matching [`EncryptingKey`] from this decrypting key.
pub fn encrypting_key(&self) -> Result<EncryptingKey<H, N>, i32> {
EncryptingKey::from_rsa(&self.inner)
}
/// Decrypt `ciphertext` and write the recovered plaintext into `out`,
/// returning the plaintext length.
pub fn decrypt(&mut self, ciphertext: &Ciphertext<N>, out: &mut [u8]) -> Result<usize, i32> {
self.decrypt_inner(ciphertext, out, None)
}
/// Decrypt `ciphertext` with an associated `label` and write the
/// recovered plaintext into `out`, returning the plaintext length.
pub fn decrypt_with_label(&mut self, ciphertext: &Ciphertext<N>, out: &mut [u8], label: &[u8]) -> Result<usize, i32> {
self.decrypt_inner(ciphertext, out, Some(label))
}
fn decrypt_inner(&mut self, ciphertext: &Ciphertext<N>, out: &mut [u8], label: Option<&[u8]>) -> Result<usize, i32> {
self.inner.private_decrypt_oaep_ex(&ciphertext.0, out, H::HASH_TYPE, H::MGF, label)
}
}
@@ -260,9 +260,15 @@ impl<H: Hash, const N: usize> VerifyingKey<H, N> {
let mut e = [0u8; MAX_E_LEN];
let mut n_len: u32 = n.len() as u32;
let mut e_len: u32 = e.len() as u32;
#[cfg(rsa_const_api)]
let key = &rsa.wc_rsakey;
// SAFETY: older wolfSSL declared the first arg as non-const, but the
// function only reads from the key (newer versions declare it const).
#[cfg(not(rsa_const_api))]
let key = core::ptr::addr_of!(rsa.wc_rsakey) as *mut sys::RsaKey;
let rc = unsafe {
sys::wc_RsaFlattenPublicKey(
&rsa.wc_rsakey,
key,
e.as_mut_ptr(), &mut e_len,
n.as_mut_ptr(), &mut n_len,
)
@@ -328,9 +334,15 @@ impl<H: Hash, const N: usize> Keypair for SigningKey<H, N> {
let mut e = [0u8; MAX_E_LEN];
let mut n_len: u32 = n.len() as u32;
let mut e_len: u32 = e.len() as u32;
#[cfg(rsa_const_api)]
let key = &self.inner.wc_rsakey;
// SAFETY: older wolfSSL declared the first arg as non-const, but the
// function only reads from the key (newer versions declare it const).
#[cfg(not(rsa_const_api))]
let key = core::ptr::addr_of!(self.inner.wc_rsakey) as *mut sys::RsaKey;
let rc = unsafe {
sys::wc_RsaFlattenPublicKey(
&self.inner.wc_rsakey,
key,
e.as_mut_ptr(), &mut e_len,
n.as_mut_ptr(), &mut n_len,
)
@@ -0,0 +1,252 @@
/*
* 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
*/
/*!
RustCrypto `password-hash` trait implementations for wolfCrypt scrypt.
This module provides [`Scrypt`], a type that implements the
[`PasswordHasher`] and [`CustomizedPasswordHasher`] traits from the
`password-hash` crate, backed by the wolfCrypt scrypt implementation. The
blanket [`PasswordVerifier`] implementation is also available, allowing
verification of existing password hashes.
Password hashes are represented in the
[PHC string format](https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md):
```text
$scrypt$ln=<log_n>,r=<r>,p=<p>$<salt>$<hash>
```
This is the same encoding used by the RustCrypto `scrypt` crate.
[`PasswordHasher`]: password_hash::PasswordHasher
[`CustomizedPasswordHasher`]: password_hash::CustomizedPasswordHasher
[`PasswordVerifier`]: password_hash::PasswordVerifier
*/
#![cfg(all(feature = "password-hash", kdf_scrypt))]
use password_hash::phc::{Ident, Output, ParamsString, PasswordHash, Salt};
use password_hash::{CustomizedPasswordHasher, Error, Result, Version};
use crate::kdf;
const SCRYPT_IDENT: Ident = Ident::new_unwrap("scrypt");
/// Recommended `log_n` (cost) parameter.
pub const RECOMMENDED_LOG_N: u8 = 17;
/// Recommended `r` (block size) parameter.
pub const RECOMMENDED_R: u32 = 8;
/// Recommended `p` (parallelism) parameter.
pub const RECOMMENDED_P: u32 = 1;
/// Default output length in bytes.
pub const DEFAULT_OUTPUT_LEN: usize = 32;
/// scrypt parameters.
#[derive(Clone, Debug)]
pub struct Params {
/// `log_n` (cost): log base 2 of the iteration count. Iterations =
/// `1 << log_n`.
pub log_n: u8,
/// `r`: block size in 128-byte octets.
pub r: u32,
/// `p`: parallelism factor.
pub p: u32,
/// Desired output hash length in bytes.
pub output_len: usize,
}
impl Default for Params {
fn default() -> Self {
Params {
log_n: RECOMMENDED_LOG_N,
r: RECOMMENDED_R,
p: RECOMMENDED_P,
output_len: DEFAULT_OUTPUT_LEN,
}
}
}
impl Params {
/// Validate the parameters against wolfCrypt's accepted ranges.
fn validate(&self) -> Result<()> {
if self.r == 0 || self.r > 8 {
return Err(Error::ParamInvalid { name: "r" });
}
if self.p == 0 {
return Err(Error::ParamInvalid { name: "p" });
}
// wolfCrypt: cost < 128 * r / 8.
let log_n_cutoff = (128u32 * self.r) / 8;
if self.log_n == 0 || u32::from(self.log_n) >= log_n_cutoff {
return Err(Error::ParamInvalid { name: "ln" });
}
if self.output_len == 0 || self.output_len > Output::MAX_LENGTH {
return Err(Error::ParamInvalid { name: "l" });
}
// wolfCrypt additionally validates that (1 << cost) * (128 * r) fits in a 32-bit word.
let max_n = u32::MAX / (128 * self.r);
let n = 1u32
.checked_shl(self.log_n as u32)
.ok_or(Error::ParamInvalid { name: "ln" })?;
if n > max_n {
return Err(Error::ParamInvalid { name: "ln" });
}
// wolfCrypt: limit p to avoid 32-bit overflow in internal buffer sizing.
let max_p1 = (u32::MAX / 4) / self.r;
let max_p2 = u32::MAX / (128 * self.r);
let max_p = max_p1.min(max_p2);
if self.p > max_p {
return Err(Error::ParamInvalid { name: "p" });
}
Ok(())
}
}
impl TryFrom<&PasswordHash> for Params {
type Error = Error;
fn try_from(hash: &PasswordHash) -> Result<Self> {
let log_n = hash
.params
.get_decimal("ln")
.ok_or(Error::ParamInvalid { name: "ln" })?;
let log_n = u8::try_from(log_n)
.map_err(|_| Error::ParamInvalid { name: "ln" })?;
let r = hash
.params
.get_decimal("r")
.ok_or(Error::ParamInvalid { name: "r" })?;
let p = hash
.params
.get_decimal("p")
.ok_or(Error::ParamInvalid { name: "p" })?;
let output_len = if let Some(ref h) = hash.hash {
h.len()
} else if let Some(l) = hash.params.get_decimal("l") &&
0 < l && (l as usize) <= Output::MAX_LENGTH {
l as usize
} else {
return Err(Error::ParamInvalid { name: "l" });
};
let params = Params { log_n, r, p, output_len };
params.validate()?;
Ok(params)
}
}
/// scrypt password hasher backed by wolfCrypt.
///
/// Implements the [`PasswordHasher`](password_hash::PasswordHasher) and
/// [`CustomizedPasswordHasher`] traits. A blanket
/// [`PasswordVerifier`](password_hash::PasswordVerifier) implementation is
/// provided by the `password-hash` crate.
///
/// # Example
///
/// ```rust
/// #[cfg(kdf_scrypt)]
/// {
/// use password_hash::PasswordHasher;
/// use wolfssl_wolfcrypt::scrypt_password_hash::{Params, Scrypt};
///
/// // Use smaller parameters in the doc test to keep it fast.
/// let hasher = Scrypt {
/// params: Params { log_n: 10, r: 8, p: 1, output_len: 32 },
/// };
/// let salt = b"0123456789abcdef"; // 16 bytes
/// let hash = hasher.hash_password_with_salt(b"password", salt)
/// .expect("hashing failed");
/// }
/// ```
#[derive(Clone, Debug, Default)]
pub struct Scrypt {
/// Default parameters.
pub params: Params,
}
impl password_hash::PasswordHasher<PasswordHash> for Scrypt {
fn hash_password_with_salt(&self, password: &[u8], salt: &[u8]) -> Result<PasswordHash> {
self.hash_password_customized(password, salt, None, None, self.params.clone())
}
}
impl CustomizedPasswordHasher<PasswordHash> for Scrypt {
type Params = Params;
fn hash_password_customized(
&self,
password: &[u8],
salt: &[u8],
algorithm: Option<&str>,
version: Option<Version>,
params: Params,
) -> Result<PasswordHash> {
if version.is_some() {
return Err(Error::Version);
}
if let Some(s) = algorithm {
let ident = Ident::new(s).map_err(|_| Error::Algorithm)?;
if ident != SCRYPT_IDENT {
return Err(Error::Algorithm);
}
}
params.validate()?;
let block_size = i32::try_from(params.r)
.map_err(|_| Error::ParamInvalid { name: "r" })?;
let parallel = i32::try_from(params.p)
.map_err(|_| Error::ParamInvalid { name: "p" })?;
let salt = Salt::new(salt)?;
let mut out_buf = [0u8; Output::MAX_LENGTH];
let out_slice = &mut out_buf[..params.output_len];
kdf::scrypt(password, salt.as_ref(), i32::from(params.log_n),
block_size, parallel, out_slice)
.map_err(|_| Error::Crypto)?;
let output = Output::new(out_slice)?;
let mut phc_params = ParamsString::new();
phc_params.add_decimal("ln", u32::from(params.log_n))?;
phc_params.add_decimal("r", params.r)?;
phc_params.add_decimal("p", params.p)?;
Ok(PasswordHash {
algorithm: SCRYPT_IDENT,
version: None,
params: phc_params,
salt: Some(salt),
hash: Some(output),
})
}
}
@@ -256,6 +256,34 @@ fn test_cfb_big_msg() {
assert_eq!(big_plain, BIG_MSG);
}
#[test]
#[cfg(aes_cfb)]
fn test_cfb_encrypt1() {
/* Test vector taken from wolfcrypt aescfb1_test() (AES-128). */
let key: [u8; 16] = [
0xcd,0xef,0x9d,0x06,0x61,0xba,0xe4,0x73,
0x8d,0x1a,0x58,0xa2,0xa6,0x22,0x8b,0x66
];
let iv: [u8; 16] = [
0x4d,0xbb,0xdc,0xaa,0x59,0xf3,0x63,0xc9,
0x2a,0x3b,0x98,0x43,0xad,0x20,0xe2,0xb7
];
let msg: [u8; 1] = [0xC0];
let mut enc = CFB::new().expect("Failed to create CFB");
let mut dec = CFB::new().expect("Failed to create CFB");
enc.init(&key, &iv).expect("Error with init()");
dec.init(&key, &iv).expect("Error with init()");
let mut cipher: [u8; 1] = [0];
enc.encrypt1(&msg, &mut cipher, 2).expect("Error with encrypt1()");
assert_eq!(cipher[0], 0x00);
let mut plain: [u8; 1] = [0];
dec.decrypt1(&cipher, &mut plain, 2).expect("Error with decrypt1()");
assert_eq!(plain[0], 0xC0);
cipher[0] = 0;
enc.encrypt1(&msg, &mut cipher, 7).expect("Error with encrypt1()");
assert_eq!(cipher[0], 0x1C);
}
#[test]
#[cfg(aes_ctr)]
fn test_ctr_encrypt_decrypt() {
@@ -17,6 +17,10 @@ fn test_cmac() {
0x07u8, 0x0a, 0x16, 0xb4, 0x6b, 0x4d, 0x41, 0x44,
0xf7, 0x9b, 0xdd, 0x9d, 0xd0, 0x4a, 0x28, 0x7c
];
let incorrect_cmac = [
0x06u8, 0x0a, 0x16, 0xb4, 0x6b, 0x4d, 0x41, 0x44,
0xf7, 0x9b, 0xdd, 0x9d, 0xd0, 0x4a, 0x28, 0x7c
];
let mut cmac = CMAC::new(&key).expect("Error with new()");
cmac.update(&message).expect("Error with update()");
let mut finalize_out = [0u8; 16];
@@ -28,6 +32,8 @@ fn test_cmac() {
assert_eq!(generate_out, finalize_out);
let valid = CMAC::verify(&key, &message, &generate_out).expect("Error with verify()");
assert!(valid);
let valid = CMAC::verify(&key, &message, &incorrect_cmac).expect("Error with verify()");
assert!(!valid);
let mut cmac = CMAC::new(&key).expect("Error with new()");
let mut generate_out = [0u8; 16];
@@ -35,4 +41,6 @@ fn test_cmac() {
assert_eq!(generate_out, finalize_out);
let valid = cmac.verify_ex(&key, &message, &generate_out, None, None).expect("Error with verify_ex()");
assert!(valid);
let valid = cmac.verify_ex(&key, &message, &incorrect_cmac, None, None).expect("Error with verify_ex()");
assert!(!valid);
}
@@ -127,6 +127,70 @@ fn test_rsa_direct() {
assert_eq!(plain_out, plain);
}
#[test]
#[cfg(all(sha256, random, rsa_oaep))]
fn test_rsa_oaep() {
common::setup();
let rng = Rc::new(RNG::new().expect("Error creating RNG"));
let key_path = "../../../certs/client-keyPub.der";
let der: Vec<u8> = fs::read(key_path).expect("Error reading key file");
let mut rsa = RSA::new_public_from_der(&der).expect("Error with new_public_from_der()");
rsa.set_shared_rng(Rc::clone(&rng)).expect("Error with set_shared_rng()");
let plain: &[u8] = b"OAEP plain text test message";
let mut enc: [u8; 512] = [0; 512];
let enc_len = rsa.public_encrypt_oaep(plain, &mut enc, RSA::HASH_TYPE_SHA256, RSA::MGF1SHA256, &rng).expect("Error with public_encrypt_oaep()");
assert!(enc_len > 0 && enc_len <= 512);
let key_path = "../../../certs/client-key.der";
let der: Vec<u8> = fs::read(key_path).expect("Error reading key file");
let mut rsa = RSA::new_from_der(&der).expect("Error with new_from_der()");
rsa.set_shared_rng(Rc::clone(&rng)).expect("Error with set_shared_rng()");
let mut plain_out: [u8; 512] = [0; 512];
let dec_len = rsa.private_decrypt_oaep(&enc[0..enc_len], &mut plain_out, RSA::HASH_TYPE_SHA256, RSA::MGF1SHA256).expect("Error with private_decrypt_oaep()");
assert_eq!(dec_len, plain.len());
assert_eq!(plain_out[0..dec_len], *plain);
// Tampered ciphertext should fail to decrypt.
let mut bad = enc;
bad[0] ^= 0xFF;
assert!(rsa.private_decrypt_oaep(&bad[0..enc_len], &mut plain_out, RSA::HASH_TYPE_SHA256, RSA::MGF1SHA256).is_err());
}
#[test]
#[cfg(all(sha256, random, rsa_oaep))]
fn test_rsa_oaep_with_label() {
common::setup();
let rng = Rc::new(RNG::new().expect("Error creating RNG"));
let label: &[u8] = b"a non-empty OAEP label";
let key_path = "../../../certs/client-keyPub.der";
let der: Vec<u8> = fs::read(key_path).expect("Error reading key file");
let mut rsa = RSA::new_public_from_der(&der).expect("Error with new_public_from_der()");
rsa.set_shared_rng(Rc::clone(&rng)).expect("Error with set_shared_rng()");
let plain: &[u8] = b"OAEP with label";
let mut enc: [u8; 512] = [0; 512];
let enc_len = rsa.public_encrypt_oaep_ex(plain, &mut enc, RSA::HASH_TYPE_SHA256, RSA::MGF1SHA256, Some(label), &rng).expect("Error with public_encrypt_oaep_ex()");
assert!(enc_len > 0 && enc_len <= 512);
let key_path = "../../../certs/client-key.der";
let der: Vec<u8> = fs::read(key_path).expect("Error reading key file");
let mut rsa = RSA::new_from_der(&der).expect("Error with new_from_der()");
rsa.set_shared_rng(Rc::clone(&rng)).expect("Error with set_shared_rng()");
// Wrong label must fail.
let mut plain_out: [u8; 512] = [0; 512];
let wrong_label: &[u8] = b"wrong label";
assert!(rsa.private_decrypt_oaep_ex(&enc[0..enc_len], &mut plain_out, RSA::HASH_TYPE_SHA256, RSA::MGF1SHA256, Some(wrong_label)).is_err());
// Correct label succeeds.
let dec_len = rsa.private_decrypt_oaep_ex(&enc[0..enc_len], &mut plain_out, RSA::HASH_TYPE_SHA256, RSA::MGF1SHA256, Some(label)).expect("Error with private_decrypt_oaep_ex()");
assert_eq!(dec_len, plain.len());
assert_eq!(plain_out[0..dec_len], *plain);
}
#[test]
#[cfg(random)]
fn test_rsa_ssl() {
@@ -0,0 +1,139 @@
#![cfg(all(rsa, rsa_oaep, random))]
mod common;
use std::fs;
use wolfssl_wolfcrypt::random::RNG;
#[test]
#[cfg(all(sha256, rsa_keygen))]
fn test_rsa2048_sha256_oaep_round_trip() {
use wolfssl_wolfcrypt::rsa_oaep::{Ciphertext, DecryptingKey, EncryptingKey, Sha256};
common::setup();
let pad_rng = RNG::new().expect("RNG");
let mut dk: DecryptingKey<Sha256, 256> =
DecryptingKey::generate(RNG::new().expect("RNG")).expect("generate 2048");
let ek: EncryptingKey<Sha256, 256> = dk.encrypting_key().expect("encrypting_key");
let msg = b"rsa oaep sha256 round trip test";
let ct: Ciphertext<256> = ek.encrypt(&pad_rng, msg).expect("encrypt");
// Encoding round-trip.
let bytes = ct.to_bytes();
assert_eq!(bytes.len(), 256);
let ct2 = Ciphertext::<256>::try_from(bytes.as_ref()).expect("parse ct");
assert_eq!(ct, ct2);
// Wrong length must fail.
assert!(Ciphertext::<256>::try_from(&bytes[..255]).is_err());
let mut out = [0u8; 256];
let n = dk.decrypt(&ct, &mut out).expect("decrypt");
assert_eq!(&out[..n], msg);
// EncryptingKey rebuilt from raw components is equivalent.
let ek_copy = EncryptingKey::<Sha256, 256>::from_components(ek.modulus(), ek.exponent())
.expect("from_components");
assert_eq!(ek, ek_copy);
let ct3: Ciphertext<256> = ek_copy.encrypt(&pad_rng, msg).expect("encrypt via rebuilt ek");
let n2 = dk.decrypt(&ct3, &mut out).expect("decrypt via rebuilt ek");
assert_eq!(&out[..n2], msg);
}
#[test]
#[cfg(sha384)]
fn test_rsa2048_sha384_oaep_with_der_keys() {
use wolfssl_wolfcrypt::rsa_oaep::{DecryptingKey, EncryptingKey, Sha384};
common::setup();
let pad_rng = RNG::new().expect("RNG");
let pub_der: Vec<u8> = fs::read("../../../certs/client-keyPub.der")
.expect("read client-keyPub.der");
let priv_der: Vec<u8> = fs::read("../../../certs/client-key.der")
.expect("read client-key.der");
let ek: EncryptingKey<Sha384, 256> = EncryptingKey::from_public_der(&pub_der)
.expect("EncryptingKey::from_public_der");
let mut dk: DecryptingKey<Sha384, 256> = DecryptingKey::from_private_der(&priv_der, RNG::new().expect("RNG"))
.expect("DecryptingKey::from_private_der");
let msg = b"oaep sha384 + der keys";
let ct = ek.encrypt(&pad_rng, msg).expect("encrypt");
let mut out = [0u8; 256];
let n = dk.decrypt(&ct, &mut out).expect("decrypt");
assert_eq!(&out[..n], msg);
}
#[test]
#[cfg(all(sha256, rsa_keygen))]
fn test_oaep_label_round_trip_and_mismatch() {
use wolfssl_wolfcrypt::rsa_oaep::{DecryptingKey, EncryptingKey, Sha256};
common::setup();
let pad_rng = RNG::new().expect("RNG");
let mut dk: DecryptingKey<Sha256, 256> =
DecryptingKey::generate(RNG::new().expect("RNG")).expect("generate 2048");
let ek: EncryptingKey<Sha256, 256> = dk.encrypting_key().expect("encrypting_key");
let msg = b"oaep with label";
let label: &[u8] = b"context-info";
let ct = ek.encrypt_with_label(&pad_rng, msg, label).expect("encrypt_with_label");
let mut out = [0u8; 256];
// Correct label succeeds.
let n = dk.decrypt_with_label(&ct, &mut out, label).expect("decrypt_with_label");
assert_eq!(&out[..n], msg);
// Wrong label must fail.
assert!(dk.decrypt_with_label(&ct, &mut out, b"other-label").is_err());
// Missing label must fail.
assert!(dk.decrypt(&ct, &mut out).is_err());
}
#[test]
#[cfg(all(sha256, rsa_keygen))]
fn test_oaep_modulus_size_mismatch_rejected() {
use wolfssl_wolfcrypt::rsa::RSA;
use wolfssl_wolfcrypt::rsa_oaep::{DecryptingKey, EncryptingKey, Sha256};
common::setup();
let rng = RNG::new().expect("RNG");
let rsa2048 = RSA::generate(2048, 65537, &rng).expect("generate");
let ek_result: Result<EncryptingKey<Sha256, 384>, _> = EncryptingKey::from_rsa(&rsa2048);
assert!(ek_result.is_err(), "encrypting key modulus mismatch must be rejected");
let dk_rng = RNG::new().expect("RNG");
let dk_result: Result<DecryptingKey<Sha256, 384>, _> = DecryptingKey::from_rsa(rsa2048, dk_rng);
assert!(dk_result.is_err(), "decrypting key modulus mismatch must be rejected");
}
#[test]
#[cfg(all(sha256, rsa_keygen))]
fn test_oaep_tampered_ciphertext_rejected() {
use wolfssl_wolfcrypt::rsa_oaep::{DecryptingKey, Sha256};
common::setup();
let pad_rng = RNG::new().expect("RNG");
let mut dk: DecryptingKey<Sha256, 256> =
DecryptingKey::generate(RNG::new().expect("RNG")).expect("generate 2048");
let ek = dk.encrypting_key().expect("encrypting_key");
let msg = b"some bytes";
let mut ct = ek.encrypt(&pad_rng, msg).expect("encrypt");
let mut bytes = ct.to_bytes();
bytes[0] ^= 0x01;
ct = wolfssl_wolfcrypt::rsa_oaep::Ciphertext::from_bytes(bytes);
let mut out = [0u8; 256];
assert!(dk.decrypt(&ct, &mut out).is_err());
}
@@ -0,0 +1,164 @@
#![cfg(all(feature = "password-hash", kdf_scrypt))]
mod common;
use password_hash::phc::PasswordHash;
use password_hash::{CustomizedPasswordHasher, PasswordHasher, PasswordVerifier};
use wolfssl_wolfcrypt::scrypt_password_hash::*;
/// Use modest scrypt parameters in tests to keep them fast while still
/// exercising the full code path.
fn test_params() -> Params {
Params { log_n: 10, r: 8, p: 1, output_len: 32 }
}
#[test]
fn test_hash_and_verify() {
common::setup();
let hasher = Scrypt { params: test_params() };
let salt = b"0123456789abcdef";
let password = b"hunter2";
let hash = hasher
.hash_password_with_salt(password, salt)
.expect("hashing failed");
assert!(hash.salt.is_some());
assert!(hash.hash.is_some());
assert_eq!(hash.hash.as_ref().unwrap().len(), 32);
hasher
.verify_password(password, &hash)
.expect("verification of correct password failed");
assert!(hasher.verify_password(b"wrong_password", &hash).is_err());
}
#[test]
fn test_hash_roundtrip_phc_string() {
common::setup();
let hasher = Scrypt { params: test_params() };
let salt = b"0123456789abcdef";
let password = b"password";
let hash = hasher
.hash_password_with_salt(password, salt)
.expect("hashing failed");
let phc_string = hash.to_string();
assert!(phc_string.starts_with("$scrypt$"));
assert!(phc_string.contains("ln=10"));
assert!(phc_string.contains("r=8"));
assert!(phc_string.contains("p=1"));
let parsed = PasswordHash::new(&phc_string).expect("parsing PHC string failed");
hasher
.verify_password(password, &parsed)
.expect("verification of parsed hash failed");
}
#[test]
fn test_default_params() {
common::setup();
let hasher = Scrypt::default();
assert_eq!(hasher.params.log_n, RECOMMENDED_LOG_N);
assert_eq!(hasher.params.r, RECOMMENDED_R);
assert_eq!(hasher.params.p, RECOMMENDED_P);
assert_eq!(hasher.params.output_len, DEFAULT_OUTPUT_LEN);
}
#[test]
fn test_customized_hash() {
common::setup();
let hasher = Scrypt::default();
let salt = b"0123456789abcdef";
let password = b"password";
let custom = Params { log_n: 11, r: 8, p: 2, output_len: 48 };
let hash = hasher
.hash_password_with_params(password, salt, custom)
.expect("customized hashing failed");
assert_eq!(hash.hash.as_ref().unwrap().len(), 48);
assert_eq!(hash.params.get_decimal("ln"), Some(11));
assert_eq!(hash.params.get_decimal("r"), Some(8));
assert_eq!(hash.params.get_decimal("p"), Some(2));
hasher
.verify_password(password, &hash)
.expect("customized hash verification failed");
}
#[test]
fn test_version_rejected() {
common::setup();
let hasher = Scrypt { params: test_params() };
let salt = b"0123456789abcdef";
let result = hasher.hash_password_customized(
b"password", salt, None, Some(1), test_params());
assert!(result.is_err());
}
#[test]
fn test_unknown_algorithm_rejected() {
common::setup();
let hasher = Scrypt { params: test_params() };
let salt = b"0123456789abcdef";
let result = hasher.hash_password_customized(
b"password", salt, Some("argon2id"), None, test_params());
assert!(result.is_err());
}
#[test]
fn test_invalid_params_rejected() {
common::setup();
let hasher = Scrypt { params: test_params() };
let salt = b"0123456789abcdef";
// r out of range
let bad = Params { log_n: 10, r: 9, p: 1, output_len: 32 };
assert!(hasher.hash_password_with_params(b"pw", salt, bad).is_err());
// p must be > 0
let bad = Params { log_n: 10, r: 8, p: 0, output_len: 32 };
assert!(hasher.hash_password_with_params(b"pw", salt, bad).is_err());
// log_n must be > 0
let bad = Params { log_n: 0, r: 8, p: 1, output_len: 32 };
assert!(hasher.hash_password_with_params(b"pw", salt, bad).is_err());
}
#[test]
fn test_deterministic_output() {
common::setup();
let hasher = Scrypt { params: test_params() };
let salt = b"0123456789abcdef";
let password = b"password";
let h1 = hasher.hash_password_with_salt(password, salt).unwrap();
let h2 = hasher.hash_password_with_salt(password, salt).unwrap();
assert_eq!(h1.hash, h2.hash);
}
#[test]
fn test_different_salts_produce_different_hashes() {
common::setup();
let hasher = Scrypt { params: test_params() };
let password = b"password";
let h1 = hasher.hash_password_with_salt(password, b"salt_aaaaaaaaaa01").unwrap();
let h2 = hasher.hash_password_with_salt(password, b"salt_aaaaaaaaaa02").unwrap();
assert_ne!(h1.hash, h2.hash);
}