Review comments

This commit is contained in:
Nicolas Williams 2021-06-04 20:54:23 -05:00 committed by Erik Larsson
parent 6363b9bbdc
commit aaa14e4bf4
3 changed files with 531 additions and 5 deletions

View file

@ -1,17 +1,105 @@
# `Passing a secret to a TPM using only the EK`
# Passing a secret to a TPM using only the public key of Endorsement Key (EK)
This is example code to pass a secret to a system by just knowing the endorsenment key.
This is example code to pass a secret to a system by just knowing its endorsenment key's public key.
We will be using the current (commit 07a92e9fa75548ea102ce90b3b6182093b3f7a73 or later) master branch of https://github.com/tpm2-software/tpm2-pytss
The terms for the systems are `client`, the system we want to pass the secret to and `server`, the system which has the secret but doesn't need a TPM.
One assumtion that will be made is that you already have the EKpub for the remote system on the local system, and trust it.
While we will use the EK in this guide any key accepted by ActivateCredential should work.
## Background
What we want is something akin to asymmetric encryption, with the local
system encrypting to the public key of the remote system. The local
system would send the ciphertext to the remote system, and the remote
system would decrypt it using its private key.
The TPM does support plain asymmetric decryption using
`TPM2_RSA_Decrypt()`. However, the `EK` is a [restricted
key](/Intro/README.md#Restricted-Cryptographic-Keys), specifically a
[restricted decryption key](/Intro/README.md#Restricted-Decryption-Keys)
which means that `TPM2_RSA_Decrypt()` will not work.
The TPM supports two constrained asymmetric decryption operations with
[restricted decryption
keys](/Intro/README.md#Restricted-Decryption-Keys):
- [`TPM2_Import()`](/TPM-Commands/TPM2_Import.md)
- [`TPM2_ActivateCredential()`](/TPM-Commands/TPM2_ActivateCredential.md)
The sender sides of those two functions are, respectively:
- [`TPM2_Duplicate()`](/TPM-Commands/TPM2_Duplicate.md)
- [`TPM2_MakeCredential()`](/TPM-Commands/TPM2_MakeCredential.md)
`TPM2_Duplicate()`/`TPM2_Import()` are specifically about sharing
private key objects from one TPM to another. We won't use those here.
[`TPM2_MakeCredential()`](/TPM-Commands/TPM2_MakeCredential.md) allows
us to encrypt a small secret (e.g., an AES key) to a remote system's
`EKpub`, and then the remote system can decrypt that with its `EK` using
[`TPM2_ActivateCredential()`](/TPM-Commands/TPM2_ActivateCredential.md).
The key background concepts here are:
- [restricted decryption keys](/Intro/README.md#Restricted-Decryption-Keys),
- and access controlled decryption with restricted decryption keys.
Most importantly,
[`TPM2_MakeCredential()`](/TPM-Commands/TPM2_MakeCredential.md) allows
the sender to specify an authorization policy that the caller of
[`TPM2_ActivateCredential()`](/TPM-Commands/TPM2_ActivateCredential.md)
must meet in order for it to be willing to decrypt the ciphertext.
> Note that `TPM2_MakeCredential()` can be implemented entirely in
> software.
> Note that duplicating a key that is fixed to TPMs requires using
> `TPM2_Duplicate()` on that TPM, otherwise if the key is not fixed to
> the TPM then `TPM2_Duplicate()` can be implemented in software.
## Concept
The concept is that the key being activated by a call to ActivateCredential on the remote system doesn't have to be generated on the TPM, as long as the public and private parts are loaded it will succeed.
By generating a temporary key pair on the local system we can run MakeCredential with the remote system EK, name of the locally generated key and the secret.
`TPM2_MakeCredential()` requires three inputs. Besides the target's
`EKpub` and the small secret to send to it, `TPM2_MakeCredential()` also
requires the [cryptographic name](/Intro/README.md#Cryptographic-Object-Naming)
of a key object that must reside on the target system's TPM -- this is
known as the _activation object_.
The key insight is that the actual public key of the object named by the
activation object name input of `TPM2_MakeCredential()` is not used at
all. Neither does `TPM2_ActivateCredential()` use the private key of
that object. The only things that matter about the activation object
are that:
a) it must exist on the target system,
b) its cryptographic name must be the same as was used on the sender side,
c) and that the caller of `TPM2_ActivateCredential()` must satisfy the activation object's [_authorization policy_](/Intro/README.md#Policies) (_if_ `adminWithPolicy` is set as an attribute of the activation object).
> NOTE: The cryptographic name of an object binds the authorization
> policy set on that object. Therefore the caller of
> `TPM2_MakeCredential()` specifies an authorization policy that the
> caller of `TPM2_ActivateCredential()` must meet if the
> `adminWithPolicy` attribute is set on the activation object.
> NOTE: Learn more about [restricted keys](/Intro/README.md#Restricted-Cryptographic-Keys),
> [authorization policies](/Intro/README.md#Policies), and
> user roles in our [introductory tutorial](/Intro/README.md).
Since the private and public key parts of the activation object are not
used and are irrelevant, they can even be fixed and published for all to
see, even the private key.
By using a well-known activation key we can avoid having to know the
cryptographic name of some unique object on the remote system's TPM!
Or we can generate a unique key but send its private part in the clear
to the remote system.
Thus we need only know the target system's TPM's `EKpub`.
## server script
```python
#!/usr/bin/python3
@ -79,6 +167,7 @@ credlob: where to save the encrypted credential generated by MakeCredential
secret: where to save the encrypted secret generated by MakeCredential
## client script
```python
#!/usr/bin/python3
@ -171,5 +260,136 @@ temp-sensitive: the temp-sensitive output from the local system script
credblob: the credblob output from the local system script
secret: the secret output from the local system script
## Example (bash)
This example uses two bash scripts:
- [`send-to-tpm.sh`](send-to-tpm.sh)
- [`tpm-receive.sh`](tpm-receive.sh)
Usage messages for those two scripts:
```
Usage: send-to-tpm.sh EK-PUB-FILE SECRET-FILE OUT-FILE [POLICY-CMD [ARGS [\; ...]]]
send-to-tpm.sh -P well-known-key-name EK-PUB-FILE SECRET-FILE OUT-FILE
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.
```
```
Usage: receive.sh CIPHERTEXT-FILE OUT-FILE [POLICY-CMD [ARGS] [;] ...]
"Activates" (decrypts) CIPHERTEXT-FILE made with TPM2_MakeCredential and
writes the plaintext to OUT-FILE.
The POLICY-CMD and arguments are one or more commands that must
leave a policy digest in a file named 'policy' in the current
directory (which will be a temporary directory).
Options:
-h This help message.
-f Overwrite OUT-FILE.
-x Trace this script.
```
Example (without policy, both scripts running on the same system):
```
: ; # NOTE: The shell prompt ($PS1) is set to ': ; ' to make it easy to
: ; # cut-and-paste.
: ;
: ; # Get the EKpub:
: ; tpm2 createek --ek-context ek.ctx --public ek.pub
: ;
: ; # Make a small secret:
: ; echo hello world > secret.txt
: ;
: ; # Make ciphertext:
: ; /tmp/send-to-tpm.sh -f ek.pub /tmp/secret /tmp/cipher
: ;
: ; # Decrypt ciphertext:
: ; /tmp/receive.sh -f /tmp/cipher /tmp/plain
name:
000b9f40e7a7a85bcc39bba777b7eda5764d91a28512d91d395ca114b14621ae321e
837197674484b3f81a90cc8d46a5d724fd52d76e06520b64f2a1da1b331469aa
certinfodata:68656c6c6f20776f726c640a
: ;
: ; # Show plaintext:
: ; cat /tmp/plain
hello world
```
Example (with policy, both scripts running on the same system):
```
: ; # NOTE: The shell prompt ($PS1) is set to ': ; ' to make it easy to
: ; # cut-and-paste.
: ;
: ; # Get the EKpub:
: ; tpm2 createek --ek-context ek.ctx --public ek.pub
: ;
: ; # Make a small secret:
: ; echo hello world > secret.txt
: ;
: ; /tmp/send-to-tpm.sh -f ek.pub /tmp/secret /tmp/cipher \
> tpm2 policysecret --session session.ctx \
> --object-context endorsement -L policy \; \
> tpm2 policycommandcode -S session.ctx -L policy \
> TPM2_CC_ActivateCredential
837197674484b3f81a90cc8d46a5d724fd52d76e06520b64f2a1da1b331469aa
cd9917cf18c3848c3a2e606986a066c68142f9bc2710a278287a650ca3bbf245
: ;
: ; /tmp/tpm-receive.sh -f /tmp/cipher /tmp/plain \
> tpm2 policysecret --session session.ctx \
--object-context endorsement \
-L policy \; \
tpm2 policycommandcode -S session.ctx -L policy \
TPM2_CC_ActivateCredential
837197674484b3f81a90cc8d46a5d724fd52d76e06520b64f2a1da1b331469aa
cd9917cf18c3848c3a2e606986a066c68142f9bc2710a278287a650ca3bbf245
name: 000bec987554f57b9918285794542c05549aa778832be169351494066907d6d95abf
837197674484b3f81a90cc8d46a5d724fd52d76e06520b64f2a1da1b331469aa
837197674484b3f81a90cc8d46a5d724fd52d76e06520b64f2a1da1b331469aa
cd9917cf18c3848c3a2e606986a066c68142f9bc2710a278287a650ca3bbf245
certinfodata:68656c6c6f20776f726c640a
: ; cat /tmp/plain
hello world
: ;
```
You can pass policy commands to the `send-to-tpm.sh` and `tpm-receive.sh`
commands as arguments, with multiple policy commands separated by a
single semi-colon (quoted, to avoid evaluation by the shell):
```bash
send-to-tpm.sh ek.pub /tmp/secret /tmp/cipher \
tpm2 policypcr -S session.ctx -l "sha256:0,1,2,3" -f $PWD/pcr.dat \
-L policy \; \
tpm2 policycommandcode -S session.ctx -L policy TPM2_CC_ActivateCredential
```
## Issues
there is no TPM based protection against replay attacks in this example
- The secret sent this way has to be small: no larger than the digest
size for the digest algorithm being used.
If the application needs to send larger secrets, then it should
generate an AES key and send that as the small secret, then encrypt
the larger secret in the AES key and send that ciphertext. (But
don't forget to also include an HMAC or MAC of the ciphertext to make
detection of errors / tampering possible.)
- There is no protection against replay attacks in this example.
Replay protection can be added by adding a timestamp to the secret
data, and by using a replay cache on the remote system.
- There is no authentication of the sender. To authenticate the sender
simply add a digital signature of the ciphertext.

131
Attestation/send-to-tpm.sh Executable file
View file

@ -0,0 +1,131 @@
#!/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
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 {
while (($# > 0)); do
cmd=()
while (($# > 0)) && [[ $1 != ';' ]]; do
cmd+=("$1")
shift
done
(($# > 0)) && shift
# Run the policy command in the temp dir. It -or the last command- must
# leave a file there named 'policy'.
(cd "$d" && "${cmd[@]}")
done
}
function make_policyDigest {
# Start a trial session, execute the given policy commands, save the
# policyDigest.
tpm2 flushcontext --transient-object
tpm2 flushcontext --loaded-session
tpm2 startauthsession --session "${d}/session.ctx"
exec_policy "$@"
}
function wkname {
# 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 --saved-session
# Load
attrs='decrypt|sign'
loadexternal_args=()
if (($# > 0)); then
make_policyDigest "$@" 1>&2
loadexternal_args+=(-L "${d}/policy")
attrs='adminwithpolicy|decrypt|sign'
fi
# Load the WK
tpm2 flushcontext --transient-object 1>&2
tpm2 flushcontext --loaded-session 1>&2
tpm2 loadexternal -C n \
-Gecc \
-r "${d}/wkpriv.pem" \
"${loadexternal_args[@]}" \
-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"

175
Attestation/tpm-receive.sh Executable file
View file

@ -0,0 +1,175 @@
#!/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.
The POLICY-CMD and arguments are one or more commands that must
leave a policy digest in a file named 'policy' in the current
directory (which will be a temporary directory).
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 {
while (($# > 0)); do
cmd=()
while (($# > 0)) && [[ $1 != ';' ]]; do
cmd+=("$1")
shift
done
(($# > 0)) && shift
# Run the policy command in the temp dir. It -or the last command- must
# leave a file there named 'policy'.
if (v cd "$d" && v "${cmd[@]}" 1> "${d}/out" 2> "${d}/err"); then
cat "${d}/out" >/dev/tty || true
else
stat=$?
echo "ERROR: Failed to run \"${cmd[0]} ...\":"
cat "${d}/out"
cat "${d}/err" 1>&2
exit $stat
fi
done
}
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 createek --key-algorithm rsa \
--ek-context "${d}/ek.ctx" \
--public "${d}/ek.pub"
# Make policyDigest and load WK
attrs='decrypt|sign'
loadexternal_args=()
if (($# > 0)); then
make_policyDigest "$@"
loadexternal_args+=(-L "${d}/policy")
attrs='adminwithpolicy|decrypt|sign'
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
tpm2 flushcontext --transient-object 1>&2
tpm2 flushcontext --loaded-session 1>&2
if v tpm2 loadexternal -C n \
-Gecc \
-r "${d}/wkpriv.pem" \
"${loadexternal_args[@]}" \
-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"