From 292ecec51ddc2c92bea9c19abf87f3ade1e80b9e Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Wed, 4 Oct 2023 16:48:18 -0400 Subject: [PATCH] Indirectly support the searching of certificates and private keys in local machine certificate stores * Support the passing in of arbitrary names for system certificate stores when leveraging the Windows certificate store integration. * Note that while the stores are still opened within the CERT_SYSTEM_STORE_CURRENT_USER context, "all current user certificate stores except the Current User/Personal store inherit the contents of local machine certificate stores." This means that certificates and private keys added to local machine certificate stores can still be accessed through the credential helper. * Do a case-insensitive comparison of the system store name passed in with the predefined system store names (as defined in the Microsoft documentation). If there is a match, canonicalize the case so that it can be used to open a valid system certificate store. Otherwise, use the provided name (assuming that the user has created a custom certificate store with that name). --- aws_signing_helper/signer.go | 16 ++++++++--- .../windows_cert_store_signer.go | 5 ++-- cmd/credentials.go | 27 ++++++++++++++++--- cmd/read_certificate_data.go | 5 +++- cmd/sign_string.go | 3 +++ 5 files changed, 46 insertions(+), 10 deletions(-) diff --git a/aws_signing_helper/signer.go b/aws_signing_helper/signer.go index 8d174e0..3ba3296 100644 --- a/aws_signing_helper/signer.go +++ b/aws_signing_helper/signer.go @@ -36,15 +36,25 @@ type SignerParams struct { } type CertIdentifier struct { - Subject string - Issuer string - SerialNumber *big.Int + Subject string + Issuer string + SerialNumber *big.Int + SystemStoreName string // Only relevant in the case of Windows } var ( // ErrUnsupportedHash is returned by Signer.Sign() when the provided hash // algorithm isn't supported. ErrUnsupportedHash = errors.New("unsupported hash algorithm") + + // Predefined system store names. + // See: https://learn.microsoft.com/en-us/windows/win32/seccrypto/system-store-locations + SystemStoreNames = []string{ + "MY", + "Root", + "Trust", + "CA", + } ) // Interface that all signers will have to implement diff --git a/aws_signing_helper/windows_cert_store_signer.go b/aws_signing_helper/windows_cert_store_signer.go index 69f4876..9047460 100644 --- a/aws_signing_helper/windows_cert_store_signer.go +++ b/aws_signing_helper/windows_cert_store_signer.go @@ -142,10 +142,11 @@ func (secStatus securityStatus) Error() string { return fmt.Sprintf("SECURITY_STATUS %d", int(secStatus)) } -// Gets the certificates that match the given CertIdentifier within the user's "MY" certificate store. +// Gets the certificates that match the given CertIdentifier within the user's specified system +// certificate store. By default, that is "MY". // If there is only a single matching certificate, then its chain will be returned too func GetMatchingCertsAndChain(certIdentifier CertIdentifier) (store windows.Handle, certCtx *windows.CertContext, certChain []*x509.Certificate, certContainers []CertificateContainer, err error) { - storeName, err := windows.UTF16PtrFromString("MY") + storeName, err := windows.UTF16PtrFromString(certIdentifier.SystemStoreName) if err != nil { return 0, nil, nil, nil, errors.New("unable to UTF-16 encode personal certificate store name") } diff --git a/cmd/credentials.go b/cmd/credentials.go index b44b2b0..ec3df3e 100644 --- a/cmd/credentials.go +++ b/cmd/credentials.go @@ -27,6 +27,7 @@ var ( privateKeyId string certificateBundleId string certSelector string + systemStoreName string libPkcs11 string @@ -65,12 +66,16 @@ 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(&systemStoreName, "system-store-name", "MY", "Name of the system store to search for within the "+ + "CERT_SYSTEM_STORE_CURRENT_USER context. By default, its value will be \"MY\". Note that this flag is only relevant for Windows "+ + "certificate stores and will be ignored otherwise") subCmd.PersistentFlags().StringVar(&libPkcs11, "pkcs11-lib", "", "Library for smart card / cryptographic device (OpenSC or vendor specific)") 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("private-key", "system-store-name") subCmd.MarkFlagsMutuallyExclusive("cert-selector", "intermediates") subCmd.MarkFlagsMutuallyExclusive("cert-selector", "reuse-pin") } @@ -173,9 +178,12 @@ func PopulateCertIdentifierFromCertSelectorStr(certSelectorStr string) (helper.C // Populates a CertIdentifier using a cert selector // Note that this method can take in a file name as a the cert selector -func PopulateCertIdentifier(certSelector string) (helper.CertIdentifier, error) { - var certIdentifier helper.CertIdentifier - var err error +func PopulateCertIdentifier(certSelector string, systemStoreName string) (helper.CertIdentifier, error) { + var ( + certIdentifier helper.CertIdentifier + err error + ) + if certSelector != "" { if strings.HasPrefix(certSelector, "file://") { certSelectorFile, err := ioutil.ReadFile(strings.TrimPrefix(certSelector, "file://")) @@ -193,13 +201,24 @@ func PopulateCertIdentifier(certSelector string) (helper.CertIdentifier, error) } } } + matchedPredefinedSystemStoreName := false + for _, predefinedSystemStoreName := range helper.SystemStoreNames { + if strings.EqualFold(systemStoreName, predefinedSystemStoreName) { + certIdentifier.SystemStoreName = predefinedSystemStoreName + matchedPredefinedSystemStoreName = true + break + } + } + if !matchedPredefinedSystemStoreName { + certIdentifier.SystemStoreName = systemStoreName + } return certIdentifier, err } // Populate CredentialsOpts that is used to aggregate all the information required to call CreateSession func PopulateCredentialsOptions() error { - certIdentifier, err := PopulateCertIdentifier(certSelector) + certIdentifier, err := PopulateCertIdentifier(certSelector, systemStoreName) if err != nil { return err } diff --git a/cmd/read_certificate_data.go b/cmd/read_certificate_data.go index b995d02..6a56c2d 100644 --- a/cmd/read_certificate_data.go +++ b/cmd/read_certificate_data.go @@ -18,6 +18,9 @@ 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(&systemStoreName, "system-store-name", "MY", "Name of the system store to search for within the "+ + "CERT_SYSTEM_STORE_CURRENT_USER context. By default, its value will be \"MY\". Note that this flag is only relevant for Windows "+ + "certificate stores and will be ignored otherwise") 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") } @@ -43,7 +46,7 @@ var readCertificateDataCmd = &cobra.Command{ Long: `Diagnostic command to read certificate data, either from files or from a certificate store`, Run: func(cmd *cobra.Command, args []string) { - certIdentifier, err := PopulateCertIdentifier(certSelector) + certIdentifier, err := PopulateCertIdentifier(certSelector, systemStoreName) if err != nil { log.Println("unable to populate CertIdentifier") os.Exit(1) diff --git a/cmd/sign_string.go b/cmd/sign_string.go index c74a551..33d87d9 100644 --- a/cmd/sign_string.go +++ b/cmd/sign_string.go @@ -78,6 +78,9 @@ 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(&systemStoreName, "system-store-name", "MY", "Name of the system store to search for within the "+ + "CERT_SYSTEM_STORE_CURRENT_USER context. By default, its value will be \"MY\". Note that this flag is only relevant for Windows "+ + "certificate stores and will be ignored otherwise") signStringCmd.PersistentFlags().StringVar(&libPkcs11, "pkcs11-lib", "", "Library for smart card / cryptographic device (default: p11-kit-proxy.{so, dll, dylib})") 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 "+