Skip to content

Add an automatically generated OpenSSL backend for the Crypto Refresh and PQC code#2392

Open
kaie wants to merge 45 commits into
rnpgp:mainfrom
kaie:pqc-openssl
Open

Add an automatically generated OpenSSL backend for the Crypto Refresh and PQC code#2392
kaie wants to merge 45 commits into
rnpgp:mainfrom
kaie:pqc-openssl

Conversation

@kaie
Copy link
Copy Markdown
Contributor

@kaie kaie commented May 28, 2026

These changes are based on top of PR #2355, which provides an implementation that uses the Botan backend, but does not provide a corresponding implementation for using the OpenSSL backend.

The patches that I'm providing here in addition have been machine generated by Claude Code.

The use of automatic generation was chosen by me, because I made the assumption that the human creative work that decided how to use crypto primitives to implement the OpenPGP functionality was already done and is contained in the base pull request, and that the implementation of the OpenSSL backend code could inspect the code that uses Botan, and produce equivalent code that uses OpenSSL.

The first patch removes some botan specific code from code that is shared across backends and replaces it with a new helper class for secure wiping of bytes. I reviewed that code and it looks correct to me.

The code builds and passes the existing RNP tests on Linux. I haven't built nor ran tests on other platforms.

A script is provided that tests the interoperability of data across the two backends.

The additional patch that addresses code review issues weren't based on a human review, but based on a re-review by the AI itself.

I'm hereby providing the code as is, but I want to make it clear that I couldn't have written the code to use OpenSSL myself in the time I have available, as I don't have experience with writing code for OpenSSL.

The code is provided in the hope it is useful, in the hope that the PQC functionality can be made available for all users of RNP regardless of which backend cryptography library is preferred, and I suggest that the code should get reviewed for correctness before considering to adopt it.

Regarding copyright headers, for all files that were newly written, Ribose was added as the only copyright holder.
However, those files were automatically created by using the equivalent code files as a template. So they are derivative work
For all files that were originally contributed by MTG, but which were modified by this work, Ribose was added as an additional copyright holder.
I'd like to leave it to Ribose to decide what the correct copyright section should be and fix those sections, if necessary.

TJ-91 added 30 commits May 19, 2026 15:57
... for rnp_generate_key_ex
add roundtrip test for PQC certs
clang-format
require Botan 3.6.0 for PQC
switch to final NIST PQC standards
update KMAC Key Combiner
fail gracefully on parsing v6 cleartext sigs
TJ-91 and others added 14 commits May 19, 2026 15:57
Only compiling PQC support now gives the option to use v4 MLKEM768+X25519 encryption subkeys.
Added a v4 ECC + PQC certificate to TUI
…ackend)

Each PQC class (ML-KEM, ML-DSA, SLH-DSA, exdsa/ecdh-kem) previously exposed
Botan types (Botan::secure_vector, botan_key() methods) in its header, which
prevented those headers from compiling without Botan installed.

- Add rnp::SecureBytes (secure_bytes.h/cpp): a backend-agnostic drop-in for
  Botan::secure_vector<uint8_t> that zeroes memory on destruction.
- In each header: replace Botan includes with "crypto/secure_bytes.h";
  replace Botan::secure_vector<uint8_t> private key members with rnp::SecureBytes;
  replace Botan::unlock(key_) with key_.unlock();
  remove botan_key() / botan_key_*() private method declarations.
- In each .cpp: move the Botan includes here; convert the removed class methods
  to file-scope static functions (e.g. kyber_privkey_from_bytes, x25519_privkey_from_bytes).

No behaviour change; Botan build compiles and passes all tests unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
std::make_unique is a C++14 feature; the project builds with -std=c++11.
Replace all std::make_unique<T>(...) with std::unique_ptr<T>(new T(...)).
(The same issue exists in kyber_ecdh_composite.cpp and is fixed there as
part of the OpenSSL AES key-wrap changes in the next commit.)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… SLH-DSA

Implements ENABLE_PQC and ENABLE_CRYPTO_REFRESH for the OpenSSL backend
(previously guarded by openssl_nope). Requires OpenSSL 3.5+; configure-time
algorithm checks disable the feature if any PQC algorithm is absent.

New files:
- hkdf_ossl.hpp/cpp: HKDF via EVP_KDF "HKDF"
- ed25519_ed448_ossl.cpp: Ed25519/Ed448 keygen, sign, verify, validate
- x25519_x448_ossl.cpp: X25519/X448 KEM encap/decap for ECDH-KEM
- exdsa_ecdhkem_ossl.cpp: ECDH-KEM and ECDSA-exdsa for NIST/brainpool curves
  (fixes two pre-existing bugs in the OpenSSL EC backend: EVP_PKEY_KEYPAIR ->
  EVP_PKEY_PRIVATE_KEY in load_nist_privkey, EVP_PKEY_check ->
  EVP_PKEY_private_check in is_valid for private-key-only objects)
- kyber_ossl.cpp: ML-KEM encap/decap using OSSL_PKEY_PARAM_ML_KEM_SEED
- dilithium_ossl.cpp: ML-DSA sign/verify using OSSL_PKEY_PARAM_ML_DSA_SEED
- sphincsplus_ossl.cpp: SLH-DSA sign/verify via EVP_PKEY_new_raw_private_key_ex

Modified:
- CMakeLists.txt: resolve_feature_state for ENABLE_CRYPTO_REFRESH and ENABLE_PQC;
  add new source files; remove openssl_nope guards
- hkdf.cpp: route to Hkdf_OpenSSL::create() instead of #error
- ec_ossl.cpp: add ECDH encap/decap helpers needed by X25519/X448 KEM
- kyber_ecdh_composite.cpp: AES-256 key wrap/unwrap via EVP_aes_256_wrap() for
  OpenSSL; also fix std::make_unique -> std::unique_ptr<>(new) for C++11
- ffi-enc.cpp: mark non-AES+AEAD and non-AES+PKESKv6 combos as expected-fail
  under CRYPTO_BACKEND_OPENSSL (OpenSSL only supports AES-OCB/EAX)
- cli_tests.py: fall back from EAX to OCB when RNP_AEAD_EAX is unavailable

Key format compatibility with Botan:
- ML-KEM: 64-byte seed (OSSL_PKEY_PARAM_ML_KEM_SEED) — matches Botan
- ML-DSA: 32-byte seed (OSSL_PKEY_PARAM_ML_DSA_SEED) — matches Botan
- SLH-DSA: 4n raw bytes via get_raw_private_key — matches Botan

All 301 OpenSSL-backend tests pass. Cross-backend round-trip tests pass for
all 7 PQC composite key types (algos 25-28, 31-33) in both directions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
src/tests/pqc-crossbackend-test.sh exercises keyring interoperability
between the Botan and OpenSSL backends for all seven PQC composite key
types (ML-DSA+Ed25519, ML-DSA+Ed448, ML-DSA+ECDSA-P384/P521, and the
three SLH-DSA variants).  Each algorithm is tested in both directions:
Botan generates / OpenSSL decrypts+verifies, and vice versa.

Requires build/ (Botan, ENABLE_CRYPTO_REFRESH=ON) and build-ossl/
(OpenSSL 3.5+, same flags) to be present; see the file header for the
cmake invocations.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- kyber_ossl.cpp, dilithium_ossl.cpp: zero seed_buf[] with secure_wipe()
  before returning from keygen; Botan used secure_vector which zeroes
  automatically, leaving the stack buffer un-wiped was a deviation
- sphincsplus_ossl.cpp: zero the temporary priv std::vector with
  secure_wipe() after copying into SecureBytes; same reasoning
- exdsa_ecdhkem_ossl.cpp (verify): replace raw ECDSA_SIG * with
  rnp::ossl::ECDSASig RAII wrapper (already used in the sign path);
  also check the return value of the encoding i2d_ECDSA_SIG call
- kyber_ecdh_composite.cpp: replace raw EVP_CIPHER_CTX * with
  rnp::ossl::evp::CipherCtx in both AES-256 key wrap and unwrap paths;
  add ossl_utils.hpp include for the OpenSSL build

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
secure_bytes.cpp was added to the OpenSSL backend's CRYPTO_SOURCES list
but omitted from the equivalent Botan section, causing a link error for
rnp::secure_wipe when building the Botan backend with ENABLE_PQC or
ENABLE_CRYPTO_REFRESH enabled.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@codecov
Copy link
Copy Markdown

codecov Bot commented May 28, 2026

Codecov Report

❌ Patch coverage is 94.00000% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 85.46%. Comparing base (6f8a677) to head (34b0b7e).

Files with missing lines Patch % Lines
src/lib/crypto/signatures.cpp 0.00% 1 Missing ⚠️
src/librepgp/stream-packet.cpp 93.33% 1 Missing ⚠️
src/librepgp/stream-sig.cpp 83.33% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #2392   +/-   ##
=======================================
  Coverage   85.46%   85.46%           
=======================================
  Files         126      126           
  Lines       22711    22732   +21     
=======================================
+ Hits        19409    19428   +19     
- Misses       3302     3304    +2     

☔ View full report in Codecov by Sentry.
📢 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.

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