-
Notifications
You must be signed in to change notification settings - Fork 0
refactor(rust_crypto): lift RSA backend to rsa 0.10.0-rc.17 #1
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
base: master
Are you sure you want to change the base?
Changes from all commits
365fcb4
c6605f2
0ed6873
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -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 } | ||||||
| rsa = { version = "0.10.0-rc.17", optional = true, features = ["sha2"] } | ||||||
|
Comment on lines
+43
to
+44
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 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:
💡 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 🤖 Prompt for AI Agents |
||||||
| 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"] } | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The dependency
Suggested change
|
||||||
|
|
||||||
| [dev-dependencies] | ||||||
| wasm-bindgen-test = "0.3.1" | ||||||
|
|
@@ -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]] | ||||||
|
|
||||||
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Suggested change
|
||||||||||||||
| } | ||||||||||||||
|
Comment on lines
+24
to
+28
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 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 -C2Repository: EffortlessSteven/jsonwebtoken Length of output: 196 🏁 Script executed: # Find Cargo.toml to check rsa crate version
find . -name "Cargo.toml" -type f | head -5Repository: 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 -40Repository: EffortlessSteven/jsonwebtoken Length of output: 1820 🏁 Script executed: # Search for all BoxedUint usage patterns in the codebase
rg -n "BoxedUint" --type rust -B2 -A2Repository: 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 -A3Repository: EffortlessSteven/jsonwebtoken Length of output: 1303 🌐 Web query:
💡 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:
💡 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 -20Repository: EffortlessSteven/jsonwebtoken Length of output: 115 🌐 Web query:
💡 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 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 -5Repository: EffortlessSteven/jsonwebtoken Length of output: 290 🌐 Web query:
💡 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 -A5Repository: EffortlessSteven/jsonwebtoken Length of output: 55 🌐 Web query:
💡 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.rsRepository: 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 -10Repository: 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 -20Repository: EffortlessSteven/jsonwebtoken Length of output: 495 Fix The 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 |
||||||||||||||
|
|
||||||||||||||
| fn try_sign_rsa<H>( | ||||||||||||||
| encoding_key: &EncodingKey, | ||||||||||||||
| msg: &[u8], | ||||||||||||||
|
|
@@ -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], | ||||||||||||||
|
|
@@ -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)?; | ||||||||||||||
| } | ||||||||||||||
| }; | ||||||||||||||
|
|
@@ -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) | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The dependency
randversion0.10does not currently exist on crates.io (the latest stable is0.9.0). Using a non-existent version will cause compilation to fail. If you intended to use the latest version that supports the newrand_coreecosystem used byrsa0.10, you likely meant version0.9.