swtpm/samples/swtpm-create-tpmca
Stefan Berger 895df2c36e tests+samples: Check for --help support to determine stat/sed tools flavor
Using the kernel name was not a good choice for determining which flavor
(BSD vs. GNU) of stat, sed, and install was being used since one could
install either one of them at least on MacOS. Instead, check whether
--help is supported by these tools.

Signed-off-by: Stefan Berger <stefanb@linux.ibm.com>
2026-05-19 19:14:37 -04:00

421 lines
9.6 KiB
Bash
Executable File

#!/usr/bin/env bash
FLAG_OVERWRITE=1
FLAG_TPM2=2
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 stat --help &>/dev/null; then
stat -c%s "$1"
else
# OpenBSD
stat -f%z "$1"
fi
}
# Get the OpenSSL provider path for the given module searching a few well-known
# paths.
function get_provider_path()
{
local module="$1"
local p
for p in \
"/usr/lib64/ossl-modules" \
"/usr/lib/ossl-modules" \
"/usr/lib/$(uname -m)-linux-gnu/ossl-modules";
do
if [ -r "${p}/${module}" ]; then
echo "${p}"
return 0
fi
done
logerr "Could not find OpenSSL provider path for ${module} module."
return 1
}
# Create a config value by escaping the proper characters
#
# @param 1: The string to escape
function escape_pkcs11_url()
{
echo "${1//;/\\;}"
}
create_localca_cert() {
local flags=$1
local dir="$2"
local outfile="$3"
local owner="$4"
local pid="$5" # TPM2 parameter
local algorithm="$6" # RSA or EC-key signing
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 pinfile=${dir}/pin
local passfile=${dir}/password
local tpmkeyurl
local msg output providerpath
export SWTPM_ROOTCA_PASSWORD
if ! [ -r "${cakey}" ] || ! [ -r "${cacert}" ]; then
# shellcheck disable=2086
if ! msg=$(openssl \
req \
-x509 \
-keyout "${cakey}" \
-newkey rsa:3072 \
${SWTPM_ROOTCA_PASSWORD:+-passout "env:SWTPM_ROOTCA_PASSWORD"} \
${SWTPM_ROOTCA_PASSWORD:--noenc} \
-out "${cacert}" \
-days 36500 \
-sha256 \
-subj "/CN=swtpm-localca-rootca" \
-addext "basicConstraints=critical,CA:TRUE" \
-addext "keyUsage=critical,keyCertSign" \
2>&1);
then
logerr "Could not create root CA."
logerr "${msg}"
rm -f "${cakey}" "${passfile}"
return 1
fi
chmod 640 "${cakey}"
rm -f "${passfile}"
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=${algorithm}" \
"--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
logerr "TPM1.2 is not supported anymore."
return 1
fi
if ! providerpath=$(get_provider_path "pkcs11.so"); then
return 1
fi
# Write any PIN into a PIN file
echo "${SWTPM_PKCS11_PIN}" > "${pinfile}"
if ! msg=$(openssl \
req \
-provider-path "${providerpath}" \
-provider pkcs11 \
-x509 \
-key "${tpmkeyurl//%00/}${SWTPM_PKCS11_PIN:+?pin-source=${pinfile}}" \
-out "${tpmca}" \
-days 36500 \
-sha256 \
-CA "${cacert}" \
-CAkey "${cakey}" \
${SWTPM_ROOTCA_PASSWORD:+-passin "env:SWTPM_ROOTCA_PASSWORD"} \
-subj "/CN=swtpm-localca" \
-addext "basicConstraints=critical,CA:TRUE" \
-addext "keyUsage=critical,keyCertSign" \
2>&1);
then
logerr "Could not create TPM CA"
logerr "${msg}"
rm -f "${pinfile}"
return 1
fi
rm -f "${pinfile}"
output="statedir = ${dir}
signingkey = $(escape_pkcs11_url "${tpmkeyurl//%00/}")
issuercert = ${tpmca}
certserial = ${dir}/certserial"
output+="$(echo -e "\nSWTPM_PKCS11_PIN = ${SWTPM_PKCS11_PIN}")"
# output+="$(echo -e "\nSWTPM_PKCS11_SO_PIN = ${SWTPM_PKCS11_SO_PIN}")"
if [ -n "${TPM_KEY_PASSWORD}" ]; then
output+="$(echo -e "\nsigningkey_password = ${TPM_KEY_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
return 0
} #create_localca_cert
usage() {
local flags=$2
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
--key-password s Password for the newly created TPM key
--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'
--tpm2 Setup a CA that uses a TPM 2.0
--algorithm <alg> Key algorithm for created TPM 2 CA. Default is rsa2048.
Possible values are: rsa2048, rsa3072, ecc256 or secp256r1,
ecc384 or secp384r1
--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
main() {
local flags=0
local dir outfile owner group msg pid
local algorithm="rsa2048"
while [ $# -ne 0 ]; do
case "$1" in
--dir)
shift
dir="$1"
;;
--overwrite)
flags=$((flags | FLAG_OVERWRITE))
;;
--key-password)
shift
TPM_KEY_PASSWORD="$1"
;;
--outfile)
shift
outfile="$1"
;;
--owner)
shift
owner="$1"
;;
--group)
shift
group="$1"
;;
--tpm2)
flags=$((flags | FLAG_TPM2))
;;
--algorithm)
shift
algorithm="$1"
;;
--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 [ $((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
if ! [[ "${algorithm}" =~ ^(rsa2048|rsa3072|ecc256|ecc384|secp256r1|secp384r1)$ ]]; then
logerr "Unsupported key algorithm for TPM CA '${algorithm}'. See --help."
return 1
else
case "${algorithm}" in
secp256r1) algorithm=ecc256;;
secp384r1) algorithm=ecc384;;
esac
fi
create_localca_cert "${flags}" "${dir}" "${outfile}" "${owner}" "${pid}" "${algorithm}"
return $?
} #main
main "$@"
exit $?