diff --git a/src/algorithms.rs b/src/algorithms.rs index f14263c..bd252e3 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -1,4 +1,5 @@ -use alloc::vec; +//! Useful algorithms related to RSA. + use digest::{Digest, DynDigest, FixedOutputReset}; use num_bigint::traits::ModInverse; use num_bigint::{BigUint, RandPrime}; diff --git a/src/errors.rs b/src/errors.rs index 5333995..c117726 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,26 +1,64 @@ +//! Error types. + +/// Alias for [`core::result::Result`] with the `rsa` crate's [`Error`] type. pub type Result = core::result::Result; /// Error types #[derive(Debug, Eq, PartialEq)] #[non_exhaustive] pub enum Error { + /// Invalid padding scheme. InvalidPaddingScheme, + + /// Decryption error. Decryption, + + /// Verification error. Verification, + + /// Message too long. MessageTooLong, + + /// Input must be hashed. InputNotHashed, + + /// Number of primes must be 2 or greater. NprimesTooSmall, + + /// Too few primes of a given length to generate an RSA key. TooFewPrimes, + + /// Invalid prime value. InvalidPrime, + + /// Invalid modulus. InvalidModulus, + + /// Invalid exponent. InvalidExponent, + + /// Invalid coefficient. InvalidCoefficient, + + /// Modulus too large. ModulusTooLarge, + + /// Public exponent too small. PublicExponentTooSmall, + + /// Public exponent too large. PublicExponentTooLarge, + + /// PKCS#1 error. Pkcs1(pkcs1::Error), + + /// PKCS#8 error. Pkcs8(pkcs8::Error), + + /// Internal error. Internal, + + /// Label too long. LabelTooLong, } diff --git a/src/key.rs b/src/key.rs index 12e60a7..6c0f1f9 100644 --- a/src/key.rs +++ b/src/key.rs @@ -17,6 +17,7 @@ use crate::padding::PaddingScheme; use crate::raw::{DecryptionPrimitive, EncryptionPrimitive}; use crate::{oaep, pkcs1v15, pss}; +/// Components of an RSA public key. pub trait PublicKeyParts { /// Returns the modulus of the key. fn n(&self) -> &BigUint; diff --git a/src/lib.rs b/src/lib.rs index 14f33e9..039987a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,17 @@ //! RSA Implementation in pure Rust. //! +//! It supports several schemes described in [RFC8017]: +//! +//! - OAEP encryption scheme +//! - PKCS#1 v1.5 encryption scheme +//! - PKCS#1 v1.5 signature scheme +//! - PSS signature scheme +//! +//! These schemes are described below. //! //! # Usage //! -//! Using PKCS1v15. +//! Using PKCS#1 v1.5. //! ``` //! use rsa::{PublicKey, RsaPrivateKey, RsaPublicKey, PaddingScheme}; //! @@ -47,7 +55,7 @@ //! assert_eq!(&data[..], &dec_data[..]); //! ``` //! -//! Using PKCS1v15 signatures +//! Using PKCS#1 v1.5 signatures //! ``` //! use rsa::RsaPrivateKey; //! use rsa::pkcs1v15::{SigningKey, VerifyingKey}; @@ -95,8 +103,8 @@ //! //! ## PKCS#1 RSA Key Encoding //! -//! PKCS#1 is a legacy format for encoding RSA keys as binary (DER) or text -//! (PEM) data. +//! PKCS#1 supports a legacy format for encoding RSA keys as binary (DER) or +//! text (PEM) data. //! //! You can recognize PEM encoded PKCS#1 keys because they have "RSA * KEY" in //! the type label, e.g.: @@ -112,8 +120,8 @@ //! toplevel of the `rsa` crate: //! //! - [`pkcs1::DecodeRsaPrivateKey`]: decode RSA private keys from PKCS#1 -//! - [`pkcs1::DecodeRsaPublicKey`]: decode RSA public keys from PKCS#1 //! - [`pkcs1::EncodeRsaPrivateKey`]: encode RSA private keys to PKCS#1 +//! - [`pkcs1::DecodeRsaPublicKey`]: decode RSA public keys from PKCS#1 //! - [`pkcs1::EncodeRsaPublicKey`]: encode RSA public keys to PKCS#1 //! //! ### Example @@ -156,8 +164,8 @@ //! toplevel of the `rsa` crate: //! //! - [`pkcs8::DecodePrivateKey`]: decode private keys from PKCS#8 -//! - [`pkcs8::DecodePublicKey`]: decode public keys from PKCS#8 //! - [`pkcs8::EncodePrivateKey`]: encode private keys to PKCS#8 +//! - [`pkcs8::DecodePublicKey`]: decode public keys from PKCS#8 //! - [`pkcs8::EncodePublicKey`]: encode public keys to PKCS#8 //! //! ### Example @@ -183,10 +191,17 @@ //! # Ok(()) //! # } //! ``` +//! +//! [RFC8017]: https://datatracker.ietf.org/doc/html/rfc8017#section-8.1 +//! +// TODO(tarcieri): figure out why rustdoc isn't rendering these links correctly +//! [`pkcs8::DecodePublicKey`]: https://docs.rs/pkcs8/latest/pkcs8/trait.DecodePublicKey.html +//! [`pkcs8::EncodePublicKey`]: https://docs.rs/pkcs8/latest/pkcs8/trait.EncodePublicKey.html #![cfg_attr(not(test), no_std)] #![cfg_attr(docsrs, feature(doc_cfg))] #![doc(html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo_small.png")] +#![warn(missing_docs)] #[macro_use] extern crate alloc; @@ -196,15 +211,10 @@ extern crate std; pub use num_bigint::BigUint; pub use rand_core; -/// Useful algorithms. pub mod algorithms; -/// Error types. pub mod errors; -/// Supported padding schemes. pub mod padding; -/// RSASSA-PKCS1-v1_5 Signature support pub mod pkcs1v15; -/// RSASSA-PSS Signature support pub mod pss; mod dummy_rng; diff --git a/src/oaep.rs b/src/oaep.rs index ee25b0a..adc166d 100644 --- a/src/oaep.rs +++ b/src/oaep.rs @@ -15,9 +15,13 @@ use crate::key::{self, PrivateKey, PublicKey}; // TODO: This is the maximum for SHA-1, unclear from the RFC what the values are for other hashing functions. const MAX_LABEL_LEN: u64 = 2_305_843_009_213_693_951; -/// Encrypts the given message with RSA and the padding -/// scheme from [PKCS#1 OAEP](https://datatracker.ietf.org/doc/html/rfc3447#section-7.1.1). The message must be no longer than the -/// length of the public modulus minus (2+ 2*hash.size()). +/// Encrypts the given message with RSA and the padding scheme from +/// [PKCS#1 OAEP]. +/// +/// The message must be no longer than the length of the public modulus minus +/// `2 + (2 * hash.size())`. +/// +/// [PKCS#1 OAEP]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1 #[inline] pub fn encrypt( rng: &mut R, @@ -63,14 +67,18 @@ pub fn encrypt( pub_key.raw_encryption_primitive(&em, pub_key.size()) } -/// Decrypts a plaintext using RSA and the padding scheme from [pkcs1# OAEP](https://datatracker.ietf.org/doc/html/rfc3447#section-7.1.2) +/// Decrypts a plaintext using RSA and the padding scheme from [PKCS#1 OAEP]. +/// /// If an `rng` is passed, it uses RSA blinding to avoid timing side-channel attacks. /// /// 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. +/// forge signatures as if they had the private key. +/// +/// See `decrypt_session_key` for a way of solving this problem. +/// +/// [PKCS#1 OAEP]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1 #[inline] pub fn decrypt( rng: Option<&mut R>, diff --git a/src/padding.rs b/src/padding.rs index 71c3e2c..f254514 100644 --- a/src/padding.rs +++ b/src/padding.rs @@ -1,3 +1,5 @@ +//! Supported padding schemes. + use alloc::boxed::Box; use alloc::string::{String, ToString}; use core::fmt; @@ -11,29 +13,44 @@ use crate::pkcs1v15; pub enum PaddingScheme { /// Encryption and Decryption using PKCS1v15 padding. PKCS1v15Encrypt, + /// Sign and Verify using PKCS1v15 padding. PKCS1v15Sign { + /// Length of hash to use. hash_len: Option, + + /// Prefix. prefix: Box<[u8]>, }, - /// Encryption and Decryption using [OAEP padding](https://datatracker.ietf.org/doc/html/rfc3447#section-7.1.1). + + /// Encryption and Decryption using [OAEP padding](https://datatracker.ietf.org/doc/html/rfc8017#section-7.1). /// /// - `digest` is used to hash the label. The maximum possible plaintext length is `m = k - 2 * h_len - 2`, - /// where `k` is the size of the RSA modulus. - /// - `mgf_digest` specifies the hash function that is used in the [MGF1](https://datatracker.ietf.org/doc/html/rfc2437#section-10.2.1). + /// where `k` is the size of the RSA modulus. + /// - `mgf_digest` specifies the hash function that is used in the [MGF1](https://datatracker.ietf.org/doc/html/rfc8017#appendix-B.2). /// - `label` is optional data that can be associated with the message. /// /// The two hash functions can, but don't need to be the same. + /// /// A prominent example is the [`AndroidKeyStore`](https://developer.android.com/guide/topics/security/cryptography#oaep-mgf1-digest). /// It uses SHA-1 for `mgf_digest` and a user-chosen SHA flavour for `digest`. OAEP { + /// Digest type to use. digest: Box, + + /// Digest to use for Mask Generation Function (MGF). mgf_digest: Box, + + /// Optional label. label: Option, }, + /// Sign and Verify using PSS padding. PSS { + /// Digest type to use. digest: Box, + + /// Salt length. salt_len: Option, }, } @@ -58,10 +75,14 @@ impl fmt::Debug for PaddingScheme { } impl PaddingScheme { + /// Create new PKCS#1 v1.5 encryption padding. pub fn new_pkcs1v15_encrypt() -> Self { PaddingScheme::PKCS1v15Encrypt } + /// Create new PKCS#1 v1.5 padding for computing a raw signature. + /// + /// This sets `hash_len` to `None` and uses an empty `prefix`. pub fn new_pkcs1v15_sign_raw() -> Self { PaddingScheme::PKCS1v15Sign { hash_len: None, @@ -69,6 +90,10 @@ impl PaddingScheme { } } + /// Create new PKCS#1 v1.5 padding for the given digest. + /// + /// The digest must have an [`AssociatedOid`]. Make sure to enable the `oid` + /// feature of the relevant digest crate. pub fn new_pkcs1v15_sign() -> Self where D: Digest + AssociatedOid, @@ -159,6 +184,7 @@ impl PaddingScheme { } } + /// New PSS padding for the given digest. pub fn new_pss() -> Self { PaddingScheme::PSS { digest: Box::new(T::new()), @@ -166,6 +192,7 @@ impl PaddingScheme { } } + /// New PSS padding for the given digest with a salt value of the given length. pub fn new_pss_with_salt(len: usize) -> Self { PaddingScheme::PSS { digest: Box::new(T::new()), diff --git a/src/pkcs1v15.rs b/src/pkcs1v15.rs index 5fb377e..c6b6fd2 100644 --- a/src/pkcs1v15.rs +++ b/src/pkcs1v15.rs @@ -1,4 +1,7 @@ -use alloc::vec; +//! PKCS#1 v1.5 support as described in [RFC8017 § 8.2]. +//! +//! [RFC8017 § 8.2]: https://datatracker.ietf.org/doc/html/rfc8017#section-8.2 + use alloc::vec::Vec; use core::fmt::{Debug, Display, Formatter, LowerHex, UpperHex}; use core::marker::PhantomData; @@ -20,6 +23,9 @@ use crate::errors::{Error, Result}; use crate::key::{self, PrivateKey, PublicKey}; use crate::{RsaPrivateKey, RsaPublicKey}; +/// PKCS#1 v1.5 signatures as described in [RFC8017 § 8.2]. +/// +/// [RFC8017 § 8.2]: https://datatracker.ietf.org/doc/html/rfc8017#section-8.2 #[derive(Clone)] pub struct Signature { bytes: Vec, @@ -95,9 +101,9 @@ impl Display for Signature { } } -// Encrypts the given message with RSA and the padding -// scheme from PKCS#1 v1.5. The message must be no longer than the -// length of the public modulus minus 11 bytes. +/// Encrypts the given message with RSA and the padding +/// scheme from PKCS#1 v1.5. The message must be no longer than the +/// length of the public modulus minus 11 bytes. #[inline] pub(crate) fn encrypt( rng: &mut R, @@ -122,13 +128,14 @@ pub(crate) fn encrypt( } /// Decrypts a plaintext using RSA and the padding scheme from PKCS#1 v1.5. -// If an `rng` is passed, it uses RSA blinding to avoid timing side-channel attacks. -// -// 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. +/// +/// If an `rng` is passed, it uses RSA blinding to avoid timing side-channel attacks. +/// +/// 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 decrypt( rng: Option<&mut R>, @@ -145,19 +152,19 @@ pub(crate) fn decrypt( Ok(out[index as usize..].to_vec()) } -// Calculates the signature of hashed using -// RSASSA-PKCS1-V1_5-SIGN from RSA PKCS#1 v1.5. Note that `hashed` must -// be the result of hashing the input message using the given hash -// function. If hash is `None`, hashed is signed directly. This isn't -// advisable except for interoperability. -// -// If `rng` is not `None` then RSA blinding will be used to avoid timing -// side-channel attacks. -// -// This function is deterministic. Thus, if the set of possible -// messages is small, an attacker may be able to build a map from -// messages to signatures and identify the signed messages. As ever, -// signatures provide authenticity, not confidentiality. +/// Calculates the signature of hashed using +/// RSASSA-PKCS1-V1_5-SIGN from RSA PKCS#1 v1.5. Note that `hashed` must +/// be the result of hashing the input message using the given hash +/// function. If hash is `None`, hashed is signed directly. This isn't +/// advisable except for interoperability. +/// +/// If `rng` is not `None` then RSA blinding will be used to avoid timing +/// side-channel attacks. +/// +/// This function is deterministic. Thus, if the set of possible +/// messages is small, an attacker may be able to build a map from +/// messages to signatures and identify the signed messages. As ever, +/// signatures provide authenticity, not confidentiality. #[inline] pub(crate) fn sign( rng: Option<&mut R>, @@ -218,7 +225,7 @@ pub(crate) fn verify( Ok(()) } -// prefix = 0x30 0x30 0x06 oid 0x05 0x00 0x04 +/// prefix = 0x30 0x30 0x06 oid 0x05 0x00 0x04 #[inline] pub(crate) fn generate_prefix() -> Vec where @@ -305,6 +312,9 @@ fn non_zero_random_bytes(rng: &mut R, data: &mut [u8]) { } } +/// Signing key for PKCS#1 v1.5 signatures as described in [RFC8017 § 8.2]. +/// +/// [RFC8017 § 8.2]: https://datatracker.ietf.org/doc/html/rfc8017#section-8.2 #[derive(Debug, Clone)] pub struct SigningKey where @@ -319,14 +329,7 @@ impl SigningKey where D: Digest, { - pub(crate) fn key(&self) -> &RsaPrivateKey { - &self.inner - } - - pub(crate) fn prefix(&self) -> Vec { - self.prefix.clone() - } - + /// Create a new signing key from the give RSA private key. pub fn new(key: RsaPrivateKey) -> Self { Self { inner: key, @@ -334,6 +337,14 @@ where phantom: Default::default(), } } + + pub(crate) fn key(&self) -> &RsaPrivateKey { + &self.inner + } + + pub(crate) fn prefix(&self) -> Vec { + self.prefix.clone() + } } impl From for SigningKey @@ -358,6 +369,7 @@ impl SigningKey where D: Digest + AssociatedOid, { + /// Create a new verifying key with a prefix for the digest `D`. pub fn new_with_prefix(key: RsaPrivateKey) -> Self { Self { inner: key, @@ -454,6 +466,9 @@ where } } +/// Verifying key for PKCS#1 v1.5 signatures as described in [RFC8017 § 8.2]. +/// +/// [RFC8017 § 8.2]: https://datatracker.ietf.org/doc/html/rfc8017#section-8.2 #[derive(Debug, Clone)] pub struct VerifyingKey where @@ -468,6 +483,7 @@ impl VerifyingKey where D: Digest, { + /// Create a new verifying key from an RSA public key. pub fn new(key: RsaPublicKey) -> Self { Self { inner: key, @@ -499,6 +515,7 @@ impl VerifyingKey where D: Digest + AssociatedOid, { + /// Create a new verifying key with a prefix for the digest `D`. pub fn new_with_prefix(key: RsaPublicKey) -> Self { Self { inner: key, diff --git a/src/pss.rs b/src/pss.rs index e9a8953..e5fa3bc 100644 --- a/src/pss.rs +++ b/src/pss.rs @@ -1,4 +1,10 @@ -use alloc::vec; +//! Support for the [Probabilistic Signature Scheme] (PSS) a.k.a. RSASSA-PSS. +//! +//! Designed by Mihir Bellare and Phillip Rogaway. Specified in [RFC8017 § 8.1]. +//! +//! [Probabilistic Signature Scheme]: https://en.wikipedia.org/wiki/Probabilistic_signature_scheme +//! [RFC8017 § 8.1]: https://datatracker.ietf.org/doc/html/rfc8017#section-8.1 + use alloc::vec::Vec; use core::fmt::{Debug, Display, Formatter, LowerHex, UpperHex}; @@ -19,6 +25,9 @@ use crate::errors::{Error, Result}; use crate::key::{PrivateKey, PublicKey}; use crate::{RsaPrivateKey, RsaPublicKey}; +/// RSASSA-PSS signatures as described in [RFC8017 § 8.1]. +/// +/// [RFC8017 § 8.1]: https://datatracker.ietf.org/doc/html/rfc8017#section-8.1 #[derive(Clone)] pub struct Signature { bytes: Vec, @@ -128,6 +137,7 @@ where } /// SignPSS calculates the signature of hashed using RSASSA-PSS. +/// /// Note that hashed must be the result of hashing the input message using the /// given hash function. The opts argument may be nil, in which case sensible /// defaults are used. @@ -172,6 +182,7 @@ fn generate_salt( } /// signPSSWithSalt calculates the signature of hashed using PSS with specified salt. +/// /// Note that hashed must be the result of hashing the input message using the /// given hash function. salt is a random sequence of bytes whose length will be /// later used to verify the signature. @@ -521,6 +532,10 @@ where } } +/// Signing key for producing RSASSA-PSS signatures as described in +/// [RFC8017 § 8.1]. +/// +/// [RFC8017 § 8.1]: https://datatracker.ietf.org/doc/html/rfc8017#section-8.1 #[derive(Debug, Clone)] pub struct SigningKey where @@ -535,10 +550,7 @@ impl SigningKey where D: Digest, { - pub(crate) fn key(&self) -> &RsaPrivateKey { - &self.inner - } - + /// Create a new RSASSA-PSS signing key. pub fn new(key: RsaPrivateKey) -> Self { Self { inner: key, @@ -547,6 +559,7 @@ where } } + /// Create a new RSASSA-PSS signing key with a salt of the given length. pub fn new_with_salt_len(key: RsaPrivateKey, salt_len: usize) -> Self { Self { inner: key, @@ -554,6 +567,10 @@ where phantom: Default::default(), } } + + pub(crate) fn key(&self) -> &RsaPrivateKey { + &self.inner + } } impl From for SigningKey @@ -644,6 +661,8 @@ where } } +/// Signing key for producing "blinded" RSASSA-PSS signatures as described in +/// [draft-irtf-cfrg-rsa-blind-signatures](https://datatracker.ietf.org/doc/draft-irtf-cfrg-rsa-blind-signatures/). #[derive(Debug, Clone)] pub struct BlindedSigningKey where @@ -658,10 +677,8 @@ impl BlindedSigningKey where D: Digest, { - pub(crate) fn key(&self) -> &RsaPrivateKey { - &self.inner - } - + /// Create a new RSASSA-PSS signing key which produces "blinded" + /// signatures. pub fn new(key: RsaPrivateKey) -> Self { Self { inner: key, @@ -670,6 +687,8 @@ where } } + /// Create a new RSASSA-PSS signing key which produces "blinded" + /// signatures with a salt of the given length. pub fn new_with_salt_len(key: RsaPrivateKey, salt_len: usize) -> Self { Self { inner: key, @@ -677,6 +696,10 @@ where phantom: Default::default(), } } + + pub(crate) fn key(&self) -> &RsaPrivateKey { + &self.inner + } } impl From for BlindedSigningKey @@ -767,6 +790,10 @@ where } } +/// Verifying key for checking the validity of RSASSA-PSS signatures as +/// described in [RFC8017 § 8.1]. +/// +/// [RFC8017 § 8.1]: https://datatracker.ietf.org/doc/html/rfc8017#section-8.1 #[derive(Debug, Clone)] pub struct VerifyingKey where @@ -780,6 +807,7 @@ impl VerifyingKey where D: Digest, { + /// Create a new RSASSA-PSS verifying key. pub fn new(key: RsaPublicKey) -> Self { Self { inner: key, @@ -1195,7 +1223,8 @@ mod test { let verifying_key = VerifyingKey::::new(pub_key); for (text, sig, expected) in &tests { - let result = verifying_key.verify_prehash(text.as_ref(), &Signature::from_bytes(sig).unwrap()); + let result = + verifying_key.verify_prehash(text.as_ref(), &Signature::from_bytes(sig).unwrap()); match expected { true => result.expect("failed to verify"), false => { @@ -1216,7 +1245,9 @@ mod test { let verifying_key = VerifyingKey::from(&signing_key); for test in &tests { - let sig = signing_key.sign_prehash_with_rng(&mut rng, &test).expect("failed to sign"); + let sig = signing_key + .sign_prehash_with_rng(&mut rng, &test) + .expect("failed to sign"); verifying_key .verify_prehash(&test, &sig) .expect("failed to verify"); @@ -1234,7 +1265,9 @@ mod test { let verifying_key = VerifyingKey::from(&signing_key); for test in &tests { - let sig = signing_key.sign_prehash_with_rng(&mut rng, &test).expect("failed to sign"); + let sig = signing_key + .sign_prehash_with_rng(&mut rng, &test) + .expect("failed to sign"); verifying_key .verify_prehash(&test, &sig) .expect("failed to verify");