Browse Source

Import upstream version 15

Sergio Correia 3 years ago
parent
commit
cbc2ed3b26
65 changed files with 3902 additions and 468 deletions
  1. 19 18
      .travis.yml
  2. 0 38
      .travis/docker
  3. 91 0
      .travis/dracut/centos.cfg.in
  4. 166 0
      .travis/dracut/dracut-unlocker
  5. 84 0
      .travis/dracut/fedora.cfg.in
  6. 0 55
      .travis/install
  7. 0 36
      .travis/script
  8. 1 0
      INSTALL.md
  9. 7 6
      README.md
  10. 1 1
      meson.build
  11. 2 0
      src/initramfs-tools/hooks/clevis.in
  12. 1 0
      src/initramfs-tools/scripts/local-bottom/clevis.in
  13. 59 15
      src/initramfs-tools/scripts/local-top/clevis.in
  14. 9 1
      src/luks/clevis-luks-bind.1.adoc
  15. 26 10
      src/luks/clevis-luks-bind.in
  16. 723 14
      src/luks/clevis-luks-common-functions
  17. 180 0
      src/luks/clevis-luks-edit
  18. 69 0
      src/luks/clevis-luks-edit.1.adoc
  19. 93 0
      src/luks/clevis-luks-regen
  20. 51 0
      src/luks/clevis-luks-regen.1.adoc
  21. 213 0
      src/luks/clevis-luks-report
  22. 41 0
      src/luks/clevis-luks-report.1.adoc
  23. 68 0
      src/luks/clevis-luks-unlock
  24. 0 130
      src/luks/clevis-luks-unlock.in
  25. 2 3
      src/luks/clevis-luks-unlockers.7.adoc
  26. 13 6
      src/luks/meson.build
  27. 27 67
      src/luks/systemd/clevis-luks-askpass
  28. 8 5
      src/luks/systemd/clevis-luks-askpass.path
  29. 4 4
      src/luks/systemd/clevis-luks-askpass.service.in
  30. 12 3
      src/luks/systemd/dracut/clevis-pin-tang/module-setup.sh.in
  31. 13 3
      src/luks/systemd/dracut/clevis/module-setup.sh.in
  32. 113 0
      src/luks/tests/assume-yes
  33. 69 0
      src/luks/tests/assume-yes-luks2
  34. 84 0
      src/luks/tests/backup-restore-luks1
  35. 84 0
      src/luks/tests/backup-restore-luks2
  36. 40 0
      src/luks/tests/bad-sss
  37. 2 1
      src/luks/tests/bind-luks2
  38. 118 0
      src/luks/tests/edit-tang-luks1
  39. 118 0
      src/luks/tests/edit-tang-luks2
  40. 67 0
      src/luks/tests/meson.build
  41. 76 0
      src/luks/tests/regen-inplace-luks1
  42. 76 0
      src/luks/tests/regen-inplace-luks2
  43. 79 0
      src/luks/tests/regen-not-inplace-luks1
  44. 79 0
      src/luks/tests/regen-not-inplace-luks2
  45. 75 0
      src/luks/tests/report-sss-luks1
  46. 75 0
      src/luks/tests/report-sss-luks2
  47. 75 0
      src/luks/tests/report-tang-luks1
  48. 75 0
      src/luks/tests/report-tang-luks2
  49. 264 12
      src/luks/tests/tests-common-functions.in
  50. 1 0
      src/luks/tests/unbind-unbound-slot-luks2
  51. 83 0
      src/luks/tests/unlock-tang-luks1
  52. 83 0
      src/luks/tests/unlock-tang-luks2
  53. 5 0
      src/luks/udisks2/clevis-luks-udisks2.c
  54. 12 2
      src/pins/sss/clevis-encrypt-sss.1.adoc
  55. 27 9
      src/pins/sss/clevis-encrypt-sss.c
  56. 2 1
      src/pins/sss/meson.build
  57. 2 0
      src/pins/sss/pin-sss
  58. 22 13
      src/pins/tang/clevis-encrypt-tang
  59. 10 1
      src/pins/tang/clevis-encrypt-tang.1.adoc
  60. 17 10
      src/pins/tang/meson.build
  61. 7 3
      src/pins/tang/pin-tang
  62. 6 0
      src/pins/tpm2/clevis-decrypt-tpm2
  63. 6 0
      src/pins/tpm2/clevis-encrypt-tpm2
  64. 1 1
      src/pins/tpm2/meson.build
  65. 66 0
      src/pins/tpm2/pin-tpm2

+ 19 - 18
.travis.yml

@@ -1,26 +1,27 @@
 ---
 language: c
-
-compiler:
-  - gcc
-
 os: linux
-sudo: required
-services: docker
+dist: focal
+addons:
+  apt:
+    packages:
+      - qemu-kvm
+      - libvirt-daemon-system
+      - libvirt-clients
+      - virtinst
+      - bridge-utils
+      - dnsmasq-base
+      - cpu-checker
+      - cloud-image-utils
+      - tang
+    update: true
+
 env:
   matrix:
-    - DISTRO=fedora:rawhide
-    - DISTRO=fedora:latest
-    - DISTRO=centos:7
     - DISTRO=centos:8
-    - DISTRO=debian:testing
-    - DISTRO=debian:latest
-    - DISTRO=ubuntu:devel
-    - DISTRO=ubuntu:rolling
-    - DISTRO=ubuntu:bionic
+    - DISTRO=centos:8-stream
+    - DISTRO=fedora:32
+    - DISTRO=fedora:rawhide
 
-before_install: ./.travis/docker before_install
-install: ./.travis/docker install
-script: ./.travis/docker script
-after_script: ./.travis/docker after_script
+script: ./.travis/dracut/dracut-unlocker
 # vim:set ts=2 sw=2 et:

+ 0 - 38
.travis/docker

@@ -1,38 +0,0 @@
-#!/bin/bash -ex
-
-CWD="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
-SOURCE_DIR=$(cd "${CWD}/.." 2> /dev/null && pwd -P)
-
-case "${1}" in
-before_install)
-    if [ "${TRAVIS_OS_NAME}" = "linux" ]; then
-        docker create \
-            --cap-add SYS_ADMIN \
-            --device /dev/loop-control \
-            --name="${TRAVIS_COMMIT}" -t \
-            -v "${SOURCE_DIR}":/tmp/build \
-            -w /tmp/build \
-            "${DISTRO}" /bin/cat
-        docker start "${TRAVIS_COMMIT}"
-    fi
-    ;;
-
-after_script)
-    if [ "${TRAVIS_OS_NAME}" = "linux" ]; then
-        docker kill "${TRAVIS_COMMIT}"
-        docker rm "${TRAVIS_COMMIT}"
-    fi
-    ;;
-
-*)
-    if [ -x "${CWD}/${1}" ]; then
-        if [ "${TRAVIS_OS_NAME}" = "linux" ]; then
-            docker exec \
-                "$(bash <(curl -s https://codecov.io/env))" \
-                -e CC -e DISTRO \
-                "${TRAVIS_COMMIT}" ./.travis/"${1}"
-        fi
-    fi
-    ;;
-esac
-# vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:

+ 91 - 0
.travis/dracut/centos.cfg.in

