From 80011968f85be9e4ffbb76d14811257321b20161 Mon Sep 17 00:00:00 2001 From: Alex Rudy Date: Sun, 2 Jul 2023 17:31:43 +0000 Subject: [PATCH 1/3] Rudimentary RSA support --- Cargo.toml | 2 +- src/key/ecdsa.rs | 1 + src/key/jwk.rs | 21 ++++++ src/key/mod.rs | 38 +++++++++- src/key/rsa.rs | 175 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 234 insertions(+), 3 deletions(-) create mode 100644 src/key/rsa.rs diff --git a/Cargo.toml b/Cargo.toml index 2f18482..ca740c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ ouroboros = { version = "0.17" } p256 = { version = "0.13", features = ["pkcs8", "arithmetic", "jwk"] } pem-rfc7468 = { version = "0.7", features = ["std"] } pkcs8 = { version = "0.10", features = ["alloc", "pem"] } -rsa = "0.9" +rsa = { version = "0.9", features = ["sha2"] } reqwest = { version = "0.11", features = [ "rustls-tls", "json", diff --git a/src/key/ecdsa.rs b/src/key/ecdsa.rs index 340762f..0912cdc 100644 --- a/src/key/ecdsa.rs +++ b/src/key/ecdsa.rs @@ -36,6 +36,7 @@ impl EcdsaSignature { /// Named elliptic curves supported by Yacme #[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[non_exhaustive] pub enum EcdsaAlgorithm { /// The NIST P-256 (a.k.a. secp256r1, prime256v1) elliptic curve. P256, diff --git a/src/key/jwk.rs b/src/key/jwk.rs index 25d803d..b8e4234 100644 --- a/src/key/jwk.rs +++ b/src/key/jwk.rs @@ -3,6 +3,7 @@ use std::fmt; use base64ct::Encoding; use elliptic_curve::sec1::Coordinates; +use rsa::traits::PublicKeyParts; use serde::ser::{self, SerializeStruct}; use sha2::Digest; @@ -48,6 +49,7 @@ impl Jwk { #[derive(Clone, PartialEq, Eq)] enum InnerJwk { EllipticCurve(elliptic_curve::JwkEcKey), + Rsa(::rsa::RsaPublicKey), } impl ser::Serialize for Jwk { @@ -66,6 +68,19 @@ impl ser::Serialize for Jwk { state.serialize_field("y", &base64ct::Base64UrlUnpadded::encode_string(y))?; state.end() } + InnerJwk::Rsa(rsa) => { + let mut state = serializer.serialize_struct("Jwk", 3)?; + state.serialize_field( + "e", + &base64ct::Base64UrlUnpadded::encode_string(&rsa.e().to_bytes_le()), + )?; + state.serialize_field("kty", "RSA")?; + state.serialize_field( + "n", + &base64ct::Base64UrlUnpadded::encode_string(&rsa.n().to_bytes_le()), + )?; + state.end() + } } } } @@ -75,3 +90,9 @@ impl From for Jwk { Jwk(InnerJwk::EllipticCurve(value)) } } + +impl From<::rsa::RsaPublicKey> for Jwk { + fn from(value: ::rsa::RsaPublicKey) -> Self { + Jwk(InnerJwk::Rsa(value)) + } +} diff --git a/src/key/mod.rs b/src/key/mod.rs index 2c2c162..de5a4f8 100644 --- a/src/key/mod.rs +++ b/src/key/mod.rs @@ -14,10 +14,11 @@ use std::fmt; pub mod cert; mod ecdsa; pub mod jwk; +mod rsa; -use self::ecdsa::EcdsaSigningKey; +use self::{ecdsa::EcdsaSigningKey, rsa::RsaSigningKey}; -pub use self::ecdsa::EcdsaAlgorithm; +pub use self::{ecdsa::EcdsaAlgorithm, rsa::RsaAlgorithm}; pub use p256; pub use pkcs8; @@ -27,6 +28,7 @@ pub use signature; #[derive(Debug)] enum InnerSignature { Ecdsa(self::ecdsa::EcdsaSignature), + Rsa(self::rsa::RsaSignature), } impl From for Signature { @@ -35,16 +37,24 @@ impl From for Signature { } } +impl From for Signature { + fn from(value: self::rsa::RsaSignature) -> Self { + Signature(InnerSignature::Rsa(value)) + } +} + impl InnerSignature { fn to_der(&self) -> der::Document { match self { InnerSignature::Ecdsa(sig) => sig.to_der(), + InnerSignature::Rsa(sig) => sig.to_der(), } } fn to_bytes(&self) -> Box<[u8]> { match self { InnerSignature::Ecdsa(sig) => sig.to_bytes(), + InnerSignature::Rsa(sig) => sig.to_bytes(), } } } @@ -146,9 +156,13 @@ impl EncodePublicKey for PublicKey { /// let algorithm = SignatureKind::Ecdsa(EcdsaAlgorithm::P256); /// ``` #[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[non_exhaustive] pub enum SignatureKind { /// ECDSA using a variety of potential curves Ecdsa(EcdsaAlgorithm), + + /// RSA Signing Key + RSA(RsaAlgorithm), } impl SignatureKind { @@ -156,6 +170,7 @@ impl SignatureKind { pub fn random(&self) -> SigningKey { match self { SignatureKind::Ecdsa(ecdsa) => SigningKey(ecdsa.random().into()), + SignatureKind::RSA(rsa) => SigningKey(rsa.random().into()), } } } @@ -190,6 +205,9 @@ impl SigningKey { SignatureKind::Ecdsa(algorithm) => Ok(SigningKey(InnerSigningKey::Ecdsa(Box::new( EcdsaSigningKey::from_pkcs8_pem(data, algorithm)?, )))), + SignatureKind::RSA(algorithm) => Ok(SigningKey(InnerSigningKey::Rsa(Box::new( + RsaSigningKey::from_pkcs8_pem(data, algorithm)?, + )))), } } @@ -197,6 +215,7 @@ impl SigningKey { pub fn kind(&self) -> SignatureKind { match &self.0 { InnerSigningKey::Ecdsa(key) => key.kind(), + InnerSigningKey::Rsa(key) => key.kind(), } } } @@ -250,6 +269,7 @@ impl signature::RandomizedDigestSigner for SigningKey { pub(crate) enum InnerSigningKey { Ecdsa(Box), + Rsa(Box), //TODO: Consider supporting other algorithms? } @@ -257,18 +277,21 @@ impl InnerSigningKey { pub(crate) fn as_jwk(&self) -> self::jwk::Jwk { match self { InnerSigningKey::Ecdsa(ecdsa) => ecdsa.as_jwk(), + InnerSigningKey::Rsa(rsa) => rsa.as_jwk(), } } pub(crate) fn public_key(&self) -> PublicKey { match self { InnerSigningKey::Ecdsa(ecdsa) => ecdsa.public_key(), + InnerSigningKey::Rsa(rsa) => rsa.public_key(), } } pub(crate) fn algorithm(&self) -> spki::AlgorithmIdentifierOwned { match self { InnerSigningKey::Ecdsa(ecdsa) => ecdsa.algorithm(), + InnerSigningKey::Rsa(rsa) => rsa.algorithm(), } } } @@ -279,6 +302,12 @@ impl From for InnerSigningKey { } } +impl From for InnerSigningKey { + fn from(value: RsaSigningKey) -> Self { + InnerSigningKey::Rsa(Box::new(value) as _) + } +} + impl PartialEq for InnerSigningKey { fn eq(&self, other: &Self) -> bool { self.public_key() == other.public_key() @@ -291,6 +320,7 @@ impl pkcs8::EncodePrivateKey for InnerSigningKey { fn to_pkcs8_der(&self) -> pkcs8::Result { match self { InnerSigningKey::Ecdsa(key) => key.to_pkcs8_der(), + InnerSigningKey::Rsa(key) => key.to_pkcs8_der(), } } } @@ -299,6 +329,7 @@ impl signature::Signer for InnerSigningKey { fn try_sign(&self, msg: &[u8]) -> Result { match self { InnerSigningKey::Ecdsa(key) => key.try_sign(msg), + InnerSigningKey::Rsa(key) => key.try_sign(msg), } } } @@ -307,6 +338,7 @@ impl signature::DigestSigner for InnerSigningKey { fn try_sign_digest(&self, digest: sha2::Sha256) -> Result { match self { InnerSigningKey::Ecdsa(key) => key.try_sign_digest(digest), + InnerSigningKey::Rsa(key) => key.try_sign_digest(digest), } } } @@ -318,6 +350,7 @@ impl signature::RandomizedDigestSigner for InnerSigning ) -> Result { match self { InnerSigningKey::Ecdsa(key) => key.try_sign_digest_with_rng(digest), + InnerSigningKey::Rsa(key) => key.try_sign_digest_with_rng(digest), } } } @@ -326,6 +359,7 @@ impl fmt::Debug for InnerSigningKey { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { InnerSigningKey::Ecdsa(_) => f.write_str("ECDSA-Key"), + InnerSigningKey::Rsa(_) => f.write_str("RSA-Key"), } } } diff --git a/src/key/rsa.rs b/src/key/rsa.rs new file mode 100644 index 0000000..d4eb384 --- /dev/null +++ b/src/key/rsa.rs @@ -0,0 +1,175 @@ +use elliptic_curve::rand_core::OsRng; +use rsa::pkcs8::{DecodePrivateKey, EncodePrivateKey}; +use signature::SignatureEncoding; +use spki::{EncodePublicKey, SignatureBitStringEncoding}; + +use super::{PublicKeyAlgorithm, Signature}; + +/// Named elliptic curves supported by Yacme +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[non_exhaustive] +pub enum RsaAlgorithm { + /// RSA PKCS#1 v1.5 signature using SHA-256 + RS256, + + /// RSA PKCS#1 v1.5 signature using SHA-348 + RS384, + + /// RSA PKCS#1 v1.5 signature using SHA-512 + RS512, +} + +impl RsaAlgorithm { + pub(crate) fn random(&self) -> RsaSigningKey { + let keypair = ::rsa::RsaPrivateKey::new(&mut OsRng, self.key_size()).unwrap(); + RsaSigningKey { + algorithm: *self, + keypair, + } + } + + pub(crate) fn key_size(&self) -> usize { + 2048 + } +} + +#[derive(Debug)] +pub(crate) enum RsaSignature { + PKCS1v15(::rsa::pkcs1v15::Signature), +} + +impl From<::rsa::pkcs1v15::Signature> for RsaSignature { + fn from(value: ::rsa::pkcs1v15::Signature) -> Self { + RsaSignature::PKCS1v15(value) + } +} + +impl RsaSignature { + pub(crate) fn to_bytes(&self) -> Box<[u8]> { + match self { + RsaSignature::PKCS1v15(signature) => signature.to_vec().into(), + } + } + + pub(crate) fn to_der(&self) -> der::Document { + match self { + RsaSignature::PKCS1v15(signature) => { + der::Document::encode_msg(&signature.to_bitstring().unwrap()).unwrap() + } + } + } +} + +/// Implements the RSA signature scheme across algorithms +#[derive(Debug, PartialEq, Eq)] +pub struct RsaSigningKey { + algorithm: RsaAlgorithm, + keypair: ::rsa::RsaPrivateKey, +} + +impl signature::Signer for RsaSigningKey { + fn try_sign(&self, msg: &[u8]) -> Result { + match self.algorithm { + RsaAlgorithm::RS256 => { + rsa::pkcs1v15::SigningKey::::from(self.keypair.clone()) + .try_sign(msg) + .map(|s| RsaSignature::from(s).into()) + } + RsaAlgorithm::RS384 => { + rsa::pkcs1v15::SigningKey::::from(self.keypair.clone()) + .try_sign(msg) + .map(|s| RsaSignature::from(s).into()) + } + RsaAlgorithm::RS512 => { + rsa::pkcs1v15::SigningKey::::from(self.keypair.clone()) + .try_sign(msg) + .map(|s| RsaSignature::from(s).into()) + } + } + } +} + +impl super::SigningKeyAlgorithm for RsaSigningKey { + fn as_jwk(&self) -> super::jwk::Jwk { + let key = self.keypair.to_public_key(); + key.into() + } + + fn public_key(&self) -> super::PublicKey { + Box::new(RsaPublicKey::PKCS1v15(self.keypair.to_public_key())).into() + } + + #[allow(unused_variables)] + fn try_sign_digest(&self, digest: sha2::Sha256) -> Result { + todo!("Digest signing not implemented for RSA") + } + + fn algorithm(&self) -> spki::AlgorithmIdentifierOwned { + spki::AlgorithmIdentifier { + oid: const_oid::db::rfc5912::RSA_ENCRYPTION, + parameters: None, + } + } + + fn kind(&self) -> super::SignatureKind { + super::SignatureKind::RSA(self.algorithm) + } + + fn to_pkcs8_der(&self) -> pkcs8::Result { + self.keypair.to_pkcs8_der().map(|d| d.into()) + } + + #[allow(unused_variables)] + fn try_sign_digest_with_rng(&self, digest: sha2::Sha256) -> Result { + todo!("Digest with rng signing not implemented for RSA"); + } +} + +impl RsaSigningKey { + pub fn new(algorithm: RsaAlgorithm, keypair: ::rsa::RsaPrivateKey) -> Self { + Self { algorithm, keypair } + } + + pub(crate) fn from_pkcs8_pem( + data: &str, + algorithm: RsaAlgorithm, + ) -> Result { + let key = ::rsa::RsaPrivateKey::from_pkcs8_der(data.as_bytes())?; + Ok(Self::new(algorithm, key)) + } +} + +enum RsaPublicKey { + PKCS1v15(::rsa::RsaPublicKey), +} + +impl From<::rsa::RsaPublicKey> for RsaPublicKey { + fn from(value: ::rsa::RsaPublicKey) -> Self { + RsaPublicKey::PKCS1v15(value) + } +} + +impl PublicKeyAlgorithm for RsaPublicKey { + fn as_jwk(&self) -> super::jwk::Jwk { + match self { + RsaPublicKey::PKCS1v15(key) => key.clone().into(), + } + } + + fn algorithm(&self) -> spki::AlgorithmIdentifierOwned { + spki::AlgorithmIdentifierOwned { + oid: const_oid::db::rfc5912::RSA_ENCRYPTION, + parameters: None, + } + } + + fn as_bytes(&self) -> Vec { + todo!("as_bytes not implemented for RSA") + } + + fn to_public_key_der(&self) -> pkcs8::spki::Result { + match self { + RsaPublicKey::PKCS1v15(key) => key.to_public_key_der(), + } + } +} From dd7f73ec8f0ea8aabb8850f3d70578041e767b3c Mon Sep 17 00:00:00 2001 From: Alex Rudy Date: Sun, 2 Jul 2023 19:00:59 +0000 Subject: [PATCH 2/3] Support PKCS#1 RSA keys --- src/key/mod.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/key/mod.rs b/src/key/mod.rs index de5a4f8..acc8c78 100644 --- a/src/key/mod.rs +++ b/src/key/mod.rs @@ -20,6 +20,7 @@ use self::{ecdsa::EcdsaSigningKey, rsa::RsaSigningKey}; pub use self::{ecdsa::EcdsaAlgorithm, rsa::RsaAlgorithm}; +use ::rsa::pkcs1::DecodeRsaPrivateKey; pub use p256; pub use pkcs8; use pkcs8::EncodePublicKey; @@ -197,6 +198,23 @@ impl SignatureKind { pub struct SigningKey(InnerSigningKey); impl SigningKey { + /// Read an RSA private key from a PEM-encoded PKCS#1 format key (usually the kind produced by OpenSSL) + /// into the current signing key document. You must provide the signature kind, as the code + /// does not currently infer the type of key in use. + pub fn from_pkcs1_pem( + data: &str, + signature: SignatureKind, + ) -> Result { + match signature { + SignatureKind::Ecdsa(_) => panic!("ECDSA not supported by PKCS1"), + SignatureKind::RSA(alg) => { + let keypair = ::rsa::RsaPrivateKey::from_pkcs1_pem(data)?; + let signing_key = self::rsa::RsaSigningKey::new(alg, keypair); + Ok(SigningKey(signing_key.into())) + } + } + } + /// Read a private key from a PEM-encoded PKCS#8 format key (usually the kind produced by /// OpenSSL) into this signing key document. You must provide the signature kind, as the code /// does not currently infer the type of key in use. From 59f3bee59fe5c912807bdc2da2dae26cb6eebdf4 Mon Sep 17 00:00:00 2001 From: Alex Rudy Date: Sun, 2 Jul 2023 19:20:19 +0000 Subject: [PATCH 3/3] Fix decode rsa key as PEM bug --- src/key/rsa.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/key/rsa.rs b/src/key/rsa.rs index d4eb384..f2d15b4 100644 --- a/src/key/rsa.rs +++ b/src/key/rsa.rs @@ -116,7 +116,7 @@ impl super::SigningKeyAlgorithm for RsaSigningKey { } fn to_pkcs8_der(&self) -> pkcs8::Result { - self.keypair.to_pkcs8_der().map(|d| d.into()) + self.keypair.to_pkcs8_der() } #[allow(unused_variables)] @@ -134,7 +134,7 @@ impl RsaSigningKey { data: &str, algorithm: RsaAlgorithm, ) -> Result { - let key = ::rsa::RsaPrivateKey::from_pkcs8_der(data.as_bytes())?; + let key = ::rsa::RsaPrivateKey::from_pkcs8_pem(data)?; Ok(Self::new(algorithm, key)) } }