Browse Source

Import upstream version 12

Sergio Correia 4 years 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

@@ -0,0 +1,51 @@
+#!/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}"
+# Binding.
+if ! clevis luks bind -d "${DEV}" tang "${CFG}" <<< "${DEFAULT_PASS}"; then
+    error "${TEST}: Binding is expected to succeed." >&2
+fi
+
+SLT=1
+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

+ 40 - 0
src/luks/tests/unbind-unbound-slot-luks1

@@ -0,0 +1,40 @@
+#!/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)"
+
+# LUKS1.
+DEV="${TMP}/luks1-device"
+new_device "luks1" "${DEV}"
+
+SLT=2
+if clevis luks unbind -d "${DEV}" -s "${SLT}"; then
+    error "${TEST}: Unbind is expected to fail for device ${DEV} and slot ${SLT}" >&2
+fi

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

@@ -0,0 +1,41 @@
+#!/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)"
+
+DEV="${TMP}/luks2-device"
+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
+fi

+ 28 - 10
src/luks/udisks2/clevis-luks-udisks2.c

@@ -314,26 +314,43 @@ recover_key(const pkt_t *jwe, char *out, size_t max, uid_t uid, gid_t gid)
         goto error;
 
     chld = fork();
-    if (chld < 0)
+    if (chld < 0) {
+        perror("fork");
         goto error;
+    }
 
     if (chld == 0) {
         char *const env[] = { "PATH=" BINDIR, NULL };
         int r = 0;
 
-        if (setgid(gid) != 0 || setegid(gid) != 0)
-            return EXIT_FAILURE;
+        if (geteuid() != 0) {
+            if (setgroups(1, &gid) != 0) {
+                perror("setgroups");
+                /* Can fail if missing permissions */
+            }
+        }
 
-        if (setuid(uid) != 0 || seteuid(uid) != 0)
-            return EXIT_FAILURE;
+        if (setgid(gid) != 0) {
+            perror("setgid");
+            exit(EXIT_FAILURE);
+        }
+
+        if (setuid(uid) != 0) {
+            perror("setuid");
+            exit(EXIT_FAILURE);
+        }
 
         r = dup2(push[PIPE_RD], STDIN_FILENO);
-        if (r != STDIN_FILENO)
-            return EXIT_FAILURE;
+        if (r != STDIN_FILENO) {
+            perror("dup2");
+            exit(EXIT_FAILURE);
+        }
 
         r = dup2(pull[PIPE_WR], STDOUT_FILENO);
-        if (r != STDOUT_FILENO)
-            return EXIT_FAILURE;
+        if (r != STDOUT_FILENO) {
+            perror("dup2");
+            exit(EXIT_FAILURE);
+        }
 
         safeclose(&push[PIPE_RD]);
         safeclose(&push[PIPE_WR]);
@@ -341,7 +358,8 @@ recover_key(const pkt_t *jwe, char *out, size_t max, uid_t uid, gid_t gid)
         safeclose(&pull[PIPE_WR]);
 
         execle(BINDIR "/clevis", "clevis", "decrypt", NULL, env);
-        return EXIT_FAILURE;
+        perror("execle");
+        exit(EXIT_FAILURE);
     }
 
     safeclose(&push[PIPE_RD]);

+ 5 - 2
src/luks/udisks2/meson.build

@@ -1,7 +1,8 @@
 audit = dependency('audit', version: '>=2.7.8', required: false)
 udisks2 = dependency('udisks2', required: false)
+gio = dependency('gio-2.0', required: false)
 
-if udisks2.found() and audit.found()
+if udisks2.found() and audit.found() and gio.found()
   autostartdir = join_paths(sysconfdir, 'xdg', 'autostart')
 
   configure_file(
@@ -16,4 +17,6 @@ if udisks2.found() and audit.found()
     install_dir: libexecdir,
     install: true,
   )
-endif
+else
+  warning('Will not build udisks2 support due to missing dependencies!')
+endif

+ 2 - 1
src/meson.build

@@ -1,9 +1,10 @@
 subdir('bash')
 subdir('luks')
 subdir('pins')
+subdir('initramfs-tools')
 
 bins += join_paths(meson.current_source_dir(), 'clevis-decrypt')
 mans += join_paths(meson.current_source_dir(), 'clevis-decrypt.1')
 
 bins += join_paths(meson.current_source_dir(), 'clevis')
-mans += join_paths(meson.current_source_dir(), 'clevis.1')
+mans += join_paths(meson.current_source_dir(), 'clevis.1')

+ 1 - 1
src/pins/meson.build

@@ -1,3 +1,3 @@
 subdir('sss')
 subdir('tang')
-subdir('tpm2')
+subdir('tpm2')

+ 5 - 5
src/pins/sss/clevis-decrypt-test

@@ -18,15 +18,15 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 
-[ $# -eq 1 -a "$1" == "--summary" ] && exit 1
+[ $# -eq 1 ] && [ "$1" == "--summary" ] && exit 2
 
-read -d . hdr
+read -r -d . hdr
 
-if [ "`jose fmt -q "$hdr" -SyOg clevis -g pin -u-`" != "test" ]; then
+if [ "$(jose fmt -q "$hdr" -SyOg clevis -g pin -u-)" != "test" ]; then
     echo "JWE pin mismatch!" >&2
     exit 1
 fi
 
-jwk=`jose fmt -q "$hdr" -SyOg clevis -g test -g jwk -Oo-` || exit 1
+jwk="$(jose fmt -q "$hdr" -SyOg clevis -g test -g jwk -Oo-)" || exit 1
 
-jose jwe dec -k- -i- < <(echo -n "$jwk$hdr."; /bin/cat)
+exec jose jwe dec -k- -i- < <(echo -n "$jwk$hdr."; /bin/cat)

+ 0 - 1
src/pins/sss/clevis-encrypt-sss.1.adoc

@@ -54,6 +54,5 @@ receive key fragments.
 
 == SEE ALSO
 
-link:clevis-encrypt-http.1.adoc[*clevis-encrypt-http*(1)],
 link:clevis-encrypt-tang.1.adoc[*clevis-encrypt-tang*(1)],
 link:clevis-decrypt.1.adoc[*clevis-decrypt*(1)]

+ 4 - 4
src/pins/sss/clevis-encrypt-test

@@ -18,18 +18,18 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 
-[ $# -eq 1 -a "$1" == "--summary" ] && exit 1
+[ $# -eq 1 ] && [ "$1" == "--summary" ] && exit 2
 
-if ! cfg=`jose fmt -j "$1" -Oo- 2>/dev/null`; then
+if ! cfg="$(jose fmt -j "$1" -Oo- 2>/dev/null)"; then
     echo "Configuration is malformed!" >&2
     exit 1
 fi
 
-jwk=`jose jwk gen -i '{"alg":"A256GCM"}'`
+jwk="$(jose jwk gen -i '{"alg":"A256GCM"}')"
 jwe='{"protected":{"clevis":{"pin":"test","test":{}}}}'
 
 if ! jose fmt -j "$cfg" -g fail -T; then
-    jwe=`jose fmt -j "$jwe" -Og protected -g clevis -g test -j "$jwk" -Os jwk -UUUUo-`
+    jwe="$(jose fmt -j "$jwe" -Og protected -g clevis -g test -j "$jwk" -Os jwk -UUUUo-)"
 fi
 
 exec jose jwe enc -i- -k- -I- -c < <(echo -n "$jwe$jwk"; /bin/cat)

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

@@ -31,4 +31,4 @@ if jansson.found() and libcrypto.found()
   test('pin-test', find_program(join_paths(src, 'pin-test')), env: env)
 else
   warning('Will not install sss pin due to missing dependencies!')
-endif
+endif

+ 12 - 12
src/pins/sss/pin-sss

@@ -1,22 +1,22 @@
 #!/bin/bash -ex
 
-e=`echo hi | clevis encrypt sss '{"t":1,"pins":{"test":[{},{}]}}'`
-d=`echo $e | clevis decrypt`
+e="$(echo hi | clevis encrypt sss '{"t":1,"pins":{"test":[{},{}]}}')"
+d="$(echo "$e" | clevis decrypt)"
 test "$d" == "hi"
 
-e=`echo hi | clevis encrypt sss '{"t":1,"pins":{"test":[{},{"fail":true}]}}'`
-d=`echo $e | clevis decrypt`
+e="$(echo hi | clevis encrypt sss '{"t":1,"pins":{"test":[{},{"fail":true}]}}')"
+d="$(echo "$e" | clevis decrypt)"
 test "$d" == "hi"
 
-e=`echo hi | clevis encrypt sss '{"t":1,"pins":{"test":[{"fail":true},{"fail":true}]}}'`
-! echo $e | clevis decrypt
+e="$(echo hi | clevis encrypt sss '{"t":1,"pins":{"test":[{"fail":true},{"fail":true}]}}')"
+! echo "$e" | clevis decrypt
 
-e=`echo hi | clevis encrypt sss '{"t":2,"pins":{"test":[{},{}]}}'`
-d=`echo $e | clevis decrypt`
+e="$(echo hi | clevis encrypt sss '{"t":2,"pins":{"test":[{},{}]}}')"
+d="$(echo "$e" | clevis decrypt)"
 test "$d" == "hi"
 
-e=`echo hi | clevis encrypt sss '{"t":2,"pins":{"test":[{},{"fail":true}]}}'`
-! echo $e | clevis decrypt
+e="$(echo hi | clevis encrypt sss '{"t":2,"pins":{"test":[{},{"fail":true}]}}')"
+! echo "$e" | clevis decrypt
 
-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":2,"pins":{"test":[{"fail":true},{"fail":true}]}}')"
+! echo "$e" | clevis decrypt

+ 3 - 3
src/pins/sss/pin-test

@@ -2,9 +2,9 @@
 
 trap 'exit' ERR
 
-e=`echo -n hi | clevis encrypt test '{}'`
-d=`echo -n "$e" | clevis decrypt`
+e="$(echo -n hi | clevis encrypt test '{}')"
+d="$(echo -n "$e" | clevis decrypt)"
 test "$d" == "hi"
 
-e=`echo -n hi | clevis encrypt test '{"fail":true}'`
+e="$(echo -n hi | clevis encrypt test '{"fail":true}')"
 ! echo "$e" | clevis decrypt

+ 22 - 21
src/pins/tang/clevis-decrypt-tang

@@ -18,73 +18,74 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 
-[ $# -eq 1 -a "$1" == "--summary" ] && exit 1
+[ $# -eq 1 ] && [ "$1" == "--summary" ] && exit 2
 
 if [ -t 0 ]; then
-    echo >&2
-    echo "Usage: clevis decrypt tang < JWE > PLAINTEXT" >&2
-    echo >&2
-    exit 1
+    exec >&2
+    echo
+    echo "Usage: clevis decrypt tang < JWE > PLAINTEXT"
+    echo
+    exit 2
 fi
 
-read -d . hdr
+read -r -d . hdr
 
-if ! jhd=`jose b64 dec -i- <<< "$hdr"`; then
+if ! jhd="$(jose b64 dec -i- <<< "$hdr")"; then
     echo "Error decoding JWE protected header!" >&2
     exit 1
 fi
 
-if [ "`jose fmt -j- -Og clevis -g pin -u- <<< "$jhd"`" != "tang" ]; then
+if [ "$(jose fmt -j- -Og clevis -g pin -u- <<< "$jhd")" != "tang" ]; then
     echo "JWE pin mismatch!" >&2
     exit 1
 fi
 
-if ! clt=`jose fmt -j- -Og epk -Oo- <<< "$jhd"`; then
+if ! clt="$(jose fmt -j- -Og epk -Oo- <<< "$jhd")"; then
     echo "JWE missing required 'epk' header parameter!" >&2
     exit 1
 fi
 
-if ! kid=`jose fmt -j- -Og kid -Su- <<< "$jhd"`; then
+if ! kid="$(jose fmt -j- -Og kid -Su- <<< "$jhd")"; then
     echo "JWE missing required 'kid' header parameter!" >&2
     exit 1
 fi
 
-if ! srv=`jose fmt -j- -Og clevis -g tang -g adv -Oo- <<< "$jhd" \
-        | jose jwk thp -i- -f "$kid"`; then
+if ! srv="$(jose fmt -j- -Og clevis -g tang -g adv -Oo- <<< "$jhd" \
+        | jose jwk thp -i- -f "$kid")"; then
     echo "JWE missing required 'clevis.tang.adv' header parameter!" >&2
     exit 1
 fi
 
-if ! url=`jose fmt -j- -Og clevis -g tang -g url -Su- <<< "$jhd"`; then
+if ! url="$(jose fmt -j- -Og clevis -g tang -g url -Su- <<< "$jhd")"; then
     echo "JWE missing required 'clevis.tang.url' header parameter!" >&2
     exit 1
 fi
 
-if ! crv=`jose fmt -j- -Og crv -Su- <<< "$clt"`; then
+if ! crv="$(jose fmt -j- -Og crv -Su- <<< "$clt")"; then
     echo "Unable to determine EPK's curve!" >&2
     exit 1
 fi
 
-if ! eph=`jose jwk gen -i "{\"alg\":\"ECMR\",\"crv\":\"$crv\"}"`; then
+if ! eph="$(jose jwk gen -i "{\"alg\":\"ECMR\",\"crv\":\"$crv\"}")"; then
     echo "Error generating ephemeral key!" >&2
     exit 1
 fi
 
-xfr=`jose jwk exc -i '{"alg":"ECMR"}' -l- -r- <<< "$clt$eph"`
+xfr="$(jose jwk exc -i '{"alg":"ECMR"}' -l- -r- <<< "$clt$eph")"
 
 url="$url/rec/$kid"
 ct="Content-Type: application/jwk+json"
-if ! rep=`curl -sfg -X POST -H "$ct" --data-binary @- "$url" <<< "$xfr"`; then
+if ! rep="$(curl -sfg -X POST -H "$ct" --data-binary @- "$url" <<< "$xfr")"; then
     echo "Error communicating with the server!" >&2
     exit 1
 fi
 
-if ! rep=`jose fmt -j- -Og kty -q EC -EUUg crv -q "$crv" -EUUo- <<< "$rep"`; then
+if ! rep="$(jose fmt -j- -Og kty -q EC -EUUg crv -q "$crv" -EUUo- <<< "$rep")"; then
     echo "Received invalid server reply!" >&2
     exit 1
 fi
 
-tmp=`jose jwk exc -i '{"alg":"ECMR"}' -l- -r- <<< "$eph$srv"`
-rep=`jose jwk pub -i- <<< "$rep"`
-jwk=`jose jwk exc -l- -r- <<< "$rep$tmp"`
+tmp="$(jose jwk exc -i '{"alg":"ECMR"}' -l- -r- <<< "$eph$srv")"
+rep="$(jose jwk pub -i- <<< "$rep")"
+jwk="$(jose jwk exc -l- -r- <<< "$rep$tmp")"
 exec jose jwe dec -k- -i- < <(echo -n "$jwk$hdr."; /bin/cat)

+ 56 - 51
src/pins/tang/clevis-encrypt-tang

@@ -26,73 +26,74 @@ if [ "$1" == "--summary" ]; then
 fi
 
 if [ -t 0 ]; then
-    echo >&2
-    echo "Usage: clevis encrypt tang CONFIG < PLAINTEXT > JWE" >&2
-    echo >&2
-    echo $SUMMARY >&2
-    echo >&2
-    echo "This command uses the following configuration properties:" >&2
-    echo >&2
-    echo "  url: <string>   The base URL of the Tang server (REQUIRED)" >&2
-    echo >&2
-    echo "  thp: <string>   The thumbprint of a trusted signing key" >&2
-    echo >&2
-    echo "  adv: <string>   A filename containing a trusted advertisement" >&2
-    echo "  adv: <object>   A trusted advertisement (raw JSON)" >&2
-    echo >&2
-    echo "Obtaining the thumbprint of a trusted signing key is easy. If you" >&2
-    echo "have access to the Tang server's database directory, simply do:" >&2
-    echo >&2
-    echo "    $ jose jwk thp -i \$DBDIR/\$SIG.jwk " >&2
-    echo >&2
-    echo "Alternatively, if you have certainty that your network connection" >&2
-    echo "is not compromised (not likely), you can download the advertisement" >&2
-    echo "yourself using:" >&2
-    echo >&2
-    echo "    $ curl -f \$URL/adv > adv.jws" >&2
-    echo >&2
-    exit 1
+    exec >&2
+    echo
+    echo "Usage: clevis encrypt tang CONFIG < PLAINTEXT > JWE"
+    echo
+    echo "$SUMMARY"
+    echo
+    echo "This command uses the following configuration properties:"
+    echo
+    echo "  url: <string>   The base URL of the Tang server (REQUIRED)"
+    echo
+    echo "  thp: <string>   The thumbprint of a trusted signing key"
+    echo
+    echo "  adv: <string>   A filename containing a trusted advertisement"
+    echo "  adv: <object>   A trusted advertisement (raw JSON)"
+    echo
+    echo "Obtaining the thumbprint of a trusted signing key is easy. If you"
+    echo "have access to the Tang server's database directory, simply do:"
+    echo
+    echo "    $ jose jwk thp -i \$DBDIR/\$SIG.jwk "
+    echo
+    echo "Alternatively, if you have certainty that your network connection"
+    echo "is not compromised (not likely), you can download the advertisement"
+    echo "yourself using:"
+    echo
+    echo "    $ curl -f \$URL/adv > adv.jws"
+    echo
+    exit 2
 fi
 
-if ! cfg=`jose fmt -j- -Oo- <<< "$1" 2>/dev/null`; then
+if ! cfg="$(jose fmt -j- -Oo- <<< "$1" 2>/dev/null)"; then
     echo "Configuration is malformed!" >&2
     exit 1
 fi
 
-if ! url=`jose fmt -j- -Og url -u- <<< "$cfg"`; then
+if ! url="$(jose fmt -j- -Og url -u- <<< "$cfg")"; then
     echo "Missing the required 'url' property!" >&2
     exit 1
 fi
 
-thp=`jose fmt -j- -Og thp -Su- <<< "$cfg"` || true
+thp="$(jose fmt -j- -Og thp -Su- <<< "$cfg")" || true
 
 ### Get the advertisement
-if jws=`jose fmt -j- -g adv -Oo- <<< "$cfg"`; then
-    thp=${thp:-any}
-elif jws=`jose fmt -j- -g adv -Su- <<< "$cfg"`; then
+if jws="$(jose fmt -j- -g adv -Oo- <<< "$cfg")"; then
+    thp="${thp:-any}"
+elif jws="$(jose fmt -j- -g adv -Su- <<< "$cfg")"; then
     if ! [ -f "$jws" ]; then
         echo "Advertisement file '$jws' not found!" >&2
         exit 1
     fi
 
-    if ! jws=`jose fmt -j- -Oo- < "$jws"`; then
+    if ! jws="$(jose fmt -j "$jws" -Oo-)"; then
         echo "Advertisement file '$jws' is malformed!" >&2
         exit 1
     fi
 
-    thp=${thp:-any}
-elif ! jws=`curl -sfg "$url/adv/$thp"`; then
+    thp="${thp:-any}"
+elif ! jws="$(curl -sfg "$url/adv/$thp")"; then
     echo "Unable to fetch advertisement: '$url/adv/$thp'!" >&2
     exit 1
 fi
 
-if ! jwks=`jose fmt -j- -Og payload -SyOg keys -AUo- <<< "$jws"`; then
+if ! jwks="$(jose fmt -j- -Og payload -SyOg keys -AUo- <<< "$jws")"; then
     echo "Advertisement is malformed!" >&2
     exit 1
 fi
 
 ### Check advertisement validity
-ver=`jose jwk use -i- -r -u verify -o- <<< "$jwks"`
+ver="$(jose jwk use -i- -r -u verify -o- <<< "$jwks")"
 if ! jose jws ver -i "$jws" -k- -a <<< "$ver"; then
     echo "Advertisement is missing signatures!" >&2
     exit 1
@@ -114,19 +115,23 @@ elif [ "$thp" != "any" ] && \
 fi
 
 ### Perform encryption
-enc=`jose jwk use -i- -r -u deriveKey -o- <<< "$jwks"`
+if ! enc="$(jose jwk use -i- -r -u deriveKey -o- <<< "$jwks")"; then
+    echo "Key derivation key not available!" >&2
+    exit 1
+fi
+
 jose fmt -j "$enc" -Og keys -A || enc="{\"keys\":[$enc]}"
 
-for jwk in `jose fmt -j- -Og keys -Af- <<< "$enc"`; do
-    jwk=`jose fmt -j- -Od key_ops -o- <<< "$jwk"`
-    jwk=`jose fmt -j- -Od alg -o- <<< "$jwk"`
-    kid=`jose jwk thp -i- <<< "$jwk"`
-    jwe='{"protected":{"alg":"ECDH-ES","enc":"A256GCM","clevis":{"pin":"tang","tang":{}}}}'
-    jwe=`jose fmt -j "$jwe" -g protected -q "$kid" -s kid -UUo-`
-    jwe=`jose fmt -j "$jwe" -g protected -g clevis -g tang -q "$url" -s url -UUUUo-`
-    jwe=`jose fmt -j "$jwe" -g protected -g clevis -g tang -j- -s adv -UUUUo- <<< "$jwks"`
-    exec jose jwe enc -i- -k- -I- -c < <(echo -n "$jwe$jwk"; /bin/cat)
-done
-
-echo "No exchange keys found!" >&2
-exit 1
+if ! jwk="$(jose fmt -j- -Og keys -Af- <<< "$enc")"; then
+    echo "No exchange keys found!" >&2
+    exit 1
+fi
+
+jwk="$(jose fmt -j- -Od key_ops -o- <<< "$jwk")"
+jwk="$(jose fmt -j- -Od alg -o- <<< "$jwk")"
+kid="$(jose jwk thp -i- <<< "$jwk")"
+jwe='{"protected":{"alg":"ECDH-ES","enc":"A256GCM","clevis":{"pin":"tang","tang":{}}}}'
+jwe="$(jose fmt -j "$jwe" -g protected -q "$kid" -s kid -UUo-)"
+jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g tang -q "$url" -s url -UUUUo-)"
+jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g tang -j- -s adv -UUUUo- <<< "$jwks")"
+exec jose jwe enc -i- -k- -I- -c < <(echo -n "$jwe$jwk"; /bin/cat)

+ 5 - 1
src/pins/tang/meson.build

@@ -6,16 +6,19 @@ actv = find_program(
 kgen = find_program(
   join_paths(libexecdir, 'tangd-keygen'),
   '/usr/libexec/tangd-keygen',
+  '/usr/lib/x86_64-linux-gnu/tangd-keygen',
   required: false
 )
 updt = find_program(
   join_paths(libexecdir, 'tangd-update'),
   '/usr/libexec/tangd-update',
+  '/usr/lib/x86_64-linux-gnu/tangd-update',
   required: false
 )
 tang = find_program(
   join_paths(libexecdir, 'tangd'),
   '/usr/libexec/tangd',
+  '/usr/lib/x86_64-linux-gnu/tangd',
   required: false
 )
 curl = find_program('curl', required: false)
@@ -32,6 +35,7 @@ if curl.found()
       join_paths(meson.source_root(), 'src'),
       meson.current_source_dir(),
       '/usr/libexec',
+      '/usr/lib/x86_64-linux-gnu',
       libexecdir,
       separator: ':'
     )
@@ -42,4 +46,4 @@ if curl.found()
   endif
 else
   warning('Will not install tang pin due to missing dependencies!')
-endif
+endif

+ 17 - 17
src/pins/tang/pin-tang

@@ -20,42 +20,42 @@
 
 function on_exit() {
     if [ "$PID" ]; then kill $PID; wait $PID || true; fi
-    [ -d "$TMP" ] && rm -rf $TMP
+    [ -d "$TMP" ] && rm -rf "$TMP"
 }
 
 trap 'on_exit' EXIT
 trap 'exit' ERR
 
-export TMP=`mktemp -d`
-mkdir -p $TMP/db
-mkdir -p $TMP/cache
+TMP="$(mktemp -d)"
+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 &
-export PID=$!
+port="$(shuf -i 1024-65536 -n 1)"
+$SD_ACTIVATE --inetd -l 127.0.0.1:$port -a tangd "$TMP"/cache &
+PID=$!
 sleep 0.25
 
-thp=`jose jwk thp -i "$TMP/db/sig.jwk"`
+thp="$(jose jwk thp -i "$TMP/db/sig.jwk")"
 adv="$TMP/cache/default.jws"
 url="http://localhost:${port}"
 
-cfg=`printf '{"url":"%s","adv":"%s"}' "$url" "$adv"`
-enc=`echo -n "hi" | clevis encrypt tang "$cfg"`
-dec=`echo -n "$enc" | clevis decrypt`
+cfg="$(printf '{"url":"%s","adv":"%s"}' "$url" "$adv")"
+enc="$(echo -n "hi" | clevis encrypt tang "$cfg")"
+dec="$(echo -n "$enc" | clevis decrypt)"
 test "$dec" == "hi"
 
-cfg=`printf '{"url":"%s","thp":"%s"}' "$url" "$thp"`
-enc=`echo -n "hi" | clevis encrypt tang "$cfg"`
-dec=`echo -n "$enc" | clevis decrypt`
+cfg="$(printf '{"url":"%s","thp":"%s"}' "$url" "$thp")"
+enc="$(echo -n "hi" | clevis encrypt tang "$cfg")"
+dec="$(echo -n "$enc" | clevis decrypt)"
 test "$dec" == "hi"
 
 kill -9 $PID
 ! wait $PID
 unset PID
 
-! echo $enc | clevis decrypt
+! echo "$enc" | clevis decrypt

+ 65 - 32
src/pins/tpm2/clevis-decrypt-tpm2

@@ -22,112 +22,145 @@
 auth="o"
 
 function on_exit() {
-    if ! rm -r $TMP; then
+    if [ ! -d "$TMP" ] || ! rm -rf "$TMP"; then
         echo "Delete temporary files failed!" >&2
+        echo "You need to clean up: $TMP" >&2
         exit 1
     fi
 }
 
-[ $# -eq 1 -a "$1" == "--summary" ] && exit 1
+[ $# -eq 1 ] && [ "$1" == "--summary" ] && exit 2
 
 if [ -t 0 ]; then
-    echo >&2
-    echo "Usage: clevis decrypt tpm2 < JWE > PLAINTEXT" >&2
-    echo >&2
-    exit 1
+    exec >&2
+    echo
+    echo "Usage: clevis decrypt tpm2 < JWE > PLAINTEXT"
+    echo
+    exit 2
 fi
 
-TPM2TOOLS_INFO=`tpm2_pcrlist -v`
+TPM2TOOLS_INFO="$(tpm2_createprimary -v)"
 
-if [[ $TPM2TOOLS_INFO != *version=\"3.* ]]; then
-    echo "The tpm2 pin requires tpm2-tools version 3" >&2
+match='version="(.)\.'
+[[ $TPM2TOOLS_INFO =~ $match ]] && TPM2TOOLS_VERSION="${BASH_REMATCH[1]}"
+if [[ $TPM2TOOLS_VERSION != 3 ]] && [[ $TPM2TOOLS_VERSION != 4 ]]; then
+    echo "The tpm2 pin requires tpm2-tools version 3 or 4" >&2
     exit 1
 fi
 
+# Old environment variables for tpm2-tools 3.0
 export TPM2TOOLS_TCTI_NAME=device
-export TPM2TOOLS_DEVICE_FILE=`ls /dev/tpmrm? 2>/dev/null`
+export TPM2TOOLS_DEVICE_FILE=
+for dev in /dev/tpmrm?; do
+    [ -e "$dev" ] || continue
+    TPM2TOOLS_DEVICE_FILE="$dev"
+    break
+done
+
+# New environment variable for tpm2-tools >= 3.1
+export TPM2TOOLS_TCTI="$TPM2TOOLS_TCTI_NAME:$TPM2TOOLS_DEVICE_FILE"
 
-if [ -z "${TPM2TOOLS_DEVICE_FILE[0]}" ]; then
+if [ -z "$TPM2TOOLS_DEVICE_FILE" ]; then
     echo "A TPM2 device with the in-kernel resource manager is needed!" >&2
     exit 1
 fi
 
-if ! [[ -r "${TPM2TOOLS_DEVICE_FILE[0]}" && -w "${TPM2TOOLS_DEVICE_FILE[0]}" ]]; then
-    echo "The ${TPM2TOOLS_DEVICE_FILE[0]} device must be readable and writable!" >&2
+if ! [[ -r "$TPM2TOOLS_DEVICE_FILE" && -w "$TPM2TOOLS_DEVICE_FILE" ]]; then
+    echo "The $TPM2TOOLS_DEVICE_FILE device must be readable and writable!" >&2
     exit 1
 fi
 
-read -d . hdr
+read -r -d . hdr
 
-if ! jhd=`jose b64 dec -i- <<< "$hdr"`; then
+if ! jhd="$(jose b64 dec -i- <<< "$hdr")"; then
     echo "Error decoding JWE protected header!" >&2
     exit 1
 fi
 
-if [ `jose fmt -j- -Og clevis -g pin -u- <<< "$jhd"` != "tpm2" ]; then
+if [ "$(jose fmt -j- -Og clevis -g pin -u- <<< "$jhd")" != "tpm2" ]; then
     echo "JWE pin mismatch!" >&2
     exit 1
 fi
 
-if ! hash=`jose fmt -j- -Og clevis -g tpm2 -g hash -Su- <<< "$jhd"`; then
+if ! hash="$(jose fmt -j- -Og clevis -g tpm2 -g hash -Su- <<< "$jhd")"; then
     echo "JWE missing required 'hash' header parameter!" >&2
     exit 1
 fi
 
-if ! key=`jose fmt -j- -Og clevis -g tpm2 -g key -Su- <<< "$jhd"`; then
+if ! key="$(jose fmt -j- -Og clevis -g tpm2 -g key -Su- <<< "$jhd")"; then
     echo "JWE missing required 'key' header parameter!" >&2
     exit 1
 fi
 
-if ! jwk_pub=`jose fmt -j- -Og clevis -g tpm2 -g jwk_pub -Su- <<< "$jhd"`; then
+if ! jwk_pub="$(jose fmt -j- -Og clevis -g tpm2 -g jwk_pub -Su- <<< "$jhd")"; then
     echo "JWE missing required 'key' header parameter!" >&2
     exit 1
 fi
 
-if ! jwk_priv=`jose fmt -j- -Og clevis -g tpm2 -g jwk_priv -Su- <<< "$jhd"`; then
+if ! jwk_priv="$(jose fmt -j- -Og clevis -g tpm2 -g jwk_priv -Su- <<< "$jhd")"; then
     echo "JWE missing required 'key' header parameter!" >&2
     exit 1
 fi
 
-if ! TMP=`mktemp -d`; then
+if ! TMP="$(mktemp -d)"; then
     echo "Creating a temporary dir for TPM files failed!" >&2
     exit 1
 fi
 
 trap 'on_exit' EXIT
 
-pcr_ids=`jose fmt -j- -Og clevis -g tpm2 -g pcr_ids -Su- <<< "$jhd"` || true
+pcr_ids="$(jose fmt -j- -Og clevis -g tpm2 -g pcr_ids -Su- <<< "$jhd")" || true
 
+pcr_spec=''
 if [ -n "$pcr_ids" ]; then
-    pcr_bank=`jose fmt -j- -Og clevis -g tpm2 -g pcr_bank -Su- <<< "$jhd"`
-    policy_options="-L $pcr_bank:$pcr_ids"
+    pcr_bank="$(jose fmt -j- -Og clevis -g tpm2 -g pcr_bank -Su- <<< "$jhd")"
+    pcr_spec="$pcr_bank:$pcr_ids"
 fi
 
-if ! `jose b64 dec -i- -O $TMP/jwk.pub <<< "$jwk_pub"`; then
+if ! jose b64 dec -i- -O "$TMP"/jwk.pub <<< "$jwk_pub"; then
     echo "Decoding jwk.pub from Base64 failed!" >&2
     exit 1
 fi
 
-if ! `jose b64 dec -i- -O $TMP/jwk.priv <<< "$jwk_priv"`; then
+if ! jose b64 dec -i- -O "$TMP"/jwk.priv <<< "$jwk_priv"; then
     echo "Decoding jwk.priv from Base64 failed!" >&2
     exit 1
 fi
 
-if ! tpm2_createprimary -Q -H "$auth" -g "$hash" -G "$key" \
-     -C $TMP/primary.context 2>/dev/null; then
+case "$TPM2TOOLS_VERSION" in
+    3) tpm2_createprimary -Q -H "$auth" -g "$hash" -G "$key" -C "$TMP"/primary.context || fail=$?;;
+    4) tpm2_createprimary -Q -C "$auth" -g "$hash" -G "$key" -c "$TMP"/primary.context || fail=$?;;
+    *) fail=1;;
+esac
+if [ -n "$fail" ]; then
     echo "Creating TPM2 primary key failed!" >&2
     exit 1
 fi
 
-if ! tpm2_load -Q -c $TMP/primary.context -u $TMP/jwk.pub -r $TMP/jwk.priv \
-     -C $TMP/load.context 2>/dev/null; then
+case "$TPM2TOOLS_VERSION" in
+    3) tpm2_load -Q -c "$TMP"/primary.context -u "$TMP"/jwk.pub -r "$TMP"/jwk.priv \
+                 -C "$TMP"/load.context || fail=$?;;
+    4) tpm2_load -Q -C "$TMP"/primary.context -u "$TMP"/jwk.pub -r "$TMP"/jwk.priv \
+                 -c "$TMP"/load.context || fail=$?;;
+    *) fail=1;;
+esac
+if [ -n "$fail" ]; then
     echo "Loading jwk to TPM2 failed!" >&2
     exit 1
 fi
 
-if ! jwk=`tpm2_unseal -c $TMP/load.context $policy_options 2>/dev/null`; then
+case "$TPM2TOOLS_VERSION" in
+    3) jwk="$(tpm2_unseal -c "$TMP"/load.context ${pcr_spec:+-L $pcr_spec})" || fail=$?;;
+    4) jwk="$(tpm2_unseal -c "$TMP"/load.context ${pcr_spec:+-p pcr:$pcr_spec})" || fail=$?;;
+    *) fail=1;;
+esac
+if [ -n "$fail" ]; then
     echo "Unsealing jwk from TPM failed!" >&2
     exit 1
 fi
 
-jose jwe dec -k- -i- < <(echo -n "$jwk$hdr."; /bin/cat)
+# The on_exit() trap will not be fired after exec, so let's clean up the temp
+# directory at this point.
+[ -d "${TMP}" ] && rm -rf "${TMP}"
+
+exec jose jwe dec -k- -i- < <(echo -n "$jwk$hdr."; /bin/cat)

+ 115 - 50
src/pins/tpm2/clevis-encrypt-tpm2

@@ -24,11 +24,12 @@ auth="o"
 # Algorithm type must be keyedhash for object with user provided sensitive data.
 alg_create_key="keyedhash"
 # Attributes for the created TPM2 object with the JWK as sensitive data.
-obj_attr="fixedtpm|fixedparent|sensitivedataorigin|noda|adminwithpolicy"
+obj_attr="fixedtpm|fixedparent|noda|adminwithpolicy"
 
 function on_exit() {
-    if ! rm -rf $TMP; then
+    if [ ! -d "$TMP" ] || ! rm -rf "$TMP"; then
         echo "Delete temporary files failed!" >&2
+        echo "You need to clean up: $TMP" >&2
         exit 1
     fi
 }
@@ -39,81 +40,126 @@ if [ "$1" == "--summary" ]; then
 fi
 
 if [ -t 0 ]; then
-    echo >&2
-    echo "Usage: clevis encrypt tpm2 CONFIG < PLAINTEXT > JWE" >&2
-    echo >&2
-    echo $SUMMARY >&2
-    echo >&2
-    echo "This command uses the following configuration properties:" >&2
-    echo >&2
-    echo "  hash: <string>  Hash algorithm used in the computation of the object name (default: sha256)" >&2
-    echo >&2
-    echo "  key: <string>   Algorithm type for the generated key (default: ecc)" >&2
-    echo >&2
-    echo "  pcr_bank: <string>   PCR algorithm bank to use for policy (default: sha1)" >&2
-    echo >&2
-    echo "  pcr_ids: <string>   PCR list used for policy. If not present, no policy is used" >&2
-    echo >&2
-    echo "  pcr_digest: <string>   Binary PCR hashes encoded in base64. If not present, the hash values are looked up" >&2
-    echo >&2
-    exit 1
+    exec >&2
+    echo
+    echo "Usage: clevis encrypt tpm2 CONFIG < PLAINTEXT > JWE"
+    echo
+    echo "$SUMMARY"
+    echo
+    echo "This command uses the following configuration properties:"
+    echo
+    echo "  hash: <string>  Hash algorithm used in the computation of the object name (default: sha256)"
+    echo
+    echo "  key: <string>   Algorithm type for the generated key (default: ecc)"
+    echo
+    echo "  pcr_bank: <string>   PCR algorithm bank to use for policy (default: sha1)"
+    echo
+    echo "  pcr_ids: <string>   PCR list used for policy. If not present, no policy is used"
+    echo
+    echo "  pcr_digest: <string>   Binary PCR hashes encoded in base64. If not present, the hash values are looked up"
+    echo
+    exit 2
 fi
 