@@ -0,0 +1,91 @@
+# Use text mode install
+text
+reboot
+
+%packages
+@^minimal-environment
+%end
+
+# SELinux configuration
+selinux --enforcing
+
+# Keyboard layouts
+keyboard --vckeymap=us-acentos --xlayouts='us (intl)'
+# System language
+lang en_US.UTF-8
+
+# Network information
+network --onboot=yes --device=eth0 --bootproto=static --ip=192.168.122.100 --netmask=255.255.255.0 --gateway=192.168.122.1 --nameserver=192.168.122.1
+network  --hostname=centos
+
+# Use network installation
+url --url=@COMPOSE@
+
+firstboot --enable
+# Do not configure the X Window System
+skipx
+
+# Basic services
+services --enabled=sshd
+
+ignoredisk --only-use=vda
+# Partition clearing information
+clearpart --all --initlabel --drive=vda
+
+# Disk partitioning information
+autopart --type=lvm --nohome --encrypted --luks-version=luks2 --pbkdf=pbkdf2 --pbkdf-iterations=1000  --pbkdf-memory=64 --passphrase=centos
+
+%post --erroronfail --interpreter /bin/bash
+printf "Changing output to TTY 3; press Alt-F3 to view\r\n" > /dev/tty1
+{
+    dnf update -y
+
+    mkdir -m0700 /root/.ssh/
+    cat <<EOF >/root/.ssh/authorized_keys
+@PUBKEY@
+EOF
+    chmod 0600 /root/.ssh/authorized_keys
+    restorecon -R /root/.ssh/
+
+    # Build and install clevis.
+    dnf config-manager -y --set-enabled PowerTools || dnf config-manager -y --set-enabled powertools
+
+    dnf -y install epel-release dnf-utils
+    dnf -y install dracut-network nmap-ncat git meson gcc libjose-devel \
+                   jq libluksmeta-devel jansson-devel cracklib-dicts \
+                   luksmeta jose tpm2-tools
+
+    git clone https://github.com/@TRAVIS_REPO_SLUG@.git @TRAVIS_REPO_SLUG@
+    cd @TRAVIS_REPO_SLUG@
+    git checkout -qf @TRAVIS_COMMIT@
+    mkdir build && pushd build
+    meson .. --prefix=/usr
+    ninja install
+
+    # Setup NBDE.
+    TANG=192.168.122.1
+    curl "${TANG}/adv" -o adv.jws
+    cfg=$(printf '{"url":"%s","adv":"adv.jws"}' "${TANG}")
+
+    for dev in $(lsblk -p -n -s -r | awk '$6 == "crypt" { getline; print $1 }' | sort -u); do
+        clevis luks bind -f -d "${dev}" tang "${cfg}" <<< centos
+    done
+
+    mkdir -p /etc/dracut.conf.d/
+    cat <<EOF >/etc/dracut.conf.d/clevis.conf
+kernel_cmdline="rd.neednet=1 ip=192.168.122.100::192.168.122.1:255.255.255.0::eth0:none:192.168.122.1"
+EOF
+
+    dracut -f --regenerate-all
+} 2>&1 | tee /root/postinstall.log > /dev/tty3
+%end
+
+# System timezone
+timezone America/Fortaleza --utc
+
+# Root password
+rootpw --plaintext centos
+
+%addon com_redhat_kdump --disable --reserve-mb='128'
+
+%end

+ 166 - 0
.travis/dracut/dracut-unlocker

@@ -0,0 +1,166 @@
+#!/bin/bash
+set -euo pipefail
+
+export VM=clevis
+
+title() {
+    [ -z "${1}" ] && return 0
+    printf '\n\n\n### %s\n' "${@}"
+    return 0
+}
+
+cmd() {
+    [ -z "${1}" ] && return 0
+    ssh "${VM}" "${@}"
+}
+
+is_unlocked() {
+    dev=${1:-}
+    [ -z "${dev}" ] && echo "ERROR" && return 0
+    luks_uuid="$(cmd cryptsetup luksUUID ${dev} | sed -e 's/-//'g)"
+    if cmd test -b /dev/disk/by-id/dm-uuid-*"${luks_uuid}"*; then
+        echo "YES"
+        return 0
+    fi
+    echo "NO"
+}
+
+wait_for_vm() {
+    local _timeout=${1:-120}
+    echo "[$(date)] Waiting up to ${_timeout} seconds for VM to respond..." >&2
+    local _start _elapsed
+    _start=${SECONDS}
+    while /bin/true; do
+        cmd ls 2>/dev/null >/dev/null && break
+        _elapsed=$((SECONDS - _start))
+        [ "${_elapsed}" -gt "${_timeout}" ] && echo "[$(date)] TIMEOUT reached" >&2 && return 1
+
+        sleep 0.1
+    done
+    _elapsed=$((SECONDS - _start))
+    echo "[$(date)] VM is up in ${_elapsed} seconds!" >&2
+    return 0
+}
+
+setup_host() {
+    ip a >&2
+    free -m >&2
+
+    sudo systemctl restart tangd-update
+}
+
+setup_vm() {
+    CWD="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
+    set -x
+
+    mkdir -p ~/.ssh
+    chmod 700 ~/.ssh
+    ssh-keygen -q -t rsa -b 4096 -N '' -f ~/.ssh/id_rsa <<<y 2>&1 >/dev/null
+    rm -f ~/.ssh/known_hosts
+
+    cat << EOF > ~/.ssh/config
+host clevis
+        user root
+        hostname 192.168.122.100
+        StrictHostKeyChecking no
+        ConnectTimeout 20
+        PasswordAuthentication no
+        PreferredAuthentications publickey
+        GSSAPIAuthentication no
+EOF
+
+    chmod 600 ~/.ssh/config
+    PUBKEY="$(< ~/.ssh/id_rsa.pub)"
+
+    NAME=clevis-vm
+    DATA=/data
+    DISK=${DATA}/disk.qcow2
+
+    KS=${DATA}/ks.cfg
+
+    case "${DISTRO}" in
+    fedora:32)
+        COMPOSE=https://download.fedoraproject.org/pub/fedora/linux/releases/32/Everything/x86_64/os/
+        KS_TEMPLATE=${CWD}/fedora.cfg.in
+        ;;
+    fedora:rawhide)
+        COMPOSE=https://download.fedoraproject.org/pub/fedora/linux/development/rawhide/Everything/x86_64/os/
+        KS_TEMPLATE=${CWD}/fedora.cfg.in
+        ;;
+    centos:8)
+        COMPOSE=http://mirror.centos.org/centos/8/BaseOS/x86_64/os/
+        KS_TEMPLATE=${CWD}/centos.cfg.in
+        ;;
+    centos:8-stream)
+        COMPOSE=http://mirror.centos.org/centos/8-stream/BaseOS/x86_64/os/
+        KS_TEMPLATE=${CWD}/centos.cfg.in
+        ;;
+    *)
+        echo "Unsupported distro [${DISTRO}]" >&2
+        exit 1
+        ;;
+    esac
+
+    sudo mkdir -m755 -p "${DATA}"
+    pushd "${DATA}"
+
+    cat "${KS_TEMPLATE}" \
+        | sed -e "s#@PUBKEY@#${PUBKEY}#g" \
+        | sed -e "s#@COMPOSE@#${COMPOSE}#g" \
+        | sed -e "s#@TRAVIS_REPO_SLUG@#${TRAVIS_REPO_SLUG}#g" \
+        | sed -e "s#@TRAVIS_COMMIT@#${TRAVIS_COMMIT}#g" \
+        | sudo tee ${KS}
+
+    sudo chown libvirt-qemu:kvm "${DATA}" -R
+
+    sudo virt-install --name=${NAME} --ram=2048 \
+        --os-variant=generic --os-type=linux --vcpus=1 --graphics=none \
+        --disk=path="${DISK}",size=7,bus=virtio,format=qcow2 \
+        --location="${COMPOSE}" --initrd-inject="${KS}" \
+        --extra-args="ip=dhcp ks=file:/ks.cfg inst.repo=${COMPOSE} net.ifnames=0 biosdevname=0 console=tty0 console=ttyS0,115200n8 serial" \
+        --console pty,target_type=serial --noreboot
+
+    set +x
+}
+
+title "host setup"
+setup_host
+
+title "VM setup"
+setup_vm
+
+# Start VM.
+title "Start VM"
+sudo virsh start "${NAME}"
+
+title "Verify dracut boot unlocker"
+# Check if it booted properly (i.e. unlocked on boot).
+if ! wait_for_vm; then
+    echo "[FAIL] Unable to verify the VM booted properly" >&2
+    exit 1
+fi
+
+title "fstab"
+cmd "cat /etc/fstab"
+
+title "crypttab"
+cmd "cat /etc/crypttab"
+
+title "Block devices"
+cmd "lsblk --fs"
+
+title "LUKS devices"
+
+# Check LUKS devices and config.
+for dev in $(cmd "lsblk -p -n -s -r " \
+            | awk '$6 == "crypt" { getline; print $1 }' | sort -u); do
+    echo "DEVICE[${dev}] CONFIG[$(cmd clevis luks list -d ${dev})] UNLOCKED[$(is_unlocked "${dev}")]"
+done
+
+title "clevis-luks-askpass journal"
+
+cmd "journalctl -xe -u clevis-luks-askpass"
+
+echo
+echo "[PASS] Test completed successfully" >&2
+exit 0

+ 84 - 0
.travis/dracut/fedora.cfg.in

