From d70820b5c2ee4efbf117553502a4577e840cc217 Mon Sep 17 00:00:00 2001 From: Nicolas Williams Date: Tue, 14 Sep 2021 20:26:48 -0500 Subject: [PATCH] WIP: Policy system, take 2 WIP WIP WIP -- not tested Let's try a different approach to expressing complex policies: - Policies as restricted bash scripts that can do very little besides evaluate TPM policies, and which are invoked via... - ...a driver script, `sbin/tpm2-policy`, that sets up the environment for running the policy, and takes optional additional artifacts to make available to the policy. - Constant artifacts would be things like: - signer keys for `policysigned` and `policyauthorize` - signer key names for `policyticket` and such - anything else you can imagine Non-constant artifacts would be things like: - saved object context files for signer keys for `policysigned` and `policyauthorize` - tickets from `verifysignature`, `policysecret`, and `policysigned` - timeout files - anything else you can imagine all of which can be written only in `.` / `$PWD`. - The policy scripts would run in the following environment: - a temp dir as the current directory, with `$TMPDIR` set to `$PWD` and in which all the necessary artifacts, including the policy script itself, shall have been placed - various TPM2_POLICY... env vars, mainly TPM2_POLICY_SESSION - `$PATH` set to have just two paths: an `rbin` (see below), and the `$PWD`/`$TMPDIR` itself into which the policy script will have been copied (this will allow the policy script to implement different sub-policies selected by its arguments by executing itself, possibly through the `rbin/policyor` wrapper). - The "rbin" directory in the PATH for the execution of policy scripts, with: - wrappers for all the tpm2_policy... commands - the wrapper for tpm2_policyor is a bit special, naturally - wrappers for tpm2_loadexternal and tpm2_verifysignature, and possibly others - links to or wrappers of a handful of useful system commands like `sha256sum`, `xxd`, `dc`, `bc`, `jq`, etc. - a wrapper around `xxd` and `cat` for creating files from stdin so that artifacts can be embedded as here documents in the script This way policies get all the expressive power of bash, and access to all the functionality of tpm2-tools' policy commands. TBD: - Test, debug, test, ... - Add rbin wrappers for importing duplicated keys (so they can provide authValues discretely!). - Maybe allow execution of `sbin/tpm2-recv` by linking it from the rbin? - Add sample policies that are interesting, like using a combination of `policysigned` and `policyauthorize` and `curl` (outside the policy script) to execute an external script that varies at runtime, or policies that use different passwords for N different users, or which use `policysigned` to let some other entity authenticate N different users (using `policyRef` to name them, say), policies requiring golden PCRs OR external policy, etc. Showcase `policyor`! It'd be very nice to be able to have a policy like this: (golden PCRs && user authValue) || (admin authValue OTP && NV revocation of OTP index) || (superadmin authValue) || ($policy_containing_policy_signed && policyauthorize_of_it) Perhaps with multiple superadmin authValues via `policyor`. Such a policy would allow a laptop to boot normally with a user password, and after emergency updates boot with an admin OTP, or after any mishaps via a superadmin password, or with a separate policy that blesses the current PCRs as golden. Such a policy could be used for sealing an NV index, or it could be set on local storage keys encrypted to the TPM's EKpub via `sbin/tpm2-send`, allowing for unattended server booting post-attestation, or attended server booting post-mishap (if the encrypted assets get stored "in the clear"). --- Makefile | 3 +- functions.sh | 106 ++++++++++++++++++++++- rbin/flushcontext | 80 +++++++++++++++++ rbin/import | 105 +++++++++++++++++++++++ rbin/load | 93 ++++++++++++++++++++ rbin/loadexternal | 113 ++++++++++++++++++++++++ rbin/policyauthorize | 95 ++++++++++++++++++++ rbin/policyauthorizenv | 93 ++++++++++++++++++++ rbin/policyauthvalue | 70 +++++++++++++++ rbin/policycommandcode | 70 +++++++++++++++ rbin/policycountertimer | 99 +++++++++++++++++++++ rbin/policycphash | 76 ++++++++++++++++ rbin/policyduplicationselect | 81 ++++++++++++++++++ rbin/policylocality | 70 +++++++++++++++ rbin/policynamehash | 75 ++++++++++++++++ rbin/policynv | 92 ++++++++++++++++++++ rbin/policynvwritten | 70 +++++++++++++++ rbin/policyor | 136 +++++++++++++++++++++++++++++ rbin/policypassword | 70 +++++++++++++++ rbin/policypcr | 79 +++++++++++++++++ rbin/policysecret | 104 ++++++++++++++++++++++ rbin/policysigned | 111 ++++++++++++++++++++++++ rbin/policytemplate | 78 +++++++++++++++++ rbin/policyticket | 96 +++++++++++++++++++++ rbin/startauthsession | 79 +++++++++++++++++ rbin/tpm2 | 85 ++++++++++++++++++ rbin/verifysignature | 92 ++++++++++++++++++++ rbin/writeartifact | 71 +++++++++++++++ sbin/safeboot | 1 + sbin/safeboot-tpm-unseal | 2 + sbin/tpm2-attest | 1 + sbin/tpm2-policy | 162 ++++++++++++++++++++++++----------- sbin/tpm2-send | 17 ++-- tests/test-policy | 65 ++++++++++++++ 34 files changed, 2581 insertions(+), 59 deletions(-) create mode 100755 rbin/flushcontext create mode 100644 rbin/import create mode 100644 rbin/load create mode 100755 rbin/loadexternal create mode 100755 rbin/policyauthorize create mode 100755 rbin/policyauthorizenv create mode 100755 rbin/policyauthvalue create mode 100755 rbin/policycommandcode create mode 100755 rbin/policycountertimer create mode 100755 rbin/policycphash create mode 100755 rbin/policyduplicationselect create mode 100755 rbin/policylocality create mode 100755 rbin/policynamehash create mode 100755 rbin/policynv create mode 100755 rbin/policynvwritten create mode 100755 rbin/policyor create mode 100755 rbin/policypassword create mode 100755 rbin/policypcr create mode 100755 rbin/policysecret create mode 100755 rbin/policysigned create mode 100755 rbin/policytemplate create mode 100755 rbin/policyticket create mode 100755 rbin/startauthsession create mode 100755 rbin/tpm2 create mode 100755 rbin/verifysignature create mode 100755 rbin/writeartifact create mode 100755 tests/test-policy diff --git a/Makefile b/Makefile index 886af57d..42e4b15b 100644 --- a/Makefile +++ b/Makefile @@ -307,9 +307,10 @@ shellcheck: sbin/tpm2-recv \ sbin/tpm2-policy \ initramfs/*/* \ + rbin/* \ tests/test-enroll.sh \ ; do \ - shellcheck $$file functions.sh ; \ + shellcheck -x $$file functions.sh ; \ done # Fetch several of the TPM certs and make them usable diff --git a/functions.sh b/functions.sh index dd8f04e6..0c9b9ef5 100755 --- a/functions.sh +++ b/functions.sh @@ -31,7 +31,6 @@ debug() { ((${VERBOSE:-0})) && echo "$@" >&2 ; return 0 ; } # ######################################## -TMP= TMP_MOUNT=n cleanup() { if [[ $TMP_MOUNT = "y" ]]; then @@ -41,8 +40,11 @@ cleanup() { [[ -n $TMP ]] && rm -rf "$TMP" } -trap cleanup EXIT -TMP=$(mktemp -d) +setup() { + TMP= + trap cleanup EXIT + TMP=$(mktemp -d) +} mount_tmp() { mount -t tmpfs none "$TMP" || die "Unable to mount temp directory" @@ -620,3 +622,101 @@ verify_sig() { || die "could not verify signature on $body with $pubkey" } + +# getopts_long lname optstring name [args...] +# +# lname is the name of an associative array whose indices are long option +# names and whose values are either the empty string (no option argument), +# or ':' (the option requires an argument). +# +# optstring is an optstring value for getopts +# +# optname is the name of a variable in which to put the matched option +# name / letter. +# +# args... is the arguments to parse. +# +# As with getopts, $OPTIND is set to the next argument to check at the +# next invocation. Unset OPTIND or set it to 1 to reset options +# processing. +# +# As with getopts, "--" is a special argument that ends options +# processing. +# +# Example: +# +# declare -A long=([foo]=: [id]=: [silent]='') +# foo=none +# id=$USER +# silent=false +# while getopts_long long f:i:sx opt "$@"; do +# case "$opt" in +# foo|f) foo=$OPTARG;; +# id|i) id=$OPTARG;; +# silent|s) silent=true; [[ -n $OPTARG ]] && echo "Look ma: optional option arguments! --silent=$OPTARG";; +# x) set -vx;; +# *) echo "Usage: $0 [--foo FOO | -f FOO] [--id USER | -i USER] [--silent | -s] [-x] ARGS" 1>&2; exit 1;; +# esac +# done +# +# shift $((OPTIND-1)) +# echo "foo=$foo id=$id silent=$silent; args: $#: $*" +function getopts_long { + if (($# < 3)); then + printf 'bash: illegal use of getopts_long\n' + printf 'Usage: getopts_long lname optstring name [ARGS]\n' + printf '\t{lname} is the name of an associative array variable\n' + printf '\twhose keys are long option names and values are\n' + printf '\tthe empty string (no argument) or ":" (argument\n' + printf '\trequired).\n\n' + printf '\t{optstring} and {name} are as for the {getopts}\n' + printf '\tbash builtin.\n' + return 1 + fi 1>&2 + [[ ${1:-} != lopts ]] && local -n lopts="$1" + local optstr="$2" + [[ ${3:-} != opt ]] && local -n opt="$3" + local optvar="$3" + shift 3 + + # shellcheck disable=SC2034 + OPTOPT= + OPTARG= + : "${OPTIND:=1}" + # shellcheck disable=SC2124 + opt=${@:$OPTIND:1} + if [[ $opt = -- ]]; then + opt='?' + return 1 + fi + if [[ $opt = --* ]]; then + # shellcheck disable=SC2034 + OPTOPT='-' + local optval=false + opt=${opt#--} + if [[ $opt = *=* ]]; then + OPTARG=${opt#*=} + opt=${opt%%=*} + optval=true + fi + ((++OPTIND)) + if [[ ${lopts[$opt]+yes} != yes ]]; then + ((OPTERR)) && printf 'bash: illegal long option %s\n' "$opt" 1>&2 + opt='?' + return 0 + fi + if [[ ${lopts[$opt]:-} = : ]]; then + if ! $optval; then + # shellcheck disable=SC2124 + OPTARG=${@:$OPTIND:1} + ((++OPTIND)) + fi + fi + return 0 + fi + if getopts "$optstr" "$optvar" "$@"; then + return 0 + else + return $? + fi +} diff --git a/rbin/flushcontext b/rbin/flushcontext new file mode 100755 index 00000000..3bb4d331 --- /dev/null +++ b/rbin/flushcontext @@ -0,0 +1,80 @@ +#!/bin/bash + +PROG=${0##*/} +# Restore the environment so that we have a sane PATH and can find, e.g., +# tpm2-tools. +# shellcheck disable=SC1090 disable=SC1091 +. ./env + +set -euo pipefail +shopt -s extglob + +TMPDIR=$PWD +[[ -n ${TPM2_POLICY_SESSION:-} ]] \ +|| die "TPM2_POLICY_SESSION is not set" +[[ ${TPM2_POLICY_SESSION} != */* || + ${TPM2_POLICY_SESSION} = ${PWD}/* ]] \ +|| die "TPM2_POLICY_SESSION does not exist" +[[ -s ${TPM2_POLICY_SESSION} ]] \ +|| die "TPM2_POLICY_SESSION does not exist" + +# shellcheck disable=SC2209 +function usage { + ((${1:-1} > 0)) && exec 1>&2 + pager=cat + if [[ -t 0 && -t 1 && -t 2 ]]; then + if [[ -z ${PAGER:-} ]] && type less >/dev/null 2>&1; then + pager=less + elif [[ -z ${PAGER:-} ]] && type more >/dev/null 2>&1; then + pager=more + elif [[ -n ${PAGER:-} ]]; then + pager=$PAGER + fi + fi + $pager < 0)) && exec 1>&2 + pager=cat + if [[ -t 0 && -t 1 && -t 2 ]]; then + if [[ -z ${PAGER:-} ]] && type less >/dev/null 2>&1; then + pager=less + elif [[ -z ${PAGER:-} ]] && type more >/dev/null 2>&1; then + pager=more + elif [[ -n ${PAGER:-} ]]; then + pager=$PAGER + fi + fi + $pager < 0)) && exec 1>&2 + pager=cat + if [[ -t 0 && -t 1 && -t 2 ]]; then + if [[ -z ${PAGER:-} ]] && type less >/dev/null 2>&1; then + pager=less + elif [[ -z ${PAGER:-} ]] && type more >/dev/null 2>&1; then + pager=more + elif [[ -n ${PAGER:-} ]]; then + pager=$PAGER + fi + fi + $pager < 0)) && exec 1>&2 + pager=cat + if [[ -t 0 && -t 1 && -t 2 ]]; then + if [[ -z ${PAGER:-} ]] && type less >/dev/null 2>&1; then + pager=less + elif [[ -z ${PAGER:-} ]] && type more >/dev/null 2>&1; then + pager=more + elif [[ -n ${PAGER:-} ]]; then + pager=$PAGER + fi + fi + $pager < 0)) && exec 1>&2 + pager=cat + if [[ -t 0 && -t 1 && -t 2 ]]; then + if [[ -z ${PAGER:-} ]] && type less >/dev/null 2>&1; then + pager=less + elif [[ -z ${PAGER:-} ]] && type more >/dev/null 2>&1; then + pager=more + elif [[ -n ${PAGER:-} ]]; then + pager=$PAGER + fi + fi + $pager < 0)) && exec 1>&2 + pager=cat + if [[ -t 0 && -t 1 && -t 2 ]]; then + if [[ -z ${PAGER:-} ]] && type less >/dev/null 2>&1; then + pager=less + elif [[ -z ${PAGER:-} ]] && type more >/dev/null 2>&1; then + pager=more + elif [[ -n ${PAGER:-} ]]; then + pager=$PAGER + fi + fi + $pager < 0)) && exec 1>&2 + pager=cat + if [[ -t 0 && -t 1 && -t 2 ]]; then + if [[ -z ${PAGER:-} ]] && type less >/dev/null 2>&1; then + pager=less + elif [[ -z ${PAGER:-} ]] && type more >/dev/null 2>&1; then + pager=more + elif [[ -n ${PAGER:-} ]]; then + pager=$PAGER + fi + fi + $pager < 0)) && exec 1>&2 + pager=cat + if [[ -t 0 && -t 1 && -t 2 ]]; then + if [[ -z ${PAGER:-} ]] && type less >/dev/null 2>&1; then + pager=less + elif [[ -z ${PAGER:-} ]] && type more >/dev/null 2>&1; then + pager=more + elif [[ -n ${PAGER:-} ]]; then + pager=$PAGER + fi + fi + $pager < 0)) && exec 1>&2 + pager=cat + if [[ -t 0 && -t 1 && -t 2 ]]; then + if [[ -z ${PAGER:-} ]] && type less >/dev/null 2>&1; then + pager=less + elif [[ -z ${PAGER:-} ]] && type more >/dev/null 2>&1; then + pager=more + elif [[ -n ${PAGER:-} ]]; then + pager=$PAGER + fi + fi + $pager < 0)) && exec 1>&2 + pager=cat + if [[ -t 0 && -t 1 && -t 2 ]]; then + if [[ -z ${PAGER:-} ]] && type less >/dev/null 2>&1; then + pager=less + elif [[ -z ${PAGER:-} ]] && type more >/dev/null 2>&1; then + pager=more + elif [[ -n ${PAGER:-} ]]; then + pager=$PAGER + fi + fi + $pager < 0)) && exec 1>&2 + pager=cat + if [[ -t 0 && -t 1 && -t 2 ]]; then + if [[ -z ${PAGER:-} ]] && type less >/dev/null 2>&1; then + pager=less + elif [[ -z ${PAGER:-} ]] && type more >/dev/null 2>&1; then + pager=more + elif [[ -n ${PAGER:-} ]]; then + pager=$PAGER + fi + fi + $pager < 0)) && exec 1>&2 + pager=cat + if [[ -t 0 && -t 1 && -t 2 ]]; then + if [[ -z ${PAGER:-} ]] && type less >/dev/null 2>&1; then + pager=less + elif [[ -z ${PAGER:-} ]] && type more >/dev/null 2>&1; then + pager=more + elif [[ -n ${PAGER:-} ]]; then + pager=$PAGER + fi + fi + $pager < 0)) && exec 1>&2 + pager=cat + if [[ -t 0 && -t 1 && -t 2 ]]; then + if [[ -z ${PAGER:-} ]] && type less >/dev/null 2>&1; then + pager=less + elif [[ -z ${PAGER:-} ]] && type more >/dev/null 2>&1; then + pager=more + elif [[ -n ${PAGER:-} ]]; then + pager=$PAGER + fi + fi + $pager < 0)) && exec 1>&2 + pager=cat + if [[ -t 0 && -t 1 && -t 2 ]]; then + if [[ -z ${PAGER:-} ]] && type less >/dev/null 2>&1; then + pager=less + elif [[ -z ${PAGER:-} ]] && type more >/dev/null 2>&1; then + pager=more + elif [[ -n ${PAGER:-} ]]; then + pager=$PAGER + fi + fi + $pager < 0)) && exec 1>&2 + pager=cat + if [[ -t 0 && -t 1 && -t 2 ]]; then + if [[ -z ${PAGER:-} ]] && type less >/dev/null 2>&1; then + pager=less + elif [[ -z ${PAGER:-} ]] && type more >/dev/null 2>&1; then + pager=more + elif [[ -n ${PAGER:-} ]]; then + pager=$PAGER + fi + fi + $pager < 0)) && exec 1>&2 + pager=cat + if [[ -t 0 && -t 1 && -t 2 ]]; then + if [[ -z ${PAGER:-} ]] && type less >/dev/null 2>&1; then + pager=less + elif [[ -z ${PAGER:-} ]] && type more >/dev/null 2>&1; then + pager=more + elif [[ -n ${PAGER:-} ]]; then + pager=$PAGER + fi + fi + $pager < 0)) || usage + +unset TPM2_POLICY_ALTERNATIVES + +declare -a policies=() +declare -a sessions=() +cleanup() { rm -rf "${policies[@]}" "${sessions[@]}"; } +trap cleanup EXIT + +let i=0 +declare -a cmd +while (($#)); do + # Extract POLICY_i from the argv + cmd=() + while (($#)) && [[ $1 != ";" ]]; do + cmd+=("$1") + shift + done + (($#)) && [[ $1 != ";" ]] && shift + + policy="digest-${TPM2_POLICY_SESSION}-${i}" + policies+=("$TPM2_POLICY") + if $trial || ((i != this_alt)); then + # Either we're not executing the policy, or not this arm of it. + sessions+=("${TPM2_POLICY_SESSION}-${i}") + tpm2 flushcontext --loaded-session + tpm2 startauthsession --session "${TPM2_POLICY_SESSION}-${i}" + TPM2_POLICY_SESSION=${TPM2_POLICY_SESSION}-${i} \ + TPM2_POLICY="$policy" \ + "${cmd[@]}" + tpm2 flushcontext --loaded-session + else + # shellcheck disable=SC2124 + TPM2_POLICY_ALTERNATIVES=${alts[@]:1:${#alts[@]}} \ + TPM2_POLICY="$policy" \ + "${cmd[@]}" + fi + ((++i)) +done + +tpm2_policyor --session "${TPM2_POLICY_SESSION}" \ + --policy "$TPM2_POLICY" \ + sha256:"$(join ',' "${policies[@]}")" diff --git a/rbin/policypassword b/rbin/policypassword new file mode 100755 index 00000000..094f3a51 --- /dev/null +++ b/rbin/policypassword @@ -0,0 +1,70 @@ +#!/bin/bash + +PROG=${0##*/} +# shellcheck disable=SC1090 disable=SC1091 +. ./env + +set -euo pipefail +shopt -s extglob + +TMPDIR=$PWD +[[ -n ${TPM2_POLICY_SESSION:-} ]] \ +|| die "TPM2_POLICY_SESSION is not set" +[[ ${TPM2_POLICY_SESSION} != */* || + ${TPM2_POLICY_SESSION} = ${PWD}/* ]] \ +|| die "TPM2_POLICY_SESSION does not exist" +[[ -s ${TPM2_POLICY_SESSION} ]] \ +|| die "TPM2_POLICY_SESSION does not exist" + +# shellcheck disable=SC2209 +function usage { + ((${1:-1} > 0)) && exec 1>&2 + pager=cat + if [[ -t 0 && -t 1 && -t 2 ]]; then + if [[ -z ${PAGER:-} ]] && type less >/dev/null 2>&1; then + pager=less + elif [[ -z ${PAGER:-} ]] && type more >/dev/null 2>&1; then + pager=more + elif [[ -n ${PAGER:-} ]]; then + pager=$PAGER + fi + fi + $pager < 0)) && exec 1>&2 + pager=cat + if [[ -t 0 && -t 1 && -t 2 ]]; then + if [[ -z ${PAGER:-} ]] && type less >/dev/null 2>&1; then + pager=less + elif [[ -z ${PAGER:-} ]] && type more >/dev/null 2>&1; then + pager=more + elif [[ -n ${PAGER:-} ]]; then + pager=$PAGER + fi + fi + $pager < 0)) && exec 1>&2 + pager=cat + if [[ -t 0 && -t 1 && -t 2 ]]; then + if [[ -z ${PAGER:-} ]] && type less >/dev/null 2>&1; then + pager=less + elif [[ -z ${PAGER:-} ]] && type more >/dev/null 2>&1; then + pager=more + elif [[ -n ${PAGER:-} ]]; then + pager=$PAGER + fi + fi + $pager < 0)) && exec 1>&2 + pager=cat + if [[ -t 0 && -t 1 && -t 2 ]]; then + if [[ -z ${PAGER:-} ]] && type less >/dev/null 2>&1; then + pager=less + elif [[ -z ${PAGER:-} ]] && type more >/dev/null 2>&1; then + pager=more + elif [[ -n ${PAGER:-} ]]; then + pager=$PAGER + fi + fi + $pager < 0)) && exec 1>&2 + pager=cat + if [[ -t 0 && -t 1 && -t 2 ]]; then + if [[ -z ${PAGER:-} ]] && type less >/dev/null 2>&1; then + pager=less + elif [[ -z ${PAGER:-} ]] && type more >/dev/null 2>&1; then + pager=more + elif [[ -n ${PAGER:-} ]]; then + pager=$PAGER + fi + fi + $pager < 0)) && exec 1>&2 + pager=cat + if [[ -t 0 && -t 1 && -t 2 ]]; then + if [[ -z ${PAGER:-} ]] && type less >/dev/null 2>&1; then + pager=less + elif [[ -z ${PAGER:-} ]] && type more >/dev/null 2>&1; then + pager=more + elif [[ -n ${PAGER:-} ]]; then + pager=$PAGER + fi + fi + $pager < 0)) && exec 1>&2 + pager=cat + if [[ -t 0 && -t 1 && -t 2 ]]; then + if [[ -z ${PAGER:-} ]] && type less >/dev/null 2>&1; then + pager=less + elif [[ -z ${PAGER:-} ]] && type more >/dev/null 2>&1; then + pager=more + elif [[ -n ${PAGER:-} ]]; then + pager=$PAGER + fi + fi + $pager < 0)) && exec 1>&2 + pager=cat + if [[ -t 0 && -t 1 && -t 2 ]]; then + if [[ -z ${PAGER:-} ]] && type less >/dev/null 2>&1; then + pager=less + elif [[ -z ${PAGER:-} ]] && type more >/dev/null 2>&1; then + pager=more + elif [[ -n ${PAGER:-} ]]; then + pager=$PAGER + fi + fi + $pager < 0)) && exec 1>&2 + pager=cat + if [[ -t 0 && -t 1 && -t 2 ]]; then + if [[ -z ${PAGER:-} ]] && type less >/dev/null 2>&1; then + pager=less + elif [[ -z ${PAGER:-} ]] && type more >/dev/null 2>&1; then + pager=more + elif [[ -n ${PAGER:-} ]]; then + pager=$PAGER + fi + fi + $pager < 0)) && exec 1>&2 + pager=cat + if [[ -t 0 && -t 1 && -t 2 ]]; then + if [[ -z ${PAGER:-} ]] && type less >/dev/null 2>&1; then + pager=less + elif [[ -z ${PAGER:-} ]] && type more >/dev/null 2>&1; then + pager=more + elif [[ -n ${PAGER:-} ]]; then + pager=$PAGER + fi + fi + $pager < "$1" +else + cat > "$1" +fi diff --git a/sbin/safeboot b/sbin/safeboot index e104e30b..a76de448 100755 --- a/sbin/safeboot +++ b/sbin/safeboot @@ -29,6 +29,7 @@ export LC_ALL=C # shellcheck source=functions.sh . "$PREFIX$DIR/functions.sh" +setup if [ -r "$PREFIX$DIR/safeboot.conf" ]; then # shellcheck source=safeboot.conf diff --git a/sbin/safeboot-tpm-unseal b/sbin/safeboot-tpm-unseal index aad13dfc..3a4ba378 100755 --- a/sbin/safeboot-tpm-unseal +++ b/sbin/safeboot-tpm-unseal @@ -32,6 +32,8 @@ for script in \ fi done +setup + # Override die to extend the boot mode PCR to indicate the failure die() { echo >&2 "$@" diff --git a/sbin/tpm2-attest b/sbin/tpm2-attest index 3be8bc0a..7c9cc2c6 100755 --- a/sbin/tpm2-attest +++ b/sbin/tpm2-attest @@ -26,6 +26,7 @@ TOP="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && cd .. && pwd )" : "${DIR:=/etc/safeboot}" . "$TOP/functions.sh" +setup if [ -r "$PREFIX$DIR/safeboot.conf" ]; then . $PREFIX$DIR/safeboot.conf diff --git a/sbin/tpm2-policy b/sbin/tpm2-policy index 0c85c2f9..d865b0c8 100755 --- a/sbin/tpm2-policy +++ b/sbin/tpm2-policy @@ -1,14 +1,8 @@ #!/bin/bash PROG=${0##*/} - -if [[ $0 = /* ]]; then - BASEDIR=${0%/*} -elif [[ $0 = */* ]]; then - BASEDIR=$PWD/${0%/*} -else - BASEDIR=$PWD -fi +BASEDIR=$(dirname "$( dirname "$(readlink -f "${BASH_SOURCE[0]}")")") +export BASEDIR set -euo pipefail -o noclobber shopt -s extglob @@ -16,61 +10,133 @@ shopt -s extglob function usage { ((${1:-1} > 0)) && exec 1>&2 cat < wrapper for {tpm2_verifysignature} + - {loadexternal} -> wrapper for {tpm2_loadexternal} + - {policy*} -> wrappers for {tpm2_policy*} + - sha256sum + - stat + - xxd - E.g.: + The wrappers will not allow access to files outside {TMPDIR}. - $ # Require that PCR 11 be unextended - $ $PROG tpm2 policypcr -l "sha256:11" + TMPDIR will be set to a directory that the policy script can use for its + needs. In particular, any key contexts loaded from policy artifacts, and any + tickets made by any of the wrapped tpm2 commands, must be placed in TMPDIR. + + The current directory when running the policy script will be the {TMPDIR}. EOF exit "${1:-1}" } # shellcheck disable=SC1090 -. "$BASEDIR/../functions.sh" - -out= -force=false -activate=false -rsa_decrypt=false -command_code= -while getopts +:ADhefo:x opt; do -case "$opt" in -A) activate=true;; -D) rsa_decrypt=true;; -h) usage 0;; -f) force=true;; -o) out=$OPTARG;; -x) set -vx;; -*) usage;; -esac -done -shift $((OPTIND - 1)) - -! $activate || ! $rsa_decrypt || die "-A and -D are mutually exclusive" -$activate && command_code=TPM2_CC_ActivateCredential -$rsa_decrypt && command_code=TPM2_CC_RSA_Decrypt +. "$BASEDIR/functions.sh" + +# shellcheck disable=SC2034 +declare -A lopts=( + [help]='' + [trace]='' + [file]=: + [path]=: + [session]=: + [policy]=: + [existing]='' + [trial]='' +) # Make a temp dir and remove it when we exit: d= -trap 'rm -rf "$d"' EXIT +trap 'cd /; rm -rf "$d"' EXIT d=$(mktemp -d) +export TMPDIR="$d" + +existing=false +alts= +trial=false +policy= +session= +while getopts_long lopts +:L:NP:S:Thx opt "$@"; do +# shellcheck disable=SC2154 +case "$opt" in +h|help) usage 0;; +x|trace) set -vx;; +F|file) cp -f "$OPTARG" "$TMPDIR";; +L|policy) policy=$OPTARG;; +E|existing) existing=true;; +P|path) alts=$OPTARG; trial=false;; +S|session) session=$OPTARG;; +T|trial) trial=true;; +*) usage;; +esac +done +shift $((OPTIND - 1)) +(($#)) || usage + +cd "$TMPDIR" + +# Save the current environment so it can be restored easily by the rbin +# wrappers. Make sure the env is not writable so that the restricted bash +# script cannot write it. +unset TPM2_POLICY_SESSION TPM2_POLICY_ALTERNATIVES TPM2_POLICY +export PREFIX DIR +(umask 0222; export -p > "${TMPDIR}/env") + +[[ -z ${session:-} ]] && session=${TMPDIR}/session +if $existing; then + [[ -z ${session:-} || ! -s ${session:-} ]] \ + && die "Session file exists" +elif ! $existing; then + if $trial; then + tpm2 startauthsession --session "$session" + else + tpm2 startauthsession --session "$session" \ + --policy-session + fi +fi -policyDigest=$(make_policyDigest $command_code "$@") -[[ -z $out ]] || ! $force >| "$out" -[[ -z $out ]] || $force > "$out" -echo "$policyDigest" +: "${policy:="$(mktemp)"}" +TPM2_POLICY_SESSION=$session +TPM2_POLICY_ALTERNATIVES=$alts +TPM2_POLICY=${policy} +export TPM2_POLICY TPM2_POLICY_SESSION TPM2_POLICY_ALTERNATIVES + +script=$1 +shift + +# Run restricted scripts with FD 4 set to /dev/null so they can redirect output +# to /dev/null using 1>&4 and 2>&4. +BASH_ENV="${BASEDIR}/functions.sh" \ +PATH="$BASEDIR/rbin" \ +/bin/rbash "${script}" "$@" 4<>/dev/null +bin2hex < "$policy" diff --git a/sbin/tpm2-send b/sbin/tpm2-send index dbd5ca81..1b6e83d3 100755 --- a/sbin/tpm2-send +++ b/sbin/tpm2-send @@ -1,17 +1,20 @@ #!/bin/bash PROG=${0##*/} -if [[ $0 = /* ]]; then - BASEDIR=${0%/*} -elif [[ $0 = */* ]]; then - BASEDIR=$PWD/${0%/*} -else - BASEDIR=$PWD -fi +BASEDIR=$( dirname "$(readlink -f "${BASH_SOURCE[0]}")") set -euo pipefail shopt -s extglob +# shellcheck disable=SC2034 +declare -A lopts=( + [help]=h + [method]=M: + [policy]=P: + [force]=f: + [trace]=x +) + # shellcheck disable=SC2209 function usage { ((${1:-1} > 0)) && exec 1>&2 diff --git a/tests/test-policy b/tests/test-policy new file mode 100755 index 00000000..20cedc5f --- /dev/null +++ b/tests/test-policy @@ -0,0 +1,65 @@ +#!/bin/bash + +PROG=${0##*/} + +set -euo pipefail +shopt -s extglob + +die() { echo "Error: $*" 1>&2; exit 1; } + +[[ ${TMPDIR:-} ]] \ +|| die "TMPDIR is not set" +[[ ${TMPDIR:-} = "$PWD" ]] \ +|| die "TMPDIR is not equal to \$PWD" +[[ -n ${TPM2_POLICY_SESSION:-} ]] \ +|| die "TPM2_POLICY_SESSION is not set" +[[ ${TPM2_POLICY_SESSION} != */* || + ${TPM2_POLICY_SESSION} = ${PWD}/* ]] \ +|| die "TPM2_POLICY_SESSION does not exist" +[[ -s ${TPM2_POLICY_SESSION} ]] \ +|| die "TPM2_POLICY_SESSION does not exist" + +IFS=: read -a path <<<"$PATH" +((${#path[@]} < 1 || ${#path[@]} > 2)) \ +&& die "PATH not set correctly (wrong number of paths)" +((${#path[@]} == 1)) && [[ ${path[0]} != */rbin ]] \ +&& die "PATH not set correctly (rbin missing)" +((${#path[@]} == 2)) \ +&& [[ ${path[0]} != */rbin && ${path[1]} != */rbin ]] \ +&& die "PATH not set correctly (rbin missing)" +((${#path[@]} == 2)) \ +&& [[ ${path[0]} != "$PWD" && ${path[1]} != "$PWD" ]] \ +&& die "PATH not set correctly (something other than \$PWD and rbin is present)" + +# shellcheck disable=SC2209 +function usage { + ((${1:-1} > 0)) && exec 1>&2 + local -a usage + readarray usage <