Skip to content

Commit b0072fd

Browse files
author
andrew
committed
Crit, zip, enc, readme, refactor
1 parent fd60bf2 commit b0072fd

File tree

3 files changed

+149
-33
lines changed

3 files changed

+149
-33
lines changed

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,20 @@ let token = decode::<Claims>(&token, &DecodingKey::from_rsa_components(jwk["n"],
146146
If your key is in PEM format, it is better performance wise to generate the `DecodingKey` once in a `lazy_static` or
147147
something similar and reuse it.
148148

149+
### Encoding and decoding JWS
150+
151+
JWS is handled the same way as JWT, but using `encode_jws` and `decode_jws`:
152+
153+
```rust
154+
let encoded = encode_jws(&Header::default(), &my_claims, &EncodingKey::from_secret("secret".as_ref()))?;
155+
my_claims = decode_jws(&encoded, &DecodingKey::from_secret("secret".as_ref()), &Validation::default())?.claims;
156+
```
157+
158+
`encode_jws` returns a `Jws<C>` struct which can be placed in other structs or serialized/deserialized from JSON directly.
159+
160+
The generic parameter in `Jws<C>` indicates the claims type and prevents accidentally encoding or decoding the wrong claims type
161+
when the Jws is nested in another struct.
162+
149163
### Convert SEC1 private key to PKCS8
150164
`jsonwebtoken` currently only supports PKCS8 format for private EC keys. If your key has `BEGIN EC PRIVATE KEY` at the top,
151165
this is a SEC1 type and can be converted to PKCS8 like so:

src/decoding.rs

Lines changed: 23 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -202,14 +202,13 @@ impl DecodingKey {
202202
}
203203
}
204204

205-
/// Verify signature of a JWT, and return header object and raw payload
206-
///
207-
/// If the token or its signature is invalid, it will return an error.
208-
fn verify_signature<'a>(
209-
token: &'a str,
205+
fn verify_signature_body(
206+
header: &Header,
207+
message: &str,
208+
signature: &str,
210209
key: &DecodingKey,
211210
validation: &Validation,
212-
) -> Result<(Header, &'a str)> {
211+
) -> Result<()> {
213212
if validation.validate_signature && validation.algorithms.is_empty() {
214213
return Err(new_error(ErrorKind::MissingAlgorithm));
215214
}
@@ -222,10 +221,6 @@ fn verify_signature<'a>(
222221
}
223222
}
224223

225-
let (signature, message) = expect_two!(token.rsplitn(2, '.'));
226-
let (payload, header) = expect_two!(message.rsplitn(2, '.'));
227-
let header = Header::from_encoded(header)?;
228-
229224
if validation.validate_signature && !validation.algorithms.contains(&header.alg) {
230225
return Err(new_error(ErrorKind::InvalidAlgorithm));
231226
}
@@ -234,6 +229,23 @@ fn verify_signature<'a>(
234229
return Err(new_error(ErrorKind::InvalidSignature));
235230
}
236231

232+
return Ok(());
233+
}
234+
235+
/// Verify signature of a JWT, and return header object and raw payload
236+
///
237+
/// If the token or its signature is invalid, it will return an error.
238+
fn verify_signature<'a>(
239+
token: &'a str,
240+
key: &DecodingKey,
241+
validation: &Validation,
242+
) -> Result<(Header, &'a str)> {
243+
let (signature, message) = expect_two!(token.rsplitn(2, '.'));
244+
let (payload, header) = expect_two!(message.rsplitn(2, '.'));
245+
let header = Header::from_encoded(header)?;
246+
247+
verify_signature_body(&header, message, signature, key, validation)?;
248+
237249
Ok((header, payload))
238250
}
239251

@@ -296,31 +308,10 @@ fn verify_jws_signature<T>(
296308
key: &DecodingKey,
297309
validation: &Validation,
298310
) -> 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-
311311
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-
317312
let message = [jws.protected.as_str(), jws.payload.as_str()].join(".");
318313

319-
if validation.validate_signature
320-
&& !verify(&jws.signature, message.as_bytes(), key, header.alg)?
321-
{
322-
return Err(new_error(ErrorKind::InvalidSignature));
323-
}
314+
verify_signature_body(&header, &message, &jws.signature, key, validation)?;
324315

325316
Ok(header)
326317
}

src/header.rs

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,110 @@
11
use std::result;
22

33
use base64::{engine::general_purpose::STANDARD, Engine};
4-
use serde::{Deserialize, Serialize};
4+
use serde::{Deserialize, Deserializer, Serialize, Serializer};
55

66
use crate::algorithms::Algorithm;
77
use crate::errors::Result;
88
use crate::jwk::Jwk;
99
use crate::serialization::b64_decode;
1010

