Skip to content

v6: add S2K Argon2 and AEAD s2k usage - #2296

Merged
ni4 merged 5 commits into
rnpgp:mainfrom
TJ-91:argon2-s2ku-aead
Jul 22, 2026
Merged

ni4 merged 5 commits into
rnpgp:mainfrom
TJ-91:argon2-s2ku-aead

Conversation

@TJ-91

@TJ-91 TJ-91 commented Dec 4, 2024

Copy link
Copy Markdown
Contributor

implements Argon2 for S2K (secret keys, SKESKs), as well as AEAD encryption for secret keys.

Does not add new API functionality, however, that might be desired to give the user fine-grained control.

Comment thread src/librepgp/stream-key.cpp Fixed
@codecov

codecov Bot commented Dec 4, 2024 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 71.87500% with 18 lines in your changes missing coverage. Please review.
✅ Project coverage is 85.39%. Comparing base (3e5f675) to head (3098cd5).
⚠️ Report is 9 commits behind head on main.

Files with missing lines Patch % Lines
src/lib/crypto/s2k.cpp 18.75% 13 Missing ⚠️
src/librepgp/stream-packet.cpp 75.00% 4 Missing ⚠️
src/librepgp/stream-key.cpp 90.90% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2296      +/-   ##
==========================================
- Coverage   85.44%   85.39%   -0.05%     
==========================================
  Files         126      126              
  Lines       22762    22784      +22     
==========================================
+ Hits        19449    19457       +8     
- Misses       3313     3327      +14     

☔ 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.

@TJ-91
TJ-91 force-pushed the argon2-s2ku-aead branch 2 times, most recently from 012e214 to 1b8207f Compare July 18, 2025 09:26
@ronaldtse

Copy link
Copy Markdown
Contributor

Rebased onto latest main (which now includes the merged v6 SKESK PR #2207), resolving conflicts in ffi-enc.cpp (test layout) and src/lib/CMakeLists.txt (kept main's feature sets, added ARGON2 to crypto-refresh requirements). The following fixes are folded into the rebased commit:

  • stream-packet.cpp: added missing break after the PGP_S2KS_ARGON2 case in pgp_sk_sesskey_t::write() — it fell through to the default case and threw.
  • stream-packet.cpp: v6 SKESK S2K length field now uses pgp_s2k_t::specifier_len() (adds Argon2 support there, deduplicates the switch).
  • stream-key.cpp: return RNP_ERROR_BAD_PARAMETERS (was 'return false' == RNP_SUCCESS) when rejecting Argon2 with non-AEAD s2k usage; added missing error returns on failed v6 key protection reads.
  • stream-dump.cpp: removed duplicated Argon2 salt print.
  • s2k.cpp: avoid signed-shift UB for encoded_m == 31.
  • ffi-enc.cpp: test now uses data/RFC9580/A.8.5-v6pkesk-v2seipd (the referenced data/test_v6_valid_data/v6pkesk.asc was removed on main); argon2 tests are guarded with plain ENABLE_CRYPTO_REFRESH.
  • rnp.h doc: matched protection string case to implementation.

Verified against RFC 9580 (sections 3.7.1.4, 3.7.2.1, 5.5.3; A.5/A.12 vectors byte-identical). Locally (Botan 3.10, ENABLE_CRYPTO_REFRESH+ENABLE_PQC): all 296 rnp_tests and all CLI suites pass; non-crypto-refresh build also compiles.

@ronaldtse

Copy link
Copy Markdown
Contributor

Update: fixed the 32-bit failures (debian-13-i386). Argon2 with the RFC 9106 first profile (m=2^21, 2 GiB) cannot be allocated in a 32-bit address space, so v6 key protection/generation failed there (std::length_error from Botan, caught and reported cleanly). Two changes folded in: (1) Key::protect now selects the Argon2 parameters per platform per RFC 9106 section 4 — t=1,p=4,m=2^21 on 64-bit, and the memory-constrained profile t=3,p=4,m=2^16 (64 MiB) on 32-bit; (2) the A.5/A.12 vector tests (which inherently need 2 GiB, as would any implementation incl. GnuPG) are skipped on 32-bit. Verified locally: argon2 tests still pass on 64-bit.

@ronaldtse

Copy link
Copy Markdown
Contributor

Just need one more approval!

@ronaldtse
ronaldtse requested a review from ni4 July 19, 2026 10:53
TJ-91 and others added 5 commits July 22, 2026 15:16
The HKDF input keying material for the secret-key KEK must be exactly
the S2K output (keysize bytes), not the whole PGP_MAX_KEY_SIZE-sized
buffer. With symmetric algorithms other than AES-256 the zero-padded
tail would derive a wrong KEK, breaking interoperability with other
implementations.
The computed nonce was never used (the IV is passed to
pgp_cipher_aead_start() instead, which is equivalent at chunk index 0)
and the conditional had an empty body.
Match the rest of pgp_s2k_t and never leave them indeterminate, e.g.
when pgp_s2k_t is default-initialized.
In non-crypto-refresh builds the specifier parameter of
pgp_s2k_t::salt_size() is unused, triggering -Wunused-parameter.
@ronaldtse

Copy link
Copy Markdown
Contributor

Rebased onto current main (3e5f675b). The original commit is preserved intact; fixes are in separate commits on top:

  • 14a7fdc2 v6: fix HKDF IKM size for AEAD secret-key crypt — the HKDF input keying material must be exactly the S2K output (keysize bytes), not the whole PGP_MAX_KEY_SIZE buffer; with symmetric algorithms other than AES-256 the zero-padded tail would derive a wrong KEK and break interoperability.
  • 4683861e v6: drop dead nonce code in crypt_secret_key_aead — the computed nonce was never used (IV is passed to pgp_cipher_aead_start(), equivalent at chunk index 0) and the conditional had an empty body.
  • e1aa3740 v6: default-initialize Argon2 s2k fields — match the rest of pgp_s2k_t.
  • 3098cd5c s2k: use named salt sizes, silence unused-parameter in non-crypto-refresh builds.
  • bf45dd14 ci: restore oss-fuzz fork workaround for fuzzing job — [rnp] Update Botan to 3.6.0 google/oss-fuzz#15882 is still open and google/oss-fuzz@master still builds Botan 3.4.0, which fails find_package(Botan 3.6.0) with ENABLE_CRYPTO_REFRESH=on; the ronaldtse/oss-fuzz@rnp-botan-3.6 workaround is needed until that upstream PR is merged.

Local verification (macOS arm64, Botan 3.6.0): full ctest suite green with -DENABLE_CRYPTO_REFRESH=ON, including the new RFC 9580 Argon2/AEAD vectors (test_ffi_argon2_locked_seckey, test_ffi_decrypt_argon2_skesk); 282/282 with -DENABLE_CRYPTO_REFRESH=OFF (feature stays inert).

@ronaldtse

Copy link
Copy Markdown
Contributor

Follow-up correction on the fuzzing CI leg: I dropped the workflow commit (bf45dd14) again — testing showed the oss-fuzz fork workaround cannot work. The cifuzz action's Docker build context is only infra/, so a modified projects/rnp/build.sh in the fork never reaches the build container; the rnp build always uses the build.sh baked into gcr.io/oss-fuzz-base/cifuzz-base:latest, which still pins Botan 3.4.0 because google/oss-fuzz#15882 is unmerged. rnp requires Botan >= 3.6.0 when ENABLE_CRYPTO_REFRESH=on, so the fuzzing job fails at configure for every PR (it does not run on main pushes, which is why main looks green). It will pass once google/oss-fuzz#15882 is merged and the cifuzz-base image is republished — no rnp-side change needed (main already points at google/oss-fuzz@master). The branch therefore keeps only the 4 code-fix commits listed above.

@ronaldtse

Copy link
Copy Markdown
Contributor

All jobs passing (except fuzzing)! Pending approval to merge.

@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!

@ni4
ni4 merged commit c57f486 into rnpgp:main Jul 22, 2026
128 of 130 checks passed
@ronaldtse

Copy link
Copy Markdown
Contributor

Merging with 2 approvals (ni4, ronaldtse). Thank you @TJ-91 for the contribution, and @ni4 for the review!

ronaldtse added a commit that referenced this pull request Aug 10, 2026
…follow-ups)

The merged PRs #2355 (PQC draft 12 + v6 salt + Ed448/X448) and #2296
(Argon2 S2K + AEAD secret-key encryption) shipped with happy-path
coverage only — import sample, verify, decrypt with correct password.
This PR adds the missing negative coverage:

- test_ffi_argon2_locked_seckey_wrong_password: wrong password must
  fail cleanly and not leave the key in a partial-unlock state;
  subsequent correct password still works after multiple failures.
- test_ffi_decrypt_argon2_skesk_wrong_password: SKESK decryption
  fails without leaking plaintext when the derived AEAD key is wrong.
- test_ffi_decrypt_pqc_pkesk_corrupted: a single byte flip near the
  end of a valid PQC-encrypted message must cause decryption to fail
  (exercises the AEAD tag verification path on the PQC PKESK).

These close the "happy-path only" codecov gap for the AEAD-tag
verification paths in stream-parse.cpp (PR #2422's earlier fix
extends to PQC ciphertexts too) and for the Argon2 S2K derivation
failure path in stream-key.cpp.

Note: locally the build is blocked by a pre-existing Botan 3.12
API incompatibility in src/lib/crypto/ec.cpp (uses Botan::EC_Group
members that became opaque in 3.12). CI runs against the Botan
version pinned in the centos-and-fedora workflow (3.6 / 3.12 from
source) where this is not an issue.
ronaldtse added a commit that referenced this pull request Aug 20, 2026
…follow-ups)

The merged PRs #2355 (PQC draft 12 + v6 salt + Ed448/X448) and #2296
(Argon2 S2K + AEAD secret-key encryption) shipped with happy-path
coverage only — import sample, verify, decrypt with correct password.
This PR adds the missing negative coverage:

- test_ffi_argon2_locked_seckey_wrong_password: wrong password must
  fail cleanly and not leave the key in a partial-unlock state;
  subsequent correct password still works after multiple failures.
- test_ffi_decrypt_argon2_skesk_wrong_password: SKESK decryption
  fails without leaking plaintext when the derived AEAD key is wrong.
- test_ffi_decrypt_pqc_pkesk_corrupted: a single byte flip near the
  end of a valid PQC-encrypted message must cause decryption to fail
  (exercises the AEAD tag verification path on the PQC PKESK).

These close the "happy-path only" codecov gap for the AEAD-tag
verification paths in stream-parse.cpp (PR #2422's earlier fix
extends to PQC ciphertexts too) and for the Argon2 S2K derivation
failure path in stream-key.cpp.

Note: locally the build is blocked by a pre-existing Botan 3.12
API incompatibility in src/lib/crypto/ec.cpp (uses Botan::EC_Group
members that became opaque in 3.12). CI runs against the Botan
version pinned in the centos-and-fedora workflow (3.6 / 3.12 from
source) where this is not an issue.
ronaldtse added a commit that referenced this pull request Aug 20, 2026
…follow-ups)

The merged PRs #2355 (PQC draft 12 + v6 salt + Ed448/X448) and #2296
(Argon2 S2K + AEAD secret-key encryption) shipped with happy-path
coverage only — import sample, verify, decrypt with correct password.
This PR adds the missing negative coverage:

- test_ffi_argon2_locked_seckey_wrong_password: wrong password must
  fail cleanly and not leave the key in a partial-unlock state;
  subsequent correct password still works after multiple failures.
- test_ffi_decrypt_argon2_skesk_wrong_password: SKESK decryption
  fails without leaking plaintext when the derived AEAD key is wrong.
- test_ffi_decrypt_pqc_pkesk_corrupted: a single byte flip near the
  end of a valid PQC-encrypted message must cause decryption to fail
  (exercises the AEAD tag verification path on the PQC PKESK).

These close the "happy-path only" codecov gap for the AEAD-tag
verification paths in stream-parse.cpp (PR #2422's earlier fix
extends to PQC ciphertexts too) and for the Argon2 S2K derivation
failure path in stream-key.cpp.

Note: locally the build is blocked by a pre-existing Botan 3.12
API incompatibility in src/lib/crypto/ec.cpp (uses Botan::EC_Group
members that became opaque in 3.12). CI runs against the Botan
version pinned in the centos-and-fedora workflow (3.6 / 3.12 from
source) where this is not an issue.
ronaldtse added a commit that referenced this pull request Aug 23, 2026
…follow-ups)

The merged PRs #2355 (PQC draft 12 + v6 salt + Ed448/X448) and #2296
(Argon2 S2K + AEAD secret-key encryption) shipped with happy-path
coverage only — import sample, verify, decrypt with correct password.
This PR adds the missing negative coverage:

- test_ffi_argon2_locked_seckey_wrong_password: wrong password must
  fail cleanly and not leave the key in a partial-unlock state;
  subsequent correct password still works after multiple failures.
- test_ffi_decrypt_argon2_skesk_wrong_password: SKESK decryption
  fails without leaking plaintext when the derived AEAD key is wrong.
- test_ffi_decrypt_pqc_pkesk_corrupted: a single byte flip near the
  end of a valid PQC-encrypted message must cause decryption to fail
  (exercises the AEAD tag verification path on the PQC PKESK).

These close the "happy-path only" codecov gap for the AEAD-tag
verification paths in stream-parse.cpp (PR #2422's earlier fix
extends to PQC ciphertexts too) and for the Argon2 S2K derivation
failure path in stream-key.cpp.

Note: locally the build is blocked by a pre-existing Botan 3.12
API incompatibility in src/lib/crypto/ec.cpp (uses Botan::EC_Group
members that became opaque in 3.12). CI runs against the Botan
version pinned in the centos-and-fedora workflow (3.6 / 3.12 from
source) where this is not an issue.
ronaldtse added a commit that referenced this pull request Aug 29, 2026
…follow-ups)

The merged PRs #2355 (PQC draft 12 + v6 salt + Ed448/X448) and #2296
(Argon2 S2K + AEAD secret-key encryption) shipped with happy-path
coverage only — import sample, verify, decrypt with correct password.
This PR adds the missing negative coverage:

- test_ffi_argon2_locked_seckey_wrong_password: wrong password must
  fail cleanly and not leave the key in a partial-unlock state;
  subsequent correct password still works after multiple failures.
- test_ffi_decrypt_argon2_skesk_wrong_password: SKESK decryption
  fails without leaking plaintext when the derived AEAD key is wrong.
- test_ffi_decrypt_pqc_pkesk_corrupted: a single byte flip near the
  end of a valid PQC-encrypted message must cause decryption to fail
  (exercises the AEAD tag verification path on the PQC PKESK).

These close the "happy-path only" codecov gap for the AEAD-tag
verification paths in stream-parse.cpp (PR #2422's earlier fix
extends to PQC ciphertexts too) and for the Argon2 S2K derivation
failure path in stream-key.cpp.

Note: locally the build is blocked by a pre-existing Botan 3.12
API incompatibility in src/lib/crypto/ec.cpp (uses Botan::EC_Group
members that became opaque in 3.12). CI runs against the Botan
version pinned in the centos-and-fedora workflow (3.6 / 3.12 from
source) where this is not an issue.
ronaldtse added a commit that referenced this pull request Aug 30, 2026
…follow-ups)

The merged PRs #2355 (PQC draft 12 + v6 salt + Ed448/X448) and #2296
(Argon2 S2K + AEAD secret-key encryption) shipped with happy-path
coverage only — import sample, verify, decrypt with correct password.
This PR adds the missing negative coverage:

- test_ffi_argon2_locked_seckey_wrong_password: wrong password must
  fail cleanly and not leave the key in a partial-unlock state;
  subsequent correct password still works after multiple failures.
- test_ffi_decrypt_argon2_skesk_wrong_password: SKESK decryption
  fails without leaking plaintext when the derived AEAD key is wrong.
- test_ffi_decrypt_pqc_pkesk_corrupted: a single byte flip near the
  end of a valid PQC-encrypted message must cause decryption to fail
  (exercises the AEAD tag verification path on the PQC PKESK).

These close the "happy-path only" codecov gap for the AEAD-tag
verification paths in stream-parse.cpp (PR #2422's earlier fix
extends to PQC ciphertexts too) and for the Argon2 S2K derivation
failure path in stream-key.cpp.

Note: locally the build is blocked by a pre-existing Botan 3.12
API incompatibility in src/lib/crypto/ec.cpp (uses Botan::EC_Group
members that became opaque in 3.12). CI runs against the Botan
version pinned in the centos-and-fedora workflow (3.6 / 3.12 from
source) where this is not an issue.
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.

4 participants