Skip to content

Commit

Permalink
WebAssembly Build Compatibility
Browse files Browse the repository at this point in the history
This update introduces an important modification to improve the crate's compatibility with WebAssembly builds. Previously, the crate depended on the Ring crate, which posed challenges for compiling it for WebAssembly. To address this, following changes were made:

1. **Default Feature - "ring"**: The default feature continues to use the `jsonwebtoken` library, which is dependent on the Ring crate.

2. **New Optional Feature - "noring"**: Optional feature, "noring", which leverages the `jsonwebtoken-rustcrypto` library. This alternative library implements the necessary cryptographic functionalities without the dependency on the Ring crate, making it a suitable choice for wasm32-unknown-unknown targets.
  • Loading branch information
robjsliwa committed Jan 16, 2024
1 parent 6c2b5eb commit c41224c
Show file tree
Hide file tree
Showing 11 changed files with 663 additions and 48 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,7 @@ jobs:
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
- name: Build without Ring
run: cargo build --no-default-features --features "noring"
- name: Run tests without Ring
run: cargo test --no-default-features --features "noring"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/target
/Cargo.lock
.vscode/
16 changes: 13 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "sdjwt"
version = "0.7.0"
version = "0.7.1"
authors = ["Rob Sliwa <robjsliwa@gmail.com>"]
license = "MIT"
readme = "README.md"
Expand All @@ -17,8 +17,18 @@ thiserror = "1.0.51"
rand = "0.8.5"
base64 = "0.21.5"
sha2 = "0.10.8"
jsonwebtoken = "9.2.0"
chrono = "0.4.31"
rsa = "0.8"
getrandom = { version = "0.2.12", features = ["js"] }

jsonwebtoken-rustcrypto = { git = "https://github.com/JadedBlueEyes/jsonwebtoken", rev = "a7758b0", optional = true }
serde_plain = { version = "1.0.2", optional = true }

jsonwebtoken = { version = "9.2.0", optional = true }

[features]
default = ["ring"]
ring = ["jsonwebtoken"]
noring = ["jsonwebtoken-rustcrypto", "serde_plain"]

[dev-dependencies]
rsa = "0.5"
104 changes: 85 additions & 19 deletions src/decoding.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
use crate::{Algorithm, Error, Validation};
use jsonwebtoken::DecodingKey;
#[cfg(feature = "noring")]
use base64::Engine;
#[cfg(feature = "ring")]
use jsonwebtoken::{
decode as jwt_decode, Algorithm as JwtAlgorithm, DecodingKey, Validation as JwtValidation,
};

#[cfg(feature = "noring")]
use jsonwebtoken_rustcrypto::{
decode as jwt_decode, Algorithm as JwtAlgorithm, DecodingKey, Validation as JwtValidation,
};
#[cfg(feature = "noring")]
use rsa::PublicKeyParts;
#[cfg(feature = "noring")]
use rsa::{pkcs1::DecodeRsaPublicKey, RsaPublicKey};

use serde_json::Value;

#[derive(Clone)]
Expand All @@ -20,64 +35,84 @@ impl KeyForDecoding {
})
}

#[cfg(feature = "ring")]
pub fn from_rsa_pem(key: &[u8]) -> Result<Self, Error> {
Ok(KeyForDecoding {
key: DecodingKey::from_rsa_pem(key)?,
})
}

#[cfg(feature = "noring")]
pub fn from_rsa_pem(key: &[u8]) -> Result<Self, Error> {
let rsa_key = RsaPublicKey::from_pkcs1_pem(std::str::from_utf8(key)?)?;

let modulus =
base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(rsa_key.n().to_bytes_be());
let exponent =
base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(rsa_key.e().to_bytes_be());

Self::from_rsa_components(&modulus, &exponent)
}

pub fn from_rsa_components(modulus: &str, exponent: &str) -> Result<Self, Error> {
Ok(KeyForDecoding {
key: DecodingKey::from_rsa_components(modulus, exponent)?,
})
}

#[cfg(feature = "ring")]
pub fn from_ec_pem(key: &[u8]) -> Result<Self, Error> {
Ok(KeyForDecoding {
key: DecodingKey::from_ec_pem(key)?,
})
}

#[cfg(feature = "ring")]
pub fn from_ed_pem(key: &[u8]) -> Result<Self, Error> {
Ok(KeyForDecoding {
key: DecodingKey::from_ed_pem(key)?,
})
}

#[cfg(feature = "ring")]
pub fn from_rsa_der(der: &[u8]) -> Self {
KeyForDecoding {
key: DecodingKey::from_rsa_der(der),
}
}

#[cfg(feature = "ring")]
pub fn from_ec_der(der: &[u8]) -> Self {
KeyForDecoding {
key: DecodingKey::from_ec_der(der),
}
}

#[cfg(feature = "ring")]
pub fn from_ed_der(der: &[u8]) -> Self {
KeyForDecoding {
key: DecodingKey::from_ed_der(der),
}
}
}