11+
const ZIP_SERIAL_DEFLATE: &'static str = "DEF";
12+
const ENC_A128CBC_HS256: &'static str = "A128CBC-HS256";
13+
const ENC_A192CBC_HS384: &'static str = "A192CBC-HS384";
14+
const ENC_A256CBC_HS512: &'static str = "A256CBC-HS512";
15+
const ENC_A128GCM: &'static str = "A128GCM";
16+
const ENC_A192GCM: &'static str = "A192GCM";
17+
const ENC_A256GCM: &'static str = "A256GCM";
18+
19+
/// Encryption algorithm for encrypted payloads.
20+
///
21+
/// Defined in [RFC7516#4.1.2](https://datatracker.ietf.org/doc/html/rfc7516#section-4.1.2).
22+
///
23+
/// Values defined in [RFC7518#5.1](https://datatracker.ietf.org/doc/html/rfc7518#section-5.1).
24+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
25+
#[allow(clippy::upper_case_acronyms)]
26+
pub enum Enc {
27+
A128CBC_HS256,
28+
A192CBC_HS384,
29+
A256CBC_HS512,
30+
A128GCM,
31+
A192GCM,
32+
A256GCM,
33+
Other(String),
34+
}
35+
36+
impl Serialize for Enc {
37+
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
38+
where
39+
S: Serializer,
40+
{
41+
match self {
42+
Enc::A128CBC_HS256 => ENC_A128CBC_HS256,
43+
Enc::A192CBC_HS384 => ENC_A192CBC_HS384,
44+
Enc::A256CBC_HS512 => ENC_A256CBC_HS512,
45+
Enc::A128GCM => ENC_A128GCM,
46+
Enc::A192GCM => ENC_A192GCM,
47+
Enc::A256GCM => ENC_A256GCM,
48+
Enc::Other(v) => v,
49+
}
50+
.serialize(serializer)
51+
}
52+
}
53+
54+
impl<'de> Deserialize<'de> for Enc {
55+
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
56+
where
57+
D: Deserializer<'de>,
58+
{
59+
let s = String::deserialize(deserializer)?;
60+
match s.as_str() {
61+
ENC_A128CBC_HS256 => return Ok(Enc::A128CBC_HS256),
62+
ENC_A192CBC_HS384 => return Ok(Enc::A192CBC_HS384),
63+
ENC_A256CBC_HS512 => return Ok(Enc::A256CBC_HS512),
64+
ENC_A128GCM => return Ok(Enc::A128GCM),
65+
ENC_A192GCM => return Ok(Enc::A192GCM),
66+
ENC_A256GCM => return Ok(Enc::A256GCM),
67+
_ => (),
68+
}
69+
Ok(Enc::Other(s))
70+
}
71+
}
72+
/// Compression applied to plaintext.
73+
///
74+
/// Defined in [RFC7516#4.1.3](https://datatracker.ietf.org/doc/html/rfc7516#section-4.1.3).
75+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
76+
pub enum Zip {
77+
Deflate,
78+
Other(String),
79+
}
80+
81+
impl Serialize for Zip {
82+
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
83+
where
84+
S: Serializer,
85+
{
86+
match self {
87+
Zip::Deflate => ZIP_SERIAL_DEFLATE,
88+
Zip::Other(v) => v,
89+
}
90+
.serialize(serializer)
91+
}
92+
}
93+
94+
impl<'de> Deserialize<'de> for Zip {
95+
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
96+
where
97+
D: Deserializer<'de>,
98+
{
99+
let s = String::deserialize(deserializer)?;
100+
match s.as_str() {
101+
ZIP_SERIAL_DEFLATE => return Ok(Zip::Deflate),
102+
_ => (),
103+
}
104+
Ok(Zip::Other(s))
105+
}
106+
}
107+
11108
/// A basic JWT header, the alg defaults to HS256 and typ is automatically
12109
/// set to `JWT`. All the other fields are optional.
13110
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
@@ -64,6 +161,17 @@ pub struct Header {
64161
#[serde(skip_serializing_if = "Option::is_none")]
65162
#[serde(rename = "x5t#S256")]
66163
pub x5t_s256: Option<String>,
164+
/// Critical - indicates header fields that must be understood by the receiver.
165+
///
166+
/// Defined in [RFC7515#4.1.6](https://tools.ietf.org/html/rfc7515#section-4.1.6).
167+
#[serde(skip_serializing_if = "Option::is_none")]
168+
pub crit: Option<Vec<String>>,
169+
/// See `Enc` for description.
170+
#[serde(skip_serializing_if = "Option::is_none")]
171+
pub enc: Option<Enc>,
172+
/// See `Zip` for description.
173+
#[serde(skip_serializing_if = "Option::is_none")]
174+
pub zip: Option<Zip>,
67175
/// ACME: The URL to which this JWS object is directed
68176
///
69177
/// Defined in [RFC8555#6.4](https://datatracker.ietf.org/doc/html/rfc8555#section-6.4).
@@ -90,6 +198,9 @@ impl Header {
90198
x5c: None,
91199
x5t: None,
92200
x5t_s256: None,
201+
crit: None,
202+
enc: None,
203+
zip: None,
93204
url: None,
94205
nonce: None,
95206
}

0 commit comments

Comments
 (0)