Skip to content

Commit

Permalink
WIP: Policy system, take 2
Browse files Browse the repository at this point in the history
                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").
  • Loading branch information
nicowilliams committed Sep 16, 2021
1 parent 0a241f0 commit 6f38748
Show file tree
Hide file tree
Showing 33 changed files with 2,495 additions and 59 deletions.
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
106 changes: 103 additions & 3 deletions functions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ debug() { ((${VERBOSE:-0})) && echo "$@" >&2 ; return 0 ; }
#
########################################

TMP=
TMP_MOUNT=n
cleanup() {
if [[ $TMP_MOUNT = "y" ]]; then
Expand All @@ -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"
Expand Down Expand Up @@ -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
}
80 changes: 80 additions & 0 deletions rbin/flushcontext
Original file line number Diff line number Diff line change
@@ -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 <<EOF
Usage: $PROG [options]
{$PROG} is a wrapper around {tpm2 flushcontext}.
See {${BASEDIR}/sbin/tpm2-policy}.
Options:
-h | --help This help message.
-x | --trace Trace this script.
-t | --transient-object
-l | --loaded-session
-s | --saved-session
EOF
exit "${1:-1}"
}

# shellcheck disable=SC1090 disable=SC1091
. "$BASEDIR/functions.sh"

# shellcheck disable=SC2034
declare -A lopts=(
[help]=''
[trace]=''
[transient-object]=:
[loaded-session]=:
[saved-session]=:
)

declare -a args=(--policy "$TPM2_POLICY" --session "$TPM2_POLICY_SESSION")

while getopts_long lopts +:hxlst opt "$@"; do
# opt is set in getopts_long
# shellcheck disable=SC2154
case "$opt" in
h|help) usage 0;;
x|trace) set -vx;;
t|transient-object|l|loaded-session|s|saved-session)
args+=(-"${OPTOPT}$opt" "$OPTARG");;
*) usage;;
esac
done
shift $((OPTIND - 1))

# Finally:
tpm2_flushcontext "${args[@]}" "$@"
105 changes: 105 additions & 0 deletions rbin/import
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#!/bin/bash

PROG=${0##*/}
# shellcheck disable=SC1090 disable=SC1091
. ./env

set -euo pipefail
shopt -s extglob

TMPDIR=$PWD

# 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 <<EOF
Usage: $PROG [options] AUTH
{$PROG} is a wrapper around {tpm2 import}.
See {${BASEDIR}/sbin/tpm2-policy}.
Options:
-h | --help This help message.
-x | --trace Trace this script.
-C | --parent-context OBJECT New parent for imported object.
-U | --parent-public FILE Parent's public part.
-s | --seed FILE Output of {tpm2 duplicate}.
-u | --public FILE Output of {tpm2 duplicate}.
-i | --input FILE Output of {tpm2 duplicate}.
-r | --private FILE Output for {tpm2 load}.
-g | --hash-algorithm ALGORITHM Hash algorithm.
-G | --key-algorithm ALGORITHM Key algorithm.
-a | --attributes ATTRIBUTES Attributes for raw external key.
-L | --policy FILE Policy for raw external key.
-k | --encryption-key
-P | --parent-auth AUTH
-p | --key-auth AUTH
--cphash FILE Write cpHash of TPM2_Import() command.
Input and output files are limited to files in the current directory.
EOF
exit "${1:-1}"
}

# shellcheck disable=SC1090 disable=SC1091
. "$BASEDIR/functions.sh"

# shellcheck disable=SC2034
declare -A lopts=(
[help]=''
[trace]=''
[parent-context]=:
[parent-public]=:
[seed]=:
[private]=:
[public]=:
[input]=:
[hash-algorithm]=:
[key-algorithm]=:
[attributes]=:
[encryption-key]=:
[parent-auth]=:
[key-auth]=:
[cphash]=:
)

declare -a args=(--policy "$TPM2_POLICY" --session "$TPM2_POLICY_SESSION")

while getopts_long lopts +:hxC:U:s:r:u:i:g:G:a:L:k:P:p: opt "$@"; do
# opt is set in getopts_long
# shellcheck disable=SC2154
case "$opt" in
h|help) usage 0;;
x|trace) set -vx;;
C|parent-context|U|parent-public|s|seed|r|private|u|public|k|encryption-key|cphash)
[[ $OPTARG = */* ]] \
&& die "Cannot reference files outside the current directory"
args+=(-"${OPTOPT}$opt" "$OPTARG");;
P|parent-auth|p|key-auth)
case "$OPTARG" in
str:*) args+=(-"${OPTOPT}$opt" "$OPTARG");;
hex:*) args+=(-"${OPTOPT}$opt" "$OPTARG");;
file:*) [[ $OPTARG = */* ]] \
&& die "Cannot reference input files outside the current directory"
args+=(-"${OPTOPT}$opt" "$OPTARG");;
*) die "Auth values must start with str: hex: or file:";;
esac;;
*) usage;;
esac
done
shift $((OPTIND - 1))

# Finally:
tpm2_import "${args[@]}" "$@"
Loading

0 comments on commit 6f38748

Please sign in to comment.