From 79bbeeb75967eba863200e819bae427c5db7da66 Mon Sep 17 00:00:00 2001 From: Nicolas Williams Date: Fri, 9 Jul 2021 15:17:09 -0500 Subject: [PATCH] Update enrollment docs, send/recv scripts --- Enrollment/README.md | 111 +++++++++---- Enrollment/send-to-tpm | 331 ++++++++++++++++++++++++++++++++++++++ Enrollment/send-to-tpm.sh | 165 ------------------- Enrollment/tpm-receive | 284 ++++++++++++++++++++++++++++++++ Enrollment/tpm-receive.sh | 194 ---------------------- 5 files changed, 697 insertions(+), 388 deletions(-) create mode 100755 Enrollment/send-to-tpm delete mode 100755 Enrollment/send-to-tpm.sh create mode 100755 Enrollment/tpm-receive delete mode 100755 Enrollment/tpm-receive.sh diff --git a/Enrollment/README.md b/Enrollment/README.md index 0367c68..c1212fd 100644 --- a/Enrollment/README.md +++ b/Enrollment/README.md @@ -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. + diff --git a/Enrollment/send-to-tpm b/Enrollment/send-to-tpm new file mode 100755 index 0000000..cb0dbc8 --- /dev/null +++ b/Enrollment/send-to-tpm @@ -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 <&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 diff --git a/Enrollment/send-to-tpm.sh b/Enrollment/send-to-tpm.sh deleted file mode 100755 index 372e81a..0000000 --- a/Enrollment/send-to-tpm.sh +++ /dev/null @@ -1,165 +0,0 @@ -#!/bin/bash - -PROG=${0##*/} - -set -euo pipefail - -function usage { - ((${1:-1} > 0)) && exec 1>&2 - cat <= 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" <&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" diff --git a/Enrollment/tpm-receive b/Enrollment/tpm-receive new file mode 100755 index 0000000..db118f0 --- /dev/null +++ b/Enrollment/tpm-receive @@ -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 <= 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 diff --git a/Enrollment/tpm-receive.sh b/Enrollment/tpm-receive.sh deleted file mode 100755 index 32593f5..0000000 --- a/Enrollment/tpm-receive.sh +++ /dev/null @@ -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 <= 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" <&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"