Update enrollment docs, send/recv scripts

This commit is contained in:
Nicolas Williams 2021-07-09 15:17:09 -05:00
parent 5b1f54e81c
commit 79bbeeb759
5 changed files with 697 additions and 388 deletions

View file

@ -23,6 +23,19 @@ For example, one might scan an endorsement key (EK) public key or
certificate from a QR code on a shipment manifest and then enroll the
device using only that information.
## Safeboot.dev Enrollment Protocol
[Safeboot.dev](https://safeboot.dev) has an enrollment script,
`attest-enroll` which can have a trivial HTTP API binding where an
authenticated and authorized client principal `POST`s an `EKpub` and a
device name to the server. The server then creates enrollment state for
the device that will be used during subsequence attestation.
The [safeboot.dev enrollment process](https://github.com/osresearch/safeboot/blob/master/docs/enrollment.md)
does not require any interaction with the enrollee device except to
extract its `EKpub`. When the `EKpub` can be determined in an off-line
manner, then the safeboot.dev enrollment process can be fully off-line.
# Server-Side State to Create during Enrollment
- device name <-> EKpub binding
@ -52,19 +65,34 @@ device post-enrollment.
## Encrypt-to-TPM Sample Scripts
A pair of scripts are included here to demonstrate how to make long-term
secrets encrypted to TPMs for use in
[attestation](/Attestation/README.md) protocols. The method used is the
one described in the [attestation
A pair of scripts used in [safeboot.dev](https://safeboot.dev) are
included here to demonstrate how to make long-term secrets encrypted to
TPMs for use in [attestation](/Attestation/README.md) protocols. The
method used is the one described in the [attestation
tutorial](/Attestation/README.md#Secret-Transport-Sub-Protocols) using
[`TPM2_MakeCredential()`](/TPM-Commands/TPM2_MakeCredential.md) and
[`TPM2_ActivateCredential()`](/TPM-Commands/TPM2_ActivateCredential.md)
with a hard-coded, _well-known_ activation key (`WK`) to implement
encryption-to-`EKpub` with (optional) sender-asserted authorization
policy:
either of two methods to encrypt to a TPM:
- [`send-to-tpm.sh`](send-to-tpm.sh)
- [`tpm-receive.sh`](tpm-receive.sh)
- The "EK" method of encryption to a TPM uses
[`TPM2_MakeCredential()`](/TPM-Commands/TPM2_MakeCredential.md) and
[`TPM2_ActivateCredential()`](/TPM-Commands/TPM2_ActivateCredential.md)
with a hard-coded, _well-known_ activation key (`WK`) to implement
encryption-to-`EKpub` with (optional) sender-asserted authorization
policy.
- The "TK" method of encryption to a TPM uses a software implementation
of [`TPM2_Duplicate()`](/TPM-Commands/TPM2_Duplicate.md) to wrap the
private part of a "transport key" (`TK`) to the target TPM, then
normal RSA encryption to the public part of the `TK`. The ciphertext
consists of the outputs of `TPM2_Duplicate()` and the ciphertext
produced by RSA encryption to the TK.
The "EK" method is the default. Both methods support sender-asserted
policies.
The scripts:
- [`send-to-tpm`](send-to-tpm)
- [`tpm-receive`](tpm-receive)
You can use these scripts like so:
@ -76,65 +104,73 @@ You can use these scripts like so:
: ;
: ; # Encrypt the secret to some TPM whose EKpub is in a file named
: ; # ek.pub:
: ; /safeboot/sbin/send-to-tpm.sh ek.pub secret.bin cipher.bin
fd32fa22c52cfc8e1a0c29eb38519f87084cab0b04b0d8f020a4d38b2f4e223e
7fdad037a921f7eec4f97c08722692028e96888f0b970dc7b3bb6a9c97e8f988
: ; /safeboot/sbin/send-to-tpm ek.pub secret.bin cipher.bin
: ;
```
```bash
: ; # Decrypt the secret:
: ; tpm-receive.sh cipher.bin secret.bin
fd32fa22c52cfc8e1a0c29eb38519f87084cab0b04b0d8f020a4d38b2f4e223e
7fdad037a921f7eec4f97c08722692028e96888f0b970dc7b3bb6a9c97e8f988
name: 000be1fe1b777ead331f2da896ced2bf7a3949d732a0c6adf6f0a292567d587c4408
: ; tpm-receive cipher.bin plaintext.bin
837197674484b3f81a90cc8d46a5d724fd52d76e06520b64f2a1da1b331469aa
fd32fa22c52cfc8e1a0c29eb38519f87084cab0b04b0d8f020a4d38b2f4e223e
7fdad037a921f7eec4f97c08722692028e96888f0b970dc7b3bb6a9c97e8f988
certinfodata:b7bd59980628c33a14377d53e165c229
name: 000bc76d1462d32d5e6051d0aa121edfa5ed66b8e7f3632ce3c5a172b1ebd8aabc40
: ;
```
- with policy
> Here we use a policy that `PCR #11` has not been extended. The
> idea is to extend it immediately after decrypting the ciphertext,
> which means that the ciphertext cannot again be decrypted later
> (by, say, some other application with access to the same TPM)
> without rebooting.
```bash
: ; # Make up a policy (here that PCR11 must be unextended):
: ; dd if=/dev/zero of=pcr.dat bs=32 count=1
: ; policy=(tpm2 policypcr -l sha256:11 -f pcr.dat)
: ;
: ; send-to-tpm.sh ek.pub secret.bin cipher.bin "${policy[@]}"
: ; send-to-tpm ek.pub secret.bin cipher.bin "${policy[@]}"
fd32fa22c52cfc8e1a0c29eb38519f87084cab0b04b0d8f020a4d38b2f4e223e
7fdad037a921f7eec4f97c08722692028e96888f0b970dc7b3bb6a9c97e8f988
7fdad037a921f7eec4f97c08722692028e96888f0b970dc7b3bb6a9c97e8f988
policyDigest:
7fdad037a921f7eec4f97c08722692028e96888f0b970dc7b3bb6a9c97e8f988
: ;
```
```bash
: ; # We have to satisfy the same policy on the receive side:
: ; policy=(tpm2 policypcr -l sha256:11 -f pcr.dat)
: ;
: ; tpm-receive.sh -f cipher.bin "${policy[@]}"
: ; tpm-receive cipher.bin plaintext.bin "${policy[@]}"
fd32fa22c52cfc8e1a0c29eb38519f87084cab0b04b0d8f020a4d38b2f4e223e
7fdad037a921f7eec4f97c08722692028e96888f0b970dc7b3bb6a9c97e8f988
name: 000be1fe1b777ead331f2da896ced2bf7a3949d732a0c6adf6f0a292567d587c4408
7fdad037a921f7eec4f97c08722692028e96888f0b970dc7b3bb6a9c97e8f988
837197674484b3f81a90cc8d46a5d724fd52d76e06520b64f2a1da1b331469aa
name: 000b20a6cc44c93ad206196c65028f9a8bf2590de0b8f89bca9e968f09f4e616dba6
fd32fa22c52cfc8e1a0c29eb38519f87084cab0b04b0d8f020a4d38b2f4e223e
7fdad037a921f7eec4f97c08722692028e96888f0b970dc7b3bb6a9c97e8f988
certinfodata:b7bd59980628c33a14377d53e165c229
7fdad037a921f7eec4f97c08722692028e96888f0b970dc7b3bb6a9c97e8f988
: ;
```
Multiple policy commands can be separated with a quoted semi-colon:
```bash
send-to-tpm.sh ... tpm2 policyblah ... \; policyfoo ...
send-to-tpm ... tpm2 policyblah ... \; policyfoo ...
```
Multiple policy commands can be separated with a quoted semi-colon:
```bash
send-to-tpm.sh ... tpm2 policyblah ... \; policyfoo ...
send-to-tpm ... tpm2 policyblah ... \; policyfoo ...
```
When a policy is specified, these scripts will automatically set the
`adminWithPolicy` attribute of the activation object, and will add
`tpm2 policycommandcode TPM2_CC_ActivateCredential` to the policy, as
that is required for activation objects with `adminWithPolicy` set.
`tpm2 policycommandcode TPM2_CC_ActivateCredential` ("EK" method) or
`tpm2 policycommandcode TPM2_CC_RSA_Decrypt` ("TK" method) to the
policy.
# Enrollment Semantics
@ -224,3 +260,20 @@ TPMs then perhaps there is a role for the TPM to play in enrollment.
# Security Considerations
TBD
The enrollment database, though it contains ciphertexts of secrets
encrypted to enrolled devices' TPMs, is nonetheless to be kept
confidential. This is necessary to avoid attacks where an attacker
compromises an enrolled device then attempts to decrypt those
ciphertexts with the enrolled device's TPM. These ciphertexts should
only be furnished to the device as part of an attestation protocol.
For the same reason, these ciphertexts must be super-encrypted when
delivering them to enrolled devices during attestation.
Ciphertexts in enrolled state should be made with suitable sender-
asserted policies. For example, asserting that `PCR #11` has not been
extended so that immediately after decrypting such a ciphertext the
client can extend `PCR #11` to make decrypting that ciphertext again
impossible without an intervening reboot.

331
Enrollment/send-to-tpm Executable file
View file

@ -0,0 +1,331 @@
#!/bin/bash
PROG=${0##*/}
set -euo pipefail
shopt -s extglob
die() { echo "${PROG:+${PROG}: }$die_msg""$*" >&2 ; exit 1 ; }
info() { ((${VERBOSE:-0})) && echo "$@" >&2 ; return 0 ; }
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 EK-PUB SECRET OUT # Null policy
$PROG EK-PUB SECRET OUT POLICY-CMD [ARGS [\\; ...]]
$PROG -P POLICY EK-PUB SECRET OUT
{$PROG} encrypts a small (up to 32 bytes) {SECRET} file (should
contain an AES key) to a target TPM -identified by {EK-PUB}- with the
caller's optional choice of policy to be enforced by that TPM. The
{EK-PUB} should be a file containing the target's EKpub in
{TPM2B_PUBLIC} format.
Options:
-h This help message.
-M EK|TK Method to use for encryption to TPM (default: EK).
-P POLICY Use the named policy or policyDigest.
-f Overwrite {OUT}.
-x Trace this script.
Policies given as positional arguments should be of the form:
tpm2 policy... args... \\; tpm2 policy args... \\; ...
without any {--session}|{-S} nor {--policy}|{-L} options.
Also, no need to include {tpm2 policycommandcode}, as {$PROG} will add
that automatically.
E.g.:
$ $PROG ./ekpub ./secret ./madecredential \\
tpm2 policypcr -l "sha256:0,1,2,3" -f pcrs
A POLICY can be a digest or an executable.
A POLICY digest would be the SHA-256 policyDigest of a policy.
A POLICY executable would be a program that, if called with no
arguments, outputs a policyDigest.
The two methods of encryption to a TPM are:
- EK Uses {TPM2_MakeCredential()} to encrypt an AES key to
the target's EKpub.
The target uses {TPM2_ActivateCredential()} to decrypt
the AES key.
A well-known key is used as the activation object, and
the given policy is associated with it.
This method produces a single file named {OUT}.
- TK Uses {TPM2_Duplicate()} to encrypt an RSA private key to
the target's EKpub, then encrypts an AES key to that
key's public key. That RSA key we refer to as a
"transport key", or TK.
The target uses {TPM2_Import()} to import the TK,
{TPM2_Load()} to load it, and {TPM2_RSA_Decrypt()} to
decrypt the AES key.
A policy, if given, is set on the TK that the TPM will
enforce when {TPM2_RSA_Decrypt()} is called.
This method produces multiple files besides {OUT},
named:
{OUT}.tk.dpriv
{OUT}.tk.seed
EOF
exit "${1:-1}"
}
force=false
method=EK
policy=
policyDigest=
while getopts +:hfxM:P: opt; do
case "$opt" in
M) method=$OPTARG;;
P) policy=$OPTARG;;
h) usage 0;;
f) force=true;;
x) set -vx;;
*) usage;;
esac
done
shift $((OPTIND - 1))
function err {
echo "ERROR: $*" 1>&2
exit 1
}
case "$method" in
EK) command_code=TPM2_CC_ActivateCredential;;
TK) command_code=TPM2_CC_RSA_Decrypt;;
*) err "METHOD must be \"EK\" or \"TK\"";;
esac
if [[ -n $policy ]] && (($# > 3)); then
echo "Error: -P and policy commands are mutually exclusive" 1>&2
exit 1
fi
if [[ -n $policy ]]; then
(($# == 3)) || usage
if ((${#policy} == 64)) &&
[[ ! -f $policy && $policy = +([0-9a-fA-F]) ]]; then
# $policy is a policyDigest
policyDigest=$policy
elif [[ -x $policy ]]; then
# Run the script in $policy to get a policyDigest
policyDigest=$("$policy")
else
err "Given policy is neither a SHA-256 policyDigest nor a policy script"
fi
fi
(($# >= 3)) || usage
ekpub_file=$1
secret_file=$2
out_file=$3
shift 3
[[ -f ${ekpub_file:-} ]] || usage
[[ -f ${secret_file:-} ]] || usage
[[ -f ${out_file:-} ]] && $force && rm -f "${out_file:-}"
[[ -f ${out_file:-} ]] && err "output file ($out_file) exists"
# Make a temp dir and remove it when we exit:
d=
trap 'rm -rf "$d"' EXIT
d=$(mktemp -d)
# Execute a policy given as arguments.
#
# The first argument may be a command code; if given, then {tpm2}
# {policycommandcode} will be added to the given policy. The rest must be
# {tpm2_policy*} or {tpm2} {policy*} commands w/o any {--session}|{-c} or
# {--policy}|{-L} arguments, and multiple commands may be given separate by
# {';'}.
#
# E.g.,
#
# exec_policy TPM2_CC_ActivateCredential "$@"
# exec_policy tpm2 policypcr ... ';' tpm2 policysigned ...
function exec_policy {
local command_code=''
local add_commandcode=true
local has_policy=false
local -a cmd
if (($# > 0)) && [[ -z $1 || $1 = TPM2_CC_* ]]; then
command_code=$1
shift
fi
while (($# > 0)); do
has_policy=true
cmd=()
while (($# > 0)) && [[ $1 != ';' ]]; do
cmd+=("$1")
if ((${#cmd[@]} == 1)) && [[ ${cmd[0]} = tpm2_* ]]; then
cmd+=( --session "${d}/session.ctx"
--policy "${d}/policy")
elif ((${#cmd[@]} == 2)) && [[ ${cmd[0]} = tpm2 ]]; then
cmd+=( --session "${d}/session.ctx"
--policy "${d}/policy")
fi
shift
done
(($# > 0)) && shift
# Run the policy command in the temp dir. It -or the last command- must
# leave a file there named 'policy'.
"${cmd[@]}" 1>&2 \
|| die "unable to execute policy command: ${cmd[*]}"
[[ ${cmd[0]} = tpm2 ]] && ((${#cmd[@]} == 1)) \
&& die "Policy is incomplete"
[[ ${cmd[0]} = tpm2 && ${cmd[1]} = policycommandcode ]] \
&& add_commandcode=false
[[ ${cmd[0]} = tpm2_policycommandcode ]] \
&& add_commandcode=false
done
if $has_policy && $add_commandcode && [[ -n $command_code ]]; then
tpm2 policycommandcode \
--session "${d}/session.ctx" \
--policy "${d}/policy" \
"$command_code" 1>&2 \
|| die "unable to execute policy command: tpm2 policycommandcode $command_code"
fi
xxd -p -c 100 "${d}/policy"
}
# Compute the policyDigest of a given policy by executing it in a trial
# session.
function make_policyDigest {
tpm2 flushcontext --transient-object
tpm2 flushcontext --loaded-session
tpm2 startauthsession --session "${d}/session.ctx"
exec_policy "$@"
}
# A well-known private key just for the TPM2_MakeCredential()-based encryption
# of secrets to TPMs. It was generated with:
# openssl genpkey -genparam \
# -algorithm EC \
# -out "${d}/ecp.pem" \
# -pkeyopt ec_paramgen_curve:secp384r1 \
# -pkeyopt ec_param_enc:named_curve
# openssl genpkey -paramfile "${d}/ecp.pem"
function wkpriv {
cat <<"EOF"
-----BEGIN PRIVATE KEY-----
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDAlMnCWue7CfXjNLibH
PTJrsOLUcoxqU3FLWYEWMI+HuPnzcwwl7SkKN6cpf4H3oQihZANiAAQ1pw6D5QVw
vymljYVDyrUriOet8zPB/9tq9XJ7A54qsVkaVufAuEJ6GIvD4xUZ27manMosJADS
aW2TLJkwxecRh2eTwPtSx2U32M2/yHeuWRV/0juiIozefPsTAlHAi3E=
-----END PRIVATE KEY-----
EOF
}
# Compute a well-known activation object's name for use in
# TPM2_MakeCredential(), binding a given policy into it.
#
# This version uses a TPM via {tpm2 loadexternal}.
function wkname_tpm {
local attrs='sign'
local has_policy=
wkpriv > "${d}/wkpriv.pem"
tpm2 flushcontext --transient-object
tpm2 flushcontext --loaded-session
tpm2 flushcontext --saved-session 1>&2
# Load
if [[ -n $policyDigest ]]; then
tpm2 startauthsession --session "${d}/session.ctx"
printf '%s' "$policyDigest" | xxd -p -r > "${d}/policy"
echo "policyDigest: $(xxd -p -c 100 < "${d}/policy")" 1>&2
attrs='adminwithpolicy|sign'
has_policy=true
elif (($# > 0)); then
make_policyDigest "$command_code" "$@" 1>&2
attrs='adminwithpolicy|sign'
has_policy=true
# Flush again, but this time not saved sessions
tpm2 flushcontext --transient-object 1>&2
tpm2 flushcontext --loaded-session 1>&2
echo "policyDigest: $(xxd -p -c 100 < "${d}/policy")" 1>&2
fi
# Load the WK
tpm2 loadexternal \
--hierarchy n \
--key-algorithm ecc \
--private "${d}/wkpriv.pem" \
${has_policy:+ --policy "${d}/policy"} \
--attributes "$attrs" \
--key-context "${d}/wk.ctx" \
| grep ^name: | cut -d' ' -f2 \
|| die "unable to load the WK into a TPM for computing its name"
}
case "$method" in
EK) info "Computing WKname"
wkname=$(wkname_tpm "$@") \
|| die "unable to compute the MakeCredential activation object's cryptographic name"
info "Encrypting to EKpub using TPM2_MakeCredential"
tpm2 makecredential \
--tcti "none" \
--encryption-key "${ekpub_file}" \
--name "$wkname" \
--secret "${secret_file}" \
--credential-blob "$out_file" \
|| die "unable to MakeCredential";;
TK) info "Generating TK"
openssl genrsa -out "${d}/tk-priv.pem" \
|| die "unable to create TK private key"
openssl rsa \
-pubout \
-in "${d}/tk-priv.pem" \
-out "${d}/tk.pem" \
|| die "unable to create TK public key"
args=()
if (($# > 0)); then
make_policyDigest "$command_code" "$@" 1>&2
args=("--policy=${d}/policy")
fi
info "Exporting TK to EKpub"
tpm2 duplicate \
--tcti none \
--parent-public="$ekpub_file" \
--wrapper-algorithm=rsa \
"${args[@]}" \
--private-key="${d}/tk-priv.pem" \
--public="${out_file}.tk.pub" \
--private="${out_file}.tk.dpriv" \
--encrypted-seed="${out_file}.tk.seed" \
|| die "$0: unable to duplicate TK into TPM for EK"
info "Encrypting to TK"
openssl rsautl \
-encrypt \
-pubin \
-inkey "${d}/tk.pem" \
-in "$secret_file" \
-out "${out_file}" \
|| die "$0: unable to encrypt to TK" ;;
esac

View file

@ -1,165 +0,0 @@
#!/bin/bash
PROG=${0##*/}
set -euo pipefail
function usage {
((${1:-1} > 0)) && exec 1>&2
cat <<EOF
Usage: $PROG EK-PUB-FILE SECRET-FILE OUT-FILE
$PROG EK-PUB-FILE SECRET-FILE OUT-FILE [POLICY-CMD [ARGS [\\; ...]]]
$PROG -P well-known-key-name EK-PUB-FILE SECRET-FILE OUT-FILE
Encrypts a small secret to a TPM's EKpub with the caller's choice of
policy.
Policies should be specified as a sequence of {tpm2 policy...}
commands, with all necessary arguments except for {--session}|{-S}
and {--policy}|{-L} options. Also, no need to include {tpm2
policycommandcode}, as that will get added. E.g.:
$ $PROG ./ekpub ./secret ./madecredential \\
tpm2 policypcr -l "sha256:0,1,2,3" -f pcrs
Options:
-h This help message.
-P WKname Use the given cryptographic name binding a policy for
recipient to meet.
-f Overwrite OUT-FILE.
-x Trace this script.
EOF
exit ${1:-1}
}
force=false
wkname=
while getopts +:hfxP: opt; do
case "$opt" in
P) wkname=$OPTARG;;
h) usage 0;;
f) force=true;;
x) set -vx;;
*) usage;;
esac
done
shift $((OPTIND - 1))
(($# >= 3)) || usage
ekpub_file=$1
secret_file=$2
out_file=$3
shift 3
function err {
echo "ERROR: $*" 1>&2
exit 1
}
[[ -f ${ekpub_file:-} ]] || usage
[[ -f ${secret_file:-} ]] || usage
[[ -f ${out_file:-} ]] && $force && rm -f "${out_file:-}"
[[ -f ${out_file:-} ]] && err "output file ($out_file) exists"
# Make a temp dir and remove it when we exit:
d=
trap 'rm -rf "$d"' EXIT
d=$(mktemp -d)
function exec_policy {
local add_commandcode=true
local has_policy=false
while (($# > 0)); do
has_policy=true
cmd=()
while (($# > 0)) && [[ $1 != ';' ]]; do
cmd+=("$1")
if ((${#cmd[@]} == 1)) && [[ ${cmd[0]} = tpm2_* ]]; then
cmd+=(--session "${d}/session.ctx" --policy "${d}/policy")
elif ((${#cmd[@]} == 2)) && [[ ${cmd[0]} = tpm2 ]]; then
cmd+=(--session "${d}/session.ctx" --policy "${d}/policy")
fi
shift
done
(($# > 0)) && shift
# Run the policy command in the temp dir. It -or the last command- must
# leave a file there named 'policy'.
"${cmd[@]}"
if [[ ${cmd[0]} = tpm2 ]] && ((${#cmd[@]} == 1)); then
echo "Policy is incomplete" 1>&2
exit 1
fi
[[ ${cmd[0]} = tpm2 && ${cmd[1]} = policycommandcode ]] &&
add_commandcode=false
[[ ${cmd[0]} = tpm2_policycommandcode ]] && add_commandcode=false
done
$has_policy && $add_commandcode &&
tpm2 policycommandcode --session "${d}/session.ctx" \
--policy "${d}/policy" \
TPM2_CC_ActivateCredential
}
function make_policyDigest {
# Start a trial session, execute the given policy commands, save the
# policyDigest.
tpm2 startauthsession --session "${d}/session.ctx"
exec_policy "$@"
}
function wkname {
local attrs='decrypt|sign'
local has_policy
# This is the WK. It was generated with:
# openssl genpkey -genparam \
# -algorithm EC \
# -out "${d}/ecp.pem" \
# -pkeyopt ec_paramgen_curve:secp384r1 \
# -pkeyopt ec_param_enc:named_curve
# openssl genpkey -paramfile "${d}/ecp.pem"
cat > "${d}/wkpriv.pem" <<EOF
-----BEGIN PRIVATE KEY-----
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDAlMnCWue7CfXjNLibH
PTJrsOLUcoxqU3FLWYEWMI+HuPnzcwwl7SkKN6cpf4H3oQihZANiAAQ1pw6D5QVw
vymljYVDyrUriOet8zPB/9tq9XJ7A54qsVkaVufAuEJ6GIvD4xUZ27manMosJADS
aW2TLJkwxecRh2eTwPtSx2U32M2/yHeuWRV/0juiIozefPsTAlHAi3E=
-----END PRIVATE KEY-----
EOF
tpm2 flushcontext --transient-object
tpm2 flushcontext --loaded-session
tpm2 flushcontext --saved-session 1>&2
# Load
attrs='decrypt|sign'
if (($# > 0)); then
make_policyDigest "$@" 1>&2
attrs='adminwithpolicy|decrypt|sign'
has_policy=true
# Flush again, but this time not saved sessions
tpm2 flushcontext --transient-object 1>&2
tpm2 flushcontext --loaded-session 1>&2
fi
# Load the WK
tpm2 loadexternal -C n \
-Gecc \
-r "${d}/wkpriv.pem" \
${has_policy:+-L "${d}/policy"} \
-a "$attrs" \
-c "${d}/wk.ctx" |
grep ^name: | cut -d' ' -f2
}
[[ -z $wkname ]] && wkname=$(wkname "$@")
tpm2 makecredential \
--tcti "none" \
--encryption-key "${ekpub_file}" \
--name "$wkname" \
--secret "${secret_file}" \
--credential-blob "$out_file"

284
Enrollment/tpm-receive Executable file
View file

@ -0,0 +1,284 @@
#!/bin/bash
PROG=${0##*/}
set -euo pipefail
shopt -s extglob
die() { echo "${PROG:+${PROG}: }$die_msg""$*" >&2 ; exit 1 ; }
info() { ((${VERBOSE:-0})) && echo "$@" >&2 ; return 0 ; }
function usage {
cat <<EOF
Usage: $PROG CIPHERTEXT OUT [POLICY-CMD [ARGS] [;] ...]
Decrypts the {CIPHERTEXT} file produced by {send-to-tpm}.
If {CIPHERTEXT}.tk.pem, {CIPHERTEXT}.tk.dpriv, {CIPHERTEXT}.tk.pub,
and {CIPHERTEXT}.tk.seed exist, then the "TK" method of encryption is
assumed. Otherwise the "EK" method of encryption is assumed.
See {send-to-tpm} for details of the two encryption-to-TPM methods
supported.
The plaintext is written to the {OUT} file.
If the sender asserted a policy, that policy must be given to {$PROG}
so it can execute and satisfy it.
Policies should be specified as a sequence of {tpm2 policy...}
commands, with all necessary arguments except for {--session}|{-S}
and {--policy}|{-L} options. Also, no need to include {tpm2
policycommandcode}, as that will get added. E.g.:
$ $PROG ./ekpub ./secret ./madecredential \\
tpm2 policypcr -l "sha256:0,1,2,3" -f pcrs
Options:
-h This help message.
-f Overwrite OUT-FILE.
-x Trace this script.
EOF
exit 1
}
force=false
while getopts +:hfx opt; do
case "$opt" in
h) usage 0;;
f) force=true;;
x) set -vx;;
*) usage;;
esac
done
shift $((OPTIND - 1))
(($# >= 2)) || usage
ciphertext_file=$1
out_file=$2
shift 2
[[ ! -f ${ciphertext_file:-} ]] && die "No ciphertext file given"
[[ -f ${out_file:-} ]] && $force && rm -f "$out_file"
[[ ! -f ${out_file:-} ]] || die "Plaintext file exists; use -f?"
d=
trap 'rm -rf "$d"' EXIT
d=$(mktemp -d)
use_tk=true
command_code=TPM2_CC_RSA_Decrypt
for i in dpriv pub seed; do
($use_tk && [[ -s ${ciphertext_file}.tk.$i ]]) || use_tk=false
done
$use_tk || command_code=TPM2_CC_ActivateCredential
# Execute a policy given as arguments.
#
# The first argument may be a command code; if given, then {tpm2}
# {policycommandcode} will be added to the given policy. The rest must be
# {tpm2_policy*} or {tpm2} {policy*} commands w/o any {--session}|{-c} or
# {--policy}|{-L} arguments, and multiple commands may be given separate by
# {';'}.
#
# E.g.,
#
# exec_policy TPM2_CC_ActivateCredential "$@"
# exec_policy tpm2 policypcr ... ';' tpm2 policysigned ...
function exec_policy {
local command_code=''
local add_commandcode=true
local has_policy=false
local -a cmd
if (($# > 0)) && [[ -z $1 || $1 = TPM2_CC_* ]]; then
command_code=$1
shift
fi
while (($# > 0)); do
has_policy=true
cmd=()
while (($# > 0)) && [[ $1 != ';' ]]; do
cmd+=("$1")
if ((${#cmd[@]} == 1)) && [[ ${cmd[0]} = tpm2_* ]]; then
cmd+=( --session "${d}/session.ctx"
--policy "${d}/policy")
elif ((${#cmd[@]} == 2)) && [[ ${cmd[0]} = tpm2 ]]; then
cmd+=( --session "${d}/session.ctx"
--policy "${d}/policy")
fi
shift
done
(($# > 0)) && shift
# Run the policy command in the temp dir. It -or the last command- must
# leave a file there named 'policy'.
"${cmd[@]}" 1>&2 \
|| die "unable to execute policy command: ${cmd[*]}"
[[ ${cmd[0]} = tpm2 ]] && ((${#cmd[@]} == 1)) \
&& die "Policy is incomplete"
[[ ${cmd[0]} = tpm2 && ${cmd[1]} = policycommandcode ]] \
&& add_commandcode=false
[[ ${cmd[0]} = tpm2_policycommandcode ]] \
&& add_commandcode=false
done
if $has_policy && $add_commandcode && [[ -n $command_code ]]; then
tpm2 policycommandcode \
--session "${d}/session.ctx" \
--policy "${d}/policy" \
"$command_code" 1>&2 \
|| die "unable to execute policy command: tpm2 policycommandcode $command_code"
fi
xxd -p -c 100 "${d}/policy"
}
# Compute the policyDigest of a given policy by executing it in a trial
# session.
function make_policyDigest {
tpm2 flushcontext --transient-object
tpm2 flushcontext --loaded-session
tpm2 startauthsession --session "${d}/session.ctx"
exec_policy "$@"
}
# A well-known private key just for the TPM2_MakeCredential()-based encryption
# of secrets to TPMs. It was generated with:
# openssl genpkey -genparam \
# -algorithm EC \
# -out "${d}/ecp.pem" \
# -pkeyopt ec_paramgen_curve:secp384r1 \
# -pkeyopt ec_param_enc:named_curve
# openssl genpkey -paramfile "${d}/ecp.pem"
function wkpriv {
cat <<"EOF"
-----BEGIN PRIVATE KEY-----
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDAlMnCWue7CfXjNLibH
PTJrsOLUcoxqU3FLWYEWMI+HuPnzcwwl7SkKN6cpf4H3oQihZANiAAQ1pw6D5QVw
vymljYVDyrUriOet8zPB/9tq9XJ7A54qsVkaVufAuEJ6GIvD4xUZ27manMosJADS
aW2TLJkwxecRh2eTwPtSx2U32M2/yHeuWRV/0juiIozefPsTAlHAi3E=
-----END PRIVATE KEY-----
EOF
}
# Get the EK handle
tpm2 flushcontext --transient-object
tpm2 flushcontext --loaded-session
tpm2 flushcontext --saved-session 1>&2
tpm2 createek \
--key-algorithm rsa \
--ek-context "${d}/ek.ctx" \
--public "${d}/ek.pub" \
|| die "tpm2: unable to create ek object"
# Make policyDigest
(($# > 0)) && make_policyDigest "$command_code" "$@"
# Create empty auth session for EK
tpm2 flushcontext --transient-object
tpm2 flushcontext --loaded-session
tpm2 startauthsession --session "${d}/sessionek.ctx" --policy-session
tpm2 policysecret --session "${d}/sessionek.ctx" --object-context endorsement
# Execute and satisfy the policy for the TK or WK
function auth {
tpm2 flushcontext --transient-object
tpm2 flushcontext --loaded-session
tpm2 startauthsession \
--session "${d}/session.ctx" \
--policy-session
# exec_policy will {die} if we fail to satisfy the policy
exec_policy "$command_code" "$@"
tpm2 flushcontext --transient-object
tpm2 flushcontext --loaded-session
}
if $use_tk; then
# attempt to load the secret wrapping key into our TPM
# as a transient object
tpm2 flushcontext --transient-object
tpm2 flushcontext --loaded-session
info "tpm2: Importing duplicate transport key"
tpm2 import \
--parent-context "${d}/ek.ctx" \
--parent-auth "session:${d}/sessionek.ctx" \
--key-algorithm rsa \
--input "${ciphertext_file}.tk.dpriv" \
--seed "${ciphertext_file}.tk.seed" \
--public "${ciphertext_file}.tk.pub" \
--private "${d}/tk.priv" \
|| die "tpm2: unable to import duplicate transport key object"
warn "tpm2: Loading duplicate transport key"
tpm2 flushcontext --transient-object
tpm2 flushcontext --loaded-session
tpm2 startauthsession --session "${d}/sessionek.ctx" --policy-session
tpm2 policysecret --session "${d}/sessionek.ctx" --object-context endorsement
tpm2 load \
--parent-context "${d}/ek.ctx" \
--auth "session:${d}/sessionek.ctx" \
--key-context "${d}/tk.ctx" \
--public "${ciphertext_file}.tk.pub" \
--private "${d}/tk.priv" \
|| die "tpm2: unable to load duplicate transport key object"
warn "tpm2: Decrypting with TK"
if (($# > 0)); then
auth "$@"
tpm2 rsadecrypt \
--auth "session:${d}/session.ctx" \
--key-context "${d}/tk.ctx" \
--output "${out_file}" \
"${ciphertext_file}" \
|| die "tpm2: unable to decrypt with transport key"
else
tpm2 rsadecrypt \
--key-context "${d}/tk.ctx" \
--output "${out_file}" \
"${ciphertext_file}" \
|| die "tpm2: unable to decrypt with transport key"
fi
else
# Load the WK for use as the activation object for
# TPM2_ActivateCredential():
tpm2 flushcontext --transient-object 1>&2
tpm2 flushcontext --loaded-session 1>&2
wkpriv > "${d}/wkpriv.pem"
attrs='sign'
adminwithpolicy=
if (($# > 0)); then
attrs='adminwithpolicy|sign'
adminwithpolicy=true
fi
if tpm2 loadexternal \
--hierarchy n \
--key-algorithm ecc \
--private "${d}/wkpriv.pem" \
${adminwithpolicy:+--policy "${d}/policy"} \
--attributes "$attrs" \
--key-context "${d}/wk.ctx" 1>&2; then
true
else
stat=$?
echo "ERROR: Failed to load WK: $?" 1>&2
exit $stat
fi
# If a policy was given to execute, create a policy session and execute
# and satisfy the policy:
activatecredential_args=()
if (($# > 0)); then
activatecredential_args+=(--credentialedkey-auth session:"${d}/session.ctx")
auth "$@"
fi
# Finally, ActivateCredential
tpm2 activatecredential \
--credentialedkey-context "${d}/wk.ctx" \
"${activatecredential_args[@]}" \
--credentialkey-context "${d}/ek.ctx" \
--credentialkey-auth session:"${d}/sessionek.ctx" \
--credential-blob "$ciphertext_file" \
--certinfo-data "$out_file" > /dev/null \
|| die "could not decrypt using TPM2_ActivateCredential()"
fi

View file

@ -1,194 +0,0 @@
#!/bin/bash
PROG=${0##*/}
set -euo pipefail
function usage {
echo "Usage: $PROG [OPTIONS] CIPHERTEXT-FILE OUT-FILE [POLICY-CMD [ARGS] [\; ...]]"
cat <<EOF
Usage: $PROG CIPHERTEXT-FILE OUT-FILE [POLICY-CMD [ARGS] [;] ...]
"Activates" (decrypts) CIPHERTEXT-FILE made with TPM2_MakeCredential and
writes the plaintext to OUT-FILE. If the sender asserted some policy,
that policy must be repeated when invoking this program to decrypt the
secret.
Policies should be specified as a sequence of {tpm2 policy...}
commands, with all necessary arguments except for {--session}|{-S}
and {--policy}|{-L} options. Also, no need to include {tpm2
policycommandcode}, as that will get added. E.g.:
$ $PROG ./ekpub ./secret ./madecredential \\
tpm2 policypcr -l "sha256:0,1,2,3" -f pcrs
Options:
-h This help message.
-f Overwrite OUT-FILE.
-x Trace this script.
EOF
exit 1
}
force=false
verbose=false
while getopts +:hfvx opt; do
case "$opt" in
h) usage 0;;
f) force=true;;
v) verbose=true;;
x) set -vx;;
*) usage;;
esac
done
shift $((OPTIND - 1))
(($# >= 2)) || usage
ciphertext_file=$1
out_file=$2
shift 2
[[ -f ${ciphertext_file:-} ]] || usage
[[ -f ${out_file:-} ]] && $force && rm -f "$out_file"
[[ -f ${out_file:-} ]] && usage
d=
trap 'rm -rf "$d"' EXIT
d=$(mktemp -d)
function v {
if $verbose; then
printf 'Running:'
printf ' %q' "$@"
printf '\n'
fi >/dev/tty || true
if "$@"; then
$verbose && printf '(SUCCESS)\n' >/dev/tty || true
else
stat=$?
printf 'ERROR: Command exited with %d\n' $stat >/dev/tty || true
return $stat
fi
}
function exec_policy {
local add_commandcode=true
local has_policy=false
while (($# > 0)); do
has_policy=true
cmd=()
while (($# > 0)) && [[ $1 != ';' ]]; do
cmd+=("$1")
if ((${#cmd[@]} == 1)) && [[ ${cmd[0]} = tpm2_* ]]; then
cmd+=(--session "${d}/session.ctx" --policy "${d}/policy")
elif ((${#cmd[@]} == 2)) && [[ ${cmd[0]} = tpm2 ]]; then
cmd+=(--session "${d}/session.ctx" --policy "${d}/policy")
fi
shift
done
(($# > 0)) && shift
# Run the policy command in the temp dir. It -or the last command- must
# leave a file there named 'policy'.
"${cmd[@]}"
if [[ ${cmd[0]} = tpm2 ]] && ((${#cmd[@]} == 1)); then
echo "Policy is incomplete" 1>&2
exit 1
fi
[[ ${cmd[0]} = tpm2 && ${cmd[1]} = policycommandcode ]] &&
add_commandcode=false
[[ ${cmd[0]} = tpm2_policycommandcode ]] && add_commandcode=false
done
$has_policy && $add_commandcode &&
tpm2 policycommandcode --session "${d}/session.ctx" \
--policy "${d}/policy" \
TPM2_CC_ActivateCredential
}
function make_policyDigest {
tpm2 flushcontext --transient-object
tpm2 flushcontext --loaded-session
v tpm2 startauthsession --session "${d}/session.ctx"
exec_policy "$@"
}
# Get the EK handle:
tpm2 flushcontext --transient-object
tpm2 flushcontext --loaded-session
tpm2 flushcontext --saved-session 1>&2
tpm2 createek --key-algorithm rsa \
--ek-context "${d}/ek.ctx" \
--public "${d}/ek.pub"
# Make policyDigest and load WK
attrs='decrypt|sign'
adminwithpolicy=
if (($# > 0)); then
make_policyDigest "$@"
attrs='adminwithpolicy|decrypt|sign'
adminwithpolicy=true
fi
rm -f "${d}/session.ctx"
# This is the WK. It was generated with:
# openssl genpkey -genparam \
# -algorithm EC \
# -out "${d}/ecp.pem" \
# -pkeyopt ec_paramgen_curve:secp384r1 \
# -pkeyopt ec_param_enc:named_curve
# openssl genpkey -paramfile "${d}/ecp.pem"
cat > "${d}/wkpriv.pem" <<EOF
-----BEGIN PRIVATE KEY-----
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDAlMnCWue7CfXjNLibH
PTJrsOLUcoxqU3FLWYEWMI+HuPnzcwwl7SkKN6cpf4H3oQihZANiAAQ1pw6D5QVw
vymljYVDyrUriOet8zPB/9tq9XJ7A54qsVkaVufAuEJ6GIvD4xUZ27manMosJADS
aW2TLJkwxecRh2eTwPtSx2U32M2/yHeuWRV/0juiIozefPsTAlHAi3E=
-----END PRIVATE KEY-----
EOF
# Load the WK
v tpm2 flushcontext --transient-object 1>&2
v tpm2 flushcontext --loaded-session 1>&2
if v tpm2 loadexternal -C n \
-Gecc \
-r "${d}/wkpriv.pem" \
${adminwithpolicy:+-L "${d}/policy"} \
-a "$attrs" \
-c "${d}/wk.ctx" > "${d}/out" 2> "${d}/err"; then
cat "${d}/out" 1>&2
else
stat=$?
echo "ERROR: Failed to load WK:" 1>&2
cat "${d}/out"
cat "${d}/err" 1>&2
exit $stat
fi
# Create empty auth session for EK
v tpm2 flushcontext --transient-object
v tpm2 flushcontext --loaded-session
v tpm2 startauthsession --session "${d}/sessionek.ctx" --policy-session
v tpm2 policysecret --session "${d}/sessionek.ctx" --object-context endorsement
activatecredential_args=()
if (($# > 0)); then
activatecredential_args+=(--credentialedkey-auth session:"${d}/session.ctx")
# Create auth session for the WK, since it has adminWithPolicy
v tpm2 flushcontext --transient-object
v tpm2 flushcontext --loaded-session
v tpm2 startauthsession --session "${d}/session.ctx" --policy-session
exec_policy "$@"
v tpm2 flushcontext --transient-object
v tpm2 flushcontext --loaded-session
fi
# Finally, ActivateCredential
$verbose && tpm2 readpublic -c "${d}/wk.ctx" | grep name:
v tpm2 activatecredential --credentialedkey-context "${d}/wk.ctx" \
"${activatecredential_args[@]}" \
--credentialkey-context "${d}/ek.ctx" \
--credentialkey-auth session:"${d}/sessionek.ctx" \
--credential-blob "$ciphertext_file" \
-o "$out_file"