mirror of
https://github.com/wolfSSL/wolfssl.git
synced 2026-07-05 17:20:48 +02:00
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:
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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" })?;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user