Skip to content

Decoupled crypto backends (the rest) #428

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 38 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
33d2c21
support wasm
itanxiao Jun 19, 2023
b116e44
upgrade dependency
itanxiao Mar 13, 2024
e2a203f
remove ring
itanxiao Mar 15, 2024
5bb1d75
Merge branch 'master' into wasm_support
itanxiao Mar 15, 2024
d80832c
fix wasm test failed
itanxiao Mar 17, 2024
69cbfa3
fix cargo format
itanxiao Mar 17, 2024
a66864f
change ci branch
itanxiao Mar 17, 2024
485fb9f
fix ci
itanxiao Mar 17, 2024
490564c
fix ci
itanxiao Mar 17, 2024
a672130
fix ci
itanxiao Mar 17, 2024
73ea869
remove ci branch
itanxiao Mar 17, 2024
a41c817
fix examples ed25519 test failed
itanxiao Apr 6, 2024
6f9b553
Next version
Keats Aug 29, 2024
e061882
Remove downgrade steps
Keats Sep 2, 2024
e5ba5a1
Merge branch 'master' into new-backends
Keats Sep 2, 2024
502faa4
feat(encoder): Add encoder builder
sidrubs Sep 30, 2024
a701547
feat(encoder): Convert to dynamic dispatch
sidrubs Sep 30, 2024
6a41ba7
feat(decoder): Create decoder
sidrubs Sep 30, 2024
66d54be
test: Get HMAC tests passing
sidrubs Oct 3, 2024
0886064
docs: Neaten up docstrings
sidrubs Oct 3, 2024
337f9ed
feat(crypto): Implement JwtSigner and JwtVerifier for aws-lc-rs
sidrubs Oct 5, 2024
a0431d8
feat: Remove builder style implementation
sidrubs Oct 12, 2024
4225e1f
feat: Use original encoding and decoding key structs
sidrubs Oct 13, 2024
78e84c1
feat(crypto): Add RSA family
sidrubs Oct 14, 2024
2a50e0d
Add ECDSA via AWS-LC
sulami May 10, 2025
19ee8f3
Implement EdDSA through AWS-LC
sulami May 10, 2025
765c04c
Verify ES and ED keys are of the right type
sulami May 10, 2025
e1305ef
Implement RSA-PSS via AWS-LC
sulami May 10, 2025
e37ebee
Implement EdDSA via RustCrypto/Dalek
sulami May 10, 2025
25f84c3
Implement EcDSA through RustCrypto
sulami May 10, 2025
4b7fdfb
Implement RSA via RustCrypto
sulami May 10, 2025
1f49f43
Clean up optional dependencies
sulami May 10, 2025
6062c96
Fix all test-breaking issues with the RustCrypto versions
sulami May 10, 2025
492760f
Re-add the crypto::{sign, verify} convenience functions
sulami May 10, 2025
eed1c1c
Require at least one crypto backend to be enabled
sulami May 10, 2025
8f60acf
Merge remote-tracking branch 'upstream/master' into decoupled-crypto-…
sulami May 11, 2025
660d89e
Ensure tests pass without use_pem feature as well
sulami May 11, 2025
9adb7da
Fix dependency features & clippy lints
sulami May 11, 2025
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Changelog

## 10.0.0 (unreleased)

## 9.3.1 (2024-02-06)

