Change PKI so that attestation certs are fully compliant. (#668)

* Change PKI so that attestation certs are fully compliant.

Initially we generated the smallest certificate possible.
Unfortunately sometimes attestation certificates are
thoroughly checked and the FIDO x509v3 extensions must be present.
This PR now creates a PKI (root CA and signing CA) with corresponding
CRLs and also allows to create multiple batch certificates for the keys
instead of a single one.
The latest generated batch cert/key is automatically symlinked so that
the previous documentation still holds.

* Change openssl options to support older versions

* OSX doesn't support long options

---------

Co-authored-by: kaczmarczyck <43844792+kaczmarczyck@users.noreply.github.com>
This commit is contained in:
Jean-Michel Picod
2023-12-18 10:47:46 +01:00
committed by GitHub
parent 5fdc6e0739
commit 6b8aa3aaf3
6 changed files with 389 additions and 52 deletions

View File

@@ -1,5 +1,5 @@
#!/bin/bash
# Copyright 2019 Google LLC
# Copyright 2019-2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -13,20 +13,19 @@
# See the License for the specific language governing permissions and
# limitations under the License.
generate_crypto_materials () {
# OpenSSL ext file location
local openssl_ext_file=tools/openssl.ext
generate_pki () {
# Curve parameters
local ecparams_file=crypto_data/ecparams.pem
# OpenSK AAGUID
local aaguid_file=crypto_data/aaguid.txt
# Root CA key pair and certificate
local ca_priv_key=crypto_data/opensk_ca.key
local ca_cert_name=crypto_data/opensk_ca
local ca_priv_key=crypto_data/ca/root-ca/private/root-ca.key
local ca_cert_name=crypto_data/ca/root-ca
# Attestation key pair and certificate that will be embedded into the
# firmware. The certificate will be signed by the Root CA.
local opensk_key=crypto_data/opensk.key
local opensk_cert_name=crypto_data/opensk_cert
# Signing CA key pair and certificate
local signing_ca_priv_key=crypto_data/ca/signing-ca/private/signing-ca.key
local signing_ca_cert_name=crypto_data/ca/signing-ca
# The upgrade private key is used for signing, the corresponding public key
# will be COSE encoded and embedded into the firmware.
@@ -36,6 +35,9 @@ generate_crypto_materials () {
# Allow invoker to override the command with a full path.
local openssl=${OPENSSL:-$(which openssl)}
# Print version for debug purposes
${openssl} version
# We need openssl command to continue
if [ ! -x "${openssl}" ]
then
@@ -47,55 +49,109 @@ generate_crypto_materials () {
set -e
force_generate="$1"
mkdir -p crypto_data
if [ ! -f "${ca_priv_key}" ]
ask_for_password="$2"
if [ "${force_generate}" = "Y" ]
then
"${openssl}" ecparam -genkey -name prime256v1 -out "${ca_priv_key}"
# Remove old OpenSK certs and CRL
rm -rf crypto_data/crl crypto_data/certs
fi
if [ ! -f "${ca_cert_name}.pem" ]
openssl_keypwd="-nodes"
openssl_batch="-batch"
if [ "${ask_for_password}" = "Y" ]
then
openssl_keypwd=""
openssl_batch=""
fi
# Create PKI directories
mkdir -p crypto_data/ca/root-ca/private crypto_data/ca/root-ca/db
mkdir -p crypto_data/ca/signing-ca/private crypto_data/ca/signing-ca/db
mkdir -p crypto_data/crl crypto_data/certs
chmod 700 crypto_data/ca/root-ca/private crypto_data/ca/signing-ca/private
# Prepare PKI databases
for fname in \
crypto_data/ca/root-ca/db/root-ca.db \
crypto_data/ca/root-ca/db/root-ca.db.attr \
crypto_data/ca/signing-ca/db/signing-ca.db \
crypto_data/ca/signing-ca/db/signing-ca.db.attr
do
if [ "${force_generate}" = "Y" -o ! -f "${fname}" ]
then
cp /dev/null "${fname}"
fi
done
# Initialize PKI serial numbers
for fname in \
crypto_data/ca/root-ca/db/root-ca.pem.srl \
crypto_data/ca/root-ca/db/root-ca.pem.srl \
crypto_data/ca/signing-ca/db/signing-ca.pem.srl \
crypto_data/ca/signing-ca/db/signing-ca.pem.srl
do
if [ "${force_generate}" = "Y" -o ! -f "${fname}" ]
then
echo 01 > "${fname}"
fi
done
# Generate AAGUID
if [ "${force_generate}" = "Y" -o ! -f "${aaguid_file}" ]
then
uuidgen > "${aaguid_file}"
fi
if [ ! -f "${ecparams_file}" ]
then
"${openssl}" ecparam -param_enc "named_curve" -name "prime256v1" -out "${ecparams_file}"
fi
if [ "${force_generate}" = "Y" -o ! -f "${ca_cert_name}.pem" ]
then
# Create root CA request and key pair
"${openssl}" req \
-new \
-key "${ca_priv_key}" \
-config tools/openssl/root-ca.conf \
-out "${ca_cert_name}.csr" \
-subj "/CN=OpenSK CA"
"${openssl}" x509 \
-trustout \
-req \
-days 7305 \
-keyout "${ca_priv_key}" \
"${openssl_keypwd}" \
"${openssl_batch}" \
-newkey "ec:${ecparams_file}"
# Make root CA certificate
"${openssl}" ca \
-selfsign \
-config tools/openssl/root-ca.conf \
"${openssl_batch}" \
-in "${ca_cert_name}.csr" \
-signkey "${ca_priv_key}" \
-outform pem \
-out "${ca_cert_name}.pem" \
-sha256
-extensions root_ca_ext
fi
if [ "${force_generate}" = "Y" -o ! -f "${opensk_key}" ]
then
"${openssl}" ecparam -genkey -name prime256v1 -out "${opensk_key}"
fi
if [ "${force_generate}" = "Y" -o ! -f "${opensk_cert_name}.pem" ]
if [ "${force_generate}" = "Y" -o ! -f "${signing_ca_cert_name}.pem" ]
then
# Create signing CA request
"${openssl}" req \
-new \
-key "${opensk_key}" \
-out "${opensk_cert_name}.csr" \
-subj "/C=US/O=OpenSK/OU=Authenticator Attestation/CN=OpenSK Hacker Edition"
"${openssl}" x509 \
-req \
-days 3652 \
-in "${opensk_cert_name}.csr" \
-CA "${ca_cert_name}.pem" \
-extfile "${openssl_ext_file}" \
-CAkey "${ca_priv_key}" \
-CAcreateserial \
-outform pem \
-out "${opensk_cert_name}.pem" \
-sha256
-config tools/openssl/signing-ca.conf \
-out "${signing_ca_cert_name}.csr" \
-keyout "${signing_ca_priv_key}" \
"${openssl_keypwd}" \
"${openssl_batch}" \
-newkey "ec:${ecparams_file}"
# Make signing CA certificate
"${openssl}" ca \
-config tools/openssl/root-ca.conf \
"${openssl_batch}" \
-in "${signing_ca_cert_name}.csr" \
-out "${signing_ca_cert_name}.pem" \
-extensions signing_ca_ext
fi
# Create firmware update key pair
if [ "${force_generate}" = "Y" -o ! -f "${opensk_upgrade}" ]
then
"${openssl}" ecparam -genkey -name prime256v1 -out "${opensk_upgrade}"
@@ -106,11 +162,88 @@ generate_crypto_materials () {
then
"${openssl}" ec -in "${opensk_upgrade}" -pubout -out "${opensk_upgrade_pub}"
fi
if [ "${force_generate}" = "Y" -o ! -f "${aaguid_file}" ]
then
uuidgen > "${aaguid_file}"
fi
}
generate_crypto_materials "$1"
generate_new_batch () {
local openssl=${OPENSSL:-$(which openssl)}
# Curve parameters
local ecparams_file=crypto_data/ecparams.pem
# OpenSK AAGUID
local aaguid_file=crypto_data/aaguid.txt
set -e
# We need openssl command to continue
if [ ! -x "${openssl}" ]
then
echo "Missing openssl command. Try to specify its full path using OPENSSL environment variable."
exit 1
fi
if [ ! -f "${ecparams_file}" ]
then
echo "Missing curve parameters. Has the PKI been generated?"
exit 1
fi
if [ ! -f "${aaguid_file}" ]
then
echo "Missing AAGUID file."
exit 1
fi
batch_id=$(uuidgen | tr -d '-')
aaguid=$(tr -d '-' < "${aaguid_file}")
# Attestation key pair and certificate that will be embedded into the
# firmware. The certificate will be signed by the Root CA.
local opensk_key=certs/${batch_id}.key
local opensk_cert_name=certs/${batch_id}
# x509v3 extension values are passed through environment variables.
export OPENSK_AAGUID="${aaguid}"
# Comma separated values of supported transport for the batch attestation certificate.
# 0=BTC, 1=BLE, 2=USB, 3=NFC
# Default to USB only
export OPENSK_TRANSPORT="${OPENSK_TRANSPORT:-2}" # comma separated values. 1=BLE, 2=USB, 3=NFC
ask_for_password="$1"
openssl_keypwd="-nodes"
openssl_batch="-batch"
if [ "${ask_for_password}" = "Y" ]
then
openssl_keypwd=""
openssl_batch=""
fi
# Generate certificate request for the current batch
"${openssl}" req \
-new \
-config "tools/openssl/opensk.conf" \
-keyout "crypto_data/${opensk_key}" \
-out "crypto_data/${opensk_cert_name}.csr" \
"${openssl_keypwd}" \
"${openssl_batch}" \
-newkey "ec:${ecparams_file}"
# Sign it using signing-CA and injecting the AAGUID as an extension
"${openssl}" ca \
-config "tools/openssl/signing-ca.conf" \
"${openssl_batch}" \
-in "crypto_data/${opensk_cert_name}.csr" \
-out "crypto_data/${opensk_cert_name}.pem" \
-extensions "fido_key_ext"
# Force symlink to the latest batch
ln -s -f "${opensk_cert_name}.pem" crypto_data/opensk_cert.pem
ln -s -f "${opensk_key}" crypto_data/opensk.key
}
if [ "X${1}" != "X" ]
then
ask_for_password=${2:-N}
generate_pki $1 $ask_for_password
if [ "$1" = "Y" -o ! -f "crypto_data/opensk.key" -o ! -f "crypto_data/opensk_cert.pem" ]
then
generate_new_batch $ask_for_password
fi
fi

View File

@@ -1 +0,0 @@
basicConstraints=CA:FALSE

26
tools/openssl/opensk.conf Normal file
View File

@@ -0,0 +1,26 @@
oid_section = OIDS
[ OIDS ]
fido_attestation = 1.3.6.1.4.1.45724.2.1.1
fido_aaguid = 1.3.6.1.4.1.45724.1.1.4
[ req ]
encrypt_key = no
default_md = sha256
utf8 = yes
string_mask = utf8only
prompt = no
distinguished_name = fido_dn
req_extensions = fido_reqext
[ fido_dn ]
countryName = "US"
organizationName = "OpenSK"
organizationalUnitName = "Authenticator Attestation"
commonName = "OpenSK Hacker Edition"
[ fido_reqext ]
keyUsage = critical,digitalSignature
subjectKeyIdentifier = hash
fido_attestation = ASN1:FORMAT:BITLIST,BITSTRING:${ENV::OPENSK_TRANSPORT}
fido_aaguid = ASN1:FORMAT:HEX,OCTETSTRING:${ENV::OPENSK_AAGUID}

View File

@@ -0,0 +1,84 @@
oid_section = OIDS
[ default ]
ca = root-ca
dir = ./crypto_data
[ req ]
encrypt_key = yes
default_md = sha256
utf8 = yes
string_mask = utf8only
prompt = no
distinguished_name = ca_dn
req_extensions = ca_reqext
[ OIDS ]
fido_attestation = 1.3.6.1.4.1.45724.2.1.1
fido_aaguid = 1.3.6.1.4.1.45724.1.1.4
[ ca_dn ]
countryName = "US"
organizationName = "OpenSK"
organizationalUnitName = "Authenticator Attestation"
commonName = "OpenSK CA"
[ ca_reqext ]
keyUsage = critical,keyCertSign,cRLSign
basicConstraints = critical,CA:true
subjectKeyIdentifier = hash
[ ca ]
default_ca = root_ca
[ root_ca ]
certificate = $dir/ca/$ca.pem
private_key = $dir/ca/$ca/private/$ca.key
new_certs_dir = $dir/ca/$ca
serial = $dir/ca/$ca/db/$ca.pem.srl
crlnumber = $dir/ca/$ca/db/$ca.pem.srl
database = $dir/ca/$ca/db/$ca.db
unique_subject = no
default_days = 36525
default_md = sha256
policy = match_pol
email_in_dn = no
preserve = no
name_opt = ca_default
cert_opt = ca_default
copy_extensions = none
x509_extensions = signing_ca_ext
default_crl_days = 365
crl_extensions = crl_ext
[ match_pol ]
countryName = match
organizationName = match
organizationalUnitName = match
commonName = supplied
[ any_pol ]
countryName = optional
stateOrProvinceName = optional
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName = optional
emailAddress = optional
[ root_ca_ext ]
keyUsage = critical,keyCertSign,cRLSign
basicConstraints = critical,CA:true
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always
fido_attestation = ASN1:FORMAT:HEX,BITSTRING:00
[ signing_ca_ext ]
keyUsage = critical,keyCertSign,cRLSign
basicConstraints = critical,CA:true,pathlen:0
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always
[ crl_ext ]
authorityKeyIdentifier = keyid:always

View File

@@ -0,0 +1,91 @@
oid_section = OIDS
[ default ]
ca = signing-ca
dir = ./crypto_data
[ req ]
default_bits = 4096
encrypt_key = yes
default_md = sha256
utf8 = yes
string_mask = utf8only
prompt = no
distinguished_name = ca_dn
req_extensions = ca_reqext
[ OIDS ]
fido_attestation = 1.3.6.1.4.1.45724.2.1.1
fido_aaguid = 1.3.6.1.4.1.45724.1.1.4
[ ca_dn ]
countryName = "US"
organizationName = "OpenSK"
organizationalUnitName = "Authenticator Attestation"
commonName = "OpenSK Signing"
[ ca_reqext ]
keyUsage = critical,keyCertSign,cRLSign
basicConstraints = critical,CA:true,pathlen:0
subjectKeyIdentifier = hash
[ ca ]
default_ca = signing_ca
[ signing_ca ]
certificate = $dir/ca/$ca.pem
private_key = $dir/ca/$ca/private/$ca.key
new_certs_dir = $dir/ca/$ca
serial = $dir/ca/$ca/db/$ca.pem.srl
crlnumber = $dir/ca/$ca/db/$ca.pem.srl
database = $dir/ca/$ca/db/$ca.db
unique_subject = no
default_days = 35064
default_md = sha256
policy = match_pol
email_in_dn = no
preserve = no
name_opt = ca_default
cert_opt = ca_default
copy_extensions = copy
x509_extensions = fido_key_ext
default_crl_days = 7
crl_extensions = crl_ext
[ match_pol ]
countryName = match
organizationName = match
organizationalUnitName = match
commonName = supplied
[ any_pol ]
countryName = optional
stateOrProvinceName = optional
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName = optional
emailAddress = optional
[ root_ca_ext ]
keyUsage = critical,keyCertSign,cRLSign
basicConstraints = critical,CA:true
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always
[ signing_ca_ext ]
keyUsage = critical,keyCertSign,cRLSign
basicConstraints = critical,CA:true,pathlen:0
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always
[ fido_key_ext ]
keyUsage = critical,digitalSignature
basicConstraints = CA:false
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always
[ crl_ext ]
authorityKeyIdentifier = keyid:always