aws_signing_helper/pkcs11_signer.go (1,002 lines of code) (raw):

package aws_signing_helper // RFC7512 defines a standard URI format for referencing PKCS#11 objects. // // Decent applications should silently accept these in place of a file name, // and Do The Right Thing. There should be no additional configuration or // anything else to confuse the user. // // Users shouldn't even need to specify the PKCS#11 "provider" library, as // most systems should use p11-kit for that. Properly packaged providers // will ship with a p11-kit 'module' file which makes them discoverable. // // p11-kit has system-wide and per-user configuration for providers, and // automatically makes all the discovered tokens available through the // "p11-kit-proxy.so" provider module. We just use *that* by default. // // So all the user should ever have to do is something like // --private-key pkcs11:manufacturer=piv_II;id=%01 // or --certificate pkcs11:object=Certificate%20for%20Digital%20Signature?pin-value=123456 // // The PKCS#11 URI is a bit of a misnomer; it's not really a unique // identifier — it's more of a search term; specifying the constraints // which must match either the token or the object therein. Some rules // for how you apply those search constraints, and in particular where // you look for a matching private key after finding a certificate, are // at http://david.woodhou.se/draft-woodhouse-cert-best-practice.html#rfc.section.8.2 // // This code is based on the C implementation at // https://gitlab.com/openconnect/openconnect/-/blob/v9.12/openssl-pkcs11.c // import ( "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/rsa" "crypto/sha256" "crypto/sha512" "crypto/x509" "errors" "fmt" "io" "log" "os" "runtime" "strconv" "strings" "unsafe" "github.com/miekg/pkcs11" pkcs11uri "github.com/stefanberger/go-pkcs11uri" ) var PKCS11_TEST_VERSION int16 = 1 var MAX_OBJECT_LIMIT int = 1000 // In our list of certs, we want to remember the CKA_ID/CKA_LABEL too. type CertObjInfo struct { id []byte label []byte cert *x509.Certificate certObject pkcs11.ObjectHandle } // In our list of keys, we want to remember the CKA_ID/CKA_LABEL too. type KeyObjInfo struct { id []byte label []byte keyObject pkcs11.ObjectHandle } // Used to enumerate slots with all token/slot info for matching. type SlotIdInfo struct { id uint info pkcs11.SlotInfo tokInfo pkcs11.TokenInfo } type PKCS11Signer struct { cert *x509.Certificate certChain []*x509.Certificate module *pkcs11.Ctx userPin string alwaysAuth uint contextSpecificPin string certUri *pkcs11uri.Pkcs11URI keyUri *pkcs11uri.Pkcs11URI reusePin bool } // Initialize a PKCS#11 module. func initializePKCS11Module(lib string) (module *pkcs11.Ctx, err error) { // In a properly configured system, nobody should need to override this. if lib == "" { switch runtime.GOOS { case "darwin": lib = "p11-kit-proxy.dylib" case "windows": lib = "p11-kit-proxy.dll" default: lib = "p11-kit-proxy.so" } } module = pkcs11.New(lib) if module == nil { err = errors.New("Failed to load provider library " + lib) goto fail } if err = module.Initialize(); err != nil { goto fail } return module, nil fail: if module != nil { module.Finalize() module.Destroy() } return nil, err } // Enumerate slots in the PKCS#11 module. This method assumes that the // module isn't nil and has been initialized. func enumerateSlotsInPKCS11Module(module *pkcs11.Ctx) (slots []SlotIdInfo, err error) { var slotIds []uint slotIds, err = module.GetSlotList(true) if err != nil { return nil, err } for _, slotId := range slotIds { var slotIdInfo SlotIdInfo var slotErr error slotIdInfo.id = slotId slotIdInfo.info, slotErr = module.GetSlotInfo(slotId) if slotErr != nil { if Debug { log.Printf("unable to get slot info for slot %d"+ " (%s)\n", slotId, slotErr) } continue } slotIdInfo.tokInfo, slotErr = module.GetTokenInfo(slotId) if slotErr != nil { if Debug { log.Printf("unable to get token info for slot %d"+ " (%s)\n", slotId, slotErr) } continue } slots = append(slots, slotIdInfo) } return slots, nil } // Return true if the URI specifies the attribute and it *doesn't* match. func mismatchAttr(uri *pkcs11uri.Pkcs11URI, attr string, val string) bool { var ( uriVal string ok bool ) uriVal, ok = uri.GetPathAttribute(attr, false) return ok && uriVal != val } // Return the set of slots which match the given URI. func matchSlots(slots []SlotIdInfo, uri *pkcs11uri.Pkcs11URI) (matches []SlotIdInfo) { var ( uriSlotNr uint64 uriSlot string ok bool ) if uri == nil { return slots } uriSlot, ok = uri.GetPathAttribute("slot-id", false) if ok { uriSlotNr, _ = strconv.ParseUint(uriSlot, 0, 32) } for _, slot := range slots { if uriSlotNr != 0 && uriSlotNr != uint64(slot.id) { continue } if mismatchAttr(uri, "token", slot.tokInfo.Label) || mismatchAttr(uri, "model", slot.tokInfo.Model) || mismatchAttr(uri, "manufacturer", slot.tokInfo.ManufacturerID) || mismatchAttr(uri, "serial", slot.tokInfo.SerialNumber) || mismatchAttr(uri, "slot-description", slot.info.SlotDescription) || mismatchAttr(uri, "slot-manufacturer", slot.info.ManufacturerID) { continue } matches = append(matches, slot) } return matches } // Convert the object-related fields in a URI to []*pkcs11.Attribute for FindObjectsInit(). func getFindTemplate(uri *pkcs11uri.Pkcs11URI, class uint) (template []*pkcs11.Attribute) { var ( v string ok bool ) template = append(template, pkcs11.NewAttribute(pkcs11.CKA_CLASS, class)) if uri == nil { return template } v, ok = uri.GetPathAttribute("object", false) if ok { template = append(template, pkcs11.NewAttribute(pkcs11.CKA_LABEL, v)) } v, ok = uri.GetPathAttribute("id", false) if ok { template = append(template, pkcs11.NewAttribute(pkcs11.CKA_ID, v)) } return template } // Gets certificate(s) within the PKCS#11 session (i.e. a given token) that // matches the given URI. func getCertsInSession(module *pkcs11.Ctx, slotId uint, session pkcs11.SessionHandle, uri *pkcs11uri.Pkcs11URI) (certs []CertObjInfo, err error) { var ( sessionCertObjects []pkcs11.ObjectHandle certObjects []pkcs11.ObjectHandle templateCrt []*pkcs11.Attribute ) // Convert the URI into a template for FindObjectsInit(). templateCrt = getFindTemplate(uri, pkcs11.CKO_CERTIFICATE) if err = module.FindObjectsInit(session, templateCrt); err != nil { return nil, err } for true { sessionCertObjects, _, err = module.FindObjects(session, MAX_OBJECT_LIMIT) if err != nil { return nil, err } if len(sessionCertObjects) == 0 { break } certObjects = append(certObjects, sessionCertObjects...) if len(sessionCertObjects) < MAX_OBJECT_LIMIT { break } } err = module.FindObjectsFinal(session) if err != nil { return nil, err } for _, certObject := range certObjects { crtAttributes := []*pkcs11.Attribute{ pkcs11.NewAttribute(pkcs11.CKA_VALUE, 0), } if crtAttributes, err = module.GetAttributeValue(session, certObject, crtAttributes); err != nil { return nil, err } rawCert := crtAttributes[0].Value var certObj CertObjInfo certObj.certObject = certObject certObj.cert, err = x509.ParseCertificate(rawCert) // nosemgrep if err != nil { return nil, errors.New("error parsing certificate") } // Fetch the CKA_ID and CKA_LABEL of the matching cert(s), so // that they can be used later when hunting for the matching // key. crtAttributes = []*pkcs11.Attribute{ pkcs11.NewAttribute(pkcs11.CKA_ID, 0), } crtAttributes, err = module.GetAttributeValue(session, certObject, crtAttributes) if err == nil { certObj.id = crtAttributes[0].Value } crtAttributes = []*pkcs11.Attribute{ pkcs11.NewAttribute(pkcs11.CKA_LABEL, 0), } crtAttributes, err = module.GetAttributeValue(session, certObject, crtAttributes) if err == nil { certObj.label = crtAttributes[0].Value } certs = append(certs, certObj) } return certs, nil } // Scan all matching slots to until we find certificates that match the URI. // If there is at least one matching certificate found, the returned session // will be left open and returned. The session may also be logged in to in the // case that the certificate being searched for could only be found after // logging in to the token. // // NB: It's generally only looking for *one* cert to use. If you want // `p11tool --list-certificates`, use that instead. func getMatchingCerts(module *pkcs11.Ctx, slots []SlotIdInfo, uri *pkcs11uri.Pkcs11URI, userPin string, single bool) (matchedSlot SlotIdInfo, session pkcs11.SessionHandle, loggedIn bool, matchingCerts []CertObjInfo, err error) { var ( errNoMatchingCerts error ) errNoMatchingCerts = errors.New("no matching certificates") if uri != nil { slots = matchSlots(slots, uri) } // http://david.woodhou.se/draft-woodhouse-cert-best-practice.html#rfc.section.8.1 // // "For locating certificates, applications first iterate over the // available tokens without logging in to them. In each token which // matches the provided PKCS#11 URI, a search is performed for // matching certificate objects." for _, slot := range slots { curSession, err := module.OpenSession(slot.id, pkcs11.CKF_SERIAL_SESSION|pkcs11.CKS_RO_PUBLIC_SESSION) if err != nil { if Debug { log.Printf("unable to open session in slot %d"+ " (%s)\n", slot.id, err) } module.CloseSession(curSession) continue } curMatchingCerts, err := getCertsInSession(module, slot.id, curSession, uri) if err == nil && len(curMatchingCerts) > 0 { matchingCerts = append(matchingCerts, curMatchingCerts...) // We only care about this value when there is a single matching // certificate found. if matchedSlot == (SlotIdInfo{}) { matchedSlot = slot session = curSession goto skipCloseSession } } module.CloseSession(curSession) skipCloseSession: } if len(matchingCerts) >= 1 { goto foundCert } // http://david.woodhou.se/draft-woodhouse-cert-best-practice.html#rfc.section.8.1 // // "If no match is found, and precisely one token was matched by the // specified URI, then the application attempts to log in to that // token using a PIN [...]. Another search is performed for matching // objects, which this time will return even any certificate objects // with the CKA_PRIVATE attribute. Is it important to note that the // login should only be attempted if there is precisely one token // which matches the URI, and not if there are multiple possible // tokens in which the object could reside." if len(slots) == 1 { if userPin != "" { curSession, err := module.OpenSession(slots[0].id, pkcs11.CKF_SERIAL_SESSION|pkcs11.CKS_RO_PUBLIC_SESSION) if err != nil { err = errNoMatchingCerts goto fail } err = module.Login(curSession, pkcs11.CKU_USER, userPin) if err != nil { err = errNoMatchingCerts goto fail } curMatchingCerts, err := getCertsInSession(module, slots[0].id, curSession, uri) if err == nil && len(curMatchingCerts) > 0 { matchingCerts = append(matchingCerts, curMatchingCerts...) // We only care about this value when there is a single matching // certificate found. if session == 0 { loggedIn = true matchedSlot = slots[0] session = curSession goto foundCert } } } else { err = errors.New("one matching slot, but no user PIN provided") goto fail } } else if len(slots) == 0 { err = errors.New("no matching slots") goto fail } else { err = errors.New("multiple matching slots") goto fail } foundCert: if single && len(matchingCerts) > 1 { err = errors.New("multiple matching certificates") goto fail } // Exactly one matching certificate after logging in to the appropriate token // iff single is true (otherwise there can be multiple matching certificates). return matchedSlot, session, loggedIn, matchingCerts, nil fail: if session != 0 { module.Logout(session) module.CloseSession(session) } return SlotIdInfo{}, session, false, nil, err } // Used to implement a cut-down version of `p11tool --list-certificates`. func GetMatchingPKCSCerts(uriStr string, lib string) (matchingCerts []CertificateContainer, err error) { var ( slots []SlotIdInfo module *pkcs11.Ctx uri *pkcs11uri.Pkcs11URI userPin string certObjs []CertObjInfo session pkcs11.SessionHandle loggedIn bool slot SlotIdInfo ) uri = pkcs11uri.New() err = uri.Parse(uriStr) if err != nil { return nil, err } userPin, _ = uri.GetQueryAttribute("pin-value", false) module, err = initializePKCS11Module(lib) if err != nil { goto cleanUp } slots, err = enumerateSlotsInPKCS11Module(module) if err != nil { goto cleanUp } slot, session, loggedIn, certObjs, err = getMatchingCerts(module, slots, uri, userPin, false) if err != nil { goto cleanUp } for _, obj := range certObjs { curUri := pkcs11uri.New() curUri.AddPathAttribute("model", slot.tokInfo.Model) curUri.AddPathAttribute("manufacturer", slot.tokInfo.ManufacturerID) curUri.AddPathAttribute("serial", slot.tokInfo.SerialNumber) curUri.AddPathAttribute("slot-description", slot.info.SlotDescription) curUri.AddPathAttribute("slot-manufacturer", slot.info.ManufacturerID) if obj.id != nil { curUri.AddPathAttribute("id", string(obj.id[:])) } if obj.label != nil { curUri.AddPathAttribute("object", string(obj.label[:])) } curUri.AddPathAttribute("type", "cert") curUriStr, err := curUri.Format() // nosemgrep if err != nil { curUriStr = "" } matchingCerts = append(matchingCerts, CertificateContainer{-1, obj.cert, curUriStr}) } // Note that this clean up should happen regardless of failure. cleanUp: if module != nil { if session != 0 { if loggedIn { module.Logout(session) } module.CloseSession(session) } module.Finalize() module.Destroy() } return matchingCerts, err } // Returns the public key associated with this PKCS11Signer. func (pkcs11Signer *PKCS11Signer) Public() crypto.PublicKey { var ( cert *x509.Certificate err error certUri *pkcs11uri.Pkcs11URI ) certUri = pkcs11Signer.certUri if certUri == nil { return nil } cert, err = pkcs11Signer.Certificate() if err == nil { return cert.PublicKey } return nil } // Closes this PKCS11Signer. func (pkcs11Signer *PKCS11Signer) Close() { var module *pkcs11.Ctx module = pkcs11Signer.module if module != nil { module.Finalize() module.Destroy() } pkcs11Signer.module = nil } // Does PIN prompting until the password has been received. // This method is used both for prompting for the user PIN and the // context-specific PIN. Note that finalAuthErrMsg should contain a // `%s` so that the actual error message can be included. func pkcs11PasswordPrompt(module *pkcs11.Ctx, session pkcs11.SessionHandle, userType uint, passwordName string, finalAuthErrMsg string) (pinValue string, err error) { var ( parseErrMsg string pin string prompt string ttyReadPath string ttyWritePath string ttyReadFile *os.File ttyWriteFile *os.File ) parseErrMsg = fmt.Sprintf("unable to read PKCS#11 %s", passwordName) prompt = fmt.Sprintf("Please enter your %s:", passwordName) ttyReadPath = "/dev/tty" ttyWritePath = ttyReadPath if runtime.GOOS == "windows" { ttyReadPath = "CONIN$" ttyWritePath = "CONOUT$" } ttyReadFile, err = os.OpenFile(ttyReadPath, os.O_RDWR, 0) if err != nil { return "", errors.New(parseErrMsg) } defer ttyReadFile.Close() ttyWriteFile, err = os.OpenFile(ttyWritePath, os.O_WRONLY, 0) if err != nil { return "", errors.New(parseErrMsg) } defer ttyWriteFile.Close() for true { pin, err = GetPassword(ttyReadFile, ttyWriteFile, prompt, parseErrMsg) if err != nil && err.Error() == parseErrMsg { continue } err = module.Login(session, userType, pin) if err != nil { // Loop on failure in case the user mistyped their PIN. if strings.Contains(err.Error(), "CKR_PIN_INCORRECT") { prompt = fmt.Sprintf("Incorrect %s. Please re-enter your %s:", passwordName, passwordName) continue } return "", fmt.Errorf(finalAuthErrMsg, err.Error()) } return pin, nil } // The code should never reach here. return "", fmt.Errorf("unexpected error when prompting for %s", passwordName) } // Helper function to sign a digest using a PKCS#11 private key handle. func signHelper(module *pkcs11.Ctx, session pkcs11.SessionHandle, privateKeyObj KeyObjInfo, slot SlotIdInfo, userPin string, alwaysAuth uint, contextSpecificPin string, reusePin bool, keyType uint, digest []byte, hashFunc crypto.Hash) (_contextSpecificPin string, signature []byte, err error) { // XXX: If you use this outside the context of IAM RA, be aware that // you'll want to use something other than SHA256 in many cases. // For TLSv1.3 the hash needs to precisely match the bit size of the // curve, IIRC. And you'll need RSA-PSS too. You might find that // ThalesIgnite/crypto11 has some of that. // e.g. https://github.com/ThalesIgnite/crypto11/blob/master/rsa.go#L230 var ( mechanism uint keyUri *pkcs11uri.Pkcs11URI keyUriStr string ) if keyType == pkcs11.CKK_EC { switch hashFunc { case crypto.SHA256: hash := sha256.Sum256(digest) digest = hash[:] case crypto.SHA384: hash := sha512.Sum384(digest) digest = hash[:] case crypto.SHA512: hash := sha512.Sum512(digest) digest = hash[:] default: return "", nil, ErrUnsupportedHash } mechanism = pkcs11.CKM_ECDSA } else { switch hashFunc { case crypto.SHA256: mechanism = pkcs11.CKM_SHA256_RSA_PKCS case crypto.SHA384: mechanism = pkcs11.CKM_SHA384_RSA_PKCS case crypto.SHA512: mechanism = pkcs11.CKM_SHA512_RSA_PKCS default: return "", nil, ErrUnsupportedHash } } err = module.SignInit(session, []*pkcs11.Mechanism{pkcs11.NewMechanism(mechanism, nil)}, privateKeyObj.keyObject) if err != nil { return "", nil, fmt.Errorf("signing initiation failed (%s)", err.Error()) } if alwaysAuth != 0 { // Set the value for the context-specific PIN used to do the signing // operation with this key. If the context-specific PIN wasn't specified // in the input, and the "reuse PIN" option was set, try to use the // user PIN as the context-specific PIN. if contextSpecificPin == "" && userPin != "" && reusePin { contextSpecificPin = userPin } if contextSpecificPin != "" { err = module.Login(session, pkcs11.CKU_CONTEXT_SPECIFIC, contextSpecificPin) if err == nil { goto afterContextSpecificLogin } else { if Debug { log.Printf("user re-authentication attempt failed (%s)\n", err.Error()) } } } // If the context-specific PIN couldn't be derived, prompt the user for // the context-specific PIN for this object. keyUri = pkcs11uri.New() keyUri.AddPathAttribute("model", slot.tokInfo.Model) keyUri.AddPathAttribute("manufacturer", slot.tokInfo.ManufacturerID) keyUri.AddPathAttribute("serial", slot.tokInfo.SerialNumber) keyUri.AddPathAttribute("slot-description", slot.info.SlotDescription) keyUri.AddPathAttribute("slot-manufacturer", slot.info.ManufacturerID) if privateKeyObj.id != nil { keyUri.AddPathAttribute("id", string(privateKeyObj.id[:])) } if privateKeyObj.label != nil { keyUri.AddPathAttribute("object", string(privateKeyObj.label[:])) } keyUri.AddPathAttribute("type", "private") keyUriStr, err = keyUri.Format() // nosemgrep if err != nil { keyUriStr = "" } passwordName := "context-specific PIN" if keyUriStr != "" { passwordName = fmt.Sprintf("context-specific PIN for private key object (%s)", keyUriStr) } finalAuthErrMsg := "user re-authentication failed (%s)" contextSpecificPin, err = pkcs11PasswordPrompt(module, session, pkcs11.CKU_CONTEXT_SPECIFIC, passwordName, finalAuthErrMsg) if err != nil { return "", nil, err } } afterContextSpecificLogin: sig, err := module.Sign(session, digest) if err != nil { return contextSpecificPin, nil, fmt.Errorf("signing failed (%s)", err.Error()) } // Yay, we have to do the ASN.1 encoding of the R, S values ourselves. if mechanism == pkcs11.CKM_ECDSA { sig, err = encodeEcdsaSigValue(sig) if err != nil { return contextSpecificPin, nil, err } } return contextSpecificPin, sig, nil } // Gets a handle to the private key object (along with some other information // that may need to be saved). func getPKCS11Key(module *pkcs11.Ctx, session pkcs11.SessionHandle, loggedIn bool, certUri *pkcs11uri.Pkcs11URI, keyUri *pkcs11uri.Pkcs11URI, noKeyUri bool, certSlotNr uint, certObj CertObjInfo, userPin string, contextSpecificPin string, reusePin bool, slots []SlotIdInfo) (_session pkcs11.SessionHandle, _userPin string, _keyUri *pkcs11uri.Pkcs11URI, keyType uint, privateKeyObj KeyObjInfo, slot SlotIdInfo, alwaysAuth uint, _contextSpecificPin string, err error) { var ( keySlot SlotIdInfo manufacturerId string templatePrivateKey []*pkcs11.Attribute privateKeyObjects []pkcs11.ObjectHandle keyAttributes []*pkcs11.Attribute ) if keyUri == nil { keyUri = certUri noKeyUri = true } if userPin == "" { userPin, _ = keyUri.GetQueryAttribute("pin-value", false) } // This time we're looking for a *single* slot, as we (presumably) // will have to log in to access the key. slots = matchSlots(slots, keyUri) if len(slots) == 1 { if certSlotNr != slots[0].id { keySlot = slots[0] manufacturerId = slots[0].info.ManufacturerID if session != 0 { if loggedIn { module.Logout(session) module.CloseSession(session) } } loggedIn = false session = 0 } } else { if Debug { log.Printf("Found %d matching slots for the PKCS#11 key\n", len(slots)) } // If the URI matched multiple slots *but* one of them is the // one (certSlotNr) that the certificate was found in, then use // that. for _, slot := range slots { if certSlotNr == slot.id { keySlot = slot manufacturerId = slot.info.ManufacturerID goto got_slot } } err = errors.New("Could not identify unique slot for PKCS#11 key") goto fail } got_slot: if session == 0 { session, err = module.OpenSession(keySlot.id, pkcs11.CKF_SERIAL_SESSION|pkcs11.CKS_RO_PUBLIC_SESSION) } if err != nil { goto fail } // And *now* we fall back to prompting the user for a PIN if necessary. if !loggedIn { if userPin == "" { passwordName := "user PIN" finalAuthErrMsg := "user authentication failed (%s)" userPin, err = pkcs11PasswordPrompt(module, session, pkcs11.CKU_USER, passwordName, finalAuthErrMsg) if err != nil { goto fail } } else { err = module.Login(session, pkcs11.CKU_USER, userPin) if err != nil { goto fail } } } retry_search: templatePrivateKey = getFindTemplate(keyUri, pkcs11.CKO_PRIVATE_KEY) if err = module.FindObjectsInit(session, templatePrivateKey); err != nil { goto fail } for true { sessionPrivateKeyObjects, _, err := module.FindObjects(session, MAX_OBJECT_LIMIT) if err != nil { goto fail } if len(sessionPrivateKeyObjects) == 0 { break } privateKeyObjects = append(privateKeyObjects, sessionPrivateKeyObjects...) if len(sessionPrivateKeyObjects) < MAX_OBJECT_LIMIT { break } } if err = module.FindObjectsFinal(session); err != nil { goto fail } // If we found multiple keys, try them until we find the one // that actually matches the cert. More realistically, there // will be only one. Sanity check that it matches the cert. for _, curPrivateKeyHandle := range privateKeyObjects { keyAttributes = []*pkcs11.Attribute{ pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, 0), } if keyAttributes, err = module.GetAttributeValue(session, curPrivateKeyHandle, keyAttributes); err != nil { continue } keyType, err = bytesToUint(keyAttributes[0].Value) if err != nil { goto fail } keyAttributes = []*pkcs11.Attribute{ pkcs11.NewAttribute(pkcs11.CKA_ALWAYS_AUTHENTICATE, 0), } keyAttributes, err = module.GetAttributeValue(session, curPrivateKeyHandle, keyAttributes) if err == nil { alwaysAuth, err = bytesToUint(keyAttributes[0].Value) if err != nil { goto fail } } else { alwaysAuth = 0 } var curPrivateKeyObj KeyObjInfo curPrivateKeyObj.keyObject = curPrivateKeyHandle // Fetch the CKA_ID and CKA_LABEL of the current private key object, so // that more specific attributes can be used to identify the private key // when prompting for a context-specifc PIN (assuming the CKA_ALWAYS_AUTHENTICATE // attribute is set on the private key object). keyAttributes = []*pkcs11.Attribute{ pkcs11.NewAttribute(pkcs11.CKA_ID, 0), } keyAttributes, err = module.GetAttributeValue(session, curPrivateKeyHandle, keyAttributes) if err == nil { curPrivateKeyObj.id = keyAttributes[0].Value } keyAttributes = []*pkcs11.Attribute{ pkcs11.NewAttribute(pkcs11.CKA_LABEL, 0), } keyAttributes, err = module.GetAttributeValue(session, curPrivateKeyHandle, keyAttributes) if err == nil { curPrivateKeyObj.label = keyAttributes[0].Value } if certObj.cert == nil { if len(privateKeyObjects) == 1 { privateKeyObj = curPrivateKeyObj break } else { err = errors.New("multiple matching private keys, but" + " no certificate provided to match with") goto fail } } var curContextSpecificPin string privateKeyMatchesCert := false curContextSpecificPin, privateKeyMatchesCert = checkPrivateKeyMatchesCert(module, session, keyType, userPin, alwaysAuth, "", reusePin, curPrivateKeyObj, keySlot, certObj.cert, manufacturerId) if privateKeyMatchesCert { privateKeyObj = curPrivateKeyObj contextSpecificPin = curContextSpecificPin break } } if privateKeyObj.keyObject == 0 { /* "If the key is not found and the original search was by * CKA_LABEL of the certificate, then repeat the search using * the CKA_ID of the certificate that was actually found, but * not requiring a CKA_LABEL match." * * http://david.woodhou.se/draft-woodhouse-cert-best-practice.html#rfc.section.8.2 */ if certObj.cert != nil { if noKeyUri { _, keyHadLabel := keyUri.GetPathAttribute("object", false) if keyHadLabel { if Debug { log.Println("unable to find private key with CKA_LABEL;" + " repeating the search using CKA_ID of the certificate" + " without requiring a CKA_LABEL match") } keyUri.RemovePathAttribute("object") keyUri.SetPathAttribute("id", escapeAll(certObj.id)) goto retry_search } } } err = errors.New("unable to find matching private key") goto fail } // So that hunting for the key can be more efficient in the future, // return a key URI that has CKA_ID and CKA_LABEL appropriately set. if privateKeyObj.id != nil && len(privateKeyObj.id) != 0 { keyUri.SetPathAttribute("id", escapeAll(privateKeyObj.id)) } if privateKeyObj.label != nil && len(privateKeyObj.label) != 0 { keyUri.SetPathAttribute("object", escapeAll(privateKeyObj.label)) } return session, userPin, keyUri, keyType, privateKeyObj, keySlot, alwaysAuth, contextSpecificPin, nil fail: return 0, "", nil, 0, KeyObjInfo{}, SlotIdInfo{}, 0, "", err } // Gets the certificate in a token, given the URI that identifies the // certificate. This method also optionally takes in a user PIN, which is // only used (and prompted for, if not given and needed) if the token has to be // logged in to, in order to obtain the certificate. func getCertificate(module *pkcs11.Ctx, certUri *pkcs11uri.Pkcs11URI, userPin string) (certSlot SlotIdInfo, slots []SlotIdInfo, session pkcs11.SessionHandle, loggedIn bool, certObj CertObjInfo, err error) { var ( matchingCerts []CertObjInfo ) slots, err = enumerateSlotsInPKCS11Module(module) if err != nil { return SlotIdInfo{}, nil, 0, false, CertObjInfo{}, err } certSlot, session, loggedIn, matchingCerts, err = getMatchingCerts(module, slots, certUri, userPin, true) if err != nil { return SlotIdInfo{}, nil, 0, false, CertObjInfo{}, err } return certSlot, slots, session, loggedIn, matchingCerts[0], nil } // Implements the crypto.Signer interface and signs the passed in digest func (pkcs11Signer *PKCS11Signer) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) { var ( module *pkcs11.Ctx session pkcs11.SessionHandle certUri *pkcs11uri.Pkcs11URI keyUri *pkcs11uri.Pkcs11URI userPin string contextSpecificPin string privateKeyObj KeyObjInfo keySlot SlotIdInfo keyType uint certSlotNr uint certObj CertObjInfo slots []SlotIdInfo loggedIn bool reusePin bool alwaysAuth uint certSlot SlotIdInfo ) hashFunc := opts.HashFunc() module = pkcs11Signer.module userPin = pkcs11Signer.userPin alwaysAuth = pkcs11Signer.alwaysAuth contextSpecificPin = pkcs11Signer.contextSpecificPin certUri = pkcs11Signer.certUri keyUri = pkcs11Signer.keyUri reusePin = pkcs11Signer.reusePin // If a PKCS#11 URI was provided for the certificate, use it. if certUri != nil { certSlot, slots, session, loggedIn, certObj, err = getCertificate(module, certUri, userPin) if err != nil { goto cleanUp } certSlotNr = certSlot.id } // Otherwise, if the certificate's PKCS#11 URI wasn't provided, enumerate slots. if certUri == nil { slots, err = enumerateSlotsInPKCS11Module(module) if err != nil { goto cleanUp } } session, userPin, keyUri, keyType, privateKeyObj, keySlot, alwaysAuth, contextSpecificPin, err = getPKCS11Key(module, session, loggedIn, certUri, keyUri, false, certSlotNr, certObj, userPin, contextSpecificPin, reusePin, slots) if err != nil { goto cleanUp } contextSpecificPin, signature, err = signHelper(module, session, privateKeyObj, keySlot, userPin, alwaysAuth, contextSpecificPin, reusePin, keyType, digest, hashFunc) if err != nil { goto cleanUp } else { pkcs11Signer.contextSpecificPin = contextSpecificPin } // Note that the session should be logged out of and closed even if there // are no errors after the signing operation. cleanUp: if session != 0 { if loggedIn { module.Logout(session) } module.CloseSession(session) } return signature, err } // Gets the *x509.Certificate associated with this PKCS11Signer. func (pkcs11Signer *PKCS11Signer) Certificate() (cert *x509.Certificate, err error) { // If there was a certificate chain associated with this Signer, it // should've been saved before. cert = pkcs11Signer.cert // If the certificate was saved, return it. if cert != nil { return cert, nil } return nil, errors.New("no certificate associated with signer") } // Checks whether the first certificate issues the second. func certIssues(issuer *x509.Certificate, candidate *x509.Certificate) bool { roots := x509.NewCertPool() roots.AddCert(issuer) opts := x509.VerifyOptions{ Roots: roots, } _, err := candidate.Verify(opts) return err != nil } // Gets the certificate chain from the given session. Certificates in the // chain are obtained through public key signature verification. func getCertificateChain(module *pkcs11.Ctx, session pkcs11.SessionHandle, cert *x509.Certificate) (certChain []*x509.Certificate, err error) { var ( certsFound []CertObjInfo ) // The certificate chain starts with the passed in end-entity certificate. certChain = append(certChain, cert) certsFound, err = getCertsInSession(module, 0, session, nil) if err != nil { return nil, err } for true { nextInChainFound := false for i, curCert := range certsFound { lastCertChainCert := certChain[len(certChain)-1] if certIssues(curCert.cert, lastCertChainCert) { nextInChainFound = true certChain = append(certChain, curCert.cert) // Remove current cert, so that it won't be iterated again. lastIndex := len(certsFound) - 1 certsFound[i] = certsFound[lastIndex] certsFound = certsFound[:lastIndex] break } } if !nextInChainFound { break } } return certChain, err } // Gets the certificate chain associated with this PKCS11Signer. func (pkcs11Signer *PKCS11Signer) CertificateChain() (certChain []*x509.Certificate, err error) { certChain = pkcs11Signer.certChain // If there was a certificate chain associated with this Signer, it // should've been saved before. if certChain != nil { return certChain, nil } return nil, errors.New("no certificate chain associated with signer") } // Checks whether the private key and certificate are associated with each other. func checkPrivateKeyMatchesCert(module *pkcs11.Ctx, session pkcs11.SessionHandle, keyType uint, userPin string, alwaysAuth uint, contextSpecificPin string, reusePin bool, privateKeyObj KeyObjInfo, keySlot SlotIdInfo, certificate *x509.Certificate, manufacturerId string) (string, bool) { var digestSuffix []byte publicKey := certificate.PublicKey ecdsaPublicKey, isEcKey := publicKey.(*ecdsa.PublicKey) if isEcKey { digestSuffixArr := sha256.Sum256(append([]byte("IAM RA"), elliptic.Marshal(ecdsaPublicKey, ecdsaPublicKey.X, ecdsaPublicKey.Y)...)) digestSuffix = digestSuffixArr[:] if keyType != pkcs11.CKK_EC { return "", false } } rsaPublicKey, isRsaKey := publicKey.(*rsa.PublicKey) if isRsaKey { digestSuffixArr := sha256.Sum256(append([]byte("IAM RA"), x509.MarshalPKCS1PublicKey(rsaPublicKey)...)) digestSuffix = digestSuffixArr[:] if keyType != pkcs11.CKK_RSA { return "", false } } // "AWS Roles Anywhere Credential Helper PKCS11 Test" || PKCS11_TEST_VERSION || // MANUFACTURER_ID || SHA256("IAM RA" || PUBLIC_KEY_BYTE_ARRAY) digest := "AWS Roles Anywhere Credential Helper PKCS11 Test" + strconv.Itoa(int(PKCS11_TEST_VERSION)) + manufacturerId + string(digestSuffix) digestBytes := []byte(digest) hash := sha256.Sum256(digestBytes) contextSpecificPin, signature, err := signHelper(module, session, privateKeyObj, keySlot, userPin, alwaysAuth, "", reusePin, keyType, digestBytes, crypto.SHA256) if err != nil { return "", false } if isEcKey { valid := ecdsa.VerifyASN1(ecdsaPublicKey, hash[:], signature) return contextSpecificPin, valid } if isRsaKey { err := rsa.VerifyPKCS1v15(rsaPublicKey, crypto.SHA256, hash[:], signature) return contextSpecificPin, err == nil } return "", false } // This is not my proudest moment. But there's no binary.NativeEndian. func bytesToUint(b []byte) (res uint, err error) { if len(b) == 1 { return uint(b[0]), nil } if len(b) == 2 { var p16 *uint16 p16 = (*uint16)(unsafe.Pointer(&b[0])) return uint(*p16), nil } if len(b) == 4 { var p32 *uint32 p32 = (*uint32)(unsafe.Pointer(&b[0])) return uint(*p32), nil } if len(b) == 8 { var p64 *uint64 p64 = (*uint64)(unsafe.Pointer(&b[0])) return uint(*p64), nil } return 0, errors.New("Unsupported integer size in bytesToUint") } /* * Lifted from pkcs11uri.go because it doesn't let us set an attribute * from a []byte; only a pct-encoded string. * https://github.com/stefanberger/go-pkcs11uri/issues/11 */ // Upper character hex digits needed for pct-encoding. const hexchar = "0123456789ABCDEF" // escapeAll pct-escapes all characters in the string. func escapeAll(s []byte) string { res := make([]byte, len(s)*3) j := 0 for i := 0; i < len(s); i++ { c := s[i] res[j] = '%' res[j+1] = hexchar[c>>4] res[j+2] = hexchar[c&0xf] j += 3 } return string(res) } // Given an optional certificate either as *x509.Certificate (because it was // already found in a file) or as a PKCS#11 URI, and an optional private key // PKCS#11 URI, return a PKCS11Signer that can be used to sign a payload // through a PKCS#11-compatible cryptographic device. func GetPKCS11Signer(libPkcs11 string, cert *x509.Certificate, certChain []*x509.Certificate, privateKeyId string, certificateId string, reusePin bool) (signer Signer, signingAlgorithm string, err error) { var ( module *pkcs11.Ctx certObj CertObjInfo session pkcs11.SessionHandle loggedIn bool keyType uint contextSpecificPin string userPin string alwaysAuth uint certSlotNr uint certUri *pkcs11uri.Pkcs11URI keyUri *pkcs11uri.Pkcs11URI slots []SlotIdInfo certSlot SlotIdInfo noKeyUri bool ) module, err = initializePKCS11Module(libPkcs11) if err != nil { goto fail } // If a PKCS#11 URI was provided for the certificate, find it. if cert == nil && certificateId != "" { certUri = pkcs11uri.New() err = certUri.Parse(certificateId) if err != nil { goto fail } userPin, _ = certUri.GetQueryAttribute("pin-value", false) certSlot, slots, session, loggedIn, certObj, err = getCertificate(module, certUri, userPin) if err != nil { goto fail } certSlotNr = certSlot.id cert = certObj.cert // So that hunting for the certificate can be more efficient in the future, // update the cert URI that has CKA_ID and CKA_VALUE appropriately set. crtAttributes := []*pkcs11.Attribute{ pkcs11.NewAttribute(pkcs11.CKA_ID, 0), } crtAttributes, err = module.GetAttributeValue(session, certObj.certObject, crtAttributes) if err == nil { certUri.SetPathAttribute("id", escapeAll(crtAttributes[0].Value)) } crtAttributes = []*pkcs11.Attribute{ pkcs11.NewAttribute(pkcs11.CKA_LABEL, 0), } crtAttributes, err = module.GetAttributeValue(session, certObj.certObject, crtAttributes) if err == nil { certUri.SetPathAttribute("object", escapeAll(crtAttributes[0].Value)) } if certChain == nil { certChain, err = getCertificateChain(module, session, cert) if err != nil { goto fail } } } else if cert != nil { // Populate certObj, so that it can be used to find the matching private key. certObj = CertObjInfo{nil, nil, cert, 0} } // If an explicit private-key option was given, use it. Otherwise // we look in the same place as the certificate URI as directed by // http://david.woodhou.se/draft-woodhouse-cert-best-practice.html#rfc.section.8.2 if privateKeyId != "" { keyUri = pkcs11uri.New() err = keyUri.Parse(privateKeyId) if err != nil { goto fail } } else { certUriStr, _ := certUri.Format() keyUri = pkcs11uri.New() keyUri.Parse(certUriStr) noKeyUri = true } if _userPin, ok := keyUri.GetQueryAttribute("pin-value", false); ok { userPin = _userPin } // If the certificate's PKCS#11 URI wasn't provided, enumerate slots. if certificateId == "" { slots, err = enumerateSlotsInPKCS11Module(module) if err != nil { goto fail } } session, userPin, keyUri, keyType, _, _, alwaysAuth, contextSpecificPin, err = getPKCS11Key(module, session, loggedIn, certUri, keyUri, noKeyUri, certSlotNr, certObj, userPin, "", reusePin, slots) if err != nil { goto fail } switch keyType { case pkcs11.CKK_EC: signingAlgorithm = aws4_x509_ecdsa_sha256 case pkcs11.CKK_RSA: signingAlgorithm = aws4_x509_rsa_sha256 default: return nil, "", errors.New("unsupported algorithm") } if session != 0 { if loggedIn { module.Logout(session) } module.CloseSession(session) } return &PKCS11Signer{cert, certChain, module, userPin, alwaysAuth, contextSpecificPin, certUri, keyUri, reusePin}, signingAlgorithm, nil fail: if module != nil { if session != 0 { if loggedIn { module.Logout(session) } module.CloseSession(session) } module.Finalize() module.Destroy() } return nil, "", err }