diff --git a/setup.sh b/setup.sh index 2136a584..97c7d458 100755 --- a/setup.sh +++ b/setup.sh @@ -1,5 +1,5 @@ #!/usr/bin/env 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. @@ -40,7 +40,11 @@ check_command "$PIP" # Ensure we have certificates, keys, etc. so that the tests can run source tools/gen_key_materials.sh -generate_crypto_materials N +generate_pki N +if [ ! -f "crypto_data/opensk.key" -o ! -f "crypto_data/opensk_cert.pem" ] +then + generate_new_batch +fi rustup show "$PIP" install --upgrade -r requirements.txt diff --git a/tools/gen_key_materials.sh b/tools/gen_key_materials.sh index 1feb704f..c248f768 100755 --- a/tools/gen_key_materials.sh +++ b/tools/gen_key_materials.sh @@ -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 + # Remove old OpenSK certs and CRL + rm -rf crypto_data/crl crypto_data/certs + fi + + 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 - "${openssl}" ecparam -genkey -name prime256v1 -out "${ca_priv_key}" + uuidgen > "${aaguid_file}" + fi + + if [ ! -f "${ecparams_file}" ] + then + "${openssl}" ecparam -param_enc "named_curve" -name "prime256v1" -out "${ecparams_file}" fi - if [ ! -f "${ca_cert_name}.pem" ] + 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 - fi - - if [ "${force_generate}" = "Y" -o ! -f "${opensk_key}" ] - then - "${openssl}" ecparam -genkey -name prime256v1 -out "${opensk_key}" + -extensions root_ca_ext 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}" ] +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 - uuidgen > "${aaguid_file}" + 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 } -generate_crypto_materials "$1" +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 diff --git a/tools/openssl.ext b/tools/openssl.ext deleted file mode 100644 index ac4f4ca8..00000000 --- a/tools/openssl.ext +++ /dev/null @@ -1 +0,0 @@ -basicConstraints=CA:FALSE \ No newline at end of file diff --git a/tools/openssl/opensk.conf b/tools/openssl/opensk.conf new file mode 100644 index 00000000..717e9c42 --- /dev/null +++ b/tools/openssl/opensk.conf @@ -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} diff --git a/tools/openssl/root-ca.conf b/tools/openssl/root-ca.conf new file mode 100644 index 00000000..5aeb4062 --- /dev/null +++ b/tools/openssl/root-ca.conf @@ -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 + diff --git a/tools/openssl/signing-ca.conf b/tools/openssl/signing-ca.conf new file mode 100644 index 00000000..c529c2c5 --- /dev/null +++ b/tools/openssl/signing-ca.conf @@ -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 +