- Update base64
Expand Down
28 changes: 21 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,35 @@ include = [
rust-version = "1.73.0"

[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
base64 = "0.22"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
signature = { version = "2.2.0", features = ["std"] }

# For PEM decoding
pem = { version = "3", optional = true }
simple_asn1 = { version = "0.6", optional = true }

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
ring = { version = "0.17.4", features = ["std"] }
# "aws_lc_rs" feature
aws-lc-rs = { version = "1.10.0", optional = true }

# "rust_crypto" feature
ed25519-dalek = { version = "2.1.1", optional = true, features = ["pkcs8"] }
hmac = { version = "0.12.1", optional = true }
p256 = { version = "0.13.2", optional = true, features = ["ecdsa"] }
p384 = { version = "0.13.0", optional = true, features = ["ecdsa"] }
rand = { version = "0.8.5", optional = true, features = ["std"], default-features = false }
rsa = { version = "0.9.6", optional = true }
sha2 = { version = "0.10.7", optional = true, features = ["oid"] }

[target.'cfg(target_arch = "wasm32")'.dependencies]
js-sys = "0.3"
ring = { version = "0.17.4", features = ["std", "wasm32_unknown_unknown_js"] }

[dev-dependencies]
wasm-bindgen-test = "0.3.1"

ed25519-dalek = { version = "2.1.1", features = ["pkcs8", "rand_core"] }
rand = { version = "0.8.5", features = ["std"], default-features = false }
rand_core = "0.6.4"
[target.'cfg(not(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi")))))'.dev-dependencies]
# For the custom time example
time = "0.3"
Expand All @@ -48,8 +60,10 @@ time = { version = "0.3", features = ["wasm-bindgen"] }
criterion = { version = "0.4", default-features = false }

[features]
default = ["use_pem"]
default = ["use_pem", "aws_lc_rs"]
use_pem = ["pem", "simple_asn1"]
rust_crypto = ["ed25519-dalek", "hmac", "p256", "p384", "rand", "rsa", "sha2"]
aws_lc_rs = ["aws-lc-rs"]

[[bench]]
name = "jwt"
Expand Down
11 changes: 7 additions & 4 deletions examples/custom_time.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation};
use serde::{Deserialize, Serialize};
use time::{Duration, OffsetDateTime};

use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation};

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

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

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

use super::super::{Claims, SECRET};
use jsonwebtoken::{
decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation,
};
use time::{Duration, OffsetDateTime};

use super::super::{Claims, SECRET};

const EXPECTED_TOKEN: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJDdXN0b20gT2Zmc2V0RGF0ZVRpbWUgc2VyL2RlIiwiaWF0IjowLCJleHAiOjMyNTAzNjgwMDAwfQ.BcPipupP9oIV6uFRI6Acn7FMLws_wA3oo6CrfeFF3Gg";

