Skip to content

Commit

Permalink
Merge pull request #18 from alexrudy/spki
Browse files Browse the repository at this point in the history
SPKI and DER support for SignatureBytes
  • Loading branch information
alexrudy authored Dec 4, 2023
2 parents 7c33ef5 + b1fc535 commit 024739c
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 74 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -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"] }
Expand All @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion src/algorithms/ecdsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ where
::ecdsa::Signature<C>: SignatureEncoding,
{
fn from(sig: ::ecdsa::Signature<C>) -> Self {
Self(Bytes::copy_from_slice(sig.to_bytes().as_ref()))
Self::from(Bytes::copy_from_slice(sig.to_bytes().as_ref()))
}
}

Expand Down
86 changes: 13 additions & 73 deletions src/algorithms/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -75,6 +72,10 @@ pub use sha2::Sha512;

use crate::key::SerializePublicJWK;

mod sig;

pub use sig::SignatureBytes;

#[cfg(feature = "ecdsa")]
pub mod ecdsa;

Expand Down Expand Up @@ -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<S, signature::Error>;
fn try_sign_token(&self, header: &str, payload: &str) -> Result<S, sig::Error>;

/// 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
Expand All @@ -214,10 +215,10 @@ where
impl<K, S> TokenSigner<S> for K
where
K: JsonWebAlgorithmDigest + SerializePublicJWK,
K: signature::DigestSigner<K::Digest, S>,
K: sig::DigestSigner<K::Digest, S>,
S: SignatureEncoding,
{
fn try_sign_token(&self, header: &str, payload: &str) -> Result<S, signature::Error> {
fn try_sign_token(&self, header: &str, payload: &str) -> Result<S, sig::Error> {
let mut digest = <Self as JsonWebAlgorithmDigest>::Digest::new();
digest.update(header.as_bytes());
digest.update(b".");
Expand All @@ -243,7 +244,7 @@ where
header: &str,
payload: &str,
rng: &mut impl CryptoRngCore,
) -> Result<S, signature::Error>;
) -> Result<S, sig::Error>;

/// 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
Expand Down Expand Up @@ -272,13 +273,13 @@ where
header: &[u8],
payload: &[u8],
signature: &[u8],
) -> Result<S, signature::Error>;
) -> Result<S, sig::Error>;
}

impl<K, S> TokenVerifier<S> for K
where
K: JsonWebAlgorithmDigest + std::fmt::Debug,
K: signature::DigestVerifier<K::Digest, S>,
K: sig::DigestVerifier<K::Digest, S>,
K::Digest: Clone + std::fmt::Debug,
S: SignatureEncoding + std::fmt::Debug,
for<'a> <S as TryFrom<&'a [u8]>>::Error: std::error::Error + Send + Sync + 'static,
Expand All @@ -288,80 +289,19 @@ where
header: &[u8],
payload: &[u8],
signature: &[u8],
) -> Result<S, signature::Error> {
) -> Result<S, sig::Error> {
let mut digest = <Self as JsonWebAlgorithmDigest>::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<Self, base64ct::Error> {
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<Bytes> for SignatureBytes {
fn from(bytes: Bytes) -> Self {
SignatureBytes(bytes)
}
}

impl From<SignatureBytes> for Bytes {
fn from(bytes: SignatureBytes) -> Self {
bytes.0.clone()
}
}

impl From<Vec<u8>> for SignatureBytes {
fn from(bytes: Vec<u8>) -> 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 {
Expand Down
109 changes: 109 additions & 0 deletions src/algorithms/sig.rs
Original file line number Diff line number Diff line change
@@ -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<Self, base64ct::Error> {
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<Bytes> for SignatureBytes {
fn from(bytes: Bytes) -> Self {
SignatureBytes(bytes)
}
}

impl From<SignatureBytes> for Bytes {
fn from(bytes: SignatureBytes) -> Self {
bytes.0.clone()
}
}

impl From<Vec<u8>> for SignatureBytes {
fn from(bytes: Vec<u8>) -> 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> {
spki::der::asn1::BitString::from_bytes(self.0.as_ref())
}
}

#[cfg(feature = "der")]
impl<'c> der::Decode<'c> for SignatureBytes {
fn decode<R: der::Reader<'c>>(reader: &mut R) -> der::Result<Self> {
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> {
der::Length::try_from(self.0.len())
}

fn encode(&self, writer: &mut impl der::Writer) -> der::Result<()> {
writer.write(self.0.as_ref())
}
}

0 comments on commit 024739c

Please sign in to comment.