File: //bin/clevis-luks-bind
#!/bin/bash -e
# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:
#
# Copyright (c) 2016 Red Hat, Inc.
# Author: Harald Hoyer <harald@redhat.com>
# Author: Nathaniel McCallum <npmccallum@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-luks-common-functions
SUMMARY="Binds a LUKS device using the specified policy"
UUID=cb6e8904-81ff-40da-a84a-07ab9ab5715e
# We require cryptsetup >= 2.0.4 to fully support LUKSv2.
# Support is determined at build time.
function luks2_supported() {
return 0
}
function usage() {
exec >&2
echo
echo "Usage: clevis luks bind [-y] [-f] [-s SLT] [-k KEY] [-t TOKEN_ID] -d DEV PIN CFG"
echo
echo "$SUMMARY":
echo
echo " -f Do not prompt for LUKSMeta initialization"
echo
echo " -d DEV The LUKS device on which to perform binding"
echo
echo " -y Automatically answer yes for all questions"
echo
echo " -s SLT The LUKS slot to use"
echo
echo " -t TKN_ID The LUKS token ID to use; only available for LUKS2"
echo
echo " -k KEY Non-interactively read LUKS password from KEY file"
echo " -k - Non-interactively read LUKS password from standard input"
echo
exit 2
}
if [ $# -eq 1 ] && [ "$1" == "--summary" ]; then
echo "$SUMMARY"
exit 0
fi
FRC=()
YES=()
while getopts ":hfyd:s:k:t:" o; do
case "$o" in
f) FRC+=(-f);;
d) DEV="$OPTARG";;
s) SLT="$OPTARG";;
k) KEY="$OPTARG";;
t) TOKEN_ID="$OPTARG";;
y) FRC+=(-f)
YES+=(-y);;
*) usage;;
esac
done
if [ -z "$DEV" ]; then
echo "Did not specify a device!" >&2
usage
fi
if ! cryptsetup isLuks "$DEV"; then
echo "$DEV is not a LUKS device!" >&2
exit 1
fi
if ! PIN="${@:$((OPTIND++)):1}" || [ -z "$PIN" ]; then
echo "Did not specify a pin!" >&2
usage
elif ! EXE=$(command -v clevis-encrypt-"${PIN}") || [ -z "${EXE}" ]; then
echo "'${PIN}' is not a valid pin!" >&2
usage
fi
if ! CFG="${@:$((OPTIND++)):1}" || [ -z "$CFG" ]; then
echo "Did not specify a pin config!" >&2
usage
fi
if luks2_supported; then
if cryptsetup isLuks --type luks1 "$DEV"; then
luks_type="luks1"
elif cryptsetup isLuks --type luks2 "$DEV";then
luks_type="luks2"
else
echo "$DEV is not a supported LUKS device!" >&2
exit 1
fi
else
luks_type="luks1"
fi
if [ "$luks_type" = "luks1" -a -n "$TOKEN_ID" ]; then
echo "$DEV is a LUKS1 device; -t is only supported in LUKS2"
exit 1
fi
if [ "${luks_type}" = "luks1" ]; then
# The first free slot, as per cryptsetup. In connection to bug #70, we may
# have to wipe out the LUKSMeta slot priot to adding the new key.
first_free_cs_slot=$(cryptsetup luksDump "${DEV}" \
| sed -rn 's|^Key Slot ([0-7]): DISABLED$|\1|p' \
| sed -n 1p)
if [ -z "${first_free_cs_slot}" ]; then
echo "There are no more free slots in ${DEV}!" >&2
exit 1
fi
fi
if [ -n "$KEY" ]; then
if [ "$KEY" == "-" ]; then
if [ "$luks_type" == "luks1" ]; then
if ! luksmeta test -d "$DEV" && [ -z "${FRC[*]}" ]; then
echo "Cannot use '-k-' without '-f' unless already initialized!" >&2
usage
fi
fi
elif ! [ -f "$KEY" ]; then
echo "Key file '$KEY' not found!" >&2
exit 1
fi
fi
# Generate a key with the same entropy as the LUKS Master Key
if ! key="$(clevis_luks_generate_key "${DEV}")" \
|| [ -z "${key}" ]; then
echo "Unable to generate key for ${DEV}" >&2
return 1
fi
# Encrypt the new key
jwe="$(echo -n "$key" | clevis encrypt "$PIN" "$CFG" "${YES}")"
# If necessary, initialize the LUKS volume
if [ "$luks_type" == "luks1" ] && ! luksmeta test -d "$DEV"; then
luksmeta init -d "$DEV" "${FRC[@]}"
fi
# Get the existing key.
case "$KEY" in
"") read -r -s -p "Enter existing LUKS password: " existing_key; echo;;
-) existing_key="$(/bin/cat)";;
*) ! IFS= read -rd '' existing_key < "$KEY";;
esac
# Check if the key is valid.
if ! cryptsetup luksOpen --test-passphrase "${DEV}" \
--key-file <(echo -n "${existing_key}"); then
exit 1
fi
pbkdf_args="--pbkdf pbkdf2 --pbkdf-force-iterations 1000"
if [ "$luks_type" == "luks1" ]; then
pbkdf_args=
# 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 saving.
if read -r _ state uuid < <(luksmeta show -d "${DEV}" \
| grep "^${first_free_cs_slot} *"); then
if [ "${state}" = "inactive" ] && [ "${uuid}" = "${UUID}" ]; then
luksmeta wipe -f -d "${DEV}" -s "${first_free_cs_slot}"
fi
fi
fi
# Add the new key.
if [ -n "$SLT" ]; then
cryptsetup luksAddKey ${pbkdf_args} --key-slot "$SLT" --key-file \
<(echo -n "$existing_key") "$DEV"
else
if [ $luks_type == "luks2" ]; then
readarray -t usedSlotsBeforeAddKey < <(cryptsetup luksDump "${DEV}" \
| sed -rn 's|^\s+([0-9]+): luks2$|\1|p')
else
readarray -t usedSlotsBeforeAddKey < <(cryptsetup luksDump "${DEV}" \
| sed -rn 's|^Key Slot ([0-7]): ENABLED$|\1|p')
fi
cryptsetup luksAddKey ${pbkdf_args} \
--key-file <(echo -n "${existing_key}") "$DEV"
fi < <(echo -n "${key}")
if [ $? -ne 0 ]; then
echo "Error while adding new key to LUKS header!" >&2
exit 1
fi
#Determine slot used by new key if a desired slot was not specified
if [ -z "$SLT" ]; then
if [ "$luks_type" == "luks2" ]; then
readarray -t usedSlotsAfterAddKey < <(cryptsetup luksDump "${DEV}" \
| sed -rn 's|^\s+([0-9]+): luks2$|\1|p')
else
readarray -t usedSlotsAfterAddKey < <(cryptsetup luksDump "${DEV}" \
| sed -rn 's|^Key Slot ([0-7]): ENABLED$|\1|p')
fi
for i in "${usedSlotsAfterAddKey[@]}"; do
if [[ ! " ${usedSlotsBeforeAddKey[@]} " =~ " ${i} " ]]; then
SLT=$i
break
fi
done
fi
if [ -z "$SLT" ]; then
echo "Error while adding new key to LUKS header! Key slot is undefined." >&2
exit 1
fi
if [ "$luks_type" == "luks1" ]; then
echo -n "$jwe" | luksmeta save -d "$DEV" -u "$UUID" -s "$SLT" 2>/dev/null
else
printf '{"type":"clevis","keyslots":["%s"],"jwe":%s}' "$SLT" "$(jose jwe fmt -i- <<< "$jwe")" \
| cryptsetup token import $([ -n "$TOKEN_ID" ] && echo '--token-id '$TOKEN_ID) "$DEV"
fi
if [ $? -ne 0 ]; then
echo "Error while saving Clevis metadata in LUKSMeta!" >&2
echo -n "$key" | cryptsetup luksRemoveKey "$DEV"
exit 1
fi