Skip to content

feat(picky-crypto): add the RustCrypto backend - #559

Merged
Benoît Cortier (CBenoit) merged 30 commits into
masterfrom
cbenoit-picky-crypto-rustcrypto
Oct 15, 2026
Merged

Benoît Cortier (CBenoit) merged 30 commits into
masterfrom
cbenoit-picky-crypto-rustcrypto

Conversation

@CBenoit

@CBenoit Benoît Cortier (CBenoit) commented Oct 10, 2026 •

Copy link
Copy Markdown
Member

Implementing the picky-crypto contract needs at least one software backend. RustCrypto covers every contract algorithm except the key-based KDFs and FFDH, and it includes the legacy algorithms NTLM, Kerberos and legacy PKCS#12 need.

Add the picky-crypto-rustcrypto crate (publish = false). It is a thin adapter: each entry maps the contract's arguments to RustCrypto calls, checks algorithm-fixed lengths and constant bytes, and maps library errors to the contract's error set. Library capabilities the adapter relies on are cited at their locked versions in code comments. fips() is always false. Library zeroize features are enabled, and adapter-held secret copies and returned buffers use zeroizing storage. No secret-holding type implements Debug.

Algorithm families are features, with nothing enabled by default. A build without features yields an empty provider, and each feature only adds entries:

  • random: the operating-system secure-random entry
  • digest: SHA-1, SHA-2 and SHA3-384/512 hashes, HMAC and PBKDF2
  • aes: AES-CBC
  • aead: AES-GCM
  • key-wrap: AES-KW
  • rsa: PKCS#1 v1.5 signature verification (MD5, SHA-1, SHA-2, SHA3-384/512), PKCS#1 v1.5 and OAEP encryption, private-key loading, 2048/3072/4096-bit generation
  • nist-ec: ECDSA and ECDH on P-256, P-384 and P-521, private-key loading and generation
  • curve25519: Ed25519 verification, loading and generation; X25519 ephemeral and static agreement
  • legacy: MD4, MD5, RC4, 3DES-EDE3-CBC and RC2-CBC
  • all: every family (64 entries)

aead, rsa, nist-ec and curve25519 enable random, because they draw randomness from its generator. Key-based KDFs and FFDH are not provided; other providers supply them.

Where the library falls short of the contract, the adapter uses library functions only:

  • RSA key generation expands a 256-bit OS seed with ChaCha20, because the library's generator interface cannot report a randomness failure.
  • Ephemeral X25519 fills its scalar from the OS generator, because the library's ephemeral type panics on randomness failure.
  • Ed25519 verification rejects non-canonical public-key encodings, which the library accepts (ZIP-215), by comparing the library's recompression with the input.
  • Ed25519 verification checks S < L (RFC 8032 §5.1.7) with curve25519-dalek, because the library's own check weakens when any crate in a build enables ed25519-dalek's legacy_compatibility feature.
  • RSA keys beyond the library's modulus or exponent limits return Unsupported: public-key encodings longer than the largest key within those limits are refused before decoding, and private keys are checked after parsing, before key construction.
  • PBKDF2 output, CBC and RC4 data, and AES-GCM ciphertext beyond 2^31 − 1 bytes return Unsupported before allocation, after the fixed-length and standard-range checks.

RSA signing and decryption are blinded, and ECDSA signing mixes OS randomness into the RFC 6979 nonce.
picky-crypto-rustcrypto/deny.toml also bans ed25519-dalek's legacy_compatibility feature over the workspace.

CONTRACT.md clarifications motivated by this backend:

  • §4: library key-validation failures map to InvalidKey, except when the library only rejects a well-formed key for its size or parameters, which maps to Unsupported.
  • Rule 4 and §4: lengths fixed by the algorithm or operation and ranges defined by its standard bound the valid domain, and their error always wins; a backend's capacity limit, a bound on a length or numeric parameter outside the must-support set, may win over a structure error with Unsupported; a backend may skip validation work that grows with the exceeded parameter.
  • §7: handling RSA private keys whose components are not a consistent RSA private key for (n, e) is implementation-defined per call, for operations the key supports: each returns InvalidKey or behaves as specified under (n, e). Protocols exposed to padding oracles apply their own countermeasure.
  • §12: exact outcome sets for those keys per test input, including the base key's export in the positive control and signing checks for keys from decryption vectors; range boundaries for RC2, RC4 and PBKDF2 with in-range controls compared across backends in differential runs; two PBKDF2 known gaps.