@@ -0,0 +1,84 @@
+# Use text mode install
+text
+reboot
+
+%packages
+@^minimal-environment
+%end
+
+# SELinux configuration
+selinux --enforcing
+
+# Keyboard layouts
+keyboard --vckeymap=us-acentos --xlayouts='us (intl)'
+# System language
+lang en_US.UTF-8
+
+# Network information
+network --onboot=yes --device=eth0 --bootproto=static --ip=192.168.122.100 --netmask=255.255.255.0 --gateway=192.168.122.1 --nameserver=192.168.122.1
+network  --hostname=fedora
+firstboot --enable
+# Do not configure the X Window System
+skipx
+
+# Basic services
+services --enabled=sshd
+
+ignoredisk --only-use=vda
+# Partition clearing information
+clearpart --all --initlabel --drive=vda
+
+# Disk partitioning information
+autopart --type=lvm --nohome --encrypted --luks-version=luks2 --pbkdf=pbkdf2 --pbkdf-iterations=1000  --pbkdf-memory=64 --passphrase=fedora
+
+%post --erroronfail --interpreter /bin/bash
+printf "Changing output to TTY 3; press Alt-F3 to view\r\n" > /dev/tty1
+{
+    dnf update -y
+
+    mkdir -m0700 /root/.ssh/
+    cat <<EOF >/root/.ssh/authorized_keys
+@PUBKEY@
+EOF
+    chmod 0600 /root/.ssh/authorized_keys
+    restorecon -R /root/.ssh/
+
+    # Build and install clevis.
+    dnf -y install dnf-utils
+    dnf -y builddep clevis
+    dnf -y install dracut-network nmap-ncat
+
+    git clone https://github.com/@TRAVIS_REPO_SLUG@.git @TRAVIS_REPO_SLUG@
+    cd @TRAVIS_REPO_SLUG@
+    git checkout -qf @TRAVIS_COMMIT@
+    mkdir build && pushd build
+    meson .. --prefix=/usr
+    ninja install
+
+    # Setup NBDE.
+    TANG=192.168.122.1
+    curl "${TANG}/adv" -o adv.jws
+    cfg=$(printf '{"url":"%s","adv":"adv.jws"}' "${TANG}")
+
+    for dev in $(lsblk -p -n -s -r | awk '$6 == "crypt" { getline; print $1 }' | sort -u); do
+        clevis luks bind -f -d "${dev}" tang "${cfg}" <<< fedora
+    done
+
+    mkdir -p /etc/dracut.conf.d/
+    cat <<EOF >/etc/dracut.conf.d/clevis.conf
+kernel_cmdline="rd.neednet=1 ip=192.168.122.100::192.168.122.1:255.255.255.0::eth0:none:192.168.122.1"
+EOF
+
+    dracut -f --regenerate-all
+} 2>&1 | tee /root/postinstall.log > /dev/tty3
+%end
+
+# System timezone
+timezone America/Fortaleza --utc
+
+# Root password
+rootpw --plaintext fedora
+
+%addon com_redhat_kdump --disable --reserve-mb='128'
+
+%end

+ 0 - 55
.travis/install

@@ -1,55 +0,0 @@
-#!/bin/bash -ex
-
-COMMON="meson curl git make file bzip2 jose tang cryptsetup jq ${CC}"
-
-case "${DISTRO}" in
-debian:*|ubuntu:*)
-    # This solves an intermittant error when fetching packages on debian
-    sed -i 's|httpredir.debian.org|ftp.us.debian.org|g' /etc/apt/sources.list
-    sed -i 's|\/archive.ubuntu|\/us.archive.ubuntu|g' /etc/apt/sources.list
-
-    apt-get clean
-
-    while ! apt-get update; do
-        sleep 5
-    done
-
-    while ! apt-get -y \
-        -o Dpkg::Options::="--force-confdef" \
-        -o Dpkg::Options::="--force-confnew" \
-        dist-upgrade; do
-        sleep 5
-    done
-
-    export DEBIAN_FRONTEND=noninteractive
-    apt-get install -y keyboard-configuration console-setup
-
-    while ! apt-get -y install ${COMMON} \
-        build-essential pkg-config libssl-dev libjansson-dev libjose-dev \
-        luksmeta libluksmeta-dev libpwquality-tools libglib2.0-dev \
-        libudisks2-dev libaudit-dev; do
-
-        sleep 5
-    done
-    ;;
-
-fedora:*)
-    dnf -y clean all
-    dnf -y --setopt=deltarpm=0 update
-    dnf -y install dnf-utils jq
-    dnf -y builddep clevis
-    ;;
-
-centos:*)
-    yum -y clean all
-    yum -y --setopt=deltarpm=0 update
-    yum install -y yum-utils
-    yum config-manager -y --set-enabled PowerTools || true
-    yum -y install epel-release
-    yum -y install ${COMMON} pkgconfig openssl-devel openssl zlib-devel \
-        jansson-devel findutils gcc libjose-devel luksmeta libluksmeta-devel \
-        audit-libs-devel tpm2-tools desktop-file-utils cracklib-dicts
-    sed -i 's|>=1\.0\.2|>=1\.0\.1|' meson.build
-    ;;
-esac
-# vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:

+ 0 - 36
.travis/script

