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