|
@@ -18,6 +18,8 @@
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
#
|
|
|
|
|
|
+CLEVIS_UUID="cb6e8904-81ff-40da-a84a-07ab9ab5715e"
|
|
|
+
|
|
|
# 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() {
|
|
@@ -63,7 +65,6 @@ clevis_luks_read_slot() {
|
|
|
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} *")"
|
|
@@ -86,7 +87,7 @@ clevis_luks_read_slot() {
|
|
|
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')
|
|
|
+ | 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
|
|
@@ -111,20 +112,25 @@ clevis_luks_read_slot() {
|
|
|
# clevis_luks_used_slots() will return the list of used slots for a given LUKS
|
|
|
# device.
|
|
|
clevis_luks_used_slots() {
|
|
|
- local DEV="${1}"
|
|
|
+ local DEV="${1:-}"
|
|
|
+ [ -z "${DEV}" ] && return 1
|
|
|
|
|
|
- local slots
|
|
|
+ local used_slots
|
|
|
if cryptsetup isLuks --type luks1 "${DEV}"; then
|
|
|
- readarray -t slots < <(cryptsetup luksDump "${DEV}" \
|
|
|
- | sed -rn 's|^Key Slot ([0-7]): ENABLED$|\1|p')
|
|
|
+ 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
|
|
|
- readarray -t slots < <(cryptsetup luksDump "${DEV}" \
|
|
|
- | sed -rn 's|^\s+([0-9]+): luks2$|\1|p')
|
|
|
+ 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 "${slots[@]}"
|
|
|
+ echo "${used_slots}"
|
|
|
}
|
|
|
|
|
|
# clevis_luks_decode_jwe() will decode a given JWE.
|
|
@@ -132,11 +138,7 @@ 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 '"')
|
|
|
+ read -r -d . coded <<< "${jwe}"
|
|
|
jose b64 dec -i- <<< "${coded}"
|
|
|
}
|
|
|
|
|
@@ -281,3 +283,710 @@ clevis_luks_read_pins_from_slot() {
|
|
|
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:-}"
|
|
|
+
|
|
|
+ [ -z "${DEV}" ] && return 1
|
|
|
+ [ -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
|
|
|
+
|
|
|
+ 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}"
|
|
|
+
|
|
|
+ [ -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 2>/dev/null)" \
|
|
|
+ || [ -z "${passphrase}" ]; then
|
|
|
+ return 1
|
|
|
+ fi
|
|
|
+
|
|
|
+ clevis_luks_check_valid_key_or_keyfile "${DEV}" "${passphrase}" || return 1
|
|
|
+ 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}"
|
|
|
+ [ -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}") \
|
|
|
+ || [ -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 _ crypt_device _; do
|
|
|
+ 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} ${dev}"
|
|
|
+ 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
|
|
|
+ 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.
|
|
|
+ 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:-}"
|
|
|
+
|
|
|
+ [ -z "${DEV}" ] && return 1
|
|
|
+ [ -z "${NEWKEY}" ] && return 1
|
|
|
+ [ -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
|
|
|
+
|
|
|
+ printf '%s' "${input}" | cryptsetup luksAddKey --batch-mode \
|
|
|
+ --key-slot "${SLT}" \
|
|
|
+ "${DEV}" \
|
|
|
+ ${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:-}"
|
|
|
+
|
|
|
+ [ -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}" 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 "${in_place}" ]; then
|
|
|
+ printf '%s' "${input}" | cryptsetup luksChangeKey "${DEV}" \
|
|
|
+ --key-slot "${SLT}" \
|
|
|
+ --batch-mode ${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:-}"
|
|
|
+
|
|
|
+ [ -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}"
|
|
|
+ return
|
|
|
+ fi
|
|
|
+
|
|
|
+ # Add a new key.
|
|
|
+ clevis_luks_add_key "${DEV}" "${SLT}" \
|
|
|
+ "${NEWKEY}" "${KEY}" "${KEYFILE}"
|
|
|
+}
|
|
|
+
|
|
|
+# 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
|
|
|
+ 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)"
|
|
|
+ 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 $(seq 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:-}"
|
|
|
+
|
|
|
+ [ -z "${DEV}" ] && return 1
|
|
|
+ [ -z "${PIN}" ] && return 1
|
|
|
+ [ -z "${CFG}" ] && return 1
|
|
|
+
|
|
|
+
|
|
|
+ if ! clevis_luks_check_valid_key_or_keyfile "${DEV}" \
|
|
|
+ "${KEY}" \
|
|
|
+ "${KEYFILE}" \
|
|
|
+ && ! KEY="$(clevis_luks_get_existing_key "${DEV}" \
|
|
|
+ "Enter existing LUKS password: " \
|
|
|
+ "recover")" || [ -z "${KEY}" ]; 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.
|
|
|
+ jwe="$(printf '%s' "${newkey}" | clevis encrypt "${PIN}" "${CFG}" ${YES})"
|
|
|
+
|
|
|
+ # We can proceed to binding, after backing up the LUKS header and
|
|
|
+ # metadata.
|
|
|
+ local CLEVIS_TMP_DIR
|
|
|
+
|
|
|
+ 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}"; 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
|
|
|
+}
|