mirror of
https://github.com/stefanberger/swtpm.git
synced 2025-08-22 19:04:35 +00:00

Extend the script that creates a CA that uses a TPM 2 for signing. For this we have to create tokens using the TPM 2 pkcs11 module's tpm2_ptool and can then use the p11tool for creating keys. Add a test case that requires a running tpm2-abrmd and tpm2_ptool. Eventually the test case should (try to) start its own tpm2-abrmd and talk to swtpm directly but the tcti module to do that isn't available as a package, yet. Signed-off-by: Stefan Berger <stefanb@linux.ibm.com>
545 lines
13 KiB
Bash
Executable File
545 lines
13 KiB
Bash
Executable File
#!/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" | sed 's/;/\\;/g'
|
|
}
|
|
|
|
# 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
|
|
|
|
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
|
|
msg=$("${CERTTOOL}" \
|
|
--generate-privkey \
|
|
${SWTPM_ROOTCA_PASSWORD:+--password "${SWTPM_ROOTCA_PASSWORD}"} \
|
|
--outfile "${cakey}" \
|
|
2>&1)
|
|
[ $? -ne 0 ] && {
|
|
logerr "Could not create root-CA key ${cakey}."
|
|
logerr "${msg}"
|
|
return 1
|
|
}
|
|
chmod 640 "${cakey}"
|
|
|
|
echo "cn=swtpm-localca-rootca" > "${template}"
|
|
echo "ca" >> "${template}"
|
|
echo "cert_signing_key" >> "${template}"
|
|
echo "expiration_days = 3650" >> "${template}"
|
|
|
|
msg=$(GNUTLS_PIN="${SWTPM_ROOTCA_PASSWORD}" ${CERTTOOL} \
|
|
--generate-self-signed \
|
|
--template "${template}" \
|
|
--outfile "${cacert}" \
|
|
--load-privkey "${cakey}" \
|
|
2>&1)
|
|
|
|
if [ $? -ne 0 ]; 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
|
|
msg=$(tpm2_ptool addtoken \
|
|
--pid "${pid}" \
|
|
--sopin "${SWTPM_PKCS11_SO_PIN}" \
|
|
--userpin "${userpin}" \
|
|
--label "${label}" 2>&1)
|
|
if [ $? -ne 0 ]; 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
|
|
msg=$(tpm2_ptool config \
|
|
--key tcti \
|
|
--value tabrmd \
|
|
--label "${label}")
|
|
if [ $? -ne 0 ]; 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
|
|
|
|
msg="$(p11tool --login --list-keys "${tokenurl}" 2>&1)"
|
|
if [ $? -eq 0 ]; then
|
|
tpmkeyurl=$(echo "${msg}" | \
|
|
grep ";object=${keylabel}" | \
|
|
sed -n "s/.*URL: //p")
|
|
fi
|
|
if [ -z "${tpmkeyurl}" ]; then
|
|
msg=$(tpm2_ptool addkey \
|
|
"--label=${label}" \
|
|
"--userpin=${userpin}" \
|
|
--algorithm=rsa2048 \
|
|
"--key-label=${keylabel}" \
|
|
--id 1 2>&1)
|
|
if [ $? -ne 0 ]; then
|
|
logerr "Error: Could not create create key under pkcs11 token ${token}"
|
|
logerr "${msg}"
|
|
return 1
|
|
fi
|
|
msg="$(p11tool --login --list-keys "${tokenurl}" 2>&1)"
|
|
if [ $? -ne 0 ]; 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}"
|
|
|
|
msg=$(p11tool --export-pubkey "${tpmkeyurl}" --login --outfile "${tpmpubkey}" 2>&1)
|
|
if [ $? -ne 0 ] || \
|
|
[ ! -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
|
|
msg="$(run_tpmtool --generate-rsa --signing --register ${params})"
|
|
if [ $? -ne 0 ]; 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}"
|
|
msg="$(run_tpmtool --generate-rsa --signing --outfile \"${tpmkey}\" ${params})"
|
|
if [ $? -ne 0 ]; 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}"
|
|
msg=$(run_tpmtool "--pubkey=${tpmkeyurl}" --outfile \"${tpmpubkey}\" ${params})
|
|
if [ $? -ne 0 ] || \
|
|
[ ! -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" > "${template}"
|
|
echo "ca" >> "${template}"
|
|
echo "cert_signing_key" >> "${template}"
|
|
echo "expiration_days = 3650" >> "${template}"
|
|
|
|
msg=$(${CERTTOOL} \
|
|
--generate-certificate \
|
|
--template "${template}" \
|
|
--outfile "${tpmca}" \
|
|
--load-ca-privkey "${cakey}" \
|
|
--load-ca-certificate "${cacert}" \
|
|
--load-privkey "${tpmkeyurl}" \
|
|
--load-pubkey "${tpmpubkey}" \
|
|
2>&1)
|
|
|
|
if [ $? -ne 0 ]; 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}"
|
|
|
|
pushd "${dir}" &>/dev/null
|
|
if [ $? -eq 0 ]; then
|
|
chown "${owner}:${group}" ./*
|
|
popd &>/dev/null
|
|
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
|
|
|
|
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
|
|
msg="$(id -u "${owner}" 2>&1)"
|
|
if [ $? -ne 0 ]; then
|
|
logerr "User ${owner} cannot be used: ${msg}"
|
|
return 1
|
|
fi
|
|
else
|
|
owner="root"
|
|
fi
|
|
if [ -n "${group}" ]; then
|
|
msg="$(id -g "${group}" 2>&1)"
|
|
if [ $? -ne 0 ]; then
|
|
logerr "Group ${group} cannot be used: ${msg}"
|
|
return 1
|
|
fi
|
|
else
|
|
group="root"
|
|
fi
|
|
fi
|
|
|
|
mkdir -p "${dir}"
|
|
if [ $? -ne 0 ]; then
|
|
logerr "Could not create directory ${dir}."
|
|
return 1
|
|
fi
|
|
|
|
create_localca_cert "${flags}" "${dir}" "${outfile}" "${owner}" "${pid}"
|
|
return $?
|
|
} #main
|
|
|
|
main "$@"
|
|
exit $? |