-TPM2TOOLS_INFO=`tpm2_pcrlist -v`
+TPM2TOOLS_INFO="$(tpm2_createprimary -v)"
 
-if [[ $TPM2TOOLS_INFO != *version=\"3.* ]]; then
-    echo "The tpm2 pin requires tpm2-tools version 3" >&2
+match='version="(.)\.'
+[[ $TPM2TOOLS_INFO =~ $match ]] && TPM2TOOLS_VERSION="${BASH_REMATCH[1]}"
+if [[ $TPM2TOOLS_VERSION != 3 ]] && [[ $TPM2TOOLS_VERSION != 4 ]]; then
+    echo "The tpm2 pin requires tpm2-tools version 3 or 4" >&2
     exit 1
 fi
 
+# Old environment variables for tpm2-tools 3.0
 export TPM2TOOLS_TCTI_NAME=device
-export TPM2TOOLS_DEVICE_FILE=`ls /dev/tpmrm? 2>/dev/null`
+export TPM2TOOLS_DEVICE_FILE=
+for dev in /dev/tpmrm?; do
+    [ -e "$dev" ] || continue
+    TPM2TOOLS_DEVICE_FILE="$dev"
+    break
+done
+
+# New environment variable for tpm2-tools >= 3.1
+export TPM2TOOLS_TCTI="$TPM2TOOLS_TCTI_NAME:$TPM2TOOLS_DEVICE_FILE"
 
-if [ -z "${TPM2TOOLS_DEVICE_FILE[0]}" ]; then
+if [ -z "$TPM2TOOLS_DEVICE_FILE" ]; then
     echo "A TPM2 device with the in-kernel resource manager is needed!" >&2
     exit 1
 fi
 
-if ! [[ -r "${TPM2TOOLS_DEVICE_FILE[0]}" && -w "${TPM2TOOLS_DEVICE_FILE[0]}" ]]; then
-    echo "The ${TPM2TOOLS_DEVICE_FILE[0]} device must be readable and writable!" >&2
+if ! [[ -r "$TPM2TOOLS_DEVICE_FILE" && -w "$TPM2TOOLS_DEVICE_FILE" ]]; then
+    echo "The $TPM2TOOLS_DEVICE_FILE device must be readable and writable!" >&2
     exit 1
 fi
 
-if ! cfg=`jose fmt -j "$1" -Oo- 2>/dev/null`; then
+if ! cfg="$(jose fmt -j "$1" -Oo- 2>/dev/null)"; then
     echo "Configuration is malformed!" >&2
     exit 1
 fi
 
-hash=`jose fmt -j- -Og hash -u- <<< "$cfg"` || hash="sha256"
+hash="$(jose fmt -j- -Og hash -u- <<< "$cfg")" || hash="sha256"
 
-key=`jose fmt -j- -Og key -u- <<< "$cfg"` || key="ecc"
+key="$(jose fmt -j- -Og key -u- <<< "$cfg")" || key="ecc"
 
-pcr_bank=`jose fmt -j- -Og pcr_hash -u- <<< "$cfg"` || pcr_bank="sha1"
+pcr_bank="$(jose fmt -j- -Og pcr_bank -u- <<< "$cfg")" || pcr_bank="sha1"
 
-pcr_ids=`jose fmt -j- -Og pcr_ids -u- <<< "$cfg"` || true
+# Issue #103: We support passing pcr_ids using both a single string, as in
+# "1,3", as well as an actual JSON array, such as ["1,"3"]. Let's handle both
+# cases here.
+if [[ ${cfg// /} != '{}' ]] \
+    && ! pcr_ids="$(jose fmt -j- -Og pcr_ids -u- 2>/dev/null <<< "$cfg")"; then
 
-pcr_digest=`jose fmt -j- -Og pcr_digest -u- <<< "$cfg"` || true
+    # We failed to parse a string, so let's try to parse a JSON array instead.
+    if jose fmt -j- -Og pcr_ids -A 2>/dev/null <<< "${cfg}"; then
+        # OK, it is an array, so let's get the items and form a string.
+        pcr_ids=
+        for pcr in $(jose fmt -j- -Og pcr_ids -Af- <<< "${cfg}" \
+                     | tr -d '"'); do
+            pcr_ids=$(printf '%s,%s' "${pcr_ids}" "${pcr}")
+        done
+        # Now let's remove the leading comma.
+        pcr_ids=${pcr_ids/#,/}
+    else
+        # Not to add a policy that was not intended, in this case, no policy
+        # at all, let's report the issue and exit.
+        echo "Parsing the requested policy failed!" >&2
+        exit 1
+    fi
+fi
 
-if ! jwk=`jose jwk gen -i '{"alg":"A256GCM"}'`; then
+pcr_digest="$(jose fmt -j- -Og pcr_digest -u- <<< "$cfg")" || true
+
+if ! jwk="$(jose jwk gen -i '{"alg":"A256GCM"}')"; then
     echo "Generating a jwk failed!" >&2
     exit 1
 fi
 
-if ! TMP=`mktemp -d`; then
+if ! TMP="$(mktemp -d)"; then
     echo "Creating a temporary dir for TPM files failed!" >&2
     exit 1
 fi
 
 trap 'on_exit' EXIT
 
-if ! tpm2_createprimary -Q -H "$auth" -g "$hash" -G "$key" -C $TMP/primary.context; then
+case "$TPM2TOOLS_VERSION" in
+    3) tpm2_createprimary -Q -H "$auth" -g "$hash" -G "$key" -C "$TMP"/primary.context || fail=$?;;
+    4) tpm2_createprimary -Q -C "$auth" -g "$hash" -G "$key" -c "$TMP"/primary.context || fail=$?;;
+    *) fail=1;;
+esac
+if [ -n "$fail" ]; then
     echo "Creating TPM2 primary key failed!" >&2
     exit 1
 fi
 
+policy_options=()
 if [ -n "$pcr_ids" ]; then
     if [ -z "$pcr_digest" ]; then
-        if ! tpm2_pcrlist -Q -L "$pcr_bank":"$pcr_ids" -o $TMP/pcr.digest; then
+        case "$TPM2TOOLS_VERSION" in
+            3) tpm2_pcrlist -Q -L "$pcr_bank":"$pcr_ids" -o "$TMP"/pcr.digest || fail=$?;;
+            4) tpm2_pcrread -Q "$pcr_bank":"$pcr_ids" -o "$TMP"/pcr.digest || fail=$?;;
+            *) fail=1;;
+        esac
+        if [ -n "$fail" ]; then
             echo "Creating PCR hashes file failed!" >&2
             exit 1
         fi
@@ -124,40 +170,59 @@ if [ -n "$pcr_ids" ]; then
         fi
     fi
 
-    if ! tpm2_createpolicy -Q -P -L "$pcr_bank":"$pcr_ids" -F $TMP/pcr.digest -f $TMP/pcr.policy; then
+    case "$TPM2TOOLS_VERSION" in
+        3) tpm2_createpolicy -Q -g "$hash" -P -L "$pcr_bank":"$pcr_ids" \
+                             -F "$TMP"/pcr.digest -f "$TMP"/pcr.policy || fail=$?;;
+        4) tpm2_createpolicy -Q -g "$hash" --policy-pcr -l "$pcr_bank":"$pcr_ids" \
+                             -f "$TMP"/pcr.digest -L "$TMP"/pcr.policy || fail=$?;;
+        *) fail=1;;
+    esac
+    if [ -n "$fail" ]; then
         echo "create policy fail, please check the environment or parameters!"
         exit 1
     fi
 
-    policy_options="-L $TMP/pcr.policy"
+    policy_options+=(-L "$TMP/pcr.policy")
+else
+    obj_attr="$obj_attr|userwithauth"
 fi
 
-if ! tpm2_create -Q -g "$hash" -G "$alg_create_key" -c $TMP/primary.context -u $TMP/jwk.pub \
-     -r $TMP/jwk.priv -A "$obj_attr" $policy_options -I- <<< "$jwk"; then
+case "$TPM2TOOLS_VERSION" in
+    3) tpm2_create -Q -g "$hash" -G "$alg_create_key" -c "$TMP"/primary.context -u "$TMP"/jwk.pub \
+                   -r "$TMP"/jwk.priv -A "$obj_attr" "${policy_options[@]}" -I- <<< "$jwk" || fail=$?;;
+    4) tpm2_create -Q -g "$hash" -C "$TMP"/primary.context -u "$TMP"/jwk.pub \
+                   -r "$TMP"/jwk.priv -a "$obj_attr" "${policy_options[@]}" -i- <<< "$jwk" || fail=$?;;
+    *) fail=1;;
+esac
+if [ -n "$fail" ]; then
     echo "Creating TPM2 object for jwk failed!" >&2
     exit 1
 fi
 
-if ! jwk_pub=`jose b64 enc -I $TMP/jwk.pub`; then
+if ! jwk_pub="$(jose b64 enc -I "$TMP"/jwk.pub)"; then
     echo "Encoding jwk.pub in Base64 failed!" >&2
     exit 1
 fi
 
-if ! jwk_priv=`jose b64 enc -I $TMP/jwk.priv`; then
+if ! jwk_priv="$(jose b64 enc -I "$TMP"/jwk.priv)"; then
     echo "Encoding jwk.priv in Base64 failed!" >&2
     exit 1
 fi
 
 jwe='{"protected":{"clevis":{"pin":"tpm2","tpm2":{}}}}'
-jwe=`jose fmt -j "$jwe" -g protected -g clevis -g tpm2 -q "$hash" -s hash -UUUUo-`
-jwe=`jose fmt -j "$jwe" -g protected -g clevis -g tpm2 -q "$key" -s key -UUUUo-`
+jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g tpm2 -q "$hash" -s hash -UUUUo-)"
+jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g tpm2 -q "$key" -s key -UUUUo-)"
 
 if [ -n "$pcr_ids" ]; then
-    jwe=`jose fmt -j "$jwe" -g protected -g clevis -g tpm2 -q "$pcr_bank" -s pcr_bank -UUUUo-`
-    jwe=`jose fmt -j "$jwe" -g protected -g clevis -g tpm2 -q "$pcr_ids" -s pcr_ids -UUUUo-`
+    jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g tpm2 -q "$pcr_bank" -s pcr_bank -UUUUo-)"
+    jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g tpm2 -q "$pcr_ids" -s pcr_ids -UUUUo-)"
 fi
 
-jwe=`jose fmt -j "$jwe" -g protected -g clevis -g tpm2 -q "$jwk_pub" -s jwk_pub -UUUUo-`
-jwe=`jose fmt -j "$jwe" -g protected -g clevis -g tpm2 -q "$jwk_priv" -s jwk_priv -UUUUo-`
+jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g tpm2 -q "$jwk_pub" -s jwk_pub -UUUUo-)"
+jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g tpm2 -q "$jwk_priv" -s jwk_priv -UUUUo-)"
+
+# The on_exit() trap will not be fired after exec, so let's clean up the temp
+# directory at this point.
+[ -d "${TMP}" ] && rm -rf "${TMP}"
 
-jose jwe enc -i- -k- -I- -c < <(echo -n "$jwe$jwk"; /bin/cat)
+exec jose jwe enc -i- -k- -I- -c < <(echo -n "$jwe$jwk"; /bin/cat)

+ 23 - 3
src/pins/tpm2/meson.build

@@ -1,8 +1,9 @@
-cmds = ['createprimary', 'pcrlist', 'createpolicy', 'create', 'load', 'unseal']
+cmds = ['tpm2_createprimary', ['tpm2_pcrread', 'tpm2_pcrlist'],
+        'tpm2_createpolicy', 'tpm2_create', 'tpm2_load', 'tpm2_unseal']
 
 all = true
 foreach cmd : cmds
-  all = all and find_program('tpm2_' + cmd, required: false).found()
+  all = all and find_program(cmd, required: false).found()
 endforeach
 
 if all
@@ -11,4 +12,23 @@ if all
   mans += join_paths(meson.current_source_dir(), 'clevis-encrypt-tpm2.1')
 else
   warning('Will not install tpm2 pin due to missing dependencies!')
-endif
+endif
+
+# Tests.
+env = environment()
+env.prepend('PATH',
+  join_paths(meson.source_root(), 'src'),
+  join_paths(meson.source_root(), 'src', 'luks'),
+  join_paths(meson.source_root(), 'src', 'luks', 'tests'),
+  join_paths(meson.source_root(), 'src', 'pins', 'sss'),
+  join_paths(meson.source_root(), 'src', 'pins', 'tang'),
+  join_paths(meson.source_root(), 'src', 'pins', 'tpm2'),
+  join_paths(meson.build_root(), 'src'),
+  join_paths(meson.build_root(), 'src', 'luks'),
+  join_paths(meson.build_root(), 'src', 'luks', 'tests'),
+  join_paths(meson.build_root(), 'src', 'pins', 'sss'),
+  join_paths(meson.build_root(), 'src', 'pins', 'tang'),
+  join_paths(meson.build_root(), 'src', 'pins', 'tpm2'),
+  separator: ':'
+)
+test('pin-tpm2', find_program('pin-tpm2'), env: env, timeout: 90)

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

@@ -0,0 +1,108 @@
+#!/bin/bash -x
+# 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}")
+
+# Code to return to mark test as skipped.
+SKIP_RET_CODE=77
+
+tpm2_available() {
+    # Old environment variables for tpm2-tools 3.0
+    export TPM2TOOLS_TCTI_NAME=device
+    export TPM2TOOLS_DEVICE_FILE=
+    for dev in /dev/tpmrm?; do
+        [ -e "${dev}" ] || continue
+        TPM2TOOLS_DEVICE_FILE="${dev}"
+        break
+    done
+
+    # New environment variable for tpm2-tools >= 3.1
+    export TPM2TOOLS_TCTI="${TPM2TOOLS_TCTI_NAME}:${TPM2TOOLS_DEVICE_FILE}"
+
+    if [ -z "${TPM2TOOLS_DEVICE_FILE}" ]; then
+        echo "A TPM2 device with the in-kernel resource manager is needed!" >&2
+        return 1
+    fi
+
+    if ! [[ -r "${TPM2TOOLS_DEVICE_FILE}" \
+            && -w "${TPM2TOOLS_DEVICE_FILE}" ]]; then
+        echo "The ${TPM2TOOLS_DEVICE_FILE} device must be readable and writable!" >&2
+        return 1
+    fi
+}
+
+# Checking if we can run this test.
+if ! tpm2_available; then
+    exit ${SKIP_RET_CODE}
+fi
+
+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 '"')
+    jose b64 dec -i- <<< "${coded}"
+}
+
+test_pcr_ids() {
+    local orig="${1}"
+    local cfg="${2}"
+    local expected_pcr_ids="${3}"
+
+    local enc
+    if ! enc=$(echo "${orig}" | clevis encrypt tpm2 "${cfg}"); then
+        echo "${TEST}: encrypt failed for cfg: ${cfg}" >&1
+        return 1
+    fi
+
+    local pcr_ids
+    pcr_ids=$(decode_jwe "${enc}" \
+              | jose fmt -j- -Og clevis -Og tpm2 -Og pcr_ids -u- 2>/dev/null)
+
+    local dec
+    dec=$(echo "${enc}" | clevis decrypt)
+
+    if [ "${orig}" != "${dec}" ]; then
+        echo "${TEST}: decoded text (${dec}) does not match original one (${orig})" >&2
+        return 1
+    fi
+
+    if [ "${pcr_ids}" != "${expected_pcr_ids}" ]; then
+        echo "${TEST}: pcr_ids (${pcr_ids}) do not match the expected (${expected_pcr_ids}) result." >&2
+        return 1
+    fi
+}
+
+test_pcr_ids "${orig}" '{}' "" || exit 1
+test_pcr_ids "${orig}" '{                   }' "" || exit 1
+
+# Issue #103: now let's try a few different configs with both strings and
+# arrays and check if we get the expected pcr_ids.
+test_pcr_ids "${orig}" '{"pcr_ids": "16"}' "16" || exit 1
+test_pcr_ids "${orig}" '{"pcr_ids": ["16"]}' "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]}' "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