Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
e22514e
tang: add hybrid ECC+KEM post-quantum provisioning and recovery
sarroutbi Sep 28, 2026
68d689d
tang: add secure transport for KEM recovery channel
sarroutbi Oct 2, 2026
6f74acc
tang: align variable and field names with protocol specification
sarroutbi Oct 2, 2026
8ac8834
Add /adv-kem request to get specific KEM public key
sarroutbi Oct 2, 2026
a4ff9c1
tang: add hybrid PQC pin for ECC+ML-KEM post-quantum encryption
sarroutbi Oct 5, 2026
12608e4
tang: remove PQC hybrid code from classical pin scripts
sarroutbi Oct 5, 2026
ba50058
tang-pqc: fix trust prompt and document dependencies
sarroutbi Oct 5, 2026
6bf4da1
tang-pqc: add dracut module and initramfs-tools hook
sarroutbi Oct 5, 2026
0ce52ee
tang: inline tang_pub recovery logic in clevis-decrypt-tang
sarroutbi Oct 5, 2026
f8e906a
Remove never used again xxd/openssl commands
sarroutbi Oct 5, 2026
d6dcd81
Fix clevis-luks-bind to find tang-pqc pin
sarroutbi Oct 5, 2026
e8284f1
tang-pqc: add tang-pqc support to SSS pin listing and regen
sarroutbi Oct 5, 2026
e66bb40
Fix clevis-luks-report and tests for tang_pub compatibility
sarroutbi Oct 5, 2026
d43d8b2
tang-pqc: add offline advertisement binding support
sarroutbi Oct 5, 2026
1d8a5f2
tang-pqc: add integration test suite
sarroutbi Oct 5, 2026
10a1d14
Fix clevis-luks-report rotation detection for tang_pub mode
sarroutbi Oct 5, 2026
a3bb755
tang-pqc: gate build on jose ML-KEM KEM API availability
sarroutbi Oct 6, 2026
36cac43
Disable shellcheck issues due to dracut well known variables
sarroutbi Oct 6, 2026
6be8c73
Make C++ dependency conditional on jose ML-KEM API availability
sarroutbi Oct 6, 2026
9fe3db6
Include gcc-c++ (not gcc-g++) to the dependencies
sarroutbi Oct 6, 2026
040125b
Adjust to closer spec definitions for secure transport
sarroutbi Oct 6, 2026
ef72ee1
Remove unused parameter
sarroutbi Oct 6, 2026
84d1781
Remove curl/openssl (leftover from legacy Tang)
sarroutbi Oct 6, 2026
5db108b
Remove unused deleter
sarroutbi Oct 7, 2026
8762edd
Cleanse possible compromisable information
sarroutbi Oct 7, 2026
c46d721
Harden tang-pqc pin against security findings
sarroutbi Oct 9, 2026
1f7972d
Validate untrusted inputs in tang-pqc pin
sarroutbi Oct 15, 2026
888d9fa
Skip version test if Tang does not support it
sarroutbi Oct 15, 2026
2fde326
Restrict curl protocols to http/https
sarroutbi Oct 15, 2026
5c6244b
RAII cleansing for sensitive data in tang-pqc pin
sarroutbi Oct 15, 2026
ec6c9ea
Limit HTTP response size to prevent OOM from malicious server
sarroutbi Oct 15, 2026
eab4731
Bound stdin reads to prevent OOM from oversized input
sarroutbi Oct 15, 2026
4cecd7c
Enforce response size limit in writeCallback
sarroutbi Oct 15, 2026
a568c15
Delete copy semantics on CleansedString/Vector
sarroutbi Oct 15, 2026
4d8afe3
Bound offline advertisement file reads
sarroutbi Oct 15, 2026
446efee
Replace raw C idioms with safer C++ alternatives
sarroutbi Oct 19, 2026
0ce4a61
Fix critical findings from security hardening triage
sarroutbi Oct 19, 2026
f0281d1
Fix high-severity findings from security hardening triage
sarroutbi Oct 19, 2026
3ccf45f
Assume no tang_pub for offline advertisement
sarroutbi Oct 19, 2026
9b3a63b
Fix offline binding logic and empty base64url validation
sarroutbi Oct 19, 2026
30959cb
Revert tang_pub-specific changes from legacy files
sarroutbi Oct 19, 2026
efbf9cf
Add configurable KEM algorithm for tang-pqc pin
sarroutbi Oct 20, 2026
56a0e58
Display KEM algorithm in clevis luks list for tang-pqc
sarroutbi Oct 20, 2026
097e561
Add man page for clevis-encrypt-tang-pqc
sarroutbi Oct 20, 2026
8ffbd47
Validate KEM algorithm from user config in encrypt
sarroutbi Oct 21, 2026
0fcc1b4
Add live /adv fallback when tang_pub is unavailable at decrypt
sarroutbi Oct 21, 2026
e5e7406
Restore tang_pub fallback in clevis-luks-report
sarroutbi Oct 29, 2026
782d057
Force adv from file in luksmeta corruption test
sarroutbi Oct 29, 2026
02b1263
Add tang-pqc references to existing man pages
sarroutbi Nov 10, 2026
7335597
Revert unrelated changes from clevis-encrypt-tang
sarroutbi Nov 10, 2026
313c023
Add report_tang_pqc for KEM key rotation detection
sarroutbi Nov 10, 2026
eb67da5
Add known-answer test for HKDF key derivation
sarroutbi Nov 10, 2026
1a66e7c
Fix tests to use ?alg parameter always
sarroutbi Nov 11, 2026
f491625
Remove tang_kem_pub from hybrid binding
sarroutbi Nov 11, 2026
e92e25b
Add transport header binding to inner blob JWE
sarroutbi Nov 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions .github/workflows/install-dependencies
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
#!/bin/bash -ex

COMMON="meson curl git make file bzip2 jose tang cryptsetup keyutils jq socat lsof procps ${CC}"
# Dynamically pick the C++ package based on $CC and $DISTRO
if [[ "${CC}" == *"clang"* ]]; then
CXX_PKG="clang"
elif [[ "${DISTRO}" == *"debian"* ]] || [[ "${DISTRO}" == *"ubuntu"* ]]; then
CXX_PKG="g++"
else
# Fedora and CentOS
CXX_PKG="gcc-c++"
fi

COMMON="meson curl git make file bzip2 jose tang cryptsetup keyutils jq socat lsof procps ${CC} ${CXX_PKG}"


case "${DISTRO}" in
debian:*|ubuntu:*)
Expand Down Expand Up @@ -36,7 +47,7 @@ debian:*|ubuntu:*)
dnf -y install --allowerasing systemd awk
dnf -y --setopt=deltarpm=0 update
dnf -y install dnf-utils jq socat cryptsetup keyutils cracklib-dicts lsof \
opensc pcsc-lite softhsm swtpm-tools tpm-tools trousers
opensc pcsc-lite softhsm swtpm-tools tpm-tools trousers ${CXX_PKG}
command -v dnf5 && dnf5 -y install dnf5-command\(builddep\) \
|| dnf -y install dnf-command\(builddep\)
dnf -y builddep clevis
Expand Down
65 changes: 65 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,71 @@ advertisement is stored, or the JSON contents of the advertisement itself. When
the advertisement is specified manually like this, Clevis presumes that the
advertisement is trusted.

#### PIN: Tang PQC (Hybrid ECC+ML-KEM)

Clevis provides an optional hybrid post-quantum pin (`tang-pqc`) that combines
classical elliptic-curve key exchange (ECMR) with ML-KEM-768 lattice-based
key encapsulation. This protects LUKS bindings against future quantum attacks
while retaining classical security guarantees.

```bash
$ echo hi | clevis encrypt tang-pqc '{"url": "http://tang.local"}' -y > hi.jwe
$ clevis decrypt < hi.jwe
hi
```

The pin produces JWE tokens with `"pin":"tang-pqc"` in the header. Decryption
uses a bidirectional KEM-secured transport channel to recover the encryption
key from the Tang server.

Tang PQC pin allows setting PQC algorithm selection between next values:
- ML-KEM-512
- ML-KEM-768 (default)
- ML-KEM-1024

```bash
$ echo hi | clevis encrypt tang-pqc '{"url": "http://tang.local", "alg": "ML-KEM-1024"}' -y > hi.jwe
$ clevis decrypt < hi.jwe
hi
```

##### Dependencies

The tang-pqc pin requires the following libraries **with post-quantum support**:

| Dependency | Minimum version / feature |
|-------------|------------------------------------------------------------|
| `jose` | Must include ML-KEM support (`jose jwk gen -i '{"alg":"ML-KEM-768"}'`, `jose jwk encap`, `jose jwk decap`) |
| `libcrypto` | OpenSSL 3.5+ (or patched build with ML-KEM-768 and EVP_KDF HKDF support) |
| `jansson` | Any version (already a Clevis core dependency) |
| `libcurl` | Any version (already used by the classical Tang pin) |

The Meson build option `tang-pqc` defaults to `auto`: if any dependency is
missing, the pin is silently skipped with a build warning. To force a build
failure when dependencies are absent, configure with:

```bash
$ meson setup build -Dtang-pqc=enabled
```

To explicitly disable the pin:

```bash
$ meson setup build -Dtang-pqc=disabled
```

##### Tang server requirements

The Tang server must support the hybrid recovery endpoints:

- `GET /adv-kem` — serves KEM public keys as a JWS signed by the same
signing keys as `/adv`
- `POST /rec-kem/$kem_kid` — performs KEM-based key recovery with
bidirectional secure transport
- `GET /version` — must report `features.hybrid_recovery: true`

These endpoints are available in the Tang server fork with PQC support.

#### PIN: TPM1 and TPM2

Clevis provides support to encrypt a key in a Trusted Platform Module 1.2 (TPM1)
Expand Down
49 changes: 48 additions & 1 deletion meson.build
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
project('clevis', 'c', license: 'GPL3+',
version: '24',
default_options: 'c_std=c99'
default_options: ['c_std=c99']
)

libexecdir = join_paths(get_option('prefix'), get_option('libexecdir'))
Expand Down Expand Up @@ -43,6 +43,52 @@ add_project_arguments(

jansson = dependency('jansson', version: '>=2.10', required: false)
jose = dependency('jose', version: '>=8')

has_kem_api = false
if not get_option('tang-pqc').disabled() and jansson.found()
cc = meson.get_compiler('c')
has_kem_api = cc.links('''
#include <jose/jose.h>
int main(void) {
json_auto_t *j = json_pack("{s:s}", "alg", "ML-KEM-768");
if (!jose_jwk_gen(NULL, j))
return 1;
json_auto_t *enc = jose_jwk_kem_enc(NULL, j);
return enc ? 0 : 1;
}
''', dependencies: [jose, jansson], name: 'ML-KEM KEM API support in jose')
endif

have_cpp = false
if has_kem_api
have_cpp = add_languages('cpp', required: get_option('tang-pqc'))
if have_cpp
add_project_arguments(
'-Wall',
'-Wextra',
'-Werror',
'-Wchar-subscripts',
'-Wformat-security',
'-Wpointer-arith',
'-Wshadow',
'-Wsign-compare',
'-Wtype-limits',
'-Wunused-function',
'-Wno-missing-field-initializers',
'-Wno-unused-parameter',
'-Wno-unknown-pragmas',
'-D_DEFAULT_SOURCE',
'-D_POSIX_C_SOURCE=200112L',
'-DBINDIR="' + bindir + '"',
'-DCLEVIS_USER="' + get_option('user') + '"',
'-DCLEVIS_GROUP="' + get_option('group') + '"',
language: 'cpp'
)
endif
elif get_option('tang-pqc').enabled()
error('tang-pqc pin is enabled but jose lacks ML-KEM KEM API support')
endif

a2x = find_program('a2x', required: false)

bins = []
Expand All @@ -65,3 +111,4 @@ else
endif

summary('TPM 1.2 pin', get_option('tpm1'), section: 'Features')
summary('Tang PQC pin', get_option('tang-pqc'), section: 'Features')
1 change: 1 addition & 0 deletions meson_options.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ option('user', type: 'string', value: 'clevis', description: 'Unprivileged user
option('group', type: 'string', value: 'clevis', description: 'Unprivileged group for secure clevis operations')
option('tpm1', type: 'feature', value: 'auto', description: 'Enable TPM 1.2 pin support')
option('pkcs11', type: 'feature', value: 'auto', description: 'Enable PKCS11 pin support')
option('tang-pqc', type: 'feature', value: 'auto', description: 'Enable Tang hybrid post-quantum (ECC+ML-KEM) pin support')
32 changes: 32 additions & 0 deletions src/clevis.1.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,32 @@ the server advertisement. Decryption, too works like our first example:

For more information, see link:clevis-encrypt-tang.1.adoc[*clevis-encrypt-tang*(1)].

== TANG PQC BINDING

Clevis also provides a hybrid post-quantum variant of the Tang pin. This
combines the classical elliptic curve key exchange with a post-quantum Key
Encapsulation Mechanism (ML-KEM) to provide defense against both classical and
quantum computing attacks:

$ clevis encrypt tang-pqc '{"url":"http://tang.srv"}' < PT > JWE
The advertisement contains the following signing keys:

_OsIk0T-E2l6qjfdDiwVmidoZjA

Do you wish to trust these keys? [ynYN] y

By default the pin uses ML-KEM-768 (NIST security level 3). A different
parameter set can be selected via the *alg* configuration property:

$ clevis encrypt tang-pqc '{"url":"http://tang.srv","alg":"ML-KEM-1024"}' < PT > JWE

Decryption works as usual:

$ clevis decrypt < JWE > PT

This pin requires a Tang server that advertises hybrid PQC support. For more
information, see link:clevis-encrypt-tang-pqc.1.adoc[*clevis-encrypt-tang-pqc*(1)].

== TPM2 BINDING

Clevis provides support to encrypt a key in a Trusted Platform Module 2.0 (TPM2)
Expand Down Expand Up @@ -83,6 +109,11 @@ For example, let's create a high-availability setup using Tang:
$ cfg='{"t":1,"pins":{"tang":[{"url":...},{"url":...}]}}'
$ clevis encrypt sss "$cfg" < PT > JWE

You can also mix classical and post-quantum Tang pins in a single SSS policy:

$ cfg='{"t":1,"pins":{"tang":[{"url":...}],"tang-pqc":[{"url":...}]}}'
$ clevis encrypt sss "$cfg" < PT > JWE

In this policy, we are declaring that we have a threshold of 1, but that there
are multiple key fragments encrypted using different Tang servers. Since our
threshold is 1, so long as any of the Tang servers are available, decryption
Expand Down Expand Up @@ -117,6 +148,7 @@ For more information, see link:clevis-luks-bind.1.adoc[*clevis-luks-bind*(1)].
== SEE ALSO

link:clevis-encrypt-tang.1.adoc[*clevis-encrypt-tang*(1)],
link:clevis-encrypt-tang-pqc.1.adoc[*clevis-encrypt-tang-pqc*(1)],
link:clevis-encrypt-tpm2.1.adoc[*clevis-encrypt-tpm2*(1)],
link:clevis-encrypt-sss.1.adoc[*clevis-encrypt-sss*(1)],
link:clevis-luks-bind.1.adoc[*clevis-luks-bind*(1)],
Expand Down
4 changes: 4 additions & 0 deletions src/initramfs-tools/hooks/clevis.in
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ fi


copy_exec @bindir@/clevis-decrypt-tang || die 1 "@bindir@/clevis-decrypt-tang not found"
if [ -x @bindir@/clevis-decrypt-tang-pqc ]; then
copy_exec @bindir@/clevis-encrypt-tang-pqc || die 1 "@bindir@/clevis-encrypt-tang-pqc not found"
copy_exec @bindir@/clevis-decrypt-tang-pqc || die 1 "@bindir@/clevis-decrypt-tang-pqc not found"
fi
copy_exec @bindir@/clevis-decrypt-sss || die 1 "@bindir@/clevis-decrypt-sss not found"
copy_exec @bindir@/clevis-decrypt-null || die 1 "@bindir@/clevis-decrypt-null not found"
copy_exec @bindir@/clevis-decrypt || die 1 "@bindir@/clevis-decrypt not found"
Expand Down
11 changes: 8 additions & 3 deletions src/luks/clevis-luks-bind.1.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ policy. This is accomplished with a simple command:

$ clevis luks bind -d /dev/sda tang '{"url":...}'

For hybrid post-quantum protection, the *tang-pqc* pin can be used instead:

$ clevis luks bind -d /dev/sda tang-pqc '{"url":...}'

This command performs four steps:

1. Creates a new key with the same entropy as the LUKS master key -- maximum entropy bits is 256.
Expand All @@ -35,9 +39,9 @@ Clevis LUKS unlockers. See link:clevis-luks-unlockers.7.adoc[*clevis-luks-unlock
Do not prompt for LUKSMeta initialization

* *-y* :
Automatically answer yes for all questions. When using _tang_, it
causes the advertisement trust check to be skipped, which can be
useful in automated deployments
Automatically answer yes for all questions. When using _tang_ or
_tang-pqc_, it causes the advertisement trust check to be skipped,
which can be useful in automated deployments

* *-d* _DEV_ :
The LUKS device on which to perform binding
Expand Down Expand Up @@ -76,5 +80,6 @@ The images cannot be shared without also sharing a master key.

link:clevis-luks-unlockers.7.adoc[*clevis-luks-unlockers*(7)],
link:clevis-encrypt-tang.1.adoc[*clevis-encrypt-tang*(1)],
link:clevis-encrypt-tang-pqc.1.adoc[*clevis-encrypt-tang-pqc*(1)],
link:clevis-encrypt-sss.1.adoc[*clevis-encrypt-sss*(1)],
link:clevis-decrypt.1.adoc[*clevis-decrypt*(1)]
20 changes: 20 additions & 0 deletions src/luks/clevis-luks-common-functions.in
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,18 @@ clevis_luks_print_pin_config() {
pin=$(printf '{"url":"%s"}' "${url}")
printf "tang '%s'" "${pin}"
;;
tang-pqc)
local url
url="$(jose fmt -j- -g url -u- <<< "${content}")"
local alg
alg="$(jose fmt -j- -g alg -u- <<< "${content}" 2>/dev/null)" || alg=
if [ -n "${alg}" ]; then
pin=$(printf '{"url":"%s","alg":"%s"}' "${url}" "${alg}")
else
pin=$(printf '{"url":"%s"}' "${url}")
fi
printf "tang-pqc '%s'" "${pin}"
;;
tpm1)
pcr_ids="$(jose fmt -j- -g pcr_ids -u- <<< "${content}")"
pin=$(printf '"pcr_ids":"%s"' "${pcr_ids}")
Expand Down Expand Up @@ -263,6 +275,7 @@ clevis_luks_process_sss_pin() {
local sss_null
local sss_pkcs11
local sss_tang
local sss_tang_pqc
local sss_tpm1
local sss_tpm2
local sss
Expand All @@ -286,6 +299,9 @@ clevis_luks_process_sss_pin() {
tang)
sss_tang="${sss_tang},${cfg}"
;;
tang-pqc)
sss_tang_pqc="${sss_tang_pqc},${cfg}"
;;
tpm1)
sss_tpm1="${sss_tpm1},${cfg}"
;;
Expand All @@ -307,6 +323,10 @@ clevis_luks_process_sss_pin() {
cfg="${cfg},"$(clevis_luks_join_sss_cfg "tang" "${sss_tang}")
fi

if [ -n "${sss_tang_pqc}" ]; then
cfg="${cfg},"$(clevis_luks_join_sss_cfg "tang-pqc" "${sss_tang_pqc}")
fi

if [ -n "${sss_tpm1}" ]; then
cfg="${cfg},"$(clevis_luks_join_sss_cfg "tpm1" "${sss_tpm1}")
fi
Expand Down
1 change: 1 addition & 0 deletions src/luks/clevis-luks-edit.1.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,4 @@ passphrase for the device.
== SEE ALSO

link:clevis-luks-list.1.adoc[*clevis-luks-list*(1)],
link:clevis-encrypt-tang-pqc.1.adoc[*clevis-encrypt-tang-pqc*(1)],
6 changes: 5 additions & 1 deletion src/luks/clevis-luks-list.1.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,20 @@ For example:
1: sss '{"t":1,"pins":{"tang":[{"url":"addr1"},{"url":"addr2"}],"tpm2":[{"hash":"sha256","key":"ecc"}],"sss":{"t":1,"pins":{"tang":[{"url":"addr3"}]}}}}'
2: tang '{"url":"addr"}'
3: tpm2 '{"hash":"sha256","key":"ecc","pcr_bank":"sha1","pcr_ids":"7"}'
4: tang-pqc '{"url":"addr4","alg":"ML-KEM-768"}'

clevis luks list -d /dev/sda1 -p
1: sss tang tpm2
2: tang
3: tpm2
4: tang-pqc

As we can see in the example above, */dev/sda1* has three slots bound each with a different pin.
As we can see in the example above, */dev/sda1* has four slots bound each with a different pin.

- Slot #1 is bound with the _sss_ pin, and uses also tang and tpm2 pins in its policy.
- Slot #2 is bound using the _tang_ pin
- Slot #3 is bound with the _tpm2_ pin
- Slot #4 is bound with the _tang-pqc_ pin (hybrid post-quantum)

Note that the output of *clevis luks list* can be used with the *clevis luks bind* command, such as:

Expand All @@ -63,5 +66,6 @@ In the above example, we listed only the pin bound to slot #2.

link:clevis-luks-bind.1.adoc[*clevis-luks-bind*(1)],
link:clevis-encrypt-tang.1.adoc[*clevis-encrypt-tang*(1)],
link:clevis-encrypt-tang-pqc.1.adoc[*clevis-encrypt-tang-pqc*(1)],
link:clevis-encrypt-tpm2.1.adoc[*clevis-encrypt-tpm2*(1)],
link:clevis-encrypt-sss.1.adoc[*clevis-encrypt-sss*(1)],
7 changes: 6 additions & 1 deletion src/luks/clevis-luks-regen.1.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ clevis-luks-regen - Regenerates a clevis binding

The *clevis luks regen* command regenerates the clevis binding for a given slot in a LUKS device, using the same configuration of the
existing binding. Its operation can be compared to performing *clevis luks unbind* and *clevis luks bind* for rebinding said slot and device.
This is useful when rotating tang keys.
This is useful when rotating tang or tang-pqc keys.

== OPTIONS

Expand All @@ -35,6 +35,7 @@ This is useful when rotating tang keys.
# clevis luks list -d /dev/sda1
1: tang '{"url":"http://tang.server"}'
2: tpm2 '{"hash":"sha256","key":"ecc"}'
3: tang-pqc '{"url":"http://tang.server","alg":"ML-KEM-768"}'

We see that slot 1 in /dev/sda1 has a tang binding with the following configuration:
'{"url":"http://tang.server"}'
Expand All @@ -44,8 +45,12 @@ This is useful when rotating tang keys.

After a successful operation, we will have the new binding using the same configuration that was already in place.

Similarly, we can regenerate the tang-pqc binding in slot 3:
# clevis luks regen -d /dev/sda1 -s 3

== SEE ALSO

link:clevis-luks-list.1.adoc[*clevis-luks-list*(1)]
link:clevis-luks-bind.1.adoc[*clevis-luks-bind*(1)]
link:clevis-luks-unbind.1.adoc[*clevis-luks-unbind*(1)]
link:clevis-encrypt-tang-pqc.1.adoc[*clevis-encrypt-tang-pqc*(1)]
Loading
Loading