Skip to content

Commit 7821a61

Browse files
author
andrew
committed
Changes to support ACME, including JWS
1 parent 193eb8d commit 7821a61

File tree

4 files changed

+99
-2
lines changed

4 files changed

+99
-2
lines changed

src/decoding.rs

+56
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::crypto::verify;
66
use crate::errors::{new_error, ErrorKind, Result};
77
use crate::header::Header;
88
use crate::jwk::{AlgorithmParameters, Jwk};
9+
use crate::jws::Jws;
910
#[cfg(feature = "use_pem")]
1011
use crate::pem::decoder::PemEncodedKey;
1112
use crate::serialization::{b64_decode, DecodedJwtPartClaims};
@@ -286,3 +287,58 @@ pub fn decode_header(token: &str) -> Result<Header> {
286287
let (_, header) = expect_two!(message.rsplitn(2, '.'));
287288
Header::from_encoded(header)
288289
}
290+
291+
/// Verify signature of a JWS, and return the header object
292+
///
293+
/// If the token or its signature is invalid, it will return an error.
294+
fn verify_jws_signature<T>(
295+
jws: &Jws<T>,
296+
key: &DecodingKey,
297+
validation: &Validation,
298+
) -> Result<Header> {
299+
if validation.validate_signature && validation.algorithms.is_empty() {
300+
return Err(new_error(ErrorKind::MissingAlgorithm));
301+
}
302+
303+
if validation.validate_signature {
304+
for alg in &validation.algorithms {
305+
if key.family != alg.family() {
306+
return Err(new_error(ErrorKind::InvalidAlgorithm));
307+
}
308+
}
309+
}
310+
311+
let header = Header::from_encoded(&jws.protected)?;
312+
313+
if validation.validate_signature && !validation.algorithms.contains(&header.alg) {
314+
return Err(new_error(ErrorKind::InvalidAlgorithm));
315+
}
316+
317+
let message = [jws.protected.as_str(), jws.payload.as_str()].join(".");
318+
319+
if validation.validate_signature
320+
&& !verify(&jws.signature, message.as_bytes(), key, header.alg)?
321+
{
322+
return Err(new_error(ErrorKind::InvalidSignature));
323+
}
324+
325+
Ok(header)
326+
}
327+
328+
/// Validate a received JWS and decode into the header and claims.
329+
pub fn decode_jws<T: DeserializeOwned>(
330+
jws: &Jws<T>,
331+
key: &DecodingKey,
332+
validation: &Validation,
333+
) -> Result<TokenData<T>> {
334+
match verify_jws_signature(jws, key, validation) {
335+
Err(e) => Err(e),
336+
Ok(header) => {
337+
let decoded_claims = DecodedJwtPartClaims::from_jwt_part_claims(&jws.payload)?;
338+
let claims = decoded_claims.deserialize()?;
339+
validate(decoded_claims.deserialize()?, validation)?;
340+
341+
Ok(TokenData { header, claims })
342+
}
343+
}
344+
}

src/encoding.rs

+28
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::algorithms::AlgorithmFamily;
55
use crate::crypto;
66
use crate::errors::{new_error, ErrorKind, Result};
77
use crate::header::Header;
8+
use crate::jws::Jws;
89
#[cfg(feature = "use_pem")]
910
use crate::pem::decoder::PemEncodedKey;
1011
use crate::serialization::b64_encode_part;
@@ -129,3 +130,30 @@ pub fn encode<T: Serialize>(header: &Header, claims: &T, key: &EncodingKey) -> R
129130

130131
Ok([message, signature].join("."))
131132
}
133+
134+
/// Encode the header and claims given and sign the payload using the algorithm from the header and the key.
135+
/// If the algorithm given is RSA or EC, the key needs to be in the PEM format. This produces a JWS instead of
136+
/// a JWT -- usage is similar to `encode`, see that for more details.
137+
pub fn encode_jws<T: Serialize>(
138+
header: &Header,
139+
claims: Option<&T>,
140+
key: &EncodingKey,
141+
) -> Result<Jws<T>> {
142+
if key.family != header.alg.family() {
143+
return Err(new_error(ErrorKind::InvalidAlgorithm));
144+
}
145+
let encoded_header = b64_encode_part(header)?;
146+
let encoded_claims = match claims {
147+
Some(claims) => b64_encode_part(claims)?,
148+
None => "".to_string(),
149+
};
150+
let message = [encoded_header.as_str(), encoded_claims.as_str()].join(".");
151+
let signature = crypto::sign(message.as_bytes(), key, header.alg)?;
152+
153+
Ok(Jws {
154+
protected: encoded_header,
155+
payload: encoded_claims,
156+
signature: signature,
157+
_pd: Default::default(),
158+
})
159+
}

src/header.rs

+12
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,16 @@ pub struct Header {
6464
#[serde(skip_serializing_if = "Option::is_none")]
6565
#[serde(rename = "x5t#S256")]
6666
pub x5t_s256: Option<String>,
67+
/// ACME: The URL to which this JWS object is directed
68+
///
69+
/// Defined in [RFC8555#6.4](https://datatracker.ietf.org/doc/html/rfc8555#section-6.4).
70+
#[serde(skip_serializing_if = "Option::is_none")]
71+
pub url: Option<String>,
72+
/// ACME: Random data for preventing replay attacks.
73+
///
74+
/// Defined in [RFC8555#6.5.2](https://datatracker.ietf.org/doc/html/rfc8555#section-6.5.2).
75+
#[serde(skip_serializing_if = "Option::is_none")]
76+
pub nonce: Option<String>,
6777
}
6878

6979
impl Header {
@@ -80,6 +90,8 @@ impl Header {
8090
x5c: None,
8191
x5t: None,
8292
x5t_s256: None,
93+
url: None,
94+
nonce: None,
8395
}
8496
}
8597

src/lib.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@ mod encoding;
1212
pub mod errors;
1313
mod header;
1414
pub mod jwk;
15+
pub mod jws;
1516
#[cfg(feature = "use_pem")]
1617
mod pem;
1718
mod serialization;
1819
mod validation;
1920

2021
pub use algorithms::Algorithm;
21-
pub use decoding::{decode, decode_header, DecodingKey, TokenData};
22-
pub use encoding::{encode, EncodingKey};
22+
pub use decoding::{decode, decode_header, decode_jws, DecodingKey, TokenData};
23+
pub use encoding::{encode, encode_jws, EncodingKey};
2324
pub use header::Header;
2425
pub use validation::{get_current_timestamp, Validation};

0 commit comments

Comments
 (0)