diff --git a/src/algorithms/pad.rs b/src/algorithms/pad.rs index 4b73d591..3e35e772 100644 --- a/src/algorithms/pad.rs +++ b/src/algorithms/pad.rs @@ -25,23 +25,39 @@ fn be_pad_into(bytes: &[u8], out: &mut [u8]) { out[start..].copy_from_slice(tail); } -/// Converts input to the new vector of the given length, using BE and with 0s left padded. +/// Integer-to-Octet-String Primitive (I2OSP) from [RFC8017 §4.1]. /// -/// `input` must fit in `padded_len` bytes. +/// Converts `input` to exactly `padded_len` big-endian bytes, left-padded with zeros. +/// `input` must fit in `padded_len` bytes. This is a caller-established precondition, checked by a +/// debug assertion. +/// +/// Use [`i2osp_zeroizing`] when encoding secret values. +/// +/// [RFC8017 §4.1]: https://www.rfc-editor.org/rfc/rfc8017.html#section-4.1 #[inline] -pub(crate) fn uint_to_be_pad(input: BoxedUint, padded_len: usize) -> Vec { +pub(crate) fn i2osp(input: BoxedUint, padded_len: usize) -> Vec { let mut out = vec![0u8; padded_len]; be_pad_into(&input.to_be_bytes(), &mut out); out } -/// Converts input to the new vector of the given length, using BE and with 0s left padded. +/// Computes [`i2osp`] (Integer-to-Octet-String Primitive) with zeroization of the input and output +/// buffers. +/// +/// The input integer, temporary byte buffer, and returned vector are each zeroized on drop. +/// The same input-width precondition as [`i2osp`] applies. +/// +/// For RSA decryption, this computes `EM = I2OSP(m, k)`, where `padded_len` is `k`, the public +/// modulus length in octets. /// -/// `input` must fit in `padded_len` bytes. +/// [draft-irtf-cfrg-rsa-guidance-10 §7.2] requires this conversion to be constant-time with respect +/// to `m`. Serialization and copying in this function depend only on `padded_len` and the input's +/// storage precision. To satisfy that requirement, callers must ensure that both widths are +/// independent of `m`'s value. /// -/// For secret values: every intermediate buffer, including the returned one, is zeroized on drop. +/// [draft-irtf-cfrg-rsa-guidance-10 §7.2]: https://www.ietf.org/archive/id/draft-irtf-cfrg-rsa-guidance-10.html#section-7.2 #[inline] -pub(crate) fn uint_to_zeroizing_be_pad(input: BoxedUint, padded_len: usize) -> Zeroizing> { +pub(crate) fn i2osp_zeroizing(input: BoxedUint, padded_len: usize) -> Zeroizing> { let input = Zeroizing::new(input); let bytes = Zeroizing::new(input.to_be_bytes()); let mut out = Zeroizing::new(vec![0u8; padded_len]); @@ -55,17 +71,17 @@ mod tests { #[test] fn be_pad() { let x = BoxedUint::from(0x0102u64); - let p = uint_to_be_pad(x.clone(), 16); + let p = i2osp(x.clone(), 16); assert_eq!(&p[..14], &[0u8; 14]); assert_eq!(&p[14..], &[1, 2]); - let p = uint_to_zeroizing_be_pad(x.clone(), 3); + let p = i2osp_zeroizing(x.clone(), 3); assert_eq!(&p[..], &[0, 1, 2]); - let p = uint_to_be_pad(x.clone(), 8); + let p = i2osp(x.clone(), 8); assert_eq!(&p[..], &[0, 0, 0, 0, 0, 0, 1, 2]); - let p = uint_to_be_pad(BoxedUint::zero_with_precision(128), 4); + let p = i2osp(BoxedUint::zero_with_precision(128), 4); assert_eq!(&p[..], &[0u8; 4]); } @@ -73,6 +89,6 @@ mod tests { #[test] #[should_panic(expected = "integer exceeds output width")] fn be_pad_rejects_broken_precondition() { - uint_to_zeroizing_be_pad(BoxedUint::from(0x0102u64), 1); + i2osp_zeroizing(BoxedUint::from(0x0102u64), 1); } } diff --git a/src/oaep.rs b/src/oaep.rs index 9cfb00f9..39665805 100644 --- a/src/oaep.rs +++ b/src/oaep.rs @@ -18,7 +18,7 @@ use digest::{Digest, FixedOutputReset}; use rand_core::TryCryptoRng; use crate::algorithms::oaep::*; -use crate::algorithms::pad::{uint_to_be_pad, uint_to_zeroizing_be_pad}; +use crate::algorithms::pad::{i2osp, i2osp_zeroizing}; use crate::algorithms::rsa::{rsa_decrypt_and_check, rsa_encrypt}; use crate::errors::{Error, Result}; use crate::key::{self, RsaPrivateKey, RsaPublicKey}; @@ -215,7 +215,7 @@ where let em = oaep_encrypt(rng, msg, digest, mgf_digest, label, pub_key.size())?; let int = BoxedUint::from_be_slice(&em, pub_key.n_bits_precision())?; - Ok(uint_to_be_pad(rsa_encrypt(pub_key, &int)?, pub_key.size())) + Ok(i2osp(rsa_encrypt(pub_key, &int)?, pub_key.size())) } /// Encrypts the given message with RSA and the padding scheme from @@ -241,7 +241,7 @@ where let em = oaep_encrypt_digest::<_, D, MGD>(rng, msg, label, pub_key.size())?; let int = BoxedUint::from_be_slice(&em, pub_key.n_bits_precision())?; - Ok(uint_to_be_pad(rsa_encrypt(pub_key, &int)?, pub_key.size())) + Ok(i2osp(rsa_encrypt(pub_key, &int)?, pub_key.size())) } /// Decrypts a plaintext using RSA and the padding scheme from [PKCS#1 OAEP]. @@ -277,7 +277,7 @@ where let ciphertext = BoxedUint::from_be_slice(ciphertext, priv_key.n_bits_precision())?; let em = rsa_decrypt_and_check(priv_key, rng, &ciphertext)?; - let mut em = uint_to_zeroizing_be_pad(em, priv_key.size()); + let mut em = i2osp_zeroizing(em, priv_key.size()); oaep_decrypt(&mut em, digest, mgf_digest, label, priv_key.size()) } @@ -314,7 +314,7 @@ where let ciphertext = BoxedUint::from_be_slice(ciphertext, priv_key.n_bits_precision())?; let em = rsa_decrypt_and_check(priv_key, rng, &ciphertext)?; - let mut em = uint_to_zeroizing_be_pad(em, priv_key.size()); + let mut em = i2osp_zeroizing(em, priv_key.size()); oaep_decrypt_digest::(&mut em, label, priv_key.size()) } diff --git a/src/pkcs1v15.rs b/src/pkcs1v15.rs index 150eb7ef..3e564b09 100644 --- a/src/pkcs1v15.rs +++ b/src/pkcs1v15.rs @@ -44,7 +44,7 @@ use crypto_bigint::BoxedUint; use digest::Digest; use rand_core::TryCryptoRng; -use crate::algorithms::pad::{uint_to_be_pad, uint_to_zeroizing_be_pad}; +use crate::algorithms::pad::{i2osp, i2osp_zeroizing}; use crate::algorithms::pkcs1v15::*; use crate::algorithms::rsa::{rsa_decrypt_and_check, rsa_encrypt}; use crate::errors::{Error, Result}; @@ -156,7 +156,7 @@ fn encrypt( let em = pkcs1v15_encrypt_pad(rng, msg, pub_key.size())?; let int = BoxedUint::from_be_slice(&em, pub_key.n_bits_precision())?; - Ok(uint_to_be_pad(rsa_encrypt(pub_key, &int)?, pub_key.size())) + Ok(i2osp(rsa_encrypt(pub_key, &int)?, pub_key.size())) } /// Decrypts a plaintext using RSA and the padding scheme from PKCS#1 v1.5. @@ -183,7 +183,7 @@ fn decrypt( let ciphertext = BoxedUint::from_be_slice(ciphertext, priv_key.n_bits_precision())?; let em = rsa_decrypt_and_check(priv_key, rng, &ciphertext)?; - let em = uint_to_zeroizing_be_pad(em, priv_key.size()); + let em = i2osp_zeroizing(em, priv_key.size()); pkcs1v15_encrypt_unpad(&em, priv_key.size()) } @@ -212,7 +212,7 @@ fn sign( let em = BoxedUint::from_be_slice(&em, priv_key.n_bits_precision())?; // The signature itself is public; only the computation is secret - Ok(uint_to_be_pad( + Ok(i2osp( rsa_decrypt_and_check(priv_key, rng, &em)?, priv_key.size(), )) @@ -226,7 +226,7 @@ fn verify(pub_key: &RsaPublicKey, prefix: &[u8], hashed: &[u8], sig: &BoxedUint) return Err(Error::Verification); } - let em = uint_to_be_pad(rsa_encrypt(pub_key, sig)?, pub_key.size()); + let em = i2osp(rsa_encrypt(pub_key, sig)?, pub_key.size()); pkcs1v15_sign_unpad(prefix, hashed, &em, pub_key.size()) } diff --git a/src/pss.rs b/src/pss.rs index a186e520..1f82022f 100644 --- a/src/pss.rs +++ b/src/pss.rs @@ -26,7 +26,7 @@ use crypto_bigint::BoxedUint; use digest::{Digest, FixedOutputReset}; use rand_core::TryCryptoRng; -use crate::algorithms::pad::uint_to_be_pad; +use crate::algorithms::pad::i2osp; use crate::algorithms::pss::*; use crate::algorithms::rsa::{rsa_decrypt_and_check, rsa_encrypt}; use crate::errors::{Error, Result}; @@ -157,7 +157,7 @@ where return Err(Error::Verification); } let raw = rsa_encrypt(pub_key, sig)?; - let mut em = uint_to_be_pad(raw, pub_key.size()); + let mut em = i2osp(raw, pub_key.size()); emsa_pss_verify(hashed, &mut em, salt_len, digest, pub_key.n().bits() as _) } @@ -176,7 +176,7 @@ where return Err(Error::Verification); } - let mut em = uint_to_be_pad(rsa_encrypt(pub_key, sig)?, pub_key.size()); + let mut em = i2osp(rsa_encrypt(pub_key, sig)?, pub_key.size()); emsa_pss_verify_digest::(hashed, &mut em, salt_len, pub_key.n().bits() as _) } @@ -243,7 +243,7 @@ where let em = BoxedUint::from_be_slice(&em, priv_key.n_bits_precision())?; let raw = rsa_decrypt_and_check(priv_key, blind_rng, &em)?; - Ok(uint_to_be_pad(raw, priv_key.size())) + Ok(i2osp(raw, priv_key.size())) } fn sign_pss_with_salt_digest( @@ -260,7 +260,7 @@ where let em = emsa_pss_encode_digest::(hashed, em_bits as _, salt)?; let em = BoxedUint::from_be_slice(&em, priv_key.n_bits_precision())?; - Ok(uint_to_be_pad( + Ok(i2osp( rsa_decrypt_and_check(priv_key, blind_rng, &em)?, priv_key.size(), ))