| //! PKCS#1 v1.5 support as described in [RFC8017 § 8.2]. |
| //! |
| //! # Usage |
| //! |
| //! See [code example in the toplevel rustdoc](../index.html#pkcs1-v15-signatures). |
| //! |
| //! [RFC8017 § 8.2]: https://datatracker.ietf.org/doc/html/rfc8017#section-8.2 |
| |
| use alloc::vec::Vec; |
| use digest::Digest; |
| use pkcs8::AssociatedOid; |
| use rand_core::CryptoRngCore; |
| use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; |
| use zeroize::Zeroizing; |
| |
| use crate::errors::{Error, Result}; |
| |
| /// Fills the provided slice with random values, which are guaranteed |
| /// to not be zero. |
| #[inline] |
| fn non_zero_random_bytes<R: CryptoRngCore + ?Sized>(rng: &mut R, data: &mut [u8]) { |
| rng.fill_bytes(data); |
| |
| for el in data { |
| if *el == 0u8 { |
| // TODO: break after a certain amount of time |
| while *el == 0u8 { |
| rng.fill_bytes(core::slice::from_mut(el)); |
| } |
| } |
| } |
| } |
| |
| /// Applied the padding scheme from PKCS#1 v1.5 for encryption. The message must be no longer than |
| /// the length of the public modulus minus 11 bytes. |
| pub(crate) fn pkcs1v15_encrypt_pad<R>( |
| rng: &mut R, |
| msg: &[u8], |
| k: usize, |
| ) -> Result<Zeroizing<Vec<u8>>> |
| where |
| R: CryptoRngCore + ?Sized, |
| { |
| if msg.len() > k - 11 { |
| return Err(Error::MessageTooLong); |
| } |
| |
| // EM = 0x00 || 0x02 || PS || 0x00 || M |
| let mut em = Zeroizing::new(vec![0u8; k]); |
| em[1] = 2; |
| non_zero_random_bytes(rng, &mut em[2..k - msg.len() - 1]); |
| em[k - msg.len() - 1] = 0; |
| em[k - msg.len()..].copy_from_slice(msg); |
| Ok(em) |
| } |
| |
| /// Removes the encryption padding scheme from PKCS#1 v1.5. |
| /// |
| /// Note that whether this function returns an error or not discloses secret |
| /// information. If an attacker can cause this function to run repeatedly and |
| /// learn whether each instance returned an error then they can decrypt and |
| /// forge signatures as if they had the private key. See |
| /// `decrypt_session_key` for a way of solving this problem. |
| #[inline] |
| pub(crate) fn pkcs1v15_encrypt_unpad(em: Vec<u8>, k: usize) -> Result<Vec<u8>> { |
| let (valid, out, index) = decrypt_inner(em, k)?; |
| if valid == 0 { |
| return Err(Error::Decryption); |
| } |
| |
| Ok(out[index as usize..].to_vec()) |
| } |
| |
| /// Removes the PKCS1v15 padding It returns one or zero in valid that indicates whether the |
| /// plaintext was correctly structured. In either case, the plaintext is |
| /// returned in em so that it may be read independently of whether it was valid |
| /// in order to maintain constant memory access patterns. If the plaintext was |
| /// valid then index contains the index of the original message in em. |
| #[inline] |
| fn decrypt_inner(em: Vec<u8>, k: usize) -> Result<(u8, Vec<u8>, u32)> { |
| if k < 11 { |
| return Err(Error::Decryption); |
| } |
| |
| let first_byte_is_zero = em[0].ct_eq(&0u8); |
| let second_byte_is_two = em[1].ct_eq(&2u8); |
| |
| // The remainder of the plaintext must be a string of non-zero random |
| // octets, followed by a 0, followed by the message. |
| // looking_for_index: 1 iff we are still looking for the zero. |
| // index: the offset of the first zero byte. |
| let mut looking_for_index = 1u8; |
| let mut index = 0u32; |
| |
| for (i, el) in em.iter().enumerate().skip(2) { |
| let equals0 = el.ct_eq(&0u8); |
| index.conditional_assign(&(i as u32), Choice::from(looking_for_index) & equals0); |
| looking_for_index.conditional_assign(&0u8, equals0); |
| } |
| |
| // The PS padding must be at least 8 bytes long, and it starts two |
| // bytes into em. |
| // TODO: WARNING: THIS MUST BE CONSTANT TIME CHECK: |
| // Ref: https://github.com/dalek-cryptography/subtle/issues/20 |
| // This is currently copy & paste from the constant time impl in |
| // go, but very likely not sufficient. |
| let valid_ps = Choice::from((((2i32 + 8i32 - index as i32 - 1i32) >> 31) & 1) as u8); |
| let valid = |
| first_byte_is_zero & second_byte_is_two & Choice::from(!looking_for_index & 1) & valid_ps; |
| index = u32::conditional_select(&0, &(index + 1), valid); |
| |
| Ok((valid.unwrap_u8(), em, index)) |
| } |
| |
| #[inline] |
| pub(crate) fn pkcs1v15_sign_pad(prefix: &[u8], hashed: &[u8], k: usize) -> Result<Vec<u8>> { |
| let hash_len = hashed.len(); |
| let t_len = prefix.len() + hashed.len(); |
| if k < t_len + 11 { |
| return Err(Error::MessageTooLong); |
| } |
| |
| // EM = 0x00 || 0x01 || PS || 0x00 || T |
| let mut em = vec![0xff; k]; |
| em[0] = 0; |
| em[1] = 1; |
| em[k - t_len - 1] = 0; |
| em[k - t_len..k - hash_len].copy_from_slice(prefix); |
| em[k - hash_len..k].copy_from_slice(hashed); |
| |
| Ok(em) |
| } |
| |
| #[inline] |
| pub(crate) fn pkcs1v15_sign_unpad(prefix: &[u8], hashed: &[u8], em: &[u8], k: usize) -> Result<()> { |
| let hash_len = hashed.len(); |
| let t_len = prefix.len() + hashed.len(); |
| if k < t_len + 11 { |
| return Err(Error::Verification); |
| } |
| |
| // EM = 0x00 || 0x01 || PS || 0x00 || T |
| let mut ok = em[0].ct_eq(&0u8); |
| ok &= em[1].ct_eq(&1u8); |
| ok &= em[k - hash_len..k].ct_eq(hashed); |
| ok &= em[k - t_len..k - hash_len].ct_eq(prefix); |
| ok &= em[k - t_len - 1].ct_eq(&0u8); |
| |
| for el in em.iter().skip(2).take(k - t_len - 3) { |
| ok &= el.ct_eq(&0xff) |
| } |
| |
| if ok.unwrap_u8() != 1 { |
| return Err(Error::Verification); |
| } |
| |
| Ok(()) |
| } |
| |
| /// prefix = 0x30 <oid_len + 8 + digest_len> 0x30 <oid_len + 4> 0x06 <oid_len> oid 0x05 0x00 0x04 <digest_len> |
| #[inline] |
| pub(crate) fn pkcs1v15_generate_prefix<D>() -> Vec<u8> |
| where |
| D: Digest + AssociatedOid, |
| { |
| let oid = D::OID.as_bytes(); |
| let oid_len = oid.len() as u8; |
| let digest_len = <D as Digest>::output_size() as u8; |
| let mut v = vec![ |
| 0x30, |
| oid_len + 8 + digest_len, |
| 0x30, |
| oid_len + 4, |
| 0x6, |
| oid_len, |
| ]; |
| v.extend_from_slice(oid); |
| v.extend_from_slice(&[0x05, 0x00, 0x04, digest_len]); |
| v |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng}; |
| |
| #[test] |
| fn test_non_zero_bytes() { |
| for _ in 0..10 { |
| let mut rng = ChaCha8Rng::from_seed([42; 32]); |
| let mut b = vec![0u8; 512]; |
| non_zero_random_bytes(&mut rng, &mut b); |
| for el in &b { |
| assert_ne!(*el, 0u8); |
| } |
| } |
| } |
| } |