@@ -1,36 +0,0 @@
-#!/bin/bash -ex
-
-findexe() {
-    while [ ${#} -gt 0 ]; do
-        while read -r -d: path; do
-            [ -f "${path}/${1}" ] \
-                && [ -x "${path}/${1}" ] \
-                && echo "${path}/${1}" && return 0
-        done <<< "${PATH}:"
-        shift
-    done
-    return 1
-}
-
-LC_ALL=$(locale -a | grep -i '^en_US\.utf') \
-         || LC_ALL=$(locale -a | grep -i '^c\.utf')
-
-export LC_ALL
-
-mkdir build
-cd build
-
-export CFLAGS="-g -coverage"
-
-if ! meson ..; then
-    cat meson-logs/meson-log.txt >&2
-    exit 1
-fi
-
-ninja=$(findexe ninja ninja-build)
-"${ninja}" test
-
-bash <(curl --retry 10 -s https://codecov.io/bash) 2>&1 \
-       | grep -E -v "has arcs (to entry|from exit) block"
-
-# vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:

+ 1 - 0
INSTALL.md

@@ -22,6 +22,7 @@ following sections describe them for the supported platforms.
 * [tang](https://github.com/latchset/tang)
 * [curl](https://github.com/curl/curl)
 * [tpm2-tools](https://github.com/tpm2-software/tpm2-tools)
+* [ncat](https://nmap.org/ncat/) (for clevis-luks-askpass)
 
 ### Fedora
 

+ 7 - 6
README.md

@@ -1,9 +1,10 @@
-[![Build Status](https://travis-ci.org/latchset/clevis.svg?branch=master)](https://travis-ci.org/latchset/clevis)
+[![build](https://github.com/latchset/clevis/workflows/build/badge.svg)](https://github.com/latchset/clevis/actions)
+[![dracut](https://img.shields.io/travis/latchset/clevis/master.svg?label=dracut+unlocker)](https://travis-ci.org/latchset/clevis)
 
 # Clevis
 
 ## Welcome to Clevis!
-Clevis is a plugable framework for automated decryption. It can be used to
+Clevis is a pluggable framework for automated decryption. It can be used to
 provide automated decryption of data or even automated unlocking of LUKS
 volumes.
 
@@ -54,10 +55,10 @@ parameter needed in this case is the URL of the Tang server. During the
 encryption process, the Tang pin requests the key advertisement from the
 server and asks you to trust the keys. This works similarly to SSH.
 
-Alternatively, you can manually load the advertisment using the `adv`
+Alternatively, you can manually load the advertisement using the `adv`
 parameter. This parameter takes either a string referencing the file where the
-advertisement is stored, or the JSON contents of the advertisment itself. When
-the advertisment is specified manually like this, Clevis presumes that the
+advertisement is stored, or the JSON contents of the advertisement itself. When
+the advertisement is specified manually like this, Clevis presumes that the
 advertisement is trusted.
 
 #### PIN: TPM2
@@ -170,7 +171,7 @@ desktop session. The unlocker should be started automatically.
 This unlocker works almost exactly the same as the Dracut unlocker. If you
 insert a removable storage device that has been bound with Clevis, we will
 attempt to unlock it automatically in parallel with a desktop password prompt.
-If automatic unlocking succeeds, the password prompt will be dissmissed without
+If automatic unlocking succeeds, the password prompt will be dismissed without
 user intervention.
 
 #### Unlocker: Clevis command

+ 1 - 1
meson.build

@@ -1,4 +1,4 @@
-project('clevis', 'c', license: 'GPL3+', version: '13',
+project('clevis', 'c', license: 'GPL3+', version: '15',
         default_options: 'c_std=c99')
 
 libexecdir = join_paths(get_option('prefix'), get_option('libexecdir'))

+ 2 - 0
src/initramfs-tools/hooks/clevis.in

@@ -59,6 +59,8 @@ fi
 copy_exec @bindir@/clevis-decrypt-tang || die 1 "@bindir@/clevis-decrypt-tang not found"
 copy_exec @bindir@/clevis-decrypt-sss || die 1 "@bindir@/clevis-decrypt-sss not found"
 copy_exec @bindir@/clevis-decrypt || die 1 "@bindir@/clevis-decrypt not found"
+copy_exec @bindir@/clevis-luks-common-functions || die 1 "@bindir@/clevis-luks-common-functions not found"
+copy_exec @bindir@/clevis-luks-list || die 1 "@bindir@/clevis-luks-list not found"
 if [ -x @bindir@/clevis-decrypt-tpm2 ]; then
     copy_exec @bindir@/clevis-decrypt-tpm2 || die 1 "@bindir@/clevis-decrypt-tpm2 not found"
     tpm2_creatprimary_bin=$(find_binary "tpm2_createprimary")

+ 1 - 0
src/initramfs-tools/scripts/local-bottom/clevis.in

@@ -42,6 +42,7 @@ kill "$pid"
 
 for iface in /sys/class/net/*; do
     if [ -e "$iface" ]; then
+        iface=$(basename "$iface")
         ip link  set   dev "$iface" down
         ip addr  flush dev "$iface"
         ip route flush dev "$iface"

+ 59 - 15
src/initramfs-tools/scripts/local-top/clevis.in

@@ -105,6 +105,12 @@ luks2_decrypt() {
     return 1
 }
 
+has_tang_pin() {
+    local dev="$1"
+
+    clevis luks list -d "${dev}" | grep -q tang
+}
+
 # Wait for askpass, and then try and decrypt immediately. Just in case
 # there are multiple devices that need decrypting, this will loop
 # infinitely (The local-bottom script will kill this after decryption)
@@ -120,6 +126,7 @@ clevisloop() {
     fi
 
     OLD_CRYPTTAB_SOURCE=""
+    local netcfg_attempted=0
 
     while true; do
 
@@ -143,6 +150,11 @@ clevisloop() {
         [ "$CRYPTTAB_SOURCE" = "$OLD_CRYPTTAB_SOURCE" ] && continue
         OLD_CRYPTTAB_SOURCE="$CRYPTTAB_SOURCE"
 
+        if [ $netcfg_attempted -eq 0 ] && has_tang_pin ${CRYPTTAB_SOURCE}; then
+            netcfg_attempted=1
+            do_configure_networking
+        fi
+
         if cryptsetup isLuks --type luks1 "$CRYPTTAB_SOURCE"; then
             # If the device is not initialized, sliently skip it.
             luksmeta test -d "$CRYPTTAB_SOURCE" || continue
@@ -197,28 +209,60 @@ clevis_all_netbootable_devices() {
         # attribute on it)
         device=$(basename "$device")
         ip -o link show "$device" | grep -q -w master && continue
-        DEVICE="$DEVICE $device"
+        if [ -z "$DEVICE" ]; then
+            DEVICE="$device"
+        else
+            DEVICE="$DEVICE $device"
+        fi
     done
     echo "$DEVICE"
 }
 
-# Check if network is up before trying to configure it.
-eth_check() {
-    for device in $(clevis_all_netbootable_devices); do
-        ip link set dev "$device" up
-        sleep 1
-        ETH_HAS_CARRIER=$(cat /sys/class/net/"$device"/carrier)
-        if [ "$ETH_HAS_CARRIER" = '1' ]; then
-            return 0
+get_specified_device() {
+    local dev="$(echo $IP | cut -d: -f6)"
+    [ -z "$dev" ] || echo $dev
+}
+
+# Workaround configure_networking() not waiting long enough for an interface
+# to appear. This code can be removed once that has been fixed in all the
+# distro releases we care about.
+wait_for_device() {
+    local device="$(get_specified_device)"
+    local ret=0
+
+    if [ -n "$device" ]; then
+        log_begin_msg "clevis: Waiting for interface ${device} to become available"
+        local netdev_wait=0
+        while [ $netdev_wait -lt 10 ]; do
+            if [ -e "/sys/class/net/${device}" ]; then
+                break
+            fi
+            netdev_wait=$((netdev_wait + 1))
+            sleep 1
+        done
+        if [ ! -e "/sys/class/net/${device}" ]; then
+            log_failure_msg "clevis: Interface ${device} did not appear in time"
+            ret=1
         fi
-    done
-    return 1
+        log_end_msg
+    fi
+
+    wait_for_udev 10
+
+    return $ret
 }
-if eth_check; then
+
+do_configure_networking() {
     # Make sure networking is set up: if booting via nfs, it already is
-    # Doesn't seem to work when added to clevisloop for some reason
-    [ "$boot" = nfs ] || configure_networking
-fi
+    if [ "$boot" != nfs ] && wait_for_device; then
+        clevis_net_cnt=$(clevis_all_netbootable_devices | tr ' ' '\n' | wc -l)
+        if [ -z "$IP" ] && [ "$clevis_net_cnt" -gt 1 ]; then
+            echo ""
+            echo "clevis: Warning: multiple network interfaces available but no ip= parameter provided."
+        fi
+        configure_networking
+    fi
+}
 
 clevisloop &
 echo $! >/run/clevis.pid

+ 9 - 1
src/luks/clevis-luks-bind.1.adoc

@@ -9,7 +9,7 @@ clevis-luks-bind - Bind a LUKS device using the specified policy
 
 == SYNOPSIS
 
-*clevis luks bind* [-f] -d DEV [-s SLT] [-k KEY] PIN CFG
+*clevis luks bind* [-f] [-y] -d DEV [-t TKN_ID] [-s SLT] [-k KEY] PIN CFG
 
 == OVERVIEW
 
@@ -34,12 +34,20 @@ Clevis LUKS unlockers. See link:clevis-luks-unlockers.7.adoc[*clevis-luks-unlock
 * *-f* :
   Do not prompt for LUKSMeta initialization
 
+* *-y* :
+  Automatically answer yes for all questions. When using _tang_, it
+  causes the advertisement trust check to be skipped, which can be
+  useful in automated deployments
+
 * *-d* _DEV_ :
   The LUKS device on which to perform binding
 
 * *-s* _SLT_ :
   The LUKSMeta slot to use for metadata storage
 
+* *-t* _TKN_ID_ :
+  The LUKS token ID to use; only available for LUKS2
+
 * *-k* _KEY_ :
   Non-interactively read LUKS password from KEY file
 

+ 26 - 10
src/luks/clevis-luks-bind.in

@@ -31,18 +31,22 @@ function luks2_supported() {
 function usage() {
     exec >&2
     echo
-    echo "Usage: clevis luks bind [-f] [-s SLT] [-k KEY] -d DEV PIN CFG"
+    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 "  -f           Do not prompt for LUKSMeta initialization"
     echo
-    echo "  -d DEV  The LUKS device on which to perform binding"
+    echo "  -d DEV       The LUKS device on which to perform binding"
     echo
-    echo "  -s SLT  The LUKS slot to use"
+    echo "  -y           Automatically answer yes for all questions"
     echo
-    echo "  -k KEY  Non-interactively read LUKS password from KEY file"
-    echo "  -k -    Non-interactively read LUKS password from standard input"
+    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
 }
@@ -53,12 +57,16 @@ if [ $# -eq 1 ] && [ "$1" == "--summary" ]; then
 fi
 
 FRC=()
-while getopts ":hfd:s:k:" o; do
+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
@@ -76,6 +84,9 @@ 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
@@ -96,12 +107,17 @@ 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' \
-                         | head -n 1)
+                         | sed -n 1p)
     if [ -z "${first_free_cs_slot}" ]; then
         echo "There are no more free slots in ${DEV}!" >&2
         exit 1
@@ -133,7 +149,7 @@ cryptsetup luksDump "$DEV" \
 )")"
 
 # Encrypt the new key
-jwe="$(echo -n "$key" | clevis encrypt "$PIN" "$CFG")"
+jwe="$(echo -n "$key" | clevis encrypt "$PIN" "$CFG" "${YES}")"
 
 # If necessary, initialize the LUKS volume
 if [ "$luks_type" == "luks1" ] && ! luksmeta test -d "$DEV"; then
@@ -211,7 +227,7 @@ 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"
+        | 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

+ 723 - 14
src/luks/clevis-luks-common-functions

@@ -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
+}

+ 180 - 0
src/luks/clevis-luks-edit

@@ -0,0 +1,180 @@
+#!/bin/bash -e
+# vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:
+#
+# Copyright (c) 2020 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-luks-common-functions
+
+SUMMARY="Edit a binding from a clevis-bound slot in a LUKS device"
+
+usage() {
+    exec >&2
+    echo "Usage: clevis luks edit [-f] -d DEV -s SLT [-c CONFIG]"
+    echo
+    echo "${SUMMARY}"
+    echo
+    echo "'clevis luks edit' uses the text editor defined in the EDITOR environment variable."
+    echo "                   If EDITOR is not defined, it will attempt to use 'vi' as default editor."
+    echo
+    echo "  -d DEV     The LUKS device to edit clevis-bound pins"
+    echo
+    echo "  -s SLOT    The slot to use when editing the clevis binding"
+    echo
+    echo "  -f         Proceed with the edit operation even if the configuration is unchanged"
+    echo
+    echo "  -c CONFIG  The updated config to use"
+    echo
+    exit 1
+}
+
+on_exit() {
+    [ -d "${CLEVIS_EDIT_TMP}" ] && rm -rf "${CLEVIS_EDIT_TMP}"
+}
+
+validate_cfg() {
+    local json="${1}"
+    [ -z "${json}" ] && return 1
+    jose fmt --json="${json}" --object 2>/dev/null
+}
+
+edit_cfg() {
+    local cfg_file="${1}"
+    local editor="${EDITOR:-vi}"
+
+    if ! command -v "${editor}" >/dev/null; then
+        echo "Editor '${editor}' not found. " >&2
+        echo "Please define a valid text editor with the EDITOR environment variable." >&2
+        exit 1
+    fi
+
+    "${editor}" "${cfg_file}" || true
+    if ! validate_cfg "${cfg_file}"; then
+        local ans=
+        while true; do
+            read -r -p \
+              "Malformed configuration. Would you like to edit again? [ynYN] " \
+            ans
+            [ "${ans}" != "y" ] && [ "${ans}" != "Y" ] && return 1
+            break
+        done
+        edit_cfg "${cfg_file}"
+    fi
+    return 0
+}
+
+if [ "${#}" -eq 1 ] && [ "${1}" = "--summary" ]; then
+    echo "${SUMMARY}"
+    exit 0
+fi
+
+CFG=
+FRC=
+while getopts ":fd:s:c:" o; do
+    case "$o" in
+    d) DEV=${OPTARG};;
+    s) SLT=${OPTARG};;
+    c) CFG=${OPTARG};;
+    f) FRC=-f;;
+    *) usage;;
+    esac
+done
+
+if [ -z "${DEV}" ]; then
+    echo "Did not specify a device!" >&2
+    usage
+fi
+
+if [ -z "${SLT}" ]; then
+    echo "Did not specify a slot!" >&2
+    usage
+fi
+
+if ! binding="$(clevis luks list -d "${DEV}" -s "${SLT}" 2>/dev/null)" \
+                || [ -z "${binding}" ]; then
+    echo "Error retrieving current configuration from ${DEV}:${SLT}" >&2
+    exit 1
+fi
+
+pin="$(echo "${binding}" | cut -d' ' -f2)"
+cfg="$(echo "${binding}" | cut -d' ' -f3 | sed -e "s/'//g")"
+
+if ! pretty_cfg="$(printf '%s' "${cfg}" | jq --monochrome-output .)" \
+                   || [ -z "${pretty_cfg}" ]; then
+    echo "Error reading the configuration from ${DEV}:${SLT}" >&2
+    exit 1
+fi
+
+if ! CLEVIS_EDIT_TMP="$(mktemp -d)" || [ -z "${CLEVIS_EDIT_TMP}" ]; then
+    echo "Creating a temporary dir for editing binding failed" >&2
+    exit 1
+fi
+
+trap 'on_exit' EXIT
+
+if [ -z "${CFG}" ]; then
+    CFG_FILE="${CLEVIS_EDIT_TMP}/cfg"
+    echo "${pretty_cfg}" > "${CFG_FILE}"
+
+    edit_cfg "${CFG_FILE}" || exit 1
+
+    if ! new_cfg="$(jq . -S < "${CFG_FILE}")" || [ -z "${new_cfg}" ]; then
+        echo "Error reading the updated config for ${DEV}:${SLT}" >&2
+        exit 1
+    fi
+else
+    if ! validate_cfg "${CFG}"; then
+        echo "Invalid configuration given as parameter with -c" >&2
+        exit 1
+    fi
+    new_cfg="$(printf '%s' "${CFG}" | jq --sort-keys --monochrome-output .)"
+fi
+
+if [ "${new_cfg}" = "$(printf '%s' "${pretty_cfg}" \
+                       | jq --sort-keys --monochrome-output .)" ] \
+        && [ -z "${FRC}" ]; then
+    echo "No changes detected; exiting" >&2
+    exit 1
+fi
+
+if ! jcfg="$(jose fmt --json="${new_cfg}" --object --output=- 2>/dev/null)" \
+             || [ -z "${jcfg}" ]; then
+    echo "Error preparing the configuration for the binding update" >&2
+    exit 1
+fi
+
+if [ -z "${CFG}" ]; then
+    printf "Pin: %s\nNew config:\n%s\n" "${pin}" "${new_cfg}"
+    while true; do
+        read -r -p \
+          "Would you like to proceed with the updated configuration? [ynYN] " \
+        ans
+        [ "${ans}" != "y" ] && [ "${ans}" != "Y" ] && exit 0
+        break
+    done
+fi
+
+# Remove temporary directory.
+rm -rf "${CLEVIS_EDIT_TMP}"
+
+echo "Updating binding..."
+if ! clevis_luks_do_bind "${DEV}" "${SLT}" "" "${pin}" "${new_cfg}" \
+                         "-y" "overwrite" 2>/dev/null; then
+    echo "Unable to update binding in ${DEV}:${SLT}. Operation cancelled." >&2
+    exit 1
+fi
+echo "Binding edited successfully" >&2

+ 69 - 0
src/luks/clevis-luks-edit.1.adoc

@@ -0,0 +1,69 @@
+CLEVIS-LUKS-EDIT(1)
+===================
+:doctype: manpage
+
+
+== NAME
+
+clevis-luks-edit - Edit a binding from a clevis-bound slot in a LUKS device
+
+== SYNOPSIS
+
+*clevis luks edit* [-f] -d DEV -s SLT [-c CONFIG]
+
+== OVERVIEW
+
+The *clevis luks edit* command edits clevis bindings from a LUKS device.
+For example:
+
+    clevis luks edit -d /dev/sda1 -s 1
+
+== OPTIONS
+
+* *-d* _DEV_ :
+  The LUKS device to edit clevis-bound pins
+
+* *-s* _SLT_ :
+  The slot to use when editing the clevis binding
+
+* *-f* :
+  Proceed with the edit operation even if the config is unchanged
+
+* *-c* _CONFIG_ :
+  The updated config to use
+
+
+== EXAMPLES
+
+    clevis luks list -d /dev/sda1
+    1: tang '{"url":"addr"}'
+
+As we can see in the example above, */dev/sda1* has one slots bound, in this case, to a _tang_ pin.
+
+We can edit this binding by issuing the following command:
+
+    clevis luks edit -d /dev/sda1 -s 1
+
+This will open a text editor -- the one set in the $EDITOR environment variable, or _vi_, as a fallback -- with the current
+configuration of this binding to be edited. In this case, we should have the following:
+
+    {
+        "url": "addr"
+    }
+
+Once at the editor, we can edit the pin configuration. For _tang_, we could edit the _url_, for instance. After completing the change,
+save the file and exit. The updated configuration will be validated for JSON, and if there are no errors, you will be shown the
+updated configuration and prompted whether to proceed.
+
+By proceeding, the binding will be updated. There may be required to provide a valid LUKS passphrase for the device.
+
+In the second example, we will update the same device and slot, but we will be providing the updated configuration as well:
+
+    clevis luks edit -d /dev/sda1 -s 1 -c '{"url":"new-addr-here"}'
+
+In this case, the binding update will be done in non-interactive mode. Note that it may also be required to provide a LUKS
+passphrase for the device.
+
+== SEE ALSO
+
+link:clevis-luks-list.1.adoc[*clevis-luks-list*(1)],

+ 93 - 0
src/luks/clevis-luks-regen

@@ -0,0 +1,93 @@
+#!/bin/bash -e
+# vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:
+#
+# Copyright (c) 2020 Red Hat, Inc.
+# Author: Radovan Sroka <rsroka@redhat.com>
+# 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-luks-common-functions
+
+SUMMARY="Regenerate clevis binding"
+
+if [ "${1}" = "--summary" ]; then
+    echo "${SUMMARY}"
+    exit 0
+fi
+
+usage_and_exit () {
+    exec >&2
+    echo "Usage: clevis luks regen [-q] -d DEV -s SLOT"
+    echo
+    echo "${SUMMARY}"
+    echo
+    echo "  -d DEV  The LUKS device on which to perform rebinding"
+    echo
+    echo "  -s SLT  The LUKS slot to use"
+    echo
+    echo "  -q      Do not prompt for confirmation"
+    echo
+    exit "${1}"
+}
+
+QOPT=
+while getopts ":hqd:s:" o; do
+    case "${o}" in
+    d) DEV="${OPTARG}";;
+    h) usage_and_exit 0;;
+    s) SLT="${OPTARG}";;
+    q) QOPT="-q";;
+    *) usage_and_exit 1;;
+    esac
+done
+
+if [ -z "${DEV}" ]; then
+    echo "Did not specify a device!" >&2
+    exit 1
+fi
+
+if [ -z "${SLT}" ]; then
+    echo "Did not specify a slot!" >&2
+    exit 1
+fi
+
+# Get pin and configuration.
+if ! pin_cfg="$(clevis luks list -d "${DEV}" -s "${SLT}")" \
+                || [ -z "${pin_cfg}" ]; then
+    return 1
+fi
+
+pin="$(echo "${pin_cfg}" | cut -d' ' -f2)"
+cfg="$(echo "${pin_cfg}" | cut -d' ' -f3 | sed -e "s/'//g")"
+if [ -z "${pin}" ] || [ -z "${cfg}" ]; then
+    echo "Invalid pin or configuration" >&2
+    exit 1
+fi
+
+echo "Regenerating binding (device ${DEV}, slot ${SLT}):"
+echo "Pin: ${pin}, Config: '${cfg}'"
+
+if [ -z "${QOPT}" ]; then
+    read -r -p "Do you want to proceed? [ynYN] " ans
+    [ "${ans}" != "y" ] && [ "${ans}" != "Y" ] && exit 0
+fi
+
+if ! clevis_luks_do_bind "${DEV}" "${SLT}" "" "${pin}" "${cfg}" \
+                         "-y" "overwrite"; then
+    echo "Unable to regenerate binding in ${DEV}:${SLT}" >&2
+    exit 1
+fi
+echo "Binding regenerated successfully" >&2

+ 51 - 0
src/luks/clevis-luks-regen.1.adoc

@@ -0,0 +1,51 @@
+CLEVIS-LUKS-REGEN(1)
+=====================
+:doctype: manpage
+
+
+== NAME
+
+clevis-luks-regen - Regenerates a clevis binding
+
+== SYNOPSIS
+
+*clevis luks regen* [-q] -d DEV -s SLT
+
+== OVERVIEW
+
+The *clevis luks regen* command regenerates the clevis binding for a given slot in a LUKS device, using the same configuration of the
+existing binding. Its operation can be compared to performing *clevis luks unbind* and *clevis luks bind* for rebinding said slot and device.
+This is useful when rotating tang keys.
+
+== OPTIONS
+
+* *-d* _DEV_ :
+  The bound LUKS device
+
+* *-s* _SLT_ :
+  The slot or key slot number for rebinding. Note that it requires that such slot is currently bound by clevis.
+
+* *-q*:
+  Do not prompt for confirmation.
+
+== EXAMPLE
+
+    Let's start by using clevis luks list to see the current binding configuration in /dev/sda1:
+
+    # clevis luks list -d /dev/sda1
+    1: tang '{"url":"http://tang.server"}'
+    2: tpm2 '{"hash":"sha256","key":"ecc"}'
+
+    We see that slot 1 in /dev/sda1 has a tang binding with the following configuration:
+    '{"url":"http://tang.server"}'
+
+    Now let's do the rebinding of slot 1:
+    # clevis luks regen -d /dev/sda1 -s 1
+
+    After a successful operation, we will have the new binding using the same configuration that was already in place.
+
+== SEE ALSO
+
+link:clevis-luks-list.1.adoc[*clevis-luks-list*(1)]
+link:clevis-luks-bind.1.adoc[*clevis-luks-bind*(1)]
+link:clevis-luks-unbind.1.adoc[*clevis-luks-unbind*(1)]

+ 213 - 0
src/luks/clevis-luks-report

@@ -0,0 +1,213 @@
+#!/bin/bash -e
+# vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:
+#
+# Copyright (c) 2018, 2020 Red Hat, Inc.
+# Author: Radovan Sroka <rsroka@redhat.com>
+# 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-luks-common-functions
+
+SUMMARY="Report tang keys' rotations"
+
+if [ "${1}" = "--summary" ]; then
+    echo "${SUMMARY}"
+    exit 0
+fi
+
+report_compare() {
+    local adv_keys="${1}"
+    local mdata_keys="${2}"
+    [ -z "${adv_keys}" ] && return 1
+    [ -z "${mdata_keys}" ] && return 1
+
+    local thp keys
+    for thp in $(printf '%s' "${mdata_keys}" | jose jwk thp --input=-); do
+        if ! printf '%s' "${adv_keys}" | jose jwk thp --input=- \
+                                              --find "${thp}" >/dev/null; then
+            keys="$(printf '%s %s' "${keys}" "${thp}")"
+        fi
+    done
+    printf '%s' "${keys}"
+}
+
+report_tang() {
+    local content="${1}"
+    [ -z "${content}" ] && return 1
+
+    local url
+    if ! url="$(jose fmt --json="${content}" --get url --unquote=-)" \
+                || [ -z "${url}" ]; then
+        echo "Invalid tang metadata; URL not found" >&2
+        return 1
+    fi
+
+    local jws
+    if ! jws="$(curl -sfg "${url}/adv")"; then
+        echo "Unable to fetch advertisement (${url}/adv)" >&2
+        return 1
+    fi
+
+    local adv_keys
+    if ! adv_keys="$(jose fmt --json="${jws}" --object --get payload \
+                     --string --b64load --object --get keys \
+                     --array --unwind --output=-)"; then
+        echo "Advertisement is malformed" >&2
+        return 1
+    fi
+
+    # Check advertisement validity.
+    local ver
+    if ! ver="$(printf '%s' "${adv_keys}" | jose jwk use --input=- \
+                                                 --required \
+                                                 --use=verify \
+                                                 --output=-)"; then
+        echo "Unable to validate advertisement" >&2
+        return 1
+    fi
+    if ! printf '%s' "${ver}" | jose jws ver --input="${jws}" --key=- \
+                                     --all; then
+        echo "Advertisement is missing signatures" >&2
+        return 1
+    fi
+    local mdata_keys
+    if ! mdata_keys="$(jose fmt --json="${content}" --get adv --output=-)" \
+                       || [ -z "${mdata_keys}" ]; then
+        echo "Keys from clevis metadata not found" >&2
+        return 1
+    fi
+    report_compare "${adv_keys}" "${mdata_keys}"
+}
+
+report_sss() {
+    local content="${1}"
+    [ -z "${content}" ] && return 1
+
+    local jwe
+    for jwe in $(jose fmt --json="${content}" --get jwe --foreach=-); do
+        jwe="$(printf '%s' "${jwe}" | sed -e 's/"//g')"
+        report_decode "${jwe}"
+    done
+}
+
+report_decode() {
+    local data64="${1}"
+    [ -z "${data64}" ] && return 1
+
+    local data
+    if ! data="$(clevis_luks_decode_jwe "${data64}")" || [ -z "${data}" ]; then
+        echo "Unable to decode metadata" >&2
+        exit 1
+    fi
+
+    local pin
+    if ! pin="$(jose fmt --json="${data}" --get clevis --get pin --unquote=-)" \
+                || [ -z "${pin}" ]; then
+        echo "Pin not found in clevis metadata" >&2
+        exit 1
+    fi
+
+    local content
+    if ! content="$(jose fmt --json="${data}" --get clevis --get "${pin}" \
+                    --output=-)" || [ -z "${content}" ]; then
+        echo "Invalid pin metadata; no content found" >&2
+        return 1
+    fi
+
+    case "${pin}" in
+    tang)
+        report_tang "${content}"
+        ;;
+    sss)
+        report_sss "${content}"
+        ;;
+    esac
+}
+
+usage_and_exit () {
+    exec >&2
+    echo "Usage: clevis luks report [-q] [-r] -d DEV -s SLOT"
+    echo
+    echo "${SUMMARY}"
+    echo
+    echo "  -d DEV  The LUKS device to check for key rotations"
+    echo
+    echo "  -s SLT  The LUKS slot to use"
+    echo
+    echo "  -q      Quiet mode; do not prompt for using 'clevis luks regen'"
+    echo
+    echo "  -r      Regenerate binding with 'clevis luks regen -q -d DEV -s SLOT'"
+    echo
+    exit "${1}"
+}
+
+while getopts "hd:s:rq" o; do
+    case "${o}" in
+    d) DEV="${OPTARG}";;
+    h) usage_and_exit 0;;
+    r) ROPT="regen";;
+    s) SLT="${OPTARG}";;
+    q) QOPT="quiet";;
+    *) usage_and_exit 1;;
+    esac
+done
+
+if [ -z "${DEV}" ]; then
+    echo "Did not specify a device!" >&2
+    exit 1
+fi
+
+if [ -z "${SLT}" ]; then
+    echo "Did not specify a slot!" >&2
+    exit 1
+fi
+
+if ! data64="$(clevis_luks_read_slot "${DEV}" "${SLT}")" \
+               || [ -z "${data64}" ]; then
+    # Error message was already displayed by clevis_luks_read_slot(),
+    # at this point.
+    exit 1
+fi
+
+if ! keys="$(report_decode "${data64}")"; then
+    echo "Unable to verify whether there are rotated keys" >&2
+    exit 1
+fi
+
+# No rotated keys.
+[ -z "${keys}" ] && exit 0
+
+echo "The following keys are not in the current advertisement and were probably rotated:"
+for k in ${keys}; do
+    printf '  %s\n' "${k}"
+done
+
+
+if [ -z "${QOPT}" ] && [ -z "${ROPT}" ]; then
+    read -r -p "Do you want to regenerate the binding with \"clevis luks regen -q -d ${DEV} -s ${SLT}\"? [ynYN] " ans
+    if [ "${ans}" = "y" ] || [ "${ans}" = "Y" ]; then
+        ROPT="regen"
+    fi
+fi
+
+if [ "${ROPT}" = "regen" ]; then
+    if ! EXE="$(command -v clevis-luks-regen)" || [ -z "${EXE}" ]; then
+        echo "Unable to find clevis luks regen" >&2
+        exit 1
+    fi
+    exec "${EXE}" -q -d "${DEV}" -s "${SLT}"
+fi
+exit 1

+ 41 - 0
src/luks/clevis-luks-report.1.adoc

@@ -0,0 +1,41 @@
+CLEVIS-LUKS-REPORT(1)
+=====================
+:doctype: manpage
+
+
+== NAME
+
+clevis-luks-report - Reports whether a pin bound to a LUKS1 or LUKS2 volume has been rotated
+
+== SYNOPSIS
+
+*clevis luks report* -d DEV -s SLT
+
+== OVERVIEW
+
+The *clevis luks report* command checks a given slot of a LUKS device and reports whether the pin bound to it
+-- if any -- has been rotated.
+
+== OPTIONS
+
+* *-d* _DEV_ :
+  The bound LUKS device
+
+* *-s* _SLT_ :
+  The slot or key slot number for the pin to be verified
+
+* *-q* :
+  Quiet mode. If used, we will not prompt whether to regenerate the binding with *clevis luks regen*
+
+* *-r* :
+  Regenerates LUKS metadata with *clevis luks regen -q -d DEV -s SLT*
+
+== EXAMPLE
+
+    Check whether the pin bound to slot 1 in /dev/sda1 has been rotated:
+
+    # clevis luks report -d /dev/sda1 -s 1
+
+== SEE ALSO
+
+link:clevis-luks-regen.1.adoc[*clevis-luks-regen*(1)]

+ 68 - 0
src/luks/clevis-luks-unlock

@@ -0,0 +1,68 @@
+#!/bin/bash -e
+# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:
+#
+# Copyright (c) 2016 Red Hat, Inc.
+# 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="Unlocks a LUKS volume"
+
+function usage() {
+    exec >&2
+    echo
+    echo "Usage: clevis luks unlock -d DEV [-n NAME]"
+    echo
+    echo "$SUMMARY":
+    echo
+    echo "  -d DEV  The LUKS device on which to perform unlocking"
+    echo
+    echo "  -n NAME The name of the unlocked device node"
+    echo
+    exit 2
+}
+
+if [ $# -eq 1 ] && [ "$1" == "--summary" ]; then
+    echo "$SUMMARY"
+    exit 0
+fi
+
+while getopts ":d:n:" o; do
+    case "$o" in
+    d) DEV="$OPTARG";;
+    n) NAME="$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
+
+NAME="${NAME:-luks-"$(cryptsetup luksUUID "$DEV")"}"
+
+if ! pt=$(clevis_luks_unlock_device "${DEV}"); then
+    echo "${DEV} could not be opened." >&2
+    exit 1
+fi
+
+cryptsetup open -d- "${DEV}" "${NAME}" < <(echo -n "${pt}")

