diff --git a/rsa/src/impl_hacl.rs b/rsa/src/impl_hacl.rs index 94daf9e0e..16a0d00d5 100644 --- a/rsa/src/impl_hacl.rs +++ b/rsa/src/impl_hacl.rs @@ -1,49 +1,86 @@ use crate::Error; -/// An RSA Signature that is `LEN` bytes long. -#[derive(Debug)] -pub struct Signature([u8; LEN]); +/// An RSA Public Key that is `LEN` bytes long. +#[derive(Debug, Clone)] +pub struct PublicKey { + n: [u8; LEN], +} -impl Signature { - pub fn new() -> Self { - Self([0u8; LEN]) - } +/// An RSA Private Key that is `LEN` bytes long. +pub struct PrivateKey { + pk: PublicKey, + d: [u8; LEN], } -impl From<[u8; LEN]> for Signature { - fn from(value: [u8; LEN]) -> Self { - Self(value) +impl alloc::fmt::Debug for PrivateKey { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("PrivateKey") + .field("pk", &self.pk) + .field("d", &"****") + .finish() } } -/// An RSA Public Key that is `LEN` bytes long. +/// An RSA Public Key backed by a slice. Use if the length is not known at compile time. #[derive(Debug, Clone)] -pub struct PublicKey { - n: [u8; LEN], +pub struct VarLenPublicKey<'a> { + n: &'a [u8], } -impl From<[u8; LEN]> for PublicKey { - fn from(n: [u8; LEN]) -> Self { - Self { n } +impl<'a> TryFrom<&'a [u8]> for VarLenPublicKey<'a> { + type Error = Error; + + fn try_from(n: &'a [u8]) -> Result { + match n.len() { + 256 | 384 | 512 | 768 | 1024 => Ok(Self { n }), + _ => Err(Error::InvalidKeyLength), + } } } -/// An RSA Private Key that is `LEN` bytes long. -pub struct PrivateKey { - pk: PublicKey, - d: [u8; LEN], +impl VarLenPublicKey<'_> { + /// Returns the length of the key + pub fn key_len(&self) -> usize { + self.n.len() + } +} +impl alloc::fmt::Debug for VarLenPrivateKey<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("PrivateKey") + .field("pk", &self.pk) + .field("d", &"****") + .finish() + } } -impl PrivateKey { +/// An RSA Private Key backed by slices. Use if the length is not known at compile time. +pub struct VarLenPrivateKey<'a> { + pk: VarLenPublicKey<'a>, + d: &'a [u8], +} + +impl<'a> VarLenPrivateKey<'a> { /// Constructor for the private key based on `n` and `d`. - pub fn from_components(n: [u8; LEN], d: [u8; LEN]) -> Self { - Self { pk: n.into(), d } + pub fn from_components(n: &'a [u8], d: &'a [u8]) -> Result { + if n.len() != d.len() { + return Err(Error::KeyLengthMismatch); + } + + Ok(Self { + pk: n.try_into()?, + d, + }) } /// Returns the public key of the private key. - pub fn pk(&self) -> &PublicKey { + pub fn pk(&self) -> &VarLenPublicKey { &self.pk } + + /// Returns the length of the keys + pub fn key_len(&self) -> usize { + self.d.len() + } } const E_BITS: u32 = 17; @@ -61,6 +98,61 @@ fn hacl_hash_alg(alg: crate::DigestAlgorithm) -> libcrux_hacl_rs::streaming_type macro_rules! impl_rsapss { ($sign_fn:ident, $verify_fn:ident, $bits:literal, $bytes:literal) => { + impl From<[u8; $bytes]> for PublicKey<$bytes> { + fn from(n: [u8; $bytes]) -> Self { + Self { n } + } + } + + impl PublicKey<$bytes> { + /// Returns the slice-based public-key + pub fn as_var_len(&self) -> VarLenPublicKey<'_> { + VarLenPublicKey { n: &self.n } + } + + /// Returns the modulus as bytes. + pub fn n(&self) -> &[u8; $bytes] { + &self.n + } + } + + impl PrivateKey<$bytes> { + /// Constructor for the private key based on `n` and `d`. + pub fn from_components(n: [u8; $bytes], d: [u8; $bytes]) -> Self { + Self { pk: n.into(), d } + } + + /// Returns the public key of the private key. + pub fn pk(&self) -> &PublicKey<$bytes> { + &self.pk + } + + /// Returns the slice-based private key + pub fn as_var_len(&self) -> VarLenPrivateKey<'_> { + VarLenPrivateKey { + pk: self.pk.as_var_len(), + d: &self.d, + } + } + + /// Returns the private exponent as bytes. + pub fn d(&self) -> &[u8; $bytes] { + &self.d + } + } + + impl<'a> From<&'a PublicKey<$bytes>> for VarLenPublicKey<'a> { + fn from(value: &'a PublicKey<$bytes>) -> Self { + value.as_var_len() + } + } + + impl<'a> From<&'a PrivateKey<$bytes>> for VarLenPrivateKey<'a> { + fn from(value: &'a PrivateKey<$bytes>) -> Self { + value.as_var_len() + } + } + /// Computes a signature over `msg` using `sk` and writes it to `sig`. /// Returns `Ok(())` on success. /// @@ -68,56 +160,14 @@ macro_rules! impl_rsapss { /// - the secret key is invalid /// - the length of `msg` exceeds `u32::MAX` /// - `salt_len` exceeds `u32::MAX - alg.hash_len() - 8` - /// - /// Ensures that the preconditions to hacl functions hold: - /// - /// - `sLen + Hash.hash_length a + 8 <= max_size_t` - /// - checked explicitly - /// - `(sLen + Hash.hash_length a + 8) less_than_max_input_length a` - /// - `max_input_length` is at least `2^62 - 1`, can't be reached since everyting - /// is less than `u32::MAX`. - /// - `sLen + Hash.hash_length a + 2 <= blocks (modBits - 1) 8` - /// - `blocks a b = (a - 1) / b + 1`, i.e. `ceil(div(a, b))` - /// - checked explicitly - /// - `msgLen `less_than_max_input_length` a` - /// - follows from the check that messages are shorter than `u32::MAX`. pub fn $sign_fn( alg: crate::DigestAlgorithm, sk: &PrivateKey<$bytes>, msg: &[u8], salt: &[u8], - sig: &mut Signature<$bytes>, + sig: &mut [u8; $bytes], ) -> Result<(), Error> { - let salt_len = salt.len().try_into().map_err(|_| Error::SaltTooLarge)?; - let msg_len = msg.len().try_into().map_err(|_| Error::MessageTooLarge)?; - - // required by precondition to verify, see - // https://github.com/hacl-star/hacl-star/blob/efbf82f29190e2aecdac8899e4f42c8cb9defc98/code/rsapss/Hacl.Spec.RSAPSS.fst#L162 - // all operands are at most u32, so coercing to u64 and then adding is safe. - if (salt_len as u64) + alg.hash_len() as u64 + 8 > u32::MAX as u64 { - return Err(Error::SaltTooLarge); - } - - let a = hacl_hash_alg(alg); - let mod_bits = $bits; - let e_bits = E_BITS; - let d_bits = $bits; - let sgnt = &mut sig.0; - - // required by precondition to verify, see - // https://github.com/hacl-star/hacl-star/blob/efbf82f29190e2aecdac8899e4f42c8cb9defc98/code/rsapss/Hacl.Spec.RSAPSS.fst#L164 - // all operands are at most u32, so coercing to u64 and then adding is safe. - if salt_len as u64 + alg.hash_len() as u64 + 2 > (mod_bits as u64 - 1) / 8 + 1 { - return Err(Error::SaltTooLarge); - } - - match crate::hacl::rsapss::rsapss_skey_sign( - a, mod_bits, e_bits, d_bits, &sk.pk.n, &E, &sk.d, salt_len, salt, msg_len, msg, - sgnt, - ) { - true => Ok(()), - false => Err(Error::SigningFailed), - } + return sign_varlen(alg, &sk.into(), msg, salt, sig); } /// Returns `Ok(())` if the provided signature is valid. @@ -127,44 +177,14 @@ macro_rules! impl_rsapss { /// - the signature verification fails /// - the length of `msg` exceeds `u32::MAX` /// - `salt_len` exceeds `u32::MAX - alg.hash_len() - 8` - /// - /// Ensures that the preconditions to hacl functions hold: - /// - /// - `sLen + Hash.hash_length a + 8 <= max_size_t` - /// - checked explicitly - /// - `(sLen + Hash.hash_length a + 8) less_than_max_input_length a` - /// - `max_input_length` is at least `2^62 - 1`, can't be reached since everyting - /// is less than `u32::MAX`. - /// - `msgLen less_than_max_input_length a` - /// - follows from the check that messages are shorter than `u32::MAX`. pub fn $verify_fn( alg: crate::DigestAlgorithm, pk: &PublicKey<$bytes>, msg: &[u8], salt_len: u32, - sig: &Signature<$bytes>, + sig: &[u8; $bytes], ) -> Result<(), Error> { - // required by precondition to verify, see - // https://github.com/hacl-star/hacl-star/blob/efbf82f29190e2aecdac8899e4f42c8cb9defc98/code/rsapss/Hacl.Spec.RSAPSS.fst#L236 - // all operands are at most u32, so coercing to u64 and then adding is safe. - if (salt_len as u64) + alg.hash_len() as u64 + 8 > u32::MAX as u64 { - return Err(Error::SaltTooLarge); - } - - let msg_len = msg.len().try_into().map_err(|_| Error::MessageTooLarge)?; - - let a = hacl_hash_alg(alg); - let mod_bits = $bits; - let e_bits = E_BITS; - let sgnt = &sig.0; - - match crate::hacl::rsapss::rsapss_pkey_verify( - a, mod_bits, e_bits, &pk.n, &E, salt_len, $bytes, /*signature length*/ - sgnt, msg_len, msg, - ) { - true => Ok(()), - false => Err(Error::VerificationFailed), - } + verify_varlen(alg, &pk.into(), msg, salt_len, sig) } }; } @@ -175,6 +195,159 @@ impl_rsapss!(sign_4096, verify_4096, 4096, 512); impl_rsapss!(sign_6144, verify_6144, 6144, 768); impl_rsapss!(sign_8192, verify_8192, 8192, 1024); +/// Computes a signature over `msg` using `sk` and writes it to `sig`. +/// Returns `Ok(())` on success. +/// +/// Returns an error in any of the following cases: +/// - the secret key is invalid +/// - the length of `msg` exceeds `u32::MAX` +/// - `salt_len` exceeds `u32::MAX - alg.hash_len() - 8` +pub fn sign( + alg: crate::DigestAlgorithm, + sk: &VarLenPrivateKey<'_>, + msg: &[u8], + salt: &[u8], + sig: &mut [u8], +) -> Result<(), Error> { + if sig.len() != sk.key_len() { + return Err(Error::InvalidSignatureLength); + } + + sign_varlen(alg, sk, msg, salt, sig) +} + +/// Returns `Ok(())` if the provided signature is valid. +/// +/// Returns an error in any of the following cases: +/// - the public key is invalid +/// - the signature verification fails +/// - the length of `msg` exceeds `u32::MAX` +/// - `salt_len` exceeds `u32::MAX - alg.hash_len() - 8` +pub fn verify( + alg: crate::DigestAlgorithm, + pk: &VarLenPublicKey<'_>, + msg: &[u8], + salt_len: u32, + sig: &[u8], +) -> Result<(), Error> { + if pk.key_len() != sig.len() { + return Err(Error::InvalidSignatureLength); + } + + verify_varlen(alg, pk, msg, salt_len, sig) +} + +/// Computes a signature over `msg` using `sk` and writes it to `sig`. +/// Returns `Ok(())` on success. +/// +/// Returns an error in any of the following cases: +/// - the secret key is invalid +/// - the length of `msg` exceeds `u32::MAX` +/// - `salt_len` exceeds `u32::MAX - alg.hash_len() - 8` +/// +/// Ensures that the preconditions to hacl functions hold: +/// +/// - `sLen + Hash.hash_length a + 8 <= max_size_t` +/// - checked explicitly +/// - `(sLen + Hash.hash_length a + 8) less_than_max_input_length a` +/// - `max_input_length` is at least `2^62 - 1`, can't be reached since everyting +/// is less than `u32::MAX`. +/// - `sLen + Hash.hash_length a + 2 <= blocks (modBits - 1) 8` +/// - `blocks a b = (a - 1) / b + 1`, i.e. `ceil(div(a, b))` +/// - checked explicitly +/// - `msgLen `less_than_max_input_length` a` +/// - follows from the check that messages are shorter than `u32::MAX`. +fn sign_varlen( + alg: crate::DigestAlgorithm, + sk: &VarLenPrivateKey<'_>, + msg: &[u8], + salt: &[u8], + sig: &mut [u8], +) -> Result<(), Error> { + let salt_len = salt.len().try_into().map_err(|_| Error::SaltTooLarge)?; + let msg_len = msg.len().try_into().map_err(|_| Error::MessageTooLarge)?; + + // required by precondition to verify, see + // https://github.com/hacl-star/hacl-star/blob/efbf82f29190e2aecdac8899e4f42c8cb9defc98/code/rsapss/Hacl.Spec.RSAPSS.fst#L162 + // all operands are at most u32, so coercing to u64 and then adding is safe. + if (salt_len as u64) + alg.hash_len() as u64 + 8 > u32::MAX as u64 { + return Err(Error::SaltTooLarge); + } + + let a = hacl_hash_alg(alg); + let mod_bits = sk.pk.n.len() as u32 * 8; + let e_bits = E_BITS; + let d_bits = sk.d.len() as u32 * 8; + + // required by precondition to verify, see + // https://github.com/hacl-star/hacl-star/blob/efbf82f29190e2aecdac8899e4f42c8cb9defc98/code/rsapss/Hacl.Spec.RSAPSS.fst#L164 + // all operands are at most u32, so coercing to u64 and then adding is safe. + if salt_len as u64 + alg.hash_len() as u64 + 2 > (mod_bits as u64 - 1) / 8 + 1 { + return Err(Error::SaltTooLarge); + } + + match crate::hacl::rsapss::rsapss_skey_sign( + a, mod_bits, e_bits, d_bits, sk.pk.n, &E, sk.d, salt_len, salt, msg_len, msg, sig, + ) { + true => Ok(()), + false => Err(Error::SigningFailed), + } +} + +/// Returns `Ok(())` if the provided signature is valid. +/// +/// Returns an error in any of the following cases: +/// - the public key is invalid +/// - the signature verification fails +/// - the length of `msg` exceeds `u32::MAX` +/// - `salt_len` exceeds `u32::MAX - alg.hash_len() - 8` +/// +/// Ensures that the preconditions to hacl functions hold: +/// +/// - `sLen + Hash.hash_length a + 8 <= max_size_t` +/// - checked explicitly +/// - `(sLen + Hash.hash_length a + 8) less_than_max_input_length a` +/// - `max_input_length` is at least `2^62 - 1`, can't be reached since everyting +/// is less than `u32::MAX`. +/// - `msgLen less_than_max_input_length a` +/// - follows from the check that messages are shorter than `u32::MAX`. +fn verify_varlen( + alg: crate::DigestAlgorithm, + pk: &VarLenPublicKey<'_>, + msg: &[u8], + salt_len: u32, + sig: &[u8], +) -> Result<(), Error> { + // required by precondition to verify, see + // https://github.com/hacl-star/hacl-star/blob/efbf82f29190e2aecdac8899e4f42c8cb9defc98/code/rsapss/Hacl.Spec.RSAPSS.fst#L236 + // all operands are at most u32, so coercing to u64 and then adding is safe. + if (salt_len as u64) + alg.hash_len() as u64 + 8 > u32::MAX as u64 { + return Err(Error::SaltTooLarge); + } + + let msg_len = msg.len().try_into().map_err(|_| Error::MessageTooLarge)?; + + let a = hacl_hash_alg(alg); + let mod_bits = pk.n.len() as u32 * 8; + let e_bits = E_BITS; + + match crate::hacl::rsapss::rsapss_pkey_verify( + a, + mod_bits, + e_bits, + pk.n, + &E, + salt_len, + sig.len() as u32, /*signature length*/ + sig, + msg_len, + msg, + ) { + true => Ok(()), + false => Err(Error::VerificationFailed), + } +} + #[cfg(test)] mod tests { use super::*; @@ -230,7 +403,7 @@ mod tests { }; let salt = [1, 2, 3, 4, 5]; let msg = [7, 8, 9, 10]; - let mut signature = Signature([0u8; 256]); + let mut signature = [0u8; 256]; sign_2048( crate::DigestAlgorithm::Sha2_256, &sk, diff --git a/rsa/src/lib.rs b/rsa/src/lib.rs index cea603c40..dfd0c38c8 100644 --- a/rsa/src/lib.rs +++ b/rsa/src/lib.rs @@ -1,5 +1,7 @@ #![no_std] +extern crate alloc; + #[cfg(not(feature = "expose-hacl"))] mod hacl { pub(crate) mod rsapss; @@ -43,7 +45,7 @@ impl DigestAlgorithm { } /// Represents errors that occurred during signing or verifying. -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub enum Error { /// Indicates that the salt is too large. SaltTooLarge, @@ -56,8 +58,37 @@ pub enum Error { /// Indicates that signing a message failed. SigningFailed, + + /// The lengths of the public and private parts of the key do not match + KeyLengthMismatch, + + /// The length of the provided key is invalid + InvalidKeyLength, + + /// The length of the provided signature is invalid + InvalidSignatureLength, } +impl alloc::fmt::Display for Error { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let text = match self { + Error::SaltTooLarge => "Indicates that the salt is too large.", + Error::MessageTooLarge => "Indicates that the message is too large.", + Error::VerificationFailed => "Indicates that the verification of a signature failed.", + Error::SigningFailed => "Indicates that signing a message failed.", + Error::KeyLengthMismatch => { + "The lengths of the public and private parts of the key do not match" + } + Error::InvalidKeyLength => "The length of the provided key is invalid", + Error::InvalidSignatureLength => "The length of the provided signature is invalid", + }; + + f.write_str(text) + } +} + +impl core::error::Error for Error {} + mod impl_hacl; pub use impl_hacl::*; diff --git a/rsa/tests/rsa_pss.rs b/rsa/tests/rsa_pss.rs index 38764b48a..cab03b135 100644 --- a/rsa/tests/rsa_pss.rs +++ b/rsa/tests/rsa_pss.rs @@ -1,4 +1,7 @@ -use libcrux_rsa::{sign_2048, verify_2048, DigestAlgorithm, PrivateKey, PublicKey, Signature}; +use libcrux_rsa::{ + sign, sign_2048, verify, verify_2048, DigestAlgorithm, Error, PrivateKey, PublicKey, + VarLenPrivateKey, +}; const MODULUS: [u8; 256] = [ 0xd2, 0x78, 0x16, 0xcb, 0x72, 0xbb, 0x6e, 0x27, 0xdb, 0x10, 0x1a, 0x6f, 0x3e, 0x64, 0x62, 0x93, @@ -41,10 +44,10 @@ const PRIVATE_EXPONENT: [u8; 256] = [ #[test] fn self_test_rsa_pss() { let pk = PublicKey::from(MODULUS); - let sk = PrivateKey::from_components(MODULUS, PRIVATE_EXPONENT); + let sk = PrivateKey::<256>::from_components(MODULUS, PRIVATE_EXPONENT); let salt = [1, 2, 3, 4, 5]; let msg = [7, 8, 9, 10]; - let mut signature = Signature::new(); + let mut signature = [0u8; 256]; sign_2048(DigestAlgorithm::Sha2_256, &sk, &msg, &salt, &mut signature).unwrap(); eprintln!("signature: {:x?}", signature); verify_2048( @@ -55,6 +58,47 @@ fn self_test_rsa_pss() { &signature, ) .expect("Error verifying signature"); + + // test the variable length signing + let mut signature = [0u8; 257]; + sign( + DigestAlgorithm::Sha2_256, + &sk.as_var_len(), + &msg, + &salt, + &mut signature[..256], + ) + .unwrap(); + verify( + DigestAlgorithm::Sha2_256, + &pk.as_var_len(), + &msg, + salt.len() as u32, + &signature[..256], + ) + .expect("error verifying signature using variable length api"); + + // test the variable length signing fails if the length is wrong + let mut signature = [0u8; 257]; + let err = sign( + DigestAlgorithm::Sha2_256, + &sk.as_var_len(), + &msg, + &salt, + &mut signature[..], + ) + .expect_err("expected singing with wrong length to fail"); + assert_eq!(err, Error::InvalidSignatureLength); + + // test the variable length key parsing fails if length is wrong + let err = VarLenPrivateKey::from_components(sk.pk().n().as_slice(), &sk.d().as_slice()[0..255]) + .expect_err("from_components should fail if wrong length is supplied"); + assert_eq!(err, Error::KeyLengthMismatch); + + // test the variable length key parsing fails if length is wrong + let err = VarLenPrivateKey::from_components(&sk.pk().n()[0..255], &sk.d()[0..255]) + .expect_err("from_components should fail if wrong length is supplied"); + assert_eq!(err, Error::InvalidKeyLength); } const N: [u8; 256] = [ @@ -100,7 +144,7 @@ fn wycheproof_single_test() { 0x03, 0xcb, 0x29, 0x10, 0xdd, 0x70, 0x67, 0x2b, 0xbf, 0xb6, 0x2e, 0xa4, 0xea, 0xad, 0x72, 0x5c, ]; - verify_2048(DigestAlgorithm::Sha2_256, &pk, &msg, 0, &signature.into()) + verify_2048(DigestAlgorithm::Sha2_256, &pk, &msg, 0, &signature) .expect("Error verifying signature"); let msg = [0x33, 0x32, 0x32, 0x32, 0x30, 0x34, 0x31, 0x30, 0x34, 0x36]; @@ -124,6 +168,6 @@ fn wycheproof_single_test() { 0xbc, 0xa4, 0x68, 0xf9, 0x3d, 0x3f, 0x13, 0x74, 0x95, 0x57, 0xb7, 0x01, 0x29, 0xef, 0x95, 0xe5, ]; - verify_2048(DigestAlgorithm::Sha2_256, &pk, &msg, 32, &signature.into()) + verify_2048(DigestAlgorithm::Sha2_256, &pk, &msg, 32, &signature) .expect("Error verifying signature"); }