Skip to content

fix(rsa): preserve V1 deterministic RSA fixtures while adopting rsa 0.10 types#304

Merged
EffortlessSteven merged 1 commit into
mainfrom
chore/rsa-0.10-rc17
Mar 26, 2026
Merged

fix(rsa): preserve V1 deterministic RSA fixtures while adopting rsa 0.10 types#304
EffortlessSteven merged 1 commit into
mainfrom
chore/rsa-0.10-rc17

Conversation

@EffortlessSteven

@EffortlessSteven EffortlessSteven commented Mar 25, 2026

Copy link
Copy Markdown
Member

Summary

This refocuses #304 as the V1-compatibility bridge for RSA.

Scope

  • keep deterministic RSA fixture bytes and KIDs unchanged (V1 compatibility)
  • migrate the owned/internal RSA path to rsa 0.10.0-rc.17
  • maintain a compatibility bridge in uselesskey-rsa for the legacy 0.9 generation path
  • keep legacy rsa 0.9.x / rand_core 0.6 usage explicitly in upstream islands (jsonwebtoken, pgp)

Compatibility

  • RSA PKCS#1/PSs signing verification in BDD/interop/RustCrypto now uses rsa::sha2::Sha256
  • cargo tree -i rsa@0.10.0-rc.17 shows the owned internal path
  • cargo tree -i rsa@0.9.10 and cargo tree -i rand_core@0.6.4 remain in explicit legacy islands only

@coderabbitai

coderabbitai Bot commented Mar 25, 2026

Copy link
Copy Markdown

Summary by CodeRabbit

  • Chores
    • Updated RSA cryptographic library dependency from version 0.9.10 to 0.10.0-rc.17 with restructured feature configuration.
    • Consolidated SHA-256 hashing imports across the codebase to use the RSA library's integrated SHA-2 module.
    • Added feature flag support for legacy RSA compatibility.

Walkthrough

Workspace rsa bumped from 0.9.10 to 0.10.0-rc.17 and its feature set changed from pem to sha2. Codebase imports of Sha256 were switched to rsa::sha2::Sha256. RSA key handling updated to use new API methods (n_bytes, e_bytes, trimmed BE conversions) and optional legacy-rsa09 support was added.

Changes

Cohort / File(s) Summary
Workspace dependency updates
Cargo.toml, crates/uselesskey-bdd-steps/Cargo.toml, crates/uselesskey-interop-tests/Cargo.toml, tests/Cargo.toml, crates/uselesskey-rustcrypto/Cargo.toml
Updated workspace rsa to 0.10.0-rc.17 and enabled sha2 feature; removed standalone sha2 deps and adjusted feature wiring.
uselesskey-rsa crate config
crates/uselesskey-rsa/Cargo.toml
Changed default features to include legacy-rsa09; added optional legacy-rsa09 feature wiring and optional RNG deps (rand_chacha / rand_core) plus workspace RNG alternatives.
RSA key handling and JWK serialization
crates/uselesskey-rsa/src/keypair.rs
Added feature-gated branching for legacy vs new rsa APIs; always store rsa10 types internally; generate/encode legacy keys then convert to rsa10 when legacy-rsa09 enabled; replaced n().to_bytes_be()/e().to_bytes_be() with n_bytes()/e_bytes() and updated private component conversions to to_be_bytes_trimmed_vartime() / retrieve().
Sha256 import path changes (tests & docs)
crates/uselesskey-interop-tests/tests/..., crates/uselesskey-rustcrypto/tests/..., crates/uselesskey-rustcrypto/src/lib.rs, crates/uselesskey-rustcrypto/README.md, tests/cross_adapter.rs, crates/uselesskey-bdd-steps/src/lib.rs
Replaced sha2::Sha256 imports with rsa::sha2::Sha256 across tests, examples, and docs; no behavioral changes aside from type resolution.
Test RNG and helper adjustments
crates/uselesskey-rustcrypto/tests/rustcrypto_cross_verify.rs, crates/uselesskey-rustcrypto/tests/...
Changed local rng() in tests to use rand_chacha10::ChaCha20Rng (deterministic seed) and updated rng imports to workspace RNG variants.
Minor code adjustments
crates/uselesskey-bdd-steps/src/lib.rs, crates/uselesskey-interop-tests/..., crates/uselesskey-rustcrypto/tests/..., crates/uselesskey-rustcrypto/tests/adapter_integration.rs
Adjusted RSA modulus size check to use RsaPublicKey::size() / n_bytes(); updated SigningKey/VerifyingKey generic type usage to reference rsa::sha2::Sha256.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰
New SHA paths I gently tread,
Bytes trimmed, old and new now wed.
Keys exchange their hopping tune,
RNG seeds beneath the moon.
I nibble bugs — then code is fed.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix(rsa): preserve V1 deterministic RSA fixtures while adopting rsa 0.10 types' accurately reflects the main change: upgrading to rsa 0.10 while maintaining backward compatibility with V1 fixtures.
Description check ✅ Passed The description clearly explains the scope, objectives, and compatibility notes for the RSA migration, relating directly to the changeset across all modified files.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/rsa-0.10-rc17

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request undertakes a significant upgrade of the project's internal RSA implementation by migrating to the rsa crate's 0.10.0-rc.17 pre-release version. This update is crucial for aligning the project's cryptographic dependencies with newer versions of rand_core and rand_chacha, ensuring modern and secure random number generation. The changes primarily involve updating dependency versions in Cargo.toml and Cargo.lock, along with necessary code adjustments in various modules to accommodate API changes in the new rsa and signature crates, particularly concerning RSA key part access and SHA256 hashing paths.

