This is not an officially supported Google product
YubiKeys implement the PIV specification for managing smart card certificates. This applet is a simpler alternative to GPG for managing asymmetric keys on a YubiKey.
This package is an alternative to Paul Tagliamonte's go-ykpiv, a wrapper for YubiKey's ykpiv.h C library. This package aims to provide:
- Better error messages
- Idiomatic Go APIs
- Modern features such as PIN protected management keys
The piv-go package can be used to generate keys and store certificates on a YubiKey. This uses a management key to generate new keys on the applet, and a PIN for signing operations. The package provides default PIN values. If the PIV credentials on the YubiKey haven't been modified, the follow code generates a new EC key on the smartcard, and provides a signing interface:
// List all smartcards connected to the system.
cards, err := piv.Cards()
if err != nil {
// ...
}
// Find a YubiKey and open the reader.
var yk *piv.YubiKey
for _, card := range cards {
if strings.Contains(strings.ToLower(card), "yubikey") {
if yk, err = piv.Open(card); err != nil {
// ...
}
break
}
}
if yk == nil {
// ...
}
// Generate a private key on the YubiKey.
key := piv.Key{
Algorithm: piv.AlgorithmEC256,
PINPolicy: piv.PINPolicyAlways,
TouchPolicy: piv.TouchPolicyAlways,
}
pub, err := yk.GenerateKey(piv.DefaultManagementKey, piv.SlotAuthentication, key)
if err != nil {
// ...
}
auth := piv.KeyAuth{PIN: piv.DefaultPIN}
priv, err := yk.PrivateKey(piv.SlotAuthentication, pub, auth)
if err != nil {
// ...
}
// Use private key to sign or decrypt.
The PIV applet has three unique credentials:
- Management key (3DES key) used to generate new keys on the YubiKey.
- PIN (up to 8 digits, usually 6) used to access signing operations.
- PUK (up to 8 digits) used to unblock the PIN. Usually set once and thrown away or managed by an administrator.
piv-go implements PIN protected management keys to store the management key on the YubiKey. This allows users to only provide a PIN and still access management capabilities.
The following code generates new, random credentials for a YubiKey:
newPINInt, err := rand.Int(rand.Reader, bit.NewInt(1_000_000))
if err != nil {
// ...
}
newPUKInt, err := rand.Int(rand.Reader, bit.NewInt(100_000_000))
if err != nil {
// ...
}
var newKey [24]byte
if _, err := io.ReadFull(rand.Reader, newKey[:]); err != nil {
// ...
}
// Format with leading zeros.
newPIN := fmt.Sprintf("%06d", newPINInt)
newPUK := fmt.Sprintf("%08d", newPUKInt)
// Set all values to a new value.
if err := yk.SetManagementKey(piv.DefaultManagementKey, newKey); err != nil {
// ...
}
if err := yk.SetPIN(piv.DefaultPUK, newPUK); err != nil {
// ...
}
if err := yk.SetPIN(piv.DefaultPIN, newPIN); err != nil {
// ...
}
// Store management key on the YubiKey.
m := piv.Metadata{ManagementKey: &newKey}
if err := yk.SetMetadata(newKey, m); err != nil {
// ...
}
fmt.Println("Credentials set. Your PIN is: %s", newPIN)
The user can user the PIN later to fetch the management key:
m, err := yk.Metadata(pin)
if err != nil {
// ...
}
if m.ManagementKey == nil {
// ...
}
key := *m.ManagementKey
The PIV applet can also store X.509 certificates on the YubiKey:
cert, err := x509.ParseCertificate(certDER)
if err != nil {
// ...
}
if err := yk.SetCertificate(managementKey, piv.SlotAuthentication, cert); err != nil {
// ...
}
The certificate can later be used in combination with the private key. For example, to serve TLS traffic:
cert, err := yk.Certificate(piv.SlotAuthentication)
if err != nil {
// ...
}
priv, err := yk.PrivateKey(piv.SlotAuthentication, cert.PublicKey, auth)
if err != nil {
// ...
}
s := &http.Server{
TLSConfig: &tls.Config{
Certificates: []tls.Certificate{
{
Certificate: [][]byte{cert.Raw},
PrivateKey: priv,
},
},
},
Handler: myHandler,
}
YubiKeys can attest that a particular key was generated on the smartcard, and that it was set with specific PIN and touch policies. The client generates a key, then asks the YubiKey to sign an attestation certificate:
// Get the YubiKey's attestation certificate, which is signed by Yubico.
yubiKeyAttestationCert, err := yk.AttestationCertificate()
if err != nil {
// ...
}
// Generate a key on the YubiKey and generate an attestation certificate for
// that key. This will be signed by the YubiKey's attestation certificate.
key := piv.Key{
Algorithm: piv.AlgorithmEC256,
PINPolicy: piv.PINPolicyAlways,
TouchPolicy: piv.TouchPolicyAlways,
}
if _, err := yk.GenerateKey(managementKey, piv.SlotAuthentication, key); err != nil {
// ...
}
slotAttestationCertificate, err := yk.Attest(piv.SlotAuthentication)
if err != nil {
// ...
}
// Send certificates to server.
A CA can then verify the attestation, proving a key was generated on the card and enforce policy:
// Server receives both certificates, then proves a key was generated on the
// YubiKey.
a, err := piv.Verify(yubiKeyAttestationCert, slotAttestationCertificate)
if err != nil {
// ...
}
if a.TouchPolicy != piv.TouchPolicyAlways {
// ...
}
// Record YubiKey's serial number and public key.
pub := slotAttestationCertificate.PublicKey
serial := a.Serial
On MacOS, piv-go doesn't require any additional packages.
To build on Linux, piv-go requires PCSC lite. To install on Debian-based distros, run:
sudo apt-get install libpcsclite-dev
On Fedora:
sudo yum install pcsc-lite-devel
On CentOS:
sudo yum install 'dnf-command(config-manager)'
sudo yum config-manager --set-enabled PowerTools
sudo yum install pcsc-lite-devel
Tests automatically find connected available YubiKeys, but won't modify the
smart card without the --wipe-yubikey
flag. To let the tests modify your
YubiKey's PIV applet, run:
go test -v ./piv --wipe-yubikey
Longer tests can be skipped with the --test.short
flag.
go test -v --short ./piv --wipe-yubikey
YubiKey's C PIV library, ykpiv, is brittle. The error messages aren't terrific, and while it has debug options, plumbing them through isn't idiomatic or convenient.
ykpiv wraps PC/SC APIs available on Windows, Mac, and Linux. There's no requirement for it to be written in any particular langauge. As an alternative to pault.ag/go/ykpiv this package re-implements ykpiv in Go instead of calling it.
OpenSSH has experimental support for U2F keys (announcement) that directly use browser U2F challenges for smart cards.