Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SPKI and DER support for SignatureBytes #18

Merged
merged 2 commits into from
Dec 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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())
}
}
Loading