#[test]
fn round_trip() {
Expand Down
34 changes: 24 additions & 10 deletions examples/ed25519.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use ed25519_dalek::pkcs8::EncodePrivateKey;
use ed25519_dalek::SigningKey;
use rand_core::OsRng;
use serde::{Deserialize, Serialize};

use jsonwebtoken::{
decode, encode, get_current_timestamp, Algorithm, DecodingKey, EncodingKey, Validation,
};
use ring::signature::{Ed25519KeyPair, KeyPair};
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
Expand All @@ -11,11 +14,16 @@ pub struct Claims {
}

fn main() {
let doc = Ed25519KeyPair::generate_pkcs8(&ring::rand::SystemRandom::new()).unwrap();
let encoding_key = EncodingKey::from_ed_der(doc.as_ref());
let signing_key = SigningKey::generate(&mut OsRng);
let pkcs8 = signing_key.to_pkcs8_der().unwrap();
let pkcs8 = pkcs8.as_bytes();
// The `to_pkcs8_der` includes the public key, the first 48 bits are the private key.
let pkcs8 = &pkcs8[..48];
let encoding_key = EncodingKey::from_ed_der(pkcs8);

let pair = Ed25519KeyPair::from_pkcs8(doc.as_ref()).unwrap();
let decoding_key = DecodingKey::from_ed_der(pair.public_key().as_ref());
let verifying_key = signing_key.verifying_key();
let public_key = verifying_key.as_bytes();
let decoding_key = DecodingKey::from_ed_der(public_key);

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

Expand All @@ -37,11 +45,17 @@ mod tests {

impl Jot {
fn new() -> Jot {
let doc = Ed25519KeyPair::generate_pkcs8(&ring::rand::SystemRandom::new()).unwrap();
let encoding_key = EncodingKey::from_ed_der(doc.as_ref());
let signing_key = SigningKey::generate(&mut OsRng);
let pkcs8 = signing_key.to_pkcs8_der().unwrap();
let pkcs8 = pkcs8.as_bytes();
// The `to_pkcs8_der` includes the public key, the first 48 bits are the private key.
let pkcs8 = &pkcs8[..48];
let encoding_key = EncodingKey::from_ed_der(&pkcs8);

let verifying_key = signing_key.verifying_key();
let public_key = verifying_key.as_bytes();
let decoding_key = DecodingKey::from_ed_der(public_key);

let pair = Ed25519KeyPair::from_pkcs8(doc.as_ref()).unwrap();
let decoding_key = DecodingKey::from_ed_der(pair.public_key().as_ref());
Jot { encoding_key, decoding_key }
}
}
Expand Down
3 changes: 2 additions & 1 deletion examples/validation.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use serde::{Deserialize, Serialize};

use jsonwebtoken::errors::ErrorKind;
use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct Claims {
Expand Down
6 changes: 4 additions & 2 deletions src/algorithms.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use crate::errors::{Error, ErrorKind, Result};
use serde::{Deserialize, Serialize};
use std::str::FromStr;

use serde::{Deserialize, Serialize};

use crate::errors::{Error, ErrorKind, Result};

#[derive(Debug, Eq, PartialEq, Copy, Clone, Serialize, Deserialize)]
pub(crate) enum AlgorithmFamily {
Hmac,
Expand Down
126 changes: 126 additions & 0 deletions src/crypto/aws_lc/ecdsa.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
//! Implementations of the [`JwtSigner`] and [`JwtVerifier`] traits for the
//! ECDSA family of algorithms using [`aws_lc_rs`]

use crate::algorithms::AlgorithmFamily;
use crate::crypto::{JwtSigner, JwtVerifier};
use crate::errors::{new_error, ErrorKind, Result};
use crate::{Algorithm, DecodingKey, EncodingKey};
use aws_lc_rs::rand::SystemRandom;
use aws_lc_rs::signature::{
EcdsaKeyPair, VerificationAlgorithm, ECDSA_P256_SHA256_FIXED, ECDSA_P256_SHA256_FIXED_SIGNING,
ECDSA_P384_SHA384_FIXED, ECDSA_P384_SHA384_FIXED_SIGNING,
};
use signature::{Error, Signer, Verifier};

pub struct Es256Signer(EcdsaKeyPair);

impl Es256Signer {
pub(crate) fn new(encoding_key: &EncodingKey) -> Result<Self> {
if encoding_key.family != AlgorithmFamily::Ec {
return Err(new_error(ErrorKind::InvalidKeyFormat));
}

Ok(Self(
EcdsaKeyPair::from_pkcs8(&ECDSA_P256_SHA256_FIXED_SIGNING, encoding_key.inner())
.map_err(|_| ErrorKind::InvalidEcdsaKey)?,
))
}
}

impl Signer<Vec<u8>> for Es256Signer {
fn try_sign(&self, msg: &[u8]) -> std::result::Result<Vec<u8>, Error> {
let rng = SystemRandom::new();
let signature = self.0.sign(&rng, msg).map_err(Error::from_source)?;
Ok(signature.as_ref().to_vec())
}
}

impl JwtSigner for Es256Signer {
fn algorithm(&self) -> Algorithm {
Algorithm::ES256
}
}

pub struct Es256Verifier(DecodingKey);

impl Es256Verifier {
pub(crate) fn new(decoding_key: &DecodingKey) -> Result<Self> {
if decoding_key.family != AlgorithmFamily::Ec {
return Err(new_error(ErrorKind::InvalidKeyFormat));
}

Ok(Self(decoding_key.clone()))
}
}

impl Verifier<Vec<u8>> for Es256Verifier {
fn verify(&self, msg: &[u8], signature: &Vec<u8>) -> std::result::Result<(), Error> {
ECDSA_P256_SHA256_FIXED
.verify_sig(self.0.as_bytes(), msg, signature)
.map_err(Error::from_source)?;
Ok(())
}
}

impl JwtVerifier for Es256Verifier {
fn algorithm(&self) -> Algorithm {
Algorithm::ES256
}
}

pub struct Es384Signer(EcdsaKeyPair);

impl Es384Signer {
pub(crate) fn new(encoding_key: &EncodingKey) -> Result<Self> {
if encoding_key.family != AlgorithmFamily::Ec {
return Err(new_error(ErrorKind::InvalidKeyFormat));
}

Ok(Self(
EcdsaKeyPair::from_pkcs8(&ECDSA_P384_SHA384_FIXED_SIGNING, encoding_key.inner())
.map_err(|_| crate::errors::ErrorKind::InvalidEcdsaKey)?,
))
}
}

impl Signer<Vec<u8>> for Es384Signer {
fn try_sign(&self, msg: &[u8]) -> std::result::Result<Vec<u8>, Error> {
let rng = SystemRandom::new();
let signature = self.0.sign(&rng, msg).map_err(Error::from_source)?;
Ok(signature.as_ref().to_vec())
}
}

impl JwtSigner for Es384Signer {
fn algorithm(&self) -> Algorithm {
Algorithm::ES384
}
}

pub struct Es384Verifier(DecodingKey);

impl Es384Verifier {
pub(crate) fn new(decoding_key: &DecodingKey) -> Result<Self> {
if decoding_key.family != AlgorithmFamily::Ec {
return Err(new_error(ErrorKind::InvalidKeyFormat));
}

Ok(Self(decoding_key.clone()))
}
}

impl Verifier<Vec<u8>> for Es384Verifier {
fn verify(&self, msg: &[u8], signature: &Vec<u8>) -> std::result::Result<(), Error> {
ECDSA_P384_SHA384_FIXED
.verify_sig(self.0.as_bytes(), msg, signature)
.map_err(Error::from_source)?;

Ok(())
}
}

impl JwtVerifier for Es384Verifier {
fn algorithm(&self) -> Algorithm {
Algorithm::ES384
}
}
60 changes: 60 additions & 0 deletions src/crypto/aws_lc/eddsa.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//! Implementations of the [`JwtSigner`] and [`JwtVerifier`] traits for EdDSA using AWS-LC-RS.

use crate::algorithms::AlgorithmFamily;
use crate::crypto::{JwtSigner, JwtVerifier};
use crate::errors::{new_error, ErrorKind, Result};
use crate::{Algorithm, DecodingKey, EncodingKey};
use aws_lc_rs::signature::{Ed25519KeyPair, VerificationAlgorithm, ED25519};
use signature::{Error, Signer, Verifier};

pub struct EdDSASigner(Ed25519KeyPair);

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

Ok(Self(
Ed25519KeyPair::from_pkcs8(encoding_key.inner())
.map_err(|_| ErrorKind::InvalidEddsaKey)?,
))
}
}

impl Signer<Vec<u8>> for EdDSASigner {
fn try_sign(&self, msg: &[u8]) -> std::result::Result<Vec<u8>, Error> {
Ok(self.0.sign(msg).as_ref().to_vec())
}
}

impl JwtSigner for EdDSASigner {
fn algorithm(&self) -> Algorithm {
Algorithm::EdDSA
}
}

pub struct EdDSAVerifier(DecodingKey);

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

Ok(Self(decoding_key.clone()))
}
}

impl Verifier<Vec<u8>> for EdDSAVerifier {
fn verify(&self, msg: &[u8], signature: &Vec<u8>) -> std::result::Result<(), Error> {
ED25519.verify_sig(self.0.as_bytes(), msg, signature).map_err(Error::from_source)?;
Ok(())
}
}

impl JwtVerifier for EdDSAVerifier {
fn algorithm(&self) -> Algorithm {
Algorithm::EdDSA
}
}
Loading