Skip to content

Commit a670368

Browse files
sulamiitanxiaoKeatssidrubs
authored
Decoupled crypto backends (the rest) (#428)
* support wasm * upgrade dependency * remove ring * fix wasm test failed * fix cargo format * change ci branch * fix ci * fix ci * fix ci * remove ci branch * fix examples ed25519 test failed * Next version * Remove downgrade steps * feat(encoder): Add encoder builder * feat(encoder): Convert to dynamic dispatch * feat(decoder): Create decoder * test: Get HMAC tests passing * docs: Neaten up docstrings * feat(crypto): Implement JwtSigner and JwtVerifier for aws-lc-rs * feat: Remove builder style implementation * feat: Use original encoding and decoding key structs * feat(crypto): Add RSA family * Add ECDSA via AWS-LC This is following the existing scheme established in hmac & rsa. * Implement EdDSA through AWS-LC * Verify ES and ED keys are of the right type * Implement RSA-PSS via AWS-LC * Implement EdDSA via RustCrypto/Dalek * Implement EcDSA through RustCrypto * Implement RSA via RustCrypto * Clean up optional dependencies * Fix all test-breaking issues with the RustCrypto versions * Re-add the crypto::{sign, verify} convenience functions * Require at least one crypto backend to be enabled * Ensure tests pass without use_pem feature as well * Fix dependency features & clippy lints * Reduce code duplication in crypto impls through macros * Move try_get_hmac_secret directly into en-/decoding key impls * Re-enable the HMAC sign & verify test cases * Uncomment the algorithm matching in verify_signature again This was commented out with the note that it wasn't "captured anywhere," presumably in tests, but is present in similar form in the pre-decoupling version of the code. * Inline _encode and _decode They had todos for docstrings, and I realized they're single-use and should probably not be exposed publicly anyway. * Fix some docstring typos * Don't default to a crypto backend In most situations AWS-LC is the better backend, but it's not available on all platforms, for example WASM. Explicitly asking the user to pick a backend is also more future-proof. * Explicitly annotate an implicit lifetime This is fixing a Clippy complaint. * Enable getrandom/js for WASM tests in CI * Update readme & changelog for version 10 * Run CI on newer versions of Ubuntu Running most of them on the latest version, at the moment 24.04, except for the test run that's pinned to Rust 1.73, which is pinned to 24.04. * Bump the MSRV to 1.85 Some dependencies are now on the 2024 edition. --------- Co-authored-by: 陈圳佳 <[email protected]> Co-authored-by: Vincent Prouillet <[email protected]> Co-authored-by: Vincent Prouillet <[email protected]> Co-authored-by: sidrubs <[email protected]>
1 parent 5cd1887 commit a670368

33 files changed

+1130
-398
lines changed

.github/workflows/ci.yml

Lines changed: 72 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,86 @@
11
name: ci
22
on:
3-
push:
4-
branches:
5-
- master
6-
pull_request:
3+
push:
4+
branches:
5+
- master
6+
pull_request:
77

88
jobs:
9-
style:
10-
name: Format
11-
runs-on: ubuntu-latest
12-
steps:
13-
- uses: actions/checkout@v3
14-
- name: Install Rust
15-
uses: dtolnay/rust-toolchain@stable
16-
with:
17-
components: rustfmt
18-
- name: Check format
19-
run: cargo fmt --check
9+
style:
10+
name: Format
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v3
14+
- name: Install Rust
15+
uses: dtolnay/rust-toolchain@stable
16+
with:
17+
components: rustfmt
18+
- name: Check format
19+
run: cargo fmt --check
2020

21-
clippy:
22-
name: Clippy
23-
runs-on: ubuntu-latest
24-
steps:
25-
- uses: actions/checkout@v3
26-
- uses: dtolnay/rust-toolchain@stable
27-
with:
28-
components: clippy
29-
- run: cargo clippy --all-targets --all-features -- -D warnings
21+
clippy:
22+
name: Clippy
23+
runs-on: ubuntu-latest
24+
strategy:
25+
matrix:
26+
backend: [ aws_lc_rs, rust_crypto ]
27+
steps:
28+
- uses: actions/checkout@v3
29+
- uses: dtolnay/rust-toolchain@stable
30+
with:
31+
components: clippy
32+
- run: cargo clippy --all-targets --features ${{ matrix.backend }} -- -D warnings
3033

31-
tests:
32-
name: Tests
33-
runs-on: ${{ matrix.os }}
34-
strategy:
35-
matrix:
36-
build: [pinned, stable, nightly]
37-
include:
38-
- build: pinned
39-
os: ubuntu-20.04
40-
rust: 1.73.0
41-
- build: stable
42-
os: ubuntu-20.04
43-
rust: stable
44-
- build: nightly
45-
os: ubuntu-20.04
46-
rust: nightly
47-
steps:
48-
- uses: actions/checkout@v3
49-
- name: Install Rust
50-
uses: dtolnay/rust-toolchain@master
51-
with:
52-
toolchain: ${{ matrix.rust }}
34+
tests:
35+
name: Tests
36+
runs-on: ${{ matrix.os }}
37+
strategy:
38+
matrix:
39+
build: [ pinned, stable, nightly ]
40+
backend: [ aws_lc_rs, rust_crypto ]
41+
include:
42+
- build: pinned
43+
os: ubuntu-24.04
44+
rust: 1.85.0
45+
- build: stable
46+
os: ubuntu-latest
47+
rust: stable
48+
- build: nightly
49+
os: ubuntu-latest
50+
rust: nightly
51+
steps:
52+
- uses: actions/checkout@v3
53+
- name: Install Rust
54+
uses: dtolnay/rust-toolchain@master
55+
with:
56+
toolchain: ${{ matrix.rust }}
5357

54-
- name: Build System Info
55-
run: rustc --version
58+
- name: Build System Info
59+
run: rustc --version
5660

57-
- name: Run tests default features
58-
run: cargo test
61+
- name: Run tests default features
62+
run: cargo test --features ${{ matrix.backend }}
5963

60-
- name: Run tests no features
61-
run: cargo test --no-default-features
64+
- name: Run tests no features
65+
run: cargo test --no-default-features --features ${{ matrix.backend }}
6266

63-
wasm:
64-
name: Run tests in wasm
65-
runs-on: ubuntu-latest
66-
steps:
67-
- uses: actions/checkout@v3
68-
- name: Install Rust
69-
uses: dtolnay/rust-toolchain@stable
70-
with:
71-
targets: wasm32-unknown-unknown
67+
wasm:
68+
name: Run tests in wasm
69+
runs-on: ubuntu-latest
70+
steps:
71+
- uses: actions/checkout@v3
72+
- name: Install Rust
73+
uses: dtolnay/rust-toolchain@stable
74+
with:
75+
targets: wasm32-unknown-unknown
7276

73-
- uses: actions/setup-node@v4
77+
- uses: actions/setup-node@v4
7478

75-
- name: Install wasm-pack
76-
run: cargo install wasm-pack
79+
- name: Install wasm-pack
80+
run: cargo install wasm-pack
7781

78-
- name: Run tests default features
79-
run: wasm-pack test --node
80-
81-
- name: Run tests no features
82-
run: wasm-pack test --node --no-default-features
82+
- name: Run tests default features
83+
run: wasm-pack test --node --features rust_crypto,getrandom/js
8384

85+
- name: Run tests no features
86+
run: wasm-pack test --node --no-default-features --features rust_crypto,getrandom/js

CHANGELOG.md

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

3+
## 10.0.0 (unreleased)
4+
5+
- Introduce alternative crypto backends (AWS-LC, RustCrypto)
6+
37
## 9.3.1 (2024-02-06)
48

59
- Update base64

Cargo.toml

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,26 +17,39 @@ include = [
1717
"README.md",
1818
"CHANGELOG.md",
1919
]
20-
rust-version = "1.73.0"
20+
rust-version = "1.85.0"
2121

2222
[dependencies]
23-
serde_json = "1.0"
24-
serde = { version = "1.0", features = ["derive"] }
2523
base64 = "0.22"
24+
serde = { version = "1.0", features = ["derive"] }
25+
serde_json = "1.0"
26+
signature = { version = "2.2.0", features = ["std"] }
27+
2628
# For PEM decoding
2729
pem = { version = "3", optional = true }
2830
simple_asn1 = { version = "0.6", optional = true }
2931

30-
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
31-
ring = { version = "0.17.4", features = ["std"] }
32+
# "aws_lc_rs" feature
33+
aws-lc-rs = { version = "1.10.0", optional = true }
34+
35+
# "rust_crypto" feature
36+
ed25519-dalek = { version = "2.1.1", optional = true, features = ["pkcs8"] }
37+
hmac = { version = "0.12.1", optional = true }
38+
p256 = { version = "0.13.2", optional = true, features = ["ecdsa"] }
39+
p384 = { version = "0.13.0", optional = true, features = ["ecdsa"] }
40+
rand = { version = "0.8.5", optional = true, features = ["std"], default-features = false }
41+
rsa = { version = "0.9.6", optional = true }
42+
sha2 = { version = "0.10.7", optional = true, features = ["oid"] }
3243

3344
[target.'cfg(target_arch = "wasm32")'.dependencies]
3445
js-sys = "0.3"
35-
ring = { version = "0.17.4", features = ["std", "wasm32_unknown_unknown_js"] }
46+
getrandom = "0.2"
3647

3748
[dev-dependencies]
3849
wasm-bindgen-test = "0.3.1"
39-
50+
ed25519-dalek = { version = "2.1.1", features = ["pkcs8", "rand_core"] }
51+
rand = { version = "0.8.5", features = ["std"], default-features = false }
52+
rand_core = "0.6.4"
4053
[target.'cfg(not(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi")))))'.dev-dependencies]
4154
# For the custom time example
4255
time = "0.3"
@@ -50,6 +63,8 @@ criterion = { version = "0.4", default-features = false }
5063
[features]
5164
default = ["use_pem"]
5265
use_pem = ["pem", "simple_asn1"]
66+
rust_crypto = ["ed25519-dalek", "hmac", "p256", "p384", "rand", "rsa", "sha2"]
67+
aws_lc_rs = ["aws-lc-rs"]
5368

5469
[[bench]]
5570
name = "jwt"

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ See [JSON Web Tokens](https://en.wikipedia.org/wiki/JSON_Web_Token) for more inf
88
Add the following to Cargo.toml:
99

1010
```toml
11-
jsonwebtoken = "9"
11+
jsonwebtoken = { version = "10", features = ["aws_lc_rs"] }
1212
# If you do not need pem decoding, you can disable the default feature `use_pem` that way:
13-
# jsonwebtoken = {version = "9", default-features = false }
13+
# jsonwebtoken = {version = "10", default-features = false, features = ["aws_lc_rs"] }
1414
serde = {version = "1.0", features = ["derive"] }
1515
```
1616

17+
Two crypto backends are available via features, `aws_lc_rs` and `rust_crypto`, exactly one of which must be enabled.
18+
1719
The minimum required Rust version (MSRV) is specified in the `rust-version` field in this project's [Cargo.toml](Cargo.toml).
1820

1921
## Algorithms

examples/custom_time.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation};
21
use serde::{Deserialize, Serialize};
32
use time::{Duration, OffsetDateTime};
43

4+
use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation};
5+
56
const SECRET: &str = "some-secret";
67

