Skip to content

Commit

Permalink
Merge branch 'chenzhenjia-wasm_support' into new-backends
Browse files Browse the repository at this point in the history
  • Loading branch information
Keats committed Aug 29, 2024
2 parents 98a18f1 + c55c12d commit c6b5c79
Show file tree
Hide file tree
Showing 20 changed files with 400 additions and 213 deletions.
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ jobs:
- name: Build System Info
run: rustc --version

- name: Downgrade bumpalo
run: cargo update -p bumpalo@3.15.4 --precise 3.14.0

- name: Downgrade half
run: cargo update -p half@2.4.0 --precise 2.2.1

- name: Run tests default features
run: cargo test

Expand Down
17 changes: 11 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,21 @@ base64 = "0.22"
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"] }

hmac = "0.12.1"
rsa = "0.9.6"
sha2 = { version = "0.10.7", features = ["oid"] }
getrandom = { version = "0.2.10", features = ["js"] }
rand = { version = "0.8.5", features = ["std"], default-features = false }
ed25519-dalek = { version = "2.1.1" }
p256 = { version = "0.13.2", features = ["ecdsa"] }
p384 = { version = "0.13.0", features = ["ecdsa"] }
rand_core = "0.6.4"
[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"] }
[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 @@ -49,7 +54,7 @@ criterion = { version = "0.4", default-features = false }

[features]
default = ["use_pem"]
use_pem = ["pem", "simple_asn1"]
use_pem = ["pem", "simple_asn1", 'p256/pem', 'p384/pem']

[[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
84 changes: 60 additions & 24 deletions src/crypto/ecdsa.rs
Original file line number Diff line number Diff line change
@@ -1,38 +1,74 @@
use ring::{rand, signature};

use crate::algorithms::Algorithm;
use crate::errors::Result;
use crate::serialization::b64_encode;
use crate::serialization::{b64_decode, b64_encode};

/// Only used internally when validating EC, to map from our enum to the Ring EcdsaVerificationAlgorithm structs.
pub(crate) fn alg_to_ec_verification(
alg: Algorithm,
) -> &'static signature::EcdsaVerificationAlgorithm {
/// The actual ECDSA signing + encoding
/// The key needs to be in PKCS8 format
pub(crate) fn sign(alg: Algorithm, key: &[u8], message: &[u8]) -> Result<String> {
match alg {
Algorithm::ES256 => &signature::ECDSA_P256_SHA256_FIXED,
Algorithm::ES384 => &signature::ECDSA_P384_SHA384_FIXED,
Algorithm::ES256 => es256_sign(key, message),
Algorithm::ES384 => es384_sign(key, message),

_ => unreachable!("Tried to get EC alg for a non-EC algorithm"),
}
}

/// Only used internally when signing EC, to map from our enum to the Ring EcdsaVerificationAlgorithm structs.
pub(crate) fn alg_to_ec_signing(alg: Algorithm) -> &'static signature::EcdsaSigningAlgorithm {
fn es256_sign(key: &[u8], message: &[u8]) -> Result<String> {
use p256::ecdsa::signature::Signer;
use p256::ecdsa::{Signature, SigningKey};
use p256::pkcs8::DecodePrivateKey;
use p256::SecretKey;
let secret_key =
SecretKey::from_pkcs8_der(key).map_err(|_e| crate::errors::ErrorKind::InvalidEcdsaKey)?;
let signing_key: SigningKey = secret_key.into();

let signature: Signature = signing_key.sign(message);
let bytes = signature.to_bytes();
Ok(b64_encode(bytes))
}

fn es384_sign(key: &[u8], message: &[u8]) -> Result<String> {
use p384::ecdsa::signature::Signer;
use p384::ecdsa::{Signature, SigningKey};
use p384::pkcs8::DecodePrivateKey;
use p384::SecretKey;
let secret_key =
SecretKey::from_pkcs8_der(key).map_err(|_e| crate::errors::ErrorKind::InvalidEcdsaKey)?;
let signing_key: SigningKey = secret_key.into();
let signature: Signature = signing_key.sign(message);
let bytes = signature.to_bytes();
Ok(b64_encode(bytes))
}

pub(crate) fn verify(alg: Algorithm, signature: &str, message: &[u8], key: &[u8]) -> Result<bool> {
match alg {
Algorithm::ES256 => &signature::ECDSA_P256_SHA256_FIXED_SIGNING,
Algorithm::ES384 => &signature::ECDSA_P384_SHA384_FIXED_SIGNING,
Algorithm::ES256 => es256_verify(signature, message, key),
Algorithm::ES384 => es384_verify(signature, message, key),
_ => unreachable!("Tried to get EC alg for a non-EC algorithm"),
}
}

/// The actual ECDSA signing + encoding
/// The key needs to be in PKCS8 format
pub fn sign(
alg: &'static signature::EcdsaSigningAlgorithm,
key: &[u8],
message: &[u8],
) -> Result<String> {
let rng = rand::SystemRandom::new();
let signing_key = signature::EcdsaKeyPair::from_pkcs8(alg, key, &rng)?;
let out = signing_key.sign(&rng, message)?;
Ok(b64_encode(out))
fn es384_verify(signature: &str, message: &[u8], key: &[u8]) -> Result<bool> {
use p384::ecdsa::signature::Verifier;
use p384::ecdsa::{Signature, VerifyingKey};
use p384::PublicKey;

let public_key =
PublicKey::from_sec1_bytes(key).map_err(|_e| crate::errors::ErrorKind::InvalidEcdsaKey)?;
let verifying_key: VerifyingKey = public_key.into();
let signature = Signature::from_slice(&b64_decode(signature)?)
.map_err(|_e| crate::errors::ErrorKind::InvalidSignature)?;
Ok(verifying_key.verify(message, &signature).is_ok())
}

fn es256_verify(signature: &str, message: &[u8], key: &[u8]) -> Result<bool> {
use p256::ecdsa::signature::Verifier;
use p256::ecdsa::{Signature, VerifyingKey};
use p256::PublicKey;
let public_key =
PublicKey::from_sec1_bytes(key).map_err(|_e| crate::errors::ErrorKind::InvalidEcdsaKey)?;
let verifying_key: VerifyingKey = public_key.into();
let signature = Signature::from_slice(&b64_decode(signature)?)
.map_err(|_e| crate::errors::ErrorKind::InvalidSignature)?;
Ok(verifying_key.verify(message, &signature).is_ok())
}
34 changes: 20 additions & 14 deletions src/crypto/eddsa.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
use ring::signature;
use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};

use crate::algorithms::Algorithm;
use crate::errors::Result;
use crate::serialization::b64_encode;
use crate::errors::{new_error, ErrorKind, Result};
use crate::serialization::{b64_decode, b64_encode};

/// Only used internally when signing or validating EdDSA, to map from our enum to the Ring EdDSAParameters structs.
pub(crate) fn alg_to_ec_verification(alg: Algorithm) -> &'static signature::EdDSAParameters {
// To support additional key subtypes, like Ed448, we would need to match on the JWK's ("crv")
// parameter.
match alg {
Algorithm::EdDSA => &signature::ED25519,
_ => unreachable!("Tried to get EdDSA alg for a non-EdDSA algorithm"),
}
fn parse_key(key: &[u8]) -> Result<SigningKey> {
let key = key.try_into().map_err(|_| new_error(ErrorKind::InvalidEddsaKey))?;
let signing_key = SigningKey::from_bytes(key);
Ok(signing_key)
}

pub(crate) fn verify(signature: &str, message: &[u8], key: &[u8]) -> Result<bool> {
let signature = b64_decode(signature)?;
let signature =
Signature::from_slice(&signature).map_err(|_e| new_error(ErrorKind::InvalidSignature))?;
let key = key.try_into().map_err(|_| new_error(ErrorKind::InvalidEddsaKey))?;
let verifying_key =
VerifyingKey::from_bytes(key).map_err(|_| new_error(ErrorKind::InvalidEddsaKey))?;
Ok(verifying_key.verify(message, &signature).is_ok())
}

/// The actual EdDSA signing + encoding
/// The key needs to be in PKCS8 format
pub fn sign(key: &[u8], message: &[u8]) -> Result<String> {
let signing_key = signature::Ed25519KeyPair::from_pkcs8_maybe_unchecked(key)?;
let key = key[16..].into();
let signing_key = parse_key(key)?;
let out = signing_key.sign(message);
Ok(b64_encode(out))
Ok(b64_encode(out.to_bytes()))
}
Loading

0 comments on commit c6b5c79

Please sign in to comment.