Skip to content

Commit 7f818d3

Browse files
peterpeterparkerarckoorKeatscoryanscovetta
authored
Rebase upstream sync and integrate v10.3.0 (#9)
* Expose cryptography backends via CryptoProvider (Keats#452) * Bump action number in CI * Bump some dep * Use try_sign to avoid panics (Keats#479) Closes Keats#473 * fix: bump minimal version requirements (Keats#481) Before these changes this fails: ``` cargo minimal-versions check --no-default-features --features aws_lc_rs ``` * Fix type confusion * Fix formatting in Ed25519 key serialization (Keats#485) In the thumbprint() function, the serialized format of the Ed25519 curve is missing double quotes around "crv", which would likely affect interoperability between other implementations. Fixes Keats#484 * feat: redo * chore: merge main * docs: redo * chore: set version --------- Co-authored-by: arckoor <33837362+arckoor@users.noreply.github.com> Co-authored-by: Vincent Prouillet <github@vincentprouillet.com> Co-authored-by: Vincent Prouillet <balthek@gmail.com> Co-authored-by: Carlos O'Ryan <coryan@google.com> Co-authored-by: Michael Scovetta <michael.scovetta@gmail.com>
1 parent 454ee34 commit 7f818d3

22 files changed

Lines changed: 539 additions & 281 deletions

File tree

.github/workflows/ci.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
name: Format
1111
runs-on: ubuntu-latest
1212
steps:
13-
- uses: actions/checkout@v3
13+
- uses: actions/checkout@v6
1414
- name: Install Rust
1515
uses: dtolnay/rust-toolchain@stable
1616
with:
@@ -41,7 +41,7 @@ jobs:
4141
include:
4242
- build: pinned
4343
os: ubuntu-24.04
44-
rust: 1.85.0
44+
rust: 1.86.0
4545
- build: stable
4646
os: ubuntu-latest
4747
rust: stable
@@ -68,13 +68,13 @@ jobs:
6868
name: Run tests in wasm
6969
runs-on: ubuntu-latest
7070
steps:
71-
- uses: actions/checkout@v3
71+
- uses: actions/checkout@v6
7272
- name: Install Rust
7373
uses: dtolnay/rust-toolchain@stable
7474
with:
7575
targets: wasm32-unknown-unknown
7676

77-
- uses: actions/setup-node@v4
77+
- uses: actions/setup-node@v6
7878

7979
- name: Install wasm-pack
8080
run: cargo install wasm-pack

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
## 10.3.0 (2026-01-27)
4+
5+
- Export everything needed to define your own CryptoProvider
6+
- Fix type confusion with exp/nbf when not required
7+
38
## 10.2.0 (2025-11-06)
49

510
- Remove `Clone` bound from decode functions

Cargo.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "jsonwebtoken-ic"
3-
version = "10.2.0-ic.1"
3+
version = "10.3.0-ic.0"
44
authors = ["Vincent Prouillet <hello@vincentprouillet.com>", "David Dal Busco <david@fluster.io>"]
55
license = "MIT"
66
readme = "README.md"
@@ -28,7 +28,7 @@ features = ["rust_crypto"]
2828

2929
[dependencies]
3030
base64 = "0.22"
31-
serde = { version = "1.0", features = ["derive"] }
31+
serde = { version = "1.0.228", features = ["derive"] }
3232
serde_json = "1.0"
3333
signature = { version = "2.2.0", features = ["std"] }
3434

@@ -37,7 +37,7 @@ pem = { version = "3", optional = true }
3737
simple_asn1 = { version = "0.6", optional = true }
3838

3939
# "aws_lc_rs" feature
40-
aws-lc-rs = { version = "1.10.0", optional = true }
40+
aws-lc-rs = { version = "1.15.0", optional = true }
4141

4242
# "rust_crypto" feature
4343
ed25519-dalek = { version = "2.1.1", optional = true, features = ["pkcs8"] }
@@ -60,12 +60,12 @@ rand_core = "0.6.4"
6060
[target.'cfg(not(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi")))))'.dev-dependencies]
6161
# For the custom time example
6262
time = "0.3"
63-
criterion = "0.4"
63+
criterion = "0.8"
6464

6565
[target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dev-dependencies]
6666
# For the custom time example
6767
time = { version = "0.3", features = ["wasm-bindgen"] }
68-
criterion = { version = "0.4", default-features = false }
68+
criterion = { version = "0.8", default-features = false }
6969

7070
[features]
7171
default = ["use_pem", "rust_crypto"]

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,14 @@ Add the following to Cargo.toml:
1313

1414
```toml
1515
# You have to select `rust_crypto`
16-
jsonwebtoken = { version = "10.2.0-ic.1", default-features = false, features = ["rust_crypto"] }
16+
jsonwebtoken = { version = "10.3.0-ic.", default-features = false, features = ["rust_crypto"] }
1717
serde = {version = "1.0", features = ["derive"] }
1818
```
1919

20-
Two crypto backends are available via features, `aws_lc_rs` and `rust_crypto`, exactly one of which must be enabled.
20+
Two crypto backends are available via features, `aws_lc_rs` and `rust_crypto`, at most one of which must be enabled. If you select neither feature, you need to provide your own `CryptoProvider`.
21+
22+
For examples of how to implement a `CryptoProvider`, see
23+
- [arckoor/jsonwebtoken-botan](https://github.com/arckoor/jsonwebtoken-botan)
2124

2225
The minimum required Rust version (MSRV) is specified in the `rust-version` field in this project's [Cargo.toml](Cargo.toml).
2326

src/algorithms.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,15 @@ use serde::{Deserialize, Serialize};
55
use crate::errors::{Error, ErrorKind, Result};
66

77
#[derive(Debug, Eq, PartialEq, Copy, Clone, Serialize, Deserialize)]
8+
/// Supported families of algorithms.
89
pub enum AlgorithmFamily {
10+
/// HMAC shared secret family.
911
Hmac,
12+
/// RSA-based public key family.
1013
Rsa,
14+
/// Edwards curve public key family.
1115
Ec,
16+
/// Elliptic curve public key family.
1217
Ed,
1318
}
1419

src/crypto/aws_lc/ecdsa.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ macro_rules! define_ecdsa_signer {
1818

1919
impl $name {
2020
pub(crate) fn new(encoding_key: &EncodingKey) -> Result<Self> {
21-
if encoding_key.family != AlgorithmFamily::Ec {
21+
if encoding_key.family() != AlgorithmFamily::Ec {
2222
return Err(new_error(ErrorKind::InvalidKeyFormat));
2323
}
2424

@@ -51,7 +51,7 @@ macro_rules! define_ecdsa_verifier {
5151

5252
impl $name {
5353
pub(crate) fn new(decoding_key: &DecodingKey) -> Result<Self> {
54-
if decoding_key.family != AlgorithmFamily::Ec {
54+
if decoding_key.family() != AlgorithmFamily::Ec {
5555
return Err(new_error(ErrorKind::InvalidKeyFormat));
5656
}
5757

src/crypto/aws_lc/eddsa.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ pub struct EdDSASigner(Ed25519KeyPair);
1111

1212
impl EdDSASigner {
1313
pub(crate) fn new(encoding_key: &EncodingKey) -> Result<Self> {
14-
if encoding_key.family != AlgorithmFamily::Ed {
14+
if encoding_key.family() != AlgorithmFamily::Ed {
1515
return Err(new_error(ErrorKind::InvalidKeyFormat));
1616
}
1717

@@ -38,7 +38,7 @@ pub struct EdDSAVerifier(DecodingKey);
3838

3939
impl EdDSAVerifier {
4040
pub(crate) fn new(decoding_key: &DecodingKey) -> Result<Self> {
41-
if decoding_key.family != AlgorithmFamily::Ed {
41+
if decoding_key.family() != AlgorithmFamily::Ed {
4242
return Err(new_error(ErrorKind::InvalidKeyFormat));
4343
}
4444

src/crypto/aws_lc/mod.rs

Lines changed: 110 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,110 @@
1-
pub(crate) mod ecdsa;
2-
pub(crate) mod eddsa;
3-
pub(crate) mod hmac;
4-
pub(crate) mod rsa;
1+
use aws_lc_rs::{
2+
digest,
3+
signature::{
4+
self as aws_sig, ECDSA_P256_SHA256_FIXED_SIGNING, ECDSA_P384_SHA384_FIXED_SIGNING,
5+
EcdsaKeyPair, KeyPair,
6+
},
7+
};
8+
9+
use crate::{
10+
Algorithm, DecodingKey, EncodingKey,
11+
crypto::{CryptoProvider, JwkUtils, JwtSigner, JwtVerifier},
12+
errors::{self, Error, ErrorKind},
13+
jwk::{EllipticCurve, ThumbprintHash},
14+
};
15+
16+
mod ecdsa;
17+
mod eddsa;
18+
mod hmac;
19+
mod rsa;
20+
21+
fn extract_rsa_public_key_components(key_content: &[u8]) -> errors::Result<(Vec<u8>, Vec<u8>)> {
22+
let key_pair = aws_sig::RsaKeyPair::from_der(key_content)
23+
.map_err(|e| ErrorKind::InvalidRsaKey(e.to_string()))?;
24+
let public = key_pair.public_key();
25+
let components = aws_sig::RsaPublicKeyComponents::<Vec<u8>>::from(public);
26+
Ok((components.n, components.e))
27+
}
28+
29+
fn extract_ec_public_key_coordinates(
30+
key_content: &[u8],
31+
alg: Algorithm,
32+
) -> errors::Result<(EllipticCurve, Vec<u8>, Vec<u8>)> {
33+
let (signing_alg, curve, pub_elem_bytes) = match alg {
34+
Algorithm::ES256 => (&ECDSA_P256_SHA256_FIXED_SIGNING, EllipticCurve::P256, 32),
35+
Algorithm::ES384 => (&ECDSA_P384_SHA384_FIXED_SIGNING, EllipticCurve::P384, 48),
36+
_ => return Err(ErrorKind::InvalidEcdsaKey.into()),
37+
};
38+
39+
let key_pair = EcdsaKeyPair::from_pkcs8(signing_alg, key_content)
40+
.map_err(|_| ErrorKind::InvalidEcdsaKey)?;
41+
42+
let pub_bytes = key_pair.public_key().as_ref();
43+
if pub_bytes[0] != 4 {
44+
return Err(ErrorKind::InvalidEcdsaKey.into());
45+
}
46+
47+
let (x, y) = pub_bytes[1..].split_at(pub_elem_bytes);
48+
Ok((curve, x.to_vec(), y.to_vec()))
49+
}
50+
51+
fn compute_digest(data: &[u8], hash_function: ThumbprintHash) -> Vec<u8> {
52+
let algorithm = match hash_function {
53+
ThumbprintHash::SHA256 => &digest::SHA256,
54+
ThumbprintHash::SHA384 => &digest::SHA384,
55+
ThumbprintHash::SHA512 => &digest::SHA512,
56+
};
57+
digest::digest(algorithm, data).as_ref().to_vec()
58+
}
59+
60+
fn new_signer(algorithm: &Algorithm, key: &EncodingKey) -> Result<Box<dyn JwtSigner>, Error> {
61+
let jwt_signer = match algorithm {
62+
Algorithm::HS256 => Box::new(hmac::Hs256Signer::new(key)?) as Box<dyn JwtSigner>,
63+
Algorithm::HS384 => Box::new(hmac::Hs384Signer::new(key)?) as Box<dyn JwtSigner>,
64+
Algorithm::HS512 => Box::new(hmac::Hs512Signer::new(key)?) as Box<dyn JwtSigner>,
65+
Algorithm::ES256 => Box::new(ecdsa::Es256Signer::new(key)?) as Box<dyn JwtSigner>,
66+
Algorithm::ES384 => Box::new(ecdsa::Es384Signer::new(key)?) as Box<dyn JwtSigner>,
67+
Algorithm::RS256 => Box::new(rsa::Rsa256Signer::new(key)?) as Box<dyn JwtSigner>,
68+
Algorithm::RS384 => Box::new(rsa::Rsa384Signer::new(key)?) as Box<dyn JwtSigner>,
69+
Algorithm::RS512 => Box::new(rsa::Rsa512Signer::new(key)?) as Box<dyn JwtSigner>,
70+
Algorithm::PS256 => Box::new(rsa::RsaPss256Signer::new(key)?) as Box<dyn JwtSigner>,
71+
Algorithm::PS384 => Box::new(rsa::RsaPss384Signer::new(key)?) as Box<dyn JwtSigner>,
72+
Algorithm::PS512 => Box::new(rsa::RsaPss512Signer::new(key)?) as Box<dyn JwtSigner>,
73+
Algorithm::EdDSA => Box::new(eddsa::EdDSASigner::new(key)?) as Box<dyn JwtSigner>,
74+
};
75+
76+
Ok(jwt_signer)
77+
}
78+
79+
fn new_verifier(
80+
algorithm: &Algorithm,
81+
key: &DecodingKey,
82+
) -> Result<Box<dyn super::JwtVerifier>, Error> {
83+
let jwt_verifier = match algorithm {
84+
Algorithm::HS256 => Box::new(hmac::Hs256Verifier::new(key)?) as Box<dyn JwtVerifier>,
85+
Algorithm::HS384 => Box::new(hmac::Hs384Verifier::new(key)?) as Box<dyn JwtVerifier>,
86+
Algorithm::HS512 => Box::new(hmac::Hs512Verifier::new(key)?) as Box<dyn JwtVerifier>,
87+
Algorithm::ES256 => Box::new(ecdsa::Es256Verifier::new(key)?) as Box<dyn JwtVerifier>,
88+
Algorithm::ES384 => Box::new(ecdsa::Es384Verifier::new(key)?) as Box<dyn JwtVerifier>,
89+
Algorithm::RS256 => Box::new(rsa::Rsa256Verifier::new(key)?) as Box<dyn JwtVerifier>,
90+
Algorithm::RS384 => Box::new(rsa::Rsa384Verifier::new(key)?) as Box<dyn JwtVerifier>,
91+
Algorithm::RS512 => Box::new(rsa::Rsa512Verifier::new(key)?) as Box<dyn JwtVerifier>,
92+
Algorithm::PS256 => Box::new(rsa::RsaPss256Verifier::new(key)?) as Box<dyn JwtVerifier>,
93+
Algorithm::PS384 => Box::new(rsa::RsaPss384Verifier::new(key)?) as Box<dyn JwtVerifier>,
94+
Algorithm::PS512 => Box::new(rsa::RsaPss512Verifier::new(key)?) as Box<dyn JwtVerifier>,
95+
Algorithm::EdDSA => Box::new(eddsa::EdDSAVerifier::new(key)?) as Box<dyn JwtVerifier>,
96+
};
97+
98+
Ok(jwt_verifier)
99+
}
100+
101+
/// The default [`CryptoProvider`] backed by [`aws_lc_rs`].
102+
pub static DEFAULT_PROVIDER: CryptoProvider = CryptoProvider {
103+
signer_factory: new_signer,
104+
verifier_factory: new_verifier,
105+
jwk_utils: JwkUtils {
106+
extract_rsa_public_key_components,
107+
extract_ec_public_key_coordinates,
108+
compute_digest,
109+
},
110+
};

src/crypto/aws_lc/rsa.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ fn verify_rsa(
3737
msg: &[u8],
3838
signature: &[u8],
3939
) -> std::result::Result<(), signature::Error> {
40-
match &decoding_key.kind {
40+
match decoding_key.kind() {
4141
DecodingKeyKind::SecretOrDer(bytes) => {
4242
let public_key = crypto_sig::UnparsedPublicKey::new(algorithm, bytes);
4343
public_key.verify(msg, signature).map_err(signature::Error::from_source)?;
@@ -57,7 +57,7 @@ macro_rules! define_rsa_signer {
5757

5858
impl $name {
5959
pub(crate) fn new(encoding_key: &EncodingKey) -> Result<Self> {
60-
if encoding_key.family != AlgorithmFamily::Rsa {
60+
if encoding_key.family() != AlgorithmFamily::Rsa {
6161
return Err(new_error(ErrorKind::InvalidKeyFormat));
6262
}
6363

@@ -85,7 +85,7 @@ macro_rules! define_rsa_verifier {
8585

8686
impl $name {
8787
pub(crate) fn new(decoding_key: &DecodingKey) -> Result<Self> {
88-
if decoding_key.family != AlgorithmFamily::Rsa {
88+
if decoding_key.family() != AlgorithmFamily::Rsa {
8989
return Err(new_error(ErrorKind::InvalidKeyFormat));
9090
}
9191

0 commit comments

Comments
 (0)