Skip to content

Instantly share code, notes, and snippets.

@op-ct
Last active June 7, 2024 23:10
Show Gist options
  • Save op-ct/e202fc911de22c018effdb3371e8335f to your computer and use it in GitHub Desktop.
Save op-ct/e202fc911de22c018effdb3371e8335f to your computer and use it in GitHub Desktop.
Creating signed TPM 2.0 endorsement key x.509v3 certificates with openssl

This script uses openssl to mock a TPM 2.0 manufacturer's Endorsement Key credentials enough to use in acceptance tests starting with fresh EKs from a newly-instantiated TPM 2.0 simulator.

Usage

bash -e  tpm2_ekcert_sign.sh  [public.ek.portion.cer]

Input

  • the two provided openssl .config in this gist, placed under the local directory tree openssl/configs/
  • a DER-encoded TPM 2.0 RSA public EK (public.ek.portion.cer)
    • To get this, dump the TPM's EK into TPM2B_PUBLIC format with:

      tpm2_getpubek -V -V -g rsa -f public.ek.portion -H ${RESERVED_HANDLE_EK:-0x81010001}`
    • public.ek.portion.cer can be extracted using a script like https://github.com/tpm2-software/tpm2-tools/blob/3.X/scripts/utils/tcgRSApub2PemDer.sh:

      tcgRSApub2PemDer.sh public.ek.portion
      • WARNING: the EK dumped from a fresh tpm2 simular will probably use a different offset than tcgRSApub2PemDer.sh's hard-coded "102" (my EK dumps were consistently offset by "60").
    • To determine the offset for a particular EK file:

      • Look in the tpm2_getpubek -V output for the second-to-last offset location (before the final BIG jump).

Output

  • a signed X.509v3 certificate with (some of) the unusual extension and encoding requirements from the TCG EKC profile specifications.
  • lots on human-readable diagnostic dumps of every step of the process under a summaries/ directory

Credits

[req]
encrypt_key = yes
prompt = no
utf8 = yes
string_mask = utf8only
distinguished_name = dn
x509_extensions = v3_ca
[v3_ca]
subjectKeyIdentifier = hash
basicConstraints = CA:TRUE
keyUsage = critical, keyCertSign, cRLSign
[dn]
O = TPM Manufacturer
OU = TPM Manufacturer Root CA
CN = TPM Manufacturer Root CA
openssl_conf = openssl_init
[openssl_init]
oid_section = tpm_oids
[tpm_oids]
TPMManufacturer=tcg_at_tpmManufacturer,2.23.133.2.1
TPMModel=tcg-at-tpmModel,2.23.133.2.2
TPMVersion=tcg-at-tpmVersion,2.23.133.2.3
TPMSpecification=tcg-at-tpmSpecification,2.23.133.2.16
[req]
#prompt = no
default_bits = 2048
encrypt_key = yes
utf8 = yes
string_mask = utf8only
certificatePolicies= 2.23.133.2.1
distinguished_name = req_distinguished_name
req_extensions = v3_req
[req_distinguished_name]
[v3_req]
subjectAltName=critical,ASN1:SEQUENCE:dir_seq
basicConstraints=critical,CA:FALSE
keyUsage = keyEncipherment
[dir_seq]
seq = EXPLICIT:4,SEQUENCE:dir_seq_seq
[dir_seq_seq]
set = SET:dir_set_1
[dir_set_1]
seq.1 = SEQUENCE:dir_seq_1
seq.2 = SEQUENCE:dir_seq_2
seq.3 = SEQUENCE:dir_seq_3
[dir_seq_1]
oid=OID:2.23.133.2.1
str=UTF8:"id:%TPM_MANUFACTERER%"
[dir_seq_2]
oid=OID:2.23.133.2.2
str=UTF8:"id:%TPM_MODEL%"
[dir_seq_3]
oid=OID:2.23.133.2.3
str=UTF8:"id:%TPM_FIRMWARE_VERSION%"
[dir_sect]
O=foo
[foo___sec]
foo.1 = ASN1:OID:"TPMModel"
foo.2 = ASN1:INTEGER:1
#!/bin/bash -e
#
# This script mocks a TPM 2.0 manufacturer's Endorsement Key credentials enough
# to run our EKC acceptance tests with a freshly-instantiated TPM 2.0 simulator.
#
# It takes a DER-encoded TPM 2.0 RSA public EK as input, and generates a signed
# X.509v3 certificate with (some of) the unusual extension and encoding
# requirements from the TCG EKC profile specifications.
#
# Usage:
#
# bash -e code_sign.sh [public.ek.portion.cer]
#
#
# The script will:
#
# - [as input] take a DER-encoded RSA public key (with a TPM 2.0 public EK)
# - create a self-signed Root CA certificate
# - generate a X.509v3 CSR from the TPM's public EK
# - [as output] generate and sign an X.509v3 EK Credential certificate
#
# The generated CSR and X.509 certificate contain structures specific to TPM
# 2.0 EK Credentials
#
# - This includes SOME of the X509v3 extensions expected from a PC Client
# Platform TPM manufacturer.
# - This does NOT icomprehensively include all of the guidance documented in
# "TCG EK Credential Profile for TPM Family 2.0"
# https://trustedcomputinggroup.org/tcg-ek-credential-profile-tpm-family-2-0/
#
# Notes:
#
# - Portions of this script were adapted from the impressively polished code
# at https://gist.github.com/vszakats/7ef9e86506f5add961bae0412ecbe696
#
# /usr/local/opt/openssl/bin/openssl list-cipher-algorithms
# /usr/local/opt/[email protected]/bin/openssl list -cipher-algorithms
# yum install --enablerepo=epel -y openssl pwgen
# 2.1 Endorsement Key
#
# Assumed to be a DER-encoded 2048-bit RSA public key (sec 2.1, TCG-EK-CP)
readonly cwd="${PWD}"
readonly base='tpm2_'
readonly root_ca="${base}CA"
readonly ekc="${base}ekc"
readonly pubkey_to_certify=${1:-public.ek.portion.pem}
readonly manufacturer_ca='tpm.manufacturer.test'
readonly verbose_files=${VERBOSE_FILES:-yes}
readonly verbose_files_dir="${VERBOSE_FILES_DIR:-summaries}/"
readonly working_dir="${WORKING_DIR:-_${base}_working_dir}/"
# ~10 years
readonly ca_cert_validity_days=3652
readonly tpm_cert_validity_days=$ca_cert_validity_days
# FIPS-compatible
readonly keylength=2048
# Redirect stdout securely to non-world-readable files
privout() {
cd "${working_dir}"; o="$1"; rm -f "$o"; touch "$o"; chmod 0600 "$o"; shift
(
"$@"
) >> "$o"
cd - &> /dev/null
}
# Redirect all output securely to non-world-readable files
privall() {
cd "${working_dir}"; ="$1"; rm -f "$o"; touch "$o"; chmod 0600 "$o"; shift
(
"$@"
) >> "$o" 2>&1
cd - &> /dev/null
}
echo "OpenSSL $(openssl version 2> /dev/null | grep -Eo -m 1 ' [0-9]+.[0-9]+.[0-9a-z]+')"
[ ! -z "${working_dir}" ] && mkdir -p "${working_dir}"
[ "${verbose_files}" == 'yes' ] && mkdir -p "${verbose_files_dir}"
echo '! Creating self-signed Root CA...'
# https://pki-tutorial.readthedocs.io/en/latest/simple/root-ca.conf.html
# https://en.wikipedia.org/wiki/X.509
readonly root_ca_csr_config="openssl/configs/${root_ca}.csr.config"
readonly root_ca_private_pem="${root_ca}_private.pem"
readonly root_ca_private_der="${root_ca}_private.der"
readonly root_ca_cert="${root_ca}.crt"
readonly root_ca_pass="$(pwgen -s 32 1)"
privout "${root_ca}.password" echo "${root_ca_pass}"
# create CA Root
# NOTE: For right now, we're only making a self-signed root CA for our tests
# PKCS #8 private key, encrypted, PEM format.
privout "${root_ca_private_pem}" \
openssl genpkey -algorithm RSA -aes-256-cbc \
-pkeyopt rsa_keygen_bits:${keylength} \
-pass "pass:${root_ca_pass}"
privout "${root_ca}_private.pem.asn1.txt" \
openssl asn1parse -i -in "${root_ca_private_pem}"
# TODO: add `-strictpem` option to all `openssl asn1parse` commands
# where a PEM file is expected @ OpenSSL 1.1.0. Otherwise
# openssl would also process the BEGIN/END separators, leading
# to occasional processing errors.
# https://github.com/openssl/openssl/issues/1381#issuecomment-237095795
# PKCS #8 private key, encrypted, DER format.
privout "${root_ca_private_der}" \
openssl pkcs8 -topk8 -v2 aes-256-cbc \
-in "${root_ca_private_pem}" -passin "pass:${root_ca_pass}" \
-outform DER -passout "pass:${root_ca_pass}"
privout "${root_ca}_private.der.asn1.txt" \
openssl asn1parse -i -inform DER -in "${root_ca_private_der}"
# Sign CA cert
# .crt is certificate (public key + subject + signature)
openssl req -batch -verbose -new -sha256 -x509 \
-days "${ca_cert_validity_days}" \
-key "${working_dir}${root_ca_private_pem}" -passin "pass:${root_ca_pass}" \
-out "${working_dir}${root_ca_cert}" -config "${root_ca_csr_config}"
openssl x509 -in "${working_dir}/${root_ca_cert}" \
-text -noout -nameopt utf8 -sha256 -fingerprint \
> "${working_dir}/${root_ca_cert}.x509.txt"
openssl asn1parse -i -in "${working_dir}/${root_ca_cert}" \
> "${working_dir}/${root_ca_cert}.asn1.txt"
# Generate a signed X.509 certificate from the TPM's Endorsment key.
#
# A TPM's private Endorsement Key is inaccessible by design, so only the public
# key is available to create a CSR.
#
# Conceptually, that seems straightforward: an X.509 CSR is essentially a public
# key with additional attributes. However:
#
# - A private key is still required to sign the CSR.
# - Most SSL tools (including `openssl req` are hard-coded to simply generate
# the public key for the CSR directly from that private key.
#
# This involves several workarounds:
#
#
# most SSL tools ―including `openssl
# req`―require a *private* key to sign a CSR, which then.
#
# So: we create a spurious private key to give to `openssl req` so it will
# create the CSR, then tell `openssl x509` to use our TPM's public key instead
# with the option `-force_pubkey FILE`.
# Even though the CSR will be based on the public key
#
#
echo '! Creating TPM Endorsement Key Certificate...'
readonly ekc_csr_config="openssl/configs/${ekc}.csr.config"
readonly pubkey_basename=${pubkey_to_certify%.*}
openssl rsa -pubin \
-inform DER -in "${pubkey_to_certify}" -text -noout \
> "${working_dir}${pubkey_to_certify}.rsa.txt"
openssl asn1parse -i -inform DER -in "${pubkey_to_certify}" \
> "${working_dir}${pubkey_to_certify}.asn1.txt"
# This creates a private key file that must exist for `openssl req` to run. The public create
readonly csr_priv_key_pass="$(pwgen -s 32 1)"
privout "${ekc}_unused.password" echo "${csr_priv_key_pass}"
privout "${ekc}_unused.private.pem" \
openssl genpkey -algorithm RSA -aes-256-cbc \
-pkeyopt rsa_keygen_bits:${keylength} \
-pass "pass:${csr_priv_key_pass}" 2> /dev/null
openssl req -batch -verbose -new -sha256 \
-subj '/' \
-passout "pass:${csr_priv_key_pass}" -keyout "${working_dir}${ekc}_unused.private.pem" \
-out "${working_dir}${ekc}.csr" -config "${ekc_csr_config}"
if [ "${verbose_files}" == 'yes' ]; then
openssl asn1parse -i -in "${ekc}.csr" > "${working_dir}${ekc}.csr.asn1.txt"
fi
output_pem_crt="${ekc}.pem.crt"
output_der_crt="${ekc}.der.crt"
# Sign a PEM-encoded certificate
openssl x509 -in "${ekc}.csr" -req \
-extfile "${ekc_csr_config}" \
-force_pubkey ${pubkey_to_certify} -keyform DER \
-CA "${working_dir}${root_ca_cert}" -CAkey "${working_dir}${root_ca_private_pem}" \
-CAcreateserial \
-passin "pass:${root_ca_pass}" \
-out "${output_pem_crt}" \
-extensions v3_req \
-days ${tpm_cert_validity_days} -sha256
# Sign a DER-encoded certificate
openssl x509 -in "${ekc}.csr" -req \
-extfile "${ekc_csr_config}" \
-force_pubkey ${pubkey_to_certify} -keyform DER \
-CA "${working_dir}${root_ca_cert}" -CAkey "${working_dir}${root_ca_private_pem}" \
-CAcreateserial \
-passin "pass:${root_ca_pass}" \
-outform der \
-out "${output_der_crt}" \
-extensions v3_req \
-days ${tpm_cert_validity_days} -sha256
# report
if [ "${verbose_files}" == 'yes' ]; then
openssl asn1parse -i -in "${output_pem_crt}" \
> "${working_dir}${ekc}.crt.asn1.txt"
openssl x509 -in "${output_pem_crt}" -text -noout -nameopt utf8 -sha256 \
-fingerprint > "${working_dir}${ekc}.crt.x509.txt"
fi
@osresearch
Copy link

Thanks for the documentation - I've used this technique for tpm2-attest ek-sign to generate signed certs for systems that don't have it. osresearch/safeboot#85

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment