Skip to content

Add a new PQC Tang pin - #583

Open
sarroutbi wants to merge 48 commits into
latchset:masterfrom
sarroutbi:tang-kem-pub
Open

sarroutbi wants to merge 48 commits into
latchset:masterfrom
sarroutbi:tang-kem-pub

Conversation

@sarroutbi

@sarroutbi sarroutbi commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

This PR adds a new tang-pqc pin implementing hybrid ECC+ML-KEM post-quantum encryption for Tang-based NBDE (Network-Bound Disk Encryption). This protects LUKS bindings against future quantum attacks while retaining classical ECMR security guarantees through a dual key exchange design.

  • clevis-encrypt-tang-pqc / clevis-decrypt-tang-pqc: Standalone encrypt/decrypt commands implementing the hybrid protocol
  • Core modules: jose_wrapper (jose C API bindings), tang_client (HTTP client for tang endpoints), key_deriver (HKDF-SHA256 key combination), ecmr_ops (EC key blinding/unblinding)
  • Protocol flow:
    • Encrypt: Performs classical ECMR key exchange via /rec/{kid}, then ML-KEM encapsulation against the server's KEM public key from /adv-kem. Both shared secrets are combined via HKDF-SHA256 (NBDE-HYBRID-v1 label) to derive the final encryption key
    • Decrypt: Recovers the classical ECMR secret via /rec/{kid}, then uses a bidirectional KEM-secured transport channel (/rec-kem/{kem_kid}) to recover the KEM shared secret. Both are recombined to reconstruct the encryption key
  • Secure transport layer: The /rec-kem recovery channel uses ML-KEM encapsulation in both directions (client-to-server and server-to-client) to protect the KEM key material in transit
  • Offline binding: Supports providing both adv and adv_kem as file paths or inline JSON for air-gapped provisioning

KEM Algorithm Selection (alg config property)

The tang-pqc pin now supports an optional alg configuration property that allows selecting which ML-KEM parameter set to use for hybrid encryption. If omitted, the default remains ML-KEM-768
(NIST security level 3), preserving full backward compatibility.

Supported values:

  • ML-KEM-512
  • ML-KEM-768
  • ML-KEM-1024

LUKS infrastructure integration

  • clevis-luks-common-functions: Adds tang-pqc to SSS pin listing, config printing, and regen support
  • clevis-luks-report: Handles tang-pqc pin for key rotation detection; supports tang_pub mode (no stored adv) by checking the kid thumbprint against the server's current advertisement
  • clevis-luks-bind: Finds tang-pqc pin executables in the correct path
  • Dracut module (clevis-pin-tang-pqc): Includes tang-pqc binaries and shared libraries in the initramfs for early-boot unlocking
  • Initramfs-tools hook: Adds tang-pqc binaries to initramfs on Debian-based systems

Build system

  • New meson option tang-pqc (default: auto) to control pin compilation
  • Dependencies: jose (with ML-KEM KEM API), libcrypto (OpenSSL 3.5+), libcurl, jansson
  • Compile-time cc.links() check for jose_jwk_kem_enc to gate build on jose ML-KEM API availability
  • C++ unit tests for jose_wrapper, key_deriver, and ecmr_ops modules

Test suite

  • C++ unit tests: test-tang-pqc-jose-wrapper, test-tang-pqc-key-deriver, test-tang-pqc-ecmr-ops
  • Integration tests: pin-tang-pqc (basic round-trip), pin-tang-pqc-version (/version and /adv-kem endpoint validation), pin-tang-pqc-offline (offline binding with file/inline adv), pin-tang-pqc-sss (SSS threshold policies with tang-pqc), pin-tang-pqc-errors (error handling)
  • Existing test fix: bind-luks1-avoid-luksmeta-corruption adapted to force adv storage (bypasses tang_pub optimization)

Documentation

  • README.md updated with tang-pqc pin usage, dependencies table, and tang server requirements

@sarroutbi
sarroutbi force-pushed the tang-kem-pub branch 6 times, most recently from 0f3053c to 1e5c5fa Compare October 9, 2026 13:53
@sarroutbi
sarroutbi force-pushed the tang-kem-pub branch 4 times, most recently from f74069b to 109404d Compare October 19, 2026 11:05
@sarroutbi sarroutbi changed the title [DRAFT][DONTMERGE] Add a new PQC Tang pin Add a new PQC Tang pin Oct 19, 2026
@sarroutbi
sarroutbi marked this pull request as ready for review October 19, 2026 15:16
sarroutbi and others added 15 commits October 29, 2026 11:00
Introduce hybrid PQC support to the Tang pin. When a Tang server
advertises an ML-KEM-768 key (kty: AKP) and reports hybrid_recovery
support via /version, clevis-encrypt-tang performs dual key agreement:
ECDH with the ECMR key and KEM encapsulation with the ML-KEM key.
Both shared secrets are combined through HKDF-SHA-256 (RFC 5869)
with domain separation (label NBDE-HYBRID-v1, version, suite, and
both key thumbprints bound into the info parameter) to derive the
content encryption key.

All hybrid cryptographic operations (ECDH, KEM encapsulation, HKDF
derivation) are performed inline using jose CLI and openssl kdf
commands. On the decrypt side, ECMR recovery via /rec is shared
with the classical path, followed by KEM recovery via /rec-kem
and HKDF derivation to reconstruct the content encryption key.

The JWE uses alg:dir with A256GCM and stores the client EC public key
as epk, the KEM ciphertext as clevis.tang.kem_ct, and the KEM key
thumbprint as kem_kid in the protected header. Both encrypt and
decrypt paths gracefully fall back to classical ECDH-ES when the
server lacks PQC support.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
Protect enc_kem_key during hybrid recovery by implementing the
bidirectional ML-KEM envelope protocol specified for the unsecure
channel, replacing the plaintext KEM shared secret exchange.

Encrypt side (clevis-encrypt-tang):
- Compute ek_digest = SHA_256(enc_kem_key) at bind time for anti-oracle
  verification on the Tang side
- Store tang_kem_pub and ek_digest in the JWE header
- Rename hybrid_cek to enc_key to match specification terminology

Decrypt side (clevis-decrypt-tang):
- Separate recovery into /rec (ECMR, inherently blinding-protected) and
  /rec-kem (KEM, bidirectional envelope-protected) as two independent
  calls
- Generate ephemeral KEM keypair (clevis_kem_priv, clevis_kem_pub)
- Establish forward transport channel via Encapsulate(tang_kem_pub)
- Encrypt {kem_ct, clevis_kem_pub, ek_digest} under forward key
- POST encrypted envelope to /rec-kem
- Decapsulate return channel and decrypt enc_kem_key from Tang response
- Derive enc_key = PRF(enc_ec_key, enc_kem_key) via HKDF
- Discard all ephemeral and transport material after use
- Extract helpers: derive_enc_key(), ecmr_unblind(), validate_tang_ec_pub(),
  ecmr_recover()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
Rename variables and JSON field names to match the secure transport
protocol specification consistently:

- kem_ct → clevis_kem_ct (JWE header and inner payload field)
- kem_ss_k/kem_ss_jwk → enc_key_k/enc_key_jwk (shared secret from KEM)
- clevis_kem_key → clevis_kem_priv (ephemeral KEM private key)
- kem_k → enc_key_k (recovered KEM shared secret)
- enc_kem_key_json → enc_key_json (decrypted key from Tang)
- encrypted_blob → clevis_encrypted_blob (request field)
- transport_ct → clevis_transport_ct (request field)
- encrypted_key → tang_encrypted_key (response field)
- transport_ct → tang_transport_ct (response field)

Fix ek_digest computation to use JWK Thumbprint (jose jwk thp -a S256)
matching Tang's jose_jwk_thp_buf(), instead of raw SHA-256 of key bytes.

Fix ek_digest being unset before it was stored in the JWE header.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
Add a new C++ pin (tang-pqc) implementing hybrid post-quantum
cryptography combining classical ECMR with ML-KEM-768 (FIPS 203).

The pin provides clevis-encrypt-tang-pqc and clevis-decrypt-tang-pqc
binaries with the following architecture:

- jose_types.h: RAII wrappers (JsonPtr, CStringPtr, BufferPtr) for
  jose/jansson resources using C++ smart pointers
- jose_wrapper: Type-safe C++ interface over jose C API (key
  generation, ECDH, KEM encapsulation, JWE, JWS, base64url)
- key_deriver: HKDF-SHA256 key derivation with domain separation
  (label "NBDE-HYBRID-v1", suite "ECMR+ML-KEM-768")
- tang_client: HTTP client using libcurl with TLS/mTLS support,
  handles /adv, /adv-kem, /rec, /rec-kem endpoints
- ecmr_ops: ECMR blinding and unblinding operations using jose's
  three ECMR modes (scalar mult, point addition, point subtraction)
- encrypt_main: Full hybrid encryption flow producing JWE with
  alg:dir and pin:tang-pqc, with JWS verification of both /adv
  and /adv-kem responses
- decrypt_main: Full hybrid decryption with secure KEM transport
  channel for recovery

Unit tests cover all crypto components without requiring a Tang
server: jose_wrapper (key gen, ECDH, KEM round-trip, JWE, base64,
thumbprints), key_deriver (determinism, domain separation, real-key
integration), and ecmr_ops (blind/unblind recovery matches ECDH
across P-256/P-384/P-521, ephemeral independence).

The pin is gated behind a meson feature option (tang-pqc, default
auto) so it is only built when dependencies (libcrypto, libcurl,
jose, jansson) are available.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
Move all hybrid ECC+KEM post-quantum encryption logic exclusively to
the tang-pqc pin, keeping the classical tang scripts focused on
standard ECDH-ES. The tang_pub feature support is retained, allowing
the server to provide its exchange key in the /rec response instead
of storing the advertisement in the JWE header.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
Read interactive trust confirmation from /dev/tty instead of stdin,
matching the classical tang pin behavior. When clevis-luks-bind pipes
the LUKS key through stdin, the prompt was consuming key data instead
of reading user input from the terminal.

Also add tang-pqc pin documentation to README.md covering usage,
build dependencies (jose with KEM, OpenSSL 3.5+), meson options,
and Tang server requirements.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
Add dracut module clevis-pin-tang-pqc to include the hybrid PQC
binaries in the initramfs for early boot LUKS unlocking. The module
depends on clevis and network, sets rd.neednet=1 when tang-pqc
bindings are detected, and installs clevis-encrypt-tang-pqc,
clevis-decrypt-tang-pqc, curl and openssl.

Also add conditional copy of tang-pqc binaries to the initramfs-tools
hook for Debian-based systems.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
Replace extracted functions (ecmr_unblind, validate_tang_ec_pub,
ecmr_recover) with inline code matching the original tang_pub patch.
No functional change — same tang_pub support, simpler structure.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
In the end, it has been decided to move
all the PQC Hybrid logic to a brand new
pin that will act as the main entrypoint
for Hybrid PQC encryption/decryption.
This change removes previous leftover when
the Hybrid PQC was implemented inside clevis-encrypt-tang
and clevis-decrypt-tang

Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
Add tang-pqc case to clevis_luks_process_sss_pin() so that
clevis luks list and clevis luks regen correctly handle SSS
bindings that include tang-pqc as a child pin.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
When the tang server supports tang_pub, clevis-encrypt-tang skips
storing adv in the JWE header. This broke clevis-luks-report (which
expected adv to always be present) and the luksmeta corruption test
(which relied on a large adv causing bind failure).

- clevis-luks-report: return success when adv is absent from metadata
  instead of failing, since rotation detection is not possible without
  stored keys but decryption still works via tang_pub
- clevis-luks-report: add tang-pqc to the pin case statement
- bind-luks1-avoid-luksmeta-corruption: provide adv as a file to force
  it into the JWE header, preserving the original test intent

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
Allow offline binding by providing both adv and adv_kem together in the
configuration. When both are present, the encrypt skips fetching from
the network and assumes tang_pub is enabled. Providing only one of
adv/adv_kem without the other is rejected with a clear error.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
Add shell-based integration tests for the tang-pqc pin, following the
same patterns as the existing tang tests:

- pin-tang-pqc: basic encrypt/decrypt round-trip, JWE header validation,
  server-down failure
- pin-tang-pqc-version: /version endpoint validation (tang_pub,
  hybrid_recovery), /adv-kem endpoint, AKP key presence
- pin-tang-pqc-offline: offline binding with file paths and inline JSON,
  validates that providing adv-only or adv_kem-only fails
- pin-tang-pqc-sss: SSS combination with two tang-pqc pins, mixed
  tang-pqc + classic tang, one-server-down resilience
- pin-tang-pqc-errors: missing url, unreachable server, wrong
  thumbprint, tampered JWE

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
sarroutbi and others added 28 commits October 29, 2026 11:03
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
Address six security findings from static review of the tang-pqc pin:

- Add cleanseJsonSecrets() to zero sensitive strings inside jansson
  objects (private keys, shared secrets, derived CEKs) before they
  are freed by json_decref, preventing heap residue recovery via
  cold-boot or memory forensics attacks.

- Replace string concatenation with json_pack() for inner payload
  construction in secureTransportRecover(), eliminating a JSON
  injection vector through unverified JWE header values parsed
  before AEAD authentication.

- Add CURLOPT_CONNECTTIMEOUT (10s) and CURLOPT_TIMEOUT (30s) to
  prevent indefinite hangs during early-boot NBDE unlock when the
  Tang server is unreachable or tarpitting.

- RAII-wrap curl_slist via SlistPtr to prevent memory leak on
  exception paths in httpPost(), and add try/catch in writeCallback
  to avoid undefined behavior from C++ exceptions propagating
  through libcurl C frames.

- Length-prefix kid and kemKid in HKDF info parameter to eliminate
  ambiguous concatenation that could allow cross-binding key
  collisions per NIST SP 800-56C. Wire-format change, safe pre-GA.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
Add input validation to prevent three classes of attacks from
crafted JWE headers stored in LUKS metadata:

- JSON injection via EC curve parameter: replace string
  concatenation with json_pack() for key spec construction,
  preventing an attacker from injecting fields like "d" into
  the ephemeral key specification.

- EC curve downgrade: whitelist crv to P-256, P-384, P-521,
  rejecting weak or unexpected curves.

- URL path injection: validate kid, kem_kid, and thp contain
  only base64url characters before interpolating into HTTP
  request paths, preventing path traversal and query injection.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
curl is now restricted to HTTP and HTTPS only,
blocking file://, gopher://, dict://, and other
protocol-smuggling SSRF vectors. The fix uses
CURL_AT_LEAST_VERSION to pick the modern API (7.85.0+)
or the deprecated fallback for older distros

Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
Replace manual OPENSSL_cleanse/cleanseJsonSecrets calls with
RAII types that cleanse automatically on destruction, ensuring
sensitive data is zeroed even when exceptions cause early exit.

Introduce CleansedString, CleansedVector and CleansedJsonPtr
in jose_types.h — each wraps its std counterpart and calls
OPENSSL_cleanse in the destructor. This follows the same RAII
pattern already used for JsonPtr, CurlPtr, and SlistPtr.

Apply the new types to all sensitive locals in
secureTransportRecover, hybridEncrypt, deriveEncKey,
and both main functions.

Also replace the last string-concatenated JSON construction
in deriveEncKey with json_pack for consistency.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
Set CURLOPT_MAXFILESIZE_LARGE to 1 MiB in the tang-pqc curl
handle. Without this, a compromised or malicious Tang server
can send an arbitrarily large response, causing unbounded
memory allocation. In the dracut/initramfs boot context, this
is a denial-of-service that prevents LUKS unlock.

Tang responses (advertisements, recovery replies) are well
under 100 KiB even with ML-KEM key material, so 1 MiB
provides ample headroom.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
Replace unbounded istream_iterator stdin reads in encrypt and decrypt
with chunk-based reading (4 KiB chunks, 1 MiB max). An attacker
controlling stdin could exhaust memory by piping arbitrarily large
input to clevis-encrypt-tang-pqc or clevis-decrypt-tang-pqc.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
CURLOPT_MAXFILESIZE_LARGE only checks the Content-Length header. A
malicious Tang server using Transfer-Encoding: chunked bypasses that
limit entirely, allowing unbounded memory growth. Add an application-
layer size check inside writeCallback to enforce the 1 MiB cap
regardless of transfer encoding.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
Implicit copies of CleansedString/CleansedVector produce plain
std::string/std::vector objects whose destructors do not cleanse
memory, leaving secrets on the heap. Delete copy constructor and
copy assignment, keeping only move semantics so any accidental
copy is caught at compile time.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
Replace unbounded istreambuf_iterator file reads for adv and adv_kem
files with a bounded readBoundedFile helper (4 KiB chunks, 1 MiB max).
A symlink to a large file or device node in the clevis config could
otherwise exhaust memory during clevis-encrypt-tang-pqc.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
Use std::array instead of C-style char[] stack buffers in read
functions, json_string_length() instead of strlen() on jansson
strings, std::string_view for JWE compact serialization, and
constexpr char_traits::length() for compile-time string constants.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
Guard decrypted plaintext with RAII in jweDecrypt so sensitive
data is always cleansed and freed, even on allocation failure.
Prevent integer overflow in curl write callback and enforce the
response size limit before appending data into memory.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
Check return values from json_object_set_new via a throwing
helper to prevent silently malformed JWE on allocation failure.
Validate curl_slist_append result before using the header list.
Reject compact JWE input with more than 5 segments per RFC 7516.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
When binding offline (providing adv and adv_kem files
instead of fetching from the server), the old code set hasTangPub = true.
This caused the encrypt side to omit the advertisement from the JWE header,
under the assumption that the Tang server would provide its public exchange
key dynamically in the /rec/$kid response via a tang_pub field.
In offline mode, we never contact the server, so we never call /version
to check features.tang_pub. We have no idea whether the destination server
supports that feature. Standard Tang servers don't include tang_pub in
recovery   responses — only newer servers that explicitly advertise it do.
What happens at decrypt time (boot):
1. The decrypt side looks for tang_pub in the server's /rec response
— not present on standard servers
2. It falls back to serverPubHint, which comes from the adv field stored
in the JWE header
3. But adv was omitted during encrypt → serverPubHint is null
4. Both paths fail → "Unable to determine server exchange key" →
permanent LUKS unlock failure at boot
Without contacting the server, we must be conservative and assume tang_pub
is NOT supported. This ensures the advertisement is always embedded in the
JWE header as a fallback. If the server does happen to support tang_pub, it
still works — the decrypt side checks tang_pub first and only falls back
to the embedded advertisement if it's absent. So storing it is harmless
extra data in the JWE, but omitting it is catastrophic.
In online mode, the code still queries /version and sets
hasTangPub = version.tangPub, so servers that support it can still benefit
from the optimization of not embedding the advertisement.

Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
Always embed the advertisement in the JWE header when binding
offline, since we cannot verify whether the destination server
supports tang_pub. Without it, decrypt fails permanently at boot
on servers that do not return tang_pub in recovery responses.

Reject empty strings in validateBase64Url to prevent an
untrusted JWE with empty kid/kem_kid from producing malformed
HTTP paths (e.g. POST /rec/) that could bypass server-side
URL routing.

Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
The tang_pub fallback logic in clevis-luks-report (handling
missing adv, kid-based rotation check) and the test adaptation
in bind-luks1-avoid-luksmeta-corruption belong with the tang_pub
feature in PR latchset#576, not in the tang-pqc branch.

Restore both files to their master behavior, keeping only the
tang-pqc case addition in clevis-luks-report which is needed
for the new pin.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
Introduce an optional 'alg' configuration property that allows
selecting the KEM algorithm (ML-KEM-512, ML-KEM-768, ML-KEM-1024)
instead of hardcoding ML-KEM-768. The algorithm flows through
encrypt (config -> JWE header -> Tang query param), decrypt
(JWE header -> key generation + derivation), and key derivation
(dynamic HKDF suite string "ECMR+<alg>").

Validate the algorithm from untrusted JWE headers against an
allowlist and use json_pack for key spec construction to prevent
JSON injection.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
Show the alg property alongside the URL when listing tang-pqc
bindings, so users can see which ML-KEM variant was configured.
Bindings without alg (pre-existing) display only the URL.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Document the tang-pqc pin configuration properties, algorithm
selection, offline binding, and the hybrid cryptographic scheme.
Wire the adoc source into the meson build so it is generated and
installed alongside the other pin man pages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
The alg property from the user's JSON config was accepted without
validation. An invalid value would be appended raw to the URL
query string (/adv-kem?alg=...) and stored in the JWE header.

Validate alg against the ML-KEM allowlist in parseConfig, matching
the validation already present on the decrypt side.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
When the JWE has no stored advertisement (tang_pub mode) and the
/rec response does not include tang_pub, fetch /adv live and look
up the server exchange key by kid thumbprint. This prevents
permanent decryption failure caused by a forged /version at bind
time, a mixed-version server pool, or a proxy stripping tang_pub.

The kid in the protected header is integrity-protected by AES-GCM,
so an attacker cannot redirect the lookup to a different key. Worst
case is one extra HTTP request in the downgrade scenario.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
The rebase onto master dropped the kid-based fallback from
report_tang, causing clevis luks report to fail with "Keys from
clevis metadata not found" for any tang or tang-pqc binding
created in tang_pub mode (no stored adv).

Restore the header parameter and the fallback that checks the
kid thumbprint against the server's live advertisement when adv
is absent.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
Provide the advertisement from a file so it is always stored in
the JWE. This keeps the oversized-adv corruption scenario testable
regardless of whether the Tang server supports tang_pub.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
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