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 \
+                   | clevis decrypt)"; then
+            error "${TEST}: tang - encrypt should succeed."
+        fi
+        if ["${pt}" != "${data}" ]; then
+            error "${TEST}: tang - pt(${pt}) != data("${data}")."
+        fi
+    done
+}
+
+test_sss() {
+    local url="${1}"
+    local sss1 sss2 data pt
+    sss1=$(printf '{"t":1, "pins": {"tang": [{"url": "%s"}]}}' "${url}")
+    sss2=$(printf '{"t":2, "pins": {"tang": [{"url": "%s"}, {"url": "%s"}]}}' \
+           "${url}" "${url}")
+
+    for data in "foo" "bar" "foo bar" "some-password-here"; do
+        if ! pt="$(echo "${data}" | clevis encrypt sss "${sss1}" -y \
+                   | clevis decrypt)"; then
+            error "${TEST}: sss1 - encrypt should succeed."
+        fi
+        if ["${pt}" != "${data}" ]; then
+            error "${TEST}: sss1 - pt(${pt}) != data("${data}")."
+        fi
+
+        if ! pt="$(echo "${data}" | clevis encrypt sss "${sss2}" -y \
+                   | clevis decrypt)"; then
+            error "${TEST}: sss2 - encrypt should succeed."
+        fi
+        if ["${pt}" != "${data}" ]; then
+            error "${TEST}: sss2 - pt(${pt}) != data("${data}")."
+        fi
+    done
+}
+
+test_tang "${url}"
+test_sss "${url}"
+
+# LUKS1.
+DEV="${TMP}/luks1-device"
+new_device "luks1" "${DEV}"
+
+# tang.
+if ! clevis luks bind -y -d "${DEV}" tang "${cfg}" <<< "${DEFAULT_PASS}"; then
+    error "${TEST}: Bind should have succeeded (tang - $DEV)."
+fi
+
+if ! clevis_luks_unlock_device "${DEV}"; then
+    error "${TEST}: we were unable to unlock ${DEV} (tang)."
+fi
+
+# sss.
+new_device "luks1" "${DEV}"
+
+sss=$(printf '{"t":2, "pins": {"tang": [{"url": "%s"}, {"url": "%s"}]}}' \
+             "${url}" "${url}")
+
+if ! clevis luks bind -y -d "${DEV}" sss "${sss}" <<< "${DEFAULT_PASS}"; then
+    error "${TEST}: Bind should have succeeded (sss - $DEV)."
+fi
+
+if ! clevis_luks_unlock_device "${DEV}"; then
+    error "${TEST}: we were unable to unlock ${DEV} (sss)."
+fi

+ 69 - 0
src/luks/tests/assume-yes-luks2

@@ -0,0 +1,69 @@
+#!/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")
+
+# LUKS2.
+DEV="${TMP}/luks2-device"
+new_device "luks2" "${DEV}"
+
+# tang.
+if ! clevis luks bind -y -d "${DEV}" tang "${cfg}" <<< "${DEFAULT_PASS}"; then
+    error "${TEST}: Bind should have succeeded (tang - $DEV)."
+fi
+
+if ! clevis_luks_unlock_device "${DEV}"; then
+    error "${TEST}: we were unable to unlock ${DEV} (tang)."
+fi
+
+# sss.
+new_device "luks2" "${DEV}"
+
+sss=$(printf '{"t":2, "pins": {"tang": [{"url": "%s"}, {"url": "%s"}]}}' \
+             "${url}" "${url}")
+
+if ! clevis luks bind -y -d "${DEV}" sss "${sss}" <<< "${DEFAULT_PASS}"; then
+    error "${TEST}: Bind should have succeeded (sss - $DEV)."
+fi
+
+if ! clevis_luks_unlock_device "${DEV}"; then
+    error "${TEST}: we were unable to unlock ${DEV} (sss)."
+fi
+

+ 84 - 0
src/luks/tests/backup-restore-luks1

@@ -0,0 +1,84 @@
+#!/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}" ] && rm -rf "${TMP}"
+}
+
+trap 'on_exit' EXIT
+
+TMP="$(mktemp -d)"
+
+ADV="${TMP}/adv.jws"
+create_tang_adv "${ADV}"
+CFG="$(printf '{"url":"foobar","adv":"%s"}' "$ADV")"
+
+# LUKS1.
+DEV="${TMP}/luks1-device"
+new_device "luks1" "${DEV}"
+
+for slt in 6 2 3; do
+    if ! clevis luks bind -f -d "${DEV}" -s "${slt}" tang "${CFG}" <<< "${DEFAULT_PASS}"; then
+        error "${TEST}: [slot: $slt] Binding is expected to succeed when given a correct (${DEFAULT_PASS}) password." >&2
+    fi
+done
+
+
+# Backup device.
+if ! clevis_luks_backup_dev "${DEV}" "${TMP}"; then
+    error "${TEST}: problem performing device backup"
+fi
+
+# Save the original device for conference later.
+BKPDEV="${TMP}"/device-for-conference
+cp "${DEV}" "${BKPDEV}"
+
+# Recreate device
+new_device "luks1" "${DEV}"
+
+used_slots=$(clevis_luks_used_slots "${DEV}")
+if [ "${used_slots}" -ne 0 ]; then
+    error "${TEST}: only used slot shold be 0 ($used_slots)"
+fi
+
+if compare_luks_header "${DEV}" "${BKPDEV}" "${TMP}"; then
+    error "${TEST}: LUKS headers should not match"
+fi
+
+if compare_luks1_metadata "${DEV}" "${BKPDEV}"; then
+    error "${TEST}: LUKS metadata should not match"
+fi
+
+# Restore from backup.
+if ! clevis_luks_restore_dev "${TMP}"; then
+    error "${TEST}: problem performing device restore"
+fi
+
+if ! compare_luks_header "${DEV}" "${BKPDEV}" "${TMP}"; then
+    error "${TEST}: LUKS headers should match"
+fi
+
+if ! compare_luks1_metadata "${DEV}" "${BKPDEV}"; then
+    error "${TEST}: metadata should match"
+fi

+ 84 - 0
src/luks/tests/backup-restore-luks2

@@ -0,0 +1,84 @@
+#!/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}" ] && rm -rf "${TMP}"
+}
+
+trap 'on_exit' EXIT
+
+TMP="$(mktemp -d)"
+
+ADV="${TMP}/adv.jws"
+create_tang_adv "${ADV}"
+CFG="$(printf '{"url":"foobar","adv":"%s"}' "$ADV")"
+
+# LUKS1.
+DEV="${TMP}/luks2-device"
+new_device "luks2" "${DEV}"
+
+for slt in 6 2 3; do
+    if ! clevis luks bind -f -d "${DEV}" -s "${slt}" tang "${CFG}" <<< "${DEFAULT_PASS}"; then
+        error "${TEST}: [slot: $slt] Binding is expected to succeed when given a correct (${DEFAULT_PASS}) password." >&2
+    fi
+done
+
+
+# Backup device.
+if ! clevis_luks_backup_dev "${DEV}" "${TMP}"; then
+    error "${TEST}: problem performing device backup"
+fi
+
+# Save the original device for conference later.
+BKPDEV="${TMP}"/device-for-conference
+cp "${DEV}" "${BKPDEV}"
+
+# Recreate device
+new_device "luks2" "${DEV}"
+
+used_slots=$(clevis_luks_used_slots "${DEV}")
+if [ "${used_slots}" -ne 0 ]; then
+    error "${TEST}: only used slot shold be 0 ($used_slots)"
+fi
+
+if compare_luks_header "${DEV}" "${BKPDEV}" "${TMP}"; then
+    error "${TEST}: LUKS headers should not match"
+fi
+
+if compare_luks2_metadata "${DEV}" "${BKPDEV}"; then
+    error "${TEST}: LUKS metadata should not match"
+fi
+
+# Restore from backup.
+if ! clevis_luks_restore_dev "${TMP}"; then
+    error "${TEST}: problem performing device restore"
+fi
+
+if ! compare_luks_header "${DEV}" "${BKPDEV}" "${TMP}"; then
+    error "${TEST}: LUKS headers should match"
+fi
+
+if ! compare_luks2_metadata "${DEV}" "${BKPDEV}"; then
+    error "${TEST}: metadata should match"
+fi

+ 40 - 0
src/luks/tests/bad-sss

@@ -0,0 +1,40 @@
+#!/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
+
+on_exit() {
+    [ -d "${TMP}" ] && rm -rf "${TMP}"
+}
+
+trap 'on_exit' EXIT
+
+TMP="$(mktemp -d)"
+
+CFG='{"t":1, "pins":{"tang":[{"url":"foo bar"}]}}'
+
+# LUKS1.
+DEV="${TMP}/luks1-device"
+new_device "luks1" "${DEV}"
+
+if clevis luks bind -f -d "${DEV}" sss "${CFG}" <<< "${DEFAULT_PASS}"; then
+    error "${TEST}: Binding is not expected to succeed when given a bad sss config." >&2
+fi

+ 2 - 1
src/luks/tests/bind-luks2

@@ -41,8 +41,9 @@ CFG="$(printf '{"url":"foobar","adv":"%s"}' "$ADV")"
 # LUKS2.
 
 DEV="${TMP}/luks2-device"
+TOKEN_ID=5
 new_device "luks2" "${DEV}"
 
-if ! clevis luks bind -d "${DEV}" tang "${CFG}" <<< "${DEFAULT_PASS}"; then
+if ! clevis luks bind -d "${DEV}" -t "$TOKEN_ID" tang "${CFG}" <<< "${DEFAULT_PASS}"; then
     error "${TEST}: Binding is expected to succeed when given a correct (${DEFAULT_PASS}) password." >&2
 fi

+ 118 - 0
src/luks/tests/edit-tang-luks1

@@ -0,0 +1,118 @@
+#!/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() {
+    local d
+    for d in "${TMP}" "${TMP2}"; do
+        [ ! -d "${d}" ] && continue
+        tang_stop "${d}"
+        rm -rf "${d}"
+    done
+}
+
+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}")
+
+# LUKS1.
+DEV="${TMP}/luks1-device"
+new_device "luks1" "${DEV}"
+
+if ! clevis luks bind -y -d "${DEV}" tang "${cfg}" <<< "${DEFAULT_PASS}"; then
+    error "${TEST}: Bind should have succeeded."
+fi
+
+# Now let's try to change the config but using the same one we already have.
+if clevis luks edit -d "${DEV}" -s 1 -c "${cfg}"; then
+    error "${TEST}: edit should have failed because the config is the same."
+fi
+
+# And now, just a broken config.
+new_cfg=$(printf '{"url&:"%s"}' "${url}")
+if clevis luks edit -d "${DEV}" -s 1 -c "${new_cfg}"; then
+    error "${TEST}: edit should have failed because of invalid JSON"
+fi
+
+# Now let's have another tang instance running and change the config to use
+# the new one.
+port2=$(get_random_port)
+TMP2="$(mktemp -d)"
+tang_run "${TMP2}" "${port2}" &
+tang_wait_until_ready "${port2}"
+new_url="http://${TANG_HOST}:${port2}"
+new_cfg=$(printf '{"url":"%s"}' "${new_url}")
+
+if ! clevis luks edit -d "${DEV}" -s 1 -c "${new_cfg}"; then
+    error "${TEST}: edit should have succeeded."
+fi
+
+# Now we test an invalid server.
+new_cfg='{"url":"localhost:1"}'
+
+if clevis luks edit -d "${DEV}" -s 1 -c "${new_cfg}"; then
+    error "${TEST}: edit should not have succeeded with a wrong server."
+fi
+
+# Make sure we can still unlock the device.
+if ! clevis_luks_unlock_device "${DEV}" >/dev/null; then
+    error "${TEST}: we should have been able to unlock the device"
+fi
+
+# And now let's use sss and start with a single tang server, then add a second
+# one.
+new_device "luks1" "${DEV}"
+cfg=$(printf '{"t":1,"pins":{"tang":[{"url":"%s"}]}}' "${url}")
+if ! clevis luks bind -y -d "${DEV}" sss "${cfg}" <<< "${DEFAULT_PASS}"; then
+    error "${TEST}: Bind should have succeeded."
+fi
+new_cfg=$(printf '{"t":1,"pins":{"tang":[{"url":"%s"},{"url":"%s"}]}}' \
+          "${url}" "${new_url}")
+
+if ! clevis luks edit -d "${DEV}" -s 1 -c "${new_cfg}"; then
+    error "${TEST}: edit should have succeeded and added a new tang server"
+fi
+
+# Now let's change the threshold to 2.
+new_cfg=$(printf '{"t":2,"pins":{"tang":[{"url":"%s"},{"url":"%s"}]}}' \
+          "${url}" "${new_url}")
+
+if ! clevis luks edit -d "${DEV}" -s 1 -c "${new_cfg}"; then
+    error "${TEST}: edit should have succeeded and added a new tang server"
+fi
+
+# And finally, let's try a broken config, with a wrong threshold.
+new_cfg=$(printf '{"t":3,"pins":{"tang":[{"url":"%s"},{"url":"%s"}]}}' \
+          "${url}" "${new_url}")
+if clevis luks edit -d "${DEV}" -s 1 -c "${new_cfg}"; then
+    error "${TEST}: edit should have failed because threshold > number of servers"
+fi

+ 118 - 0
src/luks/tests/edit-tang-luks2

@@ -0,0 +1,118 @@
+#!/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() {
+    local d
+    for d in "${TMP}" "${TMP2}"; do
+        [ ! -d "${d}" ] && continue
+        tang_stop "${d}"
+        rm -rf "${d}"
+    done
+}
+
+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}")
+
+# LUKS2.
+DEV="${TMP}/luks2-device"
+new_device "luks2" "${DEV}"
+
+if ! clevis luks bind -y -d "${DEV}" tang "${cfg}" <<< "${DEFAULT_PASS}"; then
+    error "${TEST}: Bind should have succeeded."
+fi
+
+# Now let's try to change the config but using the same one we already have.
+if clevis luks edit -d "${DEV}" -s 1 -c "${cfg}"; then
+    error "${TEST}: edit should have failed because the config is the same."
+fi
+
+# And now, just a broken config.
+new_cfg=$(printf '{"url&:"%s"}' "${url}")
+if clevis luks edit -d "${DEV}" -s 1 -c "${new_cfg}"; then
+    error "${TEST}: edit should have failed because of invalid JSON"
+fi
+
+# Now let's have another tang instance running and change the config to use
+# the new one.
+port2=$(get_random_port)
+TMP2="$(mktemp -d)"
+tang_run "${TMP2}" "${port2}" &
+tang_wait_until_ready "${port2}"
+new_url="http://${TANG_HOST}:${port2}"
+new_cfg=$(printf '{"url":"%s"}' "${new_url}")
+
+if ! clevis luks edit -d "${DEV}" -s 1 -c "${new_cfg}"; then
+    error "${TEST}: edit should have succeeded."
+fi
+
+# Now we test an invalid server.
+new_cfg='{"url":"localhost:1"}'
+
+if clevis luks edit -d "${DEV}" -s 1 -c "${new_cfg}"; then
+    error "${TEST}: edit should not have succeeded with a wrong server."
+fi
+
+# Make sure we can still unlock the device.
+if ! clevis_luks_unlock_device "${DEV}" >/dev/null; then
+    error "${TEST}: we should have been able to unlock the device"
+fi
+
+# And now let's use sss and start with a single tang server, then add a second
+# one.
+new_device "luks2" "${DEV}"
+cfg=$(printf '{"t":1,"pins":{"tang":[{"url":"%s"}]}}' "${url}")
+if ! clevis luks bind -y -d "${DEV}" sss "${cfg}" <<< "${DEFAULT_PASS}"; then
+    error "${TEST}: Bind should have succeeded."
+fi
+new_cfg=$(printf '{"t":1,"pins":{"tang":[{"url":"%s"},{"url":"%s"}]}}' \
+          "${url}" "${new_url}")
+
+if ! clevis luks edit -d "${DEV}" -s 1 -c "${new_cfg}"; then
+    error "${TEST}: edit should have succeeded and added a new tang server"
+fi
+
+# Now let's change the threshold to 2.
+new_cfg=$(printf '{"t":2,"pins":{"tang":[{"url":"%s"},{"url":"%s"}]}}' \
+          "${url}" "${new_url}")
+
+if ! clevis luks edit -d "${DEV}" -s 1 -c "${new_cfg}"; then
+    error "${TEST}: edit should have succeeded and added a new tang server"
+fi
+
+# And finally, let's try a broken config, with a wrong threshold.
+new_cfg=$(printf '{"t":3,"pins":{"tang":[{"url":"%s"},{"url":"%s"}]}}' \
+          "${url}" "${new_url}")
+if clevis luks edit -d "${DEV}" -s 1 -c "${new_cfg}"; then
+    error "${TEST}: edit should have failed because threshold > number of servers"
+fi

+ 67 - 0
src/luks/tests/meson.build

@@ -1,6 +1,39 @@
 # We use jq for comparing the pin config in the clevis luks list tests.
 jq = find_program('jq', required: false)
 
+# We use systemd-socket-activate for running test tang servers.
+actv = find_program(
+  'systemd-socket-activate',
+  'systemd-activate',
+  join_paths('/', 'usr', 'lib', 'systemd', 'systemd-activate'),
+  required: false
+)
+
+kgen = find_program(
+  join_paths(libexecdir, 'tangd-keygen'),
+  join_paths(get_option('prefix'), get_option('libdir'), 'tangd-keygen'),
+  join_paths(get_option('prefix'), get_option('libexecdir'), 'tangd-keygen'),
+  join_paths('/', 'usr', get_option('libdir'), 'tangd-keygen'),
+  join_paths('/', 'usr', get_option('libexecdir'), 'tangd-keygen'),
+  required: false
+)
+updt = find_program(
+  join_paths(libexecdir, 'tangd-update'),
+  join_paths(get_option('prefix'), get_option('libdir'), 'tangd-update'),
+  join_paths(get_option('prefix'), get_option('libexecdir'), 'tangd-update'),
+  join_paths('/', 'usr', get_option('libdir'), 'tangd-update'),
+  join_paths('/', 'usr', get_option('libexecdir'), 'tangd-update'),
+  required: false
+)
+tang = find_program(
+  join_paths(libexecdir, 'tangd'),
+  join_paths(get_option('prefix'), get_option('libdir'), 'tangd'),
+  join_paths(get_option('prefix'), get_option('libexecdir'), 'tangd'),
+  join_paths('/', 'usr', get_option('libdir'), 'tangd'),
+  join_paths('/', 'usr', get_option('libexecdir'), 'tangd'),
+  required: false
+)
+
 common_functions = configure_file(input: 'tests-common-functions.in',
   output: 'tests-common-functions',
   configuration: luksmeta_data,
@@ -24,6 +57,15 @@ env.prepend('PATH',
   separator: ':'
 )
 
+has_tang = false
+if actv.found() and kgen.found() and updt.found() and tang.found()
+  has_tang = true
+  env.set('SD_ACTIVATE', actv.path())
+  env.set('TANGD_KEYGEN', kgen.path())
+  env.set('TANGD_UPDATE', updt.path())
+  env.set('TANGD', tang.path())
+endif
+
 test('bind-wrong-pass-luks1', find_program('bind-wrong-pass-luks1'), env: env)
 test('bind-luks1', find_program('bind-luks1'), env: env)
 test('unbind-unbound-slot-luks1', find_program('unbind-unbound-slot-luks1'), env: env)
@@ -33,6 +75,7 @@ test('bind-pass-with-newline', find_program('bind-pass-with-newline-luks1'), env
 test('bind-pass-with-newline-keyfile', find_program('bind-pass-with-newline-keyfile-luks1'), env: env)
 # Bug #70.
 test('bind-already-used-luksmeta-slot', find_program('bind-already-used-luksmeta-slot'), env: env, timeout: 60)
+test('bad-sss', find_program('bad-sss'), env: env)
 
 if jq.found()
   test('list-recursive-luks1', find_program('list-recursive-luks1'), env: env)
@@ -42,6 +85,18 @@ else
   warning('Will not run "clevis luks list" tests due to missing jq dependency')
 endif
 
+if has_tang
+  test('unlock-tang-luks1', find_program('unlock-tang-luks1'), env: env, timeout: 90)
+  test('assume-yes', find_program('assume-yes'), env: env, timeout: 60)
+  test('regen-inplace-luks1', find_program('regen-inplace-luks1'), env: env, timeout: 90)
+  test('regen-not-inplace-luks1', find_program('regen-not-inplace-luks1'), env: env, timeout: 90)
+  test('report-tang-luks1', find_program('report-tang-luks1'), env: env, timeout: 90)
+  test('report-sss-luks1', find_program('report-sss-luks1'), env: env, timeout: 90)
+  test('edit-tang-luks1', find_program('edit-tang-luks1'), env: env, timeout: 150)
+endif
+
+test('backup-restore-luks1', find_program('backup-restore-luks1'), env: env, timeout: 60)
+
 # LUKS2 tests go here, and they get included if we get support for it, based
 # on the cryptsetup version.
 # Binding LUKS2 takes longer, so timeout is increased for a few tests.
@@ -56,4 +111,16 @@ if luksmeta_data.get('OLD_CRYPTSETUP') == '0'
     test('list-tang-luks2', find_program('list-tang-luks2'), env: env, timeout: 60)
     test('list-sss-tang-luks2', find_program('list-sss-tang-luks2'), env: env, timeout: 60)
   endif
+
+  if has_tang
+    test('unlock-tang-luks2', find_program('unlock-tang-luks2'), env: env, timeout: 120)
+    test('assume-yes-luks2', find_program('assume-yes-luks2'), env: env, timeout: 90)
+    test('regen-inplace-luks2', find_program('regen-inplace-luks2'), env: env, timeout: 120)
+    test('regen-not-inplace-luks2', find_program('regen-not-inplace-luks2'), env: env, timeout: 120)
+    test('report-tang-luks2', find_program('report-tang-luks2'), env: env, timeout: 120)
+    test('report-sss-luks2', find_program('report-sss-luks2'), env: env, timeout: 120)
+    test('edit-tang-luks2', find_program('edit-tang-luks2'), env: env, timeout: 210)
+  endif
+
+test('backup-restore-luks2', find_program('backup-restore-luks2'), env: env, timeout: 120)
 endif

+ 76 - 0
src/luks/tests/regen-inplace-luks1

@@ -0,0 +1,76 @@
+#!/bin/bash -x
+# 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="${0}"
+. tests-common-functions
+. clevis-luks-common-functions
+
+function on_exit() {
+    [ -d "${TMP}" ] || return 0
+    tang_stop "${TMP}"
+    rm -rf "${TMP}"
+}
+
+trap 'on_exit' EXIT
+
+TMP=$(mktemp -d)
+
+port=$(get_random_port)
+tang_run "${TMP}" "${port}" &
+tang_wait_until_ready "${port}"
+
+url="http://${TANG_HOST}:${port}"
+adv="${TMP}/adv"
+tang_get_adv "${port}" "${adv}"
+
+cfg=$(printf '{"url":"%s","adv":"%s"}' "$url" "$adv")
+
+# LUKS1.
+DEV="${TMP}/luks1-device"
+new_device "luks1" "${DEV}"
+
+if ! clevis luks bind -f -d "${DEV}" tang "${cfg}" <<< "${DEFAULT_PASS}"; then
+    error "${TEST}: Bind should have succeeded."
+fi
+
+# Now let's remove the initial passphrase.
+if ! cryptsetup luksRemoveKey --batch-mode "${DEV}" <<< "${DEFAULT_PASS}"; then
+    error "${TEST}: error removing the default password from ${DEV}."
+fi
+
+# Making sure we have a single slot enabled.
+enabled=$(clevis_luks_used_slots "${DEV}" | wc -l)
+if [ "${enabled}" -ne 1 ]; then
+    error "${TEST}: we should have only one slot enabled (${enabled})."
+fi
+
+old_key=$(clevis_luks_unlock_device_by_slot "${DEV}" "${SLT}")
+
+# Now let's try regen.
+SLT=1
+if ! clevis luks regen -q -d "${DEV}" -s "${SLT}"; then
+    error "${TEST}: clevis luks regen failed"
+fi
+
+new_key=$(clevis_luks_unlock_device_by_slot "${DEV}" "${SLT}")
+
+if [ "${old_key}" = "${new_key}" ]; then
+    error "${TEST}: the passphrases should be different"
+fi

+ 76 - 0
src/luks/tests/regen-inplace-luks2

@@ -0,0 +1,76 @@
+#!/bin/bash -x
+# 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="${0}"
+. tests-common-functions
+. clevis-luks-common-functions
+
+function on_exit() {
+    [ -d "${TMP}" ] || return 0
+    tang_stop "${TMP}"
+    rm -rf "${TMP}"
+}
+
+trap 'on_exit' EXIT
+
+TMP=$(mktemp -d)
+
+port=$(get_random_port)
+tang_run "${TMP}" "${port}" &
+tang_wait_until_ready "${port}"
+
+url="http://${TANG_HOST}:${port}"
+adv="${TMP}/adv"
+tang_get_adv "${port}" "${adv}"
+
+cfg=$(printf '{"url":"%s","adv":"%s"}' "$url" "$adv")
+
+# LUKS2.
+DEV="${TMP}/luks2-device"
+new_device "luks2" "${DEV}"
+
+if ! clevis luks bind -f -d "${DEV}" tang "${cfg}" <<< "${DEFAULT_PASS}"; then
+    error "${TEST}: Bind should have succeeded."
+fi
+
+# Now let's remove the initial passphrase.
+if ! cryptsetup luksRemoveKey --batch-mode "${DEV}" <<< "${DEFAULT_PASS}"; then
+    error "${TEST}: error removing the default password from ${DEV}."
+fi
+
+# Making sure we have a single slot enabled.
+enabled=$(clevis_luks_used_slots "${DEV}" | wc -l)
+if [ "${enabled}" -ne 1 ]; then
+    error "${TEST}: we should have only one slot enabled (${enabled})."
+fi
+
+old_key=$(clevis_luks_unlock_device_by_slot "${DEV}" "${SLT}")
+
+# Now let's try regen.
+SLT=1
+if ! clevis luks regen -q -d "${DEV}" -s "${SLT}"; then
+    error "${TEST}: clevis luks regen failed"
+fi
+
+new_key=$(clevis_luks_unlock_device_by_slot "${DEV}" "${SLT}")
+
+if [ "${old_key}" = "${new_key}" ]; then
+    error "${TEST}: the passphrases should be different"
+fi

+ 79 - 0
src/luks/tests/regen-not-inplace-luks1

@@ -0,0 +1,79 @@
+#!/bin/bash -x
+# 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="${0}"
+. tests-common-functions
+. clevis-luks-common-functions
+
+function on_exit() {
+    [ -d "${TMP}" ] || return 0
+    tang_stop "${TMP}"
+    rm -rf "${TMP}"
+}
+
+trap 'on_exit' EXIT
+
+export TMP=$(mktemp -d)
+
+port=$(get_random_port)
+tang_run "${TMP}" "${port}" &
+tang_wait_until_ready "${port}"
+
+url="http://${TANG_HOST}:${port}"
+adv="${TMP}/adv"
+tang_get_adv "${port}" "${adv}"
+
+cfg=$(printf '{"url":"%s","adv":"%s"}' "$url" "$adv")
+
+# LUKS1.
+DEV="${TMP}/luks1-device"
+new_device "luks1" "${DEV}"
+
+SLT=1
+if ! clevis luks bind -f -s "${SLT}" -d "${DEV}" tang "${cfg}" <<< "${DEFAULT_PASS}"; then
+    error "${TEST}: Bind should have succeeded."
+fi
+
+# Now let's rotate the keys in the server and remove the old ones, so that we
+# will be unable to unlock the volume using clevis and will have to provide
+# manually a password for clevis luks regen.
+tang_new_keys "${TMP}" "rotate-keys"
+tang_remove_rotated_keys "${TMP}"
+
+# Making sure we have two slots enabled.
+enabled=$(clevis_luks_used_slots "${DEV}" | wc -l)
+if [ "${enabled}" -ne 2 ]; then
+    error "${TEST}: we should have two slots enabled (${enabled})."
+fi
+
+# Make sure we cannot unlock the device.
+if clevis_luks_unlock_device_by_slot "${DEV}"; then
+    error "${TEST}: we should NOT be able to unlock ${DEV}"
+fi
+
+# Now let's try regen.
+if ! clevis luks regen -q -d "${DEV}" -s "${SLT}" <<< "${DEFAULT_PASS}"; then
+    error "${TEST}: clevis luks regen failed"
+fi
+
+# Make sure we can unlock the device.
+if ! clevis_luks_unlock_device_by_slot "${DEV}" "${SLT}"; then
+    error "${TEST}: we should be able to unlock ${DEV}"
+fi

+ 79 - 0
src/luks/tests/regen-not-inplace-luks2

@@ -0,0 +1,79 @@
+#!/bin/bash -x
+# 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="${0}"
+. tests-common-functions
+. clevis-luks-common-functions
+
+function on_exit() {
+    [ -d "${TMP}" ] || return 0
+    tang_stop "${TMP}"
+    rm -rf "${TMP}"
+}
+
+trap 'on_exit' EXIT
+
+export TMP=$(mktemp -d)
+
+port=$(get_random_port)
+tang_run "${TMP}" "${port}" &
+tang_wait_until_ready "${port}"
+
+url="http://${TANG_HOST}:${port}"
+adv="${TMP}/adv"
+tang_get_adv "${port}" "${adv}"
+
+cfg=$(printf '{"url":"%s","adv":"%s"}' "$url" "$adv")
+
+# LUKS2.
+DEV="${TMP}/luks2-device"
+new_device "luks2" "${DEV}"
+
+SLT=1
+if ! clevis luks bind -f -s "${SLT}" -d "${DEV}" tang "${cfg}" <<< "${DEFAULT_PASS}"; then
+    error "${TEST}: Bind should have succeeded."
+fi
+
+# Now let's rotate the keys in the server and remove the old ones, so that we
+# will be unable to unlock the volume using clevis and will have to provide
+# manually a password for clevis luks regen.
+tang_new_keys "${TMP}" "rotate-keys"
+tang_remove_rotated_keys "${TMP}"
+
+# Making sure we have two slots enabled.
+enabled=$(clevis_luks_used_slots "${DEV}" | wc -l)
+if [ "${enabled}" -ne 2 ]; then
+    error "${TEST}: we should have two slots enabled (${enabled})."
+fi
+
+# Make sure we cannot unlock the device.
+if clevis_luks_unlock_device_by_slot "${DEV}"; then
+    error "${TEST}: we should NOT be able to unlock ${DEV}"
+fi
+
+# Now let's try regen.
+if ! clevis luks regen -q -d "${DEV}" -s "${SLT}" <<< "${DEFAULT_PASS}"; then
+    error "${TEST}: clevis luks regen failed"
+fi
+
+# Make sure we can unlock the device.
+if ! clevis_luks_unlock_device_by_slot "${DEV}" "${SLT}"; then
+    error "${TEST}: we should be able to unlock ${DEV}"
+fi

+ 75 - 0
src/luks/tests/report-sss-luks1

@@ -0,0 +1,75 @@
+#!/bin/bash -x
+# 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="${0}"
+. tests-common-functions
+. clevis-luks-common-functions
+
+function on_exit() {
+    [ -d "${TMP}" ] || return 0
+    tang_stop "${TMP}"
+    rm -rf "${TMP}"
+}
+
+trap 'on_exit' EXIT
+
+TMP=$(mktemp -d)
+
+port=$(get_random_port)
+tang_run "${TMP}" "${port}" &
+tang_wait_until_ready "${port}"
+
+url="http://${TANG_HOST}:${port}"
+adv="${TMP}/adv"
+tang_get_adv "${port}" "${adv}"
+
+cfg=$(printf '{"t": 1, "pins":{"tang":[{"url":"%s"}], "sss":{"t":1,"pins":{"tang":[{"url":"%s"}]}}}}' "${url}" "${url}")
+
+# LUKS1.
+DEV="${TMP}/luks1-device"
+new_device "luks1" "${DEV}"
+
+SLT=3
+if ! clevis luks bind -y -d "${DEV}" -s "${SLT}" sss "${cfg}" <<< "${DEFAULT_PASS}"; then
+    error "${TEST}: Bind should have succeeded."
+fi
+
+# Report should return 0, as no keys are rotated.
+if ! clevis luks report -q -d "${DEV}" -s "${SLT}"; then
+    error "${TEST}: report should have succeeded, since no keys were rotated"
+fi
+
+# Now let's rotate the keys.
+tang_new_keys "${TMP}" "rotate-keys"
+
+# Report should now return 1.
+if clevis luks report -q -d "${DEV}" -s "${SLT}"; then
+    error "${TEST}: report should have indicated keys were rotated"
+fi
+
+# Now let's regen the keys.
+if ! clevis luks report -q -r -d "${DEV}" -s "${SLT}"; then
+    error "${TEST}: report with regen should have succeeded"
+fi
+
+# Report should return 0 again.
+if ! clevis luks report -q -d "${DEV}" -s "${SLT}"; then
+    error "${TEST}: report should have succeeded after regen"
+fi

+ 75 - 0
src/luks/tests/report-sss-luks2

@@ -0,0 +1,75 @@
+#!/bin/bash -x
+# 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="${0}"
+. tests-common-functions
+. clevis-luks-common-functions
+
+function on_exit() {
+    [ -d "${TMP}" ] || return 0
+    tang_stop "${TMP}"
+    rm -rf "${TMP}"
+}
+
+trap 'on_exit' EXIT
+
+TMP=$(mktemp -d)
+
+port=$(get_random_port)
+tang_run "${TMP}" "${port}" &
+tang_wait_until_ready "${port}"
+
+url="http://${TANG_HOST}:${port}"
+adv="${TMP}/adv"
+tang_get_adv "${port}" "${adv}"
+
+cfg=$(printf '{"t": 1, "pins":{"tang":[{"url":"%s"}], "sss":{"t":1,"pins":{"tang":[{"url":"%s"}]}}}}' "${url}" "${url}")
+
+# LUKS2.
+DEV="${TMP}/luks2-device"
+new_device "luks2" "${DEV}"
+
+SLT=3
+if ! clevis luks bind -y -d "${DEV}" -s "${SLT}" sss "${cfg}" <<< "${DEFAULT_PASS}"; then
+    error "${TEST}: Bind should have succeeded."
+fi
+
+# Report should return 0, as no keys are rotated.
+if ! clevis luks report -q -d "${DEV}" -s "${SLT}"; then
+    error "${TEST}: report should have succeeded, since no keys were rotated"
+fi
+
+# Now let's rotate the keys.
+tang_new_keys "${TMP}" "rotate-keys"
+
+# Report should now return 1.
+if clevis luks report -q -d "${DEV}" -s "${SLT}"; then
+    error "${TEST}: report should have indicated keys were rotated"
+fi
+
+# Now let's regen the keys.
+if ! clevis luks report -q -r -d "${DEV}" -s "${SLT}"; then
+    error "${TEST}: report with regen should have succeeded"
+fi
+
+# Report should return 0 again.
+if ! clevis luks report -q -d "${DEV}" -s "${SLT}"; then
+    error "${TEST}: report should have succeeded after regen"
+fi

+ 75 - 0
src/luks/tests/report-tang-luks1

@@ -0,0 +1,75 @@
+#!/bin/bash -x
+# 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="${0}"
+. tests-common-functions
+. clevis-luks-common-functions
+
+function on_exit() {
+    [ -d "${TMP}" ] || return 0
+    tang_stop "${TMP}"
+    rm -rf "${TMP}"
+}
+
+trap 'on_exit' EXIT
+
+TMP=$(mktemp -d)
+
+port=$(get_random_port)
+tang_run "${TMP}" "${port}" &
+tang_wait_until_ready "${port}"
+
+url="http://${TANG_HOST}:${port}"
+adv="${TMP}/adv"
+tang_get_adv "${port}" "${adv}"
+
+cfg=$(printf '{"url":"%s","adv":"%s"}' "$url" "$adv")
+
+# LUKS1.
+DEV="${TMP}/luks1-device"
+new_device "luks1" "${DEV}"
+
+SLT=3
+if ! clevis luks bind -f -d "${DEV}" -s "${SLT}" tang "${cfg}" <<< "${DEFAULT_PASS}"; then
+    error "${TEST}: Bind should have succeeded."
+fi
+
+# Report should return 0, as no keys are rotated.
+if ! clevis luks report -q -d "${DEV}" -s "${SLT}"; then
+    error "${TEST}: report should have succeeded, since no keys were rotated"
+fi
+
+# Now let's rotate the keys.
+tang_new_keys "${TMP}" "rotate-keys"
+
+# Report should now return 1.
+if clevis luks report -q -d "${DEV}" -s "${SLT}"; then
+    error "${TEST}: report should have indicated keys were rotated"
+fi
+
+# Now let's regen the keys.
+if ! clevis luks report -q -r -d "${DEV}" -s "${SLT}"; then
+    error "${TEST}: report with regen should have succeeded"
+fi
+
+# Report should return 0 again.
+if ! clevis luks report -q -d "${DEV}" -s "${SLT}"; then
+    error "${TEST}: report should have succeeded after regen"
+fi

+ 75 - 0
src/luks/tests/report-tang-luks2

@@ -0,0 +1,75 @@
+#!/bin/bash -x
+# 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="${0}"
+. tests-common-functions
+. clevis-luks-common-functions
+
+function on_exit() {
+    [ -d "${TMP}" ] || return 0
+    tang_stop "${TMP}"
+    rm -rf "${TMP}"
+}
+
+trap 'on_exit' EXIT
+
+TMP=$(mktemp -d)
+
+port=$(get_random_port)
+tang_run "${TMP}" "${port}" &
+tang_wait_until_ready "${port}"
+
+url="http://${TANG_HOST}:${port}"
+adv="${TMP}/adv"
+tang_get_adv "${port}" "${adv}"
+
+cfg=$(printf '{"url":"%s","adv":"%s"}' "$url" "$adv")
+
+# LUKS2.
+DEV="${TMP}/luks2-device"
+new_device "luks2" "${DEV}"
+
+SLT=3
+if ! clevis luks bind -f -d "${DEV}" -s "${SLT}" tang "${cfg}" <<< "${DEFAULT_PASS}"; then
+    error "${TEST}: Bind should have succeeded."
+fi
+
+# Report should return 0, as no keys are rotated.
+if ! clevis luks report -q -d "${DEV}" -s "${SLT}"; then
+    error "${TEST}: report should have succeeded, since no keys were rotated"
+fi
+
+# Now let's rotate the keys.
+tang_new_keys "${TMP}" "rotate-keys"
+
+# Report should now return 1.
+if clevis luks report -q -d "${DEV}" -s "${SLT}"; then
+    error "${TEST}: report should have indicated keys were rotated"
+fi
+
+# Now let's regen the keys.
+if ! clevis luks report -q -r -d "${DEV}" -s "${SLT}"; then
+    error "${TEST}: report with regen should have succeeded"
+fi
+
+# Report should return 0 again.
+if ! clevis luks report -q -d "${DEV}" -s "${SLT}"; then
+    error "${TEST}: report should have succeeded after regen"
+fi

+ 264 - 12
src/luks/tests/tests-common-functions.in

@@ -56,7 +56,7 @@ new_device() {
 
     # Some builders fail if the cryptsetup steps are not ran as root, so let's
     # skip the test now if not running as root.
-    if [ $(id -u) != 0 ]; then
+    if [ "$(id -u)" != 0 ]; then
         skip_test "WARNING: You must be root to run this test; test skipped."
     fi
 
@@ -74,9 +74,9 @@ new_device() {
         return 0
     fi
 
-    fallocate -l16M "${DEV}"
-    local extra_options='--pbkdf pbkdf2 --pbkdf-force-iterations 1000'
-    cryptsetup luksFormat --type "${LUKS}" ${extra_options} --batch-mode \
+    fallocate -l64M "${DEV}"
+    cryptsetup luksFormat --type "${LUKS}" --pbkdf pbkdf2 \
+        --pbkdf-force-iterations 1000 --batch-mode \
         --force-password "${DEV}" <<< "${PASS}"
     # Caching the just-formatted device for possible reuse.
     cp -f "${DEV}" "${DEV_CACHED}"
@@ -90,7 +90,7 @@ new_device_keyfile() {
 
     # Some builders fail if the cryptsetup steps are not ran as root, so let's
     # skip the test now if not running as root.
-    if [ $(id -u) != 0 ]; then
+    if [ "$(id -u)" != 0 ]; then
         skip_test "WARNING: You must be root to run this test; test skipped."
     fi
 
@@ -98,18 +98,270 @@ new_device_keyfile() {
         error "Invalid keyfile (${KEYFILE})."
     fi
 
-    fallocate -l16M "${DEV}"
-    local extra_options='--pbkdf pbkdf2 --pbkdf-force-iterations 1000'
-    cryptsetup luksFormat --type "${LUKS}"  ${extra_options} --batch-mode \
+    fallocate -l64M "${DEV}"
+    cryptsetup luksFormat --type "${LUKS}" --pbkdf pbkdf2 \
+        --pbkdf-force-iterations 1000 --batch-mode \
         "${DEV}" "${KEYFILE}"
 }
 
 pin_cfg_equal() {
-    local cfg1="${1}"
-    local cfg2="${1}"
+    # Let's remove the single quotes from the pin configuration.
+    local cfg1="${1//\'/}"
+    local cfg2="${2//\'/}"
 
-    diff <(jq -S . < <(echo -n "${cfg1}")) \
-         <(jq -S . < <(echo -n "${cfg2}"))
+    # Now we sort and present them in compact form.
+    local sorted_cfg1 sorted_cfg2
+    sorted_cfg1="$(jq --compact-output --sort-keys . < <(echo -n "${cfg1}"))"
+    sorted_cfg2="$(jq --compact-output --sort-keys . < <(echo -n "${cfg2}"))"
+
+    # And we finally compare.
+    if [ "${sorted_cfg1}" = "${sorted_cfg2}" ]; then
+        return 0
+    fi
+    return 1
+}
+
+compare_luks_header() {
+    DEV1="${1}"
+    DEV2="${2}"
+    TMP="${3}"
+
+    cryptsetup luksHeaderBackup "${DEV1}" \
+               --header-backup-file "${TMP}"/check-header1
+    cryptsetup luksHeaderBackup "${DEV2}" \
+               --header-backup-file "${TMP}"/check-header2
+
+    local cs1 cs2
+    cs1=$(cksum "${TMP}"/check-header1 | cut -d' ' -f 1)
+    cs2=$(cksum "${TMP}"/check-header2 | cut -d' ' -f 1)
+    rm -f "${TMP}"/check-header{1,2}
+
+    if [ "${cs1}" == "${cs2}" ]; then
+        return 0
+    fi
+    return 1
+}
+
+used_luks1_metadata_slots() {
+    DEV="${1}"
+    if ! luksmeta test -d "${DEV}"; then
+        echo ""
+        return 0
+    fi
+
+    local clevis_uuid="cb6e8904-81ff-40da-a84a-07ab9ab5715e"
+    luksmeta show -d "${DEV}" \
+        | sed -rn "s|^([0-9]+)\s+active\s+${clevis_uuid}$|\1|p" \
+        | tr '\n' ' ' | sed 's/ $//'
+}
+
+used_luks2_metadata_slots() {
+    DEV="${1}"
+    cryptsetup luksDump "${DEV}" \
+        | grep -E -A1 "^\s+[0-9]+:\s+clevis$" \
+        | sed -rn 's|^\s+Keyslot:\s+([0-9]+)$|\1|p' | sort -n \
+        | tr '\n' ' ' | sed 's/ $//'
+}
+
+used_luks2_metadata_tokens() {
+    DEV="${1}"
+    cryptsetup luksDump "${DEV}" \
+        | grep -E -B1 "^\s+Keyslot:\s+[0-9]+$" \
+        | sed -rn 's|^\s+([0-9]+): clevis|\1|p' \
+        | tr '\n' ' ' | sed 's/ $//'
+}
+
+compare_luks1_metadata() {
+    DEV1="${1}"
+    DEV2="${2}"
+
+    # If both are non-initialized, metadata is the same.
+    ! luksmeta test -d "${DEV1}" && ! luksmeta test -d "${DEV2}" && return 0
+    # Otherwise, metadata differ.
+    ! luksmeta test -d "${DEV1}" && return 1
+    ! luksmeta test -d "${DEV2}" && return 1
+
+    local slt1 slt2
+    slt1=$(used_luks1_metadata_slots "${DEV1}")
+    slt2=$(used_luks1_metadata_slots "${DEV2}")
+
+    if [ "${slt1}" != "${slt2}" ]; then
+        echo "used slots did not match ($slt1) ($slt2)" >&2
+        return 1
+    fi
+
+    local slt md1 md2
+    for slt in ${slt1}; do
+        md1="$(luksmeta load -d "${DEV}" -s "${slt}")"
+        md2="$(luksmeta load -d "${DEV2}" -s "${slt}")"
+        if [ "${md1}" != "${md2}" ]; then
+            echo "metadata in slot ${slt} did not match" >&2
+            return 1
+        fi
+    done
+    return 0
+}
+
+compare_luks2_metadata() {
+    DEV1="${1}"
+    DEV2="${2}"
+
+    local slt1 slt2
+    slt1=$(used_luks2_metadata_slots "${DEV1}")
+    slt2=$(used_luks2_metadata_slots "${DEV2}")
+
+    if [ "${slt1}" != "${slt2}" ]; then
+        echo "used slots did not match ($slt1) ($slt2)" >&2
+        return 1
+    fi
+
+    local tkn1 tkn2
+    tkn1=$(used_luks2_metadata_tokens "${DEV1}")
+    tkn2=$(used_luks2_metadata_tokens "${DEV2}")
+
+    if [ "${tkn1}" != "${tkn2}" ]; then
+        echo "used tokens did not match ($tkn1) ($tkn2)" >&2
+        return 1
+    fi
+
+    local tkn md1 md2
+    for tkn in ${tkn1}; do
+        md1="$(cryptsetup token export --token-id "${tkn}" "${DEV1}")"
+        md2="$(cryptsetup token export --token-id "${tkn}" "${DEV2}")"
+        if [ "${md1}" != "${md2}" ]; then
+            echo "metadata in token ${tkn} did not match" >&2
+            return 1
+        fi
+    done
+    return 0
+}
+
+
+# Get a random port to be used with a test tang server.
+get_random_port() {
+    shuf -i 1024-65535 -n 1
+}
+
+# Removes tang rotated keys from the test server.
+tang_remove_rotated_keys() {
+    local basedir="${1}"
+
+    if [ -z "${basedir}" ]; then
+        echo "Please pass a valid base directory for tang"
+        return 1
+    fi
+
+    [ -z "${TANGD_UPDATE}" ] && skip_test "WARNING: TANGD_UPDATE is not defined."
+
+    local db="${basedir}/db"
+    local cache="${basedir}/cache"
+    mkdir -p "${db}"
+    mkdir -p "${cache}"
+
+    pushd "${db}"
+        find . -name ".*.jwk" -exec rm -f {} \;
+    popd
+
+    "${TANGD_UPDATE}" "${db}" "${cache}"
+    return 0
+}
+
+# Creates new keys for the test tang server.
+tang_new_keys() {
+    local basedir="${1}"
+    local rotate="${2}"
+
+    if [ -z "${basedir}" ]; then
+        echo "Please pass a valid base directory for tang"
+        return 1
+    fi
+
+    [ -z "${TANGD_KEYGEN}" ] && skip_test "WARNING: TANGD_KEYGEN is not defined."
+    [ -z "${TANGD_UPDATE}" ] && skip_test "WARNING: TANGD_UPDATE is not defined."
+
+    local db="${basedir}/db"
+    local cache="${basedir}/cache"
+    mkdir -p "${db}"
+
+    if [ -n "${rotate}" ]; then
+        pushd "${db}"
+            local k
+            k=$(find . -name "*.jwk" | wc -l)
+            if [ "${k}" -gt 0 ]; then
+                for k in *.jwk; do
+                    mv -f -- "${k}" ".${k}"
+                done
+            fi
+        popd
+    fi
+
+    "${TANGD_KEYGEN}" "${db}"
+    "${TANGD_UPDATE}" "${db}" "${cache}"
+
+    return 0
+}
+
+# Start a test tang server.
+tang_run() {
+    local basedir="${1}"
+    local port="${2}"
+
+    if [ -z "${basedir}" ]; then
+        echo "Please pass a valid base directory for tang" >&2
+        return 1
+    fi
+
+    if [ -z "${port}" ]; then
+        echo "Please pass a valid port for tang" >&2
+        return 1
+    fi
+
+    if ! tang_new_keys "${basedir}"; then
+        echo "Error creating new keys for tang server" >&2
+        return 1
+    fi
+
+    local KEYS="${basedir}/cache"
+    local inetd='--inetd'
+    [ "${SD_ACTIVATE##*/}" = "systemd-activate" ] && inetd=
+
+    local pid pidfile
+    pidfile="${basedir}/tang.pid"
+
+    "${SD_ACTIVATE}" ${inetd} -l "${TANG_HOST}":"${port}" \
+            -a "${TANGD}" "${KEYS}" &
+    pid=$!
+    echo "${pid}" > "${pidfile}"
+}
+
+# Stop tang server.
+tang_stop() {
+    local basedir="${1}"
+    local pidfile="${basedir}/tang.pid"
+    [ -f "${pidfile}" ] || return 0
+
+    local pid
+    pid=$(<"${pidfile}")
+    kill "${pid}"
+}
+
+# Wait for the tang server to be operational.
+tang_wait_until_ready() {
+   local port="${1}"
+   while ! curl --output /dev/null --silent --fail \
+                http://"${TANG_HOST}":"${port}"/adv; do
+       sleep 0.1
+       echo -n . >&2
+   done
+}
+
+# Get tang advertisement.
+tang_get_adv() {
+    local port="${1}"
+    local adv="${2}"
+
+    curl -o "${adv}" http://"${TANG_HOST}":"${port}"/adv
 }
 
+export TANG_HOST=127.0.0.1
 export DEFAULT_PASS='just-some-test-password-here'

+ 1 - 0
src/luks/tests/unbind-unbound-slot-luks2

@@ -35,6 +35,7 @@ fi
 TMP="$(mktemp -d)"
 
 DEV="${TMP}/luks2-device"
+SLT=2
 new_device "luks2" "${DEV}"
 if clevis luks unbind -d "${DEV}" -s "${SLT}"; then
     error "${TEST}: Unbind is expected to fail for device ${DEV} and slot ${SLT}" >&2

+ 83 - 0
src/luks/tests/unlock-tang-luks1

@@ -0,0 +1,83 @@
+#!/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}"
+adv="${TMP}/adv"
+tang_get_adv "${port}" "${adv}"
+
+cfg=$(printf '{"url":"%s","adv":"%s"}' "$url" "$adv")
+
+# LUKS1.
+DEV="${TMP}/luks1-device"
+new_device "luks1" "${DEV}"
+
+if ! clevis luks bind -f -d "${DEV}" tang "${cfg}" <<< "${DEFAULT_PASS}"; then
+    error "${TEST}: Bind should have succeeded."
+fi
+
+if ! clevis_luks_unlock_device "${DEV}"; then
+    error "${TEST}: we were unable to unlock ${DEV}."
+fi
+
+# Let's rotate the tang keys and add another binding with the new key.
+tang_new_keys "${TMP}" "rotate-keys"
+
+# Unlock should still work now.
+if ! clevis_luks_unlock_device "${DEV}"; then
+    error "${TEST}: we should still be able to unlock ${DEV}"
+fi
+
+# Now let's remove the rotated keys.
+tang_remove_rotated_keys "${TMP}"
+
+# Unlock should not work anymore.
+if clevis_luks_unlock_dev "${DEV}"; then
+    error "${TEST}: we should not be able to unlock ${DEV}"
+fi
+
+# Now let's add another binding with the new keys.
+tang_get_adv "${port}" "${adv}" # Updating the advertisement.
+if ! clevis luks bind -f -d "${DEV}" tang "${cfg}" <<< "${DEFAULT_PASS}"; then
+    error "${TEST}: Bind should have succeeded."
+fi
+
+# Unlock should work again, using the new keys.
+if ! clevis_luks_unlock_device "${DEV}"; then
+    error "${TEST}: we should be able to unlock ${DEV} with the new keys"
+fi

+ 83 - 0
src/luks/tests/unlock-tang-luks2

@@ -0,0 +1,83 @@
+#!/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}"
+adv="${TMP}/adv"
+tang_get_adv "${port}" "${adv}"
+
+cfg=$(printf '{"url":"%s","adv":"%s"}' "$url" "$adv")
+
+# LUKS2.
+DEV="${TMP}/luks2-device"
+new_device "luks2" "${DEV}"
+
+if ! clevis luks bind -f -d "${DEV}" tang "${cfg}" <<< "${DEFAULT_PASS}"; then
+    error "${TEST}: Bind should have succeeded."
+fi
+
+if ! clevis_luks_unlock_device "${DEV}"; then
+    error "${TEST}: we were unable to unlock ${DEV}."
+fi
+
+# Let's rotate the tang keys and add another binding with the new key.
+tang_new_keys "${TMP}" "rotate-keys"
+
+# Unlock should still work now.
+if ! clevis_luks_unlock_device "${DEV}"; then
+    error "${TEST}: we should still be able to unlock ${DEV}"
+fi
+
+# Now let's remove the rotated keys.
+tang_remove_rotated_keys "${TMP}"
+
+# Unlock should not work anymore.
+if clevis_luks_unlock_device "${DEV}"; then
+    error "${TEST}: we should not be able to unlock ${DEV}"
+fi
+
+# Now let's add another binding with the new keys.
+tang_get_adv "${port}" "${adv}" # Updating the advertisement.
+if ! clevis luks bind -f -d "${DEV}" tang "${cfg}" <<< "${DEFAULT_PASS}"; then
+    error "${TEST}: Bind should have succeeded."
+fi
+
+# Unlock should work again, using the new keys.
+if ! clevis_luks_unlock_device "${DEV}"; then
+    error "${TEST}: we should be able to unlock ${DEV} with the new keys"
+fi

+ 5 - 0
src/luks/udisks2/clevis-luks-udisks2.c

@@ -330,6 +330,11 @@ recover_key(const pkt_t *jwe, char *out, size_t max, uid_t uid, gid_t gid)
             }
         }
 
+        if (initgroups(CLEVIS_USER, gid) != 0) {
+            perror("initgroups");
+            exit(EXIT_FAILURE);
+        }
+
         if (setgid(gid) != 0) {
             perror("setgid");
             exit(EXIT_FAILURE);

+ 12 - 2
src/pins/sss/clevis-encrypt-sss.1.adoc

@@ -5,11 +5,11 @@ CLEVIS-ENCRYPT-SSS(1)
 
 == NAME
 
-clevis-encrypt-sss - Encrypts using a Shamir's Secret Sharing policy 
+clevis-encrypt-sss - Encrypts using a Shamir's Secret Sharing policy
 
 == SYNOPSIS
 
-*clevis encrypt sss* CONFIG < PT > JWE
+*clevis encrypt sss* CONFIG [-y] < PT > JWE
 
 == OVERVIEW
 
@@ -52,6 +52,16 @@ The format of the *pins* property is as follows:
 When the list version of the format is used, multiple pins of that type will
 receive key fragments.
 
+== OPTIONS
+
+* *-y* :
+  Automatically answer yes for all questions. For the _tang_ pin, it will
+  skip the advertisement trust check, which can be useful in automated
+  deployments:
+
+    $ cfg='{"t":1,"pins":{"tang":[{"url":...},{"url":...}]}}'
+    $ clevis encrypt sss "$cfg" -y < PT > JWE
+
 == SEE ALSO
 
 link:clevis-encrypt-tang.1.adoc[*clevis-encrypt-tang*(1)],

+ 27 - 9
src/pins/sss/clevis-encrypt-sss.c

@@ -86,20 +86,25 @@ npins(json_t *pins)
 }
 
 static json_t *
-encrypt_frag(json_t *sss, const char *pin, const json_t *cfg)
+encrypt_frag(json_t *sss, const char *pin, const json_t *cfg, int assume_yes)
 {
-    char *args[] = { "clevis", "encrypt", (char *) pin, NULL, NULL };
+    char *args[] = { "clevis", "encrypt", (char *) pin, NULL, NULL, NULL };
     json_auto_t *jwe = json_string("");
     str_auto_t *str = NULL;
     uint8_t *pnt = NULL;
     FILE *pipe = NULL;
     size_t pntl = 0;
     pid_t pid = 0;
+    int status = 0;
 
     str = args[3] = json_dumps(cfg, JSON_SORT_KEYS | JSON_COMPACT);
     if (!str)
         return NULL;
 
+    if (assume_yes) {
+        args[4] = "-y";
+    }
+
     pnt = sss_point(sss, &pntl);
     if (!pnt)
         return NULL;
@@ -132,12 +137,15 @@ encrypt_frag(json_t *sss, const char *pin, const json_t *cfg)
     }
 
     fclose(pipe);
-    waitpid(pid, NULL, 0);
+    waitpid(pid, &status, 0);
+    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+        return NULL;
+    }
     return json_incref(jwe);
 }
 
 static json_t *
-encrypt_frags(json_int_t t, json_t *pins)
+encrypt_frags(json_int_t t, json_t *pins, int assume_yes)
 {
     const char *pname = NULL;
     json_auto_t *sss = NULL;
@@ -172,7 +180,7 @@ encrypt_frags(json_int_t t, json_t *pins)
         json_array_foreach(pcfgs, i, pcfg) {
             json_auto_t *jwe = NULL;
 
-            jwe = encrypt_frag(sss, pname, pcfg);
+            jwe = encrypt_frag(sss, pname, pcfg, assume_yes);
             if (!jwe)
                 return NULL;
 
@@ -201,14 +209,24 @@ main(int argc, char *argv[])
     const char *iv = NULL;
     json_t *pins = NULL;
     json_int_t t = 1;
+    int assume_yes = 0;
 
     if (argc == 2 && strcmp(argv[1], "--summary") == 0) {
         fprintf(stdout, "%s\n", SUMMARY);
         return EXIT_SUCCESS;
     }
 
-    if (isatty(STDIN_FILENO) || argc != 2)
-        goto usage;
+    if (isatty(STDIN_FILENO) || argc != 2) {
+        if (argc != 3) {
+            goto usage;
+        }
+
+        if (strcmp(argv[2], "-y") == 0) {
+            assume_yes = 1;
+        } else if (strlen(argv[2]) > 0) {
+            goto usage;
+        }
+    }
 
     /* Parse configuration. */
     cfg = json_loads(argv[1], 0, NULL);
@@ -228,7 +246,7 @@ main(int argc, char *argv[])
         return EXIT_FAILURE;
     }
 
-    sss = encrypt_frags(t, pins);
+    sss = encrypt_frags(t, pins, assume_yes);
     if (!sss)
         return EXIT_FAILURE;
 
@@ -287,7 +305,7 @@ main(int argc, char *argv[])
 
 usage:
     fprintf(stderr, "\n");
-    fprintf(stderr, "Usage: clevis encrypt sss CONFIG < PLAINTEXT > JWE\n");
+    fprintf(stderr, "Usage: clevis encrypt sss CONFIG [-y] < PLAINTEXT > JWE\n");
     fprintf(stderr, "\n");
     fprintf(stderr, "%s\n", SUMMARY);
     fprintf(stderr, "\n");

+ 2 - 1
src/pins/sss/meson.build

@@ -18,8 +18,9 @@ if jansson.found() and libcrypto.found()
   mans += join_paths(src, 'clevis-encrypt-sss.1')
 
   env = environment()
-  env.append('PATH',
+  env.prepend('PATH',
     join_paths(meson.source_root(), 'src'),
+    join_paths(meson.source_root(), 'src', 'pins', 'tang'),
     meson.current_build_dir(),
     '/usr/libexec',
     libexecdir,

+ 2 - 0
src/pins/sss/pin-sss

@@ -20,3 +20,5 @@ e="$(echo hi | clevis encrypt sss '{"t":2,"pins":{"test":[{},{"fail":true}]}}')"
 
 e="$(echo hi | clevis encrypt sss '{"t":2,"pins":{"test":[{"fail":true},{"fail":true}]}}')"
 ! echo "$e" | clevis decrypt
+
+! e="$(echo hi | clevis encrypt sss '{"t":1,"pins":{"tang":[{"url":"foo bar"}]}}')"

+ 22 - 13
src/pins/tang/clevis-encrypt-tang

@@ -28,10 +28,14 @@ fi
 if [ -t 0 ]; then
     exec >&2
     echo
-    echo "Usage: clevis encrypt tang CONFIG < PLAINTEXT > JWE"
+    echo "Usage: clevis encrypt tang CONFIG [-y] < PLAINTEXT > JWE"
     echo
     echo "$SUMMARY"
     echo
+    echo "  -y              Use this option for skipping the advertisement"
+    echo "                  trust check. This can be useful in automated"
+    echo "                  deployments"
+    echo
     echo "This command uses the following configuration properties:"
     echo
     echo "  url: <string>   The base URL of the Tang server (REQUIRED)"
@@ -60,6 +64,9 @@ if ! cfg="$(jose fmt -j- -Oo- <<< "$1" 2>/dev/null)"; then
     exit 1
 fi
 
+trust=
+[ -n "${2}" ] && [ "${2}" == "-y" ] && trust=yes
+
 if ! url="$(jose fmt -j- -Og url -u- <<< "$cfg")"; then
     echo "Missing the required 'url' property!" >&2
     exit 1
@@ -100,18 +107,20 @@ if ! jose jws ver -i "$jws" -k- -a <<< "$ver"; then
 fi
 
 ### Check advertisement trust
-if [ -z "$thp" ]; then
-    echo "The advertisement contains the following signing keys:" >&2
-    echo >&2
-    jose jwk thp -i- <<< "$ver" >&2
-    echo >&2
-    read -r -p "Do you wish to trust these keys? [ynYN] " ans < /dev/tty
-    [[ "$ans" =~ ^[yY]$ ]] || exit 1
-
-elif [ "$thp" != "any" ] && \
-    ! jose jwk thp -i- -f "$thp" -o /dev/null <<< "$ver"; then
-    echo "Trusted JWK '$thp' did not sign the advertisement!" >&2
-    exit 1
+if [ -z "${trust}" ]; then
+    if [ -z "$thp" ]; then
+        echo "The advertisement contains the following signing keys:" >&2
+        echo >&2
+        jose jwk thp -i- <<< "$ver" >&2
+        echo >&2
+        read -r -p "Do you wish to trust these keys? [ynYN] " ans < /dev/tty
+        [[ "$ans" =~ ^[yY]$ ]] || exit 1
+
+    elif [ "$thp" != "any" ] && \
+        ! jose jwk thp -i- -f "$thp" -o /dev/null <<< "$ver"; then
+        echo "Trusted JWK '$thp' did not sign the advertisement!" >&2
+        exit 1
+    fi
 fi
 
 ### Perform encryption

+ 10 - 1
src/pins/tang/clevis-encrypt-tang.1.adoc

@@ -9,7 +9,7 @@ clevis-encrypt-tang - Encrypts using a Tang binding server policy
 
 == SYNOPSIS
 
-*clevis encrypt tang* CONFIG < PT > JWE
+*clevis encrypt tang* CONFIG [-y] < PT > JWE
 
 == OVERVIEW
 
@@ -76,6 +76,15 @@ This command uses the following configuration properties:
 * *adv* (object) :
   A trusted advertisement (raw JSON)
 
+== OPTIONS
+
+* *-y* :
+  Automatically answer yes for all questions. Use this option for skipping
+  the advertisement trust check. This can be useful in automated deployments:
+
+    $ clevis encrypt tang '{"url":...}' -y < PT > JWE
+
+
 == SEE ALSO
 
 link:clevis-decrypt.1.adoc[*clevis-decrypt*(1)]

+ 17 - 10
src/pins/tang/meson.build

@@ -1,24 +1,31 @@
 actv = find_program(
   'systemd-socket-activate',
   'systemd-activate',
+  join_paths('/', 'usr', 'lib', 'systemd', 'systemd-activate'),
   required: false
 )
 kgen = find_program(
   join_paths(libexecdir, 'tangd-keygen'),
-  '/usr/libexec/tangd-keygen',
-  '/usr/lib/x86_64-linux-gnu/tangd-keygen',
+  join_paths(get_option('prefix'), get_option('libdir'), 'tangd-keygen'),
+  join_paths(get_option('prefix'), get_option('libexecdir'), 'tangd-keygen'),
+  join_paths('/', 'usr', get_option('libdir'), 'tangd-keygen'),
+  join_paths('/', 'usr', get_option('libexecdir'), 'tangd-keygen'),
   required: false
 )
 updt = find_program(
   join_paths(libexecdir, 'tangd-update'),
-  '/usr/libexec/tangd-update',
-  '/usr/lib/x86_64-linux-gnu/tangd-update',
+  join_paths(get_option('prefix'), get_option('libdir'), 'tangd-update'),
+  join_paths(get_option('prefix'), get_option('libexecdir'), 'tangd-update'),
+  join_paths('/', 'usr', get_option('libdir'), 'tangd-update'),
+  join_paths('/', 'usr', get_option('libexecdir'), 'tangd-update'),
   required: false
 )
 tang = find_program(
   join_paths(libexecdir, 'tangd'),
-  '/usr/libexec/tangd',
-  '/usr/lib/x86_64-linux-gnu/tangd',
+  join_paths(get_option('prefix'), get_option('libdir'), 'tangd'),
+  join_paths(get_option('prefix'), get_option('libexecdir'), 'tangd'),
+  join_paths('/', 'usr', get_option('libdir'), 'tangd'),
+  join_paths('/', 'usr', get_option('libexecdir'), 'tangd'),
   required: false
 )
 curl = find_program('curl', required: false)
@@ -31,12 +38,12 @@ if curl.found()
   if actv.found() and kgen.found() and updt.found() and tang.found()
     env = environment()
     env.set('SD_ACTIVATE', actv.path())
-    env.append('PATH',
+    env.set('TANGD_KEYGEN', kgen.path())
+    env.set('TANGD_UPDATE', updt.path())
+    env.set('TANGD', tang.path())
+    env.prepend('PATH',
       join_paths(meson.source_root(), 'src'),
       meson.current_source_dir(),
-      '/usr/libexec',
-      '/usr/lib/x86_64-linux-gnu',
-      libexecdir,
       separator: ':'
     )
 

+ 7 - 3
src/pins/tang/pin-tang

@@ -31,12 +31,16 @@ mkdir -p "$TMP"/db
 mkdir -p "$TMP"/cache
 
 # Generate the server keys
-tangd-keygen "$TMP"/db sig exc
-tangd-update "$TMP"/db "$TMP"/cache
+"${TANGD_KEYGEN}" "$TMP"/db sig exc
+"${TANGD_UPDATE}" "$TMP"/db "$TMP"/cache
 
 # Start the server
 port="$(shuf -i 1024-65536 -n 1)"
-$SD_ACTIVATE --inetd -l 127.0.0.1:$port -a tangd "$TMP"/cache &
+
+inetd='--inetd'
+[ "${SD_ACTIVATE##*/}" = "systemd-activate" ] && inetd=
+
+"$SD_ACTIVATE" $inetd -l 127.0.0.1:"$port" -a "$TANGD" "$TMP"/cache &
 PID=$!
 sleep 0.25
 

+ 6 - 0
src/pins/tpm2/clevis-decrypt-tpm2

@@ -18,6 +18,12 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 
+# First try to use the new version of the PIN implementation
+if command -v clevis-pin-tpm2 >/dev/null;
+then
+    exec clevis-pin-tpm2 decrypt $@
+fi
+
 # The owner hierarchy is the one that should be used by the Operating System.
 auth="o"
 

+ 6 - 0
src/pins/tpm2/clevis-encrypt-tpm2

@@ -18,6 +18,12 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 
+# First try to use the new version of the PIN implementation
+if command -v clevis-pin-tpm2 >/dev/null;
+then
+    exec clevis-pin-tpm2 encrypt "$@"
+fi
+
 SUMMARY="Encrypts using a TPM2.0 chip binding policy"
 # The owner hierarchy is the one that should be used by the Operating System.
 auth="o"

+ 1 - 1
src/pins/tpm2/meson.build

@@ -31,4 +31,4 @@ env.prepend('PATH',
   join_paths(meson.build_root(), 'src', 'pins', 'tpm2'),
   separator: ':'
 )
-test('pin-tpm2', find_program('pin-tpm2'), env: env, timeout: 90)
+test('pin-tpm2', find_program('pin-tpm2'), env: env, timeout: 120)

+ 66 - 0
src/pins/tpm2/pin-tpm2

@@ -94,6 +94,17 @@ test_pcr_ids() {
     fi
 }
 
+test_enc_dec() {
+    local cfg="${1}"
+    output=$(echo Working | clevis encrypt tpm2 "${cfg}" | clevis decrypt)
+
+    if [ "$output" != "Working" ]; then
+        echo "Output after decrypting doesn't match: ${output} != 'Working'"
+        return 1
+    fi
+}
+
+test_enc_dec '{}' || exit 1
 test_pcr_ids "${orig}" '{}' "" || exit 1
 test_pcr_ids "${orig}" '{                   }' "" || exit 1
 test_pcr_ids "${orig}" '{"key": "ecc"}' "" || exit 1
@@ -109,3 +120,58 @@ test_pcr_ids "${orig}" '{"pcr_ids": [4,16]}' "4,16" || exit 1
 test_pcr_ids "${orig}" '{"pcr_ids": [4,  16]}' "4,16" || exit 1
 test_pcr_ids "${orig}" '{"pcr_ids": ["4","16"]}' "4,16" || exit 1
 ! test_pcr_ids "${orig}" '{"pcr_ids": ["4","16"]}' "foo bar" || exit 1
+
+# Test with policies if we have the PIN rewrite available
+if ! $(which clevis-pin-tpm2 >/dev/null 2>&1);
+then
+    echo "No PIN rewrite available"
+    exit 0
+fi
+if ! $(which clevis-pin-tpm2-signtool >/dev/null 2>&1);
+then
+    echo "No policy signtool available"
+    exit 0
+fi
+
+function on_exit() {
+    popd
+    if [ ! -d "$TMP" ] || ! rm -rf "$TMP"; then
+        echo "Delete temporary files failed!" >&2
+        echo "You need to clean up: $TMP" >&2
+        exit 1
+    fi
+}
+if ! TMP="$(mktemp -d)"; then
+    echo "Creating a temporary dir for TPM files failed!" >&2
+    exit 1
+fi
+trap 'on_exit' EXIT
+pushd $TMP
+
+clevis-pin-tpm2-signtool >policy_working.json << EOP
+---
+- policy_ref:
+  steps:
+    - PCRs:
+        hash_algorithm: sha256
+        selection:
+          - pcr_id: 21
+            value: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+          - pcr_id: 22
+            value: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+EOP
+clevis-pin-tpm2-signtool >policy_broken.json << EOP
+---
+- policy_ref:
+  steps:
+    - PCRs:
+        hash_algorithm: sha256
+        selection:
+          - pcr_id: 21
+            value: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"
+          - pcr_id: 22
+            value: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+EOP
+
+test_enc_dec '{"policy_pubkey_path":"./publickey.json", "policy_ref": "", "policy_path": "./policy_working.json"}' || exit 1
+! test_enc_dec '{"policy_pubkey_path":"./publickey.json", "policy_ref": "", "policy_path": "./policy_broken.json"}' || exit 1