+ 0 - 130
src/luks/clevis-luks-unlock.in

@@ -1,130 +0,0 @@
-#!/bin/bash -e
-# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:
-#
-# Copyright (c) 2016 Red Hat, Inc.
-# 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="Unlocks a LUKS volume"
-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 unlock -d DEV [-n NAME]"
-    echo
-    echo "$SUMMARY":
-    echo
-    echo "  -d DEV  The LUKS device on which to perform unlocking"
-    echo
-    echo "  -n NAME The name of the unlocked device node"
-    echo
-    exit 2
-}
-
-if [ $# -eq 1 ] && [ "$1" == "--summary" ]; then
-    echo "$SUMMARY"
-    exit 0
-fi
-
-while getopts ":d:n:" o; do
-    case "$o" in
-    d) DEV="$OPTARG";;
-    n) NAME="$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 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
-NAME="${NAME:-luks-"$(cryptsetup luksUUID "$DEV")"}"
-
-luks1_decrypt() {
-    luksmeta load "$@" \
-        | clevis decrypt
-
-    local rc
-    for rc in "${PIPESTATUS[@]}"; do
-        [ $rc -eq 0 ] || return $rc
-    done
-    return 0
-}
-
-luks2_decrypt() {
-    # jose jwe fmt -c outputs extra \n, so clean it up
-    cryptsetup token export "$@" \
-        | jose fmt -j- -Og jwe -o- \
-        | jose jwe fmt -i- -c \
-        | tr -d '\n' \
-        | clevis decrypt
-
-    local rc
-    for rc in "${PIPESTATUS[@]}"; do
-        [ $rc -eq 0 ] || return $rc
-    done
-    return 0
-}
-
-if [ "$luks_type" == "luks1" ]; then
-    while read -r slot state uuid; do
-        [ "$state" == "active" ] || continue
-        [ "$uuid" == "$UUID" ] || continue
-
-        pt="$(luks1_decrypt -d $DEV -s $slot -u $UUID)" \
-            || continue
-        exec cryptsetup open -d- "$DEV" "$NAME" < <(
-            echo -n "$pt"
-        )
-    done < <(luksmeta show -d "$DEV")
-
-elif [ "$luks_type" == "luks2" ]; then
-    while read -r id; do
-        pt="$(luks2_decrypt --token-id "$id" "$DEV")" \
-            || continue
-        exec cryptsetup open -d- "$DEV" "$NAME" < <(
-            echo -n "$pt"
-        )
-    done < <(cryptsetup luksDump "$DEV" | sed -rn 's|^\s+([0-9]+): clevis|\1|p')
-fi
-
-echo "$DEV could not be opened." >&2
-exit 1

