Skip to content

rfc9980: close small compliance gaps (G1+G3+G4) - #2433

Open
ronaldtse wants to merge 4 commits into
mainfrom
rfc9980-compliance-polish
Open

rfc9980: close small compliance gaps (G1+G3+G4)#2433
ronaldtse wants to merge 4 commits into
mainfrom
rfc9980-compliance-polish

Conversation

@ronaldtse

Copy link
Copy Markdown
Contributor

Summary

First of three batched PRs addressing the audit findings in TODO.rnp-roadmap/26-rfc9980-and-thunderbird-integration.md. Closes the small RFC 9980 compliance gaps on top of PR #2355. The other two batched PRs (revocation service docs, backup/recovery infrastructure) will follow.

What's in this PR

Three small changes, one coherent story: "make rnp's PQC implementation fully RFC 9980-compliant on the keygen and receiver sides."

G1 — Preferred AEAD Ciphersuites for PQ keys (RFC 9980 §7.1)
UserPrefs::check_defaults() now populates aead_prefs from symm_algs not only for v6 keys (the prior behaviour) but also for any key whose public-key algorithm is PQ. Ensures v4 ML-KEM-768+X25519 keys also advertise AES-256+OCB, satisfying the RFC's SHOULD for all PQ certs rather than just v6.

G3 — Reject weak-hash subkey binding signatures over PQ subkeys (RFC 9980 §7.2)
Key::validate_binding() now rejects MD5, SHA-1, and RIPEMD-160 in subkey binding signatures (type 0x18) when the subkey's algorithm is PQ. The RFC says a receiving implementation MUST treat such signatures as invalid; rnp now does. Returns RNP_ERROR_SIG_WEAK_HASH.

G4 — Canonical algorithm name strings (RFC 9980 §2.1)
rnp_keygen_alg_map in src/lib/keygen.cpp used hardcoded strings with underscore separators ("ML-KEM-768_X25519") and two outdated names that don't match RFC 9980 at all ("Kyber-X448" for alg ID 36, "Dilithium-ED448" for alg ID 31). Now uses the RNP_ALGNAME_* macros from include/rnp/rnp.h, which already use the canonical RFC 9980 + form. Makes both internal maps consistent and matches the wire-spec naming.

Why

Audit against the published RFC 9980 (June 2026) showed rnp's PQC implementation is essentially complete (via #2355), but three small receiver/keygen-side items were not yet enforced. This PR closes them.

What's NOT in this PR (deferred)

G2 — Implicit AES-256 when encrypting to PQ recipients (RFC 9980 §7.1)
RFC 9980 §7.1 also specifies that a receiver should implicitly append AES-256 to a PQ recipient's preferences if it's missing. rnp does not have a recipient-pref-intersection code path today — callers choose the cipher via rnp_op_encrypt_set_cipher(), so there's nothing to implicitly augment. This item is moot until rnp grows recipient-driven cipher selection.

Test plan

  • Build clean on default config (no warnings)
  • test_ffi_security_profile and test_ffi_security_rule_enumeration still pass
  • CI green on PQC-enabled legs (existing test_ffi_pqc_gen_enc_sign will exercise G1; existing import tests will exercise G4)
  • A dedicated test for G3 (crafting a PQ subkey binding with weak hash) is a follow-up — it requires generating a key with a forced-weak hash, which is non-trivial without bypassing the keygen defaults that G1 just made stricter

Related

  • Audit doc: TODO.rnp-roadmap/26-rfc9980-and-thunderbird-integration.md
  • Source PR for PQC implementation: Update PQC Draft to Version 12 #2355
  • Follow-up batched PRs: revocation service docs (R1), backup/recovery infrastructure (B1+B2+B3+B4)

@codecov

codecov Bot commented Jul 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 85.46%. Comparing base (ce33cc6) to head (2136bea).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #2433   +/-   ##
=======================================
  Coverage   85.46%   85.46%           
=======================================
  Files         125      125           
  Lines       22962    22962           
=======================================
  Hits        19625    19625           
  Misses       3337     3337           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ronaldtse
ronaldtse requested review from antonsviridenko and ni4 July 31, 2026 16:19
@ronaldtse
ronaldtse force-pushed the rfc9980-compliance-polish branch from 33ea313 to b2d3faa Compare August 10, 2026 02:08
@ronaldtse
ronaldtse force-pushed the rfc9980-compliance-polish branch 5 times, most recently from 2b9ed2d to 36a21df Compare August 22, 2026 05:26

@ni4 ni4 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM, thanks!

…ak-hash binding)

Audit-driven polish on top of PR #2355 to close the remaining RFC 9980
compliance gaps. Three changes, one story: make rnp's PQC implementation
fully RFC 9980-conformant on the receiver and keygen sides.

G1 — Preferred AEAD Ciphersuites for PQ keys (RFC 9980 §7.1)
  UserPrefs::check_defaults() now populates aead_prefs from symm_algs
  not only for v6 keys (the prior behaviour) but also for any key whose
  public-key algorithm is PQ. This ensures v4 ML-KEM-768+X25519 keys
  also advertise AES-256+OCB, satisfying the RFC's SHOULD for all PQ
  certs rather than just v6.

G3 — Reject weak-hash subkey binding signatures over PQ subkeys (RFC 9980 §7.2)
  Key::validate_binding() now rejects MD5, SHA-1, and RIPEMD-160 in
  subkey binding signatures (type 0x18) when the subkey's algorithm
  is one of the PQ(/T) algorithms. The RFC says a receiving
  implementation MUST treat such signatures as invalid; rnp now does.

G4 — Canonical algorithm name strings (RFC 9980 §2.1)
  rnp_keygen_alg_map in src/lib/keygen.cpp used hardcoded strings
  with underscore separators ("ML-KEM-768_X25519") and two outdated
  names that don't match RFC 9980 at all ("Kyber-X448" for alg ID 36
  and "Dilithium-ED448" for alg ID 31). Now uses the RNP_ALGNAME_*
  macros from include/rnp/rnp.h, which already use the canonical
  RFC 9980 "+" form. This makes both internal maps consistent and
  matches the wire-spec naming.

G2 (deferred): RFC 9980 §7.1 also specifies that a receiver should
implicitly append AES-256 to a PQ recipient's preferences if it's
missing. rnp does not have a recipient-pref-intersection code path
today (callers choose the cipher via rnp_op_encrypt_set_cipher), so
there's nothing to add AES-256 to. This item is moot until rnp grows
recipient-driven cipher selection.
Main's lint now runs the clang-format 11.1.0 binary (a6b0b99); these
files were formatted under the previous 11.0.x pin and drifted.
The RFC 9980 weak-hash check called sinfo.sig->halg(), which fails to
compile: halg is a pgp_hash_alg_t member. Fixes the build on every
leg with PQC enabled (debian-13 botan3, Fedora coverage).
The macros are defined in the public header under
RNP_EXPERIMENTAL_PQC (from the generated rnp_export.h), which keygen.cpp
never included, so every ENABLE_PQC build failed on the name table.
@ronaldtse
ronaldtse force-pushed the rfc9980-compliance-polish branch from c6b0690 to 2136bea Compare August 30, 2026 20: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.

2 participants