Browse Source

Import upstream version 12

Sergio Correia 9 months ago
parent
commit
90ea9d4bb9
71 changed files with 2606 additions and 643 deletions
  1. 26 0
      .travis.yml
  2. 38 0
      .travis/docker
  3. 56 0
      .travis/install
  4. 36 0
      .travis/script
  5. 13 21
      README.md
  6. 3 2
      meson.build
  7. 0 1
      meson_options.txt
  8. 33 11
      src/bash/clevis
  9. 1 1
      src/bash/meson.build
  10. 20 18
      src/clevis
  11. 15 11
      src/clevis-decrypt
  12. 0 21
      src/clevis.1.adoc
  13. 90 0
      src/initramfs-tools/hooks/clevis.in
  14. 6 0
      src/initramfs-tools/hooks/meson.build
  15. 16 0
      src/initramfs-tools/meson.build
  16. 49 0
      src/initramfs-tools/scripts/local-bottom/clevis.in
  17. 6 0
      src/initramfs-tools/scripts/local-bottom/meson.build
  18. 205 0
      src/initramfs-tools/scripts/local-top/clevis.in
  19. 6 0
      src/initramfs-tools/scripts/local-top/meson.build
  20. 2 0
      src/initramfs-tools/scripts/meson.build
  21. 0 145
      src/luks/clevis-luks-bind
  22. 0 1
      src/luks/clevis-luks-bind.1.adoc
  23. 220 0
      src/luks/clevis-luks-bind.in
  24. 56 34
      src/luks/clevis-luks-unbind
  25. 0 80
      src/luks/clevis-luks-unlock
  26. 130 0
      src/luks/clevis-luks-unlock.in
  27. 30 5
      src/luks/meson.build
  28. 42 18
      src/luks/systemd/clevis-luks-askpass
  29. 14 0
      src/luks/systemd/dracut/clevis-pin-sss/meson.build
  30. 28 0
      src/luks/systemd/dracut/clevis-pin-sss/module-setup.sh.in
  31. 14 0
      src/luks/systemd/dracut/clevis-pin-tang/meson.build
  32. 36 0
      src/luks/systemd/dracut/clevis-pin-tang/module-setup.sh.in
  33. 14 0
      src/luks/systemd/dracut/clevis-pin-tpm2/meson.build
  34. 40 0
      src/luks/systemd/dracut/clevis-pin-tpm2/module-setup.sh.in
  35. 0 0
      src/luks/systemd/dracut/clevis/clevis-hook.sh.in
  36. 21 0
      src/luks/systemd/dracut/clevis/meson.build
  37. 6 37
      src/luks/systemd/dracut/module-setup.sh.in
  38. 4 21
      src/luks/systemd/dracut/meson.build
  39. 1 1
      src/luks/systemd/meson.build
  40. 102 0
      src/luks/tests/bind-already-used-luksmeta-slot
  41. 59 0
      src/luks/tests/bind-key-file-non-interactive-luks1
  42. 57 0
      src/luks/tests/bind-luks1
  43. 48 0
      src/luks/tests/bind-luks2
  44. 70 0
      src/luks/tests/bind-pass-with-newline-keyfile-luks1
  45. 67 0
      src/luks/tests/bind-pass-with-newline-luks1
  46. 56 0
      src/luks/tests/bind-wrong-pass-luks1
  47. 47 0
      src/luks/tests/bind-wrong-pass-luks2
  48. 38 0
      src/luks/tests/meson.build
  49. 107 0
      src/luks/tests/tests-common-functions.in
  50. 74 0
      src/luks/tests/unbind-luks1
  51. 51 0
      src/luks/tests/unbind-luks2
  52. 40 0
      src/luks/tests/unbind-unbound-slot-luks1
  53. 41 0
      src/luks/tests/unbind-unbound-slot-luks2
  54. 28 10
      src/luks/udisks2/clevis-luks-udisks2.c
  55. 5 2
      src/luks/udisks2/meson.build
  56. 2 1
      src/meson.build
  57. 1 1
      src/pins/meson.build
  58. 5 5
      src/pins/sss/clevis-decrypt-test
  59. 0 1
      src/pins/sss/clevis-encrypt-sss.1.adoc
  60. 4 4
      src/pins/sss/clevis-encrypt-test
  61. 1 1
      src/pins/sss/meson.build
  62. 12 12
      src/pins/sss/pin-sss
  63. 3 3
      src/pins/sss/pin-test
  64. 22 21
      src/pins/tang/clevis-decrypt-tang
  65. 56 51
      src/pins/tang/clevis-encrypt-tang
  66. 5 1
      src/pins/tang/meson.build
  67. 17 17
      src/pins/tang/pin-tang
  68. 65 32
      src/pins/tpm2/clevis-decrypt-tpm2
  69. 115 50
      src/pins/tpm2/clevis-encrypt-tpm2
  70. 23 3
      src/pins/tpm2/meson.build
  71. 108 0
      src/pins/tpm2/pin-tpm2

+ 26 - 0
.travis.yml

@@ -0,0 +1,26 @@
+---
+language: c
+
+compiler:
+  - gcc
+
+os: linux
+sudo: required
+services: docker
+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
+
+before_install: ./.travis/docker before_install
+install: ./.travis/docker install
+script: ./.travis/docker script
+after_script: ./.travis/docker after_script
+# vim:set ts=2 sw=2 et:

+ 38 - 0
.travis/docker

@@ -0,0 +1,38 @@
+#!/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:

+ 56 - 0
.travis/install

@@ -0,0 +1,56 @@
+#!/bin/bash -ex
+
+COMMON="meson curl git make file bzip2 jose tang cryptsetup ${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
+
+    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 ${COMMON} pkgconfig openssl-devel jansson-devel \
+        findutils libjose-devel luksmeta libluksmeta-devel audit-libs-devel \
+        libudisks2-devel tpm2-tools desktop-file-utils
+    ;;
+
+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:

+ 36 - 0
.travis/script

@@ -0,0 +1,36 @@
+#!/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 -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:

+ 13 - 21
README.md

@@ -1,3 +1,5 @@
+[![Build Status](https://travis-ci.org/latchset/clevis.svg?branch=master)](https://travis-ci.org/latchset/clevis)
+
 # Clevis
 
 ## Welcome to Clevis!
@@ -58,27 +60,6 @@ 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 trusted.
 
-#### PIN: HTTP
-
-Clevis also ships a pin for performing escrow using HTTP. Please note that,
-at this time, this pin does not provide HTTPS support and is suitable only
-for use over local sockets. This provides integration with services like
-[Custodia](http://github.com/latchset/custodia).
-
-For example:
-
-```bash
-$ echo hi | clevis encrypt http '{"url": "http://server.local/key"}' > hi.jwe
-```
-
-The HTTP pin generate a new (cryptographically-strong random) key and performs
-encryption using it. It then performs a PUT request to the URL specified. It is
-understood that the server will securely store this key for later retrieval.
-During decryption, the pin will perform a GET request to retrieve the key and
-perform decryption.
-
-Patches to provide support for HTTPS and authentication are welcome.
-
 #### PIN: TPM2
 
 Clevis provides support to encrypt a key in a Trusted Platform Module 2.0 (TPM2)
@@ -169,6 +150,17 @@ Upon reboot, you will be prompted to unlock the volume using a password. In
 the background, Clevis will attempt to unlock the volume automatically. If it
 succeeds, the password prompt will be cancelled and boot will continue.
 
+#### Unlocker: Initramfs-tools
+
+When using Clevis with initramfs-tools, in order to rebuild your
+initramfs you will need to run:
+
+```bash
+sudo update-initramfs -u -k 'all'
+```
+
+Upon reboot it will behave exactly as if using Dracut.
+
 #### Unlocker: UDisks2
 
 Our UDisks2 unlocker runs in your desktop session. You should not need to

+ 3 - 2
meson.build

@@ -1,4 +1,4 @@
-project('clevis', 'c', license: 'GPL3+', version: '11',
+project('clevis', 'c', license: 'GPL3+', version: '12',
         default_options: 'c_std=c99')
 
 libexecdir = join_paths(get_option('prefix'), get_option('libexecdir'))
@@ -29,6 +29,7 @@ add_project_arguments(
   '-Wno-missing-field-initializers',
   '-Wno-unused-parameter',
   '-Wno-unknown-pragmas',
+  '-D_DEFAULT_SOURCE',
   '-D_POSIX_C_SOURCE=200112L',
   '-DBINDIR="' + bindir + '"',
   '-DCLEVIS_USER="' + get_option('user') + '"',
@@ -57,4 +58,4 @@ if a2x.found()
   endforeach
 else
   warning('Will not build man pages due to missing dependencies!')
-endif
+endif

+ 0 - 1
meson_options.txt

@@ -1,3 +1,2 @@
 option('user', type: 'string', value: 'clevis', description: 'Unprivileged user for secure clevis operations')
 option('group', type: 'string', value: 'clevis', description: 'Unprivileged group for secure clevis operations')
-

+ 33 - 11
src/bash/clevis

@@ -1,26 +1,48 @@
-# bash completion support for clevis
+# bash completion support for clevis.
 
 _clevis()
 {
-    dir=$(dirname $(which clevis))
+    local dir prev cur field
+    dir=$(dirname "$(command -v clevis)")
+    prev=${COMP_WORDS[COMP_CWORD-1]}
     cur=${COMP_WORDS[COMP_CWORD]}
-    field=$(($COMP_CWORD + 1))
+    field=$((COMP_CWORD + 1))
 
-    if [[ ${COMP_WORDS[COMP_CWORD-1]} == "clevis" ]]; then
-       name=clevis-*
+    case "${prev}" in
+    -d)
+        cur=${cur:=/dev/}
+        _filedir
+        return
+        ;;
+    -k)
+        _filedir
+        return
+        ;;
+    esac
+
+    local name suggestions
+    if [[ "${COMP_WORDS[COMP_CWORD-1]}" == "clevis" ]]; then
+        name="clevis-*"
     fi
 
-    if [[ ${COMP_WORDS[COMP_CWORD-2]} == "clevis" ]]; then
-       name=clevis-${COMP_WORDS[COMP_CWORD-1]}-*
+    if [[ "${COMP_WORDS[COMP_CWORD-2]}" == "clevis" ]]; then
+        name="clevis-${COMP_WORDS[COMP_CWORD-1]}-*"
     fi
 
-    suggestions=$(find $dir -name $name | cut -d '-' -f$field | sort | uniq)
+    suggestions=
+    if [[ -n "${name}" ]]; then
+        suggestions=$(find "${dir}" -name "${name}" -executable \
+                      | cut -d '-' -f"${field}" | sort -u)
 
-    if [[ -n $cur ]]; then
-       suggestions=$(for word in ${suggestions[@]}; do echo $word | grep $cur; done)
+        local word
+        if [[ -n "${cur}" ]]; then
+            suggestions=$(for word in "${suggestions[@]}"; do \
+                          echo "${word}" | grep -- "${cur}"; done)
+        fi
     fi
 
-    COMPREPLY=($(compgen -W "$suggestions" -- "$cur"))
+    COMPREPLY=($(compgen -W "${suggestions}" -- "${cur}"))
 }
 
 complete -F _clevis clevis
+# vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:

+ 1 - 1
src/bash/meson.build

@@ -5,4 +5,4 @@ if bashcomp.found()
   install_data('clevis', install_dir: bashcompdir)
 else
   warning('Will not install bash completion due to missing dependencies!')
-endif
+endif

+ 20 - 18
src/clevis

@@ -19,8 +19,9 @@
 #
 
 function findexe() {
-    while read -d: path; do
-        [ -f "$path/$1" -a -x "$path/$1" ] && echo "$path/$1" && return 0
+    [ $# -eq 1 ] || return 1
+    while read -r -d: path; do
+        [ -f "$path/$1" ] && [ -x "$path/$1" ] && echo "$path/$1" && return 0
     done <<< "$PATH:"
     return 1
 }
@@ -31,29 +32,30 @@ while [ $# -gt 0 ]; do
     cmd="$cmd-$1"
     shift
 
-    exe=`findexe "$cmd"` && exec "$exe" "$@"
+    exe="$(findexe "$cmd")" && exec "$exe" "$@"
 done
 
-echo >&2
-echo "Command '$cmd' is invalid" >&2
-echo >&2
-echo "Usage: clevis COMMAND [OPTIONS]" >&2
-echo >&2
+exec >&2
+echo
+echo "Command '$cmd' is invalid"
+echo
+echo "Usage: clevis COMMAND [OPTIONS]"
+echo
 
 max=0
-for f in $0-*; do
-    [ -f "$f" -a -x "$f" ] || continue
-    f=${f##*/}
+for f in "$0"-*; do
+    [ -f "$f" ] && [ -x "$f" ] || continue
+    f="${f##*/}"
     [ ${#f} -gt $max ] && max=${#f}
 done
 
-for f in $0-*; do
-    [ -f "$f" -a -x "$f" ] || continue
-    summ=`$f --summary 2>/dev/null` || continue
-    f=${f##*/}
-    printf "  %-*s %s\n" "$max" "${f//-/ }" "$summ" >&2
+for f in "$0"-*; do
+    [ -f "$f" ] && [ -x "$f" ] || continue
+    summ="$("$f" --summary 2>/dev/null)" || continue
+    f="${f##*/}"
+    printf "  %-*s %s\n" "$max" "${f//-/ }" "$summ"
 done
 
-echo >&2
+echo
 
-exit 1
+exit 2

+ 15 - 11
src/clevis-decrypt

@@ -21,26 +21,27 @@
 SUMMARY="Decrypts using the policy defined at encryption time"
 
 function findexe() {
-    while read -d: path; do
-        [ -f "$path/$1" -a -x "$path/$1" ] && echo "$path/$1" && return 0
+    [ $# -eq 1 ] || return 1
+    while read -r -d: path; do
+        [ -f "$path/$1" ] && [ -x "$path/$1" ] && echo "$path/$1" && return 0
     done <<< "$PATH:"
     return 1
 }
 
-if [ "$#" -eq 1 -a "$1" == "--summary" ]; then
+if [ $# -eq 1 ] && [ "$1" == "--summary" ]; then
     echo "$SUMMARY"
     exit 0
 fi
 
 if ! [ -t 0 ]; then
-    read -d . hdr
+    read -r -d . hdr
 
-    if ! pin=`jose fmt -q "$hdr" -SyOg clevis -Og pin -Su-`; then
+    if ! pin="$(jose fmt -q "$hdr" -SyOg clevis -Og pin -Su-)"; then
         echo "JWE is missing the required 'clevis.pin' header property!" >&2
         exit 1
     fi
 
-    if ! cmd="`findexe clevis-decrypt-$pin`"; then
+    if ! cmd="$(findexe clevis-decrypt-"$pin")"; then
         echo "Unable to locate pin '$pin'!" >&2
         exit 1
     fi
@@ -48,8 +49,11 @@ if ! [ -t 0 ]; then
     exec "$cmd" < <(echo -n "$hdr."; /bin/cat)
 fi
 
-echo >&2
-echo "Usage: clevis decrypt < JWE > PLAINTEXT" >&2
-echo >&2
-echo "$SUMMARY" >&2
-echo >&2
+exec >&2
+echo
+echo "Usage: clevis decrypt < JWE > PLAINTEXT"
+echo
+echo "$SUMMARY"
+echo
+
+exit 2

+ 0 - 21
src/clevis.1.adoc

@@ -21,26 +21,6 @@ take a policy as its first argument and plaintext on standard input and to
 encrypt the data so that it can be automatically decrypted if the policy is
 met. Lets walk through an example.
 
-== HTTP ESCROW
-
-When using the HTTP pin, we create a new, cryptographically-strong, random key.
-This key is stored in a remote HTTP escrow server (using a simple PUT or POST).
-Then at decryption time, we attempt to fetch the key back again in order to
-decrypt our data. So, for our configuration we need to pass the URL to the key
-location:
-
-    $ clevis encrypt http '{"url":"https://escrow.srv/1234"}' < PT > JWE
-
-To decrypt the data, simply provide the ciphertext (JWE):
-
-    $ clevis decrypt < JWE > PLAINTEXT
-
-Notice that we did not pass any configuration during decryption. The decrypt
-command extracted the URL (and possibly other configuration) from the JWE
-object, fetched the encryption key from the escrow and performed decryption.
-
-For more information, see link:clevis-encrypt-http.1.adoc[*clevis-encrypt-http*(1)].
-
 == TANG BINDING
 
 Clevis provides support for the Tang network binding server. Tang provides
@@ -136,7 +116,6 @@ For more information, see link:clevis-luks-bind.1.adoc[*clevis-luks-bind*(1)].
 
 == SEE ALSO
 
-link:clevis-encrypt-http.1.adoc[*clevis-encrypt-http*(1)],
 link:clevis-encrypt-tang.1.adoc[*clevis-encrypt-tang*(1)],
 link:clevis-encrypt-tpm2.1.adoc[*clevis-encrypt-tpm2*(1)],
 link:clevis-encrypt-sss.1.adoc[*clevis-encrypt-sss*(1)],

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

@@ -0,0 +1,90 @@
+#!/bin/bash
+#
+# Copyright (c) 2017 Shawn Rose
+# Author: Shawn Rose <shawnandrewrose@gmail.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/>.
+#
+
+
+PREREQ="cryptroot"
+prereqs()
+{
+     echo "$PREREQ"
+}
+
+case $1 in
+prereqs)
+     prereqs
+     exit 0
+     ;;
+esac
+
+. @initramfstoolsdir@/hook-functions
+
+die() {
+    code="$1"
+    msg="$2"
+    echo "    (ERROR): $msg" >&2
+    exit $1
+}
+
+find_binary() {
+    bin_name="$1"
+    resolved=$(which ${bin_name})
+    [ -z "$resolved" ] && die 1 "Unable to find ${bin_name}"
+    echo "$resolved"
+}
+
+if [ -n "${FORCE_CLEVIS}" ] && [ "${FORCE_CLEVIS}" != "n" ]; then
+    for f in /sbin/cryptsetup /sbin/dmsetup /lib/cryptsetup/askpass; do
+        if [ ! -e "${DESTDIR}${f}" ]; then
+            die 2 "cryptsetup utility '$f' wasn't found in the generated ramdisk image. "
+        fi
+    done
+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"
+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")
+    tpm2_unseal_bin=$(find_binary "tpm2_unseal")
+    tpm2_load_bin=$(find_binary "tpm2_load")
+    copy_exec "${tpm2_creatprimary_bin}" || die 1 "Unable to copy ${tpm2_creatprimary_bin}" 
+    copy_exec "${tpm2_unseal_bin}" || die 1 "Unable to copy ${tpm2_unseal_bin}"
+    copy_exec "${tpm2_load_bin}" || die 1 "Unable to copy ${tpm2_load_bin}"
+    for _LIBRARY in @libdir@/libtss2-tcti-device.so*; do
+        if [ -e "${_LIBRARY}" ]; then
+            copy_exec "${_LIBRARY}" || die 2 "Unable to copy ${_LIBRARY}"
+        fi
+    done
+fi
+
+
+luksmeta_bin=$(find_binary "luksmeta")
+jose_bin=$(find_binary "jose")
+copy_exec "${luksmeta_bin}" || die 2 "Unable to copy ${luksmeta_bin}"
+copy_exec "${jose_bin}" || die 2 "Unable to copy ${jose_bin}"
+
+
+copy_exec @bindir@/clevis || die 1 "@bindir@/clevis not found"
+curl_bin=$(find_binary "curl")
+awk_bin=$(find_binary "awk")
+bash_bin=$(find_binary "bash")
+copy_exec "${curl_bin}" || die 2 "Unable to copy ${curl_bin} to initrd image"
+copy_exec "${awk_bin}" || die 2 "Unable to copy ${awk_bin} to initrd image"
+copy_exec "${bash_bin}" || die 2 "Unable to copy ${bash_bin} to initrd image"

+ 6 - 0
src/initramfs-tools/hooks/meson.build

@@ -0,0 +1,6 @@
+configure_file(
+  input: 'clevis.in',
+  output: 'clevis',
+  install_dir: initramfs_hooks_dir,
+  configuration: initramfs_data,
+)

+ 16 - 0
src/initramfs-tools/meson.build

@@ -0,0 +1,16 @@
+initramfs_tools = find_program('update-initramfs', required: false)
+
+if initramfs_tools.found()
+  initramfstools_dir = '/usr/share/initramfs-tools'
+  initramfs_hooks_dir =  '/usr/share/initramfs-tools/hooks'
+  initramfs_scripts_dir = '/usr/share/initramfs-tools/scripts'
+  initramfs_data = configuration_data()
+  initramfs_data.merge_from(data)
+  initramfs_data.set('initramfstoolsdir', initramfstools_dir)
+  libdir = join_paths(get_option('prefix'), get_option('libdir'))
+  initramfs_data.set('libdir', libdir)
+  subdir('hooks')
+  subdir('scripts')
+else
+  warning('Will not install initramfs-tools module due to missing dependencies!')
+endif

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

@@ -0,0 +1,49 @@
+#!/bin/sh
+#
+# Copyright (c) 2017 Shawn Rose
+#
+# Author: Shawn Rose <shawnandrewrose@gmail.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/>.
+#
+
+PREREQ=""
+
+prereqs() {
+    echo "$PREREQ"
+}
+
+case "$1" in
+    prereqs)
+        prereqs
+        exit 0
+    ;;
+esac
+
+[ -s /run/clevis.pid ] || exit 0
+
+pid=$(cat /run/clevis.pid)
+ps -l | awk -v pid="$pid" '$4==pid { system("kill " $3) }'
+kill "$pid"
+
+# Not really worried about downing extra interfaces: they will come up
+# during the actual boot. Might make this configurable later if needed.
+
+for iface in /sys/class/net/*; do
+    if [ -e "$iface" ]; then
+        ip link  set   dev "$iface" down
+        ip addr  flush dev "$iface"
+        ip route flush dev "$iface"
+    fi
+done

+ 6 - 0
src/initramfs-tools/scripts/local-bottom/meson.build

@@ -0,0 +1,6 @@
+configure_file(
+  input: 'clevis.in',
+  output: 'clevis',
+  install_dir: join_paths(initramfs_scripts_dir, 'local-bottom'),
+  configuration: initramfs_data,
+)

+ 205 - 0
src/initramfs-tools/scripts/local-top/clevis.in

@@ -0,0 +1,205 @@
+#!/bin/bash -e
+#
+# Copyright (c) 2017 Red Hat, Inc.
+# Copyright (c) 2017 Shawn Rose
+# Copyright (c) 2017 Guilhem Moulin
+#
+# Author: Harald Hoyer <harald@redhat.com>
+# Author: Nathaniel McCallum <npmccallum@redhat.com>
+# Author: Shawn Rose <shawnandrewrose@gmail.com>
+# Author: Guilhem Moulin <guilhem@guilhem.org>
+#
+# 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/>.
+#
+
+case $1 in
+prereqs) exit 0;;
+esac
+
+# Return 0 if $pid has a file descriptor pointing to $name, 1 otherwise
+in_fds() {
+    local  pid="$1" name="$2" fd
+    for fd in /proc/$pid/fd/*; do
+        if [ -e "$fd" ]; then
+            [ "$(readlink -f "$fd")" != "$name" ] || return 0
+        fi
+    done
+    return 1
+}
+
+# Print the PID of the askpass process with a file descriptor opened to
+# /lib/cryptsetup/passfifo if there is one.
+get_askpass_pid() {
+    psinfo=$(ps) # Doing this so I don't end up matching myself
+    echo "$psinfo" | awk "/$cryptkeyscript/ { print \$1 }" | while read -r pid; do
+        if in_fds "$pid" "$PASSFIFO"; then
+            echo "$pid"
+            break
+        fi
+    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
+}
+
+# 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)
+clevisloop()
+{
+    set -e
+
+    # Set the path how we want it (Probably not all needed)
+    PATH="/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin"
+
+    if [ -x /bin/plymouth ] && plymouth --ping; then
+        cryptkeyscript='plymouth ask-for-password'
+    else
+        # This has to be escaped for awk
+        cryptkeyscript='\/lib\/cryptsetup\/askpass'
+    fi
+
+    PASSFIFO='/usr/lib/cryptsetup/passfifo'
+
+    OLD_CRYPTTAB_SOURCE=""
+
+    while true; do
+
+        pid=$(get_askpass_pid)
+
+        until [ "$pid" ] && [ -p "$PASSFIFO" ]; do
+            sleep .1
+            pid=$(get_askpass_pid)
+        done
+
+        # Import CRYPTTAB_SOURCE from the askpass process.
+        local "$(grep '^CRYPTTAB_SOURCE=' /proc/"$pid"/environ)"
+
+        # Make sure that CRYPTTAB_SOURCE is actually a block device
+        [ ! -b "$CRYPTTAB_SOURCE" ] && continue
+
+        # Make the source has changed if needed
+        [ "$CRYPTTAB_SOURCE" = "$OLD_CRYPTTAB_SOURCE" ] && continue
+
+        OLD_CRYPTTAB_SOURCE="$CRYPTTAB_SOURCE"
+
+        UUID=cb6e8904-81ff-40da-a84a-07ab9ab5715e
+        if cryptsetup isLuks --type luks1 "$CRYPTTAB_SOURCE"; then
+            # If the device is not initialized, sliently skip it.
+            luksmeta test -d "$CRYPTTAB_SOURCE" || continue
+
+            luksmeta show -d "$CRYPTTAB_SOURCE" | while read -r slot state uuid; do
+                [ "$state" == "active" ] || continue
+                [ "$uuid" == "$UUID" ] || continue
+
+                if pt="$(luks1_decrypt -d "$CRYPTTAB_SOURCE" -s "$slot" -u "$UUID")"; then
+                    echo -n "$pt" > "$PASSFIFO"
+                    break
+                fi
+            done
+        elif cryptsetup isLuks --type luks2 "$CRYPTTAB_SOURCE"; then
+            cryptsetup luksDump "$CRYPTTAB_SOURCE" | sed -rn 's|^\s+([0-9]+): clevis|\1|p' | while read -r id; do
+                jwe="$(luks2_jwe --token-id "$id" "$CRYPTTAB_SOURCE")" \
+                    || continue
+
+                if pt="$(echo -n "$jwe" | clevis decrypt)"; then
+                    echo -n "$pt" > "$PASSFIFO"
+                    break
+                fi
+            done
+        fi
+        # Now that the current device has its password, let's sleep a
+        # bit. This gives cryptsetup time to actually decrypt the
+        # device and prompt for the next password if needed.
+        sleep .5
+    done
+}
+
+. /scripts/functions
+
+
+# This is a copy  of 'all_netbootable_devices/all_non_enslaved_devices' for
+# platforms that might not provide it.
+clevis_all_netbootable_devices() {
+    for device in /sys/class/net/* ; do
+            if [ ! -e "$device/flags" ]; then
+                    continue
+            fi
+
+            loop=$(($(cat "$device/flags") & 0x8 && 1 || 0))
+            bc=$(($(cat "$device/flags") & 0x2 && 1 || 0))
+            ptp=$(($(cat "$device/flags") & 0x10 && 1 || 0))
+
+            # Skip any device that is a loopback
+            if [ $loop = 1 ]; then
+                    continue
+            fi
+
+            # Skip any device that isn't a broadcast
+            # or point-to-point.
+            if [ $bc = 0 ] && [ $ptp = 0 ]; then
+                    continue
+            fi
+
+            # Skip any enslaved device (has "master" link
+            # attribute on it)
+            device=$(basename "$device")
+            ip -o link show "$device" | grep -q -w master && continue
+            DEVICE="$DEVICE $device"
+    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
+        fi
+    done
+    return 1
+}
+if eth_check; then
+    # 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
+
+clevisloop &
+echo $! > /run/clevis.pid

+ 6 - 0
src/initramfs-tools/scripts/local-top/meson.build

@@ -0,0 +1,6 @@
+configure_file(
+  input: 'clevis.in',
+  output: 'clevis',
+  install_dir: join_paths(initramfs_scripts_dir, 'local-top'),
+  configuration: initramfs_data,
+)

+ 2 - 0
src/initramfs-tools/scripts/meson.build

@@ -0,0 +1,2 @@
+subdir('local-top')
+subdir('local-bottom')

+ 0 - 145
src/luks/clevis-luks-bind

@@ -1,145 +0,0 @@
-#!/bin/bash -e
-# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:
-#
-# Copyright (c) 2016 Red Hat, Inc.
-# Author: Harald Hoyer <harald@redhat.com>
-# Author: Nathaniel McCallum <npmccallum@redhat.com>
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-#
-
-SUMMARY="Binds a LUKS device using the specified policy"
-UUID=cb6e8904-81ff-40da-a84a-07ab9ab5715e
-
-function usage() {
-    echo >&2
-    echo "Usage: clevis luks bind [-f] [-s SLT] [-k KEY] -d DEV PIN CFG" >&2
-    echo >&2
-    echo "$SUMMARY": >&2
-    echo >&2
-    echo "  -f      Do not prompt for LUKSMeta initialization" >&2
-    echo >&2
-    echo "  -d DEV  The LUKS device on which to perform binding" >&2
-    echo >&2
-    echo "  -s SLT  The LUKS slot to use" >&2
-    echo >&2
-    echo "  -k KEY  Non-interactively read LUKS password from KEY file" >&2
-    echo "  -k -    Non-interactively read LUKS password from standard input" >&2
-    echo >&2
-    exit 1
-}
-
-if [ $# -eq 1 -a "$1" == "--summary" ]; then
-    echo "$SUMMARY"
-    exit 0
-fi
-
-while getopts ":hfd:s:k:" o; do
-    case "$o" in
-    f) FRC=-f;;
-    d) export DEV=$OPTARG;;
-    s) SLT=$OPTARG;;
-    k) KEY=$OPTARG;;
-    *) usage;;
-    esac
-done
-
-if [ -z "$DEV" ]; then
-    echo "Did not specify a device!" >&2
-    usage
-fi
-
-if ! cryptsetup isLuks "$DEV"; then
-    echo "$DEV is not a LUKS device!" >&2
-    exit 1
-fi
-
-if ! PIN=${@:$((OPTIND++)):1} || [ -z "$PIN" ]; then
-    echo "Did not specify a pin!" >&2
-    usage
-fi
-
-if ! CFG=${@:$((OPTIND++)):1} || [ -z "$CFG" ]; then
-    echo "Did not specify a pin config!" >&2
-    usage
-fi
-
-if [ -n "$KEY" ]; then
-    if [ "$KEY" == "-" ]; then
-        if cryptsetup isLuks --type luks1 "$DEV"; then
-            if ! luksmeta test -d $DEV && [ -z "$FRC" ]; then
-                echo "Cannot use '-k-' without '-f' unless already initialized!" >&2
-                usage
-            fi
-        fi
-    elif ! [ -f "$KEY" ]; then
-        echo "Key file '$KEY' not found!" >&2
-        exit 1
-    fi
-fi
-
-# Generate a key with the same entropy as the LUKS Master Key
-dump=`cryptsetup luksDump $DEV`
-if cryptsetup isLuks --type luks1 "$DEV"; then
-    filt=`sed -rn 's|MK bits:[ \t]*([0-9]+)|\1|p' <<< "$dump"`
-else
-    filt=`sed -rn 's|^\s+Key:\s+([0-9]+) bits\s*$|\1|p' <<< "$dump"`
-fi
-bits=`sort -n <<<"$filt" | tail -n 1`
-export key=`pwmake $bits`
-
-# Encrypt the new key
-jwe=`echo -n "$key" | clevis encrypt "$PIN" "$CFG"`
-
-# If necessary, initialize the LUKS volume
-if cryptsetup isLuks --type luks1 "$DEV" && ! luksmeta test -d "$DEV"; then
-    luksmeta init -d "$DEV" $FRC
-fi
-
-# Get the old key
-case "$KEY" in
-"") read -s -p "Enter existing LUKS password: " old; echo;;
- -) old=`/bin/cat`;;
- *) old=`/bin/cat "$KEY"`;;
-esac
-
-# Add the new key
-if [ -n "$SLT" ]; then
-    if ! echo -e "$old\n$key" | cryptsetup luksAddKey --key-slot $SLT $DEV; then
-        echo "Error while adding new key to LUKS header!" >&2
-        exit 1
-    fi
-elif ! SLT=`echo -e "$old\n$key" \
-        | cryptsetup luksAddKey -v $DEV \
-        | sed -rn 's|^Key slot ([0-9]+) created\.$|\1|p'`; then
-    echo "Error while adding new key to LUKS header!" >&2
-    exit 1
-fi
-
-if cryptsetup isLuks --type luks1 "$DEV"; then
-    if ! echo -n $jwe | luksmeta save -d "$DEV" -u "$UUID" -s $SLT 2>/dev/null; then
-        echo "Error while saving Clevis metadata in LUKSMeta!" >&2
-        cryptsetup luksRemoveKey "$DEV" <<<"$key"
-        exit 1
-    fi
-else
-    jwe=`jose jwe fmt -i- <<<"$jwe"` # Convert to JSON Serialization
-    tok="{\"type\":\"clevis\",\"keyslots\":[\"$SLT\"],\"jwe\":$jwe}"
-
-    if ! cryptsetup token import "$DEV" <<<"$tok" ; then
-        echo "Error while saving Clevis metadata as a LUKS token!" >&2
-        cryptsetup luksRemoveKey "$DEV" <<<"$key"
-        exit 1
-    fi
-fi

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

@@ -61,7 +61,6 @@ The images cannot be shared without also sharing a master key.
 == SEE ALSO
 
 link:clevis-luks-unlockers.7.adoc[*clevis-luks-unlockers*(7)],
-link:clevis-encrypt-http.1.adoc[*clevis-encrypt-http*(1)],
 link:clevis-encrypt-tang.1.adoc[*clevis-encrypt-tang*(1)],
 link:clevis-encrypt-sss.1.adoc[*clevis-encrypt-sss*(1)],
 link:clevis-decrypt.1.adoc[*clevis-decrypt*(1)]

+ 220 - 0
src/luks/clevis-luks-bind.in

@@ -0,0 +1,220 @@
+#!/bin/bash -e
+# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:
+#
+# Copyright (c) 2016 Red Hat, Inc.
+# Author: Harald Hoyer <harald@redhat.com>
+# Author: Nathaniel McCallum <npmccallum@redhat.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+SUMMARY="Binds a LUKS device using the specified policy"
+UUID=cb6e8904-81ff-40da-a84a-07ab9ab5715e
+
+# We require cryptsetup >= 2.0.4 to fully support LUKSv2.
+# Support is determined at build time.
+function luks2_supported() {
+    return @OLD_CRYPTSETUP@
+}
+
+function usage() {
+    exec >&2
+    echo
+    echo "Usage: clevis luks bind [-f] [-s SLT] [-k KEY] -d DEV PIN CFG"
+    echo
+    echo "$SUMMARY":
+    echo
+    echo "  -f      Do not prompt for LUKSMeta initialization"
+    echo
+    echo "  -d DEV  The LUKS device on which to perform binding"
+    echo
+    echo "  -s SLT  The LUKS slot to use"
+    echo
+    echo "  -k KEY  Non-interactively read LUKS password from KEY file"
+    echo "  -k -    Non-interactively read LUKS password from standard input"
+    echo
+    exit 2
+}
+
+if [ $# -eq 1 ] && [ "$1" == "--summary" ]; then
+    echo "$SUMMARY"
+    exit 0
+fi
+
+FRC=()
+while getopts ":hfd:s:k:" o; do
+    case "$o" in
+    f) FRC+=(-f);;
+    d) DEV="$OPTARG";;
+    s) SLT="$OPTARG";;
+    k) KEY="$OPTARG";;
+    *) usage;;
+    esac
+done
+
+if [ -z "$DEV" ]; then
+    echo "Did not specify a device!" >&2
+    usage
+fi
+
+if ! cryptsetup isLuks "$DEV"; then
+    echo "$DEV is not a LUKS device!" >&2
+    exit 1
+fi
+
+if ! PIN="${@:$((OPTIND++)):1}" || [ -z "$PIN" ]; then
+    echo "Did not specify a pin!" >&2
+    usage
+fi
+
+if ! CFG="${@:$((OPTIND++)):1}" || [ -z "$CFG" ]; then
+    echo "Did not specify a pin config!" >&2
+    usage
+fi
+
+if luks2_supported; then
+    if cryptsetup isLuks --type luks1 "$DEV"; then
+        luks_type="luks1"
+    elif cryptsetup isLuks --type luks2 "$DEV";then
+        luks_type="luks2"
+    else
+        echo "$DEV is not a supported LUKS device!" >&2
+        exit 1
+    fi
+else
+    luks_type="luks1"
+fi
+
+if [ "${luks_type}" = "luks1" ]; then
+    # The first free slot, as per cryptsetup. In connection to bug #70, we may
+    # have to wipe out the LUKSMeta slot priot to adding the new key.
+    first_free_cs_slot=$(cryptsetup luksDump "${DEV}" \
+                         | sed -rn 's|^Key Slot ([0-7]): DISABLED$|\1|p' \
+                         | head -n 1)
+    if [ -z "${first_free_cs_slot}" ]; then
+        echo "There are no more free slots in ${DEV}!" >&2
+        exit 1
+    fi
+fi
+
+if [ -n "$KEY" ]; then
+    if [ "$KEY" == "-" ]; then
+        if [ "$luks_type" == "luks1" ]; then
+            if ! luksmeta test -d "$DEV" && [ -z "${FRC[*]}" ]; then
+                echo "Cannot use '-k-' without '-f' unless already initialized!" >&2
+                usage
+            fi
+        fi
+    elif ! [ -f "$KEY" ]; then
+        echo "Key file '$KEY' not found!" >&2
+        exit 1
+    fi
+fi
+
+# Generate a key with the same entropy as the LUKS Master Key
+key="$(pwmake "$(
+cryptsetup luksDump "$DEV" \
+    | if [ "$luks_type" == "luks1" ]; then
+        sed -rn 's|MK bits:[ \t]*([0-9]+)|\1|p'
+    else
+        sed -rn 's|^\s+Key:\s+([0-9]+) bits\s*$|\1|p'
+    fi | sort -n | tail -n 1
+)")"
+
+# Encrypt the new key
+jwe="$(echo -n "$key" | clevis encrypt "$PIN" "$CFG")"
+
+# If necessary, initialize the LUKS volume
+if [ "$luks_type" == "luks1" ] && ! luksmeta test -d "$DEV"; then
+    luksmeta init -d "$DEV" "${FRC[@]}"
+fi
+
+# Get the existing key.
+case "$KEY" in
+"") read -r -s -p "Enter existing LUKS password: " existing_key; echo;;
+ -) existing_key="$(/bin/cat)";;
+ *) ! IFS= read -rd '' existing_key < "$KEY";;
+esac
+
+# Check if the key is valid.
+if ! cryptsetup luksOpen --test-passphrase "${DEV}" \
+        --key-file <(echo -n "${existing_key}"); then
+    exit 1
+fi
+
+if [ "$luks_type" == "luks1" ]; then
+    # In certain circumstances, we may have LUKSMeta slots "not in sync" with
+    # cryptsetup, which means we will try to save LUKSMeta metadata over an
+    # already used or partially used slot -- github issue #70.
+    # If that is the case, let's wipe the LUKSMeta slot here prior to saving.
+    if read -r _ state uuid < <(luksmeta show -d "${DEV}" \
+            | grep "^${first_free_cs_slot} *"); then
+        if [ "${state}" = "inactive" ] && [ "${uuid}" = "${UUID}" ]; then
+            luksmeta wipe -f -d "${DEV}" -s "${first_free_cs_slot}"
+        fi
+    fi
+fi
+
+# Add the new key.
+if [ -n "$SLT" ]; then
+    cryptsetup luksAddKey --key-slot "$SLT" --key-file \
+        <(echo -n "$existing_key") "$DEV"
+else
+    if [ $luks_type == "luks2" ]; then
+        readarray -t usedSlotsBeforeAddKey < <(cryptsetup luksDump "${DEV}" \
+            | sed -rn 's|^\s+([0-9]+): luks2$|\1|p')
+    else
+        readarray -t usedSlotsBeforeAddKey < <(cryptsetup luksDump "${DEV}" \
+            | sed -rn 's|^Key Slot ([0-7]): ENABLED$|\1|p')
+    fi
+    cryptsetup luksAddKey --key-file <(echo -n "${existing_key}") "$DEV"
+fi < <(echo -n "${key}")
+if [ $? -ne 0 ]; then
+    echo "Error while adding new key to LUKS header!" >&2
+    exit 1
+fi
+
+#Determine slot used by new key if a desired slot was not specified
+if [ -z "$SLT" ]; then
+    if [ "$luks_type" == "luks2" ]; then
+        readarray -t usedSlotsAfterAddKey < <(cryptsetup luksDump "${DEV}" \
+            | sed -rn 's|^\s+([0-9]+): luks2$|\1|p')
+    else
+        readarray -t usedSlotsAfterAddKey < <(cryptsetup luksDump "${DEV}" \
+            | sed -rn 's|^Key Slot ([0-7]): ENABLED$|\1|p')
+    fi
+    for i in "${usedSlotsAfterAddKey[@]}"; do
+        if [[ ! " ${usedSlotsBeforeAddKey[@]} " =~ " ${i} " ]]; then
+            SLT=$i
+            break
+        fi
+    done
+fi
+
+if [ -z "$SLT" ]; then
+	echo "Error while adding new key to LUKS header! Key slot is undefined." >&2
+	exit 1
+fi
+
+if [ "$luks_type" == "luks1" ]; then
+    echo -n "$jwe" | luksmeta save -d "$DEV" -u "$UUID" -s "$SLT" 2>/dev/null
+else
+    printf '{"type":"clevis","keyslots":["%s"],"jwe":%s}' "$SLT" "$(jose jwe fmt -i- <<< "$jwe")" \
+        | cryptsetup token import "$DEV"
+fi
+if [ $? -ne 0 ]; then
+    echo "Error while saving Clevis metadata in LUKSMeta!" >&2
+    echo -n "$key" | cryptsetup luksRemoveKey "$DEV"
+    exit 1
+fi

+ 56 - 34
src/luks/clevis-luks-unbind

@@ -21,31 +21,39 @@
 SUMMARY="Unbinds a pin bound to 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() {
-    echo >&2
-    echo "Usage: clevis luks unbind -d DEV -s SLT" >&2
-    echo >&2
-    echo "$SUMMARY": >&2
-    echo >&2
-    echo "  -d DEV  The bound LUKS device" >&2
-    echo >&2
-    echo "  -s SLOT The LUKS slot number for the pin unbind" >&2
-    echo >&2
-    echo "  -f      Do not ask for confirmation and wipe slot in batch-mode" >&2
-    echo >&2
-    exit 1
+    exec >&2
+    echo
+    echo "Usage: clevis luks unbind -d DEV -s SLT"
+    echo
+    echo "$SUMMARY":
+    echo
+    echo "  -d DEV  The bound LUKS device"
+    echo
+    echo "  -s SLOT The LUKS slot number for the pin unbind"
+    echo
+    echo "  -f      Do not ask for confirmation and wipe slot in batch-mode"
+    echo
+    exit 2
 }
 
-if [ $# -eq 1 -a "$1" == "--summary" ]; then
+if [ $# -eq 1 ] && [ "$1" == "--summary" ]; then
     echo "$SUMMARY"
     exit 0
 fi
 
+FRC=()
 while getopts ":d:s:f" o; do
     case "$o" in
-    f) FRC=-q;;
-    d) DEV=$OPTARG;;
-    s) SLT=$OPTARG;;
+    f) FRC+=(-q);;
+    d) DEV="$OPTARG";;
+    s) SLT="$OPTARG";;
     *) usage;;
     esac
 done
@@ -60,53 +68,67 @@ if [ -z "$SLT" ]; then
     usage
 fi
 
-if cryptsetup isLuks --type luks1 "$DEV"; then
-    if ! luksmeta test -d $DEV 2>/dev/null; then
+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
+
+if [ "$luks_type" == "luks1" ]; then
+    if ! luksmeta test -d "$DEV" 2>/dev/null; then
         echo "The $DEV device does not contain a LUKSMeta header!" >&2
         exit 1
     fi
 
-    read -r slot active uuid <<< $(luksmeta show -d "$DEV" | grep "^$SLT *")
+    read -r slot state uuid < <(luksmeta show -d "$DEV" | grep "^$SLT *")
 
     if [ "$uuid" == "empty" ]; then
         echo "The LUKSMeta slot $SLT on device $DEV is already empty." >&2
         exit 1
     fi
 
-    [ "$active" == "active" ] && KILL=true
-
-elif cryptsetup isLuks --type luks2 "$DEV"; then
-    dump=`cryptsetup luksDump "$DEV"`
-    grep -q "^\s*$SLT: luks2" <<<"$dump" && KILL=true
-    TOK=`grep -E -B1 "^\s+Keyslot:\s+$SLT$" <<<"$dump" \
-        | sed -rn 's|^\s+([0-9]+): clevis|\1|p'`
+    [ "$state" == "active" ] && KILL=true
 
-else
-    echo "$DEV is not a supported LUKS device!" >&2
-    exit 1
+elif [ "$luks_type" == "luks2" ]; then
+    dump="$(cryptsetup luksDump "$DEV")"
+    grep -q "^\s*$SLT: luks2" <<< "$dump" && KILL=true
+    TOK="$(grep -E -B1 "^\s+Keyslot:\s+$SLT$" <<< "$dump" \
+        | sed -rn 's|^\s+([0-9]+): clevis|\1|p')"
 fi
 
-if [ -z "$FRC" ]; then
+if [ -z "${FRC[*]}" ]; then
     echo "The unbind operation will wipe a slot. This operation is unrecoverable." >&2
     read -r -p "Do you wish to erase LUKS slot $SLT on $DEV? [ynYN] " ans < /dev/tty
     [[ "$ans" =~ ^[yY]$ ]] || exit 0
 fi
 
 if [ -n "$KILL" ]; then
-    if ! cryptsetup luksKillSlot "$DEV" "$SLT" $FRC; then
+    if ! cryptsetup luksKillSlot "$DEV" "$SLT" "${FRC[@]}"; then
         echo "LUKS slot $SLT for device $DEV couldn't be deleted"
         exit 1
     fi
 fi
 
-if cryptsetup isLuks --type luks1 "$DEV"; then
+if [ "$luks_type" == "luks1" ]; then
     if ! luksmeta wipe -f -d "$DEV" -u "$UUID" -s "$SLT"; then
         echo "LUKSMeta slot $SLT for device $DEV couldn't be deleted"
         exit 1
     fi
-elif cryptsetup isLuks --type luks2 "$DEV" && [ -n "$TOK" ]; then
+elif [ "$luks_type" == "luks2" ] && [ -n "$TOK" ]; then
     if ! cryptsetup token remove --token-id "$TOK" "$DEV"; then
         echo "Error while removing token $TOK from LUKS device $DEV!" >&2
         exit 1
     fi
-fi
+fi

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

@@ -1,80 +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
-
-function usage() {
-    echo >&2
-    echo "Usage: clevis luks unlock -d DEV [-n NAME]" >&2
-    echo >&2
-    echo "$SUMMARY": >&2
-    echo >&2
-    echo "  -d DEV  The LUKS device on which to perform unlocking" >&2
-    echo >&2
-    echo "  -n NAME The name of the unlocked device node" >&2
-    echo >&2
-    exit 1
-}
-
-if [ $# -eq 1 -a "$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
-
-NAME=${NAME:-luks-`cryptsetup luksUUID $DEV`}
-
-if cryptsetup isLuks --type luks1 "$DEV"; then
-    while read -r slot state uuid; do
-        [ "$state" != "active" ] && continue
-        [ "$uuid" != "$UUID" ] && continue
-
-        if pt=`luksmeta load -d $DEV -s $slot -u $UUID | clevis decrypt`; then
-            echo -n "$pt" | cryptsetup open -d- "$DEV" "$NAME"
-            exit 0
-        fi
-    done <<< "$(luksmeta show -d "$DEV")"
-
-elif cryptsetup isLuks --type luks2 "$DEV"; then
-    for id in `cryptsetup luksDump "$DEV" | sed -rn 's|^\s+([0-9]+): clevis|\1|p'`; do
-        tok=`cryptsetup token export --token-id "$id" "$DEV"`
-        jwe=`jose fmt -j- -Og jwe -o- <<<"$tok" | jose jwe fmt -i- -c`
-
-        if pt=`echo -n "$jwe" | clevis decrypt`; then
-            echo -n "$pt" | cryptsetup open -d- "$DEV" "$NAME"
-            exit 0
-        fi
-    done
-fi
-
-exit 1

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

@@ -0,0 +1,130 @@
+#!/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

+ 30 - 5
src/luks/meson.build

@@ -1,21 +1,46 @@
-libcryptsetup = dependency('libcryptsetup', version: '>=2.0.4', required: false)
+
+luksmeta_data = configuration_data()
 luksmeta = dependency('luksmeta', version: '>=8', required: false)
 pwmake = find_program('pwmake', required: false)
 
+libcryptsetup = dependency('libcryptsetup', version: '>=2.0.4', required: false)
+if libcryptsetup.found()
+    luksmeta_data.set('OLD_CRYPTSETUP', '0')
+else
+    libcryptsetup = dependency('libcryptsetup', version: '>=2.0.2', required: false)
+    if libcryptsetup.found()
+        luksmeta_data.set('OLD_CRYPTSETUP', '1')
+        warning('Old version of cryptsetup found, forcing use of luksmeta')
+    endif
+endif
+
+clevis_luks_bind = configure_file(input: 'clevis-luks-bind.in',
+               output: 'clevis-luks-bind',
+               configuration: luksmeta_data)
+
+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')
 
-  bins += join_paths(meson.current_source_dir(), 'clevis-luks-unbind')
+  bins += clevis_luks_unbind
   mans += join_paths(meson.current_source_dir(), 'clevis-luks-unbind.1')
 
-  bins += join_paths(meson.current_source_dir(), 'clevis-luks-unlock')
+  bins += clevis_luks_unlock
   mans += join_paths(meson.current_source_dir(), 'clevis-luks-unlock.1')
 
-  bins += join_paths(meson.current_source_dir(), 'clevis-luks-bind')
+  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')
 else
   warning('Will not install LUKS support due to missing dependencies!')
-endif
+endif
+
+# Tests.
+subdir('tests')

+ 42 - 18
src/luks/systemd/clevis-luks-askpass

@@ -27,14 +27,39 @@ path=/run/systemd/ask-password
 while getopts ":lp:" o; do
     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
+    for question in "$path"/ask.*; do
         metadata=false
         unlocked=false
         d=
@@ -47,44 +72,43 @@ while true; do
             esac
         done < "$question"
 
-        [ -z "$d" -o -z "$s" ] && continue
+        [ "$d" ] && [ "$s" ] || continue
 
         if cryptsetup isLuks --type luks1 "$d"; then
             # If the device is not initialized, sliently skip it.
             luksmeta test -d "$d" || continue
 
             while read -r slot state uuid; do
-                [ "$state" != "active" ] && continue
-                [ "$uuid" != "$UUID" ] && continue
+                [ "$state" == "active" ] || continue
+                [ "$uuid" == "$UUID" ] || continue
                 metadata=true
 
-                if pt="`luksmeta load -d $d -s $slot -u $UUID | clevis decrypt`"; then
-                    echo -n "+$pt" | nc -U -u --send-only "$s"
+                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
-            ids=`cryptsetup luksDump "$d" | sed -rn 's|^\s+([0-9]+): clevis|\1|p'`
-            for id in $ids; do
-                tok=`cryptsetup token export --token-id "$id" "$d"`
-                jwe=`jose fmt -j- -Og jwe -o- <<<"$tok" | jose jwe fmt -i- -c`
+            while read -r id; do
+                jwe="$(luks2_jwe --token-id "$id" "$d")" \
+                    || continue
                 metadata=true
 
-                if pt=`echo -n "$jwe" | clevis decrypt`; then
-                    echo -n "+$pt" | nc -U -u --send-only "$s"
+                if pt="$(echo -n "$jwe" | clevis decrypt)"; then
+                    echo -n "+$pt" | ncat -U -u --send-only "$s"
                     unlocked=true
                     break
                 fi
-            done
+            done < <(cryptsetup luksDump "$d" | sed -rn 's|^\s+([0-9]+): clevis|\1|p')
         fi
 
-        [ $metadata == true ] || continue
-        [ $unlocked == true ] && continue
-        todo=$((todo + 1))
+        [ "$metadata" == true ] || continue
+        [ "$unlocked" == true ] && continue
+        ((todo++))
     done
 
-    if [ $todo -eq 0 ] || [ "$loop" != "true" ]; then
+    if [ $todo -eq 0 ] || [ "$loop" != true ]; then
         break;
     fi
 

+ 14 - 0
src/luks/systemd/dracut/clevis-pin-sss/meson.build

@@ -0,0 +1,14 @@
+dracut = dependency('dracut', required: false)
+
+if dracut.found()
+  dracutdir = dracut.get_pkgconfig_variable('dracutmodulesdir') + '/60' + meson.project_name() + '-pin-sss'
+
+  configure_file(
+    input: 'module-setup.sh.in',
+    output: 'module-setup.sh',
+    install_dir: dracutdir,
+    configuration: data,
+  )
+else
+  warning('Will not install dracut module clevis-pin-sss due to missing dependencies!')
+endif

+ 28 - 0
src/luks/systemd/dracut/clevis-pin-sss/module-setup.sh.in

@@ -0,0 +1,28 @@
+#!/bin/bash
+# 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/>.
+#
+
+depends() {
+    echo clevis
+    return 0
+}
+
+install() {
+    inst clevis-decrypt-sss
+}

+ 14 - 0
src/luks/systemd/dracut/clevis-pin-tang/meson.build

@@ -0,0 +1,14 @@
+dracut = dependency('dracut', required: false)
+
+if dracut.found()
+  dracutdir = dracut.get_pkgconfig_variable('dracutmodulesdir') + '/60' + meson.project_name() + '-pin-tang'
+
+  configure_file(
+    input: 'module-setup.sh.in',
+    output: 'module-setup.sh',
+    install_dir: dracutdir,
+    configuration: data,
+  )
+else
+  warning('Will not install dracut module clevis-pin-tang due to missing dependencies!')
+endif

+ 36 - 0
src/luks/systemd/dracut/clevis-pin-tang/module-setup.sh.in

@@ -0,0 +1,36 @@
+#!/bin/bash
+# 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/>.
+#
+
+depends() {
+    echo clevis network
+    return 0
+}
+
+cmdline() {
+    echo "rd.neednet=1"
+}
+
+install() {
+    cmdline > "${initdir}/etc/cmdline.d/99clevis-pin-tang.conf"
+
+    inst_multiple \
+	clevis-decrypt-tang \
+	curl
+}

+ 14 - 0
src/luks/systemd/dracut/clevis-pin-tpm2/meson.build

@@ -0,0 +1,14 @@
+dracut = dependency('dracut', required: false)
+
+if dracut.found()
+  dracutdir = dracut.get_pkgconfig_variable('dracutmodulesdir') + '/60' + meson.project_name() + '-pin-tpm2'
+
+  configure_file(
+    input: 'module-setup.sh.in',
+    output: 'module-setup.sh',
+    install_dir: dracutdir,
+    configuration: data,
+  )
+else
+  warning('Will not install dracut module clevis-pin-tpm2 due to missing dependencies!')
+endif

+ 40 - 0
src/luks/systemd/dracut/clevis-pin-tpm2/module-setup.sh.in

@@ -0,0 +1,40 @@
+#!/bin/bash
+# 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/>.
+#
+
+check() {
+    require_binaries clevis-decrypt-tpm2 tpm2_createprimary tpm2_unseal tpm2_load || return 1
+    require_any_binary tpm2_pcrread tpm2_pcrlist || return 1
+    return 0
+}
+
+depends() {
+    echo clevis
+    return 0
+}
+
+install() {
+    inst_multiple clevis-decrypt-tpm2 tpm2_createprimary tpm2_unseal tpm2_load
+    inst_multiple -o tpm2_pcrread tpm2_pcrlist
+    inst_libdir_file "libtss2-tcti-device.so*"
+}
+
+installkernel() {
+    hostonly='' instmods =drivers/char/tpm
+}

src/luks/systemd/dracut/clevis-hook.sh.in → src/luks/systemd/dracut/clevis/clevis-hook.sh.in


+ 21 - 0
src/luks/systemd/dracut/clevis/meson.build

@@ -0,0 +1,21 @@
+dracut = dependency('dracut', required: false)
+
+if dracut.found()
+  dracutdir = dracut.get_pkgconfig_variable('dracutmodulesdir') + '/60' + meson.project_name()
+
+  configure_file(
+    input: 'module-setup.sh.in',
+    output: 'module-setup.sh',
+    install_dir: dracutdir,
+    configuration: data,
+  )
+
+  configure_file(
+    input: 'clevis-hook.sh.in',
+    output: 'clevis-hook.sh',
+    install_dir: dracutdir,
+    configuration: data,
+  )
+else
+  warning('Will not install dracut module due to missing dependencies!')
+endif

+ 6 - 37
src/luks/systemd/dracut/module-setup.sh.in

@@ -19,55 +19,24 @@
 #
 
 depends() {
-    echo crypt systemd network
-    return 0
-}
-
-cmdline() {
-    echo "rd.neednet=1"
+    echo crypt systemd
+    return 255
 }
 
 install() {
-    local ret=0
-
-    cmdline > "${initdir}/etc/cmdline.d/99clevis.conf"
-
     inst_hook initqueue/online 60 "$moddir/clevis-hook.sh"
     inst_hook initqueue/settled 60 "$moddir/clevis-hook.sh"
 
-    inst_multiple /etc/services \
-        clevis-decrypt-http \
-        clevis-decrypt-tang \
-        clevis-decrypt-sss \
+    inst_multiple \
+	/etc/services \
         @libexecdir@/clevis-luks-askpass \
         clevis-decrypt \
+        cryptsetup \
         luksmeta \
         clevis \
         mktemp \
-        curl \
         jose \
-        nc
-
-    for cmd in clevis-decrypt-tpm2 \
-	tpm2_createprimary \
-	tpm2_unseal \
-	tpm2_load; do
-
-	if ! find_binary "$cmd" &>/dev/null; then
-	    ((ret++))
-	fi
-    done
-
-    if (($ret == 0)); then
-	inst_multiple clevis-decrypt-tpm2 \
-	    tpm2_createprimary \
-	    tpm2_unseal \
-	    tpm2_load
-    fi
+        ncat
 
     dracut_need_initqueue
 }
-
-installkernel() {
-    hostonly='' instmods =drivers/char/tpm
-}

+ 4 - 21
src/luks/systemd/dracut/meson.build

@@ -1,21 +1,4 @@
-dracut = dependency('dracut', required: false)
-
-if dracut.found()
-  dracutdir = dracut.get_pkgconfig_variable('dracutmodulesdir') + '/60' + meson.project_name()
-
-  configure_file(
-    input: 'module-setup.sh.in',
-    output: 'module-setup.sh',
-    install_dir: dracutdir,
-    configuration: data,
-  )
-
-  configure_file(
-    input: 'clevis-hook.sh.in',
-    output: 'clevis-hook.sh',
-    install_dir: dracutdir,
-    configuration: data,
-  )
-else
-  warning('Will not install dracut module due to missing dependencies!')
-endif
+subdir('clevis')
+subdir('clevis-pin-tang')
+subdir('clevis-pin-tpm2')
+subdir('clevis-pin-sss')

+ 1 - 1
src/luks/systemd/meson.build

@@ -16,4 +16,4 @@ if systemd.found()
   install_data('clevis-luks-askpass', install_dir: libexecdir)
 else
   warning('Will not install systemd support due to missing dependencies!')
-endif
+endif

+ 102 - 0
src/luks/tests/bind-already-used-luksmeta-slot

@@ -0,0 +1,102 @@
+#!/bin/bash -ex
+# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:
+#
+# Copyright (c) 2019 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
+
+on_exit() {
+    [ -d "${TMP}" ] && rm -rf "${TMP}"
+}
+
+trap 'on_exit' EXIT
+trap 'exit' ERR
+
+TMP="$(mktemp -d)"
+
+ADV="${TMP}/adv.jws"
+create_tang_adv "${ADV}"
+CFG="$(printf '{"url":"foobar","adv":"%s"}' "$ADV")"
+
+# LUKS1.
+DEV="${TMP}/luks1-device"
+UUID="cb6e8904-81ff-40da-a84a-07ab9ab5715e"
+
+# We can have a "partially" used if it is an inactive slot that has an UUID
+# already:
+# 1 inactive cb6e8904-81ff-40da-a84a-07ab9ab5715e
+# We end up in this situation if the cryptsetup step adding the key failed,
+# for instance because we provided a wrong pass phrase, and luksmeta saved
+# data anyway. We used to have an issue with clevis luks bind script, in which
+# we would still run luksmeta save even if the cryptsetup step failed.
+
+bind_and_verify() {
+    local DEV="${1}"
+    local PASS="${2}"
+    local SLT="${3}"
+
+    if ! clevis luks bind -f -d "${DEV}" tang "${CFG}" <<< "${PASS}"; then
+        error "${TEST}: Binding is expected to succeed when given a correct (${PASS}) password." >&2
+    fi
+
+    if ! read -r _ state uuid < <(luksmeta show -d "${DEV}" | grep "^${SLT} *"); then
+        error "${TEST}: Error reading LUKSmeta info for slot ${SLT} of ${DEV}." >&2
+    fi
+
+    if [ "${state}" != "active" ]; then
+        error "${TEST}: state (${state}) is expected to be 'active'." >&2
+    fi
+
+    if [ "${uuid}" != "${UUID}" ]; then
+        error "${TEST}: UUID ($uuid) is expected to be '${UUID}'." >&2
+    fi
+}
+
+SLT=1
+NEW_PASS="new-pass"
+PASS="${DEFAULT_PASS}"
+WRONG_PASS="wrong-password-here"
+
+new_device "luks1" "${DEV}"
+luksmeta init -f -d "${DEV}"
+if cryptsetup luksAddKey "${DEV}" < <(echo "${WRONG_PASS}"; echo -n "${NEW_PASS}"); then
+    error "${TEST}: cryptsetup should not succeed in adding key when given a wrong passphrase." >&2
+fi
+
+# Ok, the cryptsetup step failed, since we gave a wrong password. That means
+# that right now the luksmeta slot is inactive. Let's simulate the bad
+# condition by saving the UUID there anyway.
+echo "foo" | luksmeta save -d "${DEV}" -u "${UUID}"
+
+# Verify we have slot 1 like this:
+# # 1 inactive cb6e8904-81ff-40da-a84a-07ab9ab5715e
+if ! read -r _ state uuid < <(luksmeta show -d "${DEV}" | grep "^${SLT} *"); then
+    error "${TEST}: Error reading LUKSmeta info for slot ${SLT} of ${DEV}." >&2
+fi
+
+if [ "${state}" != "inactive" ]; then
+    error "${TEST}: state (${state}) is expected to be 'inactive', in case #1." >&2
+fi
+
+if [ "${uuid}" != "${UUID}" ]; then
+    error "${TEST}: UUID ($uuid) is expected to be '${UUID}', in case #1." >&2
+fi
+
+# Verify if can bind correctly in this situation.
+bind_and_verify "${DEV}" "${PASS}" "1"

+ 59 - 0
src/luks/tests/bind-key-file-non-interactive-luks1

@@ -0,0 +1,59 @@
+#!/bin/bash -ex
+# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:
+#
+# Copyright (c) 2019 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
+
+on_exit() {
+    [ -d "${TMP}" ] && rm -rf "${TMP}"
+}
+
+trap 'on_exit' EXIT
+trap 'exit' ERR
+
+TMP="$(mktemp -d)"
+
+ADV="${TMP}/adv.jws"
+create_tang_adv "${ADV}"
+CFG="$(printf '{"url":"foobar","adv":"%s"}' "$ADV")"
+UUID="cb6e8904-81ff-40da-a84a-07ab9ab5715e"
+KEYFILE="${TMP}/key"
+PASS=$(openssl rand -hex 8)
+echo -n "${PASS}" > "${KEYFILE}"
+
+# LUKS1.
+DEV="${TMP}/luks1-device"
+new_device_keyfile "luks1" "${DEV}" "${KEYFILE}"
+if ! clevis luks bind -f -k "${KEYFILE}" -d "${DEV}" tang "${CFG}"; then
+    error "${TEST}: Binding is expected to succeed when given a correct (${KEYFILE})." >&2
+fi
+
+SLT=1
+if ! read -r _ state uuid < <(luksmeta show -d "${DEV}" | grep "^${SLT} *"); then
+    error "${TEST}: Error reading LUKSmeta info for slot ${SLT} of ${DEV}." >&2
+fi
+
+if [ "${state}" != "active" ]; then
+    error "${TEST}: state (${state}) is expected to be 'active'." >&2
+fi
+
+if [ "${uuid}" != "${UUID}" ]; then
+    error "${TEST}: UUID ($uuid) is expected to be '${UUID}'." >&2
+fi

+ 57 - 0
src/luks/tests/bind-luks1

@@ -0,0 +1,57 @@
+#!/bin/bash -ex
+# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:
+#
+# Copyright (c) 2019 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
+trap 'exit' ERR
+
+TMP="$(mktemp -d)"
+
+ADV="${TMP}/adv.jws"
+create_tang_adv "${ADV}"
+CFG="$(printf '{"url":"foobar","adv":"%s"}' "$ADV")"
+
+# LUKS1.
+DEV="${TMP}/luks1-device"
+UUID="cb6e8904-81ff-40da-a84a-07ab9ab5715e"
+new_device "luks1" "${DEV}"
+
+if ! clevis luks bind -f -d "${DEV}" tang "${CFG}" <<< "${DEFAULT_PASS}"; then
+    error "${TEST}: Binding is expected to succeed when given a correct (${DEFAULT_PASS}) password." >&2
+fi
+
+SLT=1
+if ! read -r _ state uuid < <(luksmeta show -d "${DEV}" | grep "^${SLT} *"); then
+    error "${TEST}: Error reading LUKSmeta info for slot ${SLT} of ${DEV}." >&2
+fi
+
+if [ "${state}" != "active" ]; then
+    error "${TEST}: state (${state}) is expected to be 'active'." >&2
+fi
+
+if [ "${uuid}" != "${UUID}" ]; then
+    error "${TEST}: UUID ($uuid) is expected to be '${UUID}'." >&2
+fi

+ 48 - 0
src/luks/tests/bind-luks2

@@ -0,0 +1,48 @@
+#!/bin/bash -ex
+# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:
+#
+# Copyright (c) 2019 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
+trap 'exit' ERR
+
+if ! luks2_supported; then
+    error "{TEST}: LUKS2 is not supported."
+fi
+
+TMP="$(mktemp -d)"
+
+ADV="${TMP}/adv.jws"
+create_tang_adv "${ADV}"
+CFG="$(printf '{"url":"foobar","adv":"%s"}' "$ADV")"
+
+# LUKS2.
+
+DEV="${TMP}/luks2-device"
+new_device "luks2" "${DEV}"
+
+if ! clevis luks bind -d "${DEV}" tang "${CFG}" <<< "${DEFAULT_PASS}"; then
+    error "${TEST}: Binding is expected to succeed when given a correct (${DEFAULT_PASS}) password." >&2
+fi

+ 70 - 0
src/luks/tests/bind-pass-with-newline-keyfile-luks1

@@ -0,0 +1,70 @@
+#!/bin/bash -ex
+# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:
+#
+# Copyright (c) 2019 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
+
+on_exit() {
+    [ -d "${TMP}" ] && rm -rf "${TMP}"
+}
+
+trap 'on_exit' EXIT
+trap 'exit' ERR
+
+TMP="$(mktemp -d)"
+
+ADV="${TMP}/adv.jws"
+create_tang_adv "${ADV}"
+CFG="$(printf '{"url":"foobar","adv":"%s"}' "$ADV")"
+
+# LUKS1.
+DEV="${TMP}/luks1-device"
+UUID="cb6e8904-81ff-40da-a84a-07ab9ab5715e"
+
+# Using newlines and special chars in the passphrase.
+PASS="foo
+
+bar
+
+\\\&#@@&@*!)(
+
+$$$
+"
+
+KEYFILE="${TMP}/key"
+echo -n "${PASS}" > "${KEYFILE}"
+
+new_device_keyfile "luks1" "${DEV}" "${KEYFILE}"
+if ! clevis luks bind -f -k "${KEYFILE}" -d "${DEV}" tang "${CFG}"; then
+    error "${TEST}: Binding is expected to succeed when given a correct (${KEYFILE}) password." >&2
+fi
+
+SLT=1
+if ! read -r _ state uuid < <(luksmeta show -d "${DEV}" | grep "^${SLT} *"); then
+    error "${TEST}: Error reading LUKSmeta info for slot ${SLT} of ${DEV}." >&2
+fi
+
+if [ "${state}" != "active" ]; then
+    error "${TEST}: state (${state}) is expected to be 'active'." >&2
+fi
+
+if [ "${uuid}" != "${UUID}" ]; then
+    error "${TEST}: UUID ($uuid) is expected to be '${UUID}'." >&2
+fi

+ 67 - 0
src/luks/tests/bind-pass-with-newline-luks1

@@ -0,0 +1,67 @@
+#!/bin/bash -ex
+# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:
+#
+# Copyright (c) 2019 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
+
+on_exit() {
+    [ -d "${TMP}" ] && rm -rf "${TMP}"
+}
+
+trap 'on_exit' EXIT
+trap 'exit' ERR
+
+TMP="$(mktemp -d)"
+
+ADV="${TMP}/adv.jws"
+create_tang_adv "${ADV}"
+CFG="$(printf '{"url":"foobar","adv":"%s"}' "$ADV")"
+
+# LUKS1.
+DEV="${TMP}/luks1-device"
+UUID="cb6e8904-81ff-40da-a84a-07ab9ab5715e"
+
+# Using newlines and special chars in the passphrase.
+PASS="foo
+
+bar
+
+\\\&#@@&@*!)(
+
+$$$
+"
+new_device "luks1" "${DEV}" "${PASS}"
+
+if ! clevis luks bind -f -d "${DEV}" tang "${CFG}" <<< "${PASS}"; then
+    error "${TEST}: Binding is expected to succeed when given a correct (${PASS}) password." >&2
+fi
+
+SLT=1
+if ! read -r _ state uuid < <(luksmeta show -d "${DEV}" | grep "^${SLT} *"); then
+    error "${TEST}: Error reading LUKSmeta info for slot ${SLT} of ${DEV}." >&2
+fi
+
+if [ "${state}" != "active" ]; then
+    error "${TEST}: state (${state}) is expected to be 'active'." >&2
+fi
+
+if [ "${uuid}" != "${UUID}" ]; then
+    error "${TEST}: UUID ($uuid) is expected to be '${UUID}'." >&2
+fi

+ 56 - 0
src/luks/tests/bind-wrong-pass-luks1

@@ -0,0 +1,56 @@
+#!/bin/bash -ex
+# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:
+#
+# Copyright (c) 2019 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
+trap 'exit' ERR
+
+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}"
+
+if clevis luks bind -f -d "${DEV}" tang "${CFG}" <<< "wrong-passphrase"; then
+    error "${TEST}: Binding is expected to fail when given a wrong password." >&2
+fi
+
+SLT=1
+if ! read -r _ state uuid < <(luksmeta show -d "${DEV}" | grep "^${SLT} *"); then
+    error "${TEST}: Error reading LUKSmeta info for slot ${SLT} of ${DEV}." >&2
+fi
+
+if [ "${state}" != "inactive" ]; then
+    error "${TEST}: state (${state}) is expected to be 'inactive'." >&2
+fi
+
+if [ "${uuid}" != "empty" ]; then
+    error "${TEST}: UUID ($uuid) is expected to be 'empty'." >&2
+fi

+ 47 - 0
src/luks/tests/bind-wrong-pass-luks2

@@ -0,0 +1,47 @@
+#!/bin/bash -ex
+# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:
+#
+# Copyright (c) 2019 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
+trap 'exit' ERR
+
+if ! luks2_supported; then
+    error "{TEST}: LUKS2 is not supported."
+fi
+
+TMP="$(mktemp -d)"
+
+ADV="${TMP}/adv.jws"
+create_tang_adv "${ADV}"
+CFG="$(printf '{"url":"foobar","adv":"%s"}' "$ADV")"
+
+# LUKS2.
+DEV="${TMP}/luks2-device"
+new_device "luks2" "${DEV}"
+
+if clevis luks bind -d "${DEV}" tang "${CFG}" <<< "wrong-passphrase"; then
+    error "${TEST}: Binding is expected to fail when given a wrong password." >&2
+fi

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

@@ -0,0 +1,38 @@
+common_functions = configure_file(input: 'tests-common-functions.in',
+  output: 'tests-common-functions',
+  configuration: luksmeta_data,
+  install: false
+)
+
+env = environment()
+env.prepend('PATH',
+  join_paths(meson.source_root(), 'src'),
+  join_paths(meson.source_root(), 'src', 'luks'),
+  join_paths(meson.source_root(), 'src', 'pins', 'tang'),
+  join_paths(meson.source_root(), 'src', 'pins', 'tpm2'),
+  meson.current_source_dir(),
+  meson.current_build_dir(),
+  join_paths(meson.build_root(), 'src'),
+  join_paths(meson.build_root(), 'src', 'luks'),
+  separator: ':'
+)
+
+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)
+test('unbind-luks1', find_program('unbind-luks1'), env: env)
+test('bind-key-file-non-interactive', find_program('bind-key-file-non-interactive-luks1'), env: env)
+test('bind-pass-with-newline', find_program('bind-pass-with-newline-luks1'), env: 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)
+
+# 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.
+if luksmeta_data.get('OLD_CRYPTSETUP') == '0'
+  test('bind-wrong-pass-luks2', find_program('bind-wrong-pass-luks2'), env: env)
+  test('bind-luks2', find_program('bind-luks2'), env: env, timeout: 60)
+  test('unbind-unbound-slot-luks2', find_program('unbind-unbound-slot-luks2'), env: env)
+  test('unbind-luks2', find_program('unbind-luks2'), env: env, timeout: 60)
+endif

+ 107 - 0
src/luks/tests/tests-common-functions.in

@@ -0,0 +1,107 @@
+#!/bin/bash -ex
+# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:
+#
+# Copyright (c) 2019 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/>.
+#
+
+error() {
+    echo "${1}" >&2
+    exit 1
+}
+
+skip_test() {
+    echo "${1}" >&2
+    exit 77
+}
+
+# We require cryptsetup >= 2.0.4 to fully support LUKSv2.
+# Support is determined at build time.
+luks2_supported() {
+    return @OLD_CRYPTSETUP@
+}
+
+# Creates a tang adv to be used in the test.
+create_tang_adv() {
+    local adv="${1}"
+    local SIG="${TMP}/sig.jwk"
+    jose jwk gen -i '{"alg":"ES512"}' > "${SIG}"
+
+    local EXC="${TMP}/exc.jwk"
+    jose jwk gen -i '{"alg":"ECMR"}' > "${EXC}"
+
+    local TEMPLATE='{"protected":{"cty":"jwk-set+json"}}'
+    jose jwk pub -s -i "${SIG}" -i "${EXC}" \
+        | jose jws sig -I- -s "${TEMPLATE}" -k "${SIG}" -o "${adv}"
+}
+
+# Creates a new LUKS1 or LUKS2 device to be used.
+new_device() {
+    local LUKS="${1}"
+    local DEV="${2}"
+    local PASS="${3}"
+
+    # 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
+        skip_test "WARNING: You must be root to run this test; test skipped."
+    fi
+
+    # Using a default password, if none has been provided.
+    if [ -z "${PASS}" ]; then
+        PASS="${DEFAULT_PASS}"
+    fi
+
+    local DEV_CACHED="${TMP}/${LUKS}.cached"
+
+    # Let's reuse an existing device, if there is one.
+    if [ -f "${DEV_CACHED}" ]; then
+        echo "Reusing cached ${LUKS} device..."
+        cp -f "${DEV_CACHED}" "${DEV}"
+        return 0
+    fi
+
+    fallocate -l16M "${DEV}"
+    local extra_options='--pbkdf pbkdf2 --pbkdf-force-iterations 1000'
+    cryptsetup luksFormat --type "${LUKS}" ${extra_options} --batch-mode \
+        --force-password "${DEV}" <<< "${PASS}"
+    # Caching the just-formatted device for possible reuse.
+    cp -f "${DEV}" "${DEV_CACHED}"
+}
+
+# Creates a new LUKS1 or LUKS2 device to be used, using a keyfile.
+new_device_keyfile() {
+    local LUKS="${1}"
+    local DEV="${2}"
+    local KEYFILE="${3}"
+
+    # 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
+        skip_test "WARNING: You must be root to run this test; test skipped."
+    fi
+
+    if [[ -z "${KEYFILE}" ]] || [[ ! -f "${KEYFILE}" ]]; then
+        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 \
+        "${DEV}" "${KEYFILE}"
+}
+
+export DEFAULT_PASS='just-some-test-password-here'

+ 74 - 0
src/luks/tests/unbind-luks1

@@ -0,0 +1,74 @@
+#!/bin/bash -ex
+# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:
+#
+# Copyright (c) 2019 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
+trap 'exit' ERR
+
+TMP="$(mktemp -d)"
+ADV="${TMP}/adv.jws"
+create_tang_adv "${ADV}"
+CFG="$(printf '{"url":"foobar","adv":"%s"}' "$ADV")"
+
+# LUKS1.
+DEV="${TMP}/luks1-device"
+UUID="cb6e8904-81ff-40da-a84a-07ab9ab5715e"
+new_device "luks1" "${DEV}"
+
+# Bind, initially.
+if ! clevis luks bind -f -d "${DEV}" tang "${CFG}" <<< "${DEFAULT_PASS}"; then
+    error "${TEST}: Binding is expected to succeed when given a correct (${DEFAULT_PASS}) password." >&2
+fi
+
+SLT=1
+if ! read -r _ state uuid < <(luksmeta show -d "${DEV}" | grep "^${SLT} *"); then
+    error "${TEST}: Error reading LUKSmeta info for slot ${SLT} of ${DEV}." >&2
+fi
+
+if [ "${state}" != "active" ]; then
+    error "${TEST}: state (${state}) is expected to be 'active'." >&2
+fi
+
+if [ "${uuid}" != "${UUID}" ]; then
+    error "${TEST}: UUID ($uuid) is expected to be '${UUID}'." >&2
+fi
+
+# Now unbind.
+if ! clevis luks unbind -f -d "${DEV}" -s "${SLT}"; then
+    error "${TEST}: Unbind is expected to succeed for device ${DEV} and slot ${SLT}" >&2
+fi
+
+if ! read -r _ state uuid < <(luksmeta show -d "${DEV}" | grep "^${SLT} *"); then
+    error "${TEST}: Error reading LUKSmeta info for slot ${SLT} of ${DEV}." >&2
+fi
+
+if [ "${state}" != "inactive" ]; then
+    error "${TEST}: state (${state}) is expected to be 'inactive'." >&2
+fi
+
+if [ "${uuid}" != "empty" ]; then
+    error "${TEST}: UUID ($uuid) is expected to be 'empty'." >&2
+fi

+ 51 - 0
src/luks/tests/unbind-luks2