Highlights

  • RSA Crate Upgrade: Updated the workspace rsa crate to version 0.10.0-rc.17, aligning with newer cryptographic standards and features.
  • RNG Dependency Alignment: Migrated internal RSA-related crates (uselesskey-rsa, uselesskey-rustcrypto) to align with rand_core 0.10 and rand_chacha10 for modern random number generation.
  • API Adjustments: Adjusted RustCrypto/BDD/interop tests and core RSA logic to reflect API changes in rsa 0.10.0-rc.17 and signature 3, including new methods for accessing RSA key parts and updated SHA256 hashing paths.
  • Dependency Management: Ensured compatibility with existing upstream dependencies like jsonwebtoken and pgp by explicitly managing rsa versions where necessary, preventing breaking changes for external integrations.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request updates the rsa crate to version 0.10.0-rc.17 and its associated rand_core and rand_chacha dependencies. This upgrade required adjusting use statements for sha2::Sha256 to rsa::sha2::Sha256 and updating methods for accessing RSA key components. The serialization of private keys to JWK format in crates/uselesskey-rsa/src/keypair.rs needs correction due to API changes in rsa v0.10, which now wraps several key components in zeroize::Secret and other newtypes, requiring explicit unwrapping.

Comment on lines +435 to +444
let d = private.d().to_be_bytes_trimmed_vartime();
let p = primes[0].to_be_bytes_trimmed_vartime();
let q = primes[1].to_be_bytes_trimmed_vartime();
let dp = private.dp().expect("dp").to_be_bytes_trimmed_vartime();
let dq = private.dq().expect("dq").to_be_bytes_trimmed_vartime();
let qi = private
.qinv()
.expect("qinv")
.retrieve()
.to_be_bytes_trimmed_vartime();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

This code for serializing the private key to a JWK appears to be incorrect due to API changes in rsa v0.10. The new version wraps several key components in zeroize::Secret and other newtypes to prevent accidental leakage. You need to correctly unwrap these values before using them.

Specifically:

  • d(), dp(), dq() and the items in primes() now return values where the underlying Uint is wrapped in Secret. You need to use .expose_secret() to get a reference to the inner value before calling .to_be_bytes_trimmed_vartime().
  • qinv() returns an Option<Secret<Inv<...>>>. You need to call .retrieve() on the Secret to get the Inv, and then another .retrieve() on the Inv to get the underlying Uint.

The current code will not compile. Here is a suggested fix:

