Skip to content

Commit

Permalink
Indirectly support the searching of certificates and private keys in
Browse files Browse the repository at this point in the history
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).
  • Loading branch information
13ajay committed Oct 5, 2023
1 parent 6e01b11 commit 292ecec
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 10 deletions.
16 changes: 13 additions & 3 deletions aws_signing_helper/signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions aws_signing_helper/windows_cert_store_signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down
27 changes: 23 additions & 4 deletions cmd/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ var (
privateKeyId string
certificateBundleId string
certSelector string
systemStoreName string

libPkcs11 string

Expand Down Expand Up @@ -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")
}
Expand Down Expand Up @@ -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://"))
Expand All @@ -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
}
Expand Down
5 changes: 4 additions & 1 deletion cmd/read_certificate_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand All @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions cmd/sign_string.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 "+
Expand Down

0 comments on commit 292ecec

Please sign in to comment.