Skip to content

Commit

Permalink
Changes to support ACME, including JWS
Browse files Browse the repository at this point in the history
  • Loading branch information
andrew committed Jan 10, 2024
1 parent 193eb8d commit fd60bf2
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 2 deletions.
56 changes: 56 additions & 0 deletions src/decoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::crypto::verify;
use crate::errors::{new_error, ErrorKind, Result};
use crate::header::Header;
use crate::jwk::{AlgorithmParameters, Jwk};
use crate::jws::Jws;
#[cfg(feature = "use_pem")]
use crate::pem::decoder::PemEncodedKey;
use crate::serialization::{b64_decode, DecodedJwtPartClaims};
Expand Down Expand Up @@ -286,3 +287,58 @@ pub fn decode_header(token: &str) -> Result<Header> {
let (_, header) = expect_two!(message.rsplitn(2, '.'));
Header::from_encoded(header)
}

/// Verify signature of a JWS, and return the header object
///
/// If the token or its signature is invalid, it will return an error.
fn verify_jws_signature<T>(
jws: &Jws<T>,
key: &DecodingKey,
validation: &Validation,
) -> Result<Header> {
if validation.validate_signature && validation.algorithms.is_empty() {
return Err(new_error(ErrorKind::MissingAlgorithm));
}

if validation.validate_signature {
for alg in &validation.algorithms {
if key.family != alg.family() {
return Err(new_error(ErrorKind::InvalidAlgorithm));
}
}
}

let header = Header::from_encoded(&jws.protected)?;

if validation.validate_signature && !validation.algorithms.contains(&header.alg) {
return Err(new_error(ErrorKind::InvalidAlgorithm));
}

let message = [jws.protected.as_str(), jws.payload.as_str()].join(".");

if validation.validate_signature
&& !verify(&jws.signature, message.as_bytes(), key, header.alg)?
{
return Err(new_error(ErrorKind::InvalidSignature));
}

Ok(header)
}

/// Validate a received JWS and decode into the header and claims.
pub fn decode_jws<T: DeserializeOwned>(
jws: &Jws<T>,
key: &DecodingKey,
validation: &Validation,
) -> Result<TokenData<T>> {
match verify_jws_signature(jws, key, validation) {
Err(e) => Err(e),
Ok(header) => {
let decoded_claims = DecodedJwtPartClaims::from_jwt_part_claims(&jws.payload)?;
let claims = decoded_claims.deserialize()?;
validate(decoded_claims.deserialize()?, validation)?;

Ok(TokenData { header, claims })
}
}
}
28 changes: 28 additions & 0 deletions src/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::algorithms::AlgorithmFamily;
use crate::crypto;
use crate::errors::{new_error, ErrorKind, Result};
use crate::header::Header;
use crate::jws::Jws;
#[cfg(feature = "use_pem")]
use crate::pem::decoder::PemEncodedKey;
use crate::serialization::b64_encode_part;
Expand Down Expand Up @@ -129,3 +130,30 @@ pub fn encode<T: Serialize>(header: &Header, claims: &T, key: &EncodingKey) -> R

Ok([message, signature].join("."))
}

/// Encode the header and claims given and sign the payload using the algorithm from the header and the key.
/// If the algorithm given is RSA or EC, the key needs to be in the PEM format. This produces a JWS instead of
/// a JWT -- usage is similar to `encode`, see that for more details.
pub fn encode_jws<T: Serialize>(
header: &Header,
claims: Option<&T>,
key: &EncodingKey,
) -> Result<Jws<T>> {
if key.family != header.alg.family() {
return Err(new_error(ErrorKind::InvalidAlgorithm));
}
let encoded_header = b64_encode_part(header)?;
let encoded_claims = match claims {
Some(claims) => b64_encode_part(claims)?,
None => "".to_string(),
};
let message = [encoded_header.as_str(), encoded_claims.as_str()].join(".");
let signature = crypto::sign(message.as_bytes(), key, header.alg)?;

Ok(Jws {
protected: encoded_header,
payload: encoded_claims,
signature: signature,
_pd: Default::default(),
})
}
12 changes: 12 additions & 0 deletions src/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,16 @@ pub struct Header {
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "x5t#S256")]
pub x5t_s256: Option<String>,
/// ACME: The URL to which this JWS object is directed
///
/// Defined in [RFC8555#6.4](https://datatracker.ietf.org/doc/html/rfc8555#section-6.4).
#[serde(skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
/// ACME: Random data for preventing replay attacks.
///
/// Defined in [RFC8555#6.5.2](https://datatracker.ietf.org/doc/html/rfc8555#section-6.5.2).
#[serde(skip_serializing_if = "Option::is_none")]
pub nonce: Option<String>,
}

impl Header {
Expand All @@ -80,6 +90,8 @@ impl Header {
x5c: None,
x5t: None,
x5t_s256: None,
url: None,
nonce: None,
}
}

Expand Down
24 changes: 24 additions & 0 deletions src/jws.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//! JSON Web Signatures data type.
use std::marker::PhantomData;

use serde::{Deserialize, Serialize};

/// This is a serde-compatible JSON Web Signature structure.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Jws<C> {
/// The base64 encoded header data.
///
/// Defined in [RFC7515#3.2](https://tools.ietf.org/html/rfc7515#section-3.2).
pub protected: String,
/// The base64 encoded claims data.
///
/// Defined in [RFC7515#3.2](https://tools.ietf.org/html/rfc7515#section-3.2).
pub payload: String,
/// The signature on the other fields.
///
/// Defined in [RFC7515#3.2](https://tools.ietf.org/html/rfc7515#section-3.2).
pub signature: String,
/// Unused, for associating type metadata.
#[serde(skip)]
pub _pd: PhantomData<C>,
}
5 changes: 3 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ mod encoding;
pub mod errors;
mod header;
pub mod jwk;
pub mod jws;
#[cfg(feature = "use_pem")]
mod pem;
mod serialization;
mod validation;

pub use algorithms::Algorithm;
pub use decoding::{decode, decode_header, DecodingKey, TokenData};
pub use encoding::{encode, EncodingKey};
pub use decoding::{decode, decode_header, decode_jws, DecodingKey, TokenData};
pub use encoding::{encode, encode_jws, EncodingKey};
pub use header::Header;
pub use validation::{get_current_timestamp, Validation};

0 comments on commit fd60bf2

Please sign in to comment.