| #!/usr/bin/env bash |
| |
| FLAG_OVERWRITE=1 |
| FLAG_REGISTER_KEY=2 |
| FLAG_TPMTOOL_SUPPORTS_SRK_WELL_KNOWN=4 |
| FLAG_SRK_WELL_KNOWN=8 |
| FLAG_TPM2=16 |
| |
| TSS_TCSD_HOSTNAME_DEFAULT=localhost |
| TSS_TCSD_PORT_DEFAULT=30003 |
| |
| logit() |
| { |
| if [ -z "$LOGFILE" ]; then |
| echo "$@" >&1 |
| else |
| echo "$@" >> "$LOGFILE" |
| fi |
| } |
| |
| logerr() |
| { |
| if [ -z "$LOGFILE" ]; then |
| echo "Error: $*" >&2 |
| else |
| echo "Error: $*" >> "$LOGFILE" |
| fi |
| } |
| |
| # Get the size of a file in bytes |
| # |
| # @1: filename |
| function get_filesize() |
| { |
| if [[ "$(uname -s)" =~ (Linux|CYGWIN_NT-) ]]; then |
| stat -c%s "$1" |
| else |
| # OpenBSD |
| stat -f%z "$1" |
| fi |
| } |
| |
| # Create a config value by escaping the proper characters |
| # |
| # @param 1: The string to escape |
| function escape_pkcs11_url() |
| { |
| echo "${1//;/\\;}" |
| } |
| |
| # Use expect for automating the interaction with the tpmtool |
| # |
| # @param 1...: parameters to pass to tpmtool command line |
| # |
| # TPM_SRK_PASSWORD and TPM_KEY_PASSWORD global variables are used |
| # for the SRK and key passwords respectively. |
| run_tpmtool() { |
| local prg out rc |
| |
| if [ -z "$(type -P tpmtool)" ]; then |
| echo "Could not find tpmtool in PATH (${PATH})." |
| return 1 |
| fi |
| |
| # shellcheck disable=SC2124,SC2027 |
| prg="spawn tpmtool "$@" |
| expect { |
| \"Enter SRK password:\" { |
| send \"${TPM_SRK_PASSWORD}\n\" |
| exp_continue |
| } |
| \"Enter key password:\" { |
| send \"${TPM_KEY_PASSWORD}\n\" |
| exp_continue |
| } |
| \"tpmkey:\" { |
| send_user \"\n\" |
| } |
| eof { |
| exit |
| } |
| } |
| catch wait result |
| exit [lindex \$result 3] |
| " |
| out=$(expect -c "${prg}") |
| rc=$? |
| echo "${out}" |
| return $rc |
| } #run_tpmtool |
| |
| create_localca_cert() { |
| local flags=$1 |
| local dir="$2" |
| local outfile="$3" |
| local owner="$4" |
| local pid="$5" # TPM2 parameter |
| |
| local cakey=${dir}/swtpm-localca-rootca-privkey.pem |
| local cacert=${dir}/swtpm-localca-rootca-cert.pem |
| local tpmkey=${dir}/swtpm-localca-tpmca-privkey.pem |
| local tpmpubkey=${dir}/swtpm-localca-tpmca-pubkey.pem |
| local tpmca=${dir}/swtpm-localca-tpmca-cert.pem |
| local template=${dir}/template |
| local tpmkeyurl |
| local msg output |
| |
| if ! [ -r "${cakey}" ] || ! [ -r "${cacert}" ]; then |
| if ! msg=$("${CERTTOOL}" \ |
| --generate-privkey \ |
| ${SWTPM_ROOTCA_PASSWORD:+--password "${SWTPM_ROOTCA_PASSWORD}"} \ |
| --outfile "${cakey}" \ |
| 2>&1); |
| then |
| logerr "Could not create root-CA key ${cakey}." |
| logerr "${msg}" |
| return 1 |
| fi |
| chmod 640 "${cakey}" |
| |
| { |
| echo "cn=swtpm-localca-rootca" |
| echo "ca" |
| echo "cert_signing_key" |
| echo "expiration_days = 3650" |
| } > "${template}" |
| |
| if ! msg=$(GNUTLS_PIN="${SWTPM_ROOTCA_PASSWORD}" ${CERTTOOL} \ |
| --generate-self-signed \ |
| --template "${template}" \ |
| --outfile "${cacert}" \ |
| --load-privkey "${cakey}" \ |
| 2>&1); |
| then |
| logerr "Could not create root CA." |
| logerr "${msg}" |
| rm -f "${cakey}" "${template}" |
| return 1 |
| fi |
| else |
| logit "Reusing existing root CA" |
| fi |
| |
| rm -f "${tpmkey}" "${tpmpubkey}" "${tpmca}" |
| |
| if [ $((flags & FLAG_TPM2)) -ne 0 ]; then |
| local tokenurl tpmkeyurl |
| local token="swtpm-tpmca-${pid}" |
| local label="${token}" # must be same |
| local keylabel="swtpm-tpmca-key" |
| local userpin="${SWTPM_PKCS11_PIN:-swtpm-tpmca}" |
| |
| tokenurl=$(p11tool --list-tokens 2>&1 | \ |
| grep -E ";token=${token}\$" | \ |
| sed -n "s/.*URL: //p") |
| if [ -z "${tokenurl}" ]; then |
| if [ -z "${SWTPM_PKCS11_SO_PIN}" ]; then |
| logerr "The env. variable SWTPM_PKCS11_SO_PIN must be set to create token ${label}." |
| return 1 |
| fi |
| if ! msg=$(tpm2_ptool addtoken \ |
| --pid "${pid}" \ |
| --sopin "${SWTPM_PKCS11_SO_PIN}" \ |
| --userpin "${userpin}" \ |
| --label "${label}" 2>&1); |
| then |
| logerr "Error: Could not create pkcs11 token" |
| logerr "${msg}" |
| return 1 |
| fi |
| tokenurl=$(p11tool --list-tokens 2>&1 | \ |
| grep -E ";token=${token}\$" | \ |
| sed -n "s/.*URL: //p") |
| if [ -z "${tokenurl}" ]; then |
| logerr "Error: Could not get token URL for token '${token}'" |
| logerr "${msg}" |
| return 1 |
| fi |
| if ! msg=$(tpm2_ptool config \ |
| --key tcti \ |
| --value tabrmd \ |
| --label "${label}"); |
| then |
| logerr "Error: Could not set config value for tcti key" |
| logerr "${msg}" |
| return 1 |
| fi |
| fi |
| |
| export GNUTLS_PIN="${userpin}" |
| # GNUTLS_SO_PIN not needed at this point |
| |
| if msg="$(p11tool --login --list-keys "${tokenurl}" 2>&1)"; then |
| tpmkeyurl=$(echo "${msg}" | \ |
| grep ";object=${keylabel}" | \ |
| sed -n "s/.*URL: //p") |
| fi |
| if [ -z "${tpmkeyurl}" ]; then |
| if ! msg=$(tpm2_ptool addkey \ |
| "--label=${label}" \ |
| "--userpin=${userpin}" \ |
| --algorithm=rsa2048 \ |
| "--key-label=${keylabel}" \ |
| --id 1 2>&1); |
| then |
| logerr "Error: Could not create create key under pkcs11 token ${token}" |
| logerr "${msg}" |
| return 1 |
| fi |
| if ! msg="$(p11tool --login --list-keys "${tokenurl}" 2>&1)"; then |
| logerr "Error: Could not get TPM key URL for ${tokenurl}" |
| logerr "${msg}" |
| return 1 |
| fi |
| tpmkeyurl=$(echo "${msg}" | \ |
| grep ";object=${keylabel}" | \ |
| sed -n "s/.*URL: //p") |
| if [ -z "${tpmkeyurl}" ]; then |
| logerr "Error: Could not get TPM key URL for ${tokenurl}" |
| logerr "${msg}" |
| return 1 |
| fi |
| fi |
| rm -f "${tpmpubkey}" |
| |
| if ! msg=$(p11tool --export-pubkey "${tpmkeyurl}" --login --outfile "${tpmpubkey}" 2>&1) || \ |
| [ ! -r "${tpmpubkey}" ] || [ "$(get_filesize "${tpmpubkey}")" -eq 0 ]; then |
| logerr "Error: Could not get TPM public key" |
| logerr "${msg}" |
| rm -f "${tpmkey}" "${tpmpubkey}" |
| return 1 |
| fi |
| else |
| local params="" |
| |
| if [ $((flags & FLAG_SRK_WELL_KNOWN)) -ne 0 ]; then |
| unset GNUTLS_PIN |
| params="--srk-well-known" |
| else |
| export GNUTLS_PIN="${TPM_SRK_PASSWORD}" |
| fi |
| |
| if [ $((flags & FLAG_REGISTER_KEY)) -ne 0 ]; then |
| if ! msg="$(run_tpmtool --generate-rsa --signing --register ${params})"; then |
| logerr "Could not generate registered signing key with tpmtool" |
| logerr "${msg}" |
| return 1 |
| fi |
| tpmkeyurl=$(echo "${msg}" | sed -n 's/\(tpmkey:uuid=[^;]*\);.*/\1/p') |
| if [ -z "${tpmkeyurl}" ]; then |
| logerr "Could not parse tpmkey URL" |
| logerr "${msg}" |
| return 1 |
| fi |
| else |
| rm -f "${tpmkey}" |
| # shellcheck disable=SC2086 |
| if ! msg="$(run_tpmtool --generate-rsa --signing --outfile \"${tpmkey}\" ${params})"; then |
| logerr "Could not create signing key with tpmtool" |
| logerr "${msg}" |
| rm -f "${tpmkey}" |
| return 1 |
| fi |
| if [ ! -r "${tpmkey}" ] || [ "$(get_filesize "${tpmkey}")" -eq 0 ]; then |
| logerr "The TPM key file ${tpmkey} was not written properly" |
| logerr "${msg}" |
| rm -f "${tpmkey}" |
| return 1 |
| fi |
| chmod 640 "${tpmkey}" |
| tpmkeyurl="tpmkey:file=${tpmkey}" |
| fi |
| |
| rm -f "${tpmpubkey}" |
| # shellcheck disable=SC2086 |
| if ! msg=$(run_tpmtool "--pubkey=${tpmkeyurl}" --outfile \"${tpmpubkey}\" ${params}) || \ |
| [ ! -r "${tpmpubkey}" ] || [ "$(get_filesize "${tpmpubkey}")" -eq 0 ]; then |
| logerr "Error: Could not get TPM public key" |
| logerr "${msg}" |
| rm -f "${tpmkey}" "${tpmpubkey}" |
| return 1 |
| fi |
| fi |
| |
| { |
| echo "cn=swtpm-localca" |
| echo "ca" |
| echo "cert_signing_key" |
| echo "expiration_days = 3650" |
| } > "${template}" |
| |
| if ! msg=$(${CERTTOOL} \ |
| --generate-certificate \ |
| --template "${template}" \ |
| --outfile "${tpmca}" \ |
| --load-ca-privkey "${cakey}" \ |
| --load-ca-certificate "${cacert}" \ |
| --load-privkey "${tpmkeyurl}" \ |
| --load-pubkey "${tpmpubkey}" \ |
| 2>&1); |
| then |
| logerr "Could not create TPM CA" |
| logerr "${msg}" |
| rm -f "${template}" |
| return 1 |
| fi |
| |
| output="statedir = ${dir} |
| signingkey = $(escape_pkcs11_url "${tpmkeyurl}") |
| issuercert = ${tpmca} |
| certserial = ${dir}/certserial" |
| |
| if [ $((flags & FLAG_TPM2)) -eq 0 ]; then |
| output+="$(echo -e "\nTSS_TCSD_HOSTNAME = ${TSS_TCSD_HOSTNAME}")" |
| output+="$(echo -e "\nTSS_TCSD_PORT = ${TSS_TCSD_PORT}")" |
| else |
| output+="$(echo -e "\nSWTPM_PKCS11_PIN = ${SWTPM_PKCS11_PIN}")" |
| # output+="$(echo -e "\nSWTPM_PKCS11_SO_PIN = ${SWTPM_PKCS11_SO_PIN}")" |
| fi |
| if [ -n "${TPM_KEY_PASSWORD}" ]; then |
| output+="$(echo -e "\nsigningkey_password = ${TPM_KEY_PASSWORD}")" |
| fi |
| if [ -n "${TPM_SRK_PASSWORD}" ]; then |
| output+="$(echo -e "\nparentkey_password = ${TPM_SRK_PASSWORD}")" |
| fi |
| |
| if [ -n "${outfile}" ]; then |
| echo "${output}" > "${outfile}" |
| chmod 640 "${outfile}" |
| fi |
| echo "${output}" |
| |
| if [ "$(id -u)" -eq 0 ]; then |
| chown "${owner}:${group}" "${dir}" |
| |
| if pushd "${dir}" &>/dev/null; then |
| chown "${owner}:${group}" ./* |
| popd &>/dev/null || return 1 |
| fi |
| |
| if [ -n "${outfile}" ]; then |
| chown "${owner}:${group}" "${outfile}" |
| fi |
| fi |
| |
| rm -f "${template}" |
| |
| return 0 |
| } #create_localca_cert |
| |
| usage() { |
| local flags=$2 |
| |
| local tpmtool_note=" use 'well known' password if not |
| given" |
| [ $((flags & FLAG_TPMTOOL_SUPPORTS_SRK_WELL_KNOWN)) -eq 0 ] && \ |
| tpmtool_note=" |
| Note: the well known password of 20 zero bytes is not |
| supported by tpmtool" |
| |
| cat << _EOF_ |
| Create a TPM-based CA for signing EK and platform certificates. |
| |
| Usage: $(basename "$1") [options] |
| |
| THIS SCRIPT IS EXPERIMENTAL |
| |
| The following options are supported: |
| |
| --dir directory Directory where to write the CA files into; must not exist |
| unless --overwrite is passed |
| --overwrite Overwrite any data in an existing directory; tries to |
| reuse a root CA if one is found there |
| --register Create a registered TPM 1.2 key rather than a file that |
| contains the key; this option has no effect if --tpm2 is |
| used |
| --key-password s Password for the newly created TPM key; required if |
| --register is not passed |
| Note: use the same as the --srk-password (bug in certtool) |
| --srk-password s Password for the TPM's SRK;${tpmtool_note} |
| --outfile file File to write the configuration to; if not passed it will be |
| written to stdout only |
| --owner owner The owner of the directory and the files; only set if this |
| script is run as root; recommended to be 'tss' |
| --group group The group owning the directory and the files; |
| recommended to be 'tss' |
| --tss-tcsd-hostname hostname |
| The name of the host where tcsd (TrouSerS daemon) is running |
| on; default is '${TSS_TCSD_HOSTNAME_DEFAULT}' |
| --tss-tcsd-port p The TCP port on which tcsd is listening for connections; |
| default is ${TSS_TCSD_PORT_DEFAULT} |
| --tpm2 Setup a CA that uses a TPM 2.0 |
| --pid <pid> Pimary object Id used by tpm2_ptool; only valid if --tpm2 |
| is used |
| --help, -h, -? Display this help screen and exit |
| |
| |
| The following environment variables are supported: |
| |
| SWTPM_ROOTCA_PASSWORD The root CA's private key password |
| |
| _EOF_ |
| } #usage |
| |
| # Check whether tpmtool supports --srk-well-known |
| tpmtool_supports_srk_well_known() |
| { |
| local tmp |
| |
| [ -z "$(type -P tpmtool)" ] && return 1 |
| |
| tmp=$(tpmtool --help | grep "srk-well-known") |
| [ -z "${tmp}" ] && return 1 |
| return 0 |
| } |
| |
| main() { |
| local flags=0 |
| local dir outfile owner group msg pid |
| |
| if tpmtool_supports_srk_well_known; then |
| flags=$((flags | FLAG_TPMTOOL_SUPPORTS_SRK_WELL_KNOWN | FLAG_SRK_WELL_KNOWN)) |
| fi |
| |
| CERTTOOL=certtool |
| export TSS_TCSD_HOSTNAME=${TSS_TCSD_HOSTNAME_DEFAULT} |
| export TSS_TCSD_PORT=${TSS_TCSD_PORT_DEFAULT} |
| |
| while [ $# -ne 0 ]; do |
| case "$1" in |
| --dir) |
| shift |
| dir="$1" |
| ;; |
| --overwrite) |
| flags=$((flags | FLAG_OVERWRITE)) |
| ;; |
| --register) |
| flags=$((flags | FLAG_REGISTER_KEY)) |
| ;; |
| --srk-password) |
| shift |
| TPM_SRK_PASSWORD="$1" |
| flags=$((flags & ~FLAG_SRK_WELL_KNOWN)) |
| ;; |
| --key-password) |
| shift |
| TPM_KEY_PASSWORD="$1" |
| ;; |
| --outfile) |
| shift |
| outfile="$1" |
| ;; |
| --owner) |
| shift |
| owner="$1" |
| ;; |
| --group) |
| shift |
| group="$1" |
| ;; |
| --tss-tcsd-hostname) |
| shift |
| TSS_TCSD_HOSTNAME="$1" |
| ;; |
| --tss-tcsd-port) |
| shift |
| TSS_TCSD_PORT="$1" |
| ;; |
| --tpm2) |
| flags=$((flags | FLAG_TPM2)) |
| ;; |
| --pid) |
| shift |
| pid="$1" |
| ;; |
| --help|-h|-?) |
| usage "$0" "${flags}" |
| exit 0 |
| ;; |
| *) |
| logerr "Unsupported option $1" |
| exit 1 |
| ;; |
| esac |
| shift |
| done |
| if [ -z "${dir}" ]; then |
| logerr "Missing --dir option." |
| return 1 |
| fi |
| # strip trailing '/' from dir |
| dir="$(echo "${dir}" | sed -n 's|[/]*$||p')" |
| |
| if [ -d "${dir}" ] && [ $((flags & FLAG_OVERWRITE)) -eq 0 ]; then |
| logerr "Refusing to overwrite existing directory ${dir}." |
| return 1 |
| fi |
| |
| if [ -z "${TPM_SRK_PASSWORD}" ] && [ $((flags & FLAG_TPM2)) -eq 0 ] && |
| [ $((flags & FLAG_TPMTOOL_SUPPORTS_SRK_WELL_KNOWN)) -eq 0 ]; then |
| logerr "SRK password must be provided" |
| return 1 |
| fi |
| |
| if [ -z "${TPM_KEY_PASSWORD}" ] && \ |
| [ $((flags & FLAG_REGISTER_KEY)) -eq 0 ] && \ |
| [ $((flags & FLAG_TPM2)) -eq 0 ]; then |
| logerr "Key password is required" |
| return 1 |
| fi |
| |
| if [ $((flags & FLAG_TPM2)) -ne 0 ] && [ -z "${pid}" ]; then |
| logerr "--pid is required for TPM 2" |
| return 1 |
| fi |
| |
| if [ "$(id -u)" -eq 0 ]; then |
| if [ -n "${owner}" ]; then |
| if ! msg="$(id -u "${owner}" 2>&1)"; then |
| logerr "User ${owner} cannot be used: ${msg}" |
| return 1 |
| fi |
| else |
| owner="root" |
| fi |
| if [ -n "${group}" ]; then |
| if ! msg="$(id -g "${group}" 2>&1)"; then |
| logerr "Group ${group} cannot be used: ${msg}" |
| return 1 |
| fi |
| else |
| group="root" |
| fi |
| fi |
| |
| if ! mkdir -p "${dir}"; then |
| logerr "Could not create directory ${dir}." |
| return 1 |
| fi |
| |
| create_localca_cert "${flags}" "${dir}" "${outfile}" "${owner}" "${pid}" |
| return $? |
| } #main |
| |
| main "$@" |
| exit $? |