+ 2 - 3
src/luks/clevis-luks-unlockers.7.adoc

@@ -47,9 +47,8 @@ You can enable late boot unlocking by executing the following command:
 
     $ sudo systemctl enable clevis-luks-askpass.path
 
-After a reboot, Clevis will attempt to unlock all *_netdev* devices listed in
-*/etc/crypttab* when systemd prompts for their passwords. This implies that
-systemd support for *_netdev* is required.
+After a reboot, Clevis will attempt to unlock all devices listed in
+*/etc/crypttab* that have clevis bindings when systemd prompts for their passwords.
 
 == DESKTOP UNLOCKING
 

+ 13 - 6
src/luks/meson.build

@@ -21,9 +21,7 @@ clevis_luks_bind = configure_file(input: 'clevis-luks-bind.in',
 clevis_luks_unbind = configure_file(input: 'clevis-luks-unbind.in',
                output: 'clevis-luks-unbind',
                configuration: luksmeta_data)
-clevis_luks_unlock = configure_file(input: 'clevis-luks-unlock.in',
-               output: 'clevis-luks-unlock',
-               configuration: luksmeta_data)
+
 if libcryptsetup.found() and luksmeta.found() and pwmake.found()
   subdir('systemd')
   subdir('udisks2')
@@ -31,18 +29,27 @@ if libcryptsetup.found() and luksmeta.found() and pwmake.found()
   bins += clevis_luks_unbind
   mans += join_paths(meson.current_source_dir(), 'clevis-luks-unbind.1')
 
-  bins += clevis_luks_unlock
-  mans += join_paths(meson.current_source_dir(), 'clevis-luks-unlock.1')
-
   bins += clevis_luks_bind
   mans += join_paths(meson.current_source_dir(), 'clevis-luks-bind.1')
 
   mans += join_paths(meson.current_source_dir(), 'clevis-luks-unlockers.7')
 
   bins += join_paths(meson.current_source_dir(), 'clevis-luks-common-functions')
+
   bins += join_paths(meson.current_source_dir(), 'clevis-luks-list')
   mans += join_paths(meson.current_source_dir(), 'clevis-luks-list.1')
 
+  bins += join_paths(meson.current_source_dir(), 'clevis-luks-unlock')
+  mans += join_paths(meson.current_source_dir(), 'clevis-luks-unlock.1')
+
+  bins += join_paths(meson.current_source_dir(), 'clevis-luks-regen')
+  mans += join_paths(meson.current_source_dir(), 'clevis-luks-regen.1')
+
+  bins += join_paths(meson.current_source_dir(), 'clevis-luks-report')
+  mans += join_paths(meson.current_source_dir(), 'clevis-luks-report.1')
+
+  bins += join_paths(meson.current_source_dir(), 'clevis-luks-edit')
+  mans += join_paths(meson.current_source_dir(), 'clevis-luks-edit.1')
 else
   warning('Will not install LUKS support due to missing dependencies!')
 endif

+ 27 - 67
src/luks/systemd/clevis-luks-askpass

@@ -1,4 +1,5 @@
-#!/bin/bash -e
+#!/bin/bash
+set -eu
 # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:
 #
 # Copyright (c) 2016 Red Hat, Inc.
@@ -19,96 +20,55 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 
-UUID=cb6e8904-81ff-40da-a84a-07ab9ab5715e
+. clevis-luks-common-functions
 
-shopt -s nullglob
+# Make sure to exit cleanly if SIGTERM is received.
+trap 'echo "Exiting due to SIGTERM" && exit 0' TERM
 
+loop=
 path=/run/systemd/ask-password
 while getopts ":lp:" o; do
-    case "$o" in
+    case "${o}" in
     l) loop=true;;
-    p) path="$OPTARG";;
+    p) path="${OPTARG}";;
+    *) ;;
     esac
 done
 
-luks1_decrypt() {
-    luksmeta load "$@" \
-        | clevis decrypt
-
-    local rc
-    for rc in "${PIPESTATUS[@]}"; do
-        [ $rc -eq 0 ] || return $rc
-    done
-    return 0
-}
-
-luks2_jwe() {
-    # jose jwe fmt -c outputs extra \n, so clean it up
-    cryptsetup token export "$@" \
-        | jose fmt -j- -Og jwe -o- \
-        | jose jwe fmt -i- -c \
-        | tr -d '\n'
-
-    local rc
-    for rc in "${PIPESTATUS[@]}"; do
-        [ $rc -eq 0 ] || return $rc
-    done
-    return 0
-}
-
 while true; do
-    todo=0
+    for question in "${path}"/ask.*; do
+        # question will expand to itself, in case no files match, so we verify
+        # whether it actually exists, before proceeding.
+        [ ! -e "${question}" ] && continue
 
-    for question in "$path"/ask.*; do
-        metadata=false
-        unlocked=false
         d=
         s=
-
-        while read line; do
+        while read -r line; do
             case "$line" in
                 Id=cryptsetup:*) d="${line##Id=cryptsetup:}";;
                 Socket=*) s="${line##Socket=}";;
             esac
         done < "$question"
 