The conformance suite gains in-range controls next to the RC2, RC4 and PBKDF2 range boundaries and a control just above the PBKDF2 must-support output length, with differential comparison of their outputs. The suite's hand-written-input rule allows such controls, accepting success or Unsupported outside the must-support set. The picky-crypto-conformance runner gains a rustcrypto feature that registers this provider with all. CI runs it, together with a depth-2 cargo-hack clippy powerset over the backend's features, the cargo-deny feature ban, the signature area with legacy_compatibility enabled, and an MSRV check with all.

rsa is the prerelease 0.10.0-rc.18, which the workspace already locks.

Co-authored-by: Copilot App 223556219+Copilot@users.noreply.github.com

Copilot AI 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.

🟡 Changes recommended

Contract inconsistencies, ECDSA nonce handling, PBKDF2 error mapping, and the prohibited intent-file addition remain unresolved.

5 open findings
What changed in this PR

Adds a feature-gated RustCrypto implementation of the picky-crypto provider contract and integrates it with conformance testing and CI.

Changes:

  • Implements 64 cryptographic provider entries across eight feature families.
  • Clarifies contract error handling and malformed RSA-key behavior.
  • Adds conformance, feature-powerset, documentation, and dependency-policy checks.
File Description
.github/​workflows/​ci.yml Adds backend conformance and CI checks.
Cargo.lock Locks backend dependencies.
Cargo.toml Registers the backend workspace crate.
picky/​src/​signature.rs Disambiguates signature encoding.
picky-crypto/​CONTRACT.md Revises error and RSA-key rules.
picky-crypto-conformance/​Cargo.toml Adds the optional backend feature.
picky-crypto-conformance/​tests/​main.rs Registers the RustCrypto provider.
picky-crypto-rustcrypto/​AGENTS.md Adds backend maintenance guidance.
picky-crypto-rustcrypto/​Cargo.toml Defines features and dependencies.
picky-crypto-rustcrypto/​INTENT.md Adds backend intent and invariants.
picky-crypto-rustcrypto/​README.md Documents features and capabilities.
picky-crypto-rustcrypto/​deny.toml Bans weakened Ed25519 compatibility.
picky-crypto-rustcrypto/​src/​aead.rs Implements AES-GCM.
picky-crypto-rustcrypto/​src/​cbc.rs Implements CBC ciphers.
picky-crypto-rustcrypto/​src/​curve25519.rs Implements Ed25519 and X25519.
picky-crypto-rustcrypto/​src/​ec.rs Implements NIST EC operations.
picky-crypto-rustcrypto/​src/​hash.rs Implements digest entries.
picky-crypto-rustcrypto/​src/​key_wrap.rs Implements AES-KW.
picky-crypto-rustcrypto/​src/​lib.rs Assembles feature-selected entries.
picky-crypto-rustcrypto/​src/​mac.rs Implements HMAC.
picky-crypto-rustcrypto/​src/​pbkdf2.rs Implements PBKDF2.
picky-crypto-rustcrypto/​src/​random.rs Implements secure randomness.
picky-crypto-rustcrypto/​src/​rc4.rs Implements RC4.
picky-crypto-rustcrypto/​src/​rsa.rs Implements RSA operations and keys.
picky-crypto-rustcrypto/​src/​util.rs Adds zeroizing buffer and length helpers.

🧠 Review effort: Balanced


💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread picky-crypto-rustcrypto/src/ec.rs Resolved
Comment thread picky-crypto-rustcrypto/src/pbkdf2.rs Resolved
Comment thread picky-crypto-rustcrypto/INTENT.md Resolved
Comment thread picky-crypto-rustcrypto/README.md Resolved
Comment thread picky-crypto/CONTRACT.md Resolved

Copilot AI 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.

🔵 Needs a closer look

