Skip to content

Commit a2bd650

Browse files
authored
Merge pull request #55 from aws/feature/RolesAnywhere-4906
Indirectly support the searching of certificates and private keys in local machine certificate stores
2 parents 6e01b11 + 9a74406 commit a2bd650

8 files changed

+72
-20
lines changed

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
VERSION=1.1.0
1+
VERSION=1.1.1
22

33
release:
44
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

README.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -71,18 +71,20 @@ If the above is placed in a file called `selector.json`, it can be specified wit
7171
--cert-selector Key=x509Subject,Value=CN=Subject Key=x509Issuer,Value=CN=Issuer Key=x509Serial,Value=15D19632234BF759A32802C0DA88F9E8AFC8702D
7272
```
7373

74-
The example given here is quite simple (they each only contain a single RDN), so it may not be obvious, but the Subject and Issuer values roughly follow the [RFC 2253](https://www.rfc-editor.org/rfc/rfc2253.html) Distinguished Names syntax.
74+
The example given here is quite simple (the Subject and Issuer each contain only a single RDN), so it may not be obvious, but the Subject and Issuer values roughly follow the [RFC 2253](https://www.rfc-editor.org/rfc/rfc2253.html) Distinguished Names syntax.
7575

7676
### sign-string
7777

7878
Signs a fixed strings: `"AWS Roles Anywhere Credential Helper Signing Test" || SIGN_STRING_TEST_VERSION || SHA256("IAM RA" || PUBLIC_KEY_BYTE_ARRAY)`. Useful for validating your private key and digest. Either the path to the private key must be provided with the `--private-key` parameter, or a certificate selector must be provided through the `--cert-selector` parameter (if you want to use the OS certificate store integration). Other parameters that can be used are `--digest`, which must be one of `SHA256 (*default*) | SHA384 | SHA512`, and `--format`, which must be one of `text (*default*) | json | bin`.
7979

8080
### credential-process
8181

82-
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.
82+
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 could be to use the [PKCS#11 integration](#pkcs11-integration) (using the `--pkcs11-pin` flag to locate objects in PKCS#11 tokens) or (depending on your OS) use the `--cert-selector` flag. More details about the `--cert-selector` flag can be found in [this section](#cert-selector-flag).
8383

8484
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.
8585

86+
Also note that in Windows, if you would like the credential helper to search a system certificate store other than "MY" ("MY" will be the default) in the `CERT_SYSTEM_STORE_CURRENT_USER` context, you can specify the name of the certificate store through the `--system-store-name` flag. It's not possible for the credential helper to search multiple Windows system certificate stores at once currently. But it will indirectly search certificate stores in the `CERT_SYSTEM_STORE_LOCAL_MACHINE` context since all current user certificate stores will inherit contents of local machine certificate stores. The only exception to this rule is the Current User/Personal ("MY") store. Please see the [Microsoft documentation](https://learn.microsoft.com/en-us/windows-hardware/drivers/install/local-machine-and-current-user-certificate-stores?source=recommendations) for more details.
87+
8688
When `credential-process` is used, AWS SDKs store the returned AWS credentials in memory. AWS SDKs will keep track of the credential expiration and generate new AWS session credentials via the credential process, provided the certificate has not expired or been revoked.
8789

8890
When the AWS CLI uses a `credential-process`, the AWS CLI calls the `credential-process` for every CLI command issued, which will result in the creation of a new role session and a slight delay when excuting commands. To avoid this delay from getting new credentials when using the AWS CLI, you can use `serve` or `update`.

aws_signing_helper/signer.go

+13-3
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,25 @@ type SignerParams struct {
3636
}
3737

3838
type CertIdentifier struct {
39-
Subject string
40-
Issuer string
41-
SerialNumber *big.Int
39+
Subject string
40+
Issuer string
41+
SerialNumber *big.Int
42+
SystemStoreName string // Only relevant in the case of Windows
4243
}
4344

4445
var (
4546
// ErrUnsupportedHash is returned by Signer.Sign() when the provided hash
4647
// algorithm isn't supported.
4748
ErrUnsupportedHash = errors.New("unsupported hash algorithm")
49+
50+
// Predefined system store names.
51+
// See: https://learn.microsoft.com/en-us/windows/win32/seccrypto/system-store-locations
52+
SystemStoreNames = []string{
53+
"MY",
54+
"Root",
55+
"Trust",
56+
"CA",
57+
}
4858
)
4959

5060
// Interface that all signers will have to implement

aws_signing_helper/windows_cert_store_signer.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,11 @@ func (secStatus securityStatus) Error() string {
142142
return fmt.Sprintf("SECURITY_STATUS %d", int(secStatus))
143143
}
144144

145-
// Gets the certificates that match the given CertIdentifier within the user's "MY" certificate store.
145+
// Gets the certificates that match the given CertIdentifier within the user's specified system
146+
// certificate store. By default, that is "MY".
146147
// If there is only a single matching certificate, then its chain will be returned too
147148
func GetMatchingCertsAndChain(certIdentifier CertIdentifier) (store windows.Handle, certCtx *windows.CertContext, certChain []*x509.Certificate, certContainers []CertificateContainer, err error) {
148-
storeName, err := windows.UTF16PtrFromString("MY")
149+
storeName, err := windows.UTF16PtrFromString(certIdentifier.SystemStoreName)
149150
if err != nil {
150151
return 0, nil, nil, nil, errors.New("unable to UTF-16 encode personal certificate store name")
151152
}

cmd/credentials.go

+25-4
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ var (
2727
privateKeyId string
2828
certificateBundleId string
2929
certSelector string
30+
systemStoreName string
3031

3132
libPkcs11 string
3233

@@ -65,14 +66,20 @@ func initCredentialsSubCommand(subCmd *cobra.Command) {
6566
subCmd.PersistentFlags().StringVar(&certificateBundleId, "intermediates", "", "Path to intermediate certificate bundle file")
6667
subCmd.PersistentFlags().StringVar(&certSelector, "cert-selector", "", "JSON structure to identify a certificate from a certificate store. "+
6768
"Can be passed in either as string or a file name (prefixed by \"file://\")")
69+
subCmd.PersistentFlags().StringVar(&systemStoreName, "system-store-name", "MY", "Name of the system store to search for within the "+
70+
"CERT_SYSTEM_STORE_CURRENT_USER context. Note that this flag is only relevant for Windows certificate stores and will be ignored otherwise")
6871
subCmd.PersistentFlags().StringVar(&libPkcs11, "pkcs11-lib", "", "Library for smart card / cryptographic device (OpenSC or vendor specific)")
6972
subCmd.PersistentFlags().BoolVar(&reusePin, "reuse-pin", false, "Use the CKU_USER PIN as the CKU_CONTEXT_SPECIFIC PIN for "+
7073
"private key objects, when they are first used to sign. If the CKU_USER PIN doesn't work as the CKU_CONTEXT_SPECIFIC PIN "+
7174
"for a given private key object, fall back to prompting the user")
7275

76+
subCmd.MarkFlagsMutuallyExclusive("certificate", "cert-selector")
77+
subCmd.MarkFlagsMutuallyExclusive("certificate", "system-store-name")
7378
subCmd.MarkFlagsMutuallyExclusive("private-key", "cert-selector")
79+
subCmd.MarkFlagsMutuallyExclusive("private-key", "system-store-name")
7480
subCmd.MarkFlagsMutuallyExclusive("cert-selector", "intermediates")
7581
subCmd.MarkFlagsMutuallyExclusive("cert-selector", "reuse-pin")
82+
subCmd.MarkFlagsMutuallyExclusive("system-store-name", "reuse-pin")
7683
}
7784

7885
// Parses a cert selector string to a map
@@ -173,9 +180,12 @@ func PopulateCertIdentifierFromCertSelectorStr(certSelectorStr string) (helper.C
173180

174181
// Populates a CertIdentifier using a cert selector
175182
// Note that this method can take in a file name as a the cert selector
176-
func PopulateCertIdentifier(certSelector string) (helper.CertIdentifier, error) {
177-
var certIdentifier helper.CertIdentifier
178-
var err error
183+
func PopulateCertIdentifier(certSelector string, systemStoreName string) (helper.CertIdentifier, error) {
184+
var (
185+
certIdentifier helper.CertIdentifier
186+
err error
187+
)
188+
179189
if certSelector != "" {
180190
if strings.HasPrefix(certSelector, "file://") {
181191
certSelectorFile, err := ioutil.ReadFile(strings.TrimPrefix(certSelector, "file://"))
@@ -193,13 +203,24 @@ func PopulateCertIdentifier(certSelector string) (helper.CertIdentifier, error)
193203
}
194204
}
195205
}
206+
matchedPredefinedSystemStoreName := false
207+
for _, predefinedSystemStoreName := range helper.SystemStoreNames {
208+
if strings.EqualFold(systemStoreName, predefinedSystemStoreName) {
209+
certIdentifier.SystemStoreName = predefinedSystemStoreName
210+
matchedPredefinedSystemStoreName = true
211+
break
212+
}
213+
}
214+
if !matchedPredefinedSystemStoreName {
215+
certIdentifier.SystemStoreName = systemStoreName
216+
}
196217

197218
return certIdentifier, err
198219
}
199220

200221
// Populate CredentialsOpts that is used to aggregate all the information required to call CreateSession
201222
func PopulateCredentialsOptions() error {
202-
certIdentifier, err := PopulateCertIdentifier(certSelector)
223+
certIdentifier, err := PopulateCertIdentifier(certSelector, systemStoreName)
203224
if err != nil {
204225
return err
205226
}

cmd/credentials_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ func TestValidSelectorParsing(t *testing.T) {
1818
"Key=x509Issuer,Value=CN=Issuer",
1919
}
2020
for _, fixture := range fixtures {
21-
_, err := PopulateCertIdentifier(fixture)
21+
_, err := PopulateCertIdentifier(fixture, "MY")
2222
if err != nil {
2323
t.Log("Unable to populate cert identifier from selector")
2424
t.Fail()
@@ -36,7 +36,7 @@ func TestInvalidSelectorParsing(t *testing.T) {
3636
"Key=aljsdf,Value=aljsdfadsf",
3737
}
3838
for _, fixture := range fixtures {
39-
_, err := PopulateCertIdentifier(fixture)
39+
_, err := PopulateCertIdentifier(fixture, "MY")
4040
if err == nil {
4141
t.Log("Expected parsing failure, but received none")
4242
t.Fail()

cmd/read_certificate_data.go

+14-5
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,13 @@ func init() {
1818
readCertificateDataCmd.PersistentFlags().StringVar(&certificateId, "certificate", "", "Path to certificate file")
1919
readCertificateDataCmd.PersistentFlags().StringVar(&certSelector, "cert-selector", "", "JSON structure to identify a certificate from a certificate store."+
2020
" Can be passed in either as string or a file name (prefixed by \"file://\")")
21+
readCertificateDataCmd.PersistentFlags().StringVar(&systemStoreName, "system-store-name", "MY", "Name of the system store to search for within the "+
22+
"CERT_SYSTEM_STORE_CURRENT_USER context. Note that this flag is only relevant for Windows certificate stores and will be ignored otherwise")
2123
readCertificateDataCmd.PersistentFlags().StringVar(&libPkcs11, "pkcs11-lib", "", "Library for smart card / cryptographic device (OpenSC or vendor specific)")
2224
readCertificateDataCmd.PersistentFlags().BoolVar(&debug, "debug", false, "To print debug output")
25+
26+
readCertificateDataCmd.MarkFlagsMutuallyExclusive("certificate", "cert-selector")
27+
readCertificateDataCmd.MarkFlagsMutuallyExclusive("certificate", "system-store-name")
2328
}
2429

2530
type PrintCertificate func(int, helper.CertificateContainer)
@@ -43,7 +48,7 @@ var readCertificateDataCmd = &cobra.Command{
4348
Long: `Diagnostic command to read certificate data, either from files or
4449
from a certificate store`,
4550
Run: func(cmd *cobra.Command, args []string) {
46-
certIdentifier, err := PopulateCertIdentifier(certSelector)
51+
certIdentifier, err := PopulateCertIdentifier(certSelector, systemStoreName)
4752
if err != nil {
4853
log.Println("unable to populate CertIdentifier")
4954
os.Exit(1)
@@ -62,7 +67,7 @@ var readCertificateDataCmd = &cobra.Command{
6267
log.Println(err)
6368
os.Exit(1)
6469
}
65-
} else if certificateId != "" && certIdentifier == (helper.CertIdentifier{}) {
70+
} else if certificateId != "" {
6671
data, err := helper.ReadCertificateData(certificateId)
6772
if err != nil {
6873
os.Exit(1)
@@ -82,9 +87,13 @@ var readCertificateDataCmd = &cobra.Command{
8287
os.Exit(1)
8388
}
8489
}
85-
fmt.Printf("Matching identities\n")
86-
for index, certContainer := range certContainers {
87-
printFunction(index, certContainer)
90+
if len(certContainers) == 0 {
91+
fmt.Println("No matching identities")
92+
} else {
93+
fmt.Println("Matching identities")
94+
for index, certContainer := range certContainers {
95+
printFunction(index, certContainer)
96+
}
8897
}
8998
},
9099
}

cmd/sign_string.go

+10-1
Original file line numberDiff line numberDiff line change
@@ -73,17 +73,26 @@ func init() {
7373
rootCmd.AddCommand(signStringCmd)
7474
format = newEnum([]string{"json", "text", "bin"}, "json")
7575
digestArg = newEnum([]string{"SHA256", "SHA384", "SHA512"}, "SHA256")
76-
signStringCmd.PersistentFlags().StringVar(&certificateId, "certificate", "", "Path to certificate file or PKCS#11 URI to identify the certificate")
76+
signStringCmd.PersistentFlags().StringVar(&certificateId, "certificate", "", "PKCS#11 URI to identify the certificate")
7777
signStringCmd.PersistentFlags().StringVar(&privateKeyId, "private-key", "", "Path to private key file or PKCS#11 URI to identify the private key")
7878
signStringCmd.PersistentFlags().BoolVar(&debug, "debug", false, "To print debug output")
7979
signStringCmd.PersistentFlags().StringVar(&certSelector, "cert-selector", "", "JSON structure to identify a certificate from a certificate store. "+
8080
"Can be passed in either as string or a file name (prefixed by \"file://\")")
81+
signStringCmd.PersistentFlags().StringVar(&systemStoreName, "system-store-name", "MY", "Name of the system store to search for within the "+
82+
"CERT_SYSTEM_STORE_CURRENT_USER context. Note that this flag is only relevant for Windows certificate stores and will be ignored otherwise")
8183
signStringCmd.PersistentFlags().StringVar(&libPkcs11, "pkcs11-lib", "", "Library for smart card / cryptographic device (default: p11-kit-proxy.{so, dll, dylib})")
8284
signStringCmd.PersistentFlags().BoolVar(&reusePin, "reuse-pin", false, "Use the CKU_USER PIN as the CKU_CONTEXT_SPECIFIC PIN for "+
8385
"private key objects, when they are first used to sign. If the CKU_USER PIN doesn't work as the CKU_CONTEXT_SPECIFIC PIN "+
8486
"for a given private key object, fall back to prompting the user")
8587
signStringCmd.PersistentFlags().Var(format, "format", "Output format. One of json, text, and bin")
8688
signStringCmd.PersistentFlags().Var(digestArg, "digest", "One of SHA256, SHA384, and SHA512")
89+
90+
signStringCmd.MarkFlagsMutuallyExclusive("certificate", "cert-selector")
91+
signStringCmd.MarkFlagsMutuallyExclusive("certificate", "system-store-name")
92+
signStringCmd.MarkFlagsMutuallyExclusive("private-key", "cert-selector")
93+
signStringCmd.MarkFlagsMutuallyExclusive("private-key", "system-store-name")
94+
signStringCmd.MarkFlagsMutuallyExclusive("cert-selector", "reuse-pin")
95+
signStringCmd.MarkFlagsMutuallyExclusive("system-store-name", "reuse-pin")
8796
}
8897

8998
func getFixedStringToSign(publicKey crypto.PublicKey) string {

0 commit comments

Comments
 (0)