78
#[derive(Debug, PartialEq, Serialize, Deserialize)]
@@ -60,13 +61,15 @@ mod jwt_numeric_date {
6061

6162
#[cfg(test)]
6263
mod tests {
63-
const EXPECTED_TOKEN: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJDdXN0b20gT2Zmc2V0RGF0ZVRpbWUgc2VyL2RlIiwiaWF0IjowLCJleHAiOjMyNTAzNjgwMDAwfQ.BcPipupP9oIV6uFRI6Acn7FMLws_wA3oo6CrfeFF3Gg";
64+
use time::{Duration, OffsetDateTime};
6465

65-
use super::super::{Claims, SECRET};
6666
use jsonwebtoken::{
6767
decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation,
6868
};
69-
use time::{Duration, OffsetDateTime};
69+
70+
use super::super::{Claims, SECRET};
71+
72+
const EXPECTED_TOKEN: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJDdXN0b20gT2Zmc2V0RGF0ZVRpbWUgc2VyL2RlIiwiaWF0IjowLCJleHAiOjMyNTAzNjgwMDAwfQ.BcPipupP9oIV6uFRI6Acn7FMLws_wA3oo6CrfeFF3Gg";
7073

7174
#[test]
7275
fn round_trip() {

examples/ed25519.rs

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
use ed25519_dalek::pkcs8::EncodePrivateKey;
2+
use ed25519_dalek::SigningKey;
3+
use rand_core::OsRng;
4+
use serde::{Deserialize, Serialize};
5+
16
use jsonwebtoken::{
27
decode, encode, get_current_timestamp, Algorithm, DecodingKey, EncodingKey, Validation,
38
};
4-
use ring::signature::{Ed25519KeyPair, KeyPair};
5-
use serde::{Deserialize, Serialize};
69

710
#[derive(Debug, Serialize, Deserialize)]
811
pub struct Claims {
@@ -11,11 +14,16 @@ pub struct Claims {
1114
}
1215

1316
fn main() {
14-
let doc = Ed25519KeyPair::generate_pkcs8(&ring::rand::SystemRandom::new()).unwrap();
15-
let encoding_key = EncodingKey::from_ed_der(doc.as_ref());
17+
let signing_key = SigningKey::generate(&mut OsRng);
18+
let pkcs8 = signing_key.to_pkcs8_der().unwrap();
19+
let pkcs8 = pkcs8.as_bytes();
20+
// The `to_pkcs8_der` includes the public key, the first 48 bits are the private key.
21+
let pkcs8 = &pkcs8[..48];
22+
let encoding_key = EncodingKey::from_ed_der(pkcs8);
1623

17-
let pair = Ed25519KeyPair::from_pkcs8(doc.as_ref()).unwrap();
18-
let decoding_key = DecodingKey::from_ed_der(pair.public_key().as_ref());
24+
let verifying_key = signing_key.verifying_key();
25+
let public_key = verifying_key.as_bytes();
26+
let decoding_key = DecodingKey::from_ed_der(public_key);
1927

2028
let claims = Claims { sub: "test".to_string(), exp: get_current_timestamp() };
2129

@@ -37,11 +45,17 @@ mod tests {
3745

3846
impl Jot {
3947
fn new() -> Jot {
40-
let doc = Ed25519KeyPair::generate_pkcs8(&ring::rand::SystemRandom::new()).unwrap();
41-
let encoding_key = EncodingKey::from_ed_der(doc.as_ref());
48+
let signing_key = SigningKey::generate(&mut OsRng);
49+
let pkcs8 = signing_key.to_pkcs8_der().unwrap();
50+
let pkcs8 = pkcs8.as_bytes();
51+
// The `to_pkcs8_der` includes the public key, the first 48 bits are the private key.
52+
let pkcs8 = &pkcs8[..48];
53+
let encoding_key = EncodingKey::from_ed_der(&pkcs8);
54+
55+
let verifying_key = signing_key.verifying_key();
56+
let public_key = verifying_key.as_bytes();
57+
let decoding_key = DecodingKey::from_ed_der(public_key);
4258

43-
let pair = Ed25519KeyPair::from_pkcs8(doc.as_ref()).unwrap();
44-
let decoding_key = DecodingKey::from_ed_der(pair.public_key().as_ref());
4559
Jot { encoding_key, decoding_key }
4660
}
4761
}

examples/validation.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
use serde::{Deserialize, Serialize};
2+
13
use jsonwebtoken::errors::ErrorKind;
24
use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
3-
use serde::{Deserialize, Serialize};
45

56
#[derive(Debug, Serialize, Deserialize)]
67
struct Claims {

src/algorithms.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
use crate::errors::{Error, ErrorKind, Result};
2-
use serde::{Deserialize, Serialize};
31
use std::str::FromStr;
42

3+
use serde::{Deserialize, Serialize};
4+
5+
use crate::errors::{Error, ErrorKind, Result};
6+
57
#[derive(Debug, Eq, PartialEq, Copy, Clone, Serialize, Deserialize)]
68
pub(crate) enum AlgorithmFamily {
79
Hmac,

0 commit comments

Comments
 (0)