Add sample encrypt-to-TPM scripts

This commit is contained in:
Nicolas Williams 2021-06-10 17:26:12 -05:00
parent 463d39f272
commit c79b99e117
4 changed files with 456 additions and 4 deletions

View file

@ -1,6 +1,7 @@
# What Attestation is # What Attestation is
A computer can use a TPM to demonstrate: An [enrolled device](/Enrollment/README.md) can use a TPM to
demonstrate:
- possession of a valid TPM - possession of a valid TPM
@ -820,6 +821,11 @@ We'll discuss two ways to do this:
the value zero so that extending it can disable use of the value zero so that extending it can disable use of
`TPM2_MakeCredential()` post-boot. `TPM2_MakeCredential()` post-boot.
We have two sample bash scripts demonstrating this approach:
- [`send-to-tpm.sh`](/Enrollment/send-to-tpm.sh)
- [`tpm-receive.sh`](/Enrollment/tpm-receive.sh)
- use an `LTAK` -- a long-term `AK` - use an `LTAK` -- a long-term `AK`
I.e., an `AK` that lacks the `stClear` attribute, and _preferably_ I.e., an `AK` that lacks the `stClear` attribute, and _preferably_

View file

@ -38,7 +38,7 @@ device using only that information.
- encrypted filesystems? - encrypted filesystems?
- device credentials? (e.g., TLS server certificates, Kerberos keys ["keytabs"], etc.) - device credentials? (e.g., TLS server certificates, Kerberos keys ["keytabs"], etc.)
# Secrets Transport # Secrets Long-Term Storage and Transport
Every time an enrolled device reboots, or possibly more often, it may Every time an enrolled device reboots, or possibly more often, it may
have to connect to an attestation server to obtain secrets from it that have to connect to an attestation server to obtain secrets from it that
@ -46,8 +46,95 @@ the device needs in order to proceed. For example, filesystem
decryption keys, general network access, device authentication decryption keys, general network access, device authentication
credentials, etc. credentials, etc.
See [attestation](/Attestation/README.md) for details of how to See [attestation](/Attestation/README.md#Secret-Transport-Sub-Protocols)
transport secrets onto an enrolled device post-enrollment. for details of how to store and transport secrets onto an enrolled
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
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:
- [`send-to-tpm.sh`](send-to-tpm.sh)
- [`tpm-receive.sh`](tpm-receive.sh)
You can use these scripts like so:
- without policy:
```bash
: ; # Make a secret
: ; dd if=/dev/urandom of=secret.bin bs=16 count=1
: ;
: ; # 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
```
```bash
: ; # Decrypt the secret:
: ; tpm-receive.sh cipher.bin secret.bin
fd32fa22c52cfc8e1a0c29eb38519f87084cab0b04b0d8f020a4d38b2f4e223e
7fdad037a921f7eec4f97c08722692028e96888f0b970dc7b3bb6a9c97e8f988
name: 000be1fe1b777ead331f2da896ced2bf7a3949d732a0c6adf6f0a292567d587c4408
837197674484b3f81a90cc8d46a5d724fd52d76e06520b64f2a1da1b331469aa
fd32fa22c52cfc8e1a0c29eb38519f87084cab0b04b0d8f020a4d38b2f4e223e
7fdad037a921f7eec4f97c08722692028e96888f0b970dc7b3bb6a9c97e8f988
certinfodata:b7bd59980628c33a14377d53e165c229
: ;
- with policy
```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[@]}"
fd32fa22c52cfc8e1a0c29eb38519f87084cab0b04b0d8f020a4d38b2f4e223e
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[@]}"
fd32fa22c52cfc8e1a0c29eb38519f87084cab0b04b0d8f020a4d38b2f4e223e
7fdad037a921f7eec4f97c08722692028e96888f0b970dc7b3bb6a9c97e8f988
name: 000be1fe1b777ead331f2da896ced2bf7a3949d732a0c6adf6f0a292567d587c4408
837197674484b3f81a90cc8d46a5d724fd52d76e06520b64f2a1da1b331469aa
fd32fa22c52cfc8e1a0c29eb38519f87084cab0b04b0d8f020a4d38b2f4e223e
7fdad037a921f7eec4f97c08722692028e96888f0b970dc7b3bb6a9c97e8f988
certinfodata:b7bd59980628c33a14377d53e165c229
: ;
```
Multiple policy commands can be separated with a quoted semi-colon:
```bash
send-to-tpm.sh ... tpm2 policyblah ... \; policyfoo ...
```
Multiple policy commands can be separated with a quoted semi-colon:
```bash
send-to-tpm.sh ... 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.
# Enrollment Semantics # Enrollment Semantics

165
Enrollment/send-to-tpm.sh Executable file
View file

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

194
Enrollment/tpm-receive.sh Executable file
View file

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