Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
65 changes: 40 additions & 25 deletions src/pins/tang/clevis-decrypt-tang
Original file line number Diff line number Diff line change
Expand Up @@ -50,35 +50,29 @@ if ! kid="$(jose fmt -j- -Og kid -Su- <<< "$jhd")"; then
exit 1
fi

# Tang advertisement validation.
if ! keys="$(jose fmt -j- -Og clevis -g tang -g adv -Oo- <<< "${jhd}")"; then
echo "JWE missing required 'clevis.tang.adv' header parameter!" >&2
exit 1
fi

# Check if the thumbprint we have in `kid' is in the advertised keys.
CLEVIS_DEFAULT_THP_ALG=S256 # SHA-256.
CLEVIS_DEFAULT_THP_LEN=43 # Length of SHA-256 thumbprint.
CLEVIS_ALTERNATIVE_THP_ALGS=S1 # SHA-1.

# Issue a warning if we are using a hash that has a shorter length than the
# default one.
if [ "${#kid}" -lt "${CLEVIS_DEFAULT_THP_LEN}" ]; then
echo "WARNING: tang using a deprecated hash for the JWK thumbprints" >&2
fi
# The adv field is optional when the server provides tang_pub in recovery.
# However, if adv IS present, the kid must match — a mismatch indicates
# a corrupted or tampered header and must fail immediately.
srv=
if keys="$(jose fmt -j- -Og clevis -g tang -g adv -Oo- <<< "${jhd}" 2>/dev/null)"; then
if [ "${#kid}" -lt "${CLEVIS_DEFAULT_THP_LEN}" ]; then
echo "WARNING: tang using a deprecated hash for the JWK thumbprints" >&2
fi

if ! srv="$(jose jwk thp -i- -f "${kid}" -a "${CLEVIS_DEFAULT_THP_ALG}" \
<<< "${keys}")"; then
# `kid' thumprint not in the advertised keys, but it's possible it was
# generated using a different algorithm than the default one.
# Let us try the alternative supported algorithms to make sure `kid'
# really is not part of the advertised keys.
for alg in ${CLEVIS_ALTERNATIVE_THP_ALGS}; do
srv="$(jose jwk thp -i- -f "$kid" -a "${alg}" <<< "${keys}")" && break
done
if [ -z "${srv}" ]; then
echo "JWE header validation of 'clevis.tang.adv' failed: key thumbprint does not match" >&2
exit 1
if ! srv="$(jose jwk thp -i- -f "${kid}" -a "${CLEVIS_DEFAULT_THP_ALG}" \
<<< "${keys}")"; then
for alg in ${CLEVIS_ALTERNATIVE_THP_ALGS}; do
srv="$(jose jwk thp -i- -f "$kid" -a "${alg}" <<< "${keys}")" \
&& break
done
if [ -z "${srv}" ]; then
echo "JWE header validation of 'clevis.tang.adv' failed: key thumbprint does not match" >&2
exit 1
fi
fi
fi

Expand Down Expand Up @@ -120,11 +114,32 @@ xfr="$(jose jwk exc -i '{"alg":"ECMR"}' -l- -r- <<< "$clt$eph")"

rec_url="$url/rec/$kid"
ct="Content-Type: application/jwk+json"
if ! rep="$(curl -sfg "${curl_opts[@]}" -X POST -H "$ct" --data-binary @- "$rec_url" <<< "$xfr")"; then
if ! full_rep="$(curl -sfg "${curl_opts[@]}" -X POST -H "$ct" --data-binary @- "$rec_url" <<< "$xfr")"; then
echo "Error communicating with server $url" >&2
exit 1
fi

# Use tang_pub from recovery response when available.
if tang_pub="$(jose fmt -j- -Og tang_pub -Oo- <<< "$full_rep" 2>/dev/null)"; then
if tang_pub_key="$(jose jwk thp -i- -f "${kid}" -a "${CLEVIS_DEFAULT_THP_ALG}" \
<<< "$tang_pub" 2>/dev/null)"; then
srv="$tang_pub_key"
else
for alg in ${CLEVIS_ALTERNATIVE_THP_ALGS}; do
tang_pub_key="$(jose jwk thp -i- -f "$kid" -a "${alg}" \
<<< "$tang_pub" 2>/dev/null)" \
&& srv="$tang_pub_key" && break
done
fi
fi

if [ -z "$srv" ]; then
echo "Unable to determine server exchange key from tang_pub in recovery response" >&2
exit 1
fi

rep="$(jose fmt -j- -Od tang_pub -o- <<< "$full_rep" 2>/dev/null)" || rep="$full_rep"

if ! rep="$(jose fmt -j- -Og kty -q EC -EUUg crv -q "$crv" -EUUo- <<< "$rep")"; then
echo "Received invalid server reply!" >&2
exit 1
Expand Down
11 changes: 10 additions & 1 deletion src/pins/tang/clevis-encrypt-tang
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ curl_opts=()
[ -n "$key" ] && curl_opts+=(--key "$key")

### Get the advertisement
has_tang_pub=
if jws="$(jose fmt -j- -g adv -Oo- <<< "$cfg")"; then
thp="${thp:-any}"
elif jws="$(jose fmt -j- -g adv -Su- <<< "$cfg")"; then
Expand All @@ -136,6 +137,12 @@ elif jws="$(jose fmt -j- -g adv -Su- <<< "$cfg")"; then
elif ! jws="$(curl -sfg "${curl_opts[@]}" "$url/adv/$thp")"; then
echo "Unable to fetch advertisement: '$url/adv/$thp'!" >&2
exit 1
else
if ver_resp="$(curl -sfg "${curl_opts[@]}" "$url/version" 2>/dev/null)" \
&& [ "$(jose fmt -j- -Og features -Og tang_pub -o- \
<<< "$ver_resp" 2>/dev/null)" = "true" ]; then
has_tang_pub=yes
fi
fi

if ! jwks="$(jose fmt --json="${jws}" -Og payload -SyOg keys \
Expand Down Expand Up @@ -202,5 +209,7 @@ jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g tang -q "$url" -s url -UUUUo
[ -n "$cacert" ] && jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g tang -q "$cacert" -s cacert -UUUUo-)"
[ -n "$cert" ] && jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g tang -q "$cert" -s cert -UUUUo-)"
[ -n "$key" ] && jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g tang -q "$key" -s key -UUUUo-)"
jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g tang -j- -s adv -UUUUo- <<< "$jwks")"
if [ -z "$has_tang_pub" ]; then
jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g tang -j- -s adv -UUUUo- <<< "$jwks")"
fi
exec jose jwe enc -i- -k- -I- -c < <(echo -n "$jwe$jwk"; /bin/cat)
18 changes: 18 additions & 0 deletions src/pins/tang/clevis-encrypt-tang.1.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,24 @@ first place. You may also prefer installing the Tang server's CA into the
system trust store (for example with *update-ca-trust* or
*update-ca-certificates*) and omitting *cacert*.

== ADVERTISEMENT STORAGE

At bind time, when the advertisement is fetched from the Tang server, Clevis
calls *GET /version* on the server to check for *features.tang_pub*. When
the server reports this capability, the server exchange key can be obtained at
decryption time directly from the recovery response, so Clevis skips storing
the advertisement (*clevis.tang.adv*) in the JWE header. This reduces the
metadata written to the LUKS header.

If the server does not expose */version*, or *features.tang_pub* is not
present, or the advertisement is provided offline (via the *adv* configuration
property), the advertisement is stored in the JWE header as before, maintaining
full backward compatibility.

Running *clevis luks regen* against an existing binding will re-query the
server and automatically migrate to the new format when the server supports
*tang_pub*.

== OPTIONS

* *-y* :
Expand Down
1 change: 1 addition & 0 deletions src/pins/tang/tests/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,6 @@ env.prepend('PATH',

test('pin-tang', find_program('pin-tang'), env: env)
test('pin-tang-mtls', find_program('pin-tang-mtls'), env: env)
test('pin-tang-no-adv', find_program('pin-tang-no-adv'), env: env)
test('tang-validate-adv', find_program('tang-validate-adv'), env: env)
test('default-thp-alg', find_program('default-thp-alg'), env: env)
60 changes: 60 additions & 0 deletions src/pins/tang/tests/pin-tang-no-adv
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/bin/bash -xe
# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:
#
# Copyright (c) 2026 Red Hat, Inc.
# Author: Sergio Arroutbi <sarroutb@redhat.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#

. tang-common-test-functions

on_exit() {
exit_status=$?
tang_stop "${TMP}"
[ -d "$TMP" ] && rm -rf "$TMP"
exit "${exit_status}"
}

trap 'on_exit' EXIT

TMP="$(mktemp -d)"

tang_run "${TMP}" sig exc
port=$(tang_get_port "${TMP}")

thp="$(jose jwk thp -i "$TMP/db/sig.jwk")"
adv="${TMP}/adv.jws"
tang_get_adv "${port}" "${adv}"
url="http://localhost:${port}"

# Encrypt and decrypt must work regardless of tang_pub support.
cfg="$(printf '{"url":"%s","thp":"%s"}' "$url" "$thp")"
enc="$(echo -n "hi" | clevis encrypt tang "$cfg")"
dec="$(echo -n "$enc" | clevis decrypt)"
test "$dec" == "hi"

# Verify consistent behavior: if adv is absent from the JWE header,
# the server must actually return tang_pub in recovery responses.
hdr="$(echo -n "$enc" | cut -d. -f1)"
jhd="$(jose b64 dec -i- <<< "$hdr")"
if ! jose fmt -j- -Og clevis -g tang -g adv -Oo- <<< "$jhd" >/dev/null 2>&1; then
ver_resp="$(curl -sf "http://localhost:${port}/version")"
[ "$(jose fmt -j- -Og features -Og tang_pub -o- <<< "$ver_resp" \
2>/dev/null)" = "true" ]
fi

# Server down must cause decrypt to fail.
tang_stop "${TMP}"
! echo "$enc" | clevis decrypt
Loading