From f9d8b9932c8d15e68cec269c4d31f98a672efbd3 Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Tue, 1 Aug 2023 16:15:01 -0400 Subject: [PATCH 01/40] Add PKCS#11 changes back --- Makefile | 40 +- README.md | 33 +- TODO.pkcs11 | 36 + aws_signing_helper/pkcs11_signer.go | 1009 +++++++++++++++++++++++++++ aws_signing_helper/signer.go | 27 +- aws_signing_helper/signer_test.go | 18 + cmd/credentials.go | 3 + cmd/read_certificate_data.go | 9 +- cmd/sign_string.go | 5 +- go.mod | 5 +- go.sum | 8 + 11 files changed, 1173 insertions(+), 20 deletions(-) create mode 100644 TODO.pkcs11 create mode 100644 aws_signing_helper/pkcs11_signer.go diff --git a/Makefile b/Makefile index 2b3bf23..2cb78b0 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,13 @@ -VERSION=1.0.5 +VERSION=1.0.6 release: go build -buildmode=pie -ldflags "-X 'github.com/aws/rolesanywhere-credential-helper/cmd.Version=${VERSION}' -linkmode=external -w -s" -trimpath -o build/bin/aws_signing_helper main.go +# Setting up SoftHSM for PKCS#11 tests. +# This portion is largely copied from https://gitlab.com/openconnect/openconnect/-/blob/v9.12/tests/Makefile.am#L363. +SHM2_UTIL=SOFTHSM2_CONF=tst/softhsm2.conf.tmp softhsm2-util +P11TOOL=SOFTHSM2_CONF=tst/softhsm2.conf.tmp p11tool + certsdir=tst/certs curdir=$(shell pwd) @@ -13,8 +18,30 @@ ECCERTS := $(foreach digest, sha1 sha256 sha384 sha512, $(patsubst %-key.pem, %- RSACERTS := $(foreach digest, md5 sha1 sha256 sha384 sha512, $(patsubst %-key.pem, %-$(digest)-cert.pem, $(RSAKEYS))) PKCS12CERTS := $(patsubst %-cert.pem, %.p12, $(RSACERTS) $(ECCERTS)) -test: test-certs - go test -v ./... +# It's hard to do a file-based rule for the contents of the SoftHSM token. +# So just populate it as a side-effect of creating the softhsm2.conf file. +tst/softhsm2.conf: tst/softhsm2.conf.template $(PKCS8KEYS) $(RSACERTS) $(ECCERTS) + rm -rf tst/softhsm/* + sed 's|@top_srcdir@|${curdir}|g' $< > $@.tmp + $(SHM2_UTIL) --show-slots + $(SHM2_UTIL) --init-token --free --label credential-helper-test \ + --so-pin 12345678 --pin 1234 + + $(SHM2_UTIL) --token credential-helper-test --pin 1234 \ + --import $(certsdir)/rsa-2048-key-pkcs8.pem --label RSA --id 01 + $(P11TOOL) --load-certificate $(certsdir)/rsa-2048-sha256-cert.pem \ + --no-mark-private --label RSA --id 01 --set-pin 1234 --login \ + --write "pkcs11:token=credential-helper-test;pin-value=1234" + + $(SHM2_UTIL) --token credential-helper-test --pin 1234 \ + --import $(certsdir)/ec-prime256v1-key-pkcs8.pem --label EC --id 02 + $(P11TOOL) --load-certificate $(certsdir)/ec-prime256v1-sha256-cert.pem \ + --no-mark-private --label EC --id 02 --set-pin 1234 --login \ + --write "pkcs11:token=credential-helper-test;pin-value=1234" + mv $@.tmp $@ + +test: test-certs tst/softhsm2.conf + SOFTHSM2_CONF=$(curdir)/tst/softhsm2.conf go test -v ./... %-md5-cert.pem: %-key.pem SUBJ=$$(echo "$@" | sed -r 's|.*/([^/]+)-cert.pem|\1|'); \ @@ -43,6 +70,8 @@ test: test-certs -keypbe pbeWithSHA1And3-KeyTripleDES-CBC \ -inkey $${KEY} -out "$@" -in $${CERT} +# And once again, it's hard to do a file-based rule for the contents of the certificate store. +# So just populate it as a side-effect of creating the p12 file. %-pass.p12: %-cert.pem echo Creating $@... ls -l $< @@ -66,11 +95,14 @@ $(ECKEYS): $(certsdir)/cert-bundle.pem: $(RSACERTS) $(ECCERTS) cat $^ > $@ -test-certs: $(PKCS8KEYS) $(RSAKEYS) $(ECKEYS) $(RSACERTS) $(ECCERTS) $(PKCS12CERTS) $(certsdir)/cert-bundle.pem +test-certs: $(PKCS8KEYS) $(RSAKEYS) $(ECKEYS) $(RSACERTS) $(ECCERTS) $(PKCS12CERTS) $(certsdir)/cert-bundle.pem tst/softhsm2.conf +# TODO: Need to clean certificates and keys added to certificate store as well test-clean: rm -f $(RSAKEYS) $(ECKEYS) rm -f $(PKCS8KEYS) rm -f $(RSACERTS) $(ECCERTS) rm -f $(PKCS12CERTS) rm -f $(certsdir)/cert-bundle.pem + rm -f tst/softhsm2.conf + rm -rf tst/softhsm/* diff --git a/README.md b/README.md index 96e54af..e821513 100644 --- a/README.md +++ b/README.md @@ -36,9 +36,9 @@ The project also comes with two bash scripts at its root, called `generate-certs ### read-certificate-data -Reads a certificate that is on disk. Either the path to the certificate on disk is provided with the `--certificate` parameter, or the `--cert-selector` flag is provided to select a certificate within an OS certificate store. Further details about the flag are provided below. +Reads a certificate. Either the path to the certificate on disk or PKCS#11 URI to identify the certificate is provided with the `--certificate` parameter, or the `--cert-selector` flag is provided to select a certificate within an OS certificate store. Further details about the `--cert-selector` flag are provided below. -If there are multiple certificates that match a given `--cert-selector`, information about each of them is printed. +If there are multiple certificates that match a given `--cert-selector` or PKCS#11 URI (as specified through the `--certificate` parameter), information about each of them is printed. For PKCS#11, URIs for each matched certificate is also printed in the hopes that it will be useful in uniquely identifying a certificate. #### cert-selector flag @@ -129,6 +129,35 @@ The above command will import the PFX file into the user's "MY" certificate stor Also note that the above step can be done through a [Powershell cmdlet](https://learn.microsoft.com/en-us/powershell/module/pki/import-pfxcertificate?view=windowsserver2022-ps) or through [Windows CNG/Cryptography APIs](https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-pfximportcertstore). +#### PKCS#11 Integration + +As you should expect from all applications which use keys and certificates, you can simply give a +[PKCS#11 URI](https://datatracker.ietf.org/doc/html/rfc7512) in place of a filename in order to +use certificates and/or keys from hardware or software PKCS#11 tokens / HSMs. A hybrid mode +using a certificate from a file but only the key in PKCS#11 is also supported. Some examples: + + * `--certificate 'pkcs11:manufacturer=piv_II;id=%01'` + * `--certificate 'pkcs11:object=My%20RA%20key'` + * `--certificate client-cert.pem --private-key 'pkcs11:model=SoftHSM%20v2;object=My%20RA%20key'` + +Some documentation which may assist with finding the correct URI for +your key can be found [here](https://www.infradead.org/openconnect/pkcs11.html). + +Most Linux and similar *nix systems use +[p11-kit](https://p11-glue.github.io/p11-glue/p11-kit/manual/config.html) +to provide consistent system-wide and per-user configuration of +available PKCS#11 providers. Any properly packaged provider module +will register itself with p11-kit and will be automatically visible +through the `p11-kit-proxy.so` provider which is used by default. + +If you have a poorly packaged provider module from a vendor, then +after you have filed a bug you can manually create a p11-kit [module +file](https://p11-glue.github.io/p11-glue/p11-kit/manual/pkcs11-conf.html) +for it. + +For systems or containers which lack p11-kit, a specific PKCS#11 +provider library can be specified using the `--pkcs11-lib` parameter. + ### update Updates temporary credentials in the [credential file](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html). Parameters for this command include those for the `credential-process` command, as well as `--profile`, which specifies the named profile for which credentials should be updated (if the profile doesn't already exist, it will be created), and `--once`, which specifies that credentials should be updated only once. Both arguments are optional. If `--profile` isn't specified, the default profile will have its credentials updated, and if `--once` isn't specified, credentials will be continuously updated. In this case, credentials will be updated through a call to `CreateSession` five minutes before the previous set of credentials are set to expire. Please note that running the `update` command multiple times, creating multiple processes, may not work as intended. There may be issues with concurrent writes to the credentials file. diff --git a/TODO.pkcs11 b/TODO.pkcs11 new file mode 100644 index 0000000..68c81a3 --- /dev/null +++ b/TODO.pkcs11 @@ -0,0 +1,36 @@ + + + +TODO for PKCS#11 support + + • Clean up the support for entering PIN interactively, perhaps add URI + pin-source= support + + • Automated testing, perhaps by pulling in the prepopulated SoftHSM tokens + used for openconnect testing. I've manually tested those and Yubikey PIV + URIs including the following as --private-key arguments: + • pkcs11:token=openconnect-test;object=RSA;pin-value=1234 + • pkcs11:token=openconnect-test;object=EC;pin-value=1234 + Repeat those as --certificate in order to test cert to key matching. + Repeat (as both key and cert arguments) with openconnect-test[123] tokens + which are torture tests for various things seen in the wild (no pubkey, + having to log in to the token, and tokens lacking CKF_LOGIN_REQUIRED). + + My Yubikey is old and doesn't have EC support, but it's useful for testing + the CKA_ALWAYS_AUTHENTICATE support (on the Digital Signature key) and the + matching by CKA_ID when CKA_LABEL doesn't match. So (assuming you provision + the slot) this should work as both --private-key and --certificate: + • pkcs11:manufacturer=piv_II;id=%02;pin-value=123456 + And this should work as --certificate, correctly finding the key: + • pkcs11:manufacturer=piv_II;object=Certificate%20for%20Digital%20Signature?pin-value=123456 + + Check the output by using 'openssl dgst -verify', for example: + + $ build/bin/aws_signing_helper sign-string --certificate pkcs11:token=openconnect-test\;object=RSA?pin-value=1234 <<< Test | xxd -r -ps > testsig + $ openssl dgst -sha256 -engine pkcs11 -keyform engine -verify 'pkcs11:token=openconnect-test;object=RSA?pin-value=1234' -signature testsig <<< Test + + +References: + http://david.woodhou.se/draft-woodhouse-cert-best-practice.html#rfc.section.8 + https://datatracker.ietf.org/doc/html/rfc7512 + https://gitlab.com/openconnect/openconnect/-/blob/v9.12/openssl-pkcs11.c diff --git a/aws_signing_helper/pkcs11_signer.go b/aws_signing_helper/pkcs11_signer.go new file mode 100644 index 0000000..9bbe089 --- /dev/null +++ b/aws_signing_helper/pkcs11_signer.go @@ -0,0 +1,1009 @@ +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" librrary, 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" + "os" + "runtime" + "strconv" + "strings" + "unsafe" + + "github.com/miekg/pkcs11" + pkcs11uri "github.com/stefanberger/go-pkcs11uri" + "golang.org/x/term" +) + +var PKCS11_TEST_VERSION int16 = 1 +var MAX_OBJECT_LIMIT int = 1000 + +type PKCS11Signer struct { + cert *x509.Certificate + certChain []*x509.Certificate + module *pkcs11.Ctx + session pkcs11.SessionHandle + privateKeyHandle pkcs11.ObjectHandle + pin string + keyType uint +} + +// Helper function to check whether the passed in []uint contains a given element +func contains(slice []uint, find uint) bool { + for _, v := range slice { + if v == find { + return true + } + } + + return false +} + +// Used to enumerate slots with all token/slot info for matching +type SlotIdInfo struct { + id uint + info pkcs11.SlotInfo + tokInfo pkcs11.TokenInfo +} + +// Return true if the URI specifies the attribute and it *doesn't* match +func mismatchAttr(uri *pkcs11uri.Pkcs11URI, attr string, val string) 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) { + if uri == nil { + return slots + } + + var uriSlotNr uint64 + var uriSlot string + var ok bool + + 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 +} + +// Initialize and enumerate slots in the PKCS#11 module +func openPKCS11Module(lib string) (module *pkcs11.Ctx, slots []SlotIdInfo, err error) { + var slotIds []uint + + // 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 + } + + slotIds, err = module.GetSlotList(true) + if err != nil { + goto fail + } + + for _, slotId := range slotIds { + var slotIdInfo SlotIdInfo + var slotErr error + + slotIdInfo.id = slotId + slotIdInfo.info, slotErr = module.GetSlotInfo(slotId) + if slotErr != nil { + continue + } + slotIdInfo.tokInfo, slotErr = module.GetTokenInfo(slotId) + if slotErr != nil { + continue + } + + slots = append(slots, slotIdInfo) + } + + return module, slots, nil + +fail: + if module != nil { + module.Finalize() + module.Destroy() + } + return nil, nil, err +} + +// Opens a session with the PKCS #11 module +func openPKCS11Session(lib string, slot uint, uri *pkcs11uri.Pkcs11URI) (module *pkcs11.Ctx, session pkcs11.SessionHandle, err error) { + + module, _, err = openPKCS11Module(lib) + if err != nil { + goto fail + } + + session, err = module.OpenSession(slot, pkcs11.CKF_SERIAL_SESSION|pkcs11.CKS_RO_PUBLIC_SESSION) + if err != nil { + goto fail + } + + return module, session, nil + +fail: + if module != nil { + if session != 0 { + module.CloseSession(session) + } + module.Finalize() + module.Destroy() + } + return nil, 0, err +} + +// Convert the object-related fields in a URI to pkcs11.Attributes for FindObjectsInit() +func getFindTemplate(uri *pkcs11uri.Pkcs11URI, class uint) (template []*pkcs11.Attribute) { + var v string + var 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 +} + +// In our list of certs we want to remember the CKA_ID/CKA_LABEL too +type CertObjInfo struct { + id []byte + label []byte + x509 *x509.Certificate + model string + manufacturerId string + serial string +} + +// Gets certificate(s) within the PKCS#11 session (i.e. a given token) that +// match the given URI +func getCertsInSession(module *pkcs11.Ctx, slotId uint, session pkcs11.SessionHandle, uri *pkcs11uri.Pkcs11URI) (certs []CertObjInfo, err error) { + var sessionCertObjects []pkcs11.ObjectHandle + var certObjects []pkcs11.ObjectHandle + var templateCrt []*pkcs11.Attribute + var tokenInfo *pkcs11.TokenInfo + + // 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 + } + + if slotId != 0 { + tokenInfoTmp, _ := module.GetTokenInfo(slotId) + tokenInfo = &tokenInfoTmp + } + + 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.x509, 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[0] = pkcs11.NewAttribute(pkcs11.CKA_ID, 0) + crtAttributes, err = module.GetAttributeValue(session, certObject, crtAttributes) + if err == nil { + certObj.id = crtAttributes[0].Value + } + + crtAttributes[0] = pkcs11.NewAttribute(pkcs11.CKA_LABEL, 0) + crtAttributes, err = module.GetAttributeValue(session, certObject, crtAttributes) + if err == nil { + certObj.label = crtAttributes[0].Value + } + + if tokenInfo != nil { + certObj.serial = tokenInfo.SerialNumber + certObj.manufacturerId = tokenInfo.ManufacturerID + certObj.model = tokenInfo.Model + } + + certs = append(certs, certObj) + } + + return certs, nil +} + +// Scan all matching slots to until we find certificates that match the URI. +// +// 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, pinPkcs11 *string, single bool) (slotNr uint, session pkcs11.SessionHandle, loggedIn bool, matchingCerts []CertObjInfo, err error) { + 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 { + 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 slotNr == 0 { + slotNr = slot.id + session = curSession + goto skip + } + } + module.CloseSession(curSession) + skip: + } + + if single && len(matchingCerts) > 1 { + err = errors.New("multiple matching certificates") + goto fail + } + + if len(matchingCerts) > 1 { + return slotNr, session, false, matchingCerts, nil + } + + // If there is exactly one matching certificate found without logging in + // to any of the tokens, then return that one. + if single && len(matchingCerts) == 1 { + return slotNr, session, false, matchingCerts, nil + } + + // 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 && *pinPkcs11 != "" { + errNoMatchingCerts := errors.New("no matching certificates") + + 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, *pinPkcs11) + 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 { + slotNr = slots[0].id + session = curSession + goto foundCert + } + } + module.Logout(curSession) + module.CloseSession(curSession) + } + + // No matching certificates + err = errors.New("no matching certificates") + goto fail + +foundCert: + if single && len(matchingCerts) > 0 { + err = errors.New("multiple matching certificates") + goto fail + } + + // Exactly one matching certificate after logging into the appropriate token + // iff single is true (otherwise there can be multiple matching certificates) + return slotNr, session, true, matchingCerts, nil + +fail: + if session != 0 { + module.Logout(session) + module.CloseSession(session) + } + return 0, 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 + var module *pkcs11.Ctx + var uri *pkcs11uri.Pkcs11URI + var pin string + var certObjs []CertObjInfo + + uri = pkcs11uri.New() + err = uri.Parse(uriStr) + if err != nil { + return nil, err + } + + pin, _ = uri.GetQueryAttribute("pin-value", false) + + module, slots, err = openPKCS11Module(lib) + if err != nil { + return nil, err + } + + _, session, loggedIn, certObjs, err := getMatchingCerts(module, slots, uri, &pin, false) + if err != nil { + // Session has been closed and module has been destroyed already in this case + return nil, err + } + + if module != nil { + if session != 0 { + if loggedIn { + module.Logout(session) + } + module.CloseSession(session) + } + module.Finalize() + module.Destroy() + } + + for _, obj := range certObjs { + curUri := pkcs11uri.New() + curUri.AddPathAttribute("model", obj.model) + curUri.AddPathAttribute("manufacturer", obj.manufacturerId) + curUri.AddPathAttribute("serial", obj.serial) + 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() + if err != nil { + curUriStr = "" + } + matchingCerts = append(matchingCerts, CertificateContainer{obj.x509, curUriStr}) + } + return matchingCerts, err +} + +// Returns the public key associated with this PKCS11Signer +func (pkcs11Signer *PKCS11Signer) Public() crypto.PublicKey { + if pkcs11Signer.cert != nil { + return pkcs11Signer.cert.PublicKey + } + return nil +} + +// Closes this PKCS11Signer +func (pkcs11Signer *PKCS11Signer) Close() { + if module := pkcs11Signer.module; module != nil { + if session := pkcs11Signer.session; session != 0 { + module.Logout(session) + module.CloseSession(session) + } + module.Finalize() + module.Destroy() + } +} + +// Helper function to sign a digest using a PKCS#11 private key handle +func signHelper(module *pkcs11.Ctx, session pkcs11.SessionHandle, privateKeyHandle pkcs11.ObjectHandle, alwaysAuth uint, pkcs11Pin string, keyType uint, digest []byte, hashFunc crypto.Hash) (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 + 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)}, privateKeyHandle) + if err != nil { + return nil, fmt.Errorf("signing initiation failed (%s)", err.Error()) + } + + // We assume it's the same PIN as the token itself? Which was only + // "saved" for us in PKCS11Signer if the CKA_ALWAYS_AUTHENTICATE + // attribute was set (assuming this method is called with the attributes + // of an existing PKCS11Signer object). + if alwaysAuth != 0 && pkcs11Pin != "" { + err = module.Login(session, pkcs11.CKU_CONTEXT_SPECIFIC, pkcs11Pin) + if err != nil { + return nil, fmt.Errorf("user re-authentication failed (%s)", err.Error()) + } + } + + sig, err := module.Sign(session, digest) + if err != nil { + return 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 nil, err + } + } + + return sig, 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) { + module := pkcs11Signer.module + session := pkcs11Signer.session + privateKeyHandle := pkcs11Signer.privateKeyHandle + keyType := pkcs11Signer.keyType + pin := pkcs11Signer.pin + hashFunc := opts.HashFunc() + + return signHelper(module, session, privateKeyHandle, 0, pin, keyType, digest, hashFunc) +} + +// Gets the x509.Certificate associated with this PKCS11Signer +func (pkcs11Signer *PKCS11Signer) Certificate() (*x509.Certificate, error) { + return pkcs11Signer.cert, nil +} + +// 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 associated with this PKCS11Signer +func (pkcs11Signer *PKCS11Signer) CertificateChain() (chain []*x509.Certificate, err error) { + module := pkcs11Signer.module + session := pkcs11Signer.session + chain = append(chain, pkcs11Signer.cert) + + certsFound, err := getCertsInSession(module, 0, session, nil) + if err != nil { + return nil, err + } + + for true { + nextInChainFound := false + for i, curCert := range certsFound { + curLastCert := chain[len(chain)-1] + if certIssues(curLastCert, curCert.x509) { + nextInChainFound = true + chain = append(chain, curCert.x509) + + // 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 chain, nil +} + +// Gets the manufacturer ID for the PKCS #11 module +func getManufacturerId(module *pkcs11.Ctx) (string, error) { + info, err := module.GetInfo() + if err != nil { + return "", err + } + + return info.ManufacturerID, nil +} + +// Checks whether the private key and certificate are associated with each other +func checkPrivateKeyMatchesCert(module *pkcs11.Ctx, session pkcs11.SessionHandle, keyType uint, alwaysAuth uint, pinPkcs11 string, privateKeyHandle pkcs11.ObjectHandle, certificate *x509.Certificate, manufacturerId 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) + + signature, err := signHelper(module, session, privateKeyHandle, alwaysAuth, pinPkcs11, keyType, digestBytes, crypto.SHA256) + if err != nil { + return false + } + + if isEcKey { + valid := ecdsa.VerifyASN1(ecdsaPublicKey, hash[:], signature) + return valid + } + + if isRsaKey { + err := rsa.VerifyPKCS1v15(rsaPublicKey, crypto.SHA256, hash[:], signature) + return 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, certificate *x509.Certificate, certificateChain []*x509.Certificate, privateKeyId string, certificateId string) (signer Signer, signingAlgorithm string, err error) { + var templatePrivateKey []*pkcs11.Attribute + var sessionPrivateKeyObjects []pkcs11.ObjectHandle + var privateKeyHandle pkcs11.ObjectHandle + var module *pkcs11.Ctx + var session pkcs11.SessionHandle + var manufacturerId string + var certUri *pkcs11uri.Pkcs11URI + var keyUri *pkcs11uri.Pkcs11URI + var slotNr uint + var slots []SlotIdInfo + var pinPkcs11 string + var certObj []CertObjInfo + var keyAttributes []*pkcs11.Attribute + var keyType uint + var alwaysAuth uint + var privateKeyObjects []pkcs11.ObjectHandle + var loggedIn bool + + module, slots, err = openPKCS11Module(libPkcs11) + if err != nil { + goto fail + } + + // If a PKCS#11 URI was provided for the certificate, find it. + if certificate == nil && certificateId != "" { + certUri = pkcs11uri.New() + err = certUri.Parse(certificateId) + if err != nil { + goto fail + } + pinPkcs11, _ := certUri.GetQueryAttribute("pin-value", false) + slotNr, session, loggedIn, certObj, err = getMatchingCerts(module, slots, certUri, &pinPkcs11, true) + if err != nil { + goto fail + } + certificate = certObj[0].x509 + } + + // If no 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 { + keyUri = certUri + } + pinPkcs11, _ = 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 slotNr != slots[0].id { + slotNr = slots[0].id + if session != 0 { + if loggedIn { + module.Logout(session) + module.CloseSession(session) + } + } + loggedIn = false + session = 0 + } + } else { + // If the URI matched multiple slots *but* one of them is the + // one (slotNr) that the certificate was found in, then use + // that. + for _, slot := range slots { + if slot.id == slotNr { + 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(slotNr, 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 pinPkcs11 == "" { + parseErrMsg := "unable to read PKCS#11 user pin" + prompt := "Please enter your user pin:" + + ttyPath := "/dev/tty" + if runtime.GOOS == "windows" { + ttyPath = "CON" + } + + ttyFile, err := os.OpenFile(ttyPath, os.O_RDWR, 0) + if err != nil { + goto fail + } + defer ttyFile.Close() + + for true { + pinPkcs11, err = GetPassword(ttyFile, prompt, parseErrMsg) + if err != nil && err.Error() == parseErrMsg { + continue + } + + err = module.Login(session, pkcs11.CKU_USER, pinPkcs11) + if err != nil { + // Loop on failure in case the user mistyped their PIN. + if strings.Contains(err.Error(), "CKR_PIN_INCORRECT") { + prompt = "Incorrect user pin. Please re-enter your user pin:" + continue + } + goto fail + } + break + } + } else { + err = module.Login(session, pkcs11.CKU_USER, pinPkcs11) + 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 + } + + // Get manufacturer ID once, so that it can be used in the test string to sign when testing candidate private keys + manufacturerId, err = getManufacturerId(module) + if 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 { + // Find the signing algorithm + 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[0] = 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 + } + + if certificate == nil || + checkPrivateKeyMatchesCert(module, session, keyType, alwaysAuth, pinPkcs11, curPrivateKeyHandle, certificate, manufacturerId) { + privateKeyHandle = curPrivateKeyHandle + break + } + } + + if privateKeyHandle == 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 (privateKeyId == "" || privateKeyId == certificateId) && + certificate != nil && certObj[0].id != nil { + _, key_had_label := keyUri.GetPathAttribute("object", false) + if key_had_label { + keyUri.RemovePathAttribute("object") + keyUri.SetPathAttribute("id", escapeAll(certObj[0].id)) + goto retry_search + } + } + + err = errors.New("unable to find matching private key") + 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 alwaysAuth == 0 { + pinPkcs11 = "" + } + + return &PKCS11Signer{certificate, nil, module, session, privateKeyHandle, pinPkcs11, keyType}, signingAlgorithm, nil + +fail: + if module != nil { + if session != 0 { + module.Logout(session) + module.CloseSession(session) + } + module.Finalize() + module.Destroy() + } + + return nil, "", err +} + +// Prompts the user for their password +func GetPassword(ttyFile *os.File, prompt string, parseErrMsg string) (string, error) { + fmt.Fprintln(ttyFile, prompt) + passwordBytes, err := term.ReadPassword(int(ttyFile.Fd())) + if err != nil { + return "", errors.New(parseErrMsg) + } + + password := string(passwordBytes[:]) + strings.Replace(password, "\r", "", -1) // Remove CR + return password, nil +} diff --git a/aws_signing_helper/signer.go b/aws_signing_helper/signer.go index dd6f30f..d6b60ec 100644 --- a/aws_signing_helper/signer.go +++ b/aws_signing_helper/signer.go @@ -170,7 +170,7 @@ func GetSigner(opts *CredentialsOpts) (signer Signer, signatureAlgorithm string, privateKeyId = opts.CertificateId } - if opts.CertificateId != "" { + if opts.CertificateId != "" && !strings.HasPrefix(opts.CertificateId, "pkcs11:") { certificateData, err := ReadCertificateData(opts.CertificateId) if err == nil { certificateDerData, err := base64.StdEncoding.DecodeString(certificateData.CertificateData) @@ -202,15 +202,22 @@ func GetSigner(opts *CredentialsOpts) (signer Signer, signatureAlgorithm string, } } - privateKey, err := ReadPrivateKeyData(privateKeyId) - if err != nil { - return nil, "", err - } - - if Debug { - fmt.Fprintln(os.Stderr, "attempting to use FileSystemSigner") - } - return GetFileSystemSigner(privateKey, certificate, certificateChain) + if strings.HasPrefix(privateKeyId, "pkcs11:") { + if Debug { + fmt.Fprintln(os.Stderr, "attempting to use PKCS#11") + } + return GetPKCS11Signer(opts.LibPkcs11, certificate, certificateChain, opts.PrivateKeyId, opts.CertificateId) + } else { + privateKey, err := ReadPrivateKeyData(privateKeyId) + if err != nil { + return nil, "", err + } + + if Debug { + fmt.Fprintln(os.Stderr, "attempting to use FileSystemSigner") + } + return GetFileSystemSigner(privateKey, certificate, certificateChain) + } } // Obtain the date-time, formatted as specified by SigV4 diff --git a/aws_signing_helper/signer_test.go b/aws_signing_helper/signer_test.go index 97189ce..d1282b0 100644 --- a/aws_signing_helper/signer_test.go +++ b/aws_signing_helper/signer_test.go @@ -241,12 +241,30 @@ func TestSign(t *testing.T) { } } + + pkcs11_objects := []string{"RSA", "EC"} + + for _, object := range pkcs11_objects { + pkcs11_uri := fmt.Sprintf("pkcs11:token=credential-helper-test;object=%s?pin-value=1234", object) + + testTable = append(testTable, CredentialsOpts{ + CertificateId: pkcs11_uri, + }) + testTable = append(testTable, CredentialsOpts{ + PrivateKeyId: pkcs11_uri, + }) + testTable = append(testTable, CredentialsOpts{ + CertificateId: pkcs11_uri, + PrivateKeyId: pkcs11_uri, + }) + } digestList := []crypto.Hash{crypto.SHA256, crypto.SHA384, crypto.SHA512} for _, credOpts := range testTable { signer, _, err := GetSigner(&credOpts) if err != nil { + t.Log(err) var logMsg string if credOpts.CertificateId != "" || credOpts.PrivateKeyId != "" { logMsg = fmt.Sprintf("Failed to get signer for '%s'/'%s'", diff --git a/cmd/credentials.go b/cmd/credentials.go index 99749ca..285da0e 100644 --- a/cmd/credentials.go +++ b/cmd/credentials.go @@ -67,6 +67,9 @@ func initCredentialsSubCommand(subCmd *cobra.Command) { subCmd.PersistentFlags().StringVar(&certificateBundleId, "intermediates", "", "Path to intermediate certificate bundle file") subCmd.PersistentFlags().StringVar(&certSelector, "cert-selector", "", "JSON structure to identify a certificate from a certificate store. "+ "Can be passed in either as string or a file name (prefixed by \"file://\")") + subCmd.PersistentFlags().StringVar(&libPkcs11, "pkcs11-lib", "", "Library for smart card / cryptographic device (OpenSC or vendor specific)") + subCmd.PersistentFlags().StringVar(&pinPkcs11, "pkcs11-pin", "-", "Pin of the PKCS #11 user for private key access") + subCmd.PersistentFlags().UintVar(&slotPkcs11, "pkcs11-slot", 0, "PKCS #11 slot in which to search for the private key (and potentially certificate as well)") subCmd.MarkFlagsMutuallyExclusive("private-key", "cert-selector") } diff --git a/cmd/read_certificate_data.go b/cmd/read_certificate_data.go index f76e286..a2c1ae3 100644 --- a/cmd/read_certificate_data.go +++ b/cmd/read_certificate_data.go @@ -7,6 +7,7 @@ import ( "fmt" "log" "os" + "strings" helper "github.com/aws/rolesanywhere-credential-helper/aws_signing_helper" "github.com/spf13/cobra" @@ -54,7 +55,13 @@ var readCertificateDataCmd = &cobra.Command{ // PrintCertificate interface can be assigned to this variable. var printFunction PrintCertificate = DefaultPrintCertificate - if certificateId != "" && certIdentifier == (helper.CertIdentifier{}) { + if strings.HasPrefix(certificateId, "pkcs11:") { + certContainers, err = helper.GetMatchingPKCSCerts(certificateId, libPkcs11) + if err != nil { + log.Println(err) + os.Exit(1) + } + } else if certificateId != "" && certIdentifier == (helper.CertIdentifier{}) { data, err := helper.ReadCertificateData(certificateId) if err != nil { os.Exit(1) diff --git a/cmd/sign_string.go b/cmd/sign_string.go index 00401c9..f5ae416 100644 --- a/cmd/sign_string.go +++ b/cmd/sign_string.go @@ -73,11 +73,12 @@ func init() { rootCmd.AddCommand(signStringCmd) format = newEnum([]string{"json", "text", "bin"}, "json") digestArg = newEnum([]string{"SHA256", "SHA384", "SHA512"}, "SHA256") - signStringCmd.PersistentFlags().StringVar(&certificateId, "certificate", "", "Path to certificate file") - signStringCmd.PersistentFlags().StringVar(&privateKeyId, "private-key", "", "Path to private key file") + signStringCmd.PersistentFlags().StringVar(&certificateId, "certificate", "", "Path to certificate file or PKCS#11 URI to identify the certificate") + signStringCmd.PersistentFlags().StringVar(&privateKeyId, "private-key", "", "Path to private key file or PKCS#11 URI to identify the private key") signStringCmd.PersistentFlags().BoolVar(&debug, "debug", false, "To print debug output") signStringCmd.PersistentFlags().StringVar(&certSelector, "cert-selector", "", "JSON structure to identify a certificate from a certificate store. "+ "Can be passed in either as string or a file name (prefixed by \"file://\")") + signStringCmd.PersistentFlags().StringVar(&libPkcs11, "pkcs11-lib", "", "Library for smart card / cryptographic device (default: p11-kit-proxy.{so, dll, dylib})") signStringCmd.PersistentFlags().Var(format, "format", "Output format. One of json, text, and bin") signStringCmd.PersistentFlags().Var(digestArg, "digest", "One of SHA256, SHA384, and SHA512") } diff --git a/go.mod b/go.mod index f1e738c..10ba30a 100644 --- a/go.mod +++ b/go.mod @@ -6,13 +6,16 @@ require ( github.com/aws/aws-sdk-go v1.44.57 github.com/spf13/cobra v1.6.1 golang.org/x/crypto v0.10.0 - golang.org/x/sys v0.9.0 + golang.org/x/sys v0.10.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/miekg/pkcs11 v1.1.1 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/stefanberger/go-pkcs11uri v0.0.0-20230614165346-c1cad3d2f68c // indirect + golang.org/x/term v0.10.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 885c299..19c118e 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,8 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU= +github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -18,6 +20,8 @@ github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stefanberger/go-pkcs11uri v0.0.0-20230614165346-c1cad3d2f68c h1:HxmodsFg2lqbspDblBhyR6fXOYwilB6Esnw3PJSaSCA= +github.com/stefanberger/go-pkcs11uri v0.0.0-20230614165346-c1cad3d2f68c/go.mod h1:39R/xuhNgVhi+K0/zst4TLrJrVmbm6LVgl4A0+ZFS5M= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= @@ -26,7 +30,11 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= +golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 05281449e841c686be1535a7cf0925df7902cb02 Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Tue, 1 Aug 2023 16:17:53 -0400 Subject: [PATCH 02/40] Fix file formatting using 'go fmt' --- aws_signing_helper/signer.go | 34 +++++++++++++++---------------- aws_signing_helper/signer_test.go | 4 ++-- cmd/credentials.go | 2 +- cmd/read_certificate_data.go | 4 ++-- cmd/sign_string.go | 2 +- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/aws_signing_helper/signer.go b/aws_signing_helper/signer.go index d6b60ec..fa877a3 100644 --- a/aws_signing_helper/signer.go +++ b/aws_signing_helper/signer.go @@ -170,7 +170,7 @@ func GetSigner(opts *CredentialsOpts) (signer Signer, signatureAlgorithm string, privateKeyId = opts.CertificateId } - if opts.CertificateId != "" && !strings.HasPrefix(opts.CertificateId, "pkcs11:") { + if opts.CertificateId != "" && !strings.HasPrefix(opts.CertificateId, "pkcs11:") { certificateData, err := ReadCertificateData(opts.CertificateId) if err == nil { certificateDerData, err := base64.StdEncoding.DecodeString(certificateData.CertificateData) @@ -202,22 +202,22 @@ func GetSigner(opts *CredentialsOpts) (signer Signer, signatureAlgorithm string, } } - if strings.HasPrefix(privateKeyId, "pkcs11:") { - if Debug { - fmt.Fprintln(os.Stderr, "attempting to use PKCS#11") - } - return GetPKCS11Signer(opts.LibPkcs11, certificate, certificateChain, opts.PrivateKeyId, opts.CertificateId) - } else { - privateKey, err := ReadPrivateKeyData(privateKeyId) - if err != nil { - return nil, "", err - } - - if Debug { - fmt.Fprintln(os.Stderr, "attempting to use FileSystemSigner") - } - return GetFileSystemSigner(privateKey, certificate, certificateChain) - } + if strings.HasPrefix(privateKeyId, "pkcs11:") { + if Debug { + fmt.Fprintln(os.Stderr, "attempting to use PKCS#11") + } + return GetPKCS11Signer(opts.LibPkcs11, certificate, certificateChain, opts.PrivateKeyId, opts.CertificateId) + } else { + privateKey, err := ReadPrivateKeyData(privateKeyId) + if err != nil { + return nil, "", err + } + + if Debug { + fmt.Fprintln(os.Stderr, "attempting to use FileSystemSigner") + } + return GetFileSystemSigner(privateKey, certificate, certificateChain) + } } // Obtain the date-time, formatted as specified by SigV4 diff --git a/aws_signing_helper/signer_test.go b/aws_signing_helper/signer_test.go index d1282b0..144ac34 100644 --- a/aws_signing_helper/signer_test.go +++ b/aws_signing_helper/signer_test.go @@ -241,7 +241,7 @@ func TestSign(t *testing.T) { } } - + pkcs11_objects := []string{"RSA", "EC"} for _, object := range pkcs11_objects { @@ -264,7 +264,7 @@ func TestSign(t *testing.T) { for _, credOpts := range testTable { signer, _, err := GetSigner(&credOpts) if err != nil { - t.Log(err) + t.Log(err) var logMsg string if credOpts.CertificateId != "" || credOpts.PrivateKeyId != "" { logMsg = fmt.Sprintf("Failed to get signer for '%s'/'%s'", diff --git a/cmd/credentials.go b/cmd/credentials.go index 285da0e..3325853 100644 --- a/cmd/credentials.go +++ b/cmd/credentials.go @@ -67,7 +67,7 @@ func initCredentialsSubCommand(subCmd *cobra.Command) { subCmd.PersistentFlags().StringVar(&certificateBundleId, "intermediates", "", "Path to intermediate certificate bundle file") subCmd.PersistentFlags().StringVar(&certSelector, "cert-selector", "", "JSON structure to identify a certificate from a certificate store. "+ "Can be passed in either as string or a file name (prefixed by \"file://\")") - subCmd.PersistentFlags().StringVar(&libPkcs11, "pkcs11-lib", "", "Library for smart card / cryptographic device (OpenSC or vendor specific)") + subCmd.PersistentFlags().StringVar(&libPkcs11, "pkcs11-lib", "", "Library for smart card / cryptographic device (OpenSC or vendor specific)") subCmd.PersistentFlags().StringVar(&pinPkcs11, "pkcs11-pin", "-", "Pin of the PKCS #11 user for private key access") subCmd.PersistentFlags().UintVar(&slotPkcs11, "pkcs11-slot", 0, "PKCS #11 slot in which to search for the private key (and potentially certificate as well)") diff --git a/cmd/read_certificate_data.go b/cmd/read_certificate_data.go index a2c1ae3..8cfbd66 100644 --- a/cmd/read_certificate_data.go +++ b/cmd/read_certificate_data.go @@ -7,7 +7,7 @@ import ( "fmt" "log" "os" - "strings" + "strings" helper "github.com/aws/rolesanywhere-credential-helper/aws_signing_helper" "github.com/spf13/cobra" @@ -55,7 +55,7 @@ var readCertificateDataCmd = &cobra.Command{ // PrintCertificate interface can be assigned to this variable. var printFunction PrintCertificate = DefaultPrintCertificate - if strings.HasPrefix(certificateId, "pkcs11:") { + if strings.HasPrefix(certificateId, "pkcs11:") { certContainers, err = helper.GetMatchingPKCSCerts(certificateId, libPkcs11) if err != nil { log.Println(err) diff --git a/cmd/sign_string.go b/cmd/sign_string.go index f5ae416..27982cc 100644 --- a/cmd/sign_string.go +++ b/cmd/sign_string.go @@ -78,7 +78,7 @@ func init() { signStringCmd.PersistentFlags().BoolVar(&debug, "debug", false, "To print debug output") signStringCmd.PersistentFlags().StringVar(&certSelector, "cert-selector", "", "JSON structure to identify a certificate from a certificate store. "+ "Can be passed in either as string or a file name (prefixed by \"file://\")") - signStringCmd.PersistentFlags().StringVar(&libPkcs11, "pkcs11-lib", "", "Library for smart card / cryptographic device (default: p11-kit-proxy.{so, dll, dylib})") + signStringCmd.PersistentFlags().StringVar(&libPkcs11, "pkcs11-lib", "", "Library for smart card / cryptographic device (default: p11-kit-proxy.{so, dll, dylib})") signStringCmd.PersistentFlags().Var(format, "format", "Output format. One of json, text, and bin") signStringCmd.PersistentFlags().Var(digestArg, "digest", "One of SHA256, SHA384, and SHA512") } From 3b373bbfd7056c5bf8d1beb9932b984ba7d15320 Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Thu, 3 Aug 2023 15:20:36 -0400 Subject: [PATCH 03/40] Support separate context-specific PIN for PKCS#11 integration Setting the CKA_ALWAYS_AUTHENTICATE attribute on an object still hasn't been tested yet Refactor PIN prompting specifically for PKCS#11 --- aws_signing_helper/pkcs11_signer.go | 219 +++++++++++++++------------- 1 file changed, 119 insertions(+), 100 deletions(-) diff --git a/aws_signing_helper/pkcs11_signer.go b/aws_signing_helper/pkcs11_signer.go index 9bbe089..8daff22 100644 --- a/aws_signing_helper/pkcs11_signer.go +++ b/aws_signing_helper/pkcs11_signer.go @@ -54,24 +54,14 @@ var PKCS11_TEST_VERSION int16 = 1 var MAX_OBJECT_LIMIT int = 1000 type PKCS11Signer struct { - cert *x509.Certificate - certChain []*x509.Certificate - module *pkcs11.Ctx - session pkcs11.SessionHandle - privateKeyHandle pkcs11.ObjectHandle - pin string - keyType uint -} - -// Helper function to check whether the passed in []uint contains a given element -func contains(slice []uint, find uint) bool { - for _, v := range slice { - if v == find { - return true - } - } - - return false + cert *x509.Certificate + certChain []*x509.Certificate + module *pkcs11.Ctx + session pkcs11.SessionHandle + privateKeyHandle pkcs11.ObjectHandle + pin string + contextSpecificPin string + keyType uint } // Used to enumerate slots with all token/slot info for matching @@ -484,7 +474,7 @@ func GetMatchingPKCSCerts(uriStr string, lib string) (matchingCerts []Certificat curUri.AddPathAttribute("type", "cert") curUriStr, err := curUri.Format() if err != nil { - curUriStr = "" + curUriStr = "" // nosemgrep } matchingCerts = append(matchingCerts, CertificateContainer{obj.x509, curUriStr}) } @@ -512,7 +502,7 @@ func (pkcs11Signer *PKCS11Signer) Close() { } // Helper function to sign a digest using a PKCS#11 private key handle -func signHelper(module *pkcs11.Ctx, session pkcs11.SessionHandle, privateKeyHandle pkcs11.ObjectHandle, alwaysAuth uint, pkcs11Pin string, keyType uint, digest []byte, hashFunc crypto.Hash) (signature []byte, err error) { +func signHelper(module *pkcs11.Ctx, session pkcs11.SessionHandle, privateKeyHandle pkcs11.ObjectHandle, alwaysAuth uint, pkcs11Pin string, 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 @@ -532,7 +522,7 @@ func signHelper(module *pkcs11.Ctx, session pkcs11.SessionHandle, privateKeyHand hash := sha512.Sum512(digest) digest = hash[:] default: - return nil, ErrUnsupportedHash + return "", nil, ErrUnsupportedHash } mechanism = pkcs11.CKM_ECDSA } else { @@ -544,40 +534,49 @@ func signHelper(module *pkcs11.Ctx, session pkcs11.SessionHandle, privateKeyHand case crypto.SHA512: mechanism = pkcs11.CKM_SHA512_RSA_PKCS default: - return nil, ErrUnsupportedHash + return "", nil, ErrUnsupportedHash } } err = module.SignInit(session, []*pkcs11.Mechanism{pkcs11.NewMechanism(mechanism, nil)}, privateKeyHandle) if err != nil { - return nil, fmt.Errorf("signing initiation failed (%s)", err.Error()) + return "", nil, fmt.Errorf("signing initiation failed (%s)", err.Error()) } - // We assume it's the same PIN as the token itself? Which was only - // "saved" for us in PKCS11Signer if the CKA_ALWAYS_AUTHENTICATE - // attribute was set (assuming this method is called with the attributes - // of an existing PKCS11Signer object). - if alwaysAuth != 0 && pkcs11Pin != "" { - err = module.Login(session, pkcs11.CKU_CONTEXT_SPECIFIC, pkcs11Pin) + if alwaysAuth != 0 { + if pkcs11Pin != "" { + err = module.Login(session, pkcs11.CKU_CONTEXT_SPECIFIC, pkcs11Pin) + if err == nil { + goto afterContextSpecificLogin + } else { + if Debug { + fmt.Fprintf(os.Stderr, "user re-authentication attempt failed (%s)", err.Error()) + } + } + } + passwordName := "context-specific pin" + finalAuthErrMsg := "user re-authentication failed (%s)" + _, err = pkcs11PasswordPrompt(module, session, pkcs11.CKU_CONTEXT_SPECIFIC, passwordName, finalAuthErrMsg) if err != nil { - return nil, fmt.Errorf("user re-authentication failed (%s)", err.Error()) + return "", nil, err } } +afterContextSpecificLogin: sig, err := module.Sign(session, digest) if err != nil { - return nil, fmt.Errorf("signing failed (%s)", err.Error()) + return pkcs11Pin, 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 nil, err + return pkcs11Pin, nil, err } } - return sig, nil + return pkcs11Pin, sig, nil } // Implements the crypto.Signer interface and signs the passed in digest @@ -586,10 +585,19 @@ func (pkcs11Signer *PKCS11Signer) Sign(rand io.Reader, digest []byte, opts crypt session := pkcs11Signer.session privateKeyHandle := pkcs11Signer.privateKeyHandle keyType := pkcs11Signer.keyType - pin := pkcs11Signer.pin hashFunc := opts.HashFunc() + pin := pkcs11Signer.contextSpecificPin + + // We only care about the context-specific PIN, which (from my understanding) + // can be different from the user PIN. If the context-specific PIN has been + // saved already, use it. Otherwise, default to the user PIN. + if pin == "" { + pin = pkcs11Signer.pin + } - return signHelper(module, session, privateKeyHandle, 0, pin, keyType, digest, hashFunc) + // Save the context-specific PIN, so that we don't need to prompt for it anymore + pkcs11Signer.contextSpecificPin, signature, err = signHelper(module, session, privateKeyHandle, 0, pin, keyType, digest, hashFunc) // nosemgrep + return signature, err } // Gets the x509.Certificate associated with this PKCS11Signer @@ -645,18 +653,8 @@ func (pkcs11Signer *PKCS11Signer) CertificateChain() (chain []*x509.Certificate, return chain, nil } -// Gets the manufacturer ID for the PKCS #11 module -func getManufacturerId(module *pkcs11.Ctx) (string, error) { - info, err := module.GetInfo() - if err != nil { - return "", err - } - - return info.ManufacturerID, nil -} - // Checks whether the private key and certificate are associated with each other -func checkPrivateKeyMatchesCert(module *pkcs11.Ctx, session pkcs11.SessionHandle, keyType uint, alwaysAuth uint, pinPkcs11 string, privateKeyHandle pkcs11.ObjectHandle, certificate *x509.Certificate, manufacturerId string) bool { +func checkPrivateKeyMatchesCert(module *pkcs11.Ctx, session pkcs11.SessionHandle, keyType uint, alwaysAuth uint, pinPkcs11 string, privateKeyHandle pkcs11.ObjectHandle, certificate *x509.Certificate, manufacturerId string) (string, bool) { var digestSuffix []byte publicKey := certificate.PublicKey ecdsaPublicKey, isEcKey := publicKey.(*ecdsa.PublicKey) @@ -664,7 +662,7 @@ func checkPrivateKeyMatchesCert(module *pkcs11.Ctx, session pkcs11.SessionHandle digestSuffixArr := sha256.Sum256(append([]byte("IAM RA"), elliptic.Marshal(ecdsaPublicKey, ecdsaPublicKey.X, ecdsaPublicKey.Y)...)) digestSuffix = digestSuffixArr[:] if keyType != pkcs11.CKK_EC { - return false + return "", false } } @@ -673,7 +671,7 @@ func checkPrivateKeyMatchesCert(module *pkcs11.Ctx, session pkcs11.SessionHandle digestSuffixArr := sha256.Sum256(append([]byte("IAM RA"), x509.MarshalPKCS1PublicKey(rsaPublicKey)...)) digestSuffix = digestSuffixArr[:] if keyType != pkcs11.CKK_RSA { - return false + return "", false } } // "AWS Roles Anywhere Credential Helper PKCS11 Test" || PKCS11_TEST_VERSION || @@ -683,22 +681,22 @@ func checkPrivateKeyMatchesCert(module *pkcs11.Ctx, session pkcs11.SessionHandle digestBytes := []byte(digest) hash := sha256.Sum256(digestBytes) - signature, err := signHelper(module, session, privateKeyHandle, alwaysAuth, pinPkcs11, keyType, digestBytes, crypto.SHA256) + contextSpecificPin, signature, err := signHelper(module, session, privateKeyHandle, alwaysAuth, pinPkcs11, keyType, digestBytes, crypto.SHA256) if err != nil { - return false + return "", false } if isEcKey { valid := ecdsa.VerifyASN1(ecdsaPublicKey, hash[:], signature) - return valid + return contextSpecificPin, valid } if isRsaKey { err := rsa.VerifyPKCS1v15(rsaPublicKey, crypto.SHA256, hash[:], signature) - return err == nil + return contextSpecificPin, err == nil } - return false + return "", false } // This is not my proudest moment. But there's no binary.NativeEndian. @@ -762,7 +760,8 @@ func GetPKCS11Signer(libPkcs11 string, certificate *x509.Certificate, certificat var keyUri *pkcs11uri.Pkcs11URI var slotNr uint var slots []SlotIdInfo - var pinPkcs11 string + var userPin string + var contextSpecificPin string var certObj []CertObjInfo var keyAttributes []*pkcs11.Attribute var keyType uint @@ -782,8 +781,8 @@ func GetPKCS11Signer(libPkcs11 string, certificate *x509.Certificate, certificat if err != nil { goto fail } - pinPkcs11, _ := certUri.GetQueryAttribute("pin-value", false) - slotNr, session, loggedIn, certObj, err = getMatchingCerts(module, slots, certUri, &pinPkcs11, true) + userPin, _ = certUri.GetQueryAttribute("pin-value", false) + slotNr, session, loggedIn, certObj, err = getMatchingCerts(module, slots, certUri, &userPin, true) if err != nil { goto fail } @@ -802,7 +801,7 @@ func GetPKCS11Signer(libPkcs11 string, certificate *x509.Certificate, certificat } else { keyUri = certUri } - pinPkcs11, _ = keyUri.GetQueryAttribute("pin-value", false) + 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. @@ -810,6 +809,7 @@ func GetPKCS11Signer(libPkcs11 string, certificate *x509.Certificate, certificat if len(slots) == 1 { if slotNr != slots[0].id { slotNr = slots[0].id + manufacturerId = slots[0].info.ManufacturerID if session != 0 { if loggedIn { module.Logout(session) @@ -825,6 +825,7 @@ func GetPKCS11Signer(libPkcs11 string, certificate *x509.Certificate, certificat // that. for _, slot := range slots { if slot.id == slotNr { + manufacturerId = slot.info.ManufacturerID goto got_slot } } @@ -842,40 +843,15 @@ got_slot: // And *now* we fall back to prompting the user for a PIN if necessary. if !loggedIn { - if pinPkcs11 == "" { - parseErrMsg := "unable to read PKCS#11 user pin" - prompt := "Please enter your user pin:" - - ttyPath := "/dev/tty" - if runtime.GOOS == "windows" { - ttyPath = "CON" - } - - ttyFile, err := os.OpenFile(ttyPath, os.O_RDWR, 0) + if userPin == "" { + passwordName := "user pin" + finalAuthErrMsg := "user authentication failed (%s)" + _, err = pkcs11PasswordPrompt(module, session, pkcs11.CKU_USER, passwordName, finalAuthErrMsg) if err != nil { goto fail } - defer ttyFile.Close() - - for true { - pinPkcs11, err = GetPassword(ttyFile, prompt, parseErrMsg) - if err != nil && err.Error() == parseErrMsg { - continue - } - - err = module.Login(session, pkcs11.CKU_USER, pinPkcs11) - if err != nil { - // Loop on failure in case the user mistyped their PIN. - if strings.Contains(err.Error(), "CKR_PIN_INCORRECT") { - prompt = "Incorrect user pin. Please re-enter your user pin:" - continue - } - goto fail - } - break - } } else { - err = module.Login(session, pkcs11.CKU_USER, pinPkcs11) + err = module.Login(session, pkcs11.CKU_USER, userPin) if err != nil { goto fail } @@ -905,12 +881,6 @@ retry_search: goto fail } - // Get manufacturer ID once, so that it can be used in the test string to sign when testing candidate private keys - manufacturerId, err = getManufacturerId(module) - if 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. @@ -938,11 +908,22 @@ retry_search: alwaysAuth = 0 } - if certificate == nil || - checkPrivateKeyMatchesCert(module, session, keyType, alwaysAuth, pinPkcs11, curPrivateKeyHandle, certificate, manufacturerId) { + if certificate == nil { privateKeyHandle = curPrivateKeyHandle break } + + curContextSpecificPin := contextSpecificPin + if curContextSpecificPin == "" { + curContextSpecificPin = userPin + } + privateKeyMatchesCert := false + curContextSpecificPin, privateKeyMatchesCert = checkPrivateKeyMatchesCert(module, session, keyType, alwaysAuth, curContextSpecificPin, curPrivateKeyHandle, certificate, manufacturerId) + if privateKeyMatchesCert { + privateKeyHandle = curPrivateKeyHandle + contextSpecificPin = curContextSpecificPin + break + } } if privateKeyHandle == 0 { @@ -976,11 +957,7 @@ retry_search: return nil, "", errors.New("unsupported algorithm") } - if alwaysAuth == 0 { - pinPkcs11 = "" - } - - return &PKCS11Signer{certificate, nil, module, session, privateKeyHandle, pinPkcs11, keyType}, signingAlgorithm, nil + return &PKCS11Signer{certificate, nil, module, session, privateKeyHandle, userPin, contextSpecificPin, keyType}, signingAlgorithm, nil fail: if module != nil { @@ -995,6 +972,48 @@ fail: return nil, "", err } +// Does PIN prompting until the password has been received. +// 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) (string, error) { + var pin string + + parseErrMsg := fmt.Sprintf("unable to read PKCS#11 %s", passwordName) + prompt := fmt.Sprintf("Please enter your %s", passwordName) + + ttyPath := "/dev/tty" + if runtime.GOOS == "windows" { + ttyPath = "CON" + } + + ttyFile, err := os.OpenFile(ttyPath, os.O_RDWR, 0) + if err != nil { + return "", errors.New(parseErrMsg) + } + defer ttyFile.Close() + + for true { + pin, err = GetPassword(ttyFile, 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 + } + + // Code should never reach here + return "", fmt.Errorf("unexpected error when prompting for %s", passwordName) +} + // Prompts the user for their password func GetPassword(ttyFile *os.File, prompt string, parseErrMsg string) (string, error) { fmt.Fprintln(ttyFile, prompt) From 6712ed1af21e33a52b3a225a4e2829c7872fd748 Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Thu, 3 Aug 2023 15:29:28 -0400 Subject: [PATCH 04/40] Ignore semgrep finding --- aws_signing_helper/pkcs11_signer.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aws_signing_helper/pkcs11_signer.go b/aws_signing_helper/pkcs11_signer.go index 8daff22..9bdbe1f 100644 --- a/aws_signing_helper/pkcs11_signer.go +++ b/aws_signing_helper/pkcs11_signer.go @@ -472,9 +472,9 @@ func GetMatchingPKCSCerts(uriStr string, lib string) (matchingCerts []Certificat curUri.AddPathAttribute("object", string(obj.label[:])) } curUri.AddPathAttribute("type", "cert") - curUriStr, err := curUri.Format() + curUriStr, err := curUri.Format() // nosemgrep if err != nil { - curUriStr = "" // nosemgrep + curUriStr = "" } matchingCerts = append(matchingCerts, CertificateContainer{obj.x509, curUriStr}) } From 0abea75af2a859ce4d1fb69326ac2cbeea351a8e Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Sat, 5 Aug 2023 11:15:11 -0400 Subject: [PATCH 05/40] Remove unused function and fix typo in pkcs11_signer.go --- aws_signing_helper/pkcs11_signer.go | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/aws_signing_helper/pkcs11_signer.go b/aws_signing_helper/pkcs11_signer.go index 9bdbe1f..4cd07ab 100644 --- a/aws_signing_helper/pkcs11_signer.go +++ b/aws_signing_helper/pkcs11_signer.go @@ -6,7 +6,7 @@ package aws_signing_helper // 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" librrary, as +// 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. // @@ -167,32 +167,6 @@ fail: return nil, nil, err } -// Opens a session with the PKCS #11 module -func openPKCS11Session(lib string, slot uint, uri *pkcs11uri.Pkcs11URI) (module *pkcs11.Ctx, session pkcs11.SessionHandle, err error) { - - module, _, err = openPKCS11Module(lib) - if err != nil { - goto fail - } - - session, err = module.OpenSession(slot, pkcs11.CKF_SERIAL_SESSION|pkcs11.CKS_RO_PUBLIC_SESSION) - if err != nil { - goto fail - } - - return module, session, nil - -fail: - if module != nil { - if session != 0 { - module.CloseSession(session) - } - module.Finalize() - module.Destroy() - } - return nil, 0, err -} - // Convert the object-related fields in a URI to pkcs11.Attributes for FindObjectsInit() func getFindTemplate(uri *pkcs11uri.Pkcs11URI, class uint) (template []*pkcs11.Attribute) { var v string From 3819b21769ab6fd0ae681a84961bc206c44a4aec Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Thu, 10 Aug 2023 14:23:31 -0400 Subject: [PATCH 06/40] First pass at closing sessions after use --- aws_signing_helper/pkcs11_signer.go | 294 +++++++++++++++++++--------- 1 file changed, 203 insertions(+), 91 deletions(-) diff --git a/aws_signing_helper/pkcs11_signer.go b/aws_signing_helper/pkcs11_signer.go index 4cd07ab..a659094 100644 --- a/aws_signing_helper/pkcs11_signer.go +++ b/aws_signing_helper/pkcs11_signer.go @@ -57,11 +57,10 @@ type PKCS11Signer struct { cert *x509.Certificate certChain []*x509.Certificate module *pkcs11.Ctx - session pkcs11.SessionHandle - privateKeyHandle pkcs11.ObjectHandle - pin string + userPin string contextSpecificPin string - keyType uint + certUri *pkcs11uri.Pkcs11URI + keyUri *pkcs11uri.Pkcs11URI } // Used to enumerate slots with all token/slot info for matching @@ -110,10 +109,36 @@ func matchSlots(slots []SlotIdInfo, uri *pkcs11uri.Pkcs11URI) (matches []SlotIdI return matches } -// Initialize and enumerate slots in the PKCS#11 module -func openPKCS11Module(lib string) (module *pkcs11.Ctx, slots []SlotIdInfo, err error) { - var slotIds []uint +// 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) { + 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 { + continue + } + slotIdInfo.tokInfo, slotErr = module.GetTokenInfo(slotId) + if slotErr != nil { + continue + } + slots = append(slots, slotIdInfo) + } + + return slots, nil +} + +// 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 { @@ -135,26 +160,26 @@ func openPKCS11Module(lib string) (module *pkcs11.Ctx, slots []SlotIdInfo, err e goto fail } - slotIds, err = module.GetSlotList(true) + return module, nil + +fail: + if module != nil { + module.Finalize() + module.Destroy() + } + return nil, err +} + +// Initialize and enumerate slots in the PKCS#11 module. +func openPKCS11Module(lib string) (module *pkcs11.Ctx, slots []SlotIdInfo, err error) { + module, err = initializePKCS11Module(lib) if err != nil { goto fail } - for _, slotId := range slotIds { - var slotIdInfo SlotIdInfo - var slotErr error - - slotIdInfo.id = slotId - slotIdInfo.info, slotErr = module.GetSlotInfo(slotId) - if slotErr != nil { - continue - } - slotIdInfo.tokInfo, slotErr = module.GetTokenInfo(slotId) - if slotErr != nil { - continue - } - - slots = append(slots, slotIdInfo) + slots, err = enumerateSlotsInPKCS11Module(module) + if err != nil { + goto fail } return module, slots, nil @@ -466,10 +491,6 @@ func (pkcs11Signer *PKCS11Signer) Public() crypto.PublicKey { // Closes this PKCS11Signer func (pkcs11Signer *PKCS11Signer) Close() { if module := pkcs11Signer.module; module != nil { - if session := pkcs11Signer.session; session != 0 { - module.Logout(session) - module.CloseSession(session) - } module.Finalize() module.Destroy() } @@ -555,27 +576,49 @@ afterContextSpecificLogin: // 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) { - module := pkcs11Signer.module - session := pkcs11Signer.session - privateKeyHandle := pkcs11Signer.privateKeyHandle - keyType := pkcs11Signer.keyType + var ( + module *pkcs11.Ctx + session pkcs11.SessionHandle + certUri *pkcs11uri.Pkcs11URI + keyUri *pkcs11uri.Pkcs11URI + userPin string + contextSpecificPin string + ) + module = pkcs11Signer.module hashFunc := opts.HashFunc() - pin := pkcs11Signer.contextSpecificPin + userPin = pkcs11Signer.userPin + contextSpecificPin = pkcs11Signer.contextSpecificPin + certUri = pkcs11Signer.certUri + keyUri = pkcs11Signer.keyUri + + _, _, _, session, keyType, privateKeyHandle, err := getPKCS11Key(module, certUri, keyUri, userPin, contextSpecificPin) + if err != nil { + goto cleanup + } // We only care about the context-specific PIN, which (from my understanding) // can be different from the user PIN. If the context-specific PIN has been // saved already, use it. Otherwise, default to the user PIN. - if pin == "" { - pin = pkcs11Signer.pin + if contextSpecificPin == "" { + contextSpecificPin = pkcs11Signer.userPin + } + + _, signature, err = signHelper(module, session, privateKeyHandle, 0, contextSpecificPin, keyType, digest, hashFunc) + if err != nil { + goto cleanup } - // Save the context-specific PIN, so that we don't need to prompt for it anymore - pkcs11Signer.contextSpecificPin, signature, err = signHelper(module, session, privateKeyHandle, 0, pin, keyType, digest, hashFunc) // nosemgrep +cleanup: + if session != 0 { + module.Logout(session) + module.CloseSession(session) + } return signature, err } // Gets the x509.Certificate associated with this PKCS11Signer func (pkcs11Signer *PKCS11Signer) Certificate() (*x509.Certificate, error) { + // For now, this is guaranteed to be set when the signer is being created return pkcs11Signer.cert, nil } @@ -594,13 +637,35 @@ func certIssues(issuer *x509.Certificate, candidate *x509.Certificate) bool { // Gets the certificate chain associated with this PKCS11Signer func (pkcs11Signer *PKCS11Signer) CertificateChain() (chain []*x509.Certificate, err error) { - module := pkcs11Signer.module - session := pkcs11Signer.session + var ( + module *pkcs11.Ctx + session pkcs11.SessionHandle + certUri *pkcs11uri.Pkcs11URI + keyUri *pkcs11uri.Pkcs11URI + userPin string + contextSpecificPin string + certChain []*x509.Certificate + certsFound []CertObjInfo + ) + + certChain = pkcs11Signer.certChain + if certChain != nil { + return certChain, nil + } + + module = pkcs11Signer.module + certUri = pkcs11Signer.certUri + keyUri = pkcs11Signer.keyUri + userPin = pkcs11Signer.userPin + contextSpecificPin = pkcs11Signer.contextSpecificPin + + userPin, contextSpecificPin, _, session, _, _, err = getPKCS11Key(module, certUri, keyUri, userPin, contextSpecificPin) + chain = append(chain, pkcs11Signer.cert) - certsFound, err := getCertsInSession(module, 0, session, nil) + certsFound, err = getCertsInSession(module, 0, session, nil) if err != nil { - return nil, err + goto cleanup } for true { @@ -624,7 +689,15 @@ func (pkcs11Signer *PKCS11Signer) CertificateChain() (chain []*x509.Certificate, } } - return chain, nil + pkcs11Signer.certChain = chain + +cleanup: + if session != 0 { + module.Logout(session) + module.CloseSession(session) + } + + return chain, err } // Checks whether the private key and certificate are associated with each other @@ -719,43 +792,33 @@ func escapeAll(s []byte) string { 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, certificate *x509.Certificate, certificateChain []*x509.Certificate, privateKeyId string, certificateId string) (signer Signer, signingAlgorithm string, err error) { - var templatePrivateKey []*pkcs11.Attribute - var sessionPrivateKeyObjects []pkcs11.ObjectHandle - var privateKeyHandle pkcs11.ObjectHandle - var module *pkcs11.Ctx - var session pkcs11.SessionHandle - var manufacturerId string - var certUri *pkcs11uri.Pkcs11URI - var keyUri *pkcs11uri.Pkcs11URI - var slotNr uint - var slots []SlotIdInfo - var userPin string - var contextSpecificPin string - var certObj []CertObjInfo - var keyAttributes []*pkcs11.Attribute - var keyType uint - var alwaysAuth uint - var privateKeyObjects []pkcs11.ObjectHandle - var loggedIn bool - - module, slots, err = openPKCS11Module(libPkcs11) +// Gets the key type and a handle to the private key within the PKCS#11 token. +// This method assumes that module isn't nil and also has been initialized. +func getPKCS11Key(module *pkcs11.Ctx, certUri *pkcs11uri.Pkcs11URI, keyUri *pkcs11uri.Pkcs11URI, _userPin string, _contextSpecificPin string) (userPin string, contextSpecificPin string, certificate *x509.Certificate, session pkcs11.SessionHandle, keyType uint, privateKeyHandle pkcs11.ObjectHandle, err error) { + var ( + templatePrivateKey []*pkcs11.Attribute + sessionPrivateKeyObjects []pkcs11.ObjectHandle + manufacturerId string + slotNr uint + slots []SlotIdInfo + certObj []CertObjInfo + keyAttributes []*pkcs11.Attribute + alwaysAuth uint + privateKeyObjects []pkcs11.ObjectHandle + loggedIn bool + ) + + slots, err = enumerateSlotsInPKCS11Module(module) if err != nil { goto fail } // If a PKCS#11 URI was provided for the certificate, find it. - if certificate == nil && certificateId != "" { - certUri = pkcs11uri.New() - err = certUri.Parse(certificateId) - if err != nil { - goto fail - } + if certUri != nil { userPin, _ = certUri.GetQueryAttribute("pin-value", false) + if userPin == "" && _userPin != "" { + userPin = _userPin + } slotNr, session, loggedIn, certObj, err = getMatchingCerts(module, slots, certUri, &userPin, true) if err != nil { goto fail @@ -766,18 +829,15 @@ func GetPKCS11Signer(libPkcs11 string, certificate *x509.Certificate, certificat // If no 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 { + if keyUri == nil { keyUri = certUri } userPin, _ = keyUri.GetQueryAttribute("pin-value", false) + if userPin == "" && _userPin != "" { + userPin = _userPin + } - // This time we're looking for a *single* slot, as we (presumably) + // 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 { @@ -803,7 +863,7 @@ func GetPKCS11Signer(libPkcs11 string, certificate *x509.Certificate, certificat goto got_slot } } - err = errors.New("Could not identify unique slot for PKCS#11 key") + err = errors.New("could not identify unique slot for PKCS#11 key") goto fail } @@ -887,7 +947,7 @@ retry_search: break } - curContextSpecificPin := contextSpecificPin + curContextSpecificPin := _contextSpecificPin if curContextSpecificPin == "" { curContextSpecificPin = userPin } @@ -908,10 +968,9 @@ retry_search: * * http://david.woodhou.se/draft-woodhouse-cert-best-practice.html#rfc.section.8.2 */ - if (privateKeyId == "" || privateKeyId == certificateId) && - certificate != nil && certObj[0].id != nil { - _, key_had_label := keyUri.GetPathAttribute("object", false) - if key_had_label { + if keyUri == certUri && certificate != nil && certObj[0].id != nil { + _, keyHadLabel := keyUri.GetPathAttribute("object", false) + if keyHadLabel { keyUri.RemovePathAttribute("object") keyUri.SetPathAttribute("id", escapeAll(certObj[0].id)) goto retry_search @@ -922,6 +981,59 @@ retry_search: goto fail } + return userPin, contextSpecificPin, certificate, session, keyType, privateKeyHandle, nil + +fail: + if session != 0 { + module.Logout(session) + module.CloseSession(session) + } + + return "", "", nil, 0, 0, 0, err +} + +// 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, certificate *x509.Certificate, certificateChain []*x509.Certificate, privateKeyId string, certificateId string) (signer Signer, signingAlgorithm string, err error) { + var ( + module *pkcs11.Ctx + certUri *pkcs11uri.Pkcs11URI + keyUri *pkcs11uri.Pkcs11URI + certInToken *x509.Certificate + keyType uint + userPin string + contextSpecificPin string + session pkcs11.SessionHandle + ) + + module, err = initializePKCS11Module(libPkcs11) + if err != nil { + goto fail + } + + if certificate == nil && certificateId != "" { + certUri = pkcs11uri.New() + err = certUri.Parse(certificateId) + if err != nil { + goto fail + } + } + if privateKeyId != "" { + keyUri = pkcs11uri.New() + err = keyUri.Parse(privateKeyId) + if err != nil { + goto fail + } + } + + userPin, contextSpecificPin, certInToken, session, keyType, _, err = getPKCS11Key(module, certUri, keyUri, "", "") + + if certificate == nil { + certificate = certInToken + } + switch keyType { case pkcs11.CKK_EC: signingAlgorithm = aws4_x509_ecdsa_sha256 @@ -931,14 +1043,14 @@ retry_search: return nil, "", errors.New("unsupported algorithm") } - return &PKCS11Signer{certificate, nil, module, session, privateKeyHandle, userPin, contextSpecificPin, keyType}, signingAlgorithm, nil + if session != 0 { + module.Logout(session) + module.CloseSession(session) + } + return &PKCS11Signer{certificate, nil, module, userPin, contextSpecificPin, certUri, keyUri}, signingAlgorithm, nil fail: if module != nil { - if session != 0 { - module.Logout(session) - module.CloseSession(session) - } module.Finalize() module.Destroy() } From e38362590bea10d9300198c5579cbfb5f33a2e65 Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Thu, 10 Aug 2023 15:31:19 -0400 Subject: [PATCH 07/40] Fix empty credential output when TA and Profile ARN regions don't match --- aws_signing_helper/credentials.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws_signing_helper/credentials.go b/aws_signing_helper/credentials.go index be89516..d8929c7 100644 --- a/aws_signing_helper/credentials.go +++ b/aws_signing_helper/credentials.go @@ -45,7 +45,7 @@ func GenerateCredentials(opts *CredentialsOpts, signer Signer, signatureAlgorith } if trustAnchorArn.Region != profileArn.Region { - return CredentialProcessOutput{}, err + return CredentialProcessOutput{}, errors.New("trust anchor and profile regions don't match") } if opts.Region == "" { From aa0f6e8667cf688c9d67a9af7f23a874c19cb672 Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Sun, 13 Aug 2023 12:07:57 -0400 Subject: [PATCH 08/40] Save session and handle information by default * Sometimes, there are successive operations that a user may want to perform with a PKCS11Signer, each of which require an open session. In this case, it would be better to leave the session open until each of those operations have completed and only then clean things up. * By default, save session and handle information. Introduce CloseSession() method to aws_signing_helper.Signer interface if partial-cleanup has to be done after successive operations. * Don't save token information within CertObjInfo. The information isn't specific to individual certificate objects in PKCS#11. --- aws_signing_helper/credentials.go | 7 +- .../darwin_cert_store_signer.go | 3 + aws_signing_helper/file_system_signer.go | 6 +- aws_signing_helper/pkcs11_signer.go | 1062 ++++++++++------- aws_signing_helper/signer.go | 1 + .../windows_cert_store_signer.go | 3 + 6 files changed, 629 insertions(+), 453 deletions(-) diff --git a/aws_signing_helper/credentials.go b/aws_signing_helper/credentials.go index d8929c7..109283c 100644 --- a/aws_signing_helper/credentials.go +++ b/aws_signing_helper/credentials.go @@ -34,7 +34,12 @@ type CredentialsOpts struct { // Function to create session and generate credentials func GenerateCredentials(opts *CredentialsOpts, signer Signer, signatureAlgorithm string) (CredentialProcessOutput, error) { - // assign values to region and endpoint if they haven't already been assigned + // If there are resources that need to be released after using the Signer + // to perform a series of operations that are required in order to get + // temporary credentials, that is done here. + defer signer.CloseSession() + + // Assign values to region and endpoint if they haven't already been assigned trustAnchorArn, err := arn.Parse(opts.TrustAnchorArnStr) if err != nil { return CredentialProcessOutput{}, err diff --git a/aws_signing_helper/darwin_cert_store_signer.go b/aws_signing_helper/darwin_cert_store_signer.go index 7c9641d..094f185 100644 --- a/aws_signing_helper/darwin_cert_store_signer.go +++ b/aws_signing_helper/darwin_cert_store_signer.go @@ -263,6 +263,9 @@ func (signer *DarwinCertStoreSigner) Close() { } } +// Unused method for DarwinCertStoreSigner. +func (signer *DarwinCertStoreSigner) CloseSession() {} + // Sign implements the crypto.Signer interface and signs the digest func (signer *DarwinCertStoreSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { var hash []byte diff --git a/aws_signing_helper/file_system_signer.go b/aws_signing_helper/file_system_signer.go index e793543..c4389ce 100644 --- a/aws_signing_helper/file_system_signer.go +++ b/aws_signing_helper/file_system_signer.go @@ -36,8 +36,10 @@ func (fileSystemSigner FileSystemSigner) Public() crypto.PublicKey { return nil } -func (fileSystemSigner FileSystemSigner) Close() { -} +func (fileSystemSigner FileSystemSigner) Close() {} + +// Unused method for FileSystemSigner. +func (fileSystemSigner FileSystemSigner) CloseSession() {} func (fileSystemSigner FileSystemSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) { var hash []byte diff --git a/aws_signing_helper/pkcs11_signer.go b/aws_signing_helper/pkcs11_signer.go index a659094..63f6053 100644 --- a/aws_signing_helper/pkcs11_signer.go +++ b/aws_signing_helper/pkcs11_signer.go @@ -53,66 +53,76 @@ import ( var PKCS11_TEST_VERSION int16 = 1 var MAX_OBJECT_LIMIT int = 1000 -type PKCS11Signer struct { - cert *x509.Certificate - certChain []*x509.Certificate - module *pkcs11.Ctx - userPin string - contextSpecificPin string - certUri *pkcs11uri.Pkcs11URI - keyUri *pkcs11uri.Pkcs11URI +// 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 } -// Used to enumerate slots with all token/slot info for matching +// Used to enumerate slots with all token/slot info for matching. type SlotIdInfo struct { id uint info pkcs11.SlotInfo tokInfo pkcs11.TokenInfo } -// Return true if the URI specifies the attribute and it *doesn't* match -func mismatchAttr(uri *pkcs11uri.Pkcs11URI, attr string, val string) bool { - uriVal, ok := uri.GetPathAttribute(attr, false) - return ok && uriVal != val +type PKCS11Signer struct { + certObj CertObjInfo + certChain []*x509.Certificate + module *pkcs11.Ctx + userPin string + alwaysAuth uint + contextSpecificPin string + certUri *pkcs11uri.Pkcs11URI + keyUri *pkcs11uri.Pkcs11URI + session pkcs11.SessionHandle + keyType uint + privateKeyHandle pkcs11.ObjectHandle + loggedIn bool + certSlotNr uint + slots []SlotIdInfo } -// Return the set of slots which match the given uri -func matchSlots(slots []SlotIdInfo, uri *pkcs11uri.Pkcs11URI) (matches []SlotIdInfo) { - if uri == nil { - return slots +// 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" + } } - var uriSlotNr uint64 - var uriSlot string - var ok bool - - uriSlot, ok = uri.GetPathAttribute("slot-id", false) - if ok { - uriSlotNr, _ = strconv.ParseUint(uriSlot, 0, 32) + module = pkcs11.New(lib) + if module == nil { + err = errors.New("Failed to load provider library " + lib) + goto fail } - - 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) + if err = module.Initialize(); err != nil { + goto fail } - return matches + 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) { - slotIds, err := module.GetSlotList(true) + var slotIds []uint + + slotIds, err = module.GetSlotList(true) if err != nil { return nil, err } @@ -137,65 +147,58 @@ func enumerateSlotsInPKCS11Module(module *pkcs11.Ctx) (slots []SlotIdInfo, err e return slots, nil } -// 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" - } - } +// 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 + ) - 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 - } + uriVal, ok = uri.GetPathAttribute(attr, false) + return ok && uriVal != val +} - return module, nil +// 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 + ) -fail: - if module != nil { - module.Finalize() - module.Destroy() + if uri == nil { + return slots } - return nil, err -} -// Initialize and enumerate slots in the PKCS#11 module. -func openPKCS11Module(lib string) (module *pkcs11.Ctx, slots []SlotIdInfo, err error) { - module, err = initializePKCS11Module(lib) - if err != nil { - goto fail + uriSlot, ok = uri.GetPathAttribute("slot-id", false) + if ok { + uriSlotNr, _ = strconv.ParseUint(uriSlot, 0, 32) } - slots, err = enumerateSlotsInPKCS11Module(module) - if err != nil { - goto fail + 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 module, slots, nil - -fail: - if module != nil { - module.Finalize() - module.Destroy() - } - return nil, nil, err + return matches } -// Convert the object-related fields in a URI to pkcs11.Attributes for FindObjectsInit() +// 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 - var ok bool + var ( + v string + ok bool + ) template = append(template, pkcs11.NewAttribute(pkcs11.CKA_CLASS, class)) @@ -214,23 +217,14 @@ func getFindTemplate(uri *pkcs11uri.Pkcs11URI, class uint) (template []*pkcs11.A return template } -// In our list of certs we want to remember the CKA_ID/CKA_LABEL too -type CertObjInfo struct { - id []byte - label []byte - x509 *x509.Certificate - model string - manufacturerId string - serial string -} - // Gets certificate(s) within the PKCS#11 session (i.e. a given token) that -// match the given URI +// 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 - var certObjects []pkcs11.ObjectHandle - var templateCrt []*pkcs11.Attribute - var tokenInfo *pkcs11.TokenInfo + var ( + sessionCertObjects []pkcs11.ObjectHandle + certObjects []pkcs11.ObjectHandle + templateCrt []*pkcs11.Attribute + ) // Convert the URI into a template for FindObjectsInit() templateCrt = getFindTemplate(uri, pkcs11.CKO_CERTIFICATE) @@ -258,11 +252,6 @@ func getCertsInSession(module *pkcs11.Ctx, slotId uint, session pkcs11.SessionHa return nil, err } - if slotId != 0 { - tokenInfoTmp, _ := module.GetTokenInfo(slotId) - tokenInfo = &tokenInfoTmp - } - for _, certObject := range certObjects { crtAttributes := []*pkcs11.Attribute{ pkcs11.NewAttribute(pkcs11.CKA_VALUE, 0), @@ -275,7 +264,7 @@ func getCertsInSession(module *pkcs11.Ctx, slotId uint, session pkcs11.SessionHa var certObj CertObjInfo - certObj.x509, err = x509.ParseCertificate(rawCert) // nosemgrep + certObj.cert, err = x509.ParseCertificate(rawCert) // nosemgrep if err != nil { return nil, errors.New("error parsing certificate") } @@ -295,12 +284,6 @@ func getCertsInSession(module *pkcs11.Ctx, slotId uint, session pkcs11.SessionHa certObj.label = crtAttributes[0].Value } - if tokenInfo != nil { - certObj.serial = tokenInfo.SerialNumber - certObj.manufacturerId = tokenInfo.ManufacturerID - certObj.model = tokenInfo.Model - } - certs = append(certs, certObj) } @@ -308,10 +291,14 @@ func getCertsInSession(module *pkcs11.Ctx, slotId uint, session pkcs11.SessionHa } // 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 into 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, pinPkcs11 *string, single bool) (slotNr uint, session pkcs11.SessionHandle, loggedIn bool, matchingCerts []CertObjInfo, err error) { +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) { if uri != nil { slots = matchSlots(slots, uri) } @@ -334,8 +321,8 @@ func getMatchingCerts(module *pkcs11.Ctx, slots []SlotIdInfo, uri *pkcs11uri.Pkc matchingCerts = append(matchingCerts, curMatchingCerts...) // We only care about this value when there is a single matching // certificate found. - if slotNr == 0 { - slotNr = slot.id + if matchedSlot == (SlotIdInfo{}) { + matchedSlot = slot session = curSession goto skip } @@ -344,19 +331,8 @@ func getMatchingCerts(module *pkcs11.Ctx, slots []SlotIdInfo, uri *pkcs11uri.Pkc skip: } - if single && len(matchingCerts) > 1 { - err = errors.New("multiple matching certificates") - goto fail - } - - if len(matchingCerts) > 1 { - return slotNr, session, false, matchingCerts, nil - } - - // If there is exactly one matching certificate found without logging in - // to any of the tokens, then return that one. - if single && len(matchingCerts) == 1 { - return slotNr, session, false, matchingCerts, nil + if len(matchingCerts) >= 1 { + goto foundCert } // http://david.woodhou.se/draft-woodhouse-cert-best-practice.html#rfc.section.8.1 @@ -369,7 +345,7 @@ func getMatchingCerts(module *pkcs11.Ctx, slots []SlotIdInfo, uri *pkcs11uri.Pkc // 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 && *pinPkcs11 != "" { + if len(slots) == 1 && userPin != "" { errNoMatchingCerts := errors.New("no matching certificates") curSession, err := module.OpenSession(slots[0].id, pkcs11.CKF_SERIAL_SESSION|pkcs11.CKS_RO_PUBLIC_SESSION) @@ -378,7 +354,7 @@ func getMatchingCerts(module *pkcs11.Ctx, slots []SlotIdInfo, uri *pkcs11uri.Pkc goto fail } - err = module.Login(curSession, pkcs11.CKU_USER, *pinPkcs11) + err = module.Login(curSession, pkcs11.CKU_USER, userPin) if err != nil { err = errNoMatchingCerts goto fail @@ -390,7 +366,8 @@ func getMatchingCerts(module *pkcs11.Ctx, slots []SlotIdInfo, uri *pkcs11uri.Pkc // We only care about this value when there is a single matching // certificate found. if session == 0 { - slotNr = slots[0].id + loggedIn = true + matchedSlot = slots[0] session = curSession goto foundCert } @@ -404,30 +381,35 @@ func getMatchingCerts(module *pkcs11.Ctx, slots []SlotIdInfo, uri *pkcs11uri.Pkc goto fail foundCert: - if single && len(matchingCerts) > 0 { + if single && len(matchingCerts) > 1 { err = errors.New("multiple matching certificates") goto fail } // Exactly one matching certificate after logging into the appropriate token - // iff single is true (otherwise there can be multiple matching certificates) - return slotNr, session, true, matchingCerts, nil + // 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 0, session, false, nil, err + 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 - var module *pkcs11.Ctx - var uri *pkcs11uri.Pkcs11URI - var pin string - var certObjs []CertObjInfo + 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) @@ -435,35 +417,30 @@ func GetMatchingPKCSCerts(uriStr string, lib string) (matchingCerts []Certificat return nil, err } - pin, _ = uri.GetQueryAttribute("pin-value", false) + userPin, _ = uri.GetQueryAttribute("pin-value", false) - module, slots, err = openPKCS11Module(lib) + module, err = initializePKCS11Module(lib) if err != nil { - return nil, err + goto cleanUp } - _, session, loggedIn, certObjs, err := getMatchingCerts(module, slots, uri, &pin, false) + slots, err = enumerateSlotsInPKCS11Module(module) if err != nil { - // Session has been closed and module has been destroyed already in this case - return nil, err + goto cleanUp } - if module != nil { - if session != 0 { - if loggedIn { - module.Logout(session) - } - module.CloseSession(session) - } - module.Finalize() - module.Destroy() + 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", obj.model) - curUri.AddPathAttribute("manufacturer", obj.manufacturerId) - curUri.AddPathAttribute("serial", obj.serial) + 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[:])) } @@ -475,28 +452,110 @@ func GetMatchingPKCSCerts(uriStr string, lib string) (matchingCerts []Certificat if err != nil { curUriStr = "" } - matchingCerts = append(matchingCerts, CertificateContainer{obj.x509, curUriStr}) + matchingCerts = append(matchingCerts, CertificateContainer{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 +// Returns the public key associated with this PKCS11Signer. func (pkcs11Signer *PKCS11Signer) Public() crypto.PublicKey { - if pkcs11Signer.cert != nil { - return pkcs11Signer.cert.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 +// Closes this PKCS11Signer. func (pkcs11Signer *PKCS11Signer) Close() { - if module := pkcs11Signer.module; module != nil { + var ( + module *pkcs11.Ctx + session pkcs11.SessionHandle + loggedIn bool + ) + + module = pkcs11Signer.module + session = pkcs11Signer.session + loggedIn = pkcs11Signer.loggedIn + + if module != nil { + if session != 0 { + if loggedIn { + module.Logout(session) + } + module.CloseSession(session) + } module.Finalize() module.Destroy() } + + pkcs11Signer.session = 0 + pkcs11Signer.certSlotNr = 0 + pkcs11Signer.privateKeyHandle = 0 + pkcs11Signer.alwaysAuth = 0 + pkcs11Signer.loggedIn = false + pkcs11Signer.slots = nil + pkcs11Signer.module = nil +} + +// Sometimes, it may be preferable to leave sessions open since there are +// multiple functions that the PKCS11Signer has to perform that require +// sessions to be left open. After each of those functions are run, +// CloseSession can be called. +func (pkcs11Signer *PKCS11Signer) CloseSession() { + var ( + module *pkcs11.Ctx + session pkcs11.SessionHandle + loggedIn bool + ) + + module = pkcs11Signer.module + session = pkcs11Signer.session + + if module != nil { + if session != 0 { + if loggedIn { + module.Logout(session) + } + module.CloseSession(session) + } + } + + pkcs11Signer.session = 0 + pkcs11Signer.certSlotNr = 0 + pkcs11Signer.privateKeyHandle = 0 + pkcs11Signer.alwaysAuth = 0 + pkcs11Signer.loggedIn = false + pkcs11Signer.slots = nil } -// Helper function to sign a digest using a PKCS#11 private key handle +// Helper function to sign a digest using a PKCS#11 private key handle. func signHelper(module *pkcs11.Ctx, session pkcs11.SessionHandle, privateKeyHandle pkcs11.ObjectHandle, alwaysAuth uint, pkcs11Pin string, 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. @@ -574,55 +633,327 @@ afterContextSpecificLogin: return pkcs11Pin, sig, 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) { +func getPKCS11Key(module *pkcs11.Ctx, session pkcs11.SessionHandle, loggedIn bool, certUri *pkcs11uri.Pkcs11URI, keyUri *pkcs11uri.Pkcs11URI, certSlotNr uint, certObj CertObjInfo, userPin string, contextSpecificPin string, slots []SlotIdInfo) (_session pkcs11.SessionHandle, keyType uint, privateKeyHandle pkcs11.ObjectHandle, alwaysAuth uint, _contextSpecificPin string, err error) { var ( - module *pkcs11.Ctx - session pkcs11.SessionHandle - certUri *pkcs11uri.Pkcs11URI - keyUri *pkcs11uri.Pkcs11URI - userPin string - contextSpecificPin string + keySlotNr uint + manufacturerId string + templatePrivateKey []*pkcs11.Attribute + privateKeyObjects []pkcs11.ObjectHandle + keyAttributes []*pkcs11.Attribute ) - module = pkcs11Signer.module - hashFunc := opts.HashFunc() - userPin = pkcs11Signer.userPin - contextSpecificPin = pkcs11Signer.contextSpecificPin - certUri = pkcs11Signer.certUri - keyUri = pkcs11Signer.keyUri - - _, _, _, session, keyType, privateKeyHandle, err := getPKCS11Key(module, certUri, keyUri, userPin, contextSpecificPin) - if err != nil { - goto cleanup - } - - // We only care about the context-specific PIN, which (from my understanding) - // can be different from the user PIN. If the context-specific PIN has been - // saved already, use it. Otherwise, default to the user PIN. - if contextSpecificPin == "" { - contextSpecificPin = pkcs11Signer.userPin - } - - _, signature, err = signHelper(module, session, privateKeyHandle, 0, contextSpecificPin, keyType, digest, hashFunc) - if err != nil { - goto cleanup - } -cleanup: - if session != 0 { - module.Logout(session) - module.CloseSession(session) + if keyUri == nil { + keyUri = certUri } - return signature, err -} - -// Gets the x509.Certificate associated with this PKCS11Signer -func (pkcs11Signer *PKCS11Signer) Certificate() (*x509.Certificate, error) { - // For now, this is guaranteed to be set when the signer is being created - return pkcs11Signer.cert, nil -} + userPin, _ = keyUri.GetQueryAttribute("pin-value", false) -// Checks whether the first certificate issues the second + // 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 { + keySlotNr = slots[0].id + manufacturerId = slots[0].info.ManufacturerID + if session != 0 { + if loggedIn { + module.Logout(session) + module.CloseSession(session) + } + } + loggedIn = false + session = 0 + } + } else { + // If the URI matched multiple slots *but* one of them is the + // one (certSlot.id) that the certificate was found in, then use + // that. + for _, slot := range slots { + if certSlotNr == slot.id { + keySlotNr = slot.id + 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(keySlotNr, 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)" + _, 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 { + // Find the signing algorithm + 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[0] = 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 + } + + if certObj.cert == nil { + privateKeyHandle = curPrivateKeyHandle + break + } + + curContextSpecificPin := contextSpecificPin + if curContextSpecificPin == "" { + curContextSpecificPin = userPin + } + privateKeyMatchesCert := false + curContextSpecificPin, privateKeyMatchesCert = checkPrivateKeyMatchesCert(module, session, keyType, alwaysAuth, curContextSpecificPin, curPrivateKeyHandle, certObj.cert, manufacturerId) + if privateKeyMatchesCert { + privateKeyHandle = curPrivateKeyHandle + contextSpecificPin = curContextSpecificPin + break + } + } + + if privateKeyHandle == 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 + */ + keyUriStr, _ := keyUri.Format() + certUriStr, _ := certUri.Format() + if certObj.cert != nil { + if keyUriStr == certUriStr { + _, keyHadLabel := keyUri.GetPathAttribute("object", false) + if keyHadLabel { + keyUri.RemovePathAttribute("object") + keyUri.SetPathAttribute("id", escapeAll(certObj.id)) + goto retry_search + } + } + } + + err = errors.New("unable to find matching private key") + goto fail + } + + return session, keyType, privateKeyHandle, alwaysAuth, contextSpecificPin, nil + +fail: + return 0, 0, 0, 0, "", err +} + +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 + privateKeyHandle pkcs11.ObjectHandle + keyType uint + certSlotNr uint + certObj CertObjInfo + slots []SlotIdInfo + loggedIn bool + alwaysAuth uint + certSlot SlotIdInfo + ) + + hashFunc := opts.HashFunc() + + // The module is the only one that's guaranteed to be initialized properly + module = pkcs11Signer.module + userPin = pkcs11Signer.userPin + contextSpecificPin = pkcs11Signer.contextSpecificPin + certUri = pkcs11Signer.certUri + keyUri = pkcs11Signer.keyUri + privateKeyHandle = pkcs11Signer.privateKeyHandle + keyType = pkcs11Signer.keyType + certObj = pkcs11Signer.certObj + session = pkcs11Signer.session + loggedIn = pkcs11Signer.loggedIn + slots = pkcs11Signer.slots + alwaysAuth = pkcs11Signer.alwaysAuth + + if privateKeyHandle != 0 { + goto gotPrivateKey + } + + if certSlotNr != 0 { + goto gotCert + } + + if certUri != nil { + certSlot, slots, session, loggedIn, certObj, err = getCertificate(module, certUri, userPin) + if err != nil { + goto fail + } + + pkcs11Signer.slots = slots + pkcs11Signer.session = session + pkcs11Signer.loggedIn = loggedIn + pkcs11Signer.certSlotNr = certSlot.id + pkcs11Signer.certObj = certObj + } else { + pkcs11Signer.slots, err = enumerateSlotsInPKCS11Module(module) + if err != nil { + goto fail + } + } + slots = pkcs11Signer.slots + certObj = pkcs11Signer.certObj + loggedIn = pkcs11Signer.loggedIn + certSlotNr = pkcs11Signer.certSlotNr + +gotCert: + session, keyType, privateKeyHandle, alwaysAuth, contextSpecificPin, err = getPKCS11Key(module, session, loggedIn, certUri, keyUri, certSlotNr, certObj, userPin, contextSpecificPin, slots) + if err != nil { + goto fail + } + + // Save the values we need after finding the key. + pkcs11Signer.session = session + pkcs11Signer.keyType = keyType + pkcs11Signer.privateKeyHandle = privateKeyHandle + pkcs11Signer.alwaysAuth = alwaysAuth + pkcs11Signer.contextSpecificPin = contextSpecificPin + pkcs11Signer.loggedIn = true + +gotPrivateKey: + // We only care about the context-specific PIN when it comes to signing with + // objects that are marked with CKA_ALWAYS_AUTHENTICATE, which (from my + // understanding) can be different from the user PIN. If the context-specific + // PIN has been saved already, use it. Otherwise, default to the user PIN. + if contextSpecificPin == "" { + contextSpecificPin = pkcs11Signer.userPin + } + + _, signature, err = signHelper(module, session, privateKeyHandle, alwaysAuth, contextSpecificPin, keyType, digest, hashFunc) + if err != nil { + goto fail + } + +fail: + return signature, err +} + +// Gets the *x509.Certificate associated with this PKCS11Signer +func (pkcs11Signer *PKCS11Signer) Certificate() (certificate *x509.Certificate, err error) { + var ( + module *pkcs11.Ctx + userPin string + loggedIn bool + certSlot SlotIdInfo + certObj CertObjInfo + session pkcs11.SessionHandle + certUri *pkcs11uri.Pkcs11URI + cert *x509.Certificate + slots []SlotIdInfo + ) + + module = pkcs11Signer.module + cert = pkcs11Signer.certObj.cert + + if cert != nil { + return cert, nil + } + + // Otherwise, get the certificate. + certSlot, slots, session, loggedIn, certObj, err = getCertificate(module, certUri, userPin) + if err != nil { + return nil, err + } + + pkcs11Signer.slots = slots + pkcs11Signer.session = session + pkcs11Signer.loggedIn = loggedIn + pkcs11Signer.certSlotNr = certSlot.id + pkcs11Signer.certObj = certObj + + return pkcs11Signer.certObj.cert, nil +} + +// Checks whether the first certificate issues the second func certIssues(issuer *x509.Certificate, candidate *x509.Certificate) bool { roots := x509.NewCertPool() roots.AddCert(issuer) @@ -638,43 +969,48 @@ func certIssues(issuer *x509.Certificate, candidate *x509.Certificate) bool { // Gets the certificate chain associated with this PKCS11Signer func (pkcs11Signer *PKCS11Signer) CertificateChain() (chain []*x509.Certificate, err error) { var ( - module *pkcs11.Ctx - session pkcs11.SessionHandle - certUri *pkcs11uri.Pkcs11URI - keyUri *pkcs11uri.Pkcs11URI - userPin string - contextSpecificPin string - certChain []*x509.Certificate - certsFound []CertObjInfo + module *pkcs11.Ctx + session pkcs11.SessionHandle + certChain []*x509.Certificate + certsFound []CertObjInfo + cert *x509.Certificate + certUri *pkcs11uri.Pkcs11URI ) + module = pkcs11Signer.module certChain = pkcs11Signer.certChain + certUri = pkcs11Signer.certUri + if certChain != nil { return certChain, nil } - module = pkcs11Signer.module - certUri = pkcs11Signer.certUri - keyUri = pkcs11Signer.keyUri - userPin = pkcs11Signer.userPin - contextSpecificPin = pkcs11Signer.contextSpecificPin + if certUri == nil { + return nil, errors.New("signer created using only certificate; " + + "unable to get certificate chain") + } - userPin, contextSpecificPin, _, session, _, _, err = getPKCS11Key(module, certUri, keyUri, userPin, contextSpecificPin) + // If there is currently no open session, then this method will open it. + cert, err = pkcs11Signer.Certificate() + if err != nil { + return nil, err + } - chain = append(chain, pkcs11Signer.cert) + chain = append(chain, cert) + session = pkcs11Signer.session certsFound, err = getCertsInSession(module, 0, session, nil) if err != nil { - goto cleanup + return nil, err } for true { nextInChainFound := false for i, curCert := range certsFound { curLastCert := chain[len(chain)-1] - if certIssues(curLastCert, curCert.x509) { + if certIssues(curLastCert, curCert.cert) { nextInChainFound = true - chain = append(chain, curCert.x509) + chain = append(chain, curCert.cert) // Remove current cert, so that it won't be iterated again lastIndex := len(certsFound) - 1 @@ -691,12 +1027,6 @@ func (pkcs11Signer *PKCS11Signer) CertificateChain() (chain []*x509.Certificate, pkcs11Signer.certChain = chain -cleanup: - if session != 0 { - module.Logout(session) - module.CloseSession(session) - } - return chain, err } @@ -792,206 +1122,6 @@ func escapeAll(s []byte) string { return string(res) } -// Gets the key type and a handle to the private key within the PKCS#11 token. -// This method assumes that module isn't nil and also has been initialized. -func getPKCS11Key(module *pkcs11.Ctx, certUri *pkcs11uri.Pkcs11URI, keyUri *pkcs11uri.Pkcs11URI, _userPin string, _contextSpecificPin string) (userPin string, contextSpecificPin string, certificate *x509.Certificate, session pkcs11.SessionHandle, keyType uint, privateKeyHandle pkcs11.ObjectHandle, err error) { - var ( - templatePrivateKey []*pkcs11.Attribute - sessionPrivateKeyObjects []pkcs11.ObjectHandle - manufacturerId string - slotNr uint - slots []SlotIdInfo - certObj []CertObjInfo - keyAttributes []*pkcs11.Attribute - alwaysAuth uint - privateKeyObjects []pkcs11.ObjectHandle - loggedIn bool - ) - - slots, err = enumerateSlotsInPKCS11Module(module) - if err != nil { - goto fail - } - - // If a PKCS#11 URI was provided for the certificate, find it. - if certUri != nil { - userPin, _ = certUri.GetQueryAttribute("pin-value", false) - if userPin == "" && _userPin != "" { - userPin = _userPin - } - slotNr, session, loggedIn, certObj, err = getMatchingCerts(module, slots, certUri, &userPin, true) - if err != nil { - goto fail - } - certificate = certObj[0].x509 - } - - // If no 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 keyUri == nil { - keyUri = certUri - } - userPin, _ = keyUri.GetQueryAttribute("pin-value", false) - if userPin == "" && _userPin != "" { - userPin = _userPin - } - - // 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 slotNr != slots[0].id { - slotNr = slots[0].id - manufacturerId = slots[0].info.ManufacturerID - if session != 0 { - if loggedIn { - module.Logout(session) - module.CloseSession(session) - } - } - loggedIn = false - session = 0 - } - } else { - // If the URI matched multiple slots *but* one of them is the - // one (slotNr) that the certificate was found in, then use - // that. - for _, slot := range slots { - if slot.id == slotNr { - 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(slotNr, 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)" - _, 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 { - // Find the signing algorithm - 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[0] = 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 - } - - if certificate == nil { - privateKeyHandle = curPrivateKeyHandle - break - } - - curContextSpecificPin := _contextSpecificPin - if curContextSpecificPin == "" { - curContextSpecificPin = userPin - } - privateKeyMatchesCert := false - curContextSpecificPin, privateKeyMatchesCert = checkPrivateKeyMatchesCert(module, session, keyType, alwaysAuth, curContextSpecificPin, curPrivateKeyHandle, certificate, manufacturerId) - if privateKeyMatchesCert { - privateKeyHandle = curPrivateKeyHandle - contextSpecificPin = curContextSpecificPin - break - } - } - - if privateKeyHandle == 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 keyUri == certUri && certificate != nil && certObj[0].id != nil { - _, keyHadLabel := keyUri.GetPathAttribute("object", false) - if keyHadLabel { - keyUri.RemovePathAttribute("object") - keyUri.SetPathAttribute("id", escapeAll(certObj[0].id)) - goto retry_search - } - } - - err = errors.New("unable to find matching private key") - goto fail - } - - return userPin, contextSpecificPin, certificate, session, keyType, privateKeyHandle, nil - -fail: - if session != 0 { - module.Logout(session) - module.CloseSession(session) - } - - return "", "", nil, 0, 0, 0, err -} - // 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 @@ -999,13 +1129,19 @@ fail: func GetPKCS11Signer(libPkcs11 string, certificate *x509.Certificate, certificateChain []*x509.Certificate, privateKeyId string, certificateId string) (signer Signer, signingAlgorithm string, err error) { var ( module *pkcs11.Ctx - certUri *pkcs11uri.Pkcs11URI - keyUri *pkcs11uri.Pkcs11URI - certInToken *x509.Certificate + certObj CertObjInfo + session pkcs11.SessionHandle + loggedIn bool + privateKeyHandle pkcs11.ObjectHandle keyType uint - userPin string contextSpecificPin string - session pkcs11.SessionHandle + userPin string + alwaysAuth uint + certSlotNr uint + certUri *pkcs11uri.Pkcs11URI + keyUri *pkcs11uri.Pkcs11URI + slots []SlotIdInfo + certSlot SlotIdInfo ) module, err = initializePKCS11Module(libPkcs11) @@ -1013,26 +1149,49 @@ func GetPKCS11Signer(libPkcs11 string, certificate *x509.Certificate, certificat goto fail } + // If a PKCS#11 URI was provided for the certificate, find it. if certificate == 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 + certificate = certObj.cert } + + // If no 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 { + keyUri = certUri } + userPin, _ = keyUri.GetQueryAttribute("pin-value", false) - userPin, contextSpecificPin, certInToken, session, keyType, _, err = getPKCS11Key(module, certUri, keyUri, "", "") + // If the certificate URI wasn't provided, enumerate slots. + if certificateId == "" { + slots, err = enumerateSlotsInPKCS11Module(module) + if err != nil { + goto fail + } + } - if certificate == nil { - certificate = certInToken + session, keyType, privateKeyHandle, alwaysAuth, contextSpecificPin, err = getPKCS11Key(module, session, loggedIn, certUri, keyUri, certSlotNr, certObj, userPin, contextSpecificPin, slots) + if err != nil { + goto fail } + loggedIn = true switch keyType { case pkcs11.CKK_EC: @@ -1043,14 +1202,16 @@ func GetPKCS11Signer(libPkcs11 string, certificate *x509.Certificate, certificat return nil, "", errors.New("unsupported algorithm") } - if session != 0 { - module.Logout(session) - module.CloseSession(session) - } - return &PKCS11Signer{certificate, nil, module, userPin, contextSpecificPin, certUri, keyUri}, signingAlgorithm, nil + return &PKCS11Signer{certObj, nil, module, userPin, alwaysAuth, contextSpecificPin, certUri, keyUri, session, keyType, privateKeyHandle, loggedIn, certSlotNr, slots}, signingAlgorithm, nil fail: if module != nil { + if session != 0 { + if loggedIn { + module.Logout(session) + } + module.CloseSession(session) + } module.Finalize() module.Destroy() } @@ -1059,8 +1220,9 @@ fail: } // Does PIN prompting until the password has been received. -// Note that finalAuthErrMsg should contain a `%s` so that the actual -// error message can be included. +// 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) (string, error) { var pin string diff --git a/aws_signing_helper/signer.go b/aws_signing_helper/signer.go index fa877a3..39918c4 100644 --- a/aws_signing_helper/signer.go +++ b/aws_signing_helper/signer.go @@ -54,6 +54,7 @@ type Signer interface { Certificate() (certificate *x509.Certificate, err error) CertificateChain() (certificateChain []*x509.Certificate, err error) Close() + CloseSession() } // Container for certificate data returned to the SDK as JSON. diff --git a/aws_signing_helper/windows_cert_store_signer.go b/aws_signing_helper/windows_cert_store_signer.go index 6fb24ee..619fcbc 100644 --- a/aws_signing_helper/windows_cert_store_signer.go +++ b/aws_signing_helper/windows_cert_store_signer.go @@ -293,6 +293,9 @@ func (signer *WindowsCertStoreSigner) Close() { signer.store = 0 } +// Unused method for DarwinCertStoreSigner. +func (signer *WindowsCertStoreSigner) CloseSession() {} + // getPrivateKey gets this identity's private *winPrivateKey func (signer *WindowsCertStoreSigner) getPrivateKey() (*winPrivateKey, error) { if signer.privateKey != nil { From 9246a8f8bdd4aa7171a6c9e9d1ab0404e7774274 Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Sun, 13 Aug 2023 12:10:25 -0400 Subject: [PATCH 09/40] Add happy-path testing for signer.CloseSession() * Check that signing operations can be done after signer.CloseSession() * Clean up some unnecessary code/comments in signer_test.go and the Makefile --- Makefile | 1 - aws_signing_helper/signer_test.go | 9 +++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 2cb78b0..412f0d6 100644 --- a/Makefile +++ b/Makefile @@ -97,7 +97,6 @@ $(certsdir)/cert-bundle.pem: $(RSACERTS) $(ECCERTS) test-certs: $(PKCS8KEYS) $(RSAKEYS) $(ECKEYS) $(RSACERTS) $(ECCERTS) $(PKCS12CERTS) $(certsdir)/cert-bundle.pem tst/softhsm2.conf -# TODO: Need to clean certificates and keys added to certificate store as well test-clean: rm -f $(RSAKEYS) $(ECKEYS) rm -f $(PKCS8KEYS) diff --git a/aws_signing_helper/signer_test.go b/aws_signing_helper/signer_test.go index 144ac34..fad4419 100644 --- a/aws_signing_helper/signer_test.go +++ b/aws_signing_helper/signer_test.go @@ -264,7 +264,6 @@ func TestSign(t *testing.T) { for _, credOpts := range testTable { signer, _, err := GetSigner(&credOpts) if err != nil { - t.Log(err) var logMsg string if credOpts.CertificateId != "" || credOpts.PrivateKeyId != "" { logMsg = fmt.Sprintf("Failed to get signer for '%s'/'%s'", @@ -277,7 +276,6 @@ func TestSign(t *testing.T) { t.Fail() return } - defer signer.Close() pubKey := signer.Public() if credOpts.CertificateId != "" && pubKey == nil { @@ -294,6 +292,13 @@ func TestSign(t *testing.T) { t.Fail() return } + signer.CloseSession() + _, err = signer.Sign(rand.Reader, []byte(msg), digest) + if err != nil { + t.Log("Failed second signature on the input message after signer.CloseSession()") + t.Fail() + return + } if pubKey != nil { valid, _ := Verify([]byte(msg), pubKey, digest, signatureBytes) From e500b1e0eb028dacc8e3722b5810d4777a03ae08 Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Sun, 13 Aug 2023 14:18:15 -0400 Subject: [PATCH 10/40] Refactor functions and comments in pkcs11_signer.go --- aws_signing_helper/pkcs11_signer.go | 160 +++++++++++++++------------- 1 file changed, 87 insertions(+), 73 deletions(-) diff --git a/aws_signing_helper/pkcs11_signer.go b/aws_signing_helper/pkcs11_signer.go index 63f6053..df8527d 100644 --- a/aws_signing_helper/pkcs11_signer.go +++ b/aws_signing_helper/pkcs11_signer.go @@ -147,7 +147,7 @@ func enumerateSlotsInPKCS11Module(module *pkcs11.Ctx) (slots []SlotIdInfo, err e return slots, nil } -// Return true if the URI specifies the attribute and it *doesn't* match +// 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 @@ -158,7 +158,7 @@ func mismatchAttr(uri *pkcs11uri.Pkcs11URI, attr string, val string) bool { return ok && uriVal != val } -// Return the set of slots which match the given uri +// Return the set of slots which match the given URI. func matchSlots(slots []SlotIdInfo, uri *pkcs11uri.Pkcs11URI) (matches []SlotIdInfo) { var ( uriSlotNr uint64 @@ -193,7 +193,7 @@ func matchSlots(slots []SlotIdInfo, uri *pkcs11uri.Pkcs11URI) (matches []SlotIdI return matches } -// Convert the object-related fields in a URI to []*pkcs11.Attribute for FindObjectsInit() +// 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 @@ -226,7 +226,7 @@ func getCertsInSession(module *pkcs11.Ctx, slotId uint, session pkcs11.SessionHa templateCrt []*pkcs11.Attribute ) - // Convert the URI into a template for FindObjectsInit() + // Convert the URI into a template for FindObjectsInit(). templateCrt = getFindTemplate(uri, pkcs11.CKO_CERTIFICATE) if err = module.FindObjectsInit(session, templateCrt); err != nil { @@ -292,13 +292,19 @@ func getCertsInSession(module *pkcs11.Ctx, slotId uint, session pkcs11.SessionHa // 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 into in the +// 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) } @@ -346,8 +352,6 @@ func getMatchingCerts(module *pkcs11.Ctx, slots []SlotIdInfo, uri *pkcs11uri.Pkc // which matches the URI, and not if there are multiple possible // tokens in which the object could reside." if len(slots) == 1 && userPin != "" { - errNoMatchingCerts := errors.New("no matching certificates") - curSession, err := module.OpenSession(slots[0].id, pkcs11.CKF_SERIAL_SESSION|pkcs11.CKS_RO_PUBLIC_SESSION) if err != nil { err = errNoMatchingCerts @@ -376,8 +380,7 @@ func getMatchingCerts(module *pkcs11.Ctx, slots []SlotIdInfo, uri *pkcs11uri.Pkc module.CloseSession(curSession) } - // No matching certificates - err = errors.New("no matching certificates") + err = errNoMatchingCerts goto fail foundCert: @@ -386,7 +389,7 @@ foundCert: goto fail } - // Exactly one matching certificate after logging into the appropriate token + // 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 @@ -555,6 +558,62 @@ func (pkcs11Signer *PKCS11Signer) CloseSession() { pkcs11Signer.slots = 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) (string, error) { + var pin string + + parseErrMsg := fmt.Sprintf("unable to read PKCS#11 %s", passwordName) + prompt := fmt.Sprintf("Please enter your %s", passwordName) + + ttyPath := "/dev/tty" + if runtime.GOOS == "windows" { + ttyPath = "CON" + } + + ttyFile, err := os.OpenFile(ttyPath, os.O_RDWR, 0) + if err != nil { + return "", errors.New(parseErrMsg) + } + defer ttyFile.Close() + + for true { + pin, err = GetPassword(ttyFile, 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 + } + + // Code should never reach here + return "", fmt.Errorf("unexpected error when prompting for %s", passwordName) +} + +// Prompts the user for their password +func GetPassword(ttyFile *os.File, prompt string, parseErrMsg string) (string, error) { + fmt.Fprintln(ttyFile, prompt) + passwordBytes, err := term.ReadPassword(int(ttyFile.Fd())) + if err != nil { + return "", errors.New(parseErrMsg) + } + + password := string(passwordBytes[:]) + strings.Replace(password, "\r", "", -1) // Remove CR + return password, nil +} + // Helper function to sign a digest using a PKCS#11 private key handle. func signHelper(module *pkcs11.Ctx, session pkcs11.SessionHandle, privateKeyHandle pkcs11.ObjectHandle, alwaysAuth uint, pkcs11Pin string, 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 @@ -633,6 +692,8 @@ afterContextSpecificLogin: return pkcs11Pin, 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, certSlotNr uint, certObj CertObjInfo, userPin string, contextSpecificPin string, slots []SlotIdInfo) (_session pkcs11.SessionHandle, keyType uint, privateKeyHandle pkcs11.ObjectHandle, alwaysAuth uint, _contextSpecificPin string, err error) { var ( keySlotNr uint @@ -802,6 +863,10 @@ fail: return 0, 0, 0, 0, "", err } +// Gets the certificate with 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) 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 @@ -869,6 +934,7 @@ func (pkcs11Signer *PKCS11Signer) Sign(rand io.Reader, digest []byte, opts crypt goto fail } + // Save the values we need after finding the certificate. pkcs11Signer.slots = slots pkcs11Signer.session = session pkcs11Signer.loggedIn = loggedIn @@ -880,6 +946,9 @@ func (pkcs11Signer *PKCS11Signer) Sign(rand io.Reader, digest []byte, opts crypt goto fail } } + + // These variables' values could have been updated by the above function + // calls, so update them. slots = pkcs11Signer.slots certObj = pkcs11Signer.certObj loggedIn = pkcs11Signer.loggedIn @@ -917,7 +986,7 @@ fail: return signature, err } -// Gets the *x509.Certificate associated with this PKCS11Signer +// Gets the *x509.Certificate associated with this PKCS11Signer. func (pkcs11Signer *PKCS11Signer) Certificate() (certificate *x509.Certificate, err error) { var ( module *pkcs11.Ctx @@ -934,6 +1003,7 @@ func (pkcs11Signer *PKCS11Signer) Certificate() (certificate *x509.Certificate, module = pkcs11Signer.module cert = pkcs11Signer.certObj.cert + // If the certificate was saved, return it. if cert != nil { return cert, nil } @@ -953,7 +1023,7 @@ func (pkcs11Signer *PKCS11Signer) Certificate() (certificate *x509.Certificate, return pkcs11Signer.certObj.cert, nil } -// Checks whether the first certificate issues the second +// Checks whether the first certificate issues the second. func certIssues(issuer *x509.Certificate, candidate *x509.Certificate) bool { roots := x509.NewCertPool() roots.AddCert(issuer) @@ -966,7 +1036,7 @@ func certIssues(issuer *x509.Certificate, candidate *x509.Certificate) bool { return err != nil } -// Gets the certificate chain associated with this PKCS11Signer +// Gets the certificate chain associated with this PKCS11Signer. func (pkcs11Signer *PKCS11Signer) CertificateChain() (chain []*x509.Certificate, err error) { var ( module *pkcs11.Ctx @@ -1030,7 +1100,7 @@ func (pkcs11Signer *PKCS11Signer) CertificateChain() (chain []*x509.Certificate, return chain, err } -// Checks whether the private key and certificate are associated with each other +// Checks whether the private key and certificate are associated with each other. func checkPrivateKeyMatchesCert(module *pkcs11.Ctx, session pkcs11.SessionHandle, keyType uint, alwaysAuth uint, pinPkcs11 string, privateKeyHandle pkcs11.ObjectHandle, certificate *x509.Certificate, manufacturerId string) (string, bool) { var digestSuffix []byte publicKey := certificate.PublicKey @@ -1105,10 +1175,10 @@ func bytesToUint(b []byte) (res uint, err error) { * https://github.com/stefanberger/go-pkcs11uri/issues/11 */ -// upper character hex digits needed for pct-encoding +// Upper character hex digits needed for pct-encoding. const hexchar = "0123456789ABCDEF" -// escapeAll pct-escapes all characters in the string +// escapeAll pct-escapes all characters in the string. func escapeAll(s []byte) string { res := make([]byte, len(s)*3) j := 0 @@ -1125,7 +1195,7 @@ func escapeAll(s []byte) string { // 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 +// through a PKCS#11-compatible cryptographic device. func GetPKCS11Signer(libPkcs11 string, certificate *x509.Certificate, certificateChain []*x509.Certificate, privateKeyId string, certificateId string) (signer Signer, signingAlgorithm string, err error) { var ( module *pkcs11.Ctx @@ -1218,59 +1288,3 @@ fail: return nil, "", err } - -// 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) (string, error) { - var pin string - - parseErrMsg := fmt.Sprintf("unable to read PKCS#11 %s", passwordName) - prompt := fmt.Sprintf("Please enter your %s", passwordName) - - ttyPath := "/dev/tty" - if runtime.GOOS == "windows" { - ttyPath = "CON" - } - - ttyFile, err := os.OpenFile(ttyPath, os.O_RDWR, 0) - if err != nil { - return "", errors.New(parseErrMsg) - } - defer ttyFile.Close() - - for true { - pin, err = GetPassword(ttyFile, 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 - } - - // Code should never reach here - return "", fmt.Errorf("unexpected error when prompting for %s", passwordName) -} - -// Prompts the user for their password -func GetPassword(ttyFile *os.File, prompt string, parseErrMsg string) (string, error) { - fmt.Fprintln(ttyFile, prompt) - passwordBytes, err := term.ReadPassword(int(ttyFile.Fd())) - if err != nil { - return "", errors.New(parseErrMsg) - } - - password := string(passwordBytes[:]) - strings.Replace(password, "\r", "", -1) // Remove CR - return password, nil -} From b39ffd8052074b3778223356d78fae9685904b21 Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Sun, 13 Aug 2023 14:54:05 -0400 Subject: [PATCH 11/40] Refactor debug logging * Add some more debug logging to pkcs11_signer.go. * Make debug logging output to stdout. --- .../darwin_cert_store_signer.go | 4 ++-- aws_signing_helper/pkcs11_signer.go | 24 ++++++++++++++++--- aws_signing_helper/signer.go | 8 +++---- .../windows_cert_store_signer.go | 4 ++-- cmd/sign_string.go | 2 +- 5 files changed, 30 insertions(+), 12 deletions(-) diff --git a/aws_signing_helper/darwin_cert_store_signer.go b/aws_signing_helper/darwin_cert_store_signer.go index 094f185..e075b43 100644 --- a/aws_signing_helper/darwin_cert_store_signer.go +++ b/aws_signing_helper/darwin_cert_store_signer.go @@ -22,7 +22,7 @@ import ( "errors" "fmt" "io" - "os" + "log" "unsafe" ) @@ -98,7 +98,7 @@ func GetMatchingCertsAndIdentity(certIdentifier CertIdentifier) (C.SecIdentityRe } if Debug { - fmt.Fprintf(os.Stderr, "found %d matching identities\n", len(certContainers)) + log.Printf("found %d matching identities\n", len(certContainers)) } // Only retain the SecIdentityRef if it should be used later on diff --git a/aws_signing_helper/pkcs11_signer.go b/aws_signing_helper/pkcs11_signer.go index df8527d..5985967 100644 --- a/aws_signing_helper/pkcs11_signer.go +++ b/aws_signing_helper/pkcs11_signer.go @@ -39,6 +39,7 @@ import ( "errors" "fmt" "io" + "log" "os" "runtime" "strconv" @@ -134,10 +135,18 @@ func enumerateSlotsInPKCS11Module(module *pkcs11.Ctx) (slots []SlotIdInfo, err e 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 } @@ -318,6 +327,10 @@ func getMatchingCerts(module *pkcs11.Ctx, slots []SlotIdInfo, uri *pkcs11uri.Pkc 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 } @@ -330,11 +343,11 @@ func getMatchingCerts(module *pkcs11.Ctx, slots []SlotIdInfo, uri *pkcs11uri.Pkc if matchedSlot == (SlotIdInfo{}) { matchedSlot = slot session = curSession - goto skip + goto skipCloseSession } } module.CloseSession(curSession) - skip: + skipCloseSession: } if len(matchingCerts) >= 1 { @@ -663,7 +676,7 @@ func signHelper(module *pkcs11.Ctx, session pkcs11.SessionHandle, privateKeyHand goto afterContextSpecificLogin } else { if Debug { - fmt.Fprintf(os.Stderr, "user re-authentication attempt failed (%s)", err.Error()) + log.Printf("user re-authentication attempt failed (%s)\n", err.Error()) } } } @@ -846,6 +859,11 @@ retry_search: if keyUriStr == certUriStr { _, 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 diff --git a/aws_signing_helper/signer.go b/aws_signing_helper/signer.go index 39918c4..baa9a75 100644 --- a/aws_signing_helper/signer.go +++ b/aws_signing_helper/signer.go @@ -164,7 +164,7 @@ func GetSigner(opts *CredentialsOpts) (signer Signer, signatureAlgorithm string, if privateKeyId == "" { if opts.CertificateId == "" { if Debug { - fmt.Fprintln(os.Stderr, "attempting to use CertStoreSigner") + log.Println("attempting to use CertStoreSigner") } return GetCertStoreSigner(opts.CertIdentifier) } @@ -184,7 +184,7 @@ func GetSigner(opts *CredentialsOpts) (signer Signer, signatureAlgorithm string, } } else if opts.PrivateKeyId == "" { if Debug { - fmt.Fprintln(os.Stderr, "not a PEM certificate, so trying PKCS#12") + log.Println("not a PEM certificate, so trying PKCS#12") } // Not a PEM certificate? Try PKCS#12 return GetPKCS12Signer(opts.CertificateId) @@ -205,7 +205,7 @@ func GetSigner(opts *CredentialsOpts) (signer Signer, signatureAlgorithm string, if strings.HasPrefix(privateKeyId, "pkcs11:") { if Debug { - fmt.Fprintln(os.Stderr, "attempting to use PKCS#11") + log.Println("attempting to use PKCS#11") } return GetPKCS11Signer(opts.LibPkcs11, certificate, certificateChain, opts.PrivateKeyId, opts.CertificateId) } else { @@ -215,7 +215,7 @@ func GetSigner(opts *CredentialsOpts) (signer Signer, signatureAlgorithm string, } if Debug { - fmt.Fprintln(os.Stderr, "attempting to use FileSystemSigner") + log.Println("attempting to use FileSystemSigner") } return GetFileSystemSigner(privateKey, certificate, certificateChain) } diff --git a/aws_signing_helper/windows_cert_store_signer.go b/aws_signing_helper/windows_cert_store_signer.go index 619fcbc..d6c6c2e 100644 --- a/aws_signing_helper/windows_cert_store_signer.go +++ b/aws_signing_helper/windows_cert_store_signer.go @@ -40,7 +40,7 @@ import ( "fmt" "golang.org/x/sys/windows" "io" - "os" + "log" "strconv" "strings" "unsafe" @@ -178,7 +178,7 @@ func GetMatchingCertsAndChain(certIdentifier CertIdentifier) (store windows.Hand } if Debug { - fmt.Fprintf(os.Stderr, "found %d matching identities\n", len(certContainers)) + log.Printf("found %d matching identities\n", len(certContainers)) } return store, certCtx, certChain, certContainers, nil diff --git a/cmd/sign_string.go b/cmd/sign_string.go index 27982cc..1aeb4db 100644 --- a/cmd/sign_string.go +++ b/cmd/sign_string.go @@ -142,7 +142,7 @@ var signStringCmd = &cobra.Command{ stringToSignBytes = []byte(stringToSign) if credentialsOptions.Debug { - fmt.Fprintln(os.Stderr, "Signing fixed string of the form: \"AWS Roles Anywhere "+ + log.Println("Signing fixed string of the form: \"AWS Roles Anywhere "+ "Credential Helper Signing Test\" || SIGN_STRING_TEST_VERSION || SHA256(\"IAM RA\" || PUBLIC_KEY_BYTE_ARRAY)\"") } } else { From 79b8418c5d1c858ca7251d90a9c6a6f52e0a865f Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Sun, 13 Aug 2023 15:15:25 -0400 Subject: [PATCH 12/40] Disallow intermediates from being specified with certain integrations * PKCS#11 integration and certificate store integrations don't allow the user to specify intermediates. * For now, if the user uses PKCS#12 files, intermediates also can't be specified, but that should be changed. --- aws_signing_helper/pkcs11_signer.go | 2 +- aws_signing_helper/signer.go | 33 ++++++++++++++++++----------- cmd/credentials.go | 1 + cmd/sign_string.go | 2 +- 4 files changed, 24 insertions(+), 14 deletions(-) diff --git a/aws_signing_helper/pkcs11_signer.go b/aws_signing_helper/pkcs11_signer.go index 5985967..7444ab3 100644 --- a/aws_signing_helper/pkcs11_signer.go +++ b/aws_signing_helper/pkcs11_signer.go @@ -1214,7 +1214,7 @@ func escapeAll(s []byte) string { // 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, certificate *x509.Certificate, certificateChain []*x509.Certificate, privateKeyId string, certificateId string) (signer Signer, signingAlgorithm string, err error) { +func GetPKCS11Signer(libPkcs11 string, certificate *x509.Certificate, privateKeyId string, certificateId string) (signer Signer, signingAlgorithm string, err error) { var ( module *pkcs11.Ctx certObj CertObjInfo diff --git a/aws_signing_helper/signer.go b/aws_signing_helper/signer.go index baa9a75..db7a71d 100644 --- a/aws_signing_helper/signer.go +++ b/aws_signing_helper/signer.go @@ -186,6 +186,11 @@ func GetSigner(opts *CredentialsOpts) (signer Signer, signatureAlgorithm string, if Debug { log.Println("not a PEM certificate, so trying PKCS#12") } + // TODO: Allow for certificate chain to be specified with PKCS#12 integration. + if opts.CertificateBundleId != "" { + return nil, "", errors.New("can't specify certificate chain when" + + " using PKCS#12 files") + } // Not a PEM certificate? Try PKCS#12 return GetPKCS12Signer(opts.CertificateId) } else { @@ -193,22 +198,26 @@ func GetSigner(opts *CredentialsOpts) (signer Signer, signatureAlgorithm string, } } - if opts.CertificateBundleId != "" { - certificateChainPointers, err := ReadCertificateBundleData(opts.CertificateBundleId) - if err != nil { - return nil, "", err - } - for _, certificate := range certificateChainPointers { - certificateChain = append(certificateChain, certificate) - } - } - if strings.HasPrefix(privateKeyId, "pkcs11:") { if Debug { - log.Println("attempting to use PKCS#11") + log.Println("attempting to use PKCS11Signer") } - return GetPKCS11Signer(opts.LibPkcs11, certificate, certificateChain, opts.PrivateKeyId, opts.CertificateId) + if opts.CertificateBundleId != "" { + return nil, "", errors.New("can't specify certificate chain when" + + " using PKCS#11 integration") + } + return GetPKCS11Signer(opts.LibPkcs11, certificate, opts.PrivateKeyId, opts.CertificateId) } else { + if opts.CertificateBundleId != "" { + certificateChainPointers, err := ReadCertificateBundleData(opts.CertificateBundleId) + if err != nil { + return nil, "", err + } + for _, certificate := range certificateChainPointers { + certificateChain = append(certificateChain, certificate) + } + } + privateKey, err := ReadPrivateKeyData(privateKeyId) if err != nil { return nil, "", err diff --git a/cmd/credentials.go b/cmd/credentials.go index 3325853..cb6b25e 100644 --- a/cmd/credentials.go +++ b/cmd/credentials.go @@ -72,6 +72,7 @@ func initCredentialsSubCommand(subCmd *cobra.Command) { subCmd.PersistentFlags().UintVar(&slotPkcs11, "pkcs11-slot", 0, "PKCS #11 slot in which to search for the private key (and potentially certificate as well)") subCmd.MarkFlagsMutuallyExclusive("private-key", "cert-selector") + subCmd.MarkFlagsMutuallyExclusive("cert-selector", "intermediates") } // Parses a cert selector string to a map diff --git a/cmd/sign_string.go b/cmd/sign_string.go index 1aeb4db..6e7ed64 100644 --- a/cmd/sign_string.go +++ b/cmd/sign_string.go @@ -142,7 +142,7 @@ var signStringCmd = &cobra.Command{ stringToSignBytes = []byte(stringToSign) if credentialsOptions.Debug { - log.Println("Signing fixed string of the form: \"AWS Roles Anywhere "+ + log.Println("Signing fixed string of the form: \"AWS Roles Anywhere " + "Credential Helper Signing Test\" || SIGN_STRING_TEST_VERSION || SHA256(\"IAM RA\" || PUBLIC_KEY_BYTE_ARRAY)\"") } } else { From 70d07c59e62df9465fdb8cfc4c4d380ae9bd955e Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Sun, 13 Aug 2023 18:29:28 -0400 Subject: [PATCH 13/40] Modify certificate chain searching with PKCS#11 integration * Sometimes, it's possible for the certificate and private key to be found in different slots. We want to search for the rest of the certificate chain in the same slot that the certificate was found, but in this case, we only store the session that the key was found in. To get around this, reopen (and close once done) the session that the certificate was found in, in order to do searching. --- aws_signing_helper/pkcs11_signer.go | 40 ++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/aws_signing_helper/pkcs11_signer.go b/aws_signing_helper/pkcs11_signer.go index 7444ab3..317c699 100644 --- a/aws_signing_helper/pkcs11_signer.go +++ b/aws_signing_helper/pkcs11_signer.go @@ -82,6 +82,7 @@ type PKCS11Signer struct { privateKeyHandle pkcs11.ObjectHandle loggedIn bool certSlotNr uint + keyInDiffSlot bool slots []SlotIdInfo } @@ -707,7 +708,7 @@ afterContextSpecificLogin: // 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, certSlotNr uint, certObj CertObjInfo, userPin string, contextSpecificPin string, slots []SlotIdInfo) (_session pkcs11.SessionHandle, keyType uint, privateKeyHandle pkcs11.ObjectHandle, alwaysAuth uint, _contextSpecificPin string, err error) { +func getPKCS11Key(module *pkcs11.Ctx, session pkcs11.SessionHandle, loggedIn bool, certUri *pkcs11uri.Pkcs11URI, keyUri *pkcs11uri.Pkcs11URI, certSlotNr uint, certObj CertObjInfo, userPin string, contextSpecificPin string, slots []SlotIdInfo) (_session pkcs11.SessionHandle, keyInDiffSlot bool, keyType uint, privateKeyHandle pkcs11.ObjectHandle, alwaysAuth uint, _contextSpecificPin string, err error) { var ( keySlotNr uint manufacturerId string @@ -726,6 +727,7 @@ func getPKCS11Key(module *pkcs11.Ctx, session pkcs11.SessionHandle, loggedIn boo slots = matchSlots(slots, keyUri) if len(slots) == 1 { if certSlotNr != slots[0].id { + keyInDiffSlot = true keySlotNr = slots[0].id manufacturerId = slots[0].info.ManufacturerID if session != 0 { @@ -739,7 +741,7 @@ func getPKCS11Key(module *pkcs11.Ctx, session pkcs11.SessionHandle, loggedIn boo } } else { // If the URI matched multiple slots *but* one of them is the - // one (certSlot.id) that the certificate was found in, then use + // one (certSlotNr) that the certificate was found in, then use // that. for _, slot := range slots { if certSlotNr == slot.id { @@ -875,10 +877,10 @@ retry_search: goto fail } - return session, keyType, privateKeyHandle, alwaysAuth, contextSpecificPin, nil + return session, keyInDiffSlot, keyType, privateKeyHandle, alwaysAuth, contextSpecificPin, nil fail: - return 0, 0, 0, 0, "", err + return 0, false, 0, 0, 0, "", err } // Gets the certificate with a token, given the URI that identifies the @@ -920,6 +922,7 @@ func (pkcs11Signer *PKCS11Signer) Sign(rand io.Reader, digest []byte, opts crypt loggedIn bool alwaysAuth uint certSlot SlotIdInfo + keyInDiffSlot bool ) hashFunc := opts.HashFunc() @@ -973,7 +976,7 @@ func (pkcs11Signer *PKCS11Signer) Sign(rand io.Reader, digest []byte, opts crypt certSlotNr = pkcs11Signer.certSlotNr gotCert: - session, keyType, privateKeyHandle, alwaysAuth, contextSpecificPin, err = getPKCS11Key(module, session, loggedIn, certUri, keyUri, certSlotNr, certObj, userPin, contextSpecificPin, slots) + session, keyInDiffSlot, keyType, privateKeyHandle, alwaysAuth, contextSpecificPin, err = getPKCS11Key(module, session, loggedIn, certUri, keyUri, certSlotNr, certObj, userPin, contextSpecificPin, slots) if err != nil { goto fail } @@ -985,6 +988,7 @@ gotCert: pkcs11Signer.alwaysAuth = alwaysAuth pkcs11Signer.contextSpecificPin = contextSpecificPin pkcs11Signer.loggedIn = true + pkcs11Signer.keyInDiffSlot = keyInDiffSlot gotPrivateKey: // We only care about the context-specific PIN when it comes to signing with @@ -1063,6 +1067,8 @@ func (pkcs11Signer *PKCS11Signer) CertificateChain() (chain []*x509.Certificate, certsFound []CertObjInfo cert *x509.Certificate certUri *pkcs11uri.Pkcs11URI + certSlotNr uint + keyInDiffSlot bool ) module = pkcs11Signer.module @@ -1085,7 +1091,22 @@ func (pkcs11Signer *PKCS11Signer) CertificateChain() (chain []*x509.Certificate, } chain = append(chain, cert) - session = pkcs11Signer.session + certSlotNr = pkcs11Signer.certSlotNr + keyInDiffSlot = pkcs11Signer.keyInDiffSlot + + // If the certificate and key were found in different slots, the stored + // session will be one in which the key was found. In that case, the + // rest of the certificate chain should be searched for in the same slot + // that the certificate was found. + if !keyInDiffSlot { + session = pkcs11Signer.session + } else { + session, err = module.OpenSession(certSlotNr, pkcs11.CKF_SERIAL_SESSION|pkcs11.CKS_RO_PUBLIC_SESSION) + if err != nil { + return nil, err + } + defer module.CloseSession(session) + } certsFound, err = getCertsInSession(module, 0, session, nil) if err != nil { @@ -1230,6 +1251,7 @@ func GetPKCS11Signer(libPkcs11 string, certificate *x509.Certificate, privateKey keyUri *pkcs11uri.Pkcs11URI slots []SlotIdInfo certSlot SlotIdInfo + keyInDiffSlot bool ) module, err = initializePKCS11Module(libPkcs11) @@ -1267,7 +1289,7 @@ func GetPKCS11Signer(libPkcs11 string, certificate *x509.Certificate, privateKey } userPin, _ = keyUri.GetQueryAttribute("pin-value", false) - // If the certificate URI wasn't provided, enumerate slots. + // If the certificate's PKCS#11 URI wasn't provided, enumerate slots. if certificateId == "" { slots, err = enumerateSlotsInPKCS11Module(module) if err != nil { @@ -1275,7 +1297,7 @@ func GetPKCS11Signer(libPkcs11 string, certificate *x509.Certificate, privateKey } } - session, keyType, privateKeyHandle, alwaysAuth, contextSpecificPin, err = getPKCS11Key(module, session, loggedIn, certUri, keyUri, certSlotNr, certObj, userPin, contextSpecificPin, slots) + session, keyInDiffSlot, keyType, privateKeyHandle, alwaysAuth, contextSpecificPin, err = getPKCS11Key(module, session, loggedIn, certUri, keyUri, certSlotNr, certObj, userPin, contextSpecificPin, slots) if err != nil { goto fail } @@ -1290,7 +1312,7 @@ func GetPKCS11Signer(libPkcs11 string, certificate *x509.Certificate, privateKey return nil, "", errors.New("unsupported algorithm") } - return &PKCS11Signer{certObj, nil, module, userPin, alwaysAuth, contextSpecificPin, certUri, keyUri, session, keyType, privateKeyHandle, loggedIn, certSlotNr, slots}, signingAlgorithm, nil + return &PKCS11Signer{certObj, nil, module, userPin, alwaysAuth, contextSpecificPin, certUri, keyUri, session, keyType, privateKeyHandle, loggedIn, certSlotNr, keyInDiffSlot, slots}, signingAlgorithm, nil fail: if module != nil { From c38d84621edbe1c60cb6b704bf55a005d175ff20 Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Sun, 13 Aug 2023 23:01:26 -0400 Subject: [PATCH 14/40] Allow intermediates to be specified with PKCS#12 * Also format go files using 'go fmt' --- aws_signing_helper/file_system_signer.go | 4 +- aws_signing_helper/pkcs11_signer.go | 56 ++++++++++---------- aws_signing_helper/signer.go | 66 ++++++++++++------------ 3 files changed, 64 insertions(+), 62 deletions(-) diff --git a/aws_signing_helper/file_system_signer.go b/aws_signing_helper/file_system_signer.go index c4389ce..894a850 100644 --- a/aws_signing_helper/file_system_signer.go +++ b/aws_signing_helper/file_system_signer.go @@ -105,7 +105,7 @@ func GetFileSystemSigner(privateKey crypto.PrivateKey, certificate *x509.Certifi return FileSystemSigner{privateKey, certificate, certificateChain}, signingAlgorithm, nil } -func GetPKCS12Signer(certificateId string) (signer Signer, signingAlgorithm string, err error) { +func GetPKCS12Signer(certificateId string, certificateChain []*x509.Certificate) (signer Signer, signingAlgorithm string, err error) { bytes, err := os.ReadFile(certificateId) if err != nil { return nil, "", err @@ -127,7 +127,7 @@ func GetPKCS12Signer(certificateId string) (signer Signer, signingAlgorithm stri ecPrivateKey, ok := privateKey.(*ecdsa.PrivateKey) if ok { signingAlgorithm = aws4_x509_ecdsa_sha256 - return FileSystemSigner{*ecPrivateKey, certificate, nil}, signingAlgorithm, nil + return FileSystemSigner{*ecPrivateKey, certificate, certificateChain}, signingAlgorithm, nil } return nil, "", errors.New("unsupported algorithm on PKCS#12 key") diff --git a/aws_signing_helper/pkcs11_signer.go b/aws_signing_helper/pkcs11_signer.go index 317c699..19cec2f 100644 --- a/aws_signing_helper/pkcs11_signer.go +++ b/aws_signing_helper/pkcs11_signer.go @@ -82,7 +82,7 @@ type PKCS11Signer struct { privateKeyHandle pkcs11.ObjectHandle loggedIn bool certSlotNr uint - keyInDiffSlot bool + keyInDiffSlot bool slots []SlotIdInfo } @@ -727,7 +727,7 @@ func getPKCS11Key(module *pkcs11.Ctx, session pkcs11.SessionHandle, loggedIn boo slots = matchSlots(slots, keyUri) if len(slots) == 1 { if certSlotNr != slots[0].id { - keyInDiffSlot = true + keyInDiffSlot = true keySlotNr = slots[0].id manufacturerId = slots[0].info.ManufacturerID if session != 0 { @@ -922,7 +922,7 @@ func (pkcs11Signer *PKCS11Signer) Sign(rand io.Reader, digest []byte, opts crypt loggedIn bool alwaysAuth uint certSlot SlotIdInfo - keyInDiffSlot bool + keyInDiffSlot bool ) hashFunc := opts.HashFunc() @@ -988,7 +988,7 @@ gotCert: pkcs11Signer.alwaysAuth = alwaysAuth pkcs11Signer.contextSpecificPin = contextSpecificPin pkcs11Signer.loggedIn = true - pkcs11Signer.keyInDiffSlot = keyInDiffSlot + pkcs11Signer.keyInDiffSlot = keyInDiffSlot gotPrivateKey: // We only care about the context-specific PIN when it comes to signing with @@ -1061,14 +1061,14 @@ func certIssues(issuer *x509.Certificate, candidate *x509.Certificate) bool { // Gets the certificate chain associated with this PKCS11Signer. func (pkcs11Signer *PKCS11Signer) CertificateChain() (chain []*x509.Certificate, err error) { var ( - module *pkcs11.Ctx - session pkcs11.SessionHandle - certChain []*x509.Certificate - certsFound []CertObjInfo - cert *x509.Certificate - certUri *pkcs11uri.Pkcs11URI - certSlotNr uint - keyInDiffSlot bool + module *pkcs11.Ctx + session pkcs11.SessionHandle + certChain []*x509.Certificate + certsFound []CertObjInfo + cert *x509.Certificate + certUri *pkcs11uri.Pkcs11URI + certSlotNr uint + keyInDiffSlot bool ) module = pkcs11Signer.module @@ -1091,22 +1091,22 @@ func (pkcs11Signer *PKCS11Signer) CertificateChain() (chain []*x509.Certificate, } chain = append(chain, cert) - certSlotNr = pkcs11Signer.certSlotNr - keyInDiffSlot = pkcs11Signer.keyInDiffSlot - - // If the certificate and key were found in different slots, the stored - // session will be one in which the key was found. In that case, the - // rest of the certificate chain should be searched for in the same slot - // that the certificate was found. - if !keyInDiffSlot { - session = pkcs11Signer.session - } else { + certSlotNr = pkcs11Signer.certSlotNr + keyInDiffSlot = pkcs11Signer.keyInDiffSlot + + // If the certificate and key were found in different slots, the stored + // session will be one in which the key was found. In that case, the + // rest of the certificate chain should be searched for in the same slot + // that the certificate was found. + if !keyInDiffSlot { + session = pkcs11Signer.session + } else { session, err = module.OpenSession(certSlotNr, pkcs11.CKF_SERIAL_SESSION|pkcs11.CKS_RO_PUBLIC_SESSION) - if err != nil { - return nil, err - } - defer module.CloseSession(session) - } + if err != nil { + return nil, err + } + defer module.CloseSession(session) + } certsFound, err = getCertsInSession(module, 0, session, nil) if err != nil { @@ -1251,7 +1251,7 @@ func GetPKCS11Signer(libPkcs11 string, certificate *x509.Certificate, privateKey keyUri *pkcs11uri.Pkcs11URI slots []SlotIdInfo certSlot SlotIdInfo - keyInDiffSlot bool + keyInDiffSlot bool ) module, err = initializePKCS11Module(libPkcs11) diff --git a/aws_signing_helper/signer.go b/aws_signing_helper/signer.go index db7a71d..8ce5a19 100644 --- a/aws_signing_helper/signer.go +++ b/aws_signing_helper/signer.go @@ -157,8 +157,10 @@ func encodeEcdsaSigValue(signature []byte) (out []byte, err error) { // Gets the Signer based on the flags passed in by the user (from which the CredentialsOpts structure is derived) func GetSigner(opts *CredentialsOpts) (signer Signer, signatureAlgorithm string, err error) { - var certificate *x509.Certificate - var certificateChain []*x509.Certificate + var ( + certificate *x509.Certificate + certificateChain []*x509.Certificate + ) privateKeyId := opts.PrivateKeyId if privateKeyId == "" { @@ -171,7 +173,28 @@ func GetSigner(opts *CredentialsOpts) (signer Signer, signatureAlgorithm string, privateKeyId = opts.CertificateId } - if opts.CertificateId != "" && !strings.HasPrefix(opts.CertificateId, "pkcs11:") { + if strings.HasPrefix(privateKeyId, "pkcs11:") { + if Debug { + log.Println("attempting to use PKCS11Signer") + } + if opts.CertificateBundleId != "" { + return nil, "", errors.New("can't specify certificate chain when" + + " using PKCS#11 integration") + } + return GetPKCS11Signer(opts.LibPkcs11, certificate, opts.PrivateKeyId, opts.CertificateId) + } + + if opts.CertificateBundleId != "" { + certificateChainPointers, err := ReadCertificateBundleData(opts.CertificateBundleId) + if err != nil { + return nil, "", err + } + for _, certificate := range certificateChainPointers { + certificateChain = append(certificateChain, certificate) + } + } + + if opts.CertificateId != "" { certificateData, err := ReadCertificateData(opts.CertificateId) if err == nil { certificateDerData, err := base64.StdEncoding.DecodeString(certificateData.CertificateData) @@ -192,42 +215,21 @@ func GetSigner(opts *CredentialsOpts) (signer Signer, signatureAlgorithm string, " using PKCS#12 files") } // Not a PEM certificate? Try PKCS#12 - return GetPKCS12Signer(opts.CertificateId) + return GetPKCS12Signer(opts.CertificateId, certificateChain) } else { return nil, "", err } } - if strings.HasPrefix(privateKeyId, "pkcs11:") { - if Debug { - log.Println("attempting to use PKCS11Signer") - } - if opts.CertificateBundleId != "" { - return nil, "", errors.New("can't specify certificate chain when" + - " using PKCS#11 integration") - } - return GetPKCS11Signer(opts.LibPkcs11, certificate, opts.PrivateKeyId, opts.CertificateId) - } else { - if opts.CertificateBundleId != "" { - certificateChainPointers, err := ReadCertificateBundleData(opts.CertificateBundleId) - if err != nil { - return nil, "", err - } - for _, certificate := range certificateChainPointers { - certificateChain = append(certificateChain, certificate) - } - } - - privateKey, err := ReadPrivateKeyData(privateKeyId) - if err != nil { - return nil, "", err - } + privateKey, err := ReadPrivateKeyData(privateKeyId) + if err != nil { + return nil, "", err + } - if Debug { - log.Println("attempting to use FileSystemSigner") - } - return GetFileSystemSigner(privateKey, certificate, certificateChain) + if Debug { + log.Println("attempting to use FileSystemSigner") } + return GetFileSystemSigner(privateKey, certificate, certificateChain) } // Obtain the date-time, formatted as specified by SigV4 From 3cad52022513856be5936673b2f75b77bf244a8c Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Mon, 14 Aug 2023 14:20:52 -0400 Subject: [PATCH 15/40] Parse certificate chain (if provided) from PKCS#12 file --- aws_signing_helper/file_system_signer.go | 33 +------- aws_signing_helper/signer.go | 103 +++++++++++++++++++---- 2 files changed, 89 insertions(+), 47 deletions(-) diff --git a/aws_signing_helper/file_system_signer.go b/aws_signing_helper/file_system_signer.go index 894a850..b586986 100644 --- a/aws_signing_helper/file_system_signer.go +++ b/aws_signing_helper/file_system_signer.go @@ -8,10 +8,8 @@ import ( "crypto/sha512" "crypto/x509" "errors" - "golang.org/x/crypto/pkcs12" "io" "log" - "os" ) type FileSystemSigner struct { @@ -85,8 +83,7 @@ func (fileSystemSigner FileSystemSigner) CertificateChain() ([]*x509.Certificate return fileSystemSigner.certChain, nil } -// Returns a FileSystemSigner, that signs a payload using the -// private key passed in +// Returns a FileSystemSigner, that signs a payload using the private key passed in func GetFileSystemSigner(privateKey crypto.PrivateKey, certificate *x509.Certificate, certificateChain []*x509.Certificate) (signer Signer, signingAlgorithm string, err error) { // Find the signing algorithm _, isRsaKey := privateKey.(rsa.PrivateKey) @@ -104,31 +101,3 @@ func GetFileSystemSigner(privateKey crypto.PrivateKey, certificate *x509.Certifi return FileSystemSigner{privateKey, certificate, certificateChain}, signingAlgorithm, nil } - -func GetPKCS12Signer(certificateId string, certificateChain []*x509.Certificate) (signer Signer, signingAlgorithm string, err error) { - bytes, err := os.ReadFile(certificateId) - if err != nil { - return nil, "", err - } - privateKey, certificate, err := pkcs12.Decode(bytes, "") - if err != nil { - return nil, "", err - } - if privateKey == nil { - return nil, "", errors.New("PKCS#12 has no private key") - } - - rsaPrivateKey, ok := privateKey.(*rsa.PrivateKey) - if ok { - signingAlgorithm = aws4_x509_rsa_sha256 - return FileSystemSigner{*rsaPrivateKey, certificate, nil}, signingAlgorithm, nil - } - - ecPrivateKey, ok := privateKey.(*ecdsa.PrivateKey) - if ok { - signingAlgorithm = aws4_x509_ecdsa_sha256 - return FileSystemSigner{*ecPrivateKey, certificate, certificateChain}, signingAlgorithm, nil - } - - return nil, "", errors.New("unsupported algorithm on PKCS#12 key") -} diff --git a/aws_signing_helper/signer.go b/aws_signing_helper/signer.go index 8ce5a19..938e006 100644 --- a/aws_signing_helper/signer.go +++ b/aws_signing_helper/signer.go @@ -25,6 +25,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/request" + "golang.org/x/crypto/pkcs12" ) type SignerParams struct { @@ -160,6 +161,7 @@ func GetSigner(opts *CredentialsOpts) (signer Signer, signatureAlgorithm string, var ( certificate *x509.Certificate certificateChain []*x509.Certificate + privateKey crypto.PrivateKey ) privateKeyId := opts.PrivateKeyId @@ -184,16 +186,6 @@ func GetSigner(opts *CredentialsOpts) (signer Signer, signatureAlgorithm string, return GetPKCS11Signer(opts.LibPkcs11, certificate, opts.PrivateKeyId, opts.CertificateId) } - if opts.CertificateBundleId != "" { - certificateChainPointers, err := ReadCertificateBundleData(opts.CertificateBundleId) - if err != nil { - return nil, "", err - } - for _, certificate := range certificateChainPointers { - certificateChain = append(certificateChain, certificate) - } - } - if opts.CertificateId != "" { certificateData, err := ReadCertificateData(opts.CertificateId) if err == nil { @@ -209,19 +201,44 @@ func GetSigner(opts *CredentialsOpts) (signer Signer, signatureAlgorithm string, if Debug { log.Println("not a PEM certificate, so trying PKCS#12") } - // TODO: Allow for certificate chain to be specified with PKCS#12 integration. if opts.CertificateBundleId != "" { return nil, "", errors.New("can't specify certificate chain when" + - " using PKCS#12 files") + " using PKCS#12 files; certificate bundle should be provided" + + " within the PKCS#12 file") } // Not a PEM certificate? Try PKCS#12 - return GetPKCS12Signer(opts.CertificateId, certificateChain) + certificateChain, privateKey, err = ReadPKCS12Data(opts.CertificateId) + if err != nil { + return nil, "", err + } + if privateKey != nil { + ecPrivateKeyPtr, isEcKey := privateKey.(*ecdsa.PrivateKey) + if isEcKey { + privateKey = *ecPrivateKeyPtr + } + + rsaPrivateKeyPtr, isRsaKey := privateKey.(*rsa.PrivateKey) + if isRsaKey { + privateKey = *rsaPrivateKeyPtr + } + } + return GetFileSystemSigner(privateKey, certificateChain[0], certificateChain) } else { return nil, "", err } } - privateKey, err := ReadPrivateKeyData(privateKeyId) + if opts.CertificateBundleId != "" { + certificateChainPointers, err := ReadCertificateBundleData(opts.CertificateBundleId) + if err != nil { + return nil, "", err + } + for _, certificate := range certificateChainPointers { + certificateChain = append(certificateChain, certificate) + } + } + + privateKey, err = ReadPrivateKeyData(privateKeyId) if err != nil { return nil, "", err } @@ -574,7 +591,48 @@ func readPKCS8PrivateKey(privateKeyId string) (crypto.PrivateKey, error) { return *ecPrivateKey, nil } - return nil, errors.New("could not parse PKCS8 private key") + return nil, errors.New("could not parse PKCS#8 private key") +} + +// Reads and parses a PKCS#12 file (which should contain an end-entity +// certificate, (optional) certificate chain, and the key associated with the +// end-entity certificate). The end-entity certificate will be returned as the +// first certificate in the returned chain. +func ReadPKCS12Data(certificateId string) (certChain []*x509.Certificate, privateKey crypto.PrivateKey, err error) { + var ( + bytes []byte + pemBlocks []*pem.Block + ) + + bytes, err = os.ReadFile(certificateId) + if err != nil { + return nil, nil, nil + } + + pemBlocks, err = pkcs12.ToPEM(bytes, "") + if err != nil { + return nil, "", err + } + + for _, block := range pemBlocks { + cert, err := x509.ParseCertificate(block.Bytes) + if err == nil { + certChain = append(certChain, cert) + continue + } + privateKeyTmp, err := ReadPrivateKeyDataFromPEMBlock(block) + if err == nil { + privateKey = privateKeyTmp + continue + } + // If neither a certificate nor a private key could be parsed from the + // Block, ignore it and continue. + if Debug { + log.Println("unable to parse PEM block in PKCS#12 file - skipping") + } + } + + return certChain, privateKey, nil } // Load the private key referenced by `privateKeyId`. @@ -594,6 +652,21 @@ func ReadPrivateKeyData(privateKeyId string) (crypto.PrivateKey, error) { return nil, errors.New("unable to parse private key") } +// Reads private key data from a *pem.Block. +func ReadPrivateKeyDataFromPEMBlock(block *pem.Block) (key crypto.PrivateKey, err error) { + key, err = x509.ParseECPrivateKey(block.Bytes) + if err == nil { + return key, nil + } + + key, err = x509.ParsePKCS1PrivateKey(block.Bytes) + if err == nil { + return key, nil + } + + return nil, errors.New("unable to parse private key") +} + // Load the certificate referenced by `certificateId` and extract // details required by the SDK to construct the StringToSign. func ReadCertificateData(certificateId string) (CertificateData, error) { From 975b55a3f8503e75a73310764797242473bed437 Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Tue, 15 Aug 2023 00:19:22 -0400 Subject: [PATCH 16/40] Fetch resources upfront when creating PKCS11Signer * Fetch and save resources (that may not be strictly required for the signer) like the certificate chain when creating the PKCS11Signer. * Update certificate and private key URIs based on where they are first found when creating the PKCS11Signer. * Fix certificate chain lookup - requires testing. --- aws_signing_helper/credentials.go | 5 - .../darwin_cert_store_signer.go | 3 - aws_signing_helper/file_system_signer.go | 3 - aws_signing_helper/pkcs11_signer.go | 357 +++++++----------- aws_signing_helper/signer.go | 35 +- aws_signing_helper/signer_test.go | 3 +- .../windows_cert_store_signer.go | 3 - 7 files changed, 151 insertions(+), 258 deletions(-) diff --git a/aws_signing_helper/credentials.go b/aws_signing_helper/credentials.go index 109283c..54c47a9 100644 --- a/aws_signing_helper/credentials.go +++ b/aws_signing_helper/credentials.go @@ -34,11 +34,6 @@ type CredentialsOpts struct { // Function to create session and generate credentials func GenerateCredentials(opts *CredentialsOpts, signer Signer, signatureAlgorithm string) (CredentialProcessOutput, error) { - // If there are resources that need to be released after using the Signer - // to perform a series of operations that are required in order to get - // temporary credentials, that is done here. - defer signer.CloseSession() - // Assign values to region and endpoint if they haven't already been assigned trustAnchorArn, err := arn.Parse(opts.TrustAnchorArnStr) if err != nil { diff --git a/aws_signing_helper/darwin_cert_store_signer.go b/aws_signing_helper/darwin_cert_store_signer.go index e075b43..fe808be 100644 --- a/aws_signing_helper/darwin_cert_store_signer.go +++ b/aws_signing_helper/darwin_cert_store_signer.go @@ -263,9 +263,6 @@ func (signer *DarwinCertStoreSigner) Close() { } } -// Unused method for DarwinCertStoreSigner. -func (signer *DarwinCertStoreSigner) CloseSession() {} - // Sign implements the crypto.Signer interface and signs the digest func (signer *DarwinCertStoreSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { var hash []byte diff --git a/aws_signing_helper/file_system_signer.go b/aws_signing_helper/file_system_signer.go index b586986..ba0e365 100644 --- a/aws_signing_helper/file_system_signer.go +++ b/aws_signing_helper/file_system_signer.go @@ -36,9 +36,6 @@ func (fileSystemSigner FileSystemSigner) Public() crypto.PublicKey { func (fileSystemSigner FileSystemSigner) Close() {} -// Unused method for FileSystemSigner. -func (fileSystemSigner FileSystemSigner) CloseSession() {} - func (fileSystemSigner FileSystemSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) { var hash []byte switch opts.HashFunc() { diff --git a/aws_signing_helper/pkcs11_signer.go b/aws_signing_helper/pkcs11_signer.go index 19cec2f..bb96aed 100644 --- a/aws_signing_helper/pkcs11_signer.go +++ b/aws_signing_helper/pkcs11_signer.go @@ -56,9 +56,10 @@ 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 + id []byte + label []byte + cert *x509.Certificate + certObject pkcs11.ObjectHandle } // Used to enumerate slots with all token/slot info for matching. @@ -69,7 +70,7 @@ type SlotIdInfo struct { } type PKCS11Signer struct { - certObj CertObjInfo + cert *x509.Certificate certChain []*x509.Certificate module *pkcs11.Ctx userPin string @@ -77,13 +78,6 @@ type PKCS11Signer struct { contextSpecificPin string certUri *pkcs11uri.Pkcs11URI keyUri *pkcs11uri.Pkcs11URI - session pkcs11.SessionHandle - keyType uint - privateKeyHandle pkcs11.ObjectHandle - loggedIn bool - certSlotNr uint - keyInDiffSlot bool - slots []SlotIdInfo } // Initialize a PKCS#11 module. @@ -274,6 +268,8 @@ func getCertsInSession(module *pkcs11.Ctx, slotId uint, session pkcs11.SessionHa var certObj CertObjInfo + certObj.certObject = certObject + certObj.cert, err = x509.ParseCertificate(rawCert) // nosemgrep if err != nil { return nil, errors.New("error parsing certificate") @@ -511,67 +507,18 @@ func (pkcs11Signer *PKCS11Signer) Public() crypto.PublicKey { // Closes this PKCS11Signer. func (pkcs11Signer *PKCS11Signer) Close() { - var ( - module *pkcs11.Ctx - session pkcs11.SessionHandle - loggedIn bool - ) + var module *pkcs11.Ctx module = pkcs11Signer.module - session = pkcs11Signer.session - loggedIn = pkcs11Signer.loggedIn if module != nil { - if session != 0 { - if loggedIn { - module.Logout(session) - } - module.CloseSession(session) - } module.Finalize() module.Destroy() } - pkcs11Signer.session = 0 - pkcs11Signer.certSlotNr = 0 - pkcs11Signer.privateKeyHandle = 0 - pkcs11Signer.alwaysAuth = 0 - pkcs11Signer.loggedIn = false - pkcs11Signer.slots = nil pkcs11Signer.module = nil } -// Sometimes, it may be preferable to leave sessions open since there are -// multiple functions that the PKCS11Signer has to perform that require -// sessions to be left open. After each of those functions are run, -// CloseSession can be called. -func (pkcs11Signer *PKCS11Signer) CloseSession() { - var ( - module *pkcs11.Ctx - session pkcs11.SessionHandle - loggedIn bool - ) - - module = pkcs11Signer.module - session = pkcs11Signer.session - - if module != nil { - if session != 0 { - if loggedIn { - module.Logout(session) - } - module.CloseSession(session) - } - } - - pkcs11Signer.session = 0 - pkcs11Signer.certSlotNr = 0 - pkcs11Signer.privateKeyHandle = 0 - pkcs11Signer.alwaysAuth = 0 - pkcs11Signer.loggedIn = false - pkcs11Signer.slots = 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 @@ -611,7 +558,7 @@ func pkcs11PasswordPrompt(module *pkcs11.Ctx, session pkcs11.SessionHandle, user return pin, nil } - // Code should never reach here + // The code should never reach here. return "", fmt.Errorf("unexpected error when prompting for %s", passwordName) } @@ -629,7 +576,7 @@ func GetPassword(ttyFile *os.File, prompt string, parseErrMsg string) (string, e } // Helper function to sign a digest using a PKCS#11 private key handle. -func signHelper(module *pkcs11.Ctx, session pkcs11.SessionHandle, privateKeyHandle pkcs11.ObjectHandle, alwaysAuth uint, pkcs11Pin string, keyType uint, digest []byte, hashFunc crypto.Hash) (contextSpecificPin string, signature []byte, err error) { +func signHelper(module *pkcs11.Ctx, session pkcs11.SessionHandle, privateKeyHandle pkcs11.ObjectHandle, alwaysAuth uint, firstSigningOperation bool, userPin string, contextSpecificPin string, 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 @@ -671,8 +618,15 @@ func signHelper(module *pkcs11.Ctx, session pkcs11.SessionHandle, privateKeyHand } if alwaysAuth != 0 { - if pkcs11Pin != "" { - err = module.Login(session, pkcs11.CKU_CONTEXT_SPECIFIC, pkcs11Pin) + // 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 + // and this is the first signing operation with this particular key, + // use the user PIN as the context-specific PIN. + if contextSpecificPin == "" && userPin != "" && firstSigningOperation { + contextSpecificPin = userPin + } + if contextSpecificPin != "" { + err = module.Login(session, pkcs11.CKU_CONTEXT_SPECIFIC, contextSpecificPin) if err == nil { goto afterContextSpecificLogin } else { @@ -681,6 +635,9 @@ func signHelper(module *pkcs11.Ctx, session pkcs11.SessionHandle, privateKeyHand } } } + + // If the context-specific PIN couldn't be derived, prompt the user for + // the context-specific PIN for this object. passwordName := "context-specific pin" finalAuthErrMsg := "user re-authentication failed (%s)" _, err = pkcs11PasswordPrompt(module, session, pkcs11.CKU_CONTEXT_SPECIFIC, passwordName, finalAuthErrMsg) @@ -692,23 +649,23 @@ func signHelper(module *pkcs11.Ctx, session pkcs11.SessionHandle, privateKeyHand afterContextSpecificLogin: sig, err := module.Sign(session, digest) if err != nil { - return pkcs11Pin, nil, fmt.Errorf("signing failed (%s)", err.Error()) + 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 pkcs11Pin, nil, err + return contextSpecificPin, nil, err } } - return pkcs11Pin, sig, nil + 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, certSlotNr uint, certObj CertObjInfo, userPin string, contextSpecificPin string, slots []SlotIdInfo) (_session pkcs11.SessionHandle, keyInDiffSlot bool, keyType uint, privateKeyHandle pkcs11.ObjectHandle, alwaysAuth uint, _contextSpecificPin string, err error) { +func getPKCS11Key(module *pkcs11.Ctx, session pkcs11.SessionHandle, loggedIn bool, certUri *pkcs11uri.Pkcs11URI, keyUri *pkcs11uri.Pkcs11URI, certSlotNr uint, certObj CertObjInfo, userPin string, contextSpecificPin string, slots []SlotIdInfo) (_session pkcs11.SessionHandle, _keyUri *pkcs11uri.Pkcs11URI, keyType uint, privateKeyHandle pkcs11.ObjectHandle, alwaysAuth uint, _contextSpecificPin string, err error) { var ( keySlotNr uint manufacturerId string @@ -727,7 +684,6 @@ func getPKCS11Key(module *pkcs11.Ctx, session pkcs11.SessionHandle, loggedIn boo slots = matchSlots(slots, keyUri) if len(slots) == 1 { if certSlotNr != slots[0].id { - keyInDiffSlot = true keySlotNr = slots[0].id manufacturerId = slots[0].info.ManufacturerID if session != 0 { @@ -877,15 +833,29 @@ retry_search: goto fail } - return session, keyInDiffSlot, keyType, privateKeyHandle, alwaysAuth, contextSpecificPin, nil + // So that hunting for the key can be more efficient in the future, + // return a key URI that has CKA_ID and CKA_VALUE appropriately set. + keyAttributes[0] = pkcs11.NewAttribute(pkcs11.CKA_ID, 0) + keyAttributes, err = module.GetAttributeValue(session, privateKeyHandle, keyAttributes) + if err == nil { + keyUri.SetPathAttribute("id", escapeAll(keyAttributes[0].Value)) + } + + keyAttributes[0] = pkcs11.NewAttribute(pkcs11.CKA_LABEL, 0) + keyAttributes, err = module.GetAttributeValue(session, privateKeyHandle, keyAttributes) + if err == nil { + keyUri.SetPathAttribute("object", escapeAll(keyAttributes[0].Value)) + } + + return session, keyUri, keyType, privateKeyHandle, alwaysAuth, contextSpecificPin, nil fail: - return 0, false, 0, 0, 0, "", err + return 0, nil, 0, 0, 0, "", err } -// Gets the certificate with a token, given the URI that identifies the +// 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) the token has to be +// 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 ( @@ -922,127 +892,72 @@ func (pkcs11Signer *PKCS11Signer) Sign(rand io.Reader, digest []byte, opts crypt loggedIn bool alwaysAuth uint certSlot SlotIdInfo - keyInDiffSlot bool ) hashFunc := opts.HashFunc() - // The module is the only one that's guaranteed to be initialized properly module = pkcs11Signer.module userPin = pkcs11Signer.userPin + alwaysAuth = pkcs11Signer.alwaysAuth contextSpecificPin = pkcs11Signer.contextSpecificPin certUri = pkcs11Signer.certUri keyUri = pkcs11Signer.keyUri - privateKeyHandle = pkcs11Signer.privateKeyHandle - keyType = pkcs11Signer.keyType - certObj = pkcs11Signer.certObj - session = pkcs11Signer.session - loggedIn = pkcs11Signer.loggedIn - slots = pkcs11Signer.slots - alwaysAuth = pkcs11Signer.alwaysAuth - - if privateKeyHandle != 0 { - goto gotPrivateKey - } - - if certSlotNr != 0 { - goto gotCert - } + // If a PKCS#11 URI was provided for the certificate, use it. if certUri != nil { + userPin, _ = certUri.GetQueryAttribute("pin-value", false) certSlot, slots, session, loggedIn, certObj, err = getCertificate(module, certUri, userPin) if err != nil { - goto fail + goto cleanUp } + certSlotNr = certSlot.id + } - // Save the values we need after finding the certificate. - pkcs11Signer.slots = slots - pkcs11Signer.session = session - pkcs11Signer.loggedIn = loggedIn - pkcs11Signer.certSlotNr = certSlot.id - pkcs11Signer.certObj = certObj - } else { - pkcs11Signer.slots, err = enumerateSlotsInPKCS11Module(module) + userPin, _ = keyUri.GetQueryAttribute("pin-value", false) + + // Otherwise, if the certificate's PKCS#11 URI wasn't provided, enumerate slots. + if certUri == nil { + slots, err = enumerateSlotsInPKCS11Module(module) if err != nil { - goto fail + goto cleanUp } } - // These variables' values could have been updated by the above function - // calls, so update them. - slots = pkcs11Signer.slots - certObj = pkcs11Signer.certObj - loggedIn = pkcs11Signer.loggedIn - certSlotNr = pkcs11Signer.certSlotNr - -gotCert: - session, keyInDiffSlot, keyType, privateKeyHandle, alwaysAuth, contextSpecificPin, err = getPKCS11Key(module, session, loggedIn, certUri, keyUri, certSlotNr, certObj, userPin, contextSpecificPin, slots) + session, keyUri, keyType, privateKeyHandle, alwaysAuth, contextSpecificPin, err = getPKCS11Key(module, session, loggedIn, certUri, keyUri, certSlotNr, certObj, userPin, contextSpecificPin, slots) if err != nil { - goto fail + goto cleanUp } - // Save the values we need after finding the key. - pkcs11Signer.session = session - pkcs11Signer.keyType = keyType - pkcs11Signer.privateKeyHandle = privateKeyHandle - pkcs11Signer.alwaysAuth = alwaysAuth - pkcs11Signer.contextSpecificPin = contextSpecificPin - pkcs11Signer.loggedIn = true - pkcs11Signer.keyInDiffSlot = keyInDiffSlot - -gotPrivateKey: - // We only care about the context-specific PIN when it comes to signing with - // objects that are marked with CKA_ALWAYS_AUTHENTICATE, which (from my - // understanding) can be different from the user PIN. If the context-specific - // PIN has been saved already, use it. Otherwise, default to the user PIN. - if contextSpecificPin == "" { - contextSpecificPin = pkcs11Signer.userPin + _, signature, err = signHelper(module, session, privateKeyHandle, alwaysAuth, false, userPin, contextSpecificPin, keyType, digest, hashFunc) + if err != nil { + goto cleanUp } - _, signature, err = signHelper(module, session, privateKeyHandle, alwaysAuth, contextSpecificPin, keyType, digest, hashFunc) - if err != nil { - goto fail + // 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) } -fail: return signature, err } // Gets the *x509.Certificate associated with this PKCS11Signer. -func (pkcs11Signer *PKCS11Signer) Certificate() (certificate *x509.Certificate, err error) { - var ( - module *pkcs11.Ctx - userPin string - loggedIn bool - certSlot SlotIdInfo - certObj CertObjInfo - session pkcs11.SessionHandle - certUri *pkcs11uri.Pkcs11URI - cert *x509.Certificate - slots []SlotIdInfo - ) - - module = pkcs11Signer.module - cert = pkcs11Signer.certObj.cert +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 } - // Otherwise, get the certificate. - certSlot, slots, session, loggedIn, certObj, err = getCertificate(module, certUri, userPin) - if err != nil { - return nil, err - } - - pkcs11Signer.slots = slots - pkcs11Signer.session = session - pkcs11Signer.loggedIn = loggedIn - pkcs11Signer.certSlotNr = certSlot.id - pkcs11Signer.certObj = certObj - - return pkcs11Signer.certObj.cert, nil + return nil, errors.New("no certificate associated with signer") } // Checks whether the first certificate issues the second. @@ -1058,55 +973,15 @@ func certIssues(issuer *x509.Certificate, candidate *x509.Certificate) bool { return err != nil } -// Gets the certificate chain associated with this PKCS11Signer. -func (pkcs11Signer *PKCS11Signer) CertificateChain() (chain []*x509.Certificate, err error) { +// 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 ( - module *pkcs11.Ctx - session pkcs11.SessionHandle - certChain []*x509.Certificate - certsFound []CertObjInfo - cert *x509.Certificate - certUri *pkcs11uri.Pkcs11URI - certSlotNr uint - keyInDiffSlot bool + certsFound []CertObjInfo ) - module = pkcs11Signer.module - certChain = pkcs11Signer.certChain - certUri = pkcs11Signer.certUri - - if certChain != nil { - return certChain, nil - } - - if certUri == nil { - return nil, errors.New("signer created using only certificate; " + - "unable to get certificate chain") - } - - // If there is currently no open session, then this method will open it. - cert, err = pkcs11Signer.Certificate() - if err != nil { - return nil, err - } - - chain = append(chain, cert) - certSlotNr = pkcs11Signer.certSlotNr - keyInDiffSlot = pkcs11Signer.keyInDiffSlot - - // If the certificate and key were found in different slots, the stored - // session will be one in which the key was found. In that case, the - // rest of the certificate chain should be searched for in the same slot - // that the certificate was found. - if !keyInDiffSlot { - session = pkcs11Signer.session - } else { - session, err = module.OpenSession(certSlotNr, pkcs11.CKF_SERIAL_SESSION|pkcs11.CKS_RO_PUBLIC_SESSION) - if err != nil { - return nil, err - } - defer module.CloseSession(session) - } + // 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 { @@ -1116,12 +991,12 @@ func (pkcs11Signer *PKCS11Signer) CertificateChain() (chain []*x509.Certificate, for true { nextInChainFound := false for i, curCert := range certsFound { - curLastCert := chain[len(chain)-1] - if certIssues(curLastCert, curCert.cert) { + lastCertChainCert := certChain[len(certChain)-1] + if certIssues(curCert.cert, lastCertChainCert) { nextInChainFound = true - chain = append(chain, curCert.cert) + certChain = append(certChain, curCert.cert) - // Remove current cert, so that it won't be iterated again + // Remove current cert, so that it won't be iterated again. lastIndex := len(certsFound) - 1 certsFound[i] = certsFound[lastIndex] certsFound = certsFound[:lastIndex] @@ -1134,13 +1009,24 @@ func (pkcs11Signer *PKCS11Signer) CertificateChain() (chain []*x509.Certificate, } } - pkcs11Signer.certChain = chain + return certChain, err +} + +// Gets the certificate chain associated with this PKCS11Signer. +func (pkcs11Signer *PKCS11Signer) CertificateChain() (certChain []*x509.Certificate, err error) { + certChain = pkcs11Signer.certChain - return chain, err + // 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, alwaysAuth uint, pinPkcs11 string, privateKeyHandle pkcs11.ObjectHandle, certificate *x509.Certificate, manufacturerId string) (string, bool) { +func checkPrivateKeyMatchesCert(module *pkcs11.Ctx, session pkcs11.SessionHandle, keyType uint, alwaysAuth uint, userPin string, privateKeyHandle pkcs11.ObjectHandle, certificate *x509.Certificate, manufacturerId string) (string, bool) { var digestSuffix []byte publicKey := certificate.PublicKey ecdsaPublicKey, isEcKey := publicKey.(*ecdsa.PublicKey) @@ -1167,7 +1053,7 @@ func checkPrivateKeyMatchesCert(module *pkcs11.Ctx, session pkcs11.SessionHandle digestBytes := []byte(digest) hash := sha256.Sum256(digestBytes) - contextSpecificPin, signature, err := signHelper(module, session, privateKeyHandle, alwaysAuth, pinPkcs11, keyType, digestBytes, crypto.SHA256) + contextSpecificPin, signature, err := signHelper(module, session, privateKeyHandle, alwaysAuth, true, userPin, "", keyType, digestBytes, crypto.SHA256) if err != nil { return "", false } @@ -1235,13 +1121,12 @@ func escapeAll(s []byte) string { // 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, certificate *x509.Certificate, privateKeyId string, certificateId string) (signer Signer, signingAlgorithm string, err error) { +func GetPKCS11Signer(libPkcs11 string, cert *x509.Certificate, certChain []*x509.Certificate, privateKeyId string, certificateId string) (signer Signer, signingAlgorithm string, err error) { var ( module *pkcs11.Ctx certObj CertObjInfo session pkcs11.SessionHandle loggedIn bool - privateKeyHandle pkcs11.ObjectHandle keyType uint contextSpecificPin string userPin string @@ -1251,7 +1136,6 @@ func GetPKCS11Signer(libPkcs11 string, certificate *x509.Certificate, privateKey keyUri *pkcs11uri.Pkcs11URI slots []SlotIdInfo certSlot SlotIdInfo - keyInDiffSlot bool ) module, err = initializePKCS11Module(libPkcs11) @@ -1260,7 +1144,7 @@ func GetPKCS11Signer(libPkcs11 string, certificate *x509.Certificate, privateKey } // If a PKCS#11 URI was provided for the certificate, find it. - if certificate == nil && certificateId != "" { + if cert == nil && certificateId != "" { certUri = pkcs11uri.New() err = certUri.Parse(certificateId) if err != nil { @@ -1272,10 +1156,33 @@ func GetPKCS11Signer(libPkcs11 string, certificate *x509.Certificate, privateKey goto fail } certSlotNr = certSlot.id - certificate = certObj.cert + 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[0] = 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 + } + } } - // If no explicit private-key option was given, use it. Otherwise + // 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 != "" { @@ -1297,11 +1204,10 @@ func GetPKCS11Signer(libPkcs11 string, certificate *x509.Certificate, privateKey } } - session, keyInDiffSlot, keyType, privateKeyHandle, alwaysAuth, contextSpecificPin, err = getPKCS11Key(module, session, loggedIn, certUri, keyUri, certSlotNr, certObj, userPin, contextSpecificPin, slots) + session, keyUri, keyType, _, alwaysAuth, contextSpecificPin, err = getPKCS11Key(module, session, loggedIn, certUri, keyUri, certSlotNr, certObj, userPin, contextSpecificPin, slots) if err != nil { goto fail } - loggedIn = true switch keyType { case pkcs11.CKK_EC: @@ -1312,7 +1218,14 @@ func GetPKCS11Signer(libPkcs11 string, certificate *x509.Certificate, privateKey return nil, "", errors.New("unsupported algorithm") } - return &PKCS11Signer{certObj, nil, module, userPin, alwaysAuth, contextSpecificPin, certUri, keyUri, session, keyType, privateKeyHandle, loggedIn, certSlotNr, keyInDiffSlot, slots}, signingAlgorithm, nil + if session != 0 { + if loggedIn { + module.Logout(session) + } + module.CloseSession(session) + } + + return &PKCS11Signer{cert, certChain, module, userPin, alwaysAuth, contextSpecificPin, certUri, keyUri}, signingAlgorithm, nil fail: if module != nil { diff --git a/aws_signing_helper/signer.go b/aws_signing_helper/signer.go index 938e006..db43560 100644 --- a/aws_signing_helper/signer.go +++ b/aws_signing_helper/signer.go @@ -55,7 +55,6 @@ type Signer interface { Certificate() (certificate *x509.Certificate, err error) CertificateChain() (certificateChain []*x509.Certificate, err error) Close() - CloseSession() } // Container for certificate data returned to the SDK as JSON. @@ -175,18 +174,7 @@ func GetSigner(opts *CredentialsOpts) (signer Signer, signatureAlgorithm string, privateKeyId = opts.CertificateId } - if strings.HasPrefix(privateKeyId, "pkcs11:") { - if Debug { - log.Println("attempting to use PKCS11Signer") - } - if opts.CertificateBundleId != "" { - return nil, "", errors.New("can't specify certificate chain when" + - " using PKCS#11 integration") - } - return GetPKCS11Signer(opts.LibPkcs11, certificate, opts.PrivateKeyId, opts.CertificateId) - } - - if opts.CertificateId != "" { + if opts.CertificateId != "" && !strings.HasPrefix(opts.CertificateId, "pkcs11:") { certificateData, err := ReadCertificateData(opts.CertificateId) if err == nil { certificateDerData, err := base64.StdEncoding.DecodeString(certificateData.CertificateData) @@ -238,15 +226,22 @@ func GetSigner(opts *CredentialsOpts) (signer Signer, signatureAlgorithm string, } } - privateKey, err = ReadPrivateKeyData(privateKeyId) - if err != nil { - return nil, "", err - } + if strings.HasPrefix(privateKeyId, "pkcs11:") { + if Debug { + log.Println("attempting to use PKCS11Signer") + } + return GetPKCS11Signer(opts.LibPkcs11, certificate, certificateChain, opts.PrivateKeyId, opts.CertificateId) + } else { + privateKey, err = ReadPrivateKeyData(privateKeyId) + if err != nil { + return nil, "", err + } - if Debug { - log.Println("attempting to use FileSystemSigner") + if Debug { + log.Println("attempting to use FileSystemSigner") + } + return GetFileSystemSigner(privateKey, certificate, certificateChain) } - return GetFileSystemSigner(privateKey, certificate, certificateChain) } // Obtain the date-time, formatted as specified by SigV4 diff --git a/aws_signing_helper/signer_test.go b/aws_signing_helper/signer_test.go index fad4419..0b07d6f 100644 --- a/aws_signing_helper/signer_test.go +++ b/aws_signing_helper/signer_test.go @@ -292,10 +292,9 @@ func TestSign(t *testing.T) { t.Fail() return } - signer.CloseSession() _, err = signer.Sign(rand.Reader, []byte(msg), digest) if err != nil { - t.Log("Failed second signature on the input message after signer.CloseSession()") + t.Log("Failed second signature on the input message") t.Fail() return } diff --git a/aws_signing_helper/windows_cert_store_signer.go b/aws_signing_helper/windows_cert_store_signer.go index d6c6c2e..27a0e54 100644 --- a/aws_signing_helper/windows_cert_store_signer.go +++ b/aws_signing_helper/windows_cert_store_signer.go @@ -293,9 +293,6 @@ func (signer *WindowsCertStoreSigner) Close() { signer.store = 0 } -// Unused method for DarwinCertStoreSigner. -func (signer *WindowsCertStoreSigner) CloseSession() {} - // getPrivateKey gets this identity's private *winPrivateKey func (signer *WindowsCertStoreSigner) getPrivateKey() (*winPrivateKey, error) { if signer.privateKey != nil { From e02b1db33c34f5ed95e7c1ce3ebe62c500b76c05 Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Tue, 15 Aug 2023 12:23:10 -0400 Subject: [PATCH 17/40] README updates for PKCS#11 integration --- README.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e821513..2942da0 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ Signs a fixed strings: `"AWS Roles Anywhere Credential Helper Signing Test" || S ### credential-process -Vends temporary credentials by sending a `CreateSession` request to the Roles Anywhere service. The request is signed by the private key whose path can be provided with the `--private-key` parameter. Other parameters include `--certificate` (the path to the end-entity certificate), `--role-arn` (the ARN of the role to obtain temporary credentials for), `--profile-arn` (the ARN of the profile that provides a mapping for the specified role), and `--trust-anchor-arn` (the ARN of the trust anchor used to authenticate). Optional parameters that can be used are `--debug` (to provide debugging output about the request sent), `--no-verify-ssl` (to skip verification of the SSL certificate on the endpoint called), `--intermediates` (the path to intermediate certificates), `--with-proxy` (to make the binary proxy aware), `--endpoint` (the endpoint to call), `--region` (the region to scope the request to), and `--session-duration` (the duration of the vended session). Instead of passing in paths to the plaintext private key on your file system, another option (depending on your OS) could be to use the `--cert-selector` flag. More details can be found below. +Vends temporary credentials by sending a `CreateSession` request to the Roles Anywhere service. The request is signed by the private key whose path can be provided with the `--private-key` parameter. Currently, only plaintext private keys are supported. Other parameters include `--certificate` (the path to the end-entity certificate), `--role-arn` (the ARN of the role to obtain temporary credentials for), `--profile-arn` (the ARN of the profile that provides a mapping for the specified role), and `--trust-anchor-arn` (the ARN of the trust anchor used to authenticate). Optional parameters that can be used are `--debug` (to provide debugging output about the request sent), `--no-verify-ssl` (to skip verification of the SSL certificate on the endpoint called), `--intermediates` (the path to intermediate certificates), `--with-proxy` (to make the binary proxy aware), `--endpoint` (the endpoint to call), `--region` (the region to scope the request to), and `--session-duration` (the duration of the vended session). Instead of passing in paths to the plaintext private key on your file system, another option (depending on your OS) could be to use the `--cert-selector` flag. More details can be found below. Note that if more than one certificate matches the `--cert-selector` parameter within the OS-specific secure store, the `credential-process` command will fail. To find the list of certificates that match a given `--cert-selector` parameter, you can use the same flag with the `read-certificate-data` command. @@ -134,30 +134,37 @@ Also note that the above step can be done through a [Powershell cmdlet](https:// As you should expect from all applications which use keys and certificates, you can simply give a [PKCS#11 URI](https://datatracker.ietf.org/doc/html/rfc7512) in place of a filename in order to use certificates and/or keys from hardware or software PKCS#11 tokens / HSMs. A hybrid mode -using a certificate from a file but only the key in PKCS#11 is also supported. Some examples: +using a certificate from a file but only the key in the token is also supported. Some examples: * `--certificate 'pkcs11:manufacturer=piv_II;id=%01'` * `--certificate 'pkcs11:object=My%20RA%20key'` * `--certificate client-cert.pem --private-key 'pkcs11:model=SoftHSM%20v2;object=My%20RA%20key'` Some documentation which may assist with finding the correct URI for -your key can be found [here](https://www.infradead.org/openconnect/pkcs11.html). +your key can be found [here](https://www.infradead.org/openconnect/pkcs11.html). Otherwise, you +can also potentially scope down your PKCS#11 URI by using the `read-certificate-data` diagnostic +command. Most Linux and similar *nix systems use [p11-kit](https://p11-glue.github.io/p11-glue/p11-kit/manual/config.html) to provide consistent system-wide and per-user configuration of available PKCS#11 providers. Any properly packaged provider module will register itself with p11-kit and will be automatically visible -through the `p11-kit-proxy.so` provider which is used by default. +through the `p11-kit-proxy.{dylib, dll, so}` provider which is used by default. If you have a poorly packaged provider module from a vendor, then -after you have filed a bug you can manually create a p11-kit [module +after you have filed a bug, you can manually create a p11-kit [module file](https://p11-glue.github.io/p11-glue/p11-kit/manual/pkcs11-conf.html) for it. For systems or containers which lack p11-kit, a specific PKCS#11 provider library can be specified using the `--pkcs11-lib` parameter. +The searching methodology used to find objects within PKCS#11 tokens can be found +[here](https://datatracker.ietf.org/doc/html/draft-woodhouse-cert-best-practice-01). Note that +there are some differences in how tokens are searched in the credential helper, but +it should mostly follow that document. + ### update Updates temporary credentials in the [credential file](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html). Parameters for this command include those for the `credential-process` command, as well as `--profile`, which specifies the named profile for which credentials should be updated (if the profile doesn't already exist, it will be created), and `--once`, which specifies that credentials should be updated only once. Both arguments are optional. If `--profile` isn't specified, the default profile will have its credentials updated, and if `--once` isn't specified, credentials will be continuously updated. In this case, credentials will be updated through a call to `CreateSession` five minutes before the previous set of credentials are set to expire. Please note that running the `update` command multiple times, creating multiple processes, may not work as intended. There may be issues with concurrent writes to the credentials file. From d7aaea06299c2cb810445d23851a470607a5e9d7 Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Tue, 15 Aug 2023 12:24:30 -0400 Subject: [PATCH 18/40] Fix double prompting for PKCS#11 user PIN --- aws_signing_helper/pkcs11_signer.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/aws_signing_helper/pkcs11_signer.go b/aws_signing_helper/pkcs11_signer.go index bb96aed..f8153eb 100644 --- a/aws_signing_helper/pkcs11_signer.go +++ b/aws_signing_helper/pkcs11_signer.go @@ -665,7 +665,7 @@ afterContextSpecificLogin: // 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, certSlotNr uint, certObj CertObjInfo, userPin string, contextSpecificPin string, slots []SlotIdInfo) (_session pkcs11.SessionHandle, _keyUri *pkcs11uri.Pkcs11URI, keyType uint, privateKeyHandle pkcs11.ObjectHandle, alwaysAuth uint, _contextSpecificPin string, err error) { +func getPKCS11Key(module *pkcs11.Ctx, session pkcs11.SessionHandle, loggedIn bool, certUri *pkcs11uri.Pkcs11URI, keyUri *pkcs11uri.Pkcs11URI, certSlotNr uint, certObj CertObjInfo, userPin string, contextSpecificPin string, slots []SlotIdInfo) (_session pkcs11.SessionHandle, _userPin string, _keyUri *pkcs11uri.Pkcs11URI, keyType uint, privateKeyHandle pkcs11.ObjectHandle, alwaysAuth uint, _contextSpecificPin string, err error) { var ( keySlotNr uint manufacturerId string @@ -677,7 +677,10 @@ func getPKCS11Key(module *pkcs11.Ctx, session pkcs11.SessionHandle, loggedIn boo if keyUri == nil { keyUri = certUri } - userPin, _ = keyUri.GetQueryAttribute("pin-value", false) + + 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. @@ -723,7 +726,7 @@ got_slot: if userPin == "" { passwordName := "user pin" finalAuthErrMsg := "user authentication failed (%s)" - _, err = pkcs11PasswordPrompt(module, session, pkcs11.CKU_USER, passwordName, finalAuthErrMsg) + userPin, err = pkcs11PasswordPrompt(module, session, pkcs11.CKU_USER, passwordName, finalAuthErrMsg) if err != nil { goto fail } @@ -847,10 +850,10 @@ retry_search: keyUri.SetPathAttribute("object", escapeAll(keyAttributes[0].Value)) } - return session, keyUri, keyType, privateKeyHandle, alwaysAuth, contextSpecificPin, nil + return session, userPin, keyUri, keyType, privateKeyHandle, alwaysAuth, contextSpecificPin, nil fail: - return 0, nil, 0, 0, 0, "", err + return 0, "", nil, 0, 0, 0, "", err } // Gets the certificate in a token, given the URI that identifies the @@ -905,7 +908,6 @@ func (pkcs11Signer *PKCS11Signer) Sign(rand io.Reader, digest []byte, opts crypt // If a PKCS#11 URI was provided for the certificate, use it. if certUri != nil { - userPin, _ = certUri.GetQueryAttribute("pin-value", false) certSlot, slots, session, loggedIn, certObj, err = getCertificate(module, certUri, userPin) if err != nil { goto cleanUp @@ -913,8 +915,6 @@ func (pkcs11Signer *PKCS11Signer) Sign(rand io.Reader, digest []byte, opts crypt certSlotNr = certSlot.id } - userPin, _ = keyUri.GetQueryAttribute("pin-value", false) - // Otherwise, if the certificate's PKCS#11 URI wasn't provided, enumerate slots. if certUri == nil { slots, err = enumerateSlotsInPKCS11Module(module) @@ -923,7 +923,7 @@ func (pkcs11Signer *PKCS11Signer) Sign(rand io.Reader, digest []byte, opts crypt } } - session, keyUri, keyType, privateKeyHandle, alwaysAuth, contextSpecificPin, err = getPKCS11Key(module, session, loggedIn, certUri, keyUri, certSlotNr, certObj, userPin, contextSpecificPin, slots) + session, userPin, keyUri, keyType, privateKeyHandle, alwaysAuth, contextSpecificPin, err = getPKCS11Key(module, session, loggedIn, certUri, keyUri, certSlotNr, certObj, userPin, contextSpecificPin, slots) if err != nil { goto cleanUp } @@ -1204,7 +1204,7 @@ func GetPKCS11Signer(libPkcs11 string, cert *x509.Certificate, certChain []*x509 } } - session, keyUri, keyType, _, alwaysAuth, contextSpecificPin, err = getPKCS11Key(module, session, loggedIn, certUri, keyUri, certSlotNr, certObj, userPin, contextSpecificPin, slots) + session, userPin, keyUri, keyType, _, alwaysAuth, contextSpecificPin, err = getPKCS11Key(module, session, loggedIn, certUri, keyUri, certSlotNr, certObj, userPin, contextSpecificPin, slots) if err != nil { goto fail } From 5e88fcf8b93b01bb27244776e78c0df8f7f9cff4 Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Tue, 15 Aug 2023 15:22:23 -0400 Subject: [PATCH 19/40] Ignore errors when finding certificate chain to include in CreateSession request --- aws_signing_helper/credentials.go | 6 +++++- aws_signing_helper/signer.go | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/aws_signing_helper/credentials.go b/aws_signing_helper/credentials.go index 54c47a9..f071558 100644 --- a/aws_signing_helper/credentials.go +++ b/aws_signing_helper/credentials.go @@ -4,6 +4,7 @@ import ( "crypto/tls" "encoding/base64" "errors" + "log" "net/http" "runtime" @@ -87,7 +88,10 @@ func GenerateCredentials(opts *CredentialsOpts, signer Signer, signatureAlgorith } certificateChain, err := signer.CertificateChain() if err != nil { - return CredentialProcessOutput{}, errors.New("unable to find certificate chain") + // If the chain couldn't be found, don't include it in the request + if Debug { + log.Println(err) + } } rolesAnywhereClient.Handlers.Sign.PushBackNamed(request.NamedHandler{Name: "v4x509.SignRequestHandler", Fn: CreateRequestSignFunction(signer, signatureAlgorithm, certificate, certificateChain)}) diff --git a/aws_signing_helper/signer.go b/aws_signing_helper/signer.go index db43560..69eae27 100644 --- a/aws_signing_helper/signer.go +++ b/aws_signing_helper/signer.go @@ -230,6 +230,9 @@ func GetSigner(opts *CredentialsOpts) (signer Signer, signatureAlgorithm string, if Debug { log.Println("attempting to use PKCS11Signer") } + if certificate != nil { + opts.CertificateId = "" + } return GetPKCS11Signer(opts.LibPkcs11, certificate, certificateChain, opts.PrivateKeyId, opts.CertificateId) } else { privateKey, err = ReadPrivateKeyData(privateKeyId) From 999f6790f9b7fe13ae5c8330012481f63adaa88f Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Tue, 15 Aug 2023 17:45:50 -0400 Subject: [PATCH 20/40] Fix comparison between key URI and cert URI when only the latter is provided --- aws_signing_helper/pkcs11_signer.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/aws_signing_helper/pkcs11_signer.go b/aws_signing_helper/pkcs11_signer.go index f8153eb..90b5527 100644 --- a/aws_signing_helper/pkcs11_signer.go +++ b/aws_signing_helper/pkcs11_signer.go @@ -665,7 +665,7 @@ afterContextSpecificLogin: // 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, certSlotNr uint, certObj CertObjInfo, userPin string, contextSpecificPin string, slots []SlotIdInfo) (_session pkcs11.SessionHandle, _userPin string, _keyUri *pkcs11uri.Pkcs11URI, keyType uint, privateKeyHandle pkcs11.ObjectHandle, alwaysAuth uint, _contextSpecificPin string, err error) { +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, slots []SlotIdInfo) (_session pkcs11.SessionHandle, _userPin string, _keyUri *pkcs11uri.Pkcs11URI, keyType uint, privateKeyHandle pkcs11.ObjectHandle, alwaysAuth uint, _contextSpecificPin string, err error) { var ( keySlotNr uint manufacturerId string @@ -676,6 +676,7 @@ func getPKCS11Key(module *pkcs11.Ctx, session pkcs11.SessionHandle, loggedIn boo if keyUri == nil { keyUri = certUri + noKeyUri = true } if userPin == "" { @@ -814,10 +815,8 @@ retry_search: * * http://david.woodhou.se/draft-woodhouse-cert-best-practice.html#rfc.section.8.2 */ - keyUriStr, _ := keyUri.Format() - certUriStr, _ := certUri.Format() if certObj.cert != nil { - if keyUriStr == certUriStr { + if noKeyUri { _, keyHadLabel := keyUri.GetPathAttribute("object", false) if keyHadLabel { if Debug { @@ -923,7 +922,7 @@ func (pkcs11Signer *PKCS11Signer) Sign(rand io.Reader, digest []byte, opts crypt } } - session, userPin, keyUri, keyType, privateKeyHandle, alwaysAuth, contextSpecificPin, err = getPKCS11Key(module, session, loggedIn, certUri, keyUri, certSlotNr, certObj, userPin, contextSpecificPin, slots) + session, userPin, keyUri, keyType, privateKeyHandle, alwaysAuth, contextSpecificPin, err = getPKCS11Key(module, session, loggedIn, certUri, keyUri, false, certSlotNr, certObj, userPin, contextSpecificPin, slots) if err != nil { goto cleanUp } @@ -1136,6 +1135,7 @@ func GetPKCS11Signer(libPkcs11 string, cert *x509.Certificate, certChain []*x509 keyUri *pkcs11uri.Pkcs11URI slots []SlotIdInfo certSlot SlotIdInfo + noKeyUri bool ) module, err = initializePKCS11Module(libPkcs11) @@ -1193,6 +1193,7 @@ func GetPKCS11Signer(libPkcs11 string, cert *x509.Certificate, certChain []*x509 } } else { keyUri = certUri + noKeyUri = true } userPin, _ = keyUri.GetQueryAttribute("pin-value", false) @@ -1204,7 +1205,7 @@ func GetPKCS11Signer(libPkcs11 string, cert *x509.Certificate, certChain []*x509 } } - session, userPin, keyUri, keyType, _, alwaysAuth, contextSpecificPin, err = getPKCS11Key(module, session, loggedIn, certUri, keyUri, certSlotNr, certObj, userPin, contextSpecificPin, slots) + session, userPin, keyUri, keyType, _, alwaysAuth, contextSpecificPin, err = getPKCS11Key(module, session, loggedIn, certUri, keyUri, noKeyUri, certSlotNr, certObj, userPin, contextSpecificPin, slots) if err != nil { goto fail } From ebc42e3b85bc07b9144304500c28bf86a61b2f5c Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Wed, 16 Aug 2023 17:38:34 -0400 Subject: [PATCH 21/40] Change shallow copy to deep copy from certUri into keyUri in pkcs11_signer.go --- aws_signing_helper/pkcs11_signer.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/aws_signing_helper/pkcs11_signer.go b/aws_signing_helper/pkcs11_signer.go index 90b5527..6e2608e 100644 --- a/aws_signing_helper/pkcs11_signer.go +++ b/aws_signing_helper/pkcs11_signer.go @@ -1192,7 +1192,9 @@ func GetPKCS11Signer(libPkcs11 string, cert *x509.Certificate, certChain []*x509 goto fail } } else { - keyUri = certUri + certUriStr, _ := certUri.Format() + keyUri = pkcs11uri.New() + keyUri.Parse(certUriStr) noKeyUri = true } userPin, _ = keyUri.GetQueryAttribute("pin-value", false) From 9242ab019281c2d6d518798057be13713d6e25e8 Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Wed, 16 Aug 2023 17:48:19 -0400 Subject: [PATCH 22/40] Add --pkcs-lib as a valid flag for read-certificate-data --- cmd/read_certificate_data.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/read_certificate_data.go b/cmd/read_certificate_data.go index 8cfbd66..b995d02 100644 --- a/cmd/read_certificate_data.go +++ b/cmd/read_certificate_data.go @@ -18,6 +18,7 @@ func init() { readCertificateDataCmd.PersistentFlags().StringVar(&certificateId, "certificate", "", "Path to certificate file") readCertificateDataCmd.PersistentFlags().StringVar(&certSelector, "cert-selector", "", "JSON structure to identify a certificate from a certificate store."+ " Can be passed in either as string or a file name (prefixed by \"file://\")") + readCertificateDataCmd.PersistentFlags().StringVar(&libPkcs11, "pkcs11-lib", "", "Library for smart card / cryptographic device (OpenSC or vendor specific)") readCertificateDataCmd.PersistentFlags().BoolVar(&debug, "debug", false, "To print debug output") } From ed841413c2640815c1b1bbbd61226574776d2aab Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Thu, 17 Aug 2023 21:44:49 -0400 Subject: [PATCH 23/40] Remove explicit slot and PIN flags for PKCS#11 integration --- cmd/credentials.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/cmd/credentials.go b/cmd/credentials.go index cb6b25e..7bedb52 100644 --- a/cmd/credentials.go +++ b/cmd/credentials.go @@ -28,9 +28,6 @@ var ( certSelector string libPkcs11 string - pinPkcs11 string - slotPkcs11 uint - checkPkcs11 bool credentialsOptions helper.CredentialsOpts @@ -68,8 +65,6 @@ func initCredentialsSubCommand(subCmd *cobra.Command) { subCmd.PersistentFlags().StringVar(&certSelector, "cert-selector", "", "JSON structure to identify a certificate from a certificate store. "+ "Can be passed in either as string or a file name (prefixed by \"file://\")") subCmd.PersistentFlags().StringVar(&libPkcs11, "pkcs11-lib", "", "Library for smart card / cryptographic device (OpenSC or vendor specific)") - subCmd.PersistentFlags().StringVar(&pinPkcs11, "pkcs11-pin", "-", "Pin of the PKCS #11 user for private key access") - subCmd.PersistentFlags().UintVar(&slotPkcs11, "pkcs11-slot", 0, "PKCS #11 slot in which to search for the private key (and potentially certificate as well)") subCmd.MarkFlagsMutuallyExclusive("private-key", "cert-selector") subCmd.MarkFlagsMutuallyExclusive("cert-selector", "intermediates") From 2cf71f70cf4286c32236fe599c03d913950aaebb Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Tue, 5 Sep 2023 17:04:15 -0400 Subject: [PATCH 24/40] Add note about YubiKey attestation certificates --- README.md | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2942da0..2c2b553 100644 --- a/README.md +++ b/README.md @@ -160,10 +160,21 @@ for it. For systems or containers which lack p11-kit, a specific PKCS#11 provider library can be specified using the `--pkcs11-lib` parameter. -The searching methodology used to find objects within PKCS#11 tokens can be found -[here](https://datatracker.ietf.org/doc/html/draft-woodhouse-cert-best-practice-01). Note that -there are some differences in how tokens are searched in the credential helper, but -it should mostly follow that document. +The searching methodology used to find objects within PKCS#11 tokens can largely be found +[here](https://datatracker.ietf.org/doc/html/draft-woodhouse-cert-best-practice-01). Do note +that there are some slight differences in objects are found in the credential helper +application. + +Note that if you're using a YubiKey device with PIV support, when a key pair +and certificate exist in slots 9a or 9c (PIV authentication and digital signature, +respectively), the YubiKey will automatically generate an attestation certificate +for the slot. Testing has shown that the attestation certificate can't be deleted. +In this case, if you attempt to use the `CKA_ID` of your certificate to identify +it in your supplied PKCS#11 URI, there will be two certificates that match. One +way in which you can disambiguate between the two in your PKCS#11 URI can be through +`CKA_LABEL`. Attestation certificates in either of these two slots can be identified +through the hard-coded labels, `X.509 Certificate for PIV Attestation 9a` or +`X.509 Certificate for PIV Attestation 9c`. ### update From 8e3af69f4a281ab16f49729dd3c9884962dd8d35 Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Wed, 6 Sep 2023 11:22:28 -0400 Subject: [PATCH 25/40] Fix some PKCS#11 "hybrid" mode bugs. * If a certificate is provided (even if it's just the path to the file that was provided as opposed to a URI), make sure that the private key matches it when creating a PKCS11Signer. * If there is no certificate provided, and there is exactly one private key object that matches the URI, use it to create the PKCS11Signer. * Save the context-specific PIN after signing operations (not just when trying to match the appropriate private key). * Add keys with the CKA_ALWAYS_AUTHENTICATE attribute set, and use them in testing. * Add tests to make sure that certificate and private key do match when creating the PKCS11Signer. --- Makefile | 18 +++++-- aws_signing_helper/pkcs11_signer.go | 25 ++++++--- aws_signing_helper/signer_test.go | 80 ++++++++++++++++++++++++++--- 3 files changed, 107 insertions(+), 16 deletions(-) diff --git a/Makefile b/Makefile index 412f0d6..ddc84a3 100644 --- a/Makefile +++ b/Makefile @@ -28,16 +28,26 @@ tst/softhsm2.conf: tst/softhsm2.conf.template $(PKCS8KEYS) $(RSACERTS) $(ECCERTS --so-pin 12345678 --pin 1234 $(SHM2_UTIL) --token credential-helper-test --pin 1234 \ - --import $(certsdir)/rsa-2048-key-pkcs8.pem --label RSA --id 01 + --import $(certsdir)/rsa-2048-key-pkcs8.pem --label rsa-2048 --id 01 $(P11TOOL) --load-certificate $(certsdir)/rsa-2048-sha256-cert.pem \ - --no-mark-private --label RSA --id 01 --set-pin 1234 --login \ + --no-mark-private --label rsa-2048 --id 01 --set-pin 1234 --login \ --write "pkcs11:token=credential-helper-test;pin-value=1234" $(SHM2_UTIL) --token credential-helper-test --pin 1234 \ - --import $(certsdir)/ec-prime256v1-key-pkcs8.pem --label EC --id 02 + --import $(certsdir)/ec-prime256v1-key-pkcs8.pem --label ec-prime256v1 --id 02 $(P11TOOL) --load-certificate $(certsdir)/ec-prime256v1-sha256-cert.pem \ - --no-mark-private --label EC --id 02 --set-pin 1234 --login \ + --no-mark-private --label ec-prime256v1 --id 02 --set-pin 1234 --login \ --write "pkcs11:token=credential-helper-test;pin-value=1234" + + $(P11TOOL) --load-privkey $(certsdir)/rsa-2048-key-pkcs8.pem \ + --label rsa-2048-always-auth --id 03 --set-pin 1234 --login \ + --write "pkcs11:token=credential-helper-test;pin-value=1234" \ + --mark-always-authenticate + + $(P11TOOL) --load-privkey $(certsdir)/ec-prime256v1-key-pkcs8.pem \ + --label ec-prime256v1-always-auth --id 04 --set-pin 1234 --login \ + --write "pkcs11:token=credential-helper-test;pin-value=1234" \ + --mark-always-authenticate mv $@.tmp $@ test: test-certs tst/softhsm2.conf diff --git a/aws_signing_helper/pkcs11_signer.go b/aws_signing_helper/pkcs11_signer.go index 6e2608e..2a47e5b 100644 --- a/aws_signing_helper/pkcs11_signer.go +++ b/aws_signing_helper/pkcs11_signer.go @@ -527,7 +527,7 @@ func pkcs11PasswordPrompt(module *pkcs11.Ctx, session pkcs11.SessionHandle, user var pin string parseErrMsg := fmt.Sprintf("unable to read PKCS#11 %s", passwordName) - prompt := fmt.Sprintf("Please enter your %s", passwordName) + prompt := fmt.Sprintf("Please enter your %s:", passwordName) ttyPath := "/dev/tty" if runtime.GOOS == "windows" { @@ -640,7 +640,7 @@ func signHelper(module *pkcs11.Ctx, session pkcs11.SessionHandle, privateKeyHand // the context-specific PIN for this object. passwordName := "context-specific pin" finalAuthErrMsg := "user re-authentication failed (%s)" - _, err = pkcs11PasswordPrompt(module, session, pkcs11.CKU_CONTEXT_SPECIFIC, passwordName, finalAuthErrMsg) + contextSpecificPin, err = pkcs11PasswordPrompt(module, session, pkcs11.CKU_CONTEXT_SPECIFIC, passwordName, finalAuthErrMsg) if err != nil { return "", nil, err } @@ -790,8 +790,14 @@ retry_search: } if certObj.cert == nil { - privateKeyHandle = curPrivateKeyHandle - break + if len(privateKeyObjects) == 1 { + privateKeyHandle = curPrivateKeyHandle + break + } else { + err = errors.New("multiple matching private keys, but" + + " no certificate provided to match with") + goto fail + } } curContextSpecificPin := contextSpecificPin @@ -927,9 +933,11 @@ func (pkcs11Signer *PKCS11Signer) Sign(rand io.Reader, digest []byte, opts crypt goto cleanUp } - _, signature, err = signHelper(module, session, privateKeyHandle, alwaysAuth, false, userPin, contextSpecificPin, keyType, digest, hashFunc) + contextSpecificPin, signature, err = signHelper(module, session, privateKeyHandle, alwaysAuth, false, userPin, contextSpecificPin, 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 @@ -1180,6 +1188,9 @@ func GetPKCS11Signer(libPkcs11 string, cert *x509.Certificate, certChain []*x509 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 @@ -1197,7 +1208,9 @@ func GetPKCS11Signer(libPkcs11 string, cert *x509.Certificate, certChain []*x509 keyUri.Parse(certUriStr) noKeyUri = true } - userPin, _ = keyUri.GetQueryAttribute("pin-value", false) + 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 == "" { diff --git a/aws_signing_helper/signer_test.go b/aws_signing_helper/signer_test.go index 0b07d6f..31ad770 100644 --- a/aws_signing_helper/signer_test.go +++ b/aws_signing_helper/signer_test.go @@ -242,20 +242,34 @@ func TestSign(t *testing.T) { } } - pkcs11_objects := []string{"RSA", "EC"} + pkcs11_objects := []string{"rsa-2048", "ec-prime256v1"} for _, object := range pkcs11_objects { - pkcs11_uri := fmt.Sprintf("pkcs11:token=credential-helper-test;object=%s?pin-value=1234", object) + basic_pkcs11_uri := fmt.Sprintf("pkcs11:token=credential-helper-test;object=%s?pin-value=1234", object) + always_auth_pkcs11_uri := fmt.Sprintf("pkcs11:token=credential-helper-test;object=%s-always-auth?pin-value=1234", object) + cert_file := fmt.Sprintf("../tst/certs/%s-sha256-cert.pem", object) testTable = append(testTable, CredentialsOpts{ - CertificateId: pkcs11_uri, + CertificateId: basic_pkcs11_uri, }) testTable = append(testTable, CredentialsOpts{ - PrivateKeyId: pkcs11_uri, + PrivateKeyId: basic_pkcs11_uri, }) testTable = append(testTable, CredentialsOpts{ - CertificateId: pkcs11_uri, - PrivateKeyId: pkcs11_uri, + CertificateId: basic_pkcs11_uri, + PrivateKeyId: basic_pkcs11_uri, + }) + testTable = append(testTable, CredentialsOpts{ + CertificateId: cert_file, + PrivateKeyId: basic_pkcs11_uri, + }) + testTable = append(testTable, CredentialsOpts{ + CertificateId: basic_pkcs11_uri, + PrivateKeyId: always_auth_pkcs11_uri, + }) + testTable = append(testTable, CredentialsOpts{ + CertificateId: cert_file, + PrivateKeyId: always_auth_pkcs11_uri, }) } @@ -287,6 +301,9 @@ func TestSign(t *testing.T) { for _, digest := range digestList { signatureBytes, err := signer.Sign(rand.Reader, []byte(msg), digest) + // Try signing again to make sure that a context-specific PIN, if + // needed, was cached. + signer.Sign(rand.Reader, []byte(msg), digest) if err != nil { t.Log("Failed to sign the input message") t.Fail() @@ -403,6 +420,57 @@ func TestCertStoreSignerCreationFails(t *testing.T) { } } +func TestPKCS11SignerCreationFails(t *testing.T) { + testTable := []CredentialsOpts{} + + template_uri := "pkcs11:token=credential-helper-test;object=%s?pin-value=1234" + rsa_generic_uri := fmt.Sprintf(template_uri, "rsa-2048") + ec_generic_uri := fmt.Sprintf(template_uri, "ec-prime256v1") + // always_auth_rsa_uri := fmt.Sprintf(template_uri, "rsa-2048-always-auth") + // always_auth_ec_uri := fmt.Sprintf(template_uri, "ec-prime256v1-always-auth") + + testTable = append(testTable, CredentialsOpts{ + CertificateId: rsa_generic_uri, + PrivateKeyId: ec_generic_uri, + }) + testTable = append(testTable, CredentialsOpts{ + CertificateId: ec_generic_uri, + PrivateKeyId: rsa_generic_uri, + }) + testTable = append(testTable, CredentialsOpts{ + CertificateId: "../tst/certs/ec-prime256v1-sha256-cert.pem", + PrivateKeyId: rsa_generic_uri, + }) + testTable = append(testTable, CredentialsOpts{ + CertificateId: "../tst/certs/rsa-2048-sha256-cert.pem", + PrivateKeyId: ec_generic_uri, + }) + /* testTable = append(testTable, CredentialsOpts{ + CertificateId: rsa_generic_uri, + PrivateKeyId: always_auth_ec_uri, + }) + testTable = append(testTable, CredentialsOpts{ + CertificateId: ec_generic_uri, + PrivateKeyId: always_auth_rsa_uri, + }) + testTable = append(testTable, CredentialsOpts{ + CertificateId: "../tst/certs/ec-prime256v1-sha256-cert.pem", + PrivateKeyId: always_auth_rsa_uri, + }) + testTable = append(testTable, CredentialsOpts{ + CertificateId: "../tst/certs/rsa-2048-sha256-cert.pem", + PrivateKeyId: always_auth_ec_uri, + }) */ + + for _, credOpts := range testTable { + _, _, err := GetSigner(&credOpts) + if err == nil { + t.Log("Expected failure when creating certificate store signer, but received none") + t.Fail() + } + } +} + func TestUpdate(t *testing.T) { testTable := []struct { name string From 783c337214e8b8a613d219356584b46e4877405d Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Wed, 6 Sep 2023 13:52:31 -0400 Subject: [PATCH 26/40] Provide URI path attribute names for CKA_ID and CKA_LABEL in README --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 2c2b553..ff96796 100644 --- a/README.md +++ b/README.md @@ -169,12 +169,13 @@ Note that if you're using a YubiKey device with PIV support, when a key pair and certificate exist in slots 9a or 9c (PIV authentication and digital signature, respectively), the YubiKey will automatically generate an attestation certificate for the slot. Testing has shown that the attestation certificate can't be deleted. -In this case, if you attempt to use the `CKA_ID` of your certificate to identify -it in your supplied PKCS#11 URI, there will be two certificates that match. One -way in which you can disambiguate between the two in your PKCS#11 URI can be through -`CKA_LABEL`. Attestation certificates in either of these two slots can be identified -through the hard-coded labels, `X.509 Certificate for PIV Attestation 9a` or -`X.509 Certificate for PIV Attestation 9c`. +In this case, if you attempt to use the `CKA_ID` (the `id` path attribute in a URI) +of your certificate to identify it in your supplied PKCS#11 URI, there will be +two certificates that match. One way in which you can disambiguate between the +two in your PKCS#11 URI can be through `CKA_LABEL` (the `object` path attribute +in a URI). Attestation certificates in either of these two slots can be +identified through the hard-coded labels, `X.509 Certificate for PIV Attestation +9a` or `X.509 Certificate for PIV Attestation 9c`. ### update From bd1863be0113e05d6e686808ba6643ac69ffb49f Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Wed, 6 Sep 2023 14:30:44 -0400 Subject: [PATCH 27/40] Fix PIN prompting on Windows * Use different device files for console input and output on Windows (CONIN$ and CONOUT$, respectively). * Note that these changes haven't been completely tested yet. --- aws_signing_helper/pkcs11_signer.go | 40 ++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/aws_signing_helper/pkcs11_signer.go b/aws_signing_helper/pkcs11_signer.go index 2a47e5b..1d8bc66 100644 --- a/aws_signing_helper/pkcs11_signer.go +++ b/aws_signing_helper/pkcs11_signer.go @@ -523,25 +523,41 @@ func (pkcs11Signer *PKCS11Signer) Close() { // 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) (string, error) { - var pin string +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) + parseErrMsg = fmt.Sprintf("unable to read PKCS#11 %s", passwordName) + prompt = fmt.Sprintf("Please enter your %s:", passwordName) - ttyPath := "/dev/tty" + ttyReadPath = "/dev/tty" + ttyWritePath = ttyReadPath if runtime.GOOS == "windows" { - ttyPath = "CON" + ttyReadPath = "CONIN$" + ttyWritePath = "CONOUT$" + } + + ttyReadFile, err = os.OpenFile(ttyReadPath, os.O_RDWR, 0) + if err != nil { + return "", errors.New(parseErrMsg) } + defer ttyReadFile.Close() - ttyFile, err := os.OpenFile(ttyPath, os.O_RDWR, 0) + ttyWriteFile, err = os.OpenFile(ttyWritePath, os.O_WRONLY, 0) if err != nil { return "", errors.New(parseErrMsg) } - defer ttyFile.Close() + defer ttyWriteFile.Close() for true { - pin, err = GetPassword(ttyFile, prompt, parseErrMsg) + pin, err = GetPassword(ttyReadFile, ttyWriteFile, prompt, parseErrMsg) if err != nil && err.Error() == parseErrMsg { continue } @@ -563,9 +579,9 @@ func pkcs11PasswordPrompt(module *pkcs11.Ctx, session pkcs11.SessionHandle, user } // Prompts the user for their password -func GetPassword(ttyFile *os.File, prompt string, parseErrMsg string) (string, error) { - fmt.Fprintln(ttyFile, prompt) - passwordBytes, err := term.ReadPassword(int(ttyFile.Fd())) +func GetPassword(ttyReadFile *os.File, ttyWriteFile *os.File, prompt string, parseErrMsg string) (string, error) { + fmt.Fprintln(ttyWriteFile, prompt) + passwordBytes, err := term.ReadPassword(int(ttyReadFile.Fd())) if err != nil { return "", errors.New(parseErrMsg) } From 50120abcbe846bad1f8d070cba562931547d07ca Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Wed, 6 Sep 2023 16:47:20 -0400 Subject: [PATCH 28/40] Fix ReadCertificateBundleData * Terminate for-loop when PEM blocks can no longer be parsed (pem.Decode returns nil). * Add test case for parsing PEM file with comments. --- Makefile | 9 ++++++++- aws_signing_helper/signer.go | 6 +++--- aws_signing_helper/signer_test.go | 15 +++++++++++---- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index ddc84a3..392c279 100644 --- a/Makefile +++ b/Makefile @@ -105,7 +105,13 @@ $(ECKEYS): $(certsdir)/cert-bundle.pem: $(RSACERTS) $(ECCERTS) cat $^ > $@ -test-certs: $(PKCS8KEYS) $(RSAKEYS) $(ECKEYS) $(RSACERTS) $(ECCERTS) $(PKCS12CERTS) $(certsdir)/cert-bundle.pem tst/softhsm2.conf +$(certsdir)/cert-bundle-with-comments.pem: $(RSACERTS) $(ECCERTS) + for dep in $^; do \ + cat $$dep >> $@; \ + echo "Comment in bundle\n" >> $@; \ + done + +test-certs: $(PKCS8KEYS) $(RSAKEYS) $(ECKEYS) $(RSACERTS) $(ECCERTS) $(PKCS12CERTS) $(certsdir)/cert-bundle.pem $(certsdir)/cert-bundle-with-comments.pem tst/softhsm2.conf test-clean: rm -f $(RSAKEYS) $(ECKEYS) @@ -113,5 +119,6 @@ test-clean: rm -f $(RSACERTS) $(ECCERTS) rm -f $(PKCS12CERTS) rm -f $(certsdir)/cert-bundle.pem + rm -f $(certsdir)/cert-with-comments.pem rm -f tst/softhsm2.conf rm -rf tst/softhsm/* diff --git a/aws_signing_helper/signer.go b/aws_signing_helper/signer.go index 69eae27..972c8b8 100644 --- a/aws_signing_helper/signer.go +++ b/aws_signing_helper/signer.go @@ -528,7 +528,7 @@ func ReadCertificateBundleData(certificateBundleId string) ([]*x509.Certificate, for len(bytes) > 0 { block, bytes = pem.Decode(bytes) if block == nil { - return nil, errors.New("unable to parse PEM data") + break } if block.Type != "CERTIFICATE" { return nil, errors.New("invalid certificate chain") @@ -594,8 +594,8 @@ func readPKCS8PrivateKey(privateKeyId string) (crypto.PrivateKey, error) { // Reads and parses a PKCS#12 file (which should contain an end-entity // certificate, (optional) certificate chain, and the key associated with the -// end-entity certificate). The end-entity certificate will be returned as the -// first certificate in the returned chain. +// end-entity certificate). The end-entity certificate will be the first +// certificate in the returned chain. func ReadPKCS12Data(certificateId string) (certChain []*x509.Certificate, privateKey crypto.PrivateKey, err error) { var ( bytes []byte diff --git a/aws_signing_helper/signer_test.go b/aws_signing_helper/signer_test.go index 31ad770..1778a8d 100644 --- a/aws_signing_helper/signer_test.go +++ b/aws_signing_helper/signer_test.go @@ -79,10 +79,17 @@ func TestReadInvalidCertificateData(t *testing.T) { } func TestReadCertificateBundleData(t *testing.T) { - _, err := ReadCertificateBundleData("../tst/certs/cert-bundle.pem") - if err != nil { - t.Log("Failed to read certificate bundle data") - t.Fail() + fixtures := []string{ + "../tst/certs/cert-bundle.pem", + "../tst/certs/cert-bundle-with-comments.pem", + } + + for _, fixture := range fixtures { + _, err := ReadCertificateBundleData(fixture) + if err != nil { + t.Log("Failed to read certificate bundle data") + t.Fail() + } } } From 81e8290728355886c7e0d6b541120ca9d0080112 Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Thu, 7 Sep 2023 10:26:19 -0400 Subject: [PATCH 29/40] Change implementation of cert chain parsing from PKCS#12 files * There has to be an end-entity certificate in the PKCS#12 container. Otherwise, a Signer won't be created. The first certificate found in the container that doesn't issue other certificates in the container will be used. * Every certificate other than the end-entity certificate gets included in the CreateSession request as intermediates (even if they don't chain up to the end-entity certificate that was found). * Add negative testing using PKCS#12 containers with self-signed certificates. --- aws_signing_helper/signer.go | 42 ++++++++++++-- aws_signing_helper/signer_test.go | 95 ++++++++++++++++++++----------- 2 files changed, 101 insertions(+), 36 deletions(-) diff --git a/aws_signing_helper/signer.go b/aws_signing_helper/signer.go index 972c8b8..8ab343e 100644 --- a/aws_signing_helper/signer.go +++ b/aws_signing_helper/signer.go @@ -595,11 +595,18 @@ func readPKCS8PrivateKey(privateKeyId string) (crypto.PrivateKey, error) { // Reads and parses a PKCS#12 file (which should contain an end-entity // certificate, (optional) certificate chain, and the key associated with the // end-entity certificate). The end-entity certificate will be the first -// certificate in the returned chain. +// certificate in the returned chain. This method assumes that there is +// exactly one certificate that doesn't issue any others within the container +// and treats that as the end-entity certificate. Also, the order of the other +// certificates in the chain aren't guaranteed (it's also not guaranteed that +// those certificates form a chain with the end-entity certificat either). func ReadPKCS12Data(certificateId string) (certChain []*x509.Certificate, privateKey crypto.PrivateKey, err error) { var ( - bytes []byte - pemBlocks []*pem.Block + bytes []byte + pemBlocks []*pem.Block + parsedCerts []*x509.Certificate + certMap map[string]*x509.Certificate + endEntityFoundIndex int ) bytes, err = os.ReadFile(certificateId) @@ -615,7 +622,7 @@ func ReadPKCS12Data(certificateId string) (certChain []*x509.Certificate, privat for _, block := range pemBlocks { cert, err := x509.ParseCertificate(block.Bytes) if err == nil { - certChain = append(certChain, cert) + parsedCerts = append(parsedCerts, cert) continue } privateKeyTmp, err := ReadPrivateKeyDataFromPEMBlock(block) @@ -630,6 +637,33 @@ func ReadPKCS12Data(certificateId string) (certChain []*x509.Certificate, privat } } + certMap = make(map[string]*x509.Certificate) + for _, cert := range parsedCerts { + // pkix.Name.String() roughly following the RFC 2253 Distinguished Names + // syntax, so we assume that it's canonical. + issuer := cert.Issuer.String() + certMap[issuer] = cert + } + + endEntityFoundIndex = -1 + for i, cert := range parsedCerts { + subject := cert.Subject.String() + if _, ok := certMap[subject]; !ok { + certChain = append(certChain, cert) + endEntityFoundIndex = i + break + } + } + if endEntityFoundIndex == -1 { + return nil, "", errors.New("no end-entity certificate found in PKCS#12 file") + } + + for i, cert := range parsedCerts { + if i != endEntityFoundIndex { + certChain = append(certChain, cert) + } + } + return certChain, privateKey, nil } diff --git a/aws_signing_helper/signer_test.go b/aws_signing_helper/signer_test.go index 1778a8d..a62a4d7 100644 --- a/aws_signing_helper/signer_test.go +++ b/aws_signing_helper/signer_test.go @@ -194,6 +194,8 @@ func TestSign(t *testing.T) { msg := "test message" testTable := []CredentialsOpts{} + // TODO: Include tests for PKCS#12 containers, once fixtures are created + // with end-entity certificates. ec_digests := []string{"sha1", "sha256", "sha384", "sha512"} ec_curves := []string{"prime256v1", "secp384r1"} @@ -212,12 +214,6 @@ func TestSign(t *testing.T) { CertificateId: cert, PrivateKeyId: key, }) - - cert = fmt.Sprintf("../tst/certs/ec-%s-%s.p12", - curve, digest) - testTable = append(testTable, CredentialsOpts{ - CertificateId: cert, - }) } } @@ -239,13 +235,6 @@ func TestSign(t *testing.T) { CertificateId: cert, PrivateKeyId: key, }) - - cert = fmt.Sprintf("../tst/certs/rsa-%s-%s.p12", - keylen, digest) - testTable = append(testTable, CredentialsOpts{ - CertificateId: cert, - }) - } } @@ -427,14 +416,56 @@ func TestCertStoreSignerCreationFails(t *testing.T) { } } +func TestSignerCreationFails(t *testing.T) { + var cert string + testTable := []CredentialsOpts{} + + ec_digests := []string{"sha1", "sha256", "sha384", "sha512"} + ec_curves := []string{"prime256v1", "secp384r1"} + + for _, digest := range ec_digests { + for _, curve := range ec_curves { + cert = fmt.Sprintf("../tst/certs/ec-%s-%s.p12", + curve, digest) + testTable = append(testTable, CredentialsOpts{ + CertificateId: cert, + }) + } + } + + rsa_digests := []string{"md5", "sha1", "sha256", "sha384", "sha512"} + rsa_key_lengths := []string{"1024", "2048", "4096"} + + for _, digest := range rsa_digests { + for _, keylen := range rsa_key_lengths { + cert = fmt.Sprintf("../tst/certs/rsa-%s-%s.p12", + keylen, digest) + testTable = append(testTable, CredentialsOpts{ + CertificateId: cert, + }) + } + } + + for _, credOpts := range testTable { + _, _, err := GetSigner(&credOpts) + // We expect a failure since the certificates in these .p12 files are + // self-signed. When creating a signer, we expect there to be an + // end-entity certificate within the container. + if err == nil { + t.Log("Expected failure when creating PKCS#12 signer, but received none") + t.Fail() + } + } +} + func TestPKCS11SignerCreationFails(t *testing.T) { testTable := []CredentialsOpts{} template_uri := "pkcs11:token=credential-helper-test;object=%s?pin-value=1234" rsa_generic_uri := fmt.Sprintf(template_uri, "rsa-2048") ec_generic_uri := fmt.Sprintf(template_uri, "ec-prime256v1") - // always_auth_rsa_uri := fmt.Sprintf(template_uri, "rsa-2048-always-auth") - // always_auth_ec_uri := fmt.Sprintf(template_uri, "ec-prime256v1-always-auth") + always_auth_rsa_uri := fmt.Sprintf(template_uri, "rsa-2048-always-auth") + always_auth_ec_uri := fmt.Sprintf(template_uri, "ec-prime256v1-always-auth") testTable = append(testTable, CredentialsOpts{ CertificateId: rsa_generic_uri, @@ -452,27 +483,27 @@ func TestPKCS11SignerCreationFails(t *testing.T) { CertificateId: "../tst/certs/rsa-2048-sha256-cert.pem", PrivateKeyId: ec_generic_uri, }) - /* testTable = append(testTable, CredentialsOpts{ - CertificateId: rsa_generic_uri, - PrivateKeyId: always_auth_ec_uri, - }) - testTable = append(testTable, CredentialsOpts{ - CertificateId: ec_generic_uri, - PrivateKeyId: always_auth_rsa_uri, - }) - testTable = append(testTable, CredentialsOpts{ - CertificateId: "../tst/certs/ec-prime256v1-sha256-cert.pem", - PrivateKeyId: always_auth_rsa_uri, - }) - testTable = append(testTable, CredentialsOpts{ - CertificateId: "../tst/certs/rsa-2048-sha256-cert.pem", - PrivateKeyId: always_auth_ec_uri, - }) */ + testTable = append(testTable, CredentialsOpts{ + CertificateId: rsa_generic_uri, + PrivateKeyId: always_auth_ec_uri, + }) + testTable = append(testTable, CredentialsOpts{ + CertificateId: ec_generic_uri, + PrivateKeyId: always_auth_rsa_uri, + }) + testTable = append(testTable, CredentialsOpts{ + CertificateId: "../tst/certs/ec-prime256v1-sha256-cert.pem", + PrivateKeyId: always_auth_rsa_uri, + }) + testTable = append(testTable, CredentialsOpts{ + CertificateId: "../tst/certs/rsa-2048-sha256-cert.pem", + PrivateKeyId: always_auth_ec_uri, + }) for _, credOpts := range testTable { _, _, err := GetSigner(&credOpts) if err == nil { - t.Log("Expected failure when creating certificate store signer, but received none") + t.Log("Expected failure when creating PKCS#11 signer, but received none") t.Fail() } } From d4c769808fc5acc42a74b1aa26410f12ec8bf576 Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Thu, 7 Sep 2023 19:06:06 -0400 Subject: [PATCH 30/40] Add note about zeroing out variables that store PKCS#11 PINs in README --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index ff96796..559a2d7 100644 --- a/README.md +++ b/README.md @@ -177,6 +177,13 @@ in a URI). Attestation certificates in either of these two slots can be identified through the hard-coded labels, `X.509 Certificate for PIV Attestation 9a` or `X.509 Certificate for PIV Attestation 9c`. +#### Implementation Notes + +PKCS#11 PINs can be stored in memory as strings for the duration of the program and aren't +guaranteed to be zeroed out after the program is done with them. Since strings are +immutable in Golang, it's unclear how to circumvent the type system and get to the +underlying buffer to zero it out. + ### update Updates temporary credentials in the [credential file](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html). Parameters for this command include those for the `credential-process` command, as well as `--profile`, which specifies the named profile for which credentials should be updated (if the profile doesn't already exist, it will be created), and `--once`, which specifies that credentials should be updated only once. Both arguments are optional. If `--profile` isn't specified, the default profile will have its credentials updated, and if `--once` isn't specified, credentials will be continuously updated. In this case, credentials will be updated through a call to `CreateSession` five minutes before the previous set of credentials are set to expire. Please note that running the `update` command multiple times, creating multiple processes, may not work as intended. There may be issues with concurrent writes to the credentials file. From 69b35b30d3137c36e643447417f1a4e59abc39c7 Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Thu, 7 Sep 2023 21:25:57 -0400 Subject: [PATCH 31/40] Add force-prompt flag to force prompting when CKA_ALWAYS_AUTHENTICATE attribute is set. * Add force-prompt flag for credentialing commands and sign-string. * Add test for when there are multiple matching private key objects when creating PKCS11Signer. --- aws_signing_helper/credentials.go | 1 + aws_signing_helper/pkcs11_signer.go | 33 ++++++++++++++++------------- aws_signing_helper/signer.go | 2 +- aws_signing_helper/signer_test.go | 14 ++++++++++-- cmd/credentials.go | 8 ++++++- cmd/sign_string.go | 3 +++ 6 files changed, 42 insertions(+), 19 deletions(-) diff --git a/aws_signing_helper/credentials.go b/aws_signing_helper/credentials.go index f071558..96a3d39 100644 --- a/aws_signing_helper/credentials.go +++ b/aws_signing_helper/credentials.go @@ -31,6 +31,7 @@ type CredentialsOpts struct { Debug bool Version string LibPkcs11 string + ForcePrompt bool } // Function to create session and generate credentials diff --git a/aws_signing_helper/pkcs11_signer.go b/aws_signing_helper/pkcs11_signer.go index 1d8bc66..f65cccf 100644 --- a/aws_signing_helper/pkcs11_signer.go +++ b/aws_signing_helper/pkcs11_signer.go @@ -78,6 +78,7 @@ type PKCS11Signer struct { contextSpecificPin string certUri *pkcs11uri.Pkcs11URI keyUri *pkcs11uri.Pkcs11URI + forcePrompt bool } // Initialize a PKCS#11 module. @@ -592,7 +593,7 @@ func GetPassword(ttyReadFile *os.File, ttyWriteFile *os.File, prompt string, par } // Helper function to sign a digest using a PKCS#11 private key handle. -func signHelper(module *pkcs11.Ctx, session pkcs11.SessionHandle, privateKeyHandle pkcs11.ObjectHandle, alwaysAuth uint, firstSigningOperation bool, userPin string, contextSpecificPin string, keyType uint, digest []byte, hashFunc crypto.Hash) (_contextSpecificPin string, signature []byte, err error) { +func signHelper(module *pkcs11.Ctx, session pkcs11.SessionHandle, privateKeyHandle pkcs11.ObjectHandle, userPin string, alwaysAuth uint, contextSpecificPin string, forcePrompt 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 @@ -636,9 +637,9 @@ func signHelper(module *pkcs11.Ctx, session pkcs11.SessionHandle, privateKeyHand 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 - // and this is the first signing operation with this particular key, - // use the user PIN as the context-specific PIN. - if contextSpecificPin == "" && userPin != "" && firstSigningOperation { + // in the input, and the "force prompt" option wasn't set, try to use the + // user PIN as the context-specific PIN. + if contextSpecificPin == "" && userPin != "" && !forcePrompt { contextSpecificPin = userPin } if contextSpecificPin != "" { @@ -654,7 +655,7 @@ func signHelper(module *pkcs11.Ctx, session pkcs11.SessionHandle, privateKeyHand // If the context-specific PIN couldn't be derived, prompt the user for // the context-specific PIN for this object. - passwordName := "context-specific pin" + passwordName := "context-specific PIN" finalAuthErrMsg := "user re-authentication failed (%s)" contextSpecificPin, err = pkcs11PasswordPrompt(module, session, pkcs11.CKU_CONTEXT_SPECIFIC, passwordName, finalAuthErrMsg) if err != nil { @@ -681,7 +682,7 @@ afterContextSpecificLogin: // 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, slots []SlotIdInfo) (_session pkcs11.SessionHandle, _userPin string, _keyUri *pkcs11uri.Pkcs11URI, keyType uint, privateKeyHandle pkcs11.ObjectHandle, alwaysAuth uint, _contextSpecificPin string, err error) { +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, forcePrompt bool, slots []SlotIdInfo) (_session pkcs11.SessionHandle, _userPin string, _keyUri *pkcs11uri.Pkcs11URI, keyType uint, privateKeyHandle pkcs11.ObjectHandle, alwaysAuth uint, _contextSpecificPin string, err error) { var ( keySlotNr uint manufacturerId string @@ -741,7 +742,7 @@ got_slot: // And *now* we fall back to prompting the user for a PIN if necessary. if !loggedIn { if userPin == "" { - passwordName := "user pin" + passwordName := "user PIN" finalAuthErrMsg := "user authentication failed (%s)" userPin, err = pkcs11PasswordPrompt(module, session, pkcs11.CKU_USER, passwordName, finalAuthErrMsg) if err != nil { @@ -821,7 +822,7 @@ retry_search: curContextSpecificPin = userPin } privateKeyMatchesCert := false - curContextSpecificPin, privateKeyMatchesCert = checkPrivateKeyMatchesCert(module, session, keyType, alwaysAuth, curContextSpecificPin, curPrivateKeyHandle, certObj.cert, manufacturerId) + curContextSpecificPin, privateKeyMatchesCert = checkPrivateKeyMatchesCert(module, session, keyType, userPin, alwaysAuth, "", forcePrompt, curPrivateKeyHandle, certObj.cert, manufacturerId) if privateKeyMatchesCert { privateKeyHandle = curPrivateKeyHandle contextSpecificPin = curContextSpecificPin @@ -914,6 +915,7 @@ func (pkcs11Signer *PKCS11Signer) Sign(rand io.Reader, digest []byte, opts crypt certObj CertObjInfo slots []SlotIdInfo loggedIn bool + forcePrompt bool alwaysAuth uint certSlot SlotIdInfo ) @@ -926,6 +928,7 @@ func (pkcs11Signer *PKCS11Signer) Sign(rand io.Reader, digest []byte, opts crypt contextSpecificPin = pkcs11Signer.contextSpecificPin certUri = pkcs11Signer.certUri keyUri = pkcs11Signer.keyUri + forcePrompt = pkcs11Signer.forcePrompt // If a PKCS#11 URI was provided for the certificate, use it. if certUri != nil { @@ -944,12 +947,12 @@ func (pkcs11Signer *PKCS11Signer) Sign(rand io.Reader, digest []byte, opts crypt } } - session, userPin, keyUri, keyType, privateKeyHandle, alwaysAuth, contextSpecificPin, err = getPKCS11Key(module, session, loggedIn, certUri, keyUri, false, certSlotNr, certObj, userPin, contextSpecificPin, slots) + session, userPin, keyUri, keyType, privateKeyHandle, alwaysAuth, contextSpecificPin, err = getPKCS11Key(module, session, loggedIn, certUri, keyUri, false, certSlotNr, certObj, userPin, contextSpecificPin, forcePrompt, slots) if err != nil { goto cleanUp } - contextSpecificPin, signature, err = signHelper(module, session, privateKeyHandle, alwaysAuth, false, userPin, contextSpecificPin, keyType, digest, hashFunc) + contextSpecificPin, signature, err = signHelper(module, session, privateKeyHandle, userPin, alwaysAuth, contextSpecificPin, forcePrompt, keyType, digest, hashFunc) if err != nil { goto cleanUp } else { @@ -1049,7 +1052,7 @@ func (pkcs11Signer *PKCS11Signer) CertificateChain() (certChain []*x509.Certific } // Checks whether the private key and certificate are associated with each other. -func checkPrivateKeyMatchesCert(module *pkcs11.Ctx, session pkcs11.SessionHandle, keyType uint, alwaysAuth uint, userPin string, privateKeyHandle pkcs11.ObjectHandle, certificate *x509.Certificate, manufacturerId string) (string, bool) { +func checkPrivateKeyMatchesCert(module *pkcs11.Ctx, session pkcs11.SessionHandle, keyType uint, userPin string, alwaysAuth uint, contextSpecificPin string, forcePrompt bool, privateKeyHandle pkcs11.ObjectHandle, certificate *x509.Certificate, manufacturerId string) (string, bool) { var digestSuffix []byte publicKey := certificate.PublicKey ecdsaPublicKey, isEcKey := publicKey.(*ecdsa.PublicKey) @@ -1076,7 +1079,7 @@ func checkPrivateKeyMatchesCert(module *pkcs11.Ctx, session pkcs11.SessionHandle digestBytes := []byte(digest) hash := sha256.Sum256(digestBytes) - contextSpecificPin, signature, err := signHelper(module, session, privateKeyHandle, alwaysAuth, true, userPin, "", keyType, digestBytes, crypto.SHA256) + contextSpecificPin, signature, err := signHelper(module, session, privateKeyHandle, userPin, alwaysAuth, "", forcePrompt, keyType, digestBytes, crypto.SHA256) if err != nil { return "", false } @@ -1144,7 +1147,7 @@ func escapeAll(s []byte) string { // 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) (signer Signer, signingAlgorithm string, err error) { +func GetPKCS11Signer(libPkcs11 string, cert *x509.Certificate, certChain []*x509.Certificate, privateKeyId string, certificateId string, forcePrompt bool) (signer Signer, signingAlgorithm string, err error) { var ( module *pkcs11.Ctx certObj CertObjInfo @@ -1236,7 +1239,7 @@ func GetPKCS11Signer(libPkcs11 string, cert *x509.Certificate, certChain []*x509 } } - session, userPin, keyUri, keyType, _, alwaysAuth, contextSpecificPin, err = getPKCS11Key(module, session, loggedIn, certUri, keyUri, noKeyUri, certSlotNr, certObj, userPin, contextSpecificPin, slots) + session, userPin, keyUri, keyType, _, alwaysAuth, contextSpecificPin, err = getPKCS11Key(module, session, loggedIn, certUri, keyUri, noKeyUri, certSlotNr, certObj, userPin, "", forcePrompt, slots) if err != nil { goto fail } @@ -1257,7 +1260,7 @@ func GetPKCS11Signer(libPkcs11 string, cert *x509.Certificate, certChain []*x509 module.CloseSession(session) } - return &PKCS11Signer{cert, certChain, module, userPin, alwaysAuth, contextSpecificPin, certUri, keyUri}, signingAlgorithm, nil + return &PKCS11Signer{cert, certChain, module, userPin, alwaysAuth, contextSpecificPin, certUri, keyUri, forcePrompt}, signingAlgorithm, nil fail: if module != nil { diff --git a/aws_signing_helper/signer.go b/aws_signing_helper/signer.go index 8ab343e..32de497 100644 --- a/aws_signing_helper/signer.go +++ b/aws_signing_helper/signer.go @@ -233,7 +233,7 @@ func GetSigner(opts *CredentialsOpts) (signer Signer, signatureAlgorithm string, if certificate != nil { opts.CertificateId = "" } - return GetPKCS11Signer(opts.LibPkcs11, certificate, certificateChain, opts.PrivateKeyId, opts.CertificateId) + return GetPKCS11Signer(opts.LibPkcs11, certificate, certificateChain, opts.PrivateKeyId, opts.CertificateId, opts.ForcePrompt) } else { privateKey, err = ReadPrivateKeyData(privateKeyId) if err != nil { diff --git a/aws_signing_helper/signer_test.go b/aws_signing_helper/signer_test.go index a62a4d7..3c4091f 100644 --- a/aws_signing_helper/signer_test.go +++ b/aws_signing_helper/signer_test.go @@ -241,6 +241,7 @@ func TestSign(t *testing.T) { pkcs11_objects := []string{"rsa-2048", "ec-prime256v1"} for _, object := range pkcs11_objects { + base_pkcs11_uri := "pkcs11:token=credential-helper-test?pin-value=1234" basic_pkcs11_uri := fmt.Sprintf("pkcs11:token=credential-helper-test;object=%s?pin-value=1234", object) always_auth_pkcs11_uri := fmt.Sprintf("pkcs11:token=credential-helper-test;object=%s-always-auth?pin-value=1234", object) cert_file := fmt.Sprintf("../tst/certs/%s-sha256-cert.pem", object) @@ -267,6 +268,14 @@ func TestSign(t *testing.T) { CertificateId: cert_file, PrivateKeyId: always_auth_pkcs11_uri, }) + // Note that for the below test case, there are two matching keys. + // Both keys will validate with the certificate, and one will be chosen + // (it doesn't matter which, since both are the exact same key - it's + // just that one has the CKA_ALWAYS_AUTHENTICATE attribute set). + testTable = append(testTable, CredentialsOpts{ + CertificateId: cert_file, + PrivateKeyId: base_pkcs11_uri, + }) } digestList := []crypto.Hash{crypto.SHA256, crypto.SHA384, crypto.SHA512} @@ -297,8 +306,9 @@ func TestSign(t *testing.T) { for _, digest := range digestList { signatureBytes, err := signer.Sign(rand.Reader, []byte(msg), digest) - // Try signing again to make sure that a context-specific PIN, if - // needed, was cached. + // Try signing again to make sure that there aren't any issues + // with reopening sessions. Also, in some test cases, signing again + // makes sure that the context-specific PIN was saved. signer.Sign(rand.Reader, []byte(msg), digest) if err != nil { t.Log("Failed to sign the input message") diff --git a/cmd/credentials.go b/cmd/credentials.go index 7bedb52..f977fb4 100644 --- a/cmd/credentials.go +++ b/cmd/credentials.go @@ -21,13 +21,14 @@ var ( noVerifySSL bool withProxy bool debug bool + forcePrompt bool certificateId string privateKeyId string certificateBundleId string certSelector string - libPkcs11 string + libPkcs11 string credentialsOptions helper.CredentialsOpts @@ -65,9 +66,13 @@ func initCredentialsSubCommand(subCmd *cobra.Command) { subCmd.PersistentFlags().StringVar(&certSelector, "cert-selector", "", "JSON structure to identify a certificate from a certificate store. "+ "Can be passed in either as string or a file name (prefixed by \"file://\")") subCmd.PersistentFlags().StringVar(&libPkcs11, "pkcs11-lib", "", "Library for smart card / cryptographic device (OpenSC or vendor specific)") + subCmd.PersistentFlags().BoolVar(&forcePrompt, "force-prompt", false, "Force prompting for a context-specific PIN if the "+ + "CKA_ALWAYS_AUTHENTICATE attribute is set on the desired private key. This option is only relevant for the PKCS#11 integration and will "+ + "be ignored in all other cases") subCmd.MarkFlagsMutuallyExclusive("private-key", "cert-selector") subCmd.MarkFlagsMutuallyExclusive("cert-selector", "intermediates") + subCmd.MarkFlagsMutuallyExclusive("cert-selector", "force-prompt") } // Parses a cert selector string to a map @@ -215,6 +220,7 @@ func PopulateCredentialsOptions() error { Debug: debug, Version: Version, LibPkcs11: libPkcs11, + ForcePrompt: forcePrompt, } return nil diff --git a/cmd/sign_string.go b/cmd/sign_string.go index 6e7ed64..6e2048f 100644 --- a/cmd/sign_string.go +++ b/cmd/sign_string.go @@ -79,6 +79,9 @@ func init() { signStringCmd.PersistentFlags().StringVar(&certSelector, "cert-selector", "", "JSON structure to identify a certificate from a certificate store. "+ "Can be passed in either as string or a file name (prefixed by \"file://\")") signStringCmd.PersistentFlags().StringVar(&libPkcs11, "pkcs11-lib", "", "Library for smart card / cryptographic device (default: p11-kit-proxy.{so, dll, dylib})") + signStringCmd.PersistentFlags().BoolVar(&forcePrompt, "force-prompt", false, "Force prompting for a context-specific PIN if the "+ + "CKA_ALWAYS_AUTHENTICATE attribute is set on the desired private key. This option is only relevant for the PKCS#11 integration and will "+ + "be ignored in all other cases") signStringCmd.PersistentFlags().Var(format, "format", "Output format. One of json, text, and bin") signStringCmd.PersistentFlags().Var(digestArg, "digest", "One of SHA256, SHA384, and SHA512") } From 029aa43fd0a1a5f2b503618e134fbb26a16edd30 Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Fri, 8 Sep 2023 11:08:13 -0400 Subject: [PATCH 32/40] Include private key object URI in prompt message when prompting for context-specific PIN --- aws_signing_helper/pkcs11_signer.go | 106 ++++++++++++++++++++-------- 1 file changed, 76 insertions(+), 30 deletions(-) diff --git a/aws_signing_helper/pkcs11_signer.go b/aws_signing_helper/pkcs11_signer.go index f65cccf..8bc8591 100644 --- a/aws_signing_helper/pkcs11_signer.go +++ b/aws_signing_helper/pkcs11_signer.go @@ -62,6 +62,13 @@ type CertObjInfo struct { 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 @@ -593,14 +600,19 @@ func GetPassword(ttyReadFile *os.File, ttyWriteFile *os.File, prompt string, par } // Helper function to sign a digest using a PKCS#11 private key handle. -func signHelper(module *pkcs11.Ctx, session pkcs11.SessionHandle, privateKeyHandle pkcs11.ObjectHandle, userPin string, alwaysAuth uint, contextSpecificPin string, forcePrompt bool, keyType uint, digest []byte, hashFunc crypto.Hash) (_contextSpecificPin string, signature []byte, err error) { +func signHelper(module *pkcs11.Ctx, session pkcs11.SessionHandle, privateKeyObj KeyObjInfo, slot SlotIdInfo, userPin string, alwaysAuth uint, contextSpecificPin string, forcePrompt 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 + var ( + mechanism uint + keyUri *pkcs11uri.Pkcs11URI + keyUriStr string + ) + if keyType == pkcs11.CKK_EC { switch hashFunc { case crypto.SHA256: @@ -629,7 +641,7 @@ func signHelper(module *pkcs11.Ctx, session pkcs11.SessionHandle, privateKeyHand } } - err = module.SignInit(session, []*pkcs11.Mechanism{pkcs11.NewMechanism(mechanism, nil)}, privateKeyHandle) + 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()) } @@ -655,7 +667,27 @@ func signHelper(module *pkcs11.Ctx, session pkcs11.SessionHandle, privateKeyHand // 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 { @@ -682,9 +714,9 @@ afterContextSpecificLogin: // 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, forcePrompt bool, slots []SlotIdInfo) (_session pkcs11.SessionHandle, _userPin string, _keyUri *pkcs11uri.Pkcs11URI, keyType uint, privateKeyHandle pkcs11.ObjectHandle, alwaysAuth uint, _contextSpecificPin string, err error) { +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, forcePrompt bool, slots []SlotIdInfo) (_session pkcs11.SessionHandle, _userPin string, _keyUri *pkcs11uri.Pkcs11URI, keyType uint, privateKeyObj KeyObjInfo, slot SlotIdInfo, alwaysAuth uint, _contextSpecificPin string, err error) { var ( - keySlotNr uint + keySlot SlotIdInfo manufacturerId string templatePrivateKey []*pkcs11.Attribute privateKeyObjects []pkcs11.ObjectHandle @@ -705,7 +737,7 @@ func getPKCS11Key(module *pkcs11.Ctx, session pkcs11.SessionHandle, loggedIn boo slots = matchSlots(slots, keyUri) if len(slots) == 1 { if certSlotNr != slots[0].id { - keySlotNr = slots[0].id + keySlot = slots[0] manufacturerId = slots[0].info.ManufacturerID if session != 0 { if loggedIn { @@ -722,7 +754,7 @@ func getPKCS11Key(module *pkcs11.Ctx, session pkcs11.SessionHandle, loggedIn boo // that. for _, slot := range slots { if certSlotNr == slot.id { - keySlotNr = slot.id + keySlot = slot manufacturerId = slot.info.ManufacturerID goto got_slot } @@ -733,7 +765,7 @@ func getPKCS11Key(module *pkcs11.Ctx, session pkcs11.SessionHandle, loggedIn boo got_slot: if session == 0 { - session, err = module.OpenSession(keySlotNr, pkcs11.CKF_SERIAL_SESSION|pkcs11.CKS_RO_PUBLIC_SESSION) + session, err = module.OpenSession(keySlot.id, pkcs11.CKF_SERIAL_SESSION|pkcs11.CKS_RO_PUBLIC_SESSION) } if err != nil { goto fail @@ -783,7 +815,6 @@ retry_search: // that actually matches the cert. More realistically, there // will be only one. Sanity check that it matches the cert. for _, curPrivateKeyHandle := range privateKeyObjects { - // Find the signing algorithm keyAttributes = []*pkcs11.Attribute{ pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, 0), } @@ -806,9 +837,28 @@ retry_search: 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[0] = pkcs11.NewAttribute(pkcs11.CKA_ID, 0) + keyAttributes, err = module.GetAttributeValue(session, curPrivateKeyHandle, keyAttributes) + if err == nil { + curPrivateKeyObj.id = keyAttributes[0].Value + } + + keyAttributes[0] = 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 { - privateKeyHandle = curPrivateKeyHandle + privateKeyObj = curPrivateKeyObj break } else { err = errors.New("multiple matching private keys, but" + @@ -822,15 +872,15 @@ retry_search: curContextSpecificPin = userPin } privateKeyMatchesCert := false - curContextSpecificPin, privateKeyMatchesCert = checkPrivateKeyMatchesCert(module, session, keyType, userPin, alwaysAuth, "", forcePrompt, curPrivateKeyHandle, certObj.cert, manufacturerId) + curContextSpecificPin, privateKeyMatchesCert = checkPrivateKeyMatchesCert(module, session, keyType, userPin, alwaysAuth, "", forcePrompt, curPrivateKeyObj, keySlot, certObj.cert, manufacturerId) if privateKeyMatchesCert { - privateKeyHandle = curPrivateKeyHandle + privateKeyObj = curPrivateKeyObj contextSpecificPin = curContextSpecificPin break } } - if privateKeyHandle == 0 { + 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 @@ -860,22 +910,17 @@ retry_search: // So that hunting for the key can be more efficient in the future, // return a key URI that has CKA_ID and CKA_VALUE appropriately set. - keyAttributes[0] = pkcs11.NewAttribute(pkcs11.CKA_ID, 0) - keyAttributes, err = module.GetAttributeValue(session, privateKeyHandle, keyAttributes) - if err == nil { - keyUri.SetPathAttribute("id", escapeAll(keyAttributes[0].Value)) + if privateKeyObj.id != nil { + keyUri.SetPathAttribute("id", escapeAll(privateKeyObj.id)) } - - keyAttributes[0] = pkcs11.NewAttribute(pkcs11.CKA_LABEL, 0) - keyAttributes, err = module.GetAttributeValue(session, privateKeyHandle, keyAttributes) - if err == nil { - keyUri.SetPathAttribute("object", escapeAll(keyAttributes[0].Value)) + if privateKeyObj.label != nil { + keyUri.SetPathAttribute("object", escapeAll(privateKeyObj.label)) } - return session, userPin, keyUri, keyType, privateKeyHandle, alwaysAuth, contextSpecificPin, nil + return session, userPin, keyUri, keyType, privateKeyObj, keySlot, alwaysAuth, contextSpecificPin, nil fail: - return 0, "", nil, 0, 0, 0, "", err + return 0, "", nil, 0, KeyObjInfo{}, SlotIdInfo{}, 0, "", err } // Gets the certificate in a token, given the URI that identifies the @@ -909,7 +954,8 @@ func (pkcs11Signer *PKCS11Signer) Sign(rand io.Reader, digest []byte, opts crypt keyUri *pkcs11uri.Pkcs11URI userPin string contextSpecificPin string - privateKeyHandle pkcs11.ObjectHandle + privateKeyObj KeyObjInfo + keySlot SlotIdInfo keyType uint certSlotNr uint certObj CertObjInfo @@ -947,12 +993,12 @@ func (pkcs11Signer *PKCS11Signer) Sign(rand io.Reader, digest []byte, opts crypt } } - session, userPin, keyUri, keyType, privateKeyHandle, alwaysAuth, contextSpecificPin, err = getPKCS11Key(module, session, loggedIn, certUri, keyUri, false, certSlotNr, certObj, userPin, contextSpecificPin, forcePrompt, slots) + session, userPin, keyUri, keyType, privateKeyObj, keySlot, alwaysAuth, contextSpecificPin, err = getPKCS11Key(module, session, loggedIn, certUri, keyUri, false, certSlotNr, certObj, userPin, contextSpecificPin, forcePrompt, slots) if err != nil { goto cleanUp } - contextSpecificPin, signature, err = signHelper(module, session, privateKeyHandle, userPin, alwaysAuth, contextSpecificPin, forcePrompt, keyType, digest, hashFunc) + contextSpecificPin, signature, err = signHelper(module, session, privateKeyObj, keySlot, userPin, alwaysAuth, contextSpecificPin, forcePrompt, keyType, digest, hashFunc) if err != nil { goto cleanUp } else { @@ -1052,7 +1098,7 @@ func (pkcs11Signer *PKCS11Signer) CertificateChain() (certChain []*x509.Certific } // 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, forcePrompt bool, privateKeyHandle pkcs11.ObjectHandle, certificate *x509.Certificate, manufacturerId string) (string, bool) { +func checkPrivateKeyMatchesCert(module *pkcs11.Ctx, session pkcs11.SessionHandle, keyType uint, userPin string, alwaysAuth uint, contextSpecificPin string, forcePrompt bool, privateKeyObj KeyObjInfo, keySlot SlotIdInfo, certificate *x509.Certificate, manufacturerId string) (string, bool) { var digestSuffix []byte publicKey := certificate.PublicKey ecdsaPublicKey, isEcKey := publicKey.(*ecdsa.PublicKey) @@ -1079,7 +1125,7 @@ func checkPrivateKeyMatchesCert(module *pkcs11.Ctx, session pkcs11.SessionHandle digestBytes := []byte(digest) hash := sha256.Sum256(digestBytes) - contextSpecificPin, signature, err := signHelper(module, session, privateKeyHandle, userPin, alwaysAuth, "", forcePrompt, keyType, digestBytes, crypto.SHA256) + contextSpecificPin, signature, err := signHelper(module, session, privateKeyObj, keySlot, userPin, alwaysAuth, "", forcePrompt, keyType, digestBytes, crypto.SHA256) if err != nil { return "", false } @@ -1239,7 +1285,7 @@ func GetPKCS11Signer(libPkcs11 string, cert *x509.Certificate, certChain []*x509 } } - session, userPin, keyUri, keyType, _, alwaysAuth, contextSpecificPin, err = getPKCS11Key(module, session, loggedIn, certUri, keyUri, noKeyUri, certSlotNr, certObj, userPin, "", forcePrompt, slots) + session, userPin, keyUri, keyType, _, _, alwaysAuth, contextSpecificPin, err = getPKCS11Key(module, session, loggedIn, certUri, keyUri, noKeyUri, certSlotNr, certObj, userPin, "", forcePrompt, slots) if err != nil { goto fail } From be61bf74ab94953b246bcaacef17f599062a7c40 Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Fri, 8 Sep 2023 11:19:32 -0400 Subject: [PATCH 33/40] Change wording in PKCS#11 integration implementation note --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 559a2d7..ab5296c 100644 --- a/README.md +++ b/README.md @@ -179,10 +179,10 @@ identified through the hard-coded labels, `X.509 Certificate for PIV Attestation #### Implementation Notes -PKCS#11 PINs can be stored in memory as strings for the duration of the program and aren't -guaranteed to be zeroed out after the program is done with them. Since strings are -immutable in Golang, it's unclear how to circumvent the type system and get to the -underlying buffer to zero it out. +Due to this package's use of a dependency to integrate with PKCS#11 modules, we are unable +to guarantee that PINs are zeroized in memory after they are no longer needed. We will continue +to explore options to overcome this. Customers are encouraged to study the impact of this limitation +and determine whether compensating controls are warranted for their system and threat model. ### update From 3f52117fc86d20f94a2453dfbe0f3a61e9aaab76 Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Fri, 8 Sep 2023 11:54:29 -0400 Subject: [PATCH 34/40] Change "force-prompt" option to "reuse-pin" * Make the "try user PIN as the context-specific PIN" behavior opt-in, reducing the risk of lockout. * Modify unit tests to set the reuse PIN option, so that prompting doesn't occur. --- aws_signing_helper/credentials.go | 2 +- aws_signing_helper/pkcs11_signer.go | 30 ++++++++++++++--------------- aws_signing_helper/signer.go | 2 +- aws_signing_helper/signer_test.go | 7 +++++++ cmd/credentials.go | 12 ++++++------ cmd/sign_string.go | 6 +++--- 6 files changed, 33 insertions(+), 26 deletions(-) diff --git a/aws_signing_helper/credentials.go b/aws_signing_helper/credentials.go index 96a3d39..c918b4e 100644 --- a/aws_signing_helper/credentials.go +++ b/aws_signing_helper/credentials.go @@ -31,7 +31,7 @@ type CredentialsOpts struct { Debug bool Version string LibPkcs11 string - ForcePrompt bool + ReusePin bool } // Function to create session and generate credentials diff --git a/aws_signing_helper/pkcs11_signer.go b/aws_signing_helper/pkcs11_signer.go index 8bc8591..1991b53 100644 --- a/aws_signing_helper/pkcs11_signer.go +++ b/aws_signing_helper/pkcs11_signer.go @@ -85,7 +85,7 @@ type PKCS11Signer struct { contextSpecificPin string certUri *pkcs11uri.Pkcs11URI keyUri *pkcs11uri.Pkcs11URI - forcePrompt bool + reusePin bool } // Initialize a PKCS#11 module. @@ -600,7 +600,7 @@ func GetPassword(ttyReadFile *os.File, ttyWriteFile *os.File, prompt string, par } // 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, forcePrompt bool, keyType uint, digest []byte, hashFunc crypto.Hash) (_contextSpecificPin string, signature []byte, err error) { +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 @@ -649,9 +649,9 @@ func signHelper(module *pkcs11.Ctx, session pkcs11.SessionHandle, privateKeyObj 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 "force prompt" option wasn't set, try to use the + // in the input, and the "reuse PIN" option was set, try to use the // user PIN as the context-specific PIN. - if contextSpecificPin == "" && userPin != "" && !forcePrompt { + if contextSpecificPin == "" && userPin != "" && reusePin { contextSpecificPin = userPin } if contextSpecificPin != "" { @@ -714,7 +714,7 @@ afterContextSpecificLogin: // 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, forcePrompt bool, slots []SlotIdInfo) (_session pkcs11.SessionHandle, _userPin string, _keyUri *pkcs11uri.Pkcs11URI, keyType uint, privateKeyObj KeyObjInfo, slot SlotIdInfo, alwaysAuth uint, _contextSpecificPin string, err error) { +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 @@ -872,7 +872,7 @@ retry_search: curContextSpecificPin = userPin } privateKeyMatchesCert := false - curContextSpecificPin, privateKeyMatchesCert = checkPrivateKeyMatchesCert(module, session, keyType, userPin, alwaysAuth, "", forcePrompt, curPrivateKeyObj, keySlot, certObj.cert, manufacturerId) + curContextSpecificPin, privateKeyMatchesCert = checkPrivateKeyMatchesCert(module, session, keyType, userPin, alwaysAuth, "", reusePin, curPrivateKeyObj, keySlot, certObj.cert, manufacturerId) if privateKeyMatchesCert { privateKeyObj = curPrivateKeyObj contextSpecificPin = curContextSpecificPin @@ -961,7 +961,7 @@ func (pkcs11Signer *PKCS11Signer) Sign(rand io.Reader, digest []byte, opts crypt certObj CertObjInfo slots []SlotIdInfo loggedIn bool - forcePrompt bool + reusePin bool alwaysAuth uint certSlot SlotIdInfo ) @@ -974,7 +974,7 @@ func (pkcs11Signer *PKCS11Signer) Sign(rand io.Reader, digest []byte, opts crypt contextSpecificPin = pkcs11Signer.contextSpecificPin certUri = pkcs11Signer.certUri keyUri = pkcs11Signer.keyUri - forcePrompt = pkcs11Signer.forcePrompt + reusePin = pkcs11Signer.reusePin // If a PKCS#11 URI was provided for the certificate, use it. if certUri != nil { @@ -993,12 +993,12 @@ func (pkcs11Signer *PKCS11Signer) Sign(rand io.Reader, digest []byte, opts crypt } } - session, userPin, keyUri, keyType, privateKeyObj, keySlot, alwaysAuth, contextSpecificPin, err = getPKCS11Key(module, session, loggedIn, certUri, keyUri, false, certSlotNr, certObj, userPin, contextSpecificPin, forcePrompt, slots) + 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, forcePrompt, keyType, digest, hashFunc) + contextSpecificPin, signature, err = signHelper(module, session, privateKeyObj, keySlot, userPin, alwaysAuth, contextSpecificPin, reusePin, keyType, digest, hashFunc) if err != nil { goto cleanUp } else { @@ -1098,7 +1098,7 @@ func (pkcs11Signer *PKCS11Signer) CertificateChain() (certChain []*x509.Certific } // 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, forcePrompt bool, privateKeyObj KeyObjInfo, keySlot SlotIdInfo, certificate *x509.Certificate, manufacturerId string) (string, bool) { +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) @@ -1125,7 +1125,7 @@ func checkPrivateKeyMatchesCert(module *pkcs11.Ctx, session pkcs11.SessionHandle digestBytes := []byte(digest) hash := sha256.Sum256(digestBytes) - contextSpecificPin, signature, err := signHelper(module, session, privateKeyObj, keySlot, userPin, alwaysAuth, "", forcePrompt, keyType, digestBytes, crypto.SHA256) + contextSpecificPin, signature, err := signHelper(module, session, privateKeyObj, keySlot, userPin, alwaysAuth, "", reusePin, keyType, digestBytes, crypto.SHA256) if err != nil { return "", false } @@ -1193,7 +1193,7 @@ func escapeAll(s []byte) string { // 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, forcePrompt bool) (signer Signer, signingAlgorithm string, err error) { +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 @@ -1285,7 +1285,7 @@ func GetPKCS11Signer(libPkcs11 string, cert *x509.Certificate, certChain []*x509 } } - session, userPin, keyUri, keyType, _, _, alwaysAuth, contextSpecificPin, err = getPKCS11Key(module, session, loggedIn, certUri, keyUri, noKeyUri, certSlotNr, certObj, userPin, "", forcePrompt, slots) + session, userPin, keyUri, keyType, _, _, alwaysAuth, contextSpecificPin, err = getPKCS11Key(module, session, loggedIn, certUri, keyUri, noKeyUri, certSlotNr, certObj, userPin, "", reusePin, slots) if err != nil { goto fail } @@ -1306,7 +1306,7 @@ func GetPKCS11Signer(libPkcs11 string, cert *x509.Certificate, certChain []*x509 module.CloseSession(session) } - return &PKCS11Signer{cert, certChain, module, userPin, alwaysAuth, contextSpecificPin, certUri, keyUri, forcePrompt}, signingAlgorithm, nil + return &PKCS11Signer{cert, certChain, module, userPin, alwaysAuth, contextSpecificPin, certUri, keyUri, reusePin}, signingAlgorithm, nil fail: if module != nil { diff --git a/aws_signing_helper/signer.go b/aws_signing_helper/signer.go index 32de497..8d174e0 100644 --- a/aws_signing_helper/signer.go +++ b/aws_signing_helper/signer.go @@ -233,7 +233,7 @@ func GetSigner(opts *CredentialsOpts) (signer Signer, signatureAlgorithm string, if certificate != nil { opts.CertificateId = "" } - return GetPKCS11Signer(opts.LibPkcs11, certificate, certificateChain, opts.PrivateKeyId, opts.CertificateId, opts.ForcePrompt) + return GetPKCS11Signer(opts.LibPkcs11, certificate, certificateChain, opts.PrivateKeyId, opts.CertificateId, opts.ReusePin) } else { privateKey, err = ReadPrivateKeyData(privateKeyId) if err != nil { diff --git a/aws_signing_helper/signer_test.go b/aws_signing_helper/signer_test.go index 3c4091f..fbb3d78 100644 --- a/aws_signing_helper/signer_test.go +++ b/aws_signing_helper/signer_test.go @@ -263,10 +263,12 @@ func TestSign(t *testing.T) { testTable = append(testTable, CredentialsOpts{ CertificateId: basic_pkcs11_uri, PrivateKeyId: always_auth_pkcs11_uri, + ReusePin: true, }) testTable = append(testTable, CredentialsOpts{ CertificateId: cert_file, PrivateKeyId: always_auth_pkcs11_uri, + ReusePin: true, }) // Note that for the below test case, there are two matching keys. // Both keys will validate with the certificate, and one will be chosen @@ -275,6 +277,7 @@ func TestSign(t *testing.T) { testTable = append(testTable, CredentialsOpts{ CertificateId: cert_file, PrivateKeyId: base_pkcs11_uri, + ReusePin: true, }) } @@ -496,18 +499,22 @@ func TestPKCS11SignerCreationFails(t *testing.T) { testTable = append(testTable, CredentialsOpts{ CertificateId: rsa_generic_uri, PrivateKeyId: always_auth_ec_uri, + ReusePin: true, }) testTable = append(testTable, CredentialsOpts{ CertificateId: ec_generic_uri, PrivateKeyId: always_auth_rsa_uri, + ReusePin: true, }) testTable = append(testTable, CredentialsOpts{ CertificateId: "../tst/certs/ec-prime256v1-sha256-cert.pem", PrivateKeyId: always_auth_rsa_uri, + ReusePin: true, }) testTable = append(testTable, CredentialsOpts{ CertificateId: "../tst/certs/rsa-2048-sha256-cert.pem", PrivateKeyId: always_auth_ec_uri, + ReusePin: true, }) for _, credOpts := range testTable { diff --git a/cmd/credentials.go b/cmd/credentials.go index f977fb4..b44b2b0 100644 --- a/cmd/credentials.go +++ b/cmd/credentials.go @@ -21,7 +21,7 @@ var ( noVerifySSL bool withProxy bool debug bool - forcePrompt bool + reusePin bool certificateId string privateKeyId string @@ -66,13 +66,13 @@ func initCredentialsSubCommand(subCmd *cobra.Command) { subCmd.PersistentFlags().StringVar(&certSelector, "cert-selector", "", "JSON structure to identify a certificate from a certificate store. "+ "Can be passed in either as string or a file name (prefixed by \"file://\")") subCmd.PersistentFlags().StringVar(&libPkcs11, "pkcs11-lib", "", "Library for smart card / cryptographic device (OpenSC or vendor specific)") - subCmd.PersistentFlags().BoolVar(&forcePrompt, "force-prompt", false, "Force prompting for a context-specific PIN if the "+ - "CKA_ALWAYS_AUTHENTICATE attribute is set on the desired private key. This option is only relevant for the PKCS#11 integration and will "+ - "be ignored in all other cases") + subCmd.PersistentFlags().BoolVar(&reusePin, "reuse-pin", false, "Use the CKU_USER PIN as the CKU_CONTEXT_SPECIFIC PIN for "+ + "private key objects, when they are first used to sign. If the CKU_USER PIN doesn't work as the CKU_CONTEXT_SPECIFIC PIN "+ + "for a given private key object, fall back to prompting the user") subCmd.MarkFlagsMutuallyExclusive("private-key", "cert-selector") subCmd.MarkFlagsMutuallyExclusive("cert-selector", "intermediates") - subCmd.MarkFlagsMutuallyExclusive("cert-selector", "force-prompt") + subCmd.MarkFlagsMutuallyExclusive("cert-selector", "reuse-pin") } // Parses a cert selector string to a map @@ -220,7 +220,7 @@ func PopulateCredentialsOptions() error { Debug: debug, Version: Version, LibPkcs11: libPkcs11, - ForcePrompt: forcePrompt, + ReusePin: reusePin, } return nil diff --git a/cmd/sign_string.go b/cmd/sign_string.go index 6e2048f..c74a551 100644 --- a/cmd/sign_string.go +++ b/cmd/sign_string.go @@ -79,9 +79,9 @@ func init() { signStringCmd.PersistentFlags().StringVar(&certSelector, "cert-selector", "", "JSON structure to identify a certificate from a certificate store. "+ "Can be passed in either as string or a file name (prefixed by \"file://\")") signStringCmd.PersistentFlags().StringVar(&libPkcs11, "pkcs11-lib", "", "Library for smart card / cryptographic device (default: p11-kit-proxy.{so, dll, dylib})") - signStringCmd.PersistentFlags().BoolVar(&forcePrompt, "force-prompt", false, "Force prompting for a context-specific PIN if the "+ - "CKA_ALWAYS_AUTHENTICATE attribute is set on the desired private key. This option is only relevant for the PKCS#11 integration and will "+ - "be ignored in all other cases") + signStringCmd.PersistentFlags().BoolVar(&reusePin, "reuse-pin", false, "Use the CKU_USER PIN as the CKU_CONTEXT_SPECIFIC PIN for "+ + "private key objects, when they are first used to sign. If the CKU_USER PIN doesn't work as the CKU_CONTEXT_SPECIFIC PIN "+ + "for a given private key object, fall back to prompting the user") signStringCmd.PersistentFlags().Var(format, "format", "Output format. One of json, text, and bin") signStringCmd.PersistentFlags().Var(digestArg, "digest", "One of SHA256, SHA384, and SHA512") } From a68303e4b9f0ade131e54b27e3478e2b29ffb5fb Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Fri, 8 Sep 2023 14:51:56 -0400 Subject: [PATCH 35/40] Add more debug logging for PKCS#11 integration --- aws_signing_helper/pkcs11_signer.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/aws_signing_helper/pkcs11_signer.go b/aws_signing_helper/pkcs11_signer.go index 1991b53..431c3a6 100644 --- a/aws_signing_helper/pkcs11_signer.go +++ b/aws_signing_helper/pkcs11_signer.go @@ -749,6 +749,9 @@ func getPKCS11Key(module *pkcs11.Ctx, session pkcs11.SessionHandle, loggedIn boo 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. From 3cb350e7b68312c925c1702aba4db61b92b65bde Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Fri, 8 Sep 2023 14:52:41 -0400 Subject: [PATCH 36/40] Update version number for PKCS#11 integration release --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 392c279..9cb395c 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -VERSION=1.0.6 +VERSION=1.1.0 release: go build -buildmode=pie -ldflags "-X 'github.com/aws/rolesanywhere-credential-helper/cmd.Version=${VERSION}' -linkmode=external -w -s" -trimpath -o build/bin/aws_signing_helper main.go From 769e625fdf63e3fa76268a47ccc8df09e10f8d00 Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Fri, 8 Sep 2023 20:19:49 -0400 Subject: [PATCH 37/40] Update README with more documentation for PKCS#11 integration * Refactor PKCS#11 section of README and add "Other Notes" as a sub-section. * Add documentation on the "reuse-pin" flag. --- README.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ab5296c..ba3c8d0 100644 --- a/README.md +++ b/README.md @@ -160,11 +160,23 @@ for it. For systems or containers which lack p11-kit, a specific PKCS#11 provider library can be specified using the `--pkcs11-lib` parameter. +The other relevant parameter is `--reuse-pin`. This is a boolean parameter that can +be specified if the private key object you would like to use to sign data has the +`CKA_ALWAYS_AUTHENTICATE` attribute set and the `CKU_CONTEXT_SPECIFIC` PIN for the +object matches the `CKU_USER` PIN. If this parameter isn't set, you will be prompted +to provide the `CKU_CONTEXT_SPECIFIC` PIN for the object through the console. If this +parameter is set and the `CKU_USER` PIN doesn't match the `CKU_CONTEXT_SPECIFIC` PIN, +the credential helper application will fall back to prompting you. + The searching methodology used to find objects within PKCS#11 tokens can largely be found [here](https://datatracker.ietf.org/doc/html/draft-woodhouse-cert-best-practice-01). Do note -that there are some slight differences in objects are found in the credential helper +that there are some slight differences in how objects are found in the credential helper application. +#### Other Notes + +##### YubiKey Attestation Certificates + Note that if you're using a YubiKey device with PIV support, when a key pair and certificate exist in slots 9a or 9c (PIV authentication and digital signature, respectively), the YubiKey will automatically generate an attestation certificate @@ -177,7 +189,7 @@ in a URI). Attestation certificates in either of these two slots can be identified through the hard-coded labels, `X.509 Certificate for PIV Attestation 9a` or `X.509 Certificate for PIV Attestation 9c`. -#### Implementation Notes +##### Implementation Note Due to this package's use of a dependency to integrate with PKCS#11 modules, we are unable to guarantee that PINs are zeroized in memory after they are no longer needed. We will continue From 8698e72adeddf11f0c5729556137b97d146afe38 Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Tue, 19 Sep 2023 18:02:44 -0400 Subject: [PATCH 38/40] Miscellaneous minor fixes * Restructure getMatchingCerts() so that more detailed error messages are returned when matching certificate(s) can't be found. * Remove some unnecessary code relating to saving context-specific PINs. * Fix typo in comment. --- aws_signing_helper/pkcs11_signer.go | 65 +++++++++++++++-------------- 1 file changed, 34 insertions(+), 31 deletions(-) diff --git a/aws_signing_helper/pkcs11_signer.go b/aws_signing_helper/pkcs11_signer.go index 431c3a6..b07e74e 100644 --- a/aws_signing_helper/pkcs11_signer.go +++ b/aws_signing_helper/pkcs11_signer.go @@ -369,38 +369,44 @@ func getMatchingCerts(module *pkcs11.Ctx, slots []SlotIdInfo, uri *pkcs11uri.Pkc // 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 && userPin != "" { - curSession, err := module.OpenSession(slots[0].id, pkcs11.CKF_SERIAL_SESSION|pkcs11.CKS_RO_PUBLIC_SESSION) - if err != nil { - err = errNoMatchingCerts - goto fail - } + 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 - } + 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 + 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 } - module.Logout(curSession) - module.CloseSession(curSession) + } else if len(slots) == 0 { + err = errors.New("no matching slots") + goto fail + } else { + err = errors.New("multiple matching slots") + goto fail } - err = errNoMatchingCerts - goto fail - foundCert: if single && len(matchingCerts) > 1 { err = errors.New("multiple matching certificates") @@ -870,10 +876,7 @@ retry_search: } } - curContextSpecificPin := contextSpecificPin - if curContextSpecificPin == "" { - curContextSpecificPin = userPin - } + var curContextSpecificPin string privateKeyMatchesCert := false curContextSpecificPin, privateKeyMatchesCert = checkPrivateKeyMatchesCert(module, session, keyType, userPin, alwaysAuth, "", reusePin, curPrivateKeyObj, keySlot, certObj.cert, manufacturerId) if privateKeyMatchesCert { @@ -912,7 +915,7 @@ retry_search: } // So that hunting for the key can be more efficient in the future, - // return a key URI that has CKA_ID and CKA_VALUE appropriately set. + // return a key URI that has CKA_ID and CKA_LABEL appropriately set. if privateKeyObj.id != nil { keyUri.SetPathAttribute("id", escapeAll(privateKeyObj.id)) } From aa6535104fe4b5a8d4980edaa2528fc48616fd87 Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Tue, 19 Sep 2023 18:08:19 -0400 Subject: [PATCH 39/40] Update THIRD-PARTY-LICENSES.txt --- THIRD-PARTY-LICENSES.txt | 1120 +++++++++++++++++++++++++++++++++++++- 1 file changed, 1118 insertions(+), 2 deletions(-) diff --git a/THIRD-PARTY-LICENSES.txt b/THIRD-PARTY-LICENSES.txt index 56b0638..4685d1b 100644 --- a/THIRD-PARTY-LICENSES.txt +++ b/THIRD-PARTY-LICENSES.txt @@ -1,6 +1,1123 @@ +** go-pkcs11uri; version v0.0.0-20230614165346-c1cad3d2f68c -- github.com/stefanberger/go-pkcs11uri + + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS +* For go-pkcs11uri see also this required NOTICE: + (c) Copyright IBM Corporation, 2020 + +------ + +** Cobra; version v1.6.1 -- https://github.com/spf13/cobra + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. +* For Cobra see also this required NOTICE: + Copyright 2013-2023 The Cobra Authors + +------ + +** yaml; version v2.4.0 -- https://github.com/go-yaml/yaml/tree/v2.4.0 + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +* For yaml see also this required NOTICE: + Copyright (c) 2011-2019 Canonical Ltd + Copyright (c) 2006-2010 Kirill Simonov + +------ + +** mousetrap; version v1.0.1 -- https://github.com/inconshreveable/mousetrap + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2022 Alan Shreve (@inconshreveable) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +* For mousetrap see also this required NOTICE: + N/A + +------ + +** go-jmespath; version v0.4.0 -- https://github.com/jmespath/go-jmespath + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +* For go-jmespath see also this required NOTICE: + Copyright 2015 James Saryerwinnie + +------ + +** sys; version v0.10.0 -- https://cs.opensource.google/go/x/sys +Copyright (c) 2009 The Go Authors. All rights reserved. +** term; version v0.10.0 -- https://cs.opensource.google/go/x/term +Copyright (c) 2009 The Go Authors. All rights reserved. +** crypto; version v0.10.0 -- https://cs.opensource.google/go/x/crypto +Copyright (c) 2009 The Go Authors. All rights reserved. + +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +------ + +** pkcs11; version v1.1.1 -- github.com/miekg/pkcs11 +Copyright (c) 2013 Miek Gieben. All rights reserved. + +Copyright (c) 2013 Miek Gieben. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Miek Gieben nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +------ + +** pflag; version v1.0.5 -- github.com/spf13/pflag +Copyright (c) 2012 Alex Ogier. All rights reserved. +Copyright (c) 2012 The Go Authors. All rights reserved. + +Copyright (c) 2012 Alex Ogier. All rights reserved. +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +------ + +** go-spew; version v1.1.1 -- https://github.com/davecgh/go-spew +Copyright (c) 2012-2016 Dave Collins + +ISC License + +Copyright (c) 2012-2016 Dave Collins + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +------ + ** smimesign; version v0.2.0-rc1 -- https://github.com/github/smimesign Copyright (c) 2017 GitHub, Inc. - + MIT License Copyright (c) 2017 GitHub, Inc. @@ -22,4 +1139,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - From 86085523ee7e797a63275b0eae1a11f9847579c0 Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Tue, 19 Sep 2023 18:14:28 -0400 Subject: [PATCH 40/40] Add note about unattended workloads and the reuse-pin flag --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ba3c8d0..59d3fd2 100644 --- a/README.md +++ b/README.md @@ -166,7 +166,12 @@ be specified if the private key object you would like to use to sign data has th object matches the `CKU_USER` PIN. If this parameter isn't set, you will be prompted to provide the `CKU_CONTEXT_SPECIFIC` PIN for the object through the console. If this parameter is set and the `CKU_USER` PIN doesn't match the `CKU_CONTEXT_SPECIFIC` PIN, -the credential helper application will fall back to prompting you. +the credential helper application will fall back to prompting you. In an unattended +scenario, this flag is very helpful. There is currently no way in which to specify +the `CKU_CONTEXT_SPECIFIC` PIN without being prompted for it, so you are out of luck +for the time being when it comes to unattended workloads if the `CKU_CONTEXT_SPECIFIC` +PIN of the private key object you want to use is different from the `CKU_USER` PIN of +the token that it belongs to. The searching methodology used to find objects within PKCS#11 tokens can largely be found [here](https://datatracker.ietf.org/doc/html/draft-woodhouse-cert-best-practice-01). Do note