fn build_validation(validation: &Validation) -> jsonwebtoken::Validation {
let mut valid = jsonwebtoken::Validation::new(match validation.algorithms {
Algorithm::HS256 => jsonwebtoken::Algorithm::HS256,
Algorithm::HS384 => jsonwebtoken::Algorithm::HS384,
Algorithm::HS512 => jsonwebtoken::Algorithm::HS512,
Algorithm::RS256 => jsonwebtoken::Algorithm::RS256,
Algorithm::RS384 => jsonwebtoken::Algorithm::RS384,
Algorithm::RS512 => jsonwebtoken::Algorithm::RS512,
Algorithm::ES256 => jsonwebtoken::Algorithm::ES256,
Algorithm::ES384 => jsonwebtoken::Algorithm::ES384,
Algorithm::PS256 => jsonwebtoken::Algorithm::PS256,
Algorithm::PS384 => jsonwebtoken::Algorithm::PS384,
Algorithm::PS512 => jsonwebtoken::Algorithm::PS512,
Algorithm::EdDSA => jsonwebtoken::Algorithm::EdDSA,
#[cfg(feature = "ring")]
fn build_validation(validation: &Validation) -> JwtValidation {
let mut valid = JwtValidation::new(match validation.algorithms {
Algorithm::HS256 => JwtAlgorithm::HS256,
Algorithm::HS384 => JwtAlgorithm::HS384,
Algorithm::HS512 => JwtAlgorithm::HS512,
Algorithm::RS256 => JwtAlgorithm::RS256,
Algorithm::RS384 => JwtAlgorithm::RS384,
Algorithm::RS512 => JwtAlgorithm::RS512,
Algorithm::ES256 => JwtAlgorithm::ES256,
Algorithm::ES384 => JwtAlgorithm::ES384,
Algorithm::PS256 => JwtAlgorithm::PS256,
Algorithm::PS384 => JwtAlgorithm::PS384,
Algorithm::PS512 => JwtAlgorithm::PS512,
Algorithm::EdDSA => JwtAlgorithm::EdDSA,
});

valid.required_spec_claims = validation.required_spec_claims.clone();
valid.leeway = validation.leeway;
valid.validate_exp = validation.validate_exp;
Expand All @@ -89,13 +124,39 @@ fn build_validation(validation: &Validation) -> jsonwebtoken::Validation {
valid
}

#[cfg(feature = "noring")]
fn build_validation(validation: &Validation) -> JwtValidation {
let mut valid = JwtValidation::new(match validation.algorithms {
Algorithm::HS256 => JwtAlgorithm::HS256,
Algorithm::HS384 => JwtAlgorithm::HS384,
Algorithm::HS512 => JwtAlgorithm::HS512,
Algorithm::RS256 => JwtAlgorithm::RS256,
Algorithm::RS384 => JwtAlgorithm::RS384,
Algorithm::RS512 => JwtAlgorithm::RS512,
Algorithm::ES256 => JwtAlgorithm::ES256,
Algorithm::ES384 => JwtAlgorithm::ES384,
Algorithm::PS256 => JwtAlgorithm::PS256,
Algorithm::PS384 => JwtAlgorithm::PS384,
Algorithm::PS512 => JwtAlgorithm::PS512,
Algorithm::EdDSA => JwtAlgorithm::EdDSA,
});

valid.leeway = validation.leeway;
valid.validate_exp = validation.validate_exp;
valid.validate_nbf = validation.validate_nbf;
valid.aud = validation.aud.clone();
valid.iss = validation.iss.clone().and_then(|mut hs| hs.drain().next());
valid.sub = validation.sub.clone();
valid
}

pub fn decode(
token: &str,
key: &KeyForDecoding,
validation: &Validation,
) -> Result<(Value, Value), Error> {
let validation = build_validation(validation);
let token_data = jsonwebtoken::decode(token, &key.key, &validation)?;
let token_data = jwt_decode(token, &key.key, &validation)?;
let header: Value = serde_json::from_str(&serde_json::to_string(&token_data.header)?)?;
Ok((header, token_data.claims))
}
Expand Down Expand Up @@ -124,7 +185,9 @@ mod tests {
use super::*;
use chrono::{Duration, Utc};
use rand::rngs::OsRng;
use rsa::{pkcs1::ToRsaPublicKey, pkcs8::ToPrivateKey, RsaPrivateKey, RsaPublicKey};
use rsa::pkcs1::EncodeRsaPublicKey;
use rsa::pkcs8::EncodePrivateKey;
use rsa::{RsaPrivateKey, RsaPublicKey};

const TEST_CLAIMS: &str = r#"{
"sub": "user_42",
Expand Down Expand Up @@ -158,8 +221,11 @@ mod tests {

fn convert_to_pem(private_key: RsaPrivateKey, public_key: RsaPublicKey) -> (String, String) {
(
private_key.to_pkcs8_pem().unwrap().to_string(),
public_key.to_pkcs1_pem().unwrap(),
private_key
.to_pkcs8_pem(rsa::pkcs8::LineEnding::CR)
.unwrap()
.to_string(),
public_key.to_pkcs1_pem(rsa::pkcs1::LineEnding::CR).unwrap(),
)
}

Expand Down
Loading

0 comments on commit c41224c

Please sign in to comment.