Skip to content

Instantly share code, notes, and snippets.

@jay0lee
Last active August 21, 2024 21:13
Show Gist options
  • Save jay0lee/9865cee7fb4f3ad57fb65d14fb6e52a1 to your computer and use it in GitHub Desktop.
Save jay0lee/9865cee7fb4f3ad57fb65d14fb6e52a1 to your computer and use it in GitHub Desktop.
Resets and fully configures the PIV app of a Yubikey for mTLS connections
#!/usr/bin/env bash
###
### Shell script to reset Yubikey PIV app and then fully generate and setup a
### non-exportable private key on the Yubikey that's ready to make mTLS
### requests.
###
### Running on Ubuntu, the following packages are needed:
###
### sudo apt update; sudo apt install opensc yubikey-manager gnutls-bin libengine-pkcs11-openssl
###
### Example run:
###
### $ bash yk-mtls-setup.sh
### Are you sure you want to reset the PIV app on the attached Yubikey? (y/N) y
### New PUK set.
### New PIN set.
### New PUK: 5aKVNt6f New PIN: gZVGgL3z
### Generating private key on Yubikey...
### Generating certificate on Yubikey...
### Exporting certificate from Yubikey...
### Calculating fingerprint...
### Certificate fingerprint is (copy this down):
###
### 7pbGBd15XuADg2YPGlIYzZWaq/ekJQbvRVi8P3YEEZs
###
### Cert to use (copy this down):
###
### pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=c17adfc5b0b478ec;token=yubikey-mtls-setup-script;pin-value=gZVGgL3z
###
### (end of output by command)
###
### The fingerprint value is useful server-side (like in CAA access levels).
### The "Cert to use value can be used directly by curl to perform mTLS using the new client cert.
### We'll copy the cert value to a variable to use with curl:
###
### export cert="pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=99135642b242eb71;token=yubikey-mtls-setup-script;pin-value=N3hwrizb"
###
### Now we can tell curl to GET a demo website using mTLS and our cert stored on the Yubikey:
###
### curl --cert "$cert" https://certauth.idrix.fr/json/ | jq .
###
### the output shows us that mTLS with the Yubikey is working and sending our certificate.
### the JSON value shows our client certificate's subject value and fingerprint. Notice
### the fingerprint is in a different format from the fingerprint above that Google uses.
use_slot="9a"
default_mk="010203040506070801020304050607080102030405060708"
default_pin="123456"
default_puk="12345678"
algorithm="ECCP256"
cert_subject="yubikey-mtls-setup-script"
valid_chars="a-zA-Z0-9"
newpuk=$(dd if=/dev/urandom count=1 status=none | tr -dc "$valid_chars" | fold -w 8 | head -n 1)
newpin=$(dd if=/dev/urandom count=1 status=none | tr -dc "$valid_chars" | fold -w 8 | head -n 1)
PARAMS=""
while (( "$#" )); do
case "$1" in
--slot)
# Yubikey PIV slot to use. Default is 9a.
# https://docs.yubico.com/yesdk/users-manual/application-piv/slots.html
if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
use_slot=$2
shift 2
else
echo "Error: Argument for $1 is missing" >&2
exit 1
fi
;;
--newpin)
# 8 char PIN to set for Yubikey slot. Default is a random string.
# Notice this also ends up in the cert string we output at the end.
if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
newpin="$2"
shift 2
else
echo "Error: Argument for $1 is missing" >&2
exit 1
fi
;;
--newpuk)
# 8 char PUK to set for Yubikey slot. Default is a random string.
# this is only used if you forget and lock the PIN.
if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
newpuk=$2
shift 2
else
echo "Error: Argument for $1 is missing" >&2
exit 1
fi
;;
--algorithm)
# Crypto Algorithm to use for private key. Default is ECCP256.
if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
algorithm=$2
shift 2
else
echo "Error: Argument for $1 is missing" >&2
exit 1
fi
;;
--subject)
# Subject value to set on the client certificate. Default is a string
# that IDs this script. Should not contain spaces or special chars
# beyond @ . - and _
if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
cert_subject=$2
shift 2
else
echo "Error: Argument for $1 is missing" >&2
exit 1
fi
;;
*)
echo "Error: Unsupported argument $1" >&2
exit 1
;;
esac
done
# set positional arguments in their proper place
eval set -- "$PARAMS"
if [ ! -z "$(echo -n "$cert_subject" | tr -d 'a-zA-Z0-9-_@.')" ]; then
echo "ERROR: --subject should only be a-z, A-Z, 0-9, -, _, @ and ."
exit 1
fi
if ! [ -x "$(command -v jq)" ]; then
echo 'Error: jq is not installed and is needed for JSON parsing.' >&2
exit 1
fi
if ! [ -x "$(command -v openssl)" ]; then
echo 'Error: openssl is not installed and is needed for hashing functions.' >&2
exit 1
fi
if ! [ -x "$(command -v ykman)" ]; then
echo 'Error: ykman is not installed and is needed for setting up the Yubikey. You need to install the Ubuntu yubikey-manager package.' >&2
exit 1
fi
if ! [ -x "$(command -v p11tool)" ]; then
echo 'Error: p11tool is not installed. You need to install the Ubuntu opensc package.' >&2
exit 1
fi
read -p "Are you sure you want to reset the PIV app on the attached Yubikey? (y/N) " yn
if [ "$yn" != "y" ]; then
"Giving up then. Enter exactly y next time."
exit 1
fi
ykman piv reset --force &> /dev/null
ykman piv access change-management-key \
--protect \
--pin "$default_pin" \
--management-key "$default_mk"
ykman piv access change-puk --puk "$default_puk" --new-puk "$newpuk"
ykman piv access change-pin --pin "$default_pin" --new-pin "$newpin"
echo -e "New PUK: ${newpuk} New PIN: ${newpin}"
echo "Generating private key on Yubikey..."
pubkey=$(ykman piv keys generate \
--pin "$newpin" \
--algorithm "$algorithm" \
--format "PEM" \
--pin-policy "ALWAYS" \
--touch-policy "NEVER" \
"$use_slot" -)
echo "Generating certificate on Yubikey..."
echo -e "$pubkey" | ykman piv certificates generate \
--hash-algorithm "SHA256" \
--valid-days 36500 \
--pin "$newpin" \
--subject "$cert_subject" \
"$use_slot" -
echo "Exporting certificate from Yubikey..."
pubcert=$(ykman piv certificates "export" \
--format PEM \
"$use_slot" -)
echo "Calculating fingerprint..."
fingerprint=$(echo -e "$pubcert" \
| openssl x509 -outform DER \
| openssl dgst -sha256 -binary - \
| openssl base64 \
| tr -d "=")
echo -e "Certificate fingerprint is (copy this down):\n\n ${fingerprint}\n"
encoded_subject=$(jq -rn --arg x "$cert_subject" '$x|@uri')
pkcs11_cert=$(p11tool --list-tokens \
| grep "URL:" \
| grep "$encoded_subject" \
| cut -d' ' -f2)
pkcs11_cert="${pkcs11_cert};pin-value=${newpin}"
echo -e "Cert to use (copy this down):\n\n ${pkcs11_cert}\n"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment