Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 6 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,16 @@ 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 }
rand10 = { package = "rand", version = "0.10", optional = true, features = ["std", "thread_rng"], default-features = false }
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The dependency rand version 0.10 does not currently exist on crates.io (the latest stable is 0.9.0). Using a non-existent version will cause compilation to fail. If you intended to use the latest version that supports the new rand_core ecosystem used by rsa 0.10, you likely meant version 0.9.

Suggested change
rand10 = { package = "rand", version = "0.10", optional = true, features = ["std", "thread_rng"], default-features = false }
rand10 = { package = "rand", version = "0.9", optional = true, features = ["std", "thread_rng"], default-features = false }

rsa = { version = "0.10.0-rc.17", optional = true, features = ["sha2"] }
Comment on lines +43 to +44
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

rand 0.10 crate features thread_rng std

💡 Result:

In the rand 0.10 crate, the "thread_rng" feature enables the thread-local random number generator ThreadRng and the convenience function rand::rng. It is enabled by default and depends on the "std", "std_rng", and "sys_rng" features. The "std" feature (also default) enables std library functionality, including alloc support. ThreadRng uses ChaCha (12-rounds) as its CSPRNG, automatically seeded via SysRng, and provides fast, reasonably secure randomness for general use. It is available only with the "thread_rng" feature.

Citations:


🌐 Web query:

RSA crate 0.10.0 stable release

💡 Result:

The RustCrypto RSA crate (https://crates.io/crates/rsa) has not released a stable 0.10.0 version as of 2026-04-15. The latest version is 0.10.0-rc.17, released on 2026-03-10. The current stable release is 0.9.10 from 2026-01-06. The repository status indicates it is still in Phase 1 development toward 1.0, explaining the ongoing release candidates for 0.10. No evidence of a stable 0.10.0 release exists in crates.io listings, GitHub commits, or CHANGELOG.

Citations:


Consider upgrading from the RSA release candidate when 0.10.0 stable becomes available.

Using 0.10.0-rc.17 introduces stability risk since the API may change before the final stable release. As of April 2026, stable 0.10.0 has not been released (latest is 0.9.10). Pin this version and document the upgrade path when stable is available.

The thread_rng feature in rand 0.10 is valid and correctly specified—no changes needed there.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Cargo.toml` around lines 43 - 44, The rsa dependency is referencing a release
candidate (rsa = "0.10.0-rc.17"), which is unstable; change it to the latest
published stable (e.g., rsa = "0.9.10") while preserving optional = true and
features = ["sha2"], and add a brief comment in Cargo.toml or the project
upgrade notes indicating to revisit and upgrade to rsa 0.10.0 (remove the pinned
stable and switch to 0.10.x) once the final 0.10.0 release is published and API
compatibility is verified.

sha2 = { version = "0.10.7", optional = true, features = ["oid"] }

[target.'cfg(target_arch = "wasm32")'.dependencies]
js-sys = "0.3"
getrandom = "0.2"
# rand 0.10 pulls getrandom 0.4 on wasm; enable wasm_js there
# while keeping the existing wasm-pack CI feature invocation unchanged.
getrandom_04 = { package = "getrandom", version = "0.4", features = ["wasm_js"] }
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The dependency getrandom version 0.4 does not currently exist on crates.io (the latest stable is 0.3.0). Additionally, the feature name for WebAssembly support in getrandom 0.3+ is wasm-js (with a hyphen), not wasm_js. Please verify the intended version and feature name.

Suggested change
getrandom_04 = { package = "getrandom", version = "0.4", features = ["wasm_js"] }
getrandom_03 = { package = "getrandom", version = "0.3", features = ["wasm-js"] }


[dev-dependencies]
wasm-bindgen-test = "0.3.1"
Expand All @@ -66,7 +69,7 @@ criterion = { version = "0.8", default-features = false }
[features]
default = ["use_pem"]
use_pem = ["pem", "simple_asn1"]
rust_crypto = ["ed25519-dalek", "hmac", "p256", "p384", "rand", "rsa", "sha2"]
rust_crypto = ["ed25519-dalek", "hmac", "p256", "p384", "rand10", "rsa", "sha2"]
aws_lc_rs = ["aws-lc-rs"]

[[bench]]
Expand Down
2 changes: 1 addition & 1 deletion src/crypto/rust_crypto/ecdsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ use crate::{Algorithm, DecodingKey, EncodingKey};
use p256::ecdsa::{
Signature as Signature256, SigningKey as SigningKey256, VerifyingKey as VerifyingKey256,
};
use p256::pkcs8::DecodePrivateKey;
use p384::ecdsa::{
Signature as Signature384, SigningKey as SigningKey384, VerifyingKey as VerifyingKey384,
};
use rsa::pkcs8::DecodePrivateKey;
use signature::{Error, Signer, Verifier};

macro_rules! define_ecdsa_signer {
Expand Down
12 changes: 11 additions & 1 deletion src/crypto/rust_crypto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,21 @@ mod eddsa;
mod hmac;
mod rsa;

fn trim_leading_zeroes(bytes: &[u8]) -> Vec<u8> {
match bytes.iter().position(|byte| *byte != 0) {
Some(first_non_zero) => bytes[first_non_zero..].to_vec(),
None => vec![0],
}
}

fn extract_rsa_public_key_components(key_content: &[u8]) -> errors::Result<(Vec<u8>, Vec<u8>)> {
let private_key = RsaPrivateKey::from_pkcs1_der(key_content)
.map_err(|e| ErrorKind::InvalidRsaKey(e.to_string()))?;
let public_key = private_key.to_public_key();
Ok((public_key.n().to_bytes_be(), public_key.e().to_bytes_be()))
Ok((
trim_leading_zeroes(public_key.n().to_be_bytes().as_ref()),
trim_leading_zeroes(public_key.e().to_be_bytes().as_ref()),
))
}

fn extract_ec_public_key_coordinates(
Expand Down
52 changes: 31 additions & 21 deletions src/crypto/rust_crypto/rsa.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,32 @@
//! Implementations of the [`JwtSigner`] and [`JwtVerifier`] traits for the
//! RSA family of algorithms using RustCrypto.

use hmac::digest::FixedOutputReset;
// Use the hash types re-exported by `rsa` so this backend stays in the same
// digest ecosystem as the 0.10 RC line without a crate-wide sha2 migration.
use rsa::{
BigUint, Pkcs1v15Sign, Pss, RsaPublicKey,
BoxedUint, Pkcs1v15Sign, Pss, RsaPrivateKey, RsaPublicKey,
pkcs1::{DecodeRsaPrivateKey, DecodeRsaPublicKey},
pkcs1v15::SigningKey,
pkcs8::AssociatedOid,
pss::BlindedSigningKey,
sha2::{
Sha256, Sha384, Sha512,
digest::{Digest, FixedOutputReset},
},
traits::SignatureScheme,
};
use sha2::{Digest, Sha256, Sha384, Sha512};
use signature::{RandomizedSigner, SignatureEncoding, Signer, Verifier};
use signature::{Signer, Verifier};

use crate::algorithms::AlgorithmFamily;
use crate::crypto::{JwtSigner, JwtVerifier};
use crate::decoding::DecodingKeyKind;
use crate::errors::{ErrorKind, Result, new_error};
use crate::{Algorithm, DecodingKey, EncodingKey};

fn to_boxed_uint(bytes: &[u8]) -> std::result::Result<BoxedUint, signature::Error> {
let bits_precision =
u32::try_from(bytes.len().saturating_mul(8)).map_err(signature::Error::from_source)?;
BoxedUint::from_be_slice(bytes, bits_precision).map_err(signature::Error::from_source)
Comment on lines +25 to +27
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The bits_precision calculation is likely to cause a panic or error for many RSA keys. crypto-bigint (used by rsa 0.10) requires bits_precision to be a multiple of Limb::BITS (which is 32 or 64 depending on the target). For common RSA exponents like 65537 ([1, 0, 1]), bytes.len() * 8 is 24, which is not a multiple of 64, causing BoxedUint::from_be_slice to return a Precision error. You should round the precision up to the nearest multiple of 64 to ensure compatibility across platforms.

Suggested change
let bits_precision =
u32::try_from(bytes.len().saturating_mul(8)).map_err(signature::Error::from_source)?;
BoxedUint::from_be_slice(bytes, bits_precision).map_err(signature::Error::from_source)
let bits = bytes.len().saturating_mul(8);
let bits_precision = u32::try_from(bits.next_multiple_of(64)).map_err(signature::Error::from_source)?;
BoxedUint::from_be_slice(bytes, bits_precision).map_err(signature::Error::from_source)

}
Comment on lines +24 to +28
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify BoxedUint::from_be_slice signature in the rsa crate's crypto-bigint dependency
# Look for usage patterns in the crate to confirm API compatibility

# Check if there are any tests or examples using similar patterns
rg -n "BoxedUint::from_be_slice" --type rust
rg -n "to_be_bytes\|from_be_slice" --type rust -C2

Repository: EffortlessSteven/jsonwebtoken

Length of output: 196


🏁 Script executed:

# Find Cargo.toml to check rsa crate version
find . -name "Cargo.toml" -type f | head -5

Repository: EffortlessSteven/jsonwebtoken

Length of output: 85


🏁 Script executed:

# Check rsa crate dependencies
grep -A5 "rsa" Cargo.toml 2>/dev/null || echo "Cargo.toml not found in root"

Repository: EffortlessSteven/jsonwebtoken

Length of output: 448


🏁 Script executed:

# Examine the full context of the to_boxed_uint function and its usage
cat -n src/crypto/rust_crypto/rsa.rs | head -40

Repository: EffortlessSteven/jsonwebtoken

Length of output: 1820


🏁 Script executed:

# Search for all BoxedUint usage patterns in the codebase
rg -n "BoxedUint" --type rust -B2 -A2

Repository: EffortlessSteven/jsonwebtoken

Length of output: 1079


🏁 Script executed:

# Check if there are tests for the to_boxed_uint function
rg -n "to_boxed_uint" --type rust -B3 -A3

Repository: EffortlessSteven/jsonwebtoken

Length of output: 1303


🌐 Web query:

BoxedUint::from_be_slice rsa 0.10 API signature crypto-bigint

💡 Result:

In the RustCrypto RSA crate version 0.10 (currently in RC, latest 0.10.0-rc.17 as of 2026-03-10), RSA keys use crypto-bigint's BoxedUint for big integers like modulus n and exponent e. To construct an RSA public key from big-endian byte slices (e.g., for raw RSA signature verification), use RsaPublicKey::new(n, e), where n and e are BoxedUint created via BoxedUint::from_be_slice(slice). From the source code in RustCrypto/RSA src/key.rs: pub fn new(n: BoxedUint, e: BoxedUint) -> Result It validates the modulus (must be odd, non-zero) and creates BoxedMontyParams for modular arithmetic. BoxedUint::from_be_slice is a const fn method on BoxedUint (available since crypto-bigint ~0.5 per changelog), parsing big-endian bytes into a heap-allocated big integer, trimming leading zeros. Example usage for signature verification (hazmat warning: low-level, validate inputs): use rsa::{RsaPublicKey, signature::{Verifier, DigestVerifier}}; use crypto_bigint::BoxedUint; use sha2::Sha256; // Assume n_bytes and e_bytes are big-endian byte slices for n and e let n = BoxedUint::from_be_slice(&n_bytes); let e = BoxedUint::from_be_slice(&e_bytes); let pub_key = RsaPublicKey::new(n, e).expect("invalid public key"); // For PKCS1v15 signature verification: let digest = Sha256::digest(b"message"); pub_key.verify_digest(&pkcs1v15::Signature, digest, &signature_bytes)?;

Citations:


🌐 Web query:

rsa 0.10.0-rc.17 BoxedUint::from_be_slice two parameters bits_precision signature

💡 Result:

In the RustCrypto RSA crate version 0.10.0-rc.17, BoxedUint is a struct from the crypto-bigint dependency, described as a "Fixed-precision heap-allocated big unsigned integer". It is used in RSA key components like the modulus n (NonZero) and exponents. No explicit documentation or source snippets for a BoxedUint::from_be_slice method with two parameters (likely a &[u8] slice and bits_precision usize) were found in the available results. However, crypto-bigint provides methods like from_be_hex for Uint (stack-allocated counterpart), suggesting from_be_slice likely decodes a big-endian byte slice into a BoxedUint, with bits_precision specifying the fixed bit width (e.g., for RSA key sizes like 2048 bits). This method is likely used in low-level "hazmat" RSA operations, such as constructing RSA keys from components or processing raw signatures, where signatures are byte arrays decoded to bigints matching the key's bit precision. For example, in key validation, bits_precision is compared between primes and modulus (p.bits_precision.cmp(&q.bits_precision)), and resizing occurs with methods like resize_unchecked. The hazmat module provides low-level RSA functions, potentially involving signature encoding/decoding with such conversions. Official docs are at https://docs.rs/rsa/0.10.0-rc.17/rsa/ (though some builds failed; check latest). Source at https://github.com/RustCrypto/RSA. To use: enable the "hazmat" feature. Example context from key.rs source: primes.iter.fold(BoxedUint::one, |acc, p| acc.concatenating_mul(&p)).

Citations:


🏁 Script executed:

# Check if there's a Cargo.lock file that might show resolved versions
cat Cargo.lock | grep -A10 "name = \"rsa\"" | head -20

Repository: EffortlessSteven/jsonwebtoken

Length of output: 115


🌐 Web query:

site:github.com RustCrypto RSA BoxedUint from_be_slice 0.10 source code

💡 Result:

The RustCrypto RSA crate (https://github.com/RustCrypto/RSA) uses crypto_bigint::BoxedUint from the crypto-bigint crate (https://github.com/RustCrypto/crypto-bigint) for big integer arithmetic in version 0.10 (currently in RC as of March 2026, e.g., v0.10.0-rc.17). This switch was made in PR #394 (merged Feb 2025) to replace num-bigint with crypto-bigint for constant-time operations. No source code snippet for a from_be_slice method on BoxedUint was found in the search results. BoxedUint is a heap-allocated big integer type designed for cryptographic use, with constant-time operations. It likely supports decoding from big-endian byte slices via methods like from_be_bytes, from_be_slice, or decode_be, but the exact implementation is in the crypto-bigint source (likely src/integer/boxed.rs or similar). For the latest source, view: - RSA master: https://github.com/RustCrypto/RSA/blob/master/src/key.rs (uses BoxedUint) - crypto-bigint master: https://github.com/RustCrypto/crypto-bigint/tree/master/src (contains BoxedUint impl) Official docs: https://docs.rs/rsa (latest) and https://docs.rs/crypto-bigint (check for 0.10 compat). No 0.10 docs.rs page found, suggesting pre-release status.

Citations:


🏁 Script executed:

# Search for any test files or examples that show BoxedUint::from_be_slice usage
fd -e rs -type f | xargs rg -l "from_be_slice" | head -5

Repository: EffortlessSteven/jsonwebtoken

Length of output: 290


🌐 Web query:

crypto-bigint BoxedUint from_be_slice documentation API signature parameters

💡 Result:

The crypto-bigint crate provides BoxedUint as a fixed-precision heap-allocated big unsigned integer (requires the alloc feature). It includes methods like from_be_slice_vartime(&[u8]) and from_le_slice_vartime(&[u8]) for parsing from byte slices, added in version 0.6.0 per the changelog. Constant-time versions from_be_slice(&[u8]) and from_le_slice(&[u8]) were added earlier. There is no explicit documentation snippet for the exact signature of from_be_slice in the searched results, but analogous Uint::from_be_slice(bytes: &[u8]) -> Self exists as a const fn for big-endian byte slices. BoxedUint likely has pub fn from_be_slice(bytes: &[u8]) -> Result<Self, TryFromSliceError>, where TryFromSliceError is returned if the slice cannot instantiate the object (e.g., wrong length or leading zero issues). Usage requires alloc feature; it's designed for cryptographic constant-time operations.

Citations:


🏁 Script executed:

# Look for any comments or documentation about the bits_precision parameter in the RSA file
rg -n "bits_precision\|from_be_slice" src/crypto/rust_crypto/rsa.rs -B5 -A5

Repository: EffortlessSteven/jsonwebtoken

Length of output: 55


🌐 Web query:

rsa crate from_be_slice implementation 0.10 rc17 two parameters boxed_uint bits_precision

💡 Result:

In the Rust rsa crate version 0.10.0-rc.17, there is no direct from_be_slice method on BoxedUint (from the crypto-bigint dependency) that takes two parameters including bits_precision. Instead, crypto-bigint's Uint type (used internally) provides pub const fn from_be_slice(bytes: &[u8]) -> Self, which decodes big-endian bytes into a stack-allocated big integer without a precision parameter. BoxedUint is a heap-allocated wrapper around Uint. RSA key construction uses RsaPublicKey::new(n: BoxedUint, e: BoxedUint) -> Result or RsaPrivateKey::from_components(n: BoxedUint, ...), where BoxedUint values are typically created via decoding (e.g., PKCS#1/PKCS#8 parsing) or key generation. The bits parameter appears in key generation like RsaPrivateKey::new(rng, bit_size: usize), specifying modulus size, not parsing. No evidence of a two-parameter from_be_slice(bits_precision) in the crate or crypto-bigint. Key parsing likely uses from_be_slice internally during PEM/DER decoding via pkcs1/pkcs8 dependencies, trimming leading zeros and validating size against RsaPublicKey::MAX_SIZE (4096 bits).

Citations:


🏁 Script executed:

# Try to view the exact lines around from_be_slice in context
sed -n '20,35p' src/crypto/rust_crypto/rsa.rs

Repository: EffortlessSteven/jsonwebtoken

Length of output: 651


🏁 Script executed:

# Check if there are any build/test configurations that might reveal compilation status
find . -name "*.toml" -o -name "pytest.ini" -o -name "tox.ini" | head -10

Repository: EffortlessSteven/jsonwebtoken

Length of output: 100


🏁 Script executed:

# Search the entire rsa.rs file for any trait implementations or custom from_be_slice
cat src/crypto/rust_crypto/rsa.rs | grep -n "from_be_slice\|impl\|trait" | head -20

Repository: EffortlessSteven/jsonwebtoken

Length of output: 495


Fix BoxedUint::from_be_slice API call—remove the bits_precision parameter.

The BoxedUint::from_be_slice method in rsa 0.10.0-rc.17 accepts only a single parameter (&[u8]). The current call with two parameters—BoxedUint::from_be_slice(bytes, bits_precision)—is incompatible with the actual API and will not compile. Remove the bits_precision argument:

fn to_boxed_uint(bytes: &[u8]) -> std::result::Result<BoxedUint, signature::Error> {
    BoxedUint::from_be_slice(bytes).map_err(signature::Error::from_source)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/crypto/rust_crypto/rsa.rs` around lines 24 - 28, In to_boxed_uint remove
the unused bits_precision computation and call BoxedUint::from_be_slice with
only the bytes slice (i.e., replace BoxedUint::from_be_slice(bytes,
bits_precision) with BoxedUint::from_be_slice(bytes)) and map its error with
signature::Error::from_source so the function compiles against the rsa
0.10.0-rc.17 API; keep the function signature and the final
map_err(signature::Error::from_source) behavior.


fn try_sign_rsa<H>(
encoding_key: &EncodingKey,
msg: &[u8],
Expand All @@ -27,21 +35,22 @@ fn try_sign_rsa<H>(
where
H: Digest + AssociatedOid + FixedOutputReset,
{
let mut rng = rand::thread_rng();
if pss {
let private_key = rsa::RsaPrivateKey::from_pkcs1_der(encoding_key.inner())
.map_err(signature::Error::from_source)?;
let signing_key = BlindedSigningKey::<H>::new(private_key);
Ok(signing_key.sign_with_rng(&mut rng, msg).to_vec())
let mut rng = rand10::rng();
let private_key = RsaPrivateKey::from_pkcs1_der(encoding_key.inner())
.map_err(signature::Error::from_source)?;
let digest = H::digest(msg);

let signature = if pss {
private_key.sign_with_rng(&mut rng, Pss::<H>::new(), digest.as_ref())
} else {
let private_key = rsa::RsaPrivateKey::from_pkcs1_der(encoding_key.inner())
.map_err(signature::Error::from_source)?;
let signing_key = SigningKey::<H>::new(private_key);
Ok(signing_key.sign_with_rng(&mut rng, msg).to_vec())
private_key.sign_with_rng(&mut rng, Pkcs1v15Sign::new::<H>(), digest.as_ref())
}
.map_err(signature::Error::from_source)?;

Ok(signature)
}

fn verify_rsa<S: SignatureScheme, H: Digest + AssociatedOid>(
fn verify_rsa<S: SignatureScheme, H: Digest>(
scheme: S,
decoding_key: &DecodingKey,
msg: &[u8],
Expand All @@ -53,12 +62,13 @@ fn verify_rsa<S: SignatureScheme, H: Digest + AssociatedOid>(
DecodingKeyKind::SecretOrDer(bytes) => {
RsaPublicKey::from_pkcs1_der(bytes)
.map_err(signature::Error::from_source)?
.verify(scheme, &digest, signature)
.verify(scheme, digest.as_ref(), signature)
.map_err(signature::Error::from_source)?;
}
DecodingKeyKind::RsaModulusExponent { n, e } => {
RsaPublicKey::new(BigUint::from_bytes_be(n), BigUint::from_bytes_be(e))?
.verify(scheme, &digest, signature)
RsaPublicKey::new(to_boxed_uint(n)?, to_boxed_uint(e)?)
.map_err(signature::Error::from_source)?
.verify(scheme, digest.as_ref(), signature)
.map_err(signature::Error::from_source)?;
}
};
Expand Down Expand Up @@ -115,7 +125,7 @@ macro_rules! define_rsa_verifier {
signature: &Vec<u8>,
) -> std::result::Result<(), signature::Error> {
if $pss {
verify_rsa::<Pss, $hash>(Pss::new::<$hash>(), &self.0, msg, signature)
verify_rsa::<_, $hash>(Pss::<$hash>::new(), &self.0, msg, signature)
} else {
verify_rsa::<_, $hash>(Pkcs1v15Sign::new::<$hash>(), &self.0, msg, signature)
}
Expand Down