|
@@ -0,0 +1,283 @@
|
|
|
+#!/bin/bash -e
|
|
|
+# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:
|
|
|
+#
|
|
|
+# Copyright (c) 2019 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/>.
|
|
|
+#
|
|
|
+
|
|
|
+# valid_slot() will check whether a given slot is possibly valid, i.e., if it
|
|
|
+# is a numeric value within the specified range.
|
|
|
+valid_slot() {
|
|
|
+ local SLT="${1}"
|
|
|
+ local MAX_SLOTS="${2}"
|
|
|
+ case "${SLT}" in
|
|
|
+ ''|*[!0-9]*)
|
|
|
+ return 1
|
|
|
+ ;;
|
|
|
+ *)
|
|
|
+ # We got an integer, now let's make sure it is within the
|
|
|
+ # supported range.
|
|
|
+ if [ "${SLT}" -ge "${MAX_SLOTS}" ]; then
|
|
|
+ return 1
|
|
|
+ fi
|
|
|
+ ;;
|
|
|
+ esac
|
|
|
+}
|
|
|
+
|
|
|
+# clevis_luks_read_slot() will read a particular slot of a given device, which
|
|
|
+# should be either LUKS1 or LUKS2. Returns 1 in case of failure; 0 in case of
|
|
|
+# success.
|
|
|
+clevis_luks_read_slot() {
|
|
|
+ local DEV="${1}"
|
|
|
+ local SLT="${2}"
|
|
|
+
|
|
|
+ if [ -z "${DEV}" ] || [ -z "${SLT}" ]; then
|
|
|
+ echo "Need both a device and a slot as arguments." >&2
|
|
|
+ return 1
|
|
|
+ fi
|
|
|
+
|
|
|
+ local DATA_CODED=''
|
|
|
+ local MAX_LUKS1_SLOTS=8
|
|
|
+ local MAX_LUKS2_SLOTS=32
|
|
|
+ if cryptsetup isLuks --type luks1 "${DEV}"; then
|
|
|
+ if ! valid_slot "${SLT}" "${MAX_LUKS1_SLOTS}"; then
|
|
|
+ echo "Please, provide a valid key slot number; 0-7 for LUKS1" >&2
|
|
|
+ return 1
|
|
|
+ fi
|
|
|
+
|
|
|
+ if ! luksmeta test -d "${DEV}"; then
|
|
|
+ echo "The ${DEV} device is not valid!" >&2
|
|
|
+ return 1
|
|
|
+ fi
|
|
|
+
|
|
|
+ local CLEVIS_UUID="cb6e8904-81ff-40da-a84a-07ab9ab5715e"
|
|
|
+ local uuid
|
|
|
+ # Pattern from luksmeta: active slot uuid.
|
|
|
+ read -r _ _ uuid <<< "$(luksmeta show -d "${DEV}" | grep "^${SLT} *")"
|
|
|
+
|
|
|
+ if [ "${uuid}" != ${CLEVIS_UUID}"" ]; then
|
|
|
+ echo "Not a clevis slot!" >&2
|
|
|
+ return 1
|
|
|
+ fi
|
|
|
+
|
|
|
+ if ! DATA_CODED="$(luksmeta load -d "${DEV}" -s "${SLT}")"; then
|
|
|
+ echo "Cannot load data from ${DEV} slot:${SLT}!" >&2
|
|
|
+ return 1
|
|
|
+ fi
|
|
|
+ elif cryptsetup isLuks --type luks2 "${DEV}"; then
|
|
|
+ if ! valid_slot "${SLT}" "${MAX_LUKS2_SLOTS}"; then
|
|
|
+ echo "Please, provide a valid key slot number; 0-31 for LUKS2" >&2
|
|
|
+ return 1
|
|
|
+ fi
|
|
|
+
|
|
|
+ local token_id
|
|
|
+ token_id=$(cryptsetup luksDump "${DEV}" \
|
|
|
+ | grep -E -B1 "^\s+Keyslot:\s+${SLT}$" \
|
|
|
+ | head -n 1 | sed -rn 's|^\s+([0-9]+): clevis|\1|p')
|
|
|
+ if [ -z "${token_id}" ]; then
|
|
|
+ echo "Cannot load data from ${DEV} slot:${SLT}. No token found!" >&2
|
|
|
+ return 1
|
|
|
+ fi
|
|
|
+
|
|
|
+ local token
|
|
|
+ token=$(cryptsetup token export --token-id "${token_id}" "${DEV}")
|
|
|
+ DATA_CODED=$(jose fmt -j- -Og jwe -o- <<< "${token}" \
|
|
|
+ | jose jwe fmt -i- -c)
|
|
|
+
|
|
|
+ if [ -z "${DATA_CODED}" ]; then
|
|
|
+ echo "Cannot load data from ${DEV} slot:${SLT}!" >&2
|
|
|
+ return 1
|
|
|
+ fi
|
|
|
+ else
|
|
|
+ echo "${DEV} is not a supported LUKS device!" >&2
|
|
|
+ return 1
|
|
|
+ fi
|
|
|
+ echo "${DATA_CODED}"
|
|
|
+}
|
|
|
+
|
|
|
+# clevis_luks_used_slots() will return the list of used slots for a given LUKS
|
|
|
+# device.
|
|
|
+clevis_luks_used_slots() {
|
|
|
+ local DEV="${1}"
|
|
|
+
|
|
|
+ local slots
|
|
|
+ if cryptsetup isLuks --type luks1 "${DEV}"; then
|
|
|
+ readarray -t slots < <(cryptsetup luksDump "${DEV}" \
|
|
|
+ | sed -rn 's|^Key Slot ([0-7]): ENABLED$|\1|p')
|
|
|
+ elif cryptsetup isLuks --type luks2 "${DEV}"; then
|
|
|
+ readarray -t slots < <(cryptsetup luksDump "${DEV}" \
|
|
|
+ | sed -rn 's|^\s+([0-9]+): luks2$|\1|p')
|
|
|
+ else
|
|
|
+ echo "${DEV} is not a supported LUKS device!" >&2
|
|
|
+ return 1
|
|
|
+ fi
|
|
|
+ echo "${slots[@]}"
|
|
|
+}
|
|
|
+
|
|
|
+# clevis_luks_decode_jwe() will decode a given JWE.
|
|
|
+clevis_luks_decode_jwe() {
|
|
|
+ local jwe="${1}"
|
|
|
+
|
|
|
+ local coded
|
|
|
+ if ! coded=$(jose jwe fmt -i- <<< "${jwe}"); then
|
|
|
+ return 1
|
|
|
+ fi
|
|
|
+
|
|
|
+ coded=$(jose fmt -j- -g protected -u- <<< "${coded}" | tr -d '"')
|
|
|
+ jose b64 dec -i- <<< "${coded}"
|
|
|
+}
|
|
|
+
|
|
|
+# clevis_luks_print_pin_config() will print the config of a given pin; i.e.
|
|
|
+# for tang it will display the associated url address, and for tpm2, the
|
|
|
+# properties in place, like the hash, for instance.
|
|
|
+clevis_luks_print_pin_config() {
|
|
|
+ local P="${1}"
|
|
|
+ local decoded="${2}"
|
|
|
+
|
|
|
+ local content
|
|
|
+ if ! content="$(jose fmt -j- -g clevis -g "${P}" -o- <<< "${decoded}")" \
|
|
|
+ || [ -z "${content}" ]; then
|
|
|
+ return 1
|
|
|
+ fi
|
|
|
+
|
|
|
+ local pin=
|
|
|
+ case "${P}" in
|
|
|
+ tang)
|
|
|
+ local url
|
|
|
+ url="$(jose fmt -j- -g url -u- <<< "${content}")"
|
|
|
+ pin=$(printf '{"url":"%s"}' "${url}")
|
|
|
+ printf "tang '%s'" "${pin}"
|
|
|
+ ;;
|
|
|
+ tpm2)
|
|
|
+ # Valid properties for tpm2 pin are the following:
|
|
|
+ # hash, key, pcr_bank, pcr_ids, pcr_digest.
|
|
|
+ local key
|
|
|
+ local value
|
|
|
+ for key in 'hash' 'key' 'pcr_bank' 'pcr_ids' 'pcr_digest'; do
|
|
|
+ if value=$(jose fmt -j- -g "${key}" -u- <<< "${content}"); then
|
|
|
+ pin=$(printf '%s,"%s":"%s"' "${pin}" "${key}" "${value}")
|
|
|
+ fi
|
|
|
+ done
|
|
|
+ # Remove possible leading comma.
|
|
|
+ pin=${pin/#,/}
|
|
|
+ printf "tpm2 '{%s}'" "${pin}"
|
|
|
+ ;;
|
|
|
+ sss)
|
|
|
+ local threshold
|
|
|
+ threshold=$(jose fmt -j- -Og t -o- <<< "${content}")
|
|
|
+ clevis_luks_process_sss_pin "${content}" "${threshold}"
|
|
|
+ ;;
|
|
|
+ *)
|
|
|
+ printf "unknown pin '%s'" "${P}"
|
|
|
+ ;;
|
|
|
+ esac
|
|
|
+}
|
|
|
+
|
|
|
+# clevis_luks_decode_pin_config() will receive a JWE and extract a pin config
|
|
|
+# from it.
|
|
|
+clevis_luks_decode_pin_config() {
|
|
|
+ local jwe="${1}"
|
|
|
+
|
|
|
+ local decoded
|
|
|
+ if ! decoded=$(clevis_luks_decode_jwe "${jwe}"); then
|
|
|
+ return 1
|
|
|
+ fi
|
|
|
+
|
|
|
+ local P
|
|
|
+ if ! P=$(jose fmt -j- -Og clevis -g pin -u- <<< "${decoded}"); then
|
|
|
+ return 1
|
|
|
+ fi
|
|
|
+
|
|
|
+ clevis_luks_print_pin_config "${P}" "${decoded}"
|
|
|
+}
|
|
|
+
|
|
|
+# clevis_luks_join_sss_cfg() will receive a list of configurations for a given
|
|
|
+# pin and returns it as list, in the format PIN [cfg1, cfg2, ..., cfgN].
|
|
|
+clevis_luks_join_sss_cfg() {
|
|
|
+ local pin="${1}"
|
|
|
+ local cfg="${2}"
|
|
|
+ cfg=$(echo "${cfg}" | tr -d "'" | sed -e 's/^,//')
|
|
|
+ printf '"%s":[%s]' "${pin}" "${cfg}"
|
|
|
+}
|
|
|
+
|
|
|
+# clevis_luks_process_sss_pin() will receive a JWE with information on the sss
|
|
|
+# pin config, and also its associated threshold, and will extract the info.
|
|
|
+clevis_luks_process_sss_pin() {
|
|
|
+ local jwe="${1}"
|
|
|
+ local threshold="${2}"
|
|
|
+
|
|
|
+ local sss_tang
|
|
|
+ local sss_tpm2
|
|
|
+ local sss
|
|
|
+ local pin_cfg
|
|
|
+ local pin
|
|
|
+ local cfg
|
|
|
+
|
|
|
+ local coded
|
|
|
+ for coded in $(jose fmt -j- -Og jwe -Af- <<< "${jwe}"| tr -d '"'); do
|
|
|
+ if ! pin_cfg="$(clevis_luks_decode_pin_config "${coded}")"; then
|
|
|
+ continue
|
|
|
+ fi
|
|
|
+ read -r pin cfg <<< "${pin_cfg}"
|
|
|
+ case "${pin}" in
|
|
|
+ tang)
|
|
|
+ sss_tang="${sss_tang},${cfg}"
|
|
|
+ ;;
|
|
|
+ tpm2)
|
|
|
+ sss_tpm2="${sss_tpm2},${cfg}"
|
|
|
+ ;;
|
|
|
+ sss)
|
|
|
+ sss=$(echo "${cfg}" | tr -d "'")
|
|
|
+ ;;
|
|
|
+ esac
|
|
|
+ done
|
|
|
+
|
|
|
+ cfg=
|
|
|
+ if [ -n "${sss_tang}" ]; then
|
|
|
+ cfg=$(clevis_luks_join_sss_cfg "tang" "${sss_tang}")
|
|
|
+ fi
|
|
|
+
|
|
|
+ if [ -n "${sss_tpm2}" ]; then
|
|
|
+ cfg="${cfg},"$(clevis_luks_join_sss_cfg "tpm2" "${sss_tpm2}")
|
|
|
+ fi
|
|
|
+
|
|
|
+ if [ -n "${sss}" ]; then
|
|
|
+ cfg=$(printf '%s,"sss":%s' "${cfg}" "${sss}")
|
|
|
+ fi
|
|
|
+
|
|
|
+ # Remove possible leading comma.
|
|
|
+ cfg=${cfg/#,/}
|
|
|
+ pin=$(printf '{"t":%d,"pins":{%s}}' "${threshold}" "${cfg}")
|
|
|
+ printf "sss '%s'" "${pin}"
|
|
|
+}
|
|
|
+
|
|
|
+# clevis_luks_read_pins_from_slot() will receive a given device and slot and
|
|
|
+# will then output its associated policy configuration.
|
|
|
+clevis_luks_read_pins_from_slot() {
|
|
|
+ local DEV="${1}"
|
|
|
+ local SLOT="${2}"
|
|
|
+
|
|
|
+ local jwe
|
|
|
+ if ! jwe=$(clevis_luks_read_slot "${DEV}" "${SLOT}" 2>/dev/null); then
|
|
|
+ return 1
|
|
|
+ fi
|
|
|
+
|
|
|
+ local cfg
|
|
|
+ if ! cfg="$(clevis_luks_decode_pin_config "${jwe}")"; then
|
|
|
+ return 1
|
|
|
+ fi
|
|
|
+ printf "%s: %s\n" "${SLOT}" "${cfg}"
|
|
|
+}
|