Suggested change
let d = private.d().to_be_bytes_trimmed_vartime();
let p = primes[0].to_be_bytes_trimmed_vartime();
let q = primes[1].to_be_bytes_trimmed_vartime();
let dp = private.dp().expect("dp").to_be_bytes_trimmed_vartime();
let dq = private.dq().expect("dq").to_be_bytes_trimmed_vartime();
let qi = private
.qinv()
.expect("qinv")
.retrieve()
.to_be_bytes_trimmed_vartime();
let d = private.d().expose_secret().to_be_bytes_trimmed_vartime();
let p = primes[0].expose_secret().to_be_bytes_trimmed_vartime();
let q = primes[1].expose_secret().to_be_bytes_trimmed_vartime();
let dp = private.dp().expect("dp").expose_secret().to_be_bytes_trimmed_vartime();
let dq = private.dq().expect("dq").expose_secret().to_be_bytes_trimmed_vartime();
let qi = private
.qinv()
.expect("qinv")
.retrieve()
.retrieve()
.to_be_bytes_trimmed_vartime();

@EffortlessSteven EffortlessSteven changed the title chore(rsa): migrate internal RSA crates to 0.10.0-rc.17 fix(rsa): preserve V1 deterministic RSA fixtures while adopting rsa 0.10 types Mar 25, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
crates/uselesskey-rsa/src/keypair.rs (2)

613-639: ⚠️ Potential issue | 🟠 Major

Version the derivation namespace for the rsa10 generator.

This branch swaps to a different RNG/keygen stack but still writes under the v1 cache domain. For builds without legacy-rsa09, the same (seed, label, spec, variant) now produces different fixture material under uselesskey:rsa:keypair, so the deterministic compatibility contract is no longer true.

🔧 Possible fix
- pub const DOMAIN_RSA_KEYPAIR: &str = "uselesskey:rsa:keypair";
+ #[cfg(feature = "legacy-rsa09")]
+ pub const DOMAIN_RSA_KEYPAIR: &str = "uselesskey:rsa:keypair";
+ #[cfg(not(feature = "legacy-rsa09"))]
+ pub const DOMAIN_RSA_KEYPAIR: &str = "uselesskey:rsa:keypair:v2";

As per coding guidelines, "Maintain deterministic derivation stability; bump derivation version if algorithm changes".

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

In `@crates/uselesskey-rsa/src/keypair.rs` around lines 613 - 639, The rsa10
keygen (rsa10::RsaPrivateKey / rsa10::RsaPublicKey) path currently writes under
the existing derivation namespace ("uselesskey:rsa:keypair") which breaks
deterministic compatibility; change the namespace used by the non-legacy branch
(guarded by #[cfg(not(feature = "legacy-rsa09"))]) to a bumped version (e.g.,
"uselesskey:rsa:keypair:v2" or "uselesskey:rsa:keypair:rsa10") so
rsa10-generated fixtures live in a new domain, and leave the legacy (feature =
"legacy-rsa09") namespace unchanged; update all code that constructs or looks up
that derivation key in keypair.rs to use the new string in the non-legacy cfg
path.

405-459: 🧹 Nitpick | 🔵 Trivial

JWK fields are already protected by assert_eq!(v1, v2) in the determinism test.

Line 675 of tests/determinism_regression.rs (fn jwk_rsa_deterministic) compares the full JWK object using assert_eq!(v1, v2), which includes all cryptographic fields (n, e, d, p, q, dp, dq, qi). Any change to field encoding or padding will fail the test.

The snapshot captures only metadata (kty, alg, use, kid), but the underlying regression protection is sound. To improve maintainability and visibility, consider expanding the snapshot to explicitly include the cryptographic field values (e.g., first 32 characters of n and e, or redacted placeholders) so future changes to encoding are immediately visible in snapshots.

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

In `@crates/uselesskey-rsa/src/keypair.rs` around lines 405 - 459, The determinism
test already asserts full JWK equality, but the snapshot only shows metadata;
update the jwk_rsa_deterministic snapshot (the assertion that records the
snapshot of private_key_jwk()) to include representative cryptographic field
data so regressions in encodings are visible—modify the snapshot capture to
include either redacted placeholders or truncated prefixes (e.g., first 32
chars) of fields n, e, d, p, q, dp, dq, qi produced by
RsaKeypair::private_key_jwk() (look for private_key_jwk and the test fn
jwk_rsa_deterministic in tests/determinism_regression.rs) and ensure the
snapshot comparison records those values while still redacting full secrets.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/uselesskey-rsa/Cargo.toml`:
- Around line 33-34: The Cargo.toml currently sets default = ["legacy-rsa09"],
causing most workspace consumers to pull the legacy rsa09 path implicitly;
change the default feature list to default = [] so legacy-rsa09 is opt-in, and
update documentation/consumer crates to enable the legacy-rsa09 feature (or set
default-features = false) when they intentionally need the "legacy-rsa09"
feature that depends on "rsa09", "rand_chacha", and "rand_core". Ensure the
feature name "legacy-rsa09" remains defined and referenced consistently so
consumers can explicitly opt in.

---

Outside diff comments:
In `@crates/uselesskey-rsa/src/keypair.rs`:
- Around line 613-639: The rsa10 keygen (rsa10::RsaPrivateKey /
rsa10::RsaPublicKey) path currently writes under the existing derivation
namespace ("uselesskey:rsa:keypair") which breaks deterministic compatibility;
change the namespace used by the non-legacy branch (guarded by #[cfg(not(feature
= "legacy-rsa09"))]) to a bumped version (e.g., "uselesskey:rsa:keypair:v2" or
"uselesskey:rsa:keypair:rsa10") so rsa10-generated fixtures live in a new
domain, and leave the legacy (feature = "legacy-rsa09") namespace unchanged;
update all code that constructs or looks up that derivation key in keypair.rs to
use the new string in the non-legacy cfg path.
- Around line 405-459: The determinism test already asserts full JWK equality,
but the snapshot only shows metadata; update the jwk_rsa_deterministic snapshot
(the assertion that records the snapshot of private_key_jwk()) to include
representative cryptographic field data so regressions in encodings are
visible—modify the snapshot capture to include either redacted placeholders or
truncated prefixes (e.g., first 32 chars) of fields n, e, d, p, q, dp, dq, qi
produced by RsaKeypair::private_key_jwk() (look for private_key_jwk and the test
fn jwk_rsa_deterministic in tests/determinism_regression.rs) and ensure the
snapshot comparison records those values while still redacting full secrets.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: fde6905f-16d4-46e7-ae23-5df1716bdc6a

📥 Commits

Reviewing files that changed from the base of the PR and between d7d523e and 113d5d7.

⛔ Files ignored due to path filters (14)
  • Cargo.lock is excluded by !**/*.lock
  • crates/uselesskey-rustcrypto/tests/snapshots/snapshots_rustcrypto__rsa_snapshots__rustcrypto_rsa_2048_public_key.snap is excluded by !**/*.snap
  • crates/uselesskey-rustcrypto/tests/snapshots/snapshots_rustcrypto__rsa_snapshots__rustcrypto_rsa_4096_public_key.snap is excluded by !**/*.snap
  • crates/uselesskey-rustls/tests/snapshots/snapshots_rustls__x509_snapshots__rustls_chain_metadata.snap is excluded by !**/*.snap
  • crates/uselesskey-rustls/tests/snapshots/snapshots_rustls__x509_snapshots__rustls_self_signed_cert.snap is excluded by !**/*.snap
  • crates/uselesskey-rustls/tests/snapshots/snapshots_rustls__x509_snapshots__rustls_self_signed_custom_domain.snap is excluded by !**/*.snap
  • crates/uselesskey-rustls/tests/snapshots/snapshots_rustls__x509_snapshots__rustls_x509_chain_key_sizes.snap is excluded by !**/*.snap
  • crates/uselesskey-tonic/tests/snapshots/snapshots_tonic__chain_snapshots__tonic_chain_custom_domain_metadata.snap is excluded by !**/*.snap
  • crates/uselesskey-tonic/tests/snapshots/snapshots_tonic__chain_snapshots__tonic_chain_tls_metadata.snap is excluded by !**/*.snap
  • crates/uselesskey-tonic/tests/snapshots/snapshots_tonic__key_size_snapshots__tonic_chain_key_sizes.snap is excluded by !**/*.snap
  • crates/uselesskey-tonic/tests/snapshots/snapshots_tonic__mtls_snapshots__tonic_mtls_chain_cert_details.snap is excluded by !**/*.snap
  • crates/uselesskey-tonic/tests/snapshots/snapshots_tonic__mtls_snapshots__tonic_mtls_configs_build.snap is excluded by !**/*.snap
  • crates/uselesskey-tonic/tests/snapshots/snapshots_tonic__self_signed_snapshots__tonic_self_signed_custom_domain.snap is excluded by !**/*.snap
  • crates/uselesskey-tonic/tests/snapshots/snapshots_tonic__self_signed_snapshots__tonic_self_signed_tls_metadata.snap is excluded by !**/*.snap
📒 Files selected for processing (21)
  • Cargo.toml
  • crates/uselesskey-bdd-steps/Cargo.toml
  • crates/uselesskey-bdd-steps/src/lib.rs
  • crates/uselesskey-interop-tests/Cargo.toml
  • crates/uselesskey-interop-tests/tests/all_adapters.rs
  • crates/uselesskey-interop-tests/tests/cross_adapter.rs
  • crates/uselesskey-interop-tests/tests/cross_adapter_interop_matrix.rs
  • crates/uselesskey-interop-tests/tests/cross_adapter_signing.rs
  • crates/uselesskey-interop-tests/tests/cross_verify.rs
  • crates/uselesskey-interop-tests/tests/interop.rs
  • crates/uselesskey-interop-tests/tests/random_mode.rs
  • crates/uselesskey-rsa/Cargo.toml
  • crates/uselesskey-rsa/src/keypair.rs
  • crates/uselesskey-rustcrypto/Cargo.toml
  • crates/uselesskey-rustcrypto/README.md
  • crates/uselesskey-rustcrypto/src/lib.rs
  • crates/uselesskey-rustcrypto/tests/adapter_integration.rs
  • crates/uselesskey-rustcrypto/tests/rustcrypto_comprehensive.rs
  • crates/uselesskey-rustcrypto/tests/rustcrypto_cross_verify.rs
  • tests/Cargo.toml
  • tests/cross_adapter.rs

Comment on lines +33 to +34
default = ["legacy-rsa09"]
legacy-rsa09 = ["dep:rsa09", "dep:rand_chacha", "dep:rand_core"]

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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

python - <<'PY'
import pathlib, tomllib

for p in sorted(pathlib.Path(".").rglob("Cargo.toml")):
    data = tomllib.loads(p.read_text(encoding="utf-8"))
    for section in ("dependencies", "dev-dependencies", "build-dependencies"):
        deps = data.get(section) or {}
        dep = deps.get("uselesskey-rsa")
        if dep is None:
            continue
        if isinstance(dep, str):
            print(f"{p} [{section}] -> version-only (default features implicitly ON)")
        elif isinstance(dep, dict):
            df = dep.get("default-features", "<implicit true>")
            feats = dep.get("features", [])
            print(f"{p} [{section}] -> default-features={df}, features={feats}")
PY

Repository: EffortlessMetrics/uselesskey

Length of output: 1900


Most workspace consumers will silently pull legacy rsa09 by default; consider making legacy opt-in.

With default = ["legacy-rsa09"], 11 out of 12 workspace consumers that depend on uselesskey-rsa are using implicit default features and thus pulling the legacy path. Only uselesskey-rustcrypto explicitly sets default-features = false. This contradicts the containment objective of having legacy dependencies be explicit and isolated. Switch to default = [] and require consumers that need legacy support to explicitly enable legacy-rsa09.

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

In `@crates/uselesskey-rsa/Cargo.toml` around lines 33 - 34, The Cargo.toml
currently sets default = ["legacy-rsa09"], causing most workspace consumers to
pull the legacy rsa09 path implicitly; change the default feature list to
default = [] so legacy-rsa09 is opt-in, and update documentation/consumer crates
to enable the legacy-rsa09 feature (or set default-features = false) when they
intentionally need the "legacy-rsa09" feature that depends on "rsa09",
"rand_chacha", and "rand_core". Ensure the feature name "legacy-rsa09" remains
defined and referenced consistently so consumers can explicitly opt in.

@EffortlessSteven EffortlessSteven merged commit 4e65410 into main Mar 26, 2026
3 of 4 checks passed
@EffortlessSteven EffortlessSteven deleted the chore/rsa-0.10-rc17 branch April 3, 2026 04:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant