#!/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/>.
#

SUMMARY="Binds a LUKS device using the specified policy"
UUID=cb6e8904-81ff-40da-a84a-07ab9ab5715e

function usage() {
    echo >&2
    echo "Usage: clevis luks bind [-f] [-s SLT] [-k KEY] -d DEV PIN CFG" >&2
    echo >&2
    echo "$SUMMARY": >&2
    echo >&2
    echo "  -f      Do not prompt for LUKSMeta initialization" >&2
    echo >&2
    echo "  -d DEV  The LUKS device on which to perform binding" >&2
    echo >&2
    echo "  -s SLT  The LUKS slot to use" >&2
    echo >&2
    echo "  -k KEY  Non-interactively read LUKS password from KEY file" >&2
    echo "  -k -    Non-interactively read LUKS password from standard input" >&2
    echo >&2
    exit 1
}

if [ $# -eq 1 -a "$1" == "--summary" ]; then
    echo "$SUMMARY"
    exit 0
fi

while getopts ":hfd:s:k:" o; do
    case "$o" in
    f) FRC=-f;;
    d) export 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 [ -n "$KEY" ]; then
    if [ "$KEY" == "-" ]; then
        if cryptsetup isLuks --type luks1 "$DEV"; 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
dump=`cryptsetup luksDump $DEV`
if cryptsetup isLuks --type luks1 "$DEV"; then
    filt=`sed -rn 's|MK bits:[ \t]*([0-9]+)|\1|p' <<< "$dump"`
else
    filt=`sed -rn 's|^\s+Key:\s+([0-9]+) bits\s*$|\1|p' <<< "$dump"`
fi
bits=`sort -n <<<"$filt" | tail -n 1`
export key=`pwmake $bits`

# Encrypt the new key
jwe=`echo -n "$key" | clevis encrypt "$PIN" "$CFG"`

# If necessary, initialize the LUKS volume
if cryptsetup isLuks --type luks1 "$DEV" && ! luksmeta test -d "$DEV"; then
    luksmeta init -d "$DEV" $FRC
fi

# Get the old key
case "$KEY" in
"") read -s -p "Enter existing LUKS password: " old; echo;;
 -) old=`/bin/cat`;;
 *) old=`/bin/cat "$KEY"`;;
esac

# Add the new key
if [ -n "$SLT" ]; then
    if ! echo -e "$old\n$key" | cryptsetup luksAddKey --key-slot $SLT $DEV; then
        echo "Error while adding new key to LUKS header!" >&2
        exit 1
    fi
elif ! SLT=`echo -e "$old\n$key" \
        | cryptsetup luksAddKey -v $DEV \
        | sed -rn 's|^Key slot ([0-9]+) created\.$|\1|p'`; then
    echo "Error while adding new key to LUKS header!" >&2
    exit 1
fi

if cryptsetup isLuks --type luks1 "$DEV"; then
    if ! echo -n $jwe | luksmeta save -d "$DEV" -u "$UUID" -s $SLT 2>/dev/null; then
        echo "Error while saving Clevis metadata in LUKSMeta!" >&2
        cryptsetup luksRemoveKey "$DEV" <<<"$key"
        exit 1
    fi
else
    jwe=`jose jwe fmt -i- <<<"$jwe"` # Convert to JSON Serialization
    tok="{\"type\":\"clevis\",\"keyslots\":[\"$SLT\"],\"jwe\":$jwe}"

    if ! cryptsetup token import "$DEV" <<<"$tok" ; then
        echo "Error while saving Clevis metadata as a LUKS token!" >&2
        cryptsetup luksRemoveKey "$DEV" <<<"$key"
        exit 1
    fi
fi