#!/bin/bash -e # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2016 Red Hat, Inc. # Author: Harald Hoyer # Author: Nathaniel McCallum # # 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 . # 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 @OLD_CRYPTSETUP@ } function usage() { exec >&2 echo echo "Usage: clevis luks bind [-f] [-s SLT] [-k KEY] -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 " -s SLT The LUKS slot to use" 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=() while getopts ":hfd:s:k:" o; do case "$o" in f) FRC+=(-f);; d) DEV="$OPTARG";; s) SLT="$OPTARG";; k) KEY="$OPTARG";; *) 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 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" ]; 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' \ | head -n 1) 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 key="$(pwmake "$( cryptsetup luksDump "$DEV" \ | if [ "$luks_type" == "luks1" ]; then sed -rn 's|MK bits:[ \t]*([0-9]+)|\1|p' else sed -rn 's|^\s+Key:\s+([0-9]+) bits\s*$|\1|p' fi | sort -n | tail -n 1 )")" # Encrypt the new key jwe="$(echo -n "$key" | clevis encrypt "$PIN" "$CFG")" # 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 if [ "$luks_type" == "luks1" ]; then # 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 --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 --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 "$DEV" fi if [ $? -ne 0 ]; then echo "Error while saving Clevis metadata in LUKSMeta!" >&2 echo -n "$key" | cryptsetup luksRemoveKey "$DEV" exit 1 fi