The new contract wording misclassifies non-key library validation failures as InvalidKey.

1 open finding
4 resolved since last review

🧠 Review effort: Balanced

Copilot AI 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.

🔵 Needs a closer look

The broad cryptographic and key-handling surface warrants final human security review despite comprehensive conformance coverage.

0 open findings

1 resolved since last review

🧠 Review effort: Balanced

@CBenoit
Benoît Cortier (CBenoit) force-pushed the cbenoit-picky-crypto-rustcrypto branch 7 times, most recently from 6482c34 to 89e0b3f Compare October 13, 2026 08:19
Base automatically changed from cbenoit-picky-crypto-testsuite to master October 13, 2026 08:43
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
ECDSA signing now mixes operating-system randomness into the RFC 6979 nonce, as the contract requires for ECDSA nonces. PBKDF2 reports an output allocation failure as unsupported only above the must-support output length.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The section 4 rule on library validation failures covers key validation only; non-key inputs keep the errors the section 4 table assigns.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Conformance outcome sets for RSA private keys with inconsistent or non-distinct components are exact per test input: a signature verifies under (n, e) or the call returns InvalidKey, and decrypting a ciphertext with valid padding returns the expected plaintext or InvalidKey, never VerificationFailed.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI 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.

🟡 Changes recommended

RSA limit handling misclassifies malformed oversized keys, and RSA generation is not exercised by normal CI.

2 open findings

🧠 Review effort: Balanced

Comment thread picky-crypto-rustcrypto/src/rsa.rs Resolved
Comment thread picky-crypto-rustcrypto/src/rsa.rs Resolved
The conformance suite generates RSA keys only in extended runs, so an inline test now generates a small key through the backend's seeded generation path and loads it.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
An input beyond a backend's size limit may be refused with Unsupported before its structure is examined, whether or not it is also malformed. Rule 4, the section 4 conformance expectations, section 6.9 and the section 12 eligibility rules follow it; a size limit lies above the must-support set and never applies to a length the algorithm or operation fixes.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Public and private RSA key encodings longer than the largest encoding within the rsa crate's limits return Unsupported before any decoder runs, so no oversized input reaches code whose cost and stack use grow with its size. The caps are derived from the library's maximum modulus and exponent plus DER overhead.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI 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.

🔵 Needs a closer look

The broad cryptographic surface and contract changes warrant final human security review despite no identified defects.

0 open findings

🧠 Review effort: Balanced

@CBenoit

Benoît Cortier (CBenoit) commented Oct 14, 2026 •

Copy link
Copy Markdown
Member Author

Review notes, kept out of the squash commit:

  • Faults accepted as covered by review (no wrong output, panic or interoperability difference, and a trivially reviewable check): the PBKDF2 capacity-limit branch returning another error than Unsupported, or being removed, is not exercised, because reaching it needs an output above 2^31 − 1 bytes.
  • UNTESTED: markers in picky-crypto-rustcrypto/src:
    • pbkdf2.rs: output lengths beyond the capacity limit return Unsupported before any allocation.
    • pbkdf2.rs: an allocation failure returns Unsupported only beyond the must-support maximum, and ProviderFailure otherwise.
    • cbc.rs: data beyond the capacity limit returns Unsupported before it is copied.
    • rc4.rs: data beyond the capacity limit returns Unsupported before it is copied.
    • aead.rs: sealing and opening return Unsupported beyond the capacity limit, before any allocation or copy.
    • rsa.rs (sign, decrypt): the private-key operation is blinded with the operating-system generator.
    • ec.rs: the ECDSA RFC 6979 nonce is hedged with operating-system random bytes.
    • rsa.rs (generate_pkcs8): the seed and the generator state are wiped after use; wiping the seed's stack copies is best effort.
    • util.rs (buffer): outputs and intermediate copies are wiped on drop.
    • lib.rs (crate-wide): secret copies stay in Zeroizing or are wiped wherever the adapter holds them.
    • lib.rs (crate-wide): no type holding secret material implements or derives Debug.
  • Not marked, because tests pin them: the RSA public-key encoded-length cap and its precedence over decoding (encoded_length_cap), the private-key modulus check before key construction (private_modulus_above_public_limit_is_unsupported), Ed25519 S < L (the CI run with legacy_compatibility), and the freshness of RSA encryption, key generation, AES-GCM nonces and ephemeral agreement (suite freshness checks).
  • Line counts against master at 80d11e3: backend source +1685 (about 260 of it inline adapter tests), conformance suite +218/−11, manifests and runner +75, documentation +130/−19, CI +29/−2, Cargo.lock +69 (md4 0.11.0, rc4 0.2.0 and this crate).

Note

LLM-assisted content (no human feedback).

Copilot AI 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.

🔵 Needs a closer look

The broad cryptographic and contract-visible surface warrants final human security review despite no specific defect found.

0 open findings

🧠 Review effort: Balanced

RFC 8032 §5.1.7 requires S < L.
ed25519-dalek weakens its own check when any crate in a build enables its `legacy_compatibility` feature, and the cargo-deny ban protects only this workspace.
A CI step runs the signature conformance area with that feature enabled, so the adapter check is exercised on the Wycheproof S + L signatures.
The MSRV matrix now checks the backend with every feature.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@CBenoit
Benoît Cortier (CBenoit) force-pushed the cbenoit-picky-crypto-rustcrypto branch from e634153 to 3edb3e8 Compare October 14, 2026 12:41
`UNTESTED:` comments mark the properties no test checks: capacity-limit branches, PBKDF2's allocation-failure mapping, RSA blinding, ECDSA nonce hedging, RSA key-generation seed wiping and buffer zeroization.
The RSA encoded-length caps state their invariant in `INVARIANT:` doc comments.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The thin-adapter and conformance invariants are already stated by the picky-crypto and conformance suite intents.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…istency

Rule 4 states which outcome wins (a bounds error always, a capacity limit over a structure error) without prescribing an evaluation order.
Section 4 allows skipping validation work that grows with an exceeded parameter.
Sections 7 and 12 describe RSA private keys whose components are not a consistent key for (n, e), which also covers composite factors.
The padding-oracle sentence keeps only the protocol countermeasure, since the decrypt row already states the error.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
DER decoding at the locked versions does not grow the stack with input size, and the loader's modulus check keeps oversized keys away from the library's key construction.
The README separates error masking from the timing advisory, and AGENTS.md also forbids editing the contract to make the backend pass.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…gths

The inconsistent-RSA areas check the base key's public-key export next to its positive-control load.
The differential self-test's PBKDF2 mock differs at both compared control lengths, and each length is asserted separately.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI 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.

🟡 Changes recommended

Decryption-derived inconsistent RSA keys lack the signing checks required by the updated contract.

1 open finding

🧠 Review effort: Balanced

The PKCS#8 decoder skips attributes of any length, so a whole-document cap refused valid maximum-size keys with attributes, which the contract allows only to load or to fail with InvalidKey.
DER parsing does not grow with the input, and the modulus and exponent check after parsing already keeps oversized keys away from the library's key construction.
The exact public-key cap stays.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Base keys published only with decryption vectors, and keys derived from them, now get signing checks on the vectors' published messages.
With no published signature to pin the base key, a signature passes only if the provider's verifier, or else the conformance runner's reference verifier, accepts it, and the check fails if neither exists.
Keys from signature vectors keep their checks.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@CBenoit
Benoît Cortier (CBenoit) requested a balanced review from Copilot October 15, 2026 04:38

Copilot AI 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.

🔵 Needs a closer look

The broad cryptographic implementation and revised RSA contract semantics require final maintainer security review.

0 open findings

1 resolved since last review

🧠 Review effort: Balanced

The `random` feature provides the operating-system secure-random entry on its own, for code that needs only randomness.
The families that draw randomness (`aead`, `rsa`, `nist-ec`, `curve25519`) enable it.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@CBenoit
Benoît Cortier (CBenoit) merged commit 8d4a419 into master Oct 15, 2026
17 checks passed
@CBenoit
Benoît Cortier (CBenoit) deleted the cbenoit-picky-crypto-rustcrypto branch October 15, 2026 07:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants