Browse Source

Merge upstream version 18

Christoph Biedl 2 years ago
parent
commit
4ceda44c4a

+ 1 - 1
meson.build

@@ -1,5 +1,5 @@
 project('clevis', 'c', license: 'GPL3+',
 project('clevis', 'c', license: 'GPL3+',
-  version: '16',
+  version: '18',
   default_options: 'c_std=c99'
   default_options: 'c_std=c99'
 )
 )
 
 

+ 2 - 0
src/initramfs-tools/scripts/local-top/clevis.in

@@ -130,6 +130,8 @@ clevisloop() {
 
 
     while true; do
     while true; do
 
 
+        # Re-get the askpass PID in case there are multiple encrypted devices
+        pid=""
         until [ "$pid" ] && [ -p "$PASSFIFO" ]; do
         until [ "$pid" ] && [ -p "$PASSFIFO" ]; do
             sleep .1
             sleep .1
             pid_fifo=$(get_askpass_pid)
             pid_fifo=$(get_askpass_pid)

+ 6 - 1
src/luks/clevis-luks-bind

@@ -88,6 +88,11 @@ if ! CFG="${@:$((OPTIND++)):1}" || [ -z "$CFG" ]; then
     usage
     usage
 fi
 fi
 
 
+# Check whether the config is valid JSON.
+if ! jose fmt --json="${CFG}" --object 2>/dev/null; then
+    echo "Configuration is malformed; it should be valid JSON" >&2
+    exit 1
+fi
 
 
 if [ "${luks_type}" = "luks1" ] && [ -n "${TOKEN_ID}" ]; then
 if [ "${luks_type}" = "luks1" ] && [ -n "${TOKEN_ID}" ]; then
     echo "${DEV} is a LUKS1 device; -t is only supported in LUKS2" >&2
     echo "${DEV} is a LUKS1 device; -t is only supported in LUKS2" >&2
@@ -119,7 +124,7 @@ if [ "${luks_type}" = "luks1" ] && ! luksmeta test -d "${DEV}"; then
     luksmeta init -d "${DEV}" ${FRC}
     luksmeta init -d "${DEV}" ${FRC}
 fi
 fi
 
 
-if ! clevis_luks_do_bind "${DEV}" "${SLT}" "" \
+if ! clevis_luks_do_bind "${DEV}" "${SLT}" "${TOKEN_ID}" \
                          "${PIN}" "${CFG}" \
                          "${PIN}" "${CFG}" \
                          "${YES}" "" \
                          "${YES}" "" \
                          "${existing_key}" "${keyfile}"; then
                          "${existing_key}" "${keyfile}"; then

+ 3 - 3
src/luks/clevis-luks-common-functions.in

@@ -323,7 +323,7 @@ clevis_luks_unlock_device_by_slot() {
         return 1
         return 1
     fi
     fi
 
 
-    if ! passphrase="$(printf '%s' "${jwe}" | clevis decrypt 2>/dev/null)" \
+    if ! passphrase="$(printf '%s' "${jwe}" | clevis decrypt)" \
                        || [ -z "${passphrase}" ]; then
                        || [ -z "${passphrase}" ]; then
         return 1
         return 1
     fi
     fi
@@ -894,7 +894,7 @@ clevis_luks_first_free_slot() {
     elif cryptsetup isLuks --type luks2 "${DEV}"; then
     elif cryptsetup isLuks --type luks2 "${DEV}"; then
         local used_slots slt
         local used_slots slt
         used_slots="$(clevis_luks_used_slots "${DEV}")"
         used_slots="$(clevis_luks_used_slots "${DEV}")"
-        for slt in $(seq 0 31); do
+        for slt in {0..31}; do
             if ! echo "${used_slots}" | grep -q "^${slt}$"; then
             if ! echo "${used_slots}" | grep -q "^${slt}$"; then
                 first_free_slot="${slt}"
                 first_free_slot="${slt}"
                 break
                 break
@@ -978,7 +978,7 @@ clevis_luks_do_bind() {
         return 1
         return 1
     fi
     fi
 
 
-    if ! clevis_luks_save_slot "${DEV}" "${SLT}" "${tkn_id}" \
+    if ! clevis_luks_save_slot "${DEV}" "${SLT}" "${TKN_ID}" \
                                "${jwe}" "${OVERWRITE}"; then
                                "${jwe}" "${OVERWRITE}"; then
         echo "Unable to update metadata; operation cancelled" >&2
         echo "Unable to update metadata; operation cancelled" >&2
         clevis_luks_restore_dev "${CLEVIS_TMP_DIR}" || :
         clevis_luks_restore_dev "${CLEVIS_TMP_DIR}" || :

+ 1 - 1
src/luks/clevis-luks-regen

@@ -67,7 +67,7 @@ fi
 # Get pin and configuration.
 # Get pin and configuration.
 if ! pin_cfg="$(clevis luks list -d "${DEV}" -s "${SLT}")" \
 if ! pin_cfg="$(clevis luks list -d "${DEV}" -s "${SLT}")" \
                 || [ -z "${pin_cfg}" ]; then
                 || [ -z "${pin_cfg}" ]; then
-    return 1
+    exit 1
 fi
 fi
 
 
 pin="$(echo "${pin_cfg}" | cut -d' ' -f2)"
 pin="$(echo "${pin_cfg}" | cut -d' ' -f2)"

+ 18 - 7
src/luks/clevis-luks-unlock

@@ -24,7 +24,7 @@ SUMMARY="Unlocks a LUKS volume"
 function usage() {
 function usage() {
     exec >&2
     exec >&2
     echo
     echo
-    echo "Usage: clevis luks unlock -d DEV [-n NAME]"
+    echo "Usage: clevis luks unlock -d DEV [-n NAME] [-t SLT]"
     echo
     echo
     echo "$SUMMARY":
     echo "$SUMMARY":
     echo
     echo
@@ -32,6 +32,9 @@ function usage() {
     echo
     echo
     echo "  -n NAME The name of the unlocked device node"
     echo "  -n NAME The name of the unlocked device node"
     echo
     echo
+    echo "  -t SLT Test the passphrase for the given slot without unlocking"
+    echo "         the device"
+    echo
     exit 2
     exit 2
 }
 }
 
 
@@ -40,10 +43,11 @@ if [ $# -eq 1 ] && [ "$1" == "--summary" ]; then
     exit 0
     exit 0
 fi
 fi
 
 
-while getopts ":d:n:" o; do
+while getopts ":d:n:t:" o; do
     case "$o" in
     case "$o" in
     d) DEV="$OPTARG";;
     d) DEV="$OPTARG";;
     n) NAME="$OPTARG";;
     n) NAME="$OPTARG";;
+    t) SLT="$OPTARG";;
     *) usage;;
     *) usage;;
     esac
     esac
 done
 done
@@ -60,9 +64,16 @@ fi
 
 
 NAME="${NAME:-luks-"$(cryptsetup luksUUID "$DEV")"}"
 NAME="${NAME:-luks-"$(cryptsetup luksUUID "$DEV")"}"
 
 
-if ! pt=$(clevis_luks_unlock_device "${DEV}"); then
-    echo "${DEV} could not be opened." >&2
-    exit 1
-fi
+if [ -n "$SLT" ]; then
+    if ! clevis_luks_unlock_device_by_slot "${DEV}" "${SLT}" >/dev/null; then
+        echo "Test for token slot ${SLT} on device ${DEV} failed." >&2
+        exit 1
+    fi
+else
+    if ! pt=$(clevis_luks_unlock_device "${DEV}"); then
+        echo "${DEV} could not be opened." >&2
+        exit 1
+    fi
 
 
-echo -n "${pt}" | cryptsetup open -d- "${DEV}" "${NAME}"
+    echo -n "${pt}" | cryptsetup open -d- "${DEV}" "${NAME}"
+fi

+ 4 - 1
src/luks/clevis-luks-unlock.1.adoc

@@ -9,7 +9,7 @@ clevis-luks-unlock - Unlocks a LUKS device bound with a Clevis policy
 
 
 == SYNOPSIS
 == SYNOPSIS
 
 
-*clevis luks unlock* -d DEV [-n NAME]
+*clevis luks unlock* -d DEV [-n NAME] [-t SLT]
 
 
 == OVERVIEW
 == OVERVIEW
 
 
@@ -26,6 +26,9 @@ provisioned Clevis policy. For example:
 * *-n* _NAME_ :
 * *-n* _NAME_ :
   The name to give the unlocked device node
   The name to give the unlocked device node
 
 
+* *-t* _SLT_ :
+  Test the passphrase for the given slot without unlocking the device
+
 == SEE ALSO
 == SEE ALSO
 
 
 link:clevis-luks-bind.1.adoc[*clevis-luks-bind*(1)]
 link:clevis-luks-bind.1.adoc[*clevis-luks-bind*(1)]

+ 4 - 0
src/luks/tests/bind-luks2

@@ -47,3 +47,7 @@ new_device "luks2" "${DEV}"
 if ! clevis luks bind -d "${DEV}" -t "$TOKEN_ID" tang "${CFG}" <<< "${DEFAULT_PASS}"; then
 if ! clevis luks bind -d "${DEV}" -t "$TOKEN_ID" tang "${CFG}" <<< "${DEFAULT_PASS}"; then
     error "${TEST}: Binding is expected to succeed when given a correct (${DEFAULT_PASS}) password." >&2
     error "${TEST}: Binding is expected to succeed when given a correct (${DEFAULT_PASS}) password." >&2
 fi
 fi
+
+if ! cryptsetup token export --token-id=5 "${DEV}"; then
+    error "${TEST}: Clevis did not add the LUKS2 token to the correct slot." >&2
+fi

+ 4 - 0
src/luks/tests/meson.build

@@ -1,6 +1,10 @@
 # We use jq for comparing the pin config in the clevis luks list tests.
 # We use jq for comparing the pin config in the clevis luks list tests.
 jq = find_program('jq', required: false)
 jq = find_program('jq', required: false)
 
 
+# We use cryptsetup for testing LUKS2 binding and saving the token in a
+# given token slot.
+cryptsetup = find_program('cryptsetup', required: true)
+
 common_functions = configure_file(input: 'tests-common-functions.in',
 common_functions = configure_file(input: 'tests-common-functions.in',
   output: 'tests-common-functions',
   output: 'tests-common-functions',
   configuration: luksmeta_data,
   configuration: luksmeta_data,

+ 28 - 2
src/pins/tang/clevis-decrypt-tang

@@ -50,12 +50,38 @@ if ! kid="$(jose fmt -j- -Og kid -Su- <<< "$jhd")"; then
     exit 1
     exit 1
 fi
 fi
 
 
-if ! srv="$(jose fmt -j- -Og clevis -g tang -g adv -Oo- <<< "$jhd" \
-        | jose jwk thp -i- -f "$kid")"; then
+# 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
     echo "JWE missing required 'clevis.tang.adv' header parameter!" >&2
     exit 1
     exit 1
 fi
 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
+
+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
+    fi
+fi
+
 if ! url="$(jose fmt -j- -Og clevis -g tang -g url -Su- <<< "$jhd")"; then
 if ! url="$(jose fmt -j- -Og clevis -g tang -g url -Su- <<< "$jhd")"; then
     echo "JWE missing required 'clevis.tang.url' header parameter!" >&2
     echo "JWE missing required 'clevis.tang.url' header parameter!" >&2
     exit 1
     exit 1

+ 18 - 5
src/pins/tang/clevis-encrypt-tang

@@ -64,6 +64,9 @@ if ! cfg="$(jose fmt -j- -Oo- <<< "$1" 2>/dev/null)"; then
     exit 1
     exit 1
 fi
 fi
 
 
+CLEVIS_DEFAULT_THP_ALG=S256       # SHA-256.
+CLEVIS_ALTERNATIVE_THP_ALGS=S1    # SHA-1.
+
 trust=
 trust=
 [ -n "${2}" ] && [ "${2}" == "-y" ] && trust=yes
 [ -n "${2}" ] && [ "${2}" == "-y" ] && trust=yes
 
 
@@ -112,15 +115,25 @@ if [ -z "${trust}" ]; then
     if [ -z "$thp" ]; then
     if [ -z "$thp" ]; then
         echo "The advertisement contains the following signing keys:" >&2
         echo "The advertisement contains the following signing keys:" >&2
         echo >&2
         echo >&2
-        jose jwk thp -i- <<< "$ver" >&2
+        jose jwk thp -i- -a "${CLEVIS_DEFAULT_THP_ALG}" <<< "$ver" >&2
         echo >&2
         echo >&2
         read -r -p "Do you wish to trust these keys? [ynYN] " ans < /dev/tty
         read -r -p "Do you wish to trust these keys? [ynYN] " ans < /dev/tty
         [[ "$ans" =~ ^[yY]$ ]] || exit 1
         [[ "$ans" =~ ^[yY]$ ]] || exit 1
 
 
     elif [ "$thp" != "any" ] && \
     elif [ "$thp" != "any" ] && \
-        ! jose jwk thp -i- -f "$thp" -o /dev/null <<< "$ver"; then
-        echo "Trusted JWK '$thp' did not sign the advertisement!" >&2
-        exit 1
+        ! jose jwk thp -i- -f "${thp}" -a "${CLEVIS_DEFAULT_THP_ALG}" \
+          -o /dev/null <<< "$ver"; then
+        # Thumbprint of trusted JWK did not match the signature. Let's check
+        # alternative thumbprints generated with clevis supported hash
+        # algorithms to be sure.
+        for alg in ${CLEVIS_ALTERNATIVE_THP_ALGS}; do
+            srv="$(jose jwk thp -i- -f "${thp}" -a "${alg}" <<< "${ver}")" \
+                   && break
+        done
+        if [ -z "${srv}" ]; then
+            echo "Trusted JWK '$thp' did not sign the advertisement!" >&2
+            exit 1
+        fi
     fi
     fi
 fi
 fi
 
 
@@ -139,7 +152,7 @@ fi
 
 
 jwk="$(jose fmt -j- -Od key_ops -o- <<< "$jwk")"
 jwk="$(jose fmt -j- -Od key_ops -o- <<< "$jwk")"
 jwk="$(jose fmt -j- -Od alg -o- <<< "$jwk")"
 jwk="$(jose fmt -j- -Od alg -o- <<< "$jwk")"
-kid="$(jose jwk thp -i- <<< "$jwk")"
+kid="$(jose jwk thp -i- -a "${CLEVIS_DEFAULT_THP_ALG}"  <<< "$jwk")"
 jwe='{"protected":{"alg":"ECDH-ES","enc":"A256GCM","clevis":{"pin":"tang","tang":{}}}}'
 jwe='{"protected":{"alg":"ECDH-ES","enc":"A256GCM","clevis":{"pin":"tang","tang":{}}}}'
 jwe="$(jose fmt -j "$jwe" -g protected -q "$kid" -s kid -UUo-)"
 jwe="$(jose fmt -j "$jwe" -g protected -q "$kid" -s kid -UUo-)"
 jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g tang -q "$url" -s url -UUUUo-)"
 jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g tang -q "$url" -s url -UUUUo-)"

+ 120 - 0
src/pins/tang/tests/default-thp-alg

@@ -0,0 +1,120 @@
+#!/bin/bash
+set -exo pipefail
+# vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:
+#
+# Copyright (c) 2020 Red Hat, Inc.
+# Author: Sergio Correia <scorreia@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
+
+TEST=$(basename "${0}")
+
+on_exit() {
+    exit_status=$?
+    tang_stop "${TMP}"
+    [ -d "${TMP}" ] && rm -rf "${TMP}"
+    exit "${exit_status}"
+}
+
+trap 'on_exit' EXIT
+
+TMP="$(mktemp -d)"
+
+port=$(tang_new_random_port)
+tang_run "${TMP}" "${port}"
+
+url="http://localhost:${port}"
+data="just a sample text"
+
+# Get the advertisement and extract the keys.
+adv="$(tang_get_adv "${port}")"
+
+jwks="$(jose fmt --json="${adv}" --get payload --b64load --output=-)"
+enc="$(printf '%s' "${jwks}" | jose jwk use --input=- --required \
+       --use deriveKey --output=-)"
+
+jose fmt --json="${enc}" --get keys --array \
+      || enc="$(printf '{"keys": [%s]}' "${enc}")"
+
+jwk="$(jose fmt --json="${enc}" --get keys --array --foreach=- \
+       | jose fmt --json=- --delete key_ops --delete alg --output=-)"
+
+jwe_t='{"protected":{"alg":"ECDH-ES","enc":"A256GCM","clevis":{"pin":"tang","tang":{}}}}'
+jwe_t="$(jose fmt --json="${jwe_t}" --get protected --get clevis --get tang --quote "${url}" --set url -UUUUo-)"
+jwe_t="$(printf '%s' "${jwks}" | jose fmt --json="${jwe_t}" --get protected --get clevis --get tang --json=- --set adv -UUUUo-)"
+
+# We currently support SHA-1 (legacy) and SHA-256.
+CLEVIS_SUPPORTED_THP_ALGS="S1 S256"
+# Now we will use every hash algorithm supported by jose to create a thumbprint
+# for `kid', then we do the encoding and verify clevis decrypt can decode it
+# correctly.
+for alg in ${CLEVIS_SUPPORTED_THP_ALGS}; do
+    kid="$(printf '%s' "${jwk}" | jose jwk thp -a "${alg}" --input=-)"
+    jwe="$(jose fmt --json="${jwe_t}" --get protected --quote "${kid}" -s kid -UUo-)"
+
+    encoded=$(printf '%s%s' "${jwk}" "${data}" \
+              | jose jwe enc --input="${jwe}" --key=- --detached=- --compact)
+
+    if ! decoded="$(printf '%s' "${encoded}" | clevis decrypt)"; then
+        tang_error "${TEST}: decoding is expected to work (alg = ${alg})"
+    fi
+
+    if  [ "${decoded}" != "${data}" ]; then
+        tang_error "${TEST}: tang decrypt should have succeeded decoded[${decoded}] data[${data}] (alg = ${alg})"
+    fi
+done
+
+# Now let's test encryption providing the thp in the configuration.
+data="just another test"
+for alg in ${CLEVIS_SUPPORTED_THP_ALGS}; do
+    thp="$(jose fmt --json="${adv}" -g payload -y -o- \
+           | jose jwk use -i- -r -u verify -o- \
+           | jose jwk thp -i- -a "${alg}")"
+    cfg="$(printf '{"url":"%s", "thp":"%s"}' "${url}" "${thp}")"
+    if ! encoded=$(printf '%s' "${data}" | clevis encrypt tang "${cfg}"); then
+        tang_error "${TEST}: tang encryption should have succeeded when providing the thp (${thp}) with any supported algorithm (${alg})"
+    fi
+
+    if ! decoded="$(printf '%s' "${encoded}" | clevis decrypt)"; then
+        tang_error "${TEST}: decoding is expected to work (thp alg = ${alg})"
+    fi
+
+    if  [ "${decoded}" != "${data}" ]; then
+        tang_error "${TEST}: tang decrypt should have succeeded decoded[${decoded}] data[${data}] (alg = ${alg})"
+    fi
+
+done
+
+# Let's also try some unsupported thp hash algorithms.
+UNSUPPORTED="S224 S384 S512" # SHA-224, SHA-384, SHA-512.
+for alg in ${UNSUPPORTED}; do
+    thp="$(jose fmt --json="${adv}" -g payload -y -o- \
+           | jose jwk use -i- -r -u verify -o- \
+           | jose jwk thp -i- -a "${alg}")"
+    cfg="$(printf '{"url":"%s", "thp":"%s"}' "${url}" "${thp}")"
+    if echo foo | clevis encrypt tang "${cfg}" >/dev/null; then
+        tang_error "${TEST}: tang encryption should have failed when providing the thp (${thp}) with an unsupported algorithm (${alg})"
+    fi
+done
+
+# Now let's try some bad values for thp.
+for thp in "" "foo" "invalid"; do
+    cfg="$(printf '{"url":"%s", "thp":"%s"}' "${url}" "${thp}")"
+    if echo foo | clevis encrypt tang "${cfg}" >/dev/null; then
+        tang_error "${TEST}: tang encryption expected to fail when providing a bad thp"
+    fi
+done

+ 1 - 0
src/pins/tang/tests/meson.build

@@ -63,3 +63,4 @@ env.prepend('PATH',
 
 
 test('pin-tang', find_program('pin-tang'), env: env)
 test('pin-tang', find_program('pin-tang'), env: env)
 test('tang-validate-adv', find_program('tang-validate-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)