diff --git a/Cargo.toml b/Cargo.toml index 9266e16..e738aa6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ categories = [ base64ct = { version = "1.6", features = ["std"] } bytes = { version = "1.5" } chrono = { version = "0.4", features = ["serde"] } +der = { version = "0.7.8", optional = true } digest = { version = "0.10" } ecdsa = { version = "0.16", features = ["signing", "der"], optional = true } hmac = { version = "0.12", optional = true } @@ -32,6 +33,7 @@ serde_json = "1" sha1 = "0.10" sha2 = "0.10" signature = { version = "2.2", features = ["digest", "std"] } +spki = { version = "0.7", optional = true } thiserror = "1" url = { version = "2.5", features = ["serde"] } zeroize = { version = "1.7", features = ["serde", "derive"] } @@ -54,6 +56,8 @@ ecdsa = ["dep:ecdsa", "dep:elliptic-curve"] p256 = ["dep:p256", "ecdsa"] p384 = ["dep:p384", "ecdsa"] p521 = ["dep:p521", "ecdsa"] +spki = ["dep:spki"] +der = ["dep:der"] [[example]] name = "acme-new-account" diff --git a/src/algorithms/ecdsa.rs b/src/algorithms/ecdsa.rs index f4317e8..be9f662 100644 --- a/src/algorithms/ecdsa.rs +++ b/src/algorithms/ecdsa.rs @@ -113,7 +113,7 @@ where ::ecdsa::Signature: SignatureEncoding, { fn from(sig: ::ecdsa::Signature) -> Self { - Self(Bytes::copy_from_slice(sig.to_bytes().as_ref())) + Self::from(Bytes::copy_from_slice(sig.to_bytes().as_ref())) } } diff --git a/src/algorithms/mod.rs b/src/algorithms/mod.rs index 9f3d6c2..d1249c3 100644 --- a/src/algorithms/mod.rs +++ b/src/algorithms/mod.rs @@ -54,15 +54,12 @@ //! //! [RFC7518]: https://tools.ietf.org/html/rfc7518 -use std::fmt; +use ::signature::SignatureEncoding; -use base64ct::Encoding; -use bytes::Bytes; use digest::Digest; #[cfg(feature = "rand")] use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; -use signature::SignatureEncoding; #[cfg(any(feature = "p256", feature = "hmac", feature = "rsa"))] pub use sha2::Sha256; @@ -75,6 +72,10 @@ pub use sha2::Sha512; use crate::key::SerializePublicJWK; +mod sig; + +pub use sig::SignatureBytes; + #[cfg(feature = "ecdsa")] pub mod ecdsa; @@ -197,7 +198,7 @@ where /// Sign the contents of the JWT, when provided with the base64url-encoded header /// and payload. This is the JWS Signature value, and will be base64url-encoded /// and appended to the compact representation of the JWT. - fn try_sign_token(&self, header: &str, payload: &str) -> Result; + fn try_sign_token(&self, header: &str, payload: &str) -> Result; /// Sign the contents of the JWT, when provided with the base64url-encoded header /// and payload. This is the JWS Signature value, and will be base64url-encoded @@ -214,10 +215,10 @@ where impl TokenSigner for K where K: JsonWebAlgorithmDigest + SerializePublicJWK, - K: signature::DigestSigner, + K: sig::DigestSigner, S: SignatureEncoding, { - fn try_sign_token(&self, header: &str, payload: &str) -> Result { + fn try_sign_token(&self, header: &str, payload: &str) -> Result { let mut digest = ::Digest::new(); digest.update(header.as_bytes()); digest.update(b"."); @@ -243,7 +244,7 @@ where header: &str, payload: &str, rng: &mut impl CryptoRngCore, - ) -> Result; + ) -> Result; /// Sign the contents of the JWT, when provided with the base64url-encoded header /// and payload. This is the JWS Signature value, and will be base64url-encoded @@ -272,13 +273,13 @@ where header: &[u8], payload: &[u8], signature: &[u8], - ) -> Result; + ) -> Result; } impl TokenVerifier for K where K: JsonWebAlgorithmDigest + std::fmt::Debug, - K: signature::DigestVerifier, + K: sig::DigestVerifier, K::Digest: Clone + std::fmt::Debug, S: SignatureEncoding + std::fmt::Debug, for<'a> >::Error: std::error::Error + Send + Sync + 'static, @@ -288,80 +289,19 @@ where header: &[u8], payload: &[u8], signature: &[u8], - ) -> Result { + ) -> Result { let mut digest = ::Digest::new(); digest.update(header); digest.update(b"."); digest.update(payload); - let signature = signature - .try_into() - .map_err(signature::Error::from_source)?; + let signature = signature.try_into().map_err(sig::Error::from_source)?; self.verify_digest(digest, &signature)?; Ok(signature) } } -/// A signature which has not been matched to an algorithm or key. -/// -/// This is a basic signature `struct` which can be used to store any signature -/// on the heap. It is used to store the signature of a JWT before it is verified, -/// or if a signature has a variable length. -#[derive(Clone, PartialEq, Eq, Hash)] -pub struct SignatureBytes(Bytes); - -impl SignatureBytes { - /// Create a new signature from a base64url-encoded string. - pub fn from_b64url(data: &str) -> Result { - Ok(Self(Bytes::from(base64ct::Base64UrlUnpadded::decode_vec( - data, - )?))) - } -} - -impl fmt::Debug for SignatureBytes { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("SignatureBytes") - .field(&base64ct::Base64UrlUnpadded::encode_string(self.0.as_ref())) - .finish() - } -} - -impl AsRef<[u8]> for SignatureBytes { - fn as_ref(&self) -> &[u8] { - &self.0 - } -} - -impl From<&[u8]> for SignatureBytes { - fn from(bytes: &[u8]) -> Self { - SignatureBytes(bytes.to_owned().into()) - } -} - -impl From for SignatureBytes { - fn from(bytes: Bytes) -> Self { - SignatureBytes(bytes) - } -} - -impl From for Bytes { - fn from(bytes: SignatureBytes) -> Self { - bytes.0.clone() - } -} - -impl From> for SignatureBytes { - fn from(bytes: Vec) -> Self { - SignatureBytes(bytes.into()) - } -} - -impl signature::SignatureEncoding for SignatureBytes { - type Repr = Bytes; -} - /// A macro to implement the required traits for common JWS alogorithms. #[macro_export] macro_rules! jose_algorithm { diff --git a/src/algorithms/sig.rs b/src/algorithms/sig.rs new file mode 100644 index 0000000..c7040e7 --- /dev/null +++ b/src/algorithms/sig.rs @@ -0,0 +1,109 @@ +use std::fmt; + +use base64ct::Encoding; +use bytes::Bytes; + +pub use signature::*; + +/// A signature which has not been matched to an algorithm or key. +/// +/// This is a basic signature `struct` which can be used to store any signature +/// on the heap. It is used to store the signature of a JWT before it is verified, +/// or if a signature has a variable length. +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct SignatureBytes(Bytes); + +impl SignatureBytes { + /// Create a new signature from a base64url-encoded string. + pub fn from_b64url(data: &str) -> std::result::Result { + Ok(Self(Bytes::from(base64ct::Base64UrlUnpadded::decode_vec( + data, + )?))) + } +} + +impl fmt::Debug for SignatureBytes { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("SignatureBytes") + .field(&base64ct::Base64UrlUnpadded::encode_string(self.0.as_ref())) + .finish() + } +} + +impl AsRef<[u8]> for SignatureBytes { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl From<&[u8]> for SignatureBytes { + fn from(bytes: &[u8]) -> Self { + SignatureBytes(bytes.to_owned().into()) + } +} + +impl From for SignatureBytes { + fn from(bytes: Bytes) -> Self { + SignatureBytes(bytes) + } +} + +impl From for Bytes { + fn from(bytes: SignatureBytes) -> Self { + bytes.0.clone() + } +} + +impl From> for SignatureBytes { + fn from(bytes: Vec) -> Self { + SignatureBytes(bytes.into()) + } +} + +impl signature::SignatureEncoding for SignatureBytes { + type Repr = Bytes; +} + +#[cfg(feature = "spki")] +impl spki::SignatureBitStringEncoding for SignatureBytes { + fn to_bitstring(&self) -> spki::der::Result { + spki::der::asn1::BitString::from_bytes(self.0.as_ref()) + } +} + +#[cfg(feature = "der")] +impl<'c> der::Decode<'c> for SignatureBytes { + fn decode>(reader: &mut R) -> der::Result { + use der::Encode; + + let header = reader.peek_header()?; + header.tag.assert_eq(der::Tag::Sequence)?; + + let len = (header.encoded_len()? + header.length)?; + let mut buf = Vec::with_capacity(usize::try_from(len)?); + let slice = buf + .get_mut(..usize::try_from(len)?) + .ok_or_else(|| reader.error(der::Tag::Sequence.length_error().kind()))?; + + reader.read_into(slice)?; + Ok(Self::from(buf)) + } +} + +#[cfg(feature = "der")] +impl der::Tagged for SignatureBytes { + fn tag(&self) -> der::Tag { + der::Tag::Sequence + } +} + +#[cfg(feature = "der")] +impl der::Encode for SignatureBytes { + fn encoded_len(&self) -> der::Result { + der::Length::try_from(self.0.len()) + } + + fn encode(&self, writer: &mut impl der::Writer) -> der::Result<()> { + writer.write(self.0.as_ref()) + } +}