-        [ "$d" ] && [ "$s" ] || continue
-
-        if cryptsetup isLuks --type luks1 "$d"; then
-            # If the device is not initialized, sliently skip it.
-            luksmeta test -d "$d" || continue
+        [ -b "${d}" ] || continue
+        [ -S "${s}" ] || continue
 
-            while read -r slot state uuid; do
-                [ "$state" == "active" ] || continue
-                [ "$uuid" == "$UUID" ] || continue
-                metadata=true
-
-                if pt="$(luks1_decrypt -d "$d" -s "$slot" -u "$UUID")"; then
-                    echo -n "+$pt" | ncat -U -u --send-only "$s"
-                    unlocked=true
-                    break
-                fi
-            done < <(luksmeta show -d "$d")
-        elif cryptsetup isLuks --type luks2 "$d"; then
-            while read -r id; do
-                jwe="$(luks2_jwe --token-id "$id" "$d")" \
-                    || continue
-                metadata=true
+        if ! pt="$(clevis_luks_unlock_device "${d}")" || [ -z "${pt}" ]; then
+            continue
+        fi
 
-                if pt="$(echo -n "$jwe" | clevis decrypt)"; then
-                    echo -n "+$pt" | ncat -U -u --send-only "$s"
-                    unlocked=true
-                    break
-                fi
-            done < <(cryptsetup luksDump "$d" | sed -rn 's|^\s+([0-9]+): clevis|\1|p')
+        uuid="$(cryptsetup luksUUID "${d}")"
+        if ! printf '+%s' "${pt}" | ncat -U -u --send-only "${s}"; then
+            echo "Unable to unlock ${d} (UUID=${uuid}) with recovered passphrase" >&2
+            continue
         fi
 
-        [ "$metadata" == true ] || continue
-        [ "$unlocked" == true ] && continue
-        ((todo++))
+        echo "Unlocked ${d} (UUID=${uuid}) successfully" >&2
     done
 
-    if [ $todo -eq 0 ] || [ "$loop" != true ]; then
+    [ "${loop}" != true ] && break
+    # Checking for pending devices to be unlocked.
+    if remaining=$(clevis_devices_to_unlock) && [ -z "${remaining}" ]; then
         break;
     fi
 

+ 8 - 5
src/luks/systemd/clevis-luks-askpass.path

@@ -1,10 +1,13 @@
 [Unit]
-Description=Clevis systemd-ask-password Watcher
-Before=remote-fs-pre.target
-Wants=remote-fs-pre.target
+Description=Forward Password Requests to Clevis Directory Watch
+Documentation=man:clevis-luks-unlockers(7)
+DefaultDependencies=no
+Before=cryptsetup-pre.target
+Wants=cryptsetup-pre.target
 
 [Path]
-PathChanged=/run/systemd/ask-password
+DirectoryNotEmpty=/run/systemd/ask-password
+MakeDirectory=yes
 
 [Install]
-WantedBy=remote-fs.target
+WantedBy=cryptsetup.target

+ 4 - 4
src/luks/systemd/clevis-luks-askpass.service.in

@@ -1,8 +1,8 @@
 [Unit]
-Description=Clevis LUKS systemd-ask-password Responder
-Requires=network-online.target
-After=network-online.target
+Description=Forward Password Requests to Clevis
+Documentation=man:clevis-luks-unlockers(7)
+DefaultDependencies=no
 
 [Service]
-Type=oneshot
+Type=simple
 ExecStart=@libexecdir@/clevis-luks-askpass -l

+ 12 - 3
src/luks/systemd/dracut/clevis-pin-tang/module-setup.sh.in

@@ -23,12 +23,21 @@ depends() {
     return 0
 }
 
-cmdline() {
-    echo "rd.neednet=1"
+have_tang_bindings() {
+    . clevis-luks-common-functions
+    local dev
+    for dev in $(clevis_devices_to_unlock "list-open-devices"); do
+        if clevis luks list -d "${dev}" | grep -q tang; then
+            return 0
+        fi
+    done
+    return 1
 }
 
 install() {
-    cmdline > "${initdir}/etc/cmdline.d/99clevis-pin-tang.conf"
+    if [ "${hostonly_cmdline}" = "yes" ] && have_tang_bindings; then
+        echo "rd.neednet=1" > "${initdir}/etc/cmdline.d/99clevis-pin-tang.conf"
+    fi
 
     inst_multiple \
 	clevis-decrypt-tang \

+ 13 - 3
src/luks/systemd/dracut/clevis/module-setup.sh.in

@@ -24,13 +24,23 @@ depends() {
 }
 
 install() {
-    inst_hook initqueue/online 60 "$moddir/clevis-hook.sh"
-    inst_hook initqueue/settled 60 "$moddir/clevis-hook.sh"
+    if dracut_module_included "systemd"; then
+        inst_multiple \
+            $systemdsystemunitdir/clevis-luks-askpass.service \
+            $systemdsystemunitdir/clevis-luks-askpass.path
+        systemctl -q --root "$initdir" add-wants cryptsetup.target clevis-luks-askpass.path
+    else
+        inst_hook initqueue/online 60 "$moddir/clevis-hook.sh"
+        inst_hook initqueue/settled 60 "$moddir/clevis-hook.sh"
+    fi
 
     inst_multiple \
-	/etc/services \
+        /etc/services \
         @libexecdir@/clevis-luks-askpass \
+        clevis-luks-common-functions \
+        grep sed cut \
         clevis-decrypt \
+        clevis-luks-list \
         cryptsetup \
         luksmeta \
         clevis \

+ 113 - 0
src/luks/tests/assume-yes

@@ -0,0 +1,113 @@
+#!/bin/bash -ex
+# vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:
+#
+# Copyright (c) 2020 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/>.
+
+TEST=$(basename "${0}")
+. tests-common-functions
+
+. clevis-luks-common-functions
+
+on_exit() {
+    [ ! -d "${TMP}" ] && return 0
+    tang_stop "${TMP}"
+    rm -rf "${TMP}"
+}
+
+trap 'on_exit' EXIT
+trap 'on_exit' ERR
+
+TMP="$(mktemp -d)"
+
+port=$(get_random_port)
+tang_run "${TMP}" "${port}" &
+tang_wait_until_ready "${port}"
+
+url="http://${TANG_HOST}:${port}"
+cfg=$(printf '{"url":"%s"}' "$url")
+
+test_tang() {
+    local url="${1}"
+    local cfg data pt
+    cfg=$(printf '{"url":"%s"}' "$url")
+
+    for data in "foo" "bar" "foo bar" "some-password-here"; do
+        if ! pt="$(echo "${data}" | clevis encrypt tang "${cfg}" -y \