Skip to content

Instantly share code, notes, and snippets.

@joostd
Last active January 29, 2025 12:25
Show Gist options
  • Save joostd/40590010192b700f3358d72277fcba61 to your computer and use it in GitHub Desktop.
Save joostd/40590010192b700f3358d72277fcba61 to your computer and use it in GitHub Desktop.
Check if a YubiHSM 2 FIPS key attestation and CSR meet CA/B forum requirements for code signing
#!/bin/bash
# extend PATH with location of yubihsm-parse-attestation tool
PATH=$PATH:~/go/bin
# check for installed tools
command -v curl >/dev/null 2>&1 \
|| { echo >&2 "please install curl - see https://github.com/curl/curl"; exit 1; }
command -v openssl >/dev/null 2>&1 \
|| { echo >&2 "please install openssl - see https://github.com/openssl/openssl"; exit 1; }
command -v jq >/dev/null 2>&1 \
|| { echo >&2 "please install jq - see https://github.com/jqlang/jq"; exit 1; }
command -v yubihsm-parse-attestation >/dev/null 2>&1 \
|| { echo >&2 "please install yubihsm-parse-attestation - see https://github.com/YubicoLabs/yubihsm-parse-attestation"; exit 1; }
# command line arguments with defaults
CSR="${1:-csr.pem}"
ATTESTATION=${2:-attestation.pem}
PRELOADED=${3:-preloaded.pem}
# check file types
echo -n "input file type "
file -E $CSR | grep -e 'RFC1421' -e 'PEM certificate request' \
|| { echo >&2 "❌ CSR expected as first argument"; exit 1 ; }
echo -n "input file type "
file -E $ATTESTATION | grep 'PEM certificate$' \
|| { echo >&2 "❌ attestation certificate expected as second argument"; exit 1 ; }
echo -n "input file type "
file -E $PRELOADED | grep 'PEM certificate$' \
|| { echo >&2 "❌ attestation issuer certificate expected as third argument"; exit 1 ; }
# Yubico YubiHSM CA certificates
SUBCA=E45DA5F361B091B30D8F2C6FA040DB6FEF57918E.pem
ROOTCA=yubihsm2-attest-ca-crt.pem
# download if missing
curl -s -o $SUBCA -z yubihsm-subca.pem https://developers.yubico.com/YubiHSM2/Concepts/E45DA5F361B091B30D8F2C6FA040DB6FEF57918E.pem
curl -s -o $ROOTCA -z yubihsm2-attest-ca-crt.pem https://developers.yubico.com/YubiHSM2/Concepts/yubihsm2-attest-ca-crt.pem
openssl x509 -noout -fingerprint -sha256 -in $ROOTCA | grep -q 09:4A:3A:C4:93:C2:BD:CD:65:A5:4B:DF:40:19:0F:52:BB:03:F7:15:63:97:A3:FC:69:D8:AA:9A:39:2F:B7:24$ \
|| { echo >&2 "❌ YubiHSM 2 Root CA fingerprint mismatch"; exit 1; }
# 0. check if CSR parses and has a correct signature
openssl req -in $CSR -noout -verify \
|| { echo >&2 "❌ cannot verify $CSR"; exit 1; }
# 1. check if public key in CSR matches public key in attestation certificate:
cmp -s \
<(openssl req -noout -in $CSR -pubkey) \
<(openssl x509 -noout -in $ATTESTATION -pubkey) \
|| { echo >&2 "❌ public key in $CSR does not match public key in $ATTESTATION"; exit 1; }
# 2. Validate CA chain
echo -n "CA path validation for "
openssl verify \
-untrusted $PRELOADED \
-untrusted E45DA5F361B091B30D8F2C6FA040DB6FEF57918E.pem \
-CAfile yubihsm2-attest-ca-crt.pem \
$ATTESTATION \
|| { echo >&2 "❌ cannot validate $ATTESTATION"; exit 1; }
# 3. Parse attestation certificate
json=$(yubihsm-parse-attestation --format json $ATTESTATION) \
|| { echo >&2 "❌ cannot parse $ATTESTATION"; exit 1; }
echo Attestation:
echo $json | jq -c \
|| { echo >&2 "❌ cannot parse JSON from attestation"; exit 1; }
# optional - check firmware
echo -n Checking firmware version...
FW=$(echo $json | jq -r '.device.firmware')
if [[ "$FW" > "2.4.1" ]]; then
echo >&2 "❗ this script is only tested with versions 2.2 and 2.4 of YubiHSM 2 FIPS firmware"
fi
echo -n Checking FIPS certificate...
openssl asn1parse -in preloaded.pem | grep 1.3.6.1.4.1.41482.4.10 -A1 | tail -1 | egrep -o 0201[0-9]+$
# 020106 = https://csrc.nist.gov/projects/cryptographic-module-validation-program/certificate/3916
# 020109 = pending, see https://csrc.nist.gov/projects/cryptographic-module-validation-program/modules-in-process/modules-in-process-list
# 4. check CA/B forum requirements for code signing
# FIPS approved mode is OID is available since fw 2.4.1
if [[ "$FW" > "2.4.0" ]]; then
echo -n Checking FIPS status...
echo $json | jq -e .key.fips \
|| { echo >&2 "❗ YubiHSM 2 not in FIPS approved mode"; }
fi
echo -n Checking origin...
echo $json | jq -e 'select(.key.origin == ["generated"]) | . != {}' \
|| { echo >&2 "❌ signing key not generated on YubiHSM 2"; exit 1; }
echo -n Checking capabilities...
echo $json | jq -e '[.key.capabilities[]!="exportable_under_wrap"] | all' \
|| { echo >&2 "❌ key is exportable"; exit 1; }
echo -n Checking signing key/algorithm...
echo $json | jq -e 'select(.key.algorithm == "RSA" and .key.size == (3072,4096) or .key.algorithm=="ECDSA" and .key.curve == ("P-256","P-384","P-521")) | . != {}' \
|| { echo >&2 "❌ key/algorithm not allowed"; exit 1; }
echo "✅ CA/B forum code signing requirement checks passed"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment