12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085 |
- #!/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/>.
- #
- CLEVIS_UUID="cb6e8904-81ff-40da-a84a-07ab9ab5715e"
- enable_debugging() {
- # Automatically enable debugging if in initramfs phase and rd.debug
- if [ -e /usr/lib/dracut-lib.sh ]; then
- local bashopts=$-
- # Because dracut is loosely written, disable hardening options temporarily
- [[ $bashopts != *u* ]] || set +u
- [[ $bashopts != *e* ]] || set +e
- . /usr/lib/dracut-lib.sh
- [[ $bashopts != *u* ]] || set -u
- [[ $bashopts != *e* ]] || set -e
- fi
- }
- enable_debugging
- # 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 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}$" \
- | sed -n 1p | 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:-}"
- [ -z "${DEV}" ] && return 1
- local used_slots
- if cryptsetup isLuks --type luks1 "${DEV}"; then
- if ! used_slots=$(cryptsetup luksDump "${DEV}" 2>/dev/null \
- | sed -rn 's|^Key Slot ([0-7]): ENABLED$|\1|p'); then
- return 1
- fi
- elif cryptsetup isLuks --type luks2 "${DEV}"; then
- if ! used_slots=$(cryptsetup luksDump "${DEV}" 2>/dev/null \
- | sed -rn 's|^\s+([0-9]+): luks2$|\1|p'); then
- return 1
- fi
- else
- echo "${DEV} is not a supported LUKS device!" >&2
- return 1
- fi
- echo "${used_slots}"
- }
- # clevis_luks_decode_jwe() will decode a given JWE.
- clevis_luks_decode_jwe() {
- local jwe="${1}"
- local coded
- read -r -d . coded <<< "${jwe}"
- 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}"
- }
- # clevis_luks_check_valid_key_or_keyfile() receives a devices and either a
- # passphrase or keyfile and then checks whether it is able to unlock the
- # device wih the received passphrase/keyfile.
- clevis_luks_check_valid_key_or_keyfile() {
- local DEV="${1}"
- local KEY="${2:-}"
- local KEYFILE="${3:-}"
- local SLT="${4:-}"
- local EXISTING_TOKEN_ID="${5:-}"
- [ -z "${DEV}" ] && return 1
- [ -z "${EXISTING_TOKEN_ID}" ] && [ -z "${KEYFILE}" ] && [ -z "${KEY}" ] && return 1
- local extra_args
- extra_args="$([ -n "${SLT}" ] && printf -- '--key-slot %s' "${SLT}")"
- if [ -n "${KEYFILE}" ]; then
- cryptsetup open --test-passphrase "${DEV}" --key-file "${KEYFILE}" \
- ${extra_args}
- return
- fi
- if [ -n "${EXISTING_TOKEN_ID}" ]; then
- cryptsetup open --test-passphrase "${DEV}" --token-id "${EXISTING_TOKEN_ID}" \
- ${extra_args}
- return
- fi
- printf '%s' "${KEY}" | cryptsetup open --test-passphrase "${DEV}" \
- ${extra_args}
- }
- # clevis_luks_unlock_device_by_slot() does the unlock of the device and slot
- # passed as parameters and returns the decoded passphrase.
- clevis_luks_unlock_device_by_slot() {
- local DEV="${1}"
- local SLT="${2}"
- local SKIP_CHECK="${3}"
- [ -z "${DEV}" ] && return 1
- [ -z "${SLT}" ] && return 1
- local jwe passphrase
- if ! jwe="$(clevis_luks_read_slot "${DEV}" "${SLT}" 2>/dev/null)" \
- || [ -z "${jwe}" ]; then
- return 1
- fi
- if ! passphrase="$(printf '%s' "${jwe}" | clevis decrypt)" \
- || [ -z "${passphrase}" ]; then
- return 1
- fi
- if [ -z "${SKIP_CHECK}" ]; then
- clevis_luks_check_valid_key_or_keyfile "${DEV}" "${passphrase}" || return 1
- fi
- printf '%s' "${passphrase}"
- }
- # clevis_luks_unlock_device() does the unlock of the device passed as
- # parameter and returns the decoded passphrase.
- clevis_luks_unlock_device() {
- local DEV="${1}"
- local SKIP_CHECK="YES"
- [ -z "${DEV}" ] && return 1
- local used_slots
- if ! used_slots=$(clevis_luks_used_slots "${DEV}") \
- || [ -z "${used_slots}" ]; then
- return 1
- fi
- local slt pt
- for slt in ${used_slots}; do
- if ! pt=$(clevis_luks_unlock_device_by_slot "${DEV}" "${slt}" "${SKIP_CHECK}") \
- || [ -z "${pt}" ]; then
- continue
- fi
- printf '%s' "${pt}"
- return 0
- done
- return 1
- }
- # clevis_map_device() tries to map the device received as a parameter to a
- # block device. As per crypttab(5), we support /path/to/encrypted/blockdev
- # or UUID=<uuid>.
- clevis_map_device() {
- local CDEV="${1}"
- if [[ "${CDEV}" == UUID=* ]]; then
- CDEV=/dev/disk/by-uuid/${CDEV#UUID=}
- fi
- if [[ "${CDEV}" == /* ]] && [ -b "${CDEV}" ]; then
- echo "${CDEV}"
- else
- # Invalid crypttab entry.
- return 1
- fi
- }
- # clevis_is_luks_device_by_uuid_open() checks whether the LUKS device whose
- # UUID was passed as a parameter is already open.
- clevis_is_luks_device_by_uuid_open() {
- local dev_luks_uuid="${1}"
- [ -z "${dev_luks_uuid}" ] && return 1
- dev_luks_uuid="$(echo "${dev_luks_uuid}" | sed -e 's/-//g')"
- test -b /dev/disk/by-id/dm-uuid-*"${dev_luks_uuid}"*
- }
- # clevis_devices_to_unlock() returns a list of devices to be unlocked, as per
- # the info from crypttab.
- clevis_devices_to_unlock() {
- local list_open_devices="${1:-}"
- [ ! -r /etc/crypttab ] && return 1
- local dev clevis_devices crypt_device dev_uuid bindings
- clevis_devices=
- # Build list of devices to unlock.
- while read -r _volname_ crypt_device _; do
- # skip empty lines and lines which begin with the '#' char, per
- # crypttab(5)
- case $_volname_ in
- ''|\#*) continue ;;
- esac
- if ! dev=$(clevis_map_device "${crypt_device}") \
- || [ -z "${dev}" ]; then
- # Unable to get the device - maybe it's not available, e.g. a
- # device on a volume group that has not been activated yet.
- # Add it to the list anyway, since it's a pending device.
- clevis_devices="${clevis_devices} ${crypt_device}"
- continue
- fi
- # Check if this device has clevis bindings.
- if ! bindings="$(clevis luks list -d "${dev}" 2>/dev/null)" \
- || [ -z "${bindings}" ]; then
- continue
- fi
- if [ -z "${list_open_devices}" ]; then
- # Check if this device is already open.
- dev_uuid="$(cryptsetup luksUUID "${dev}")"
- if clevis_is_luks_device_by_uuid_open "${dev_uuid}"; then
- continue
- fi
- fi
- clevis_devices="${clevis_devices} ${dev}"
- done < /etc/crypttab
- echo "${clevis_devices}" | sed -e 's/^ //'
- }
- # clevis_luks1_save_slot() works with LUKS1 devices and it saves a given JWE
- # to a specific device and slot. The last parameter indicates whether we
- # should overwrite existing metadata.
- clevis_luks1_save_slot() {
- local DEV="${1}"
- local SLOT="${2}"
- local JWE="${3}"
- local SHOULD_OVERWRITE="${4:-}"
- luksmeta test -d "${DEV}" || return 1
- if luksmeta load -d "${DEV}" -s "${SLOT}" -u "${CLEVIS_UUID}" \
- >/dev/null 2>/dev/null; then
- [ -z "${SHOULD_OVERWRITE}" ] && return 1
- if ! luksmeta wipe -f -d "${DEV}" -s "${SLOT}" \
- -u "${CLEVIS_UUID}"; then
- echo "Error wiping slot ${SLOT} from ${DEV}" >&2
- return 1
- fi
- fi
- if ! echo -n "${JWE}" | luksmeta save -d "${DEV}" -s "${SLOT}" \
- -u "${CLEVIS_UUID}"; then
- echo "Error saving metadata to LUKSMeta slot ${SLOT} from ${DEV}" >&2
- return 1
- fi
- if ! luksmeta test -d "${DEV}" 2>/dev/null >/dev/null ; then
- echo "Error detected after saving metadata to LUKSMeta slot ${SLOT}, device ${DEV}" >&2
- return 1
- fi
- return 0
- }
- # clevis_luks2_save_slot() works with LUKS2 devices and it saves a given JWE
- # to a specific device and slot. The last parameter indicates whether we
- # should overwrite existing metadata.
- clevis_luks2_save_slot() {
- local DEV="${1}"
- local SLOT="${2}"
- local TKN_ID="${3}"
- local JWE="${4}"
- local SHOULD_OVERWRITE="${5:-}"
- # Sanitize clevis LUKS2 tokens. Remove "orphan" clevis tokens, i.e.,
- # tokens that are not linked to any key slots.
- local token array_len
- for token in $(cryptsetup luksDump "${DEV}" \
- | sed -rn 's|^\s+([0-9]+): clevis|\1|p'); do
- # Let's check the length of the "keyslots" array. If zero, it means
- # no key slots are linked, which is a problem.
- if ! array_len=$(cryptsetup token export --token-id \
- "${token}" "${DEV}" \
- | jose fmt --json=- --get keyslots --array --length \
- --output=-) || [ "${array_len}" -eq 0 ]; then
- # Remove bad token.
- cryptsetup token remove --token-id "${token}" "${DEV}"
- fi
- done
- if ! token="$(cryptsetup luksDump "${DEV}" \
- | grep -E -B1 "^\s+Keyslot:\s+${SLOT}$" \
- | sed -rn 's|^\s+([0-9]+): clevis|\1|p')"; then
- echo "Error trying to read token from LUKS2 device ${DEV}, slot ${SLOT}" >&2
- return 1
- fi
- if [ -n "${token}" ]; then
- [ -z "${SHOULD_OVERWRITE}" ] && return 1
- if ! cryptsetup token remove --token-id "${token}" "${DEV}"; then
- echo "Error while removing token ${token} from LUKS2 device ${DEV}" >&2
- return 1
- fi
- fi
- if [ -n "${SHOULD_OVERWRITE}" ] && [ -n "${TKN_ID}" ]; then
- cryptsetup token remove --token-id "${TKN_ID}" "${DEV}" 2>/dev/null || :
- fi
- local metadata
- metadata=$(printf '{"type":"clevis","keyslots":["%s"],"jwe":%s}' \
- "${SLOT}" "$(jose jwe fmt --input="${JWE}")")
- if ! printf '%s' "${metadata}" | cryptsetup token import \
- $([ -n "${TKN_ID}" ] && printf -- '--token-id %s' "${TKN_ID}") \
- "${DEV}"; then
- echo "Error saving metadata to LUKS2 header in device ${DEV}" >&2
- return 1
- fi
- return 0
- }
- # clevis_luks_save_slot() saves a given JWE to a LUKS device+slot. It can also
- # overwrite existing metadata.
- clevis_luks_save_slot() {
- local DEV="${1}"
- local SLOT="${2}"
- local TKN_ID="${3}"
- local JWE="${4}"
- local SHOULD_OVERWRITE="${5:-}"
- if cryptsetup isLuks --type luks1 "${DEV}"; then
- clevis_luks1_save_slot "${DEV}" "${SLOT}" "${JWE}" \
- "${SHOULD_OVERWRITE}" || return 1
- elif cryptsetup isLuks --type luks2 "${DEV}"; then
- clevis_luks2_save_slot "${DEV}" "${SLOT}" "${TKN_ID}" "${JWE}" \
- "${SHOULD_OVERWRITE}" || return 1
- else
- return 1
- fi
- return 0
- }
- # clevis_luks1_backup_dev() backups the LUKSMeta slots from a LUKS device,
- # which can be restored with clevis_luks1_restore_dev().
- clevis_luks1_backup_dev() {
- local DEV="${1}"
- local TMP="${2}"
- [ -z "${DEV}" ] && return 1
- [ -z "${TMP}" ] && return 1
- luksmeta test -d "${DEV}" || return 0
- touch "${TMP}/initialized"
- local used_slots slt uuid jwe fname
- if ! used_slots=$(clevis_luks_used_slots "${DEV}") \
- || [ -z "${used_slots}" ]; then
- return 1
- fi
- for slt in ${used_slots}; do
- if ! uuid=$(luksmeta show -d "${DEV}" -s "${slt}") \
- || [ -z "${uuid}" ]; then
- continue
- fi
- if ! jwe=$(luksmeta load -d "${DEV}" -s "${slt}") \
- || [ -z "${jwe}" ]; then
- continue
- fi
- fname=$(printf "slot_%s_%s" "${slt}" "${uuid}")
- printf "%s" "${jwe}" > "${TMP}/${fname}"
- done
- return 0
- }
- # clevis_luks1_restore_dev() takes care of restoring the LUKSMeta slots from
- # a LUKS device that was backup'ed by clevis_luks1_backup_dev().
- clevis_luks1_restore_dev() {
- local DEV="${1}"
- local TMP="${2}"
- [ -z "${DEV}" ] && return 1
- [ -z "${TMP}" ] && return 1
- [ -e "${TMP}/initialized" ] || return 0
- luksmeta test -d "${DEV}" || luksmeta init -f -d "${DEV}"
- local slt uuid jwe fname
- for fname in "${TMP}"/slot_*; do
- [ -f "${fname}" ] || break
- if ! slt=$(echo "${fname}" | cut -d '_' -f 2) \
- || [ -z "${slt}" ]; then
- continue
- fi
- if ! uuid=$(echo "${fname}" | cut -d '_' -f 3) \
- || [ -z "${uuid}" ]; then
- continue
- fi
- if ! jwe=$(cat "${fname}") || [ -z "${jwe}" ]; then
- continue
- fi
- if ! clevis_luks1_save_slot "${DEV}" "${slt}" \
- "${jwe}" "overwrite"; then
- echo "Error restoring LUKSmeta slot ${slt} from ${DEV}" >&2
- return 1
- fi
- done
- return 0
- }
- # clevis_luks_backup_dev() backups a particular LUKS device, which can then
- # be restored with clevis_luks_restore_dev().
- clevis_luks_backup_dev() {
- local DEV="${1}"
- local TMP="${2}"
- [ -z "${DEV}" ] && return 1
- [ -z "${TMP}" ] && return 1
- printf '%s' "${DEV}" > "${TMP}/device"
- printf '%s' "${DEV}" > "${TMP}/device"
- local HDR
- HDR="${TMP}/$(basename "${DEV}").header"
- if ! cryptsetup luksHeaderBackup "${DEV}" --batch-mode \
- --header-backup-file "${HDR}"; then
- echo "Error backing up LUKS header from ${DEV}" >&2
- return 1
- fi
- # If LUKS1, we need to manually back up (and later restore) the
- # LUKSmeta slots. For LUKS2, simply saving the header also saves
- # the associated tokens.
- if cryptsetup isLuks --type luks1 "${DEV}"; then
- if ! clevis_luks1_backup_dev "${DEV}" "${TMP}"; then
- return 1
- fi
- fi
- return 0
- }
- # clevis_luks_restore_dev() restores a given device that was backup'ed by
- # clevis_luks_backup_dev().
- clevis_luks_restore_dev() {
- local TMP="${1}"
- [ -z "${TMP}" ] && return 1
- [ -r "${TMP}"/device ] || return 1
- local DEV
- DEV="$(cat "${TMP}"/device)"
- local HDR
- HDR="${TMP}/$(basename "${DEV}").header"
- if [ ! -e "${HDR}" ]; then
- echo "LUKS header backup does not exist" >&2
- return 1
- fi
- if ! cryptsetup luksHeaderRestore "${DEV}" --batch-mode \
- --header-backup-file "${HDR}"; then
- echo "Error restoring LUKS header from ${DEV}" >&2
- return 1
- fi
- # If LUKS1, we need to manually back up (and later restore) the
- # LUKSmeta slots. For LUKS2, simply saving the header also saves
- # the associated tokens.
- if cryptsetup isLuks --type luks1 "${DEV}"; then
- if ! clevis_luks1_restore_dev "${DEV}" "${TMP}"; then
- return 1
- fi
- fi
- return 0
- }
- # clevis_luks_get_existing_key() may try to recover a valid password from
- # existing bindings and additionally prompt the user for the passphrase.
- clevis_luks_get_existing_key() {
- local DEV="${1}"
- local PROMPT="${2}"
- local RECOVER="${3:-}"
- [ -z "${DEV}" ] && return 1
- local pt
- if [ -n "${RECOVER}" ] && pt="$(clevis_luks_unlock_device "${DEV}")" \
- && [ -n "${pt}" ]; then
- printf '%s' "${pt}"
- return 0
- fi
- # Let's prompt the user for the password.
- IFS= read -r -s -p "${PROMPT}" pt; echo >&2
- # Check if key is valid.
- clevis_luks_check_valid_key_or_keyfile "${DEV}" "${pt}" || return 1
- printf '%s' "${pt}"
- }
- # clevis_luks_luksmeta_sync_fix() makes sure LUKSmeta slots are sync'ed with
- # cryptsetup, in order to prevent issues when saving clevis metadata.
- clevis_luks_luksmeta_sync_fix() {
- local DEV="${DEV}"
- [ -z "${DEV}" ] && return 1
- # This applies only to LUKS1 devices.
- cryptsetup isLuks --type luks1 "${DEV}" || return 0
- # No issues if the LUKSmeta metadata is not initialized.
- luksmeta test -d "${DEV}" || return 0
- local first_free_slot
- if ! first_free_slot=$(clevis_luks_first_free_slot "${DEV}") \
- || [ -z "${first_free_slot}" ]; then
- echo "There are possibly no free slots in ${DEV}" >&2
- return 1
- fi
- # In certain circumstances, we may have LUKSMeta slots "not in sync" with
- # cryptsetup, which means we will try to save LUKSMeta metadata over an
- # already used or partially used slot -- github issue #70.
- # If that is the case, let's wipe the LUKSMeta slot here prior to using
- # the LUKSMeta slot.
- local lmeta_slot lmeta_status lmeta_uuid
- lmeta_slot="$(luksmeta show -d "${DEV}" | grep "^${first_free_slot}")"
- # 1 active cb6e8904-81ff-40da-a84a-07ab9ab5715e
- # 2 inactive cb6e8904-81ff-40da-a84a-07ab9ab5715e
- lmeta_status="$(echo "${lmeta_slot}" | awk '{print $2}')"
- [ "${lmeta_status}" != 'inactive' ] && return 0
- lmeta_uuid="$(echo "${lmeta_slot}" | awk '{print $3}')"
- [ "${lmeta_uuid}" != "${CLEVIS_UUID}" ] && return 0
- luksmeta wipe -f -d "${DEV}" -s "${first_free_slot}"
- }
- # clevis_luks_add_key() adds a new key to a key slot.
- clevis_luks_add_key() {
- local DEV="${1}"
- local SLT="${2}"
- local NEWKEY="${3}"
- local KEY="${4}"
- local KEYFILE="${5:-}"
- local EXISTING_TOKEN_ID="${6:-}"
- [ -z "${DEV}" ] && return 1
- [ -z "${NEWKEY}" ] && return 1
- [ -z "${EXISTING_TOKEN_ID}" ] && [ -z "${KEY}" ] && [ -z "${KEYFILE}" ] && return 1
- local extra_args='' input
- input="$(printf '%s\n%s' "${KEY}" "${NEWKEY}")"
- if [ -n "${KEYFILE}" ]; then
- extra_args="$(printf -- '--key-file %s' "${KEYFILE}")"
- input="$(printf '%s' "${NEWKEY}")"
- fi
- if [ -n "${EXISTING_TOKEN_ID}" ]; then
- extra_args="$(printf -- '--token-id %s' "${EXISTING_TOKEN_ID}")"
- input="$(printf '%s' "${NEWKEY}")"
- fi
- local pbkdf_args="--pbkdf pbkdf2 --pbkdf-force-iterations 1000"
- printf '%s' "${input}" | cryptsetup luksAddKey --batch-mode \
- --key-slot "${SLT}" \
- "${DEV}" \
- ${pbkdf_args} \
- ${extra_args}
- }
- # clevis_luks_update_key() will update a key slot with a new key.
- clevis_luks_update_key() {
- local DEV="${1}"
- local SLT="${2}"
- local NEWKEY="${3}"
- local KEY="${4}"
- local KEYFILE="${5:-}"
- local EXISTING_TOKEN_ID="${6:-}"
- [ -z "${DEV}" ] && return 1
- [ -z "${NEWKEY}" ] && return 1
- # Update the key slot with the new key. If we have the key for this slot,
- # the change happens in-place. Otherwise, we kill the slot and re-add it.
- local in_place
- clevis_luks_check_valid_key_or_keyfile "${DEV}" \
- "${KEY}" "${KEYFILE}" \
- "${SLT}" "${EXISTING_TOKEN_ID}" 2>/dev/null \
- && in_place=true
- local input extra_args=
- input="$(printf '%s\n%s' "${KEY}" "${NEWKEY}")"
- if [ -n "${KEYFILE}" ]; then
- extra_args="$(printf -- '--key-file %s' "${KEYFILE}")"
- input="$(printf '%s' "${NEWKEY}")"
- fi
- if [ -n "${EXISTING_TOKEN_ID}" ]; then
- extra_args="$(printf -- '--token-id %s' "${EXISTING_TOKEN_ID}")"
- input="$(printf '%s' "${NEWKEY}")"
- fi
- local pbkdf_args="--pbkdf pbkdf2 --pbkdf-force-iterations 1000"
- if [ -n "${in_place}" ]; then
- printf '%s' "${input}" | cryptsetup luksChangeKey "${DEV}" \
- --key-slot "${SLT}" \
- --batch-mode \
- ${pbkdf_args} \
- ${extra_args}
- return
- fi
- if ! printf '%s' "${input}" | cryptsetup luksKillSlot "${DEV}" \
- "${SLT}" \
- ${extra_args}; then
- echo "Error wiping slot ${SLT} from ${DEV}" >&2
- return 1
- fi
- clevis_luks_add_key "${DEV}" "${SLT}" "${NEWKEY}" "${KEY}" "${KEYFILE}"
- }
- # clevis_luks_save_key_to_slot() will save a new key to a slot. It can either
- # add a new key to a slot or updating an already used slot.
- clevis_luks_save_key_to_slot() {
- local DEV="${1}"
- local SLT="${2}"
- local NEWKEY="${3}"
- local KEY="${4}"
- local KEYFILE="${5:-}"
- local OVERWRITE="${6:-}"
- local EXISTING_TOKEN_ID="${7:-}"
- [ -z "${DEV}" ] && return 1
- [ -z "${SLT}" ] && return 1
- [ -z "${NEWKEY}" ] && return 1
- # Make sure LUKSmeta slots are in sync with cryptsetup, to avoid the
- # problem reported in github issue #70. Applies to LUKS1 only.
- clevis_luks_luksmeta_sync_fix "${DEV}"
- # Let's check if we are adding a new key or updating an existing one.
- local update
- update="$(clevis_luks_used_slots "${DEV}" | grep "^${SLT}$")"
- if [ -n "${update}" ]; then
- # Replace an existing key.
- [ -n "${OVERWRITE}" ] || return 1
- clevis_luks_update_key "${DEV}" "${SLT}" \
- "${NEWKEY}" "${KEY}" "${KEYFILE}" "${EXISTING_TOKEN_ID}"
- return
- fi
- # Add a new key.
- clevis_luks_add_key "${DEV}" "${SLT}" \
- "${NEWKEY}" "${KEY}" "${KEYFILE}" "${EXISTING_TOKEN_ID}"
- }
- # clevis_luks_generate_key() generates a new key for use with clevis.
- clevis_luks_generate_key() {
- local DEV="${1}"
- [ -z "${DEV}" ] && return 1
- local dump filter bits
- local MAX_ENTROPY_BITS=256 # Maximum allowed by pwmake.
- dump=$(cryptsetup luksDump "${DEV}")
- if cryptsetup isLuks --type luks1 "${DEV}"; then
- filter="$(echo "${dump}" | sed -rn 's|MK bits:[ \t]*([0-9]+)|\1|p')"
- elif cryptsetup isLuks --type luks2 "${DEV}"; then
- filter="$(echo -n "${dump}" | \
- sed -rn 's|^\s+Key:\s+([0-9]+) bits\s*$|\1|p')"
- else
- return 1
- fi
- bits="$(echo -n "${filter}" | sort -n | tail -n 1)"
- if [ "${bits}" -gt "${MAX_ENTROPY_BITS}" ]; then
- bits="${MAX_ENTROPY_BITS}"
- fi
- pwmake "${bits}"
- }
- # clevis_luks_token_id_by_slot() returns the token ID linked to a
- # particular LUKS2 key slot.
- clevis_luks_token_id_by_slot() {
- local DEV="${1}"
- local SLT="${2}"
- [ -z "${DEV}" ] && return 1
- [ -z "${SLT}" ] && return 1
- cryptsetup isLuks --type luks1 "${DEV}" && echo && return
- local tkn_id
- tkn_id="$(cryptsetup luksDump "${DEV}" \
- | grep -E -B1 "^\s+Keyslot:\s+${SLT}$" \
- | sed -rn 's|^\s+([0-9]+): clevis|\1|p')"
- printf '%s' "${tkn_id}"
- }
- # clevis_luks_cleanup() removes the temporary directory used to store the data
- # relevant to device backup and restore.
- clevis_luks_cleanup() {
- [ -z "${CLEVIS_TMP_DIR}" ] && return 0
- [ -d "${CLEVIS_TMP_DIR}" ] || return 0
- if ! rm -rf "${CLEVIS_TMP_DIR}"; then
- echo "Deleting temporary files failed!" >&2
- echo "You may need to clean up '${CLEVIS_TMP_DIR}'" >&2
- exit 1
- fi
- unset CLEVIS_TMP_DIR
- }
- # clevis_luks_first_free_slot() returns the first key slot that is available
- # in a LUKS device.
- clevis_luks_first_free_slot() {
- local DEV="${1}"
- [ -z "${DEV}" ] && return 1
- local first_free_slot
- if cryptsetup isLuks --type luks1 "${DEV}"; then
- first_free_slot=$(cryptsetup luksDump "${DEV}" \
- | sed -rn 's|^Key Slot ([0-7]): DISABLED$|\1|p' \
- | sed -n 1p)
- elif cryptsetup isLuks --type luks2 "${DEV}"; then
- local used_slots slt
- used_slots="$(clevis_luks_used_slots "${DEV}")"
- for slt in {0..31}; do
- if ! echo "${used_slots}" | grep -q "^${slt}$"; then
- first_free_slot="${slt}"
- break
- fi
- done
- else
- echo "Unsupported device ${DEV}" >&2
- return 1
- fi
- echo "${first_free_slot}"
- }
- # clevis_luks_do_bind() creates or updates a particular binding.
- clevis_luks_do_bind() {
- local DEV="${1}"
- local SLT="${2}"
- local TKN_ID="${3}"
- local PIN="${4}"
- local CFG="${5}"
- local YES="${6:-}"
- local OVERWRITE="${7:-}"
- local KEY="${8:-}"
- local KEYFILE="${9:-}"
- local EXISTING_TOKEN_ID="${10:-}"
- [ -z "${DEV}" ] && return 1
- [ -z "${PIN}" ] && return 1
- [ -z "${CFG}" ] && return 1
- if ! clevis_luks_check_valid_key_or_keyfile "${DEV}" \
- "${KEY}" \
- "${KEYFILE}" \
- "" \
- "${EXISTING_TOKEN_ID}" \
- && ! KEY="$(clevis_luks_get_existing_key "${DEV}" \
- "Enter existing LUKS password: " \
- "recover")"; then
- return 1
- fi
- local newkey jwe
- if ! newkey="$(clevis_luks_generate_key "${DEV}")" \
- || [ -z "${newkey}" ]; then
- echo "Unable to generate a new key" >&2
- return 1
- fi
- # Encrypt the new key.
- if ! jwe="$(printf '%s' "${newkey}" | clevis encrypt "${PIN}" "${CFG}" \
- ${YES})" || [ -z "${jwe}" ]; then
- echo "Unable to perform encryption with PIN '${PIN}' and config '${CFG}'" >&2
- return 1
- fi
- # We can proceed to binding, after backing up the LUKS header and
- # metadata.
- local CLEVIS_TMP_DIR
- mkdir -p "${TMPDIR:-/tmp}"
- if ! CLEVIS_TMP_DIR="$(mktemp -d)" || [ -z "${CLEVIS_TMP_DIR}" ]; then
- echo "Unable to create a a temporary dir for device backup/restore" >&2
- return 1
- fi
- export CLEVIS_TMP_DIR
- trap 'clevis_luks_cleanup' EXIT
- # Backup LUKS header.
- if ! clevis_luks_backup_dev "${DEV}" "${CLEVIS_TMP_DIR}"; then
- echo "Unable to back up LUKS header from ${DEV}" >&2
- return 1
- fi
- if [ -z "${SLT}" ] && ! SLT=$(clevis_luks_first_free_slot "${DEV}") \
- || [ -z "${SLT}" ]; then
- echo "Unable to find a free slot in ${DEV}" >&2
- return 1
- fi
- [ -z "${TKN_ID}" ] && ! TKN_ID="$(clevis_luks_token_id_by_slot "${DEV}" \
- "${SLT}")" && return 1
- if ! clevis_luks_save_key_to_slot "${DEV}" "${SLT}" \
- "${newkey}" "${KEY}" "${KEYFILE}" \
- "${OVERWRITE}" "${EXISTING_TOKEN_ID}"; then
- echo "Unable to save/update key slot; operation cancelled" >&2
- clevis_luks_restore_dev "${CLEVIS_TMP_DIR}" || :
- rm -rf "${CLEVIS_TMP_DIR}"
- return 1
- fi
- if ! clevis_luks_save_slot "${DEV}" "${SLT}" "${TKN_ID}" \
- "${jwe}" "${OVERWRITE}"; then
- echo "Unable to update metadata; operation cancelled" >&2
- clevis_luks_restore_dev "${CLEVIS_TMP_DIR}" || :
- rm -rf "${CLEVIS_TMP_DIR}"
- return 1
- fi
- clevis_luks_cleanup
- trap - EXIT
- return 0
- }
- # clevis_luks_luks2_supported() indicates whether we support LUKS2 devices.
- # Support is determined at build time.
- function clevis_luks_luks2_supported() {
- # We require cryptsetup >= 2.0.4 to fully support LUKSv2.
- return @OLD_CRYPTSETUP@
- }
- # clevis_luks_luks2_existing_token_id_supported() indicates whether
- # cryptsetup allows token id for passphrase providing
- function clevis_luks_luks2_existing_token_id_supported() {
- # We require cryptsetup >= 2.6.0 to fully support LUKSv2 addkey/open by token ID
- return @OLD_CRYPTSETUP_EXISTING_TOKEN_ID@
- }
- # clevis_luks_type() returns the LUKS type of a device, e.g. "luks1".
- clevis_luks_type() {
- local DEV="${1}"
- [ -z "${DEV}" ] && return 1
- local luks_type
- if cryptsetup isLuks --type luks1 "${DEV}"; then
- luks_type="luks1"
- elif cryptsetup isLuks --type luks2 "${DEV}"; then
- clevis_luks_luks2_supported "${DEV}" || return 1
- luks_type="luks2"
- else
- return 1
- fi
- echo "${luks_type}"
- }
|