mirror of
https://github.com/tpm2dev/tpm.dev.tutorials.git
synced 2024-12-26 19:32:10 +00:00
Add sample encrypt-to-TPM scripts
This commit is contained in:
parent
463d39f272
commit
c79b99e117
4 changed files with 456 additions and 4 deletions
|
@ -1,6 +1,7 @@
|
|||
# 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
|
||||
|
||||
|
@ -820,6 +821,11 @@ We'll discuss two ways to do this:
|
|||
the value zero so that extending it can disable use of
|
||||
`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`
|
||||
|
||||
I.e., an `AK` that lacks the `stClear` attribute, and _preferably_
|
||||
|
|
|
@ -38,7 +38,7 @@ device using only that information.
|
|||
- encrypted filesystems?
|
||||
- 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
|
||||
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
|
||||
credentials, etc.
|
||||
|
||||
See [attestation](/Attestation/README.md) for details of how to
|
||||
transport secrets onto an enrolled device post-enrollment.
|
||||
See [attestation](/Attestation/README.md#Secret-Transport-Sub-Protocols)
|
||||
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
|
||||
|
||||
|
|
165
Enrollment/send-to-tpm.sh
Executable file
165
Enrollment/send-to-tpm.sh
Executable 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
194
Enrollment/tpm-receive.sh
Executable 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"
|
Loading…
Reference in a new issue