#!/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