#!/bin/bash -e # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2019 Red Hat, Inc. # Author: Sergio Correia # # 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 . # # 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}" }