Skip to content
Merged

Next #436

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
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

## 10.0.0 (unreleased)

- Introduce alternative crypto backends (AWS-LC, RustCrypto)
- BREAKING: now using traits for crypto backends, you have to choose between `aws_lc_rs` and `rust_crypto`
- Add `Clone` bound to `decode`
- Support decoding byte slices
- Support JWS

## 9.3.1 (2024-02-06)

Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "jsonwebtoken"
version = "9.3.1"
version = "10.0.0"
authors = ["Vincent Prouillet <hello@vincentprouillet.com>"]
license = "MIT"
readme = "README.md"
Expand Down
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ See [JSON Web Tokens](https://en.wikipedia.org/wiki/JSON_Web_Token) for more inf
Add the following to Cargo.toml:

```toml
# You will have to select either `aws_lc_rs` or `rust_crypto` as backend if you're not using your own
jsonwebtoken = { version = "10", features = ["aws_lc_rs"] }
# If you do not need pem decoding, you can disable the default feature `use_pem` that way:
# jsonwebtoken = {version = "10", default-features = false, features = ["aws_lc_rs"] }
Expand Down Expand Up @@ -110,6 +111,28 @@ RSA/EC, the key should always be the content of the private key in PEM or DER fo
If your key is in PEM format, it is better performance wise to generate the `EncodingKey` once in a `lazy_static` or
something similar and reuse it.

### Encoding and decoding JWS

JWS is handled the same way as JWT, but using `encode_jws` and `decode_jws`:

```rust
let encoded = encode_jws(&Header::default(), &my_claims, &EncodingKey::from_secret("secret".as_ref()))?;
my_claims = decode_jws(&encoded, &DecodingKey::from_secret("secret".as_ref()), &Validation::default())?.claims;
```

`encode_jws` returns a `Jws<C>` struct which can be placed in other structs or serialized/deserialized from JSON directly.

The generic parameter in `Jws<C>` indicates the claims type and prevents accidentally encoding or decoding the wrong claims type
when the Jws is nested in another struct.

### JWK Thumbprints

If you have a JWK object, you can generate a thumbprint like

```
let tp = my_jwk.thumbprint(&jsonwebtoken::DIGEST_SHA256);
```

### Decoding

```rust
Expand Down
2 changes: 1 addition & 1 deletion examples/custom_header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::collections::HashMap;
use jsonwebtoken::errors::ErrorKind;
use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};

#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, Clone)]
struct Claims {
sub: String,
company: String,
Expand Down
2 changes: 1 addition & 1 deletion examples/custom_time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation};

const SECRET: &str = "some-secret";

#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
struct Claims {
sub: String,
#[serde(with = "jwt_numeric_date")]
Expand Down
2 changes: 1 addition & 1 deletion examples/ed25519.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use jsonwebtoken::{
decode, encode, get_current_timestamp, Algorithm, DecodingKey, EncodingKey, Validation,
};

#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Claims {
sub: String,
exp: u64,
Expand Down
2 changes: 1 addition & 1 deletion examples/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
use jsonwebtoken::errors::ErrorKind;
use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};

#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, Clone)]
struct Claims {
aud: String,
sub: String,
Expand Down
3 changes: 0 additions & 3 deletions src/algorithms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ impl AlgorithmFamily {
Algorithm::RS512,
Algorithm::PS256,
Algorithm::PS384,
Algorithm::PS384,
Algorithm::PS512,
],
Self::Ec => &[Algorithm::ES256, Algorithm::ES384],
Expand All @@ -32,8 +31,6 @@ impl AlgorithmFamily {
}
}



/// The algorithms supported for signing/verifying JWTs
#[allow(clippy::upper_case_acronyms)]
#[derive(Debug, Default, PartialEq, Eq, Hash, Copy, Clone, Serialize, Deserialize)]
Expand Down
49 changes: 31 additions & 18 deletions src/decoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ impl DecodingKey {
pub fn family(&self) -> AlgorithmFamily {
self.family
}

/// If you're using HMAC, use this.
pub fn from_secret(secret: &[u8]) -> Self {
DecodingKey {
Expand Down Expand Up @@ -259,7 +259,7 @@ impl DecodingKey {
/// use serde::{Deserialize, Serialize};
/// use jsonwebtoken::{decode, DecodingKey, Validation, Algorithm};
///
/// #[derive(Debug, Serialize, Deserialize)]
/// #[derive(Debug, Clone, Serialize, Deserialize)]
/// struct Claims {
/// sub: String,
/// company: String
Expand All @@ -269,11 +269,12 @@ impl DecodingKey {
/// // Claims is a struct that implements Deserialize
/// let token_message = decode::<Claims>(&token, &DecodingKey::from_secret("secret".as_ref()), &Validation::new(Algorithm::HS256));
/// ```
pub fn decode<T: DeserializeOwned>(
token: &str,
pub fn decode<T: DeserializeOwned + Clone>(
token: impl AsRef<[u8]>,
key: &DecodingKey,
validation: &Validation,
) -> Result<TokenData<T>> {
let token = token.as_ref();
let header = decode_header(token)?;

if validation.validate_signature && !validation.algorithms.contains(&header.alg) {
Expand Down Expand Up @@ -324,20 +325,20 @@ pub fn jwt_verifier_factory(
/// let token = "a.jwt.token".to_string();
/// let header = decode_header(&token);
/// ```
pub fn decode_header(token: &str) -> Result<Header> {
let (_, message) = expect_two!(token.rsplitn(2, '.'));
let (_, header) = expect_two!(message.rsplitn(2, '.'));
pub fn decode_header(token: impl AsRef<[u8]>) -> Result<Header> {
let token = token.as_ref();
let (_, message) = expect_two!(token.rsplitn(2, |b| *b == b'.'));
let (_, header) = expect_two!(message.rsplitn(2, |b| *b == b'.'));
Header::from_encoded(header)
}

/// Verify the signature of a JWT, and return a header object and raw payload.
///
/// If the token or its signature is invalid, it will return an error.
fn verify_signature<'a>(
token: &'a str,
pub(crate) fn verify_signature_body(
message: &[u8],
signature: &[u8],
header: &Header,
validation: &Validation,
verifying_provider: Box<dyn JwtVerifier>,
) -> Result<(Header, &'a str)> {
) -> Result<()> {
if validation.validate_signature && validation.algorithms.is_empty() {
return Err(new_error(ErrorKind::MissingAlgorithm));
}
Expand All @@ -350,19 +351,31 @@ fn verify_signature<'a>(
}
}

let (signature, message) = expect_two!(token.rsplitn(2, '.'));
let (payload, header) = expect_two!(message.rsplitn(2, '.'));
let header = Header::from_encoded(header)?;

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

if validation.validate_signature
&& verifying_provider.verify(message.as_bytes(), &b64_decode(signature)?).is_err()
&& verifying_provider.verify(message, &b64_decode(signature)?).is_err()
{
return Err(new_error(ErrorKind::InvalidSignature));
}

Ok(())
}

/// Verify the signature of a JWT, and return a header object and raw payload.
///
/// If the token or its signature is invalid, it will return an error.
fn verify_signature<'a>(
token: &'a [u8],
validation: &Validation,
verifying_provider: Box<dyn JwtVerifier>,
) -> Result<(Header, &'a [u8])> {
let (signature, message) = expect_two!(token.rsplitn(2, |b| *b == b'.'));
let (payload, header) = expect_two!(message.rsplitn(2, |b| *b == b'.'));
let header = Header::from_encoded(header)?;
verify_signature_body(message, signature, &header, validation, verifying_provider)?;

Ok((header, payload))
}
15 changes: 12 additions & 3 deletions src/encoding.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use std::fmt::{Debug, Formatter};

use base64::{engine::general_purpose::STANDARD, Engine};
use base64::{
engine::general_purpose::{STANDARD, URL_SAFE},
Engine,
};
use serde::ser::Serialize;

use crate::algorithms::AlgorithmFamily;
Expand Down Expand Up @@ -36,15 +39,15 @@ use crate::crypto::rust_crypto::{
#[derive(Clone)]
pub struct EncodingKey {
pub(crate) family: AlgorithmFamily,
content: Vec<u8>,
pub(crate) content: Vec<u8>,
}

impl EncodingKey {
/// The algorithm family this key is for.
pub fn family(&self) -> AlgorithmFamily {
self.family
}

/// If you're using a HMAC secret that is not base64, use that.
pub fn from_secret(secret: &[u8]) -> Self {
EncodingKey { family: AlgorithmFamily::Hmac, content: secret.to_vec() }
Expand All @@ -56,6 +59,12 @@ impl EncodingKey {
Ok(EncodingKey { family: AlgorithmFamily::Hmac, content: out })
}

/// For loading websafe base64 HMAC secrets, ex: ACME EAB credentials.
pub fn from_urlsafe_base64_secret(secret: &str) -> Result<Self> {
let out = URL_SAFE.decode(secret)?;
Ok(EncodingKey { family: AlgorithmFamily::Hmac, content: out })
}

/// If you are loading a RSA key from a .pem file.
/// This errors if the key is not a valid RSA key.
/// Only exists if the feature `use_pem` is enabled.
Expand Down
Loading