Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: support all roles in yubikey #653

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions trustmanager/x509utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,7 @@ func CertsToKeys(certs []*x509.Certificate) map[string]data.PublicKey {
}

// NewCertificate returns an X509 Certificate following a template, given a GUN and validity interval.
func NewCertificate(gun string, startTime, endTime time.Time) (*x509.Certificate, error) {
func NewCertificate(subject string, startTime, endTime time.Time) (*x509.Certificate, error) {
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)

serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
Expand All @@ -567,7 +567,7 @@ func NewCertificate(gun string, startTime, endTime time.Time) (*x509.Certificate
return &x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: gun,
CommonName: subject,
},
NotBefore: startTime,
NotAfter: endTime,
Expand Down
117 changes: 78 additions & 39 deletions trustmanager/yubikey/yubikeystore.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,12 @@ package yubikey
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"crypto/x509"
"errors"
"fmt"
"io"
"math/big"
"os"
"time"

Expand All @@ -25,6 +23,7 @@ import (
)

const (
// defaults we'll attempt to use
USER_PIN = "123456"
SO_USER_PIN = "010203040506070801020304050607080102030405060708"
numSlots = 4 // number of slots in the yubikey
Expand Down Expand Up @@ -85,12 +84,13 @@ var pkcs11Lib string
func init() {
for _, loc := range possiblePkcs11Libs {
_, err := os.Stat(loc)
if err == nil {
p := pkcs11.New(loc)
if p != nil {
pkcs11Lib = loc
return
}
if err != nil {
continue
}
p := pkcs11.New(loc)
if p != nil {
pkcs11Lib = loc
return
}
}
}
Expand Down Expand Up @@ -217,15 +217,33 @@ func addECDSAKey(
}

ecdsaPrivKeyD := ensurePrivateKeySize(ecdsaPrivKey.D.Bytes())
return createLoadCert(ctx,
session,
pkcs11KeyID,
role,
ecdsaPrivKeyD,
ecdsaPrivKey.Public(),
ecdsaPrivKey,
)
}

func createLoadCert(
ctx IPKCS11Ctx,
session pkcs11.SessionHandle,
pkcs11KeyID []byte,
role string,
privBytes []byte,
pubKey crypto.PublicKey,
privKey crypto.PrivateKey,
) error {
// Hard-coded policy: the generated certificate expires in 10 years.
startTime := time.Now()
template, err := trustmanager.NewCertificate(role, startTime, startTime.AddDate(10, 0, 0))
if err != nil {
return fmt.Errorf("failed to create the certificate template: %v", err)
}

certBytes, err := x509.CreateCertificate(rand.Reader, template, template, ecdsaPrivKey.Public(), ecdsaPrivKey)
certBytes, err := x509.CreateCertificate(rand.Reader, template, template, pubKey, privKey)
if err != nil {
return fmt.Errorf("failed to create the certificate: %v", err)
}
Expand All @@ -241,7 +259,7 @@ func addECDSAKey(
pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, pkcs11.CKK_ECDSA),
pkcs11.NewAttribute(pkcs11.CKA_ID, pkcs11KeyID),
pkcs11.NewAttribute(pkcs11.CKA_EC_PARAMS, []byte{0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07}),
pkcs11.NewAttribute(pkcs11.CKA_VALUE, ecdsaPrivKeyD),
pkcs11.NewAttribute(pkcs11.CKA_VALUE, privBytes),
pkcs11.NewAttribute(pkcs11.CKA_VENDOR_DEFINED, yubikeyKeymode),
}

Expand All @@ -262,13 +280,11 @@ func getECDSAKey(ctx IPKCS11Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byt
findTemplate := []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
pkcs11.NewAttribute(pkcs11.CKA_ID, pkcs11KeyID),
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PUBLIC_KEY),
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_CERTIFICATE),
}

attrTemplate := []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, []byte{0}),
pkcs11.NewAttribute(pkcs11.CKA_EC_POINT, []byte{0}),
pkcs11.NewAttribute(pkcs11.CKA_EC_PARAMS, []byte{0}),
pkcs11.NewAttribute(pkcs11.CKA_VALUE, []byte{0}),
}

if err := ctx.FindObjectsInit(session, findTemplate); err != nil {
Expand Down Expand Up @@ -297,22 +313,38 @@ func getECDSAKey(ctx IPKCS11Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byt
}

// Iterate through all the attributes of this key and saves CKA_PUBLIC_EXPONENT and CKA_MODULUS. Removes ordering specific issues.
var rawPubKey []byte
var rawCert []byte
for _, a := range attr {
if a.Type == pkcs11.CKA_EC_POINT {
rawPubKey = a.Value
if a.Type == pkcs11.CKA_VALUE {
rawCert = a.Value
}

}
if len(rawCert) == 0 {
logrus.Debugf("failed to retrieve certificate")
return nil, "", err
}
cert, err := x509.ParseCertificate(rawCert)
if err != nil {
logrus.Debugf("failed to parse certificate")
return nil, "", err
}

ecdsaPubKey := ecdsa.PublicKey{Curve: elliptic.P256(), X: new(big.Int).SetBytes(rawPubKey[3:35]), Y: new(big.Int).SetBytes(rawPubKey[35:])}
pubBytes, err := x509.MarshalPKIXPublicKey(&ecdsaPubKey)
var (
ecdsaPubKey *ecdsa.PublicKey
ok bool
)
ecdsaPubKey, ok = cert.PublicKey.(*ecdsa.PublicKey)
if !ok {
logrus.Debugf("could not read public key from certificate")
return nil, "", err
}
pubBytes, err := x509.MarshalPKIXPublicKey(ecdsaPubKey)
if err != nil {
logrus.Debugf("Failed to Marshal public key")
logrus.Debugf("failed to Marshal public key: %s", err.Error())
return nil, "", err
}

return data.NewECDSAPublicKey(pubBytes), data.CanonicalRootRole, nil
return data.NewECDSAPublicKey(pubBytes), cert.Subject.CommonName, nil
}

// Sign returns a signature for a given signature request
Expand Down Expand Up @@ -420,7 +452,6 @@ func yubiListKeys(ctx IPKCS11Ctx, session pkcs11.SessionHandle) (keys map[string
keys = make(map[string]yubiSlot)
findTemplate := []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
//pkcs11.NewAttribute(pkcs11.CKA_ID, pkcs11KeyID),
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_CERTIFICATE),
}

Expand Down Expand Up @@ -659,12 +690,6 @@ func (s *YubiKeyStore) AddKey(keyInfo trustmanager.KeyInfo, privKey data.Private
func (s *YubiKeyStore) addKey(keyID, role string, privKey data.PrivateKey) (
bool, error) {

// We only allow adding root keys for now
if role != data.CanonicalRootRole {
return false, fmt.Errorf(
"yubikey only supports storing root keys, got %s for key: %s", role, keyID)
}

ctx, session, err := SetupHSMEnv(pkcs11Lib, s.libLoader)
if err != nil {
logrus.Debugf("Failed to initialize PKCS11 environment: %s", err.Error())
Expand All @@ -686,18 +711,32 @@ func (s *YubiKeyStore) addKey(keyID, role string, privKey data.PrivateKey) (
}
logrus.Debugf("Attempting to store key using yubikey slot %v", slot)

err = addECDSAKey(
ctx, session, privKey, slot, s.passRetriever, role)
if err == nil {
s.keys[privKey.ID()] = yubiSlot{
role: role,
slotID: slot,
}
return true, nil
switch privKey.(type) {
case *data.ECDSAPrivateKey:
err = addECDSAKey(
ctx,
session,
privKey,
slot,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been wondering if we should change the signatures for addECDSAKey and createLoadCert to have a slotID variable instead of privKeyID variable, WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The terminology is weird and we never settled on something. If memory serves (from discussions with the Yubikey folks 4-5 months back), in PKCS11 a "slot" is technically a device, i.e. the Yubikey itself is a PKCS11 "slot". I'm not sure what the correct term is for the space that a key actually resides in. We should find out and stop using "slot" because it'll just confuse anyone familiar with PKCS11.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like "token"? The pkcs11 spec has a nice section for definitions on page 12 of the core specification PDF (numbered pg6 on the document)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, token does appear to be the correct PKCS11 name. I'll switch over to that in the PR.

s.passRetriever,
role,
)
case *data.RSAPrivateKey:
return false, fmt.Errorf("RSA keys not currently supported in hardware")
default:
return false, fmt.Errorf("key type not recognized")
}
if err != nil {
logrus.Debugf("Failed to add key to yubikey: %v", err)
return false, err
}
logrus.Debugf("Failed to add key to yubikey: %v", err)

return false, err
// everything succeeded, cache keyID: (role, slot) for lookup
s.keys[privKey.ID()] = yubiSlot{
role: role,
slotID: slot,
}
return true, nil
}

// GetKey retrieves a key from the Yubikey only (it does not look inside the
Expand Down