├── .gitignore ├── script └── run-local-kms ├── README.md ├── doc.go ├── .golangci.yml ├── go.mod ├── LICENSE ├── .github └── workflows │ └── go.yml ├── kms_test.go ├── decrypter_test.go ├── kms.go ├── go.sum ├── decrypter.go ├── signer_test.go ├── testdata └── seed.yaml └── signer.go /.gitignore: -------------------------------------------------------------------------------- 1 | /.gobincache 2 | -------------------------------------------------------------------------------- /script/run-local-kms: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | exec docker run -p 8087:8080 -v "$(pwd)/testdata/seed.yaml:/init/seed.yaml" nsmithuk/local-kms:3.11.7 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # awskms 2 | 3 | A Go [`crypto.Signer`](https://golang.org/pkg/crypto/#Signer) and [`crypto.Decrypter`](https://golang.org/pkg/crypto/#Decrypter) implementation for [AWS's KMS Asymmetric key support](https://docs.aws.amazon.com/kms/latest/developerguide/symmetric-asymmetric.html) 4 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Package awskms implements a crypto.Signer that uses AWS's KMS service 2 | // 3 | // e.g for creating a suitible key: 4 | // `aws kms create-key --customer-master-key-spec RSA_2048 --key-usage SIGN_VERIFY` 5 | // `aws kms create-key --customer-master-key-spec RSA_2048 --key-usage ENCRYPT_DECRYPT` 6 | package awskms 7 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | deadline: 5m 3 | 4 | output: 5 | format: colored-line-number 6 | 7 | issues: 8 | exclude-use-default: false 9 | exclude: 10 | - "exported \\w+ (\\S*['.]*)([a-zA-Z'.*]*) should have comment( \\(or a comment on this block\\))? or be unexported" 11 | - "Error return value of .((os\\.)?std(out|err)\\..*|.*Close|.*Flush|os\\.Remove(All)?|.*printf?|os\\.(Un)?Setenv). is not checked" 12 | 13 | linters: 14 | enable: 15 | - errcheck 16 | - golint 17 | - goimports 18 | - govet 19 | - misspell 20 | - staticcheck 21 | - unused 22 | - unparam 23 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/lstoll/awskms 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/aws/aws-sdk-go-v2 v1.24.0 7 | github.com/aws/aws-sdk-go-v2/config v1.26.2 8 | github.com/aws/aws-sdk-go-v2/service/kms v1.27.7 9 | ) 10 | 11 | require ( 12 | github.com/aws/aws-sdk-go-v2/credentials v1.16.13 // indirect 13 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10 // indirect 14 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.9 // indirect 15 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.9 // indirect 16 | github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 // indirect 17 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect 18 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.9 // indirect 19 | github.com/aws/aws-sdk-go-v2/service/sso v1.18.5 // indirect 20 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5 // indirect 21 | github.com/aws/aws-sdk-go-v2/service/sts v1.26.6 // indirect 22 | github.com/aws/smithy-go v1.19.0 // indirect 23 | ) 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Lincoln Stoll 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 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. 8 | 9 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | on: [push] 3 | jobs: 4 | 5 | build: 6 | name: Build 7 | runs-on: ubuntu-latest 8 | 9 | services: 10 | local-kms: 11 | image: nsmithuk/local-kms:3.11.7 12 | volumes: 13 | # for some reason only testdata works here - if it's a subdir or the 14 | # file, it'll fail the checkout because it can't be used. So just roll 15 | # with this for now. 16 | - ${{ github.workspace }}/testdata/:/init/ 17 | ports: 18 | - 8087:8080 19 | 20 | steps: 21 | 22 | - name: Set up latest Go 23 | uses: actions/setup-go@v4 24 | with: 25 | go-version: stable 26 | 27 | - name: Check out code 28 | uses: actions/checkout@v4 29 | 30 | - name: Restart local-kms 31 | # Restart local-kms after volumes have been checked out 32 | # https://github.com/orgs/community/discussions/42127 33 | uses: docker://docker 34 | with: 35 | args: docker restart "${{ job.services.local-kms.id }}" 36 | 37 | - name: Go test 38 | run: go test -v ./... 39 | env: 40 | TEST_LOCAL_KMS: "1" 41 | 42 | - name: golangci-lint 43 | uses: golangci/golangci-lint-action@v3 44 | with: 45 | version: latest 46 | -------------------------------------------------------------------------------- /kms_test.go: -------------------------------------------------------------------------------- 1 | package awskms 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "testing" 7 | 8 | "github.com/aws/aws-sdk-go-v2/config" 9 | "github.com/aws/aws-sdk-go-v2/credentials" 10 | "github.com/aws/aws-sdk-go-v2/service/kms" 11 | ) 12 | 13 | type testKeys struct { 14 | ECSignVerifyAlias string 15 | RSASignVerifyAlias string 16 | RSAEncryptDecryptAlias string 17 | 18 | // these are only wired up for local-kms usage 19 | ECSignVerifyARN string 20 | RSASignVerifyARN string 21 | RSAEncryptDecryptARN string 22 | } 23 | 24 | func testKMSClient(t *testing.T) (*kms.Client, testKeys) { 25 | t.Helper() 26 | 27 | if os.Getenv("TEST_KMS") == "" && os.Getenv("TEST_LOCAL_KMS") == "" { 28 | t.Skip("TEST_KMS or TEST_LOCAL_KMS not set, skipping") 29 | } 30 | 31 | keys := testKeys{ 32 | ECSignVerifyAlias: os.Getenv("TEST_KMS_ALIAS_EC_SIGN_VERIFY"), 33 | RSASignVerifyAlias: os.Getenv("TEST_KMS_ALIAS_RSA_SIGN_VERIFY"), 34 | RSAEncryptDecryptAlias: os.Getenv("TEST_KMS_ALIAS_RSA_ENCRYPT_DECRYPT"), 35 | } 36 | 37 | kmsOpts := []func(*kms.Options){} 38 | awscfg, err := config.LoadDefaultConfig(context.TODO()) 39 | if err != nil { 40 | t.Fatalf("loading default aws config: %v", err) 41 | } 42 | 43 | if os.Getenv("TEST_LOCAL_KMS") != "" { 44 | kmsAddr := "http://localhost:8087" // run-local-kms/github actions default 45 | if addr := os.Getenv("TEST_LOCAL_KMS_ADDR"); addr != "" { 46 | kmsAddr = addr 47 | } 48 | kmsOpts = append(kmsOpts, func(o *kms.Options) { 49 | o.BaseEndpoint = &kmsAddr 50 | }) 51 | 52 | awscfg.Region = "us-east-2" 53 | awscfg.Credentials = credentials.NewStaticCredentialsProvider("AKIA11111111", "2222222222222", "") 54 | 55 | keys.ECSignVerifyAlias = "alias/ec_sign_verify" 56 | keys.RSASignVerifyAlias = "alias/rsa_sign_verify" 57 | keys.RSAEncryptDecryptAlias = "alias/rsa_encrypt_decrypt" 58 | 59 | keys.ECSignVerifyARN = "arn:aws:kms:eu-west-2:111122223333:key/3aa0759e-169c-4de4-bb74-38b02b319e9d" 60 | keys.RSASignVerifyARN = "arn:aws:kms:eu-west-2:111122223333:key/cd7b6e4a-154a-4fb8-b013-63bb43c48cd8" 61 | keys.RSAEncryptDecryptARN = "arn:aws:kms:eu-west-2:111122223333:key/a1d398f5-2b1c-40d5-82f4-bb97696d6975" 62 | } 63 | 64 | kmsClient := kms.NewFromConfig(awscfg, kmsOpts...) 65 | 66 | return kmsClient, keys 67 | } 68 | 69 | func TestExtractKeyID(t *testing.T) { 70 | arn := "arn:aws:kms:us-west-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab" 71 | 72 | keyID, err := extractKeyID(arn) 73 | if err != nil { 74 | t.Fatalf("error extracting key ID: %v", err) 75 | } 76 | 77 | if got, want := keyID, "1234abcd-12ab-34cd-56ef-1234567890ab"; got != want { 78 | t.Fatalf("got: %s, want: %s", got, want) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /decrypter_test.go: -------------------------------------------------------------------------------- 1 | package awskms 2 | 3 | import ( 4 | "context" 5 | "crypto" 6 | "crypto/rand" 7 | "crypto/rsa" 8 | "crypto/sha1" 9 | "crypto/sha256" 10 | "hash" 11 | "strings" 12 | "testing" 13 | "time" 14 | 15 | kmstypes "github.com/aws/aws-sdk-go-v2/service/kms/types" 16 | ) 17 | 18 | func TestDecrypterRSA(t *testing.T) { 19 | client, aliases := testKMSClient(t) 20 | 21 | ctx := context.Background() 22 | ctx, cancel := context.WithTimeout(ctx, 5*time.Second) 23 | defer cancel() 24 | 25 | decrypter, err := NewDecrypter(ctx, client, aliases.RSAEncryptDecryptAlias) 26 | if err != nil { 27 | t.Fatalf("error creating decrypter: %v", err) 28 | } 29 | 30 | pubKey, ok := decrypter.Public().(*rsa.PublicKey) 31 | if !ok { 32 | t.Fatalf("public key is not RSA key, it is a %T", decrypter.Public()) 33 | } 34 | 35 | message := []byte(`hello this is a message`) 36 | 37 | for _, tc := range []struct { 38 | Name string 39 | Hash hash.Hash 40 | Options crypto.DecrypterOpts 41 | }{ 42 | { 43 | Name: "Nil opts", 44 | Hash: sha1.New(), 45 | Options: nil, 46 | }, 47 | { 48 | Name: "Options", 49 | Hash: sha256.New(), 50 | Options: &DecrypterOpts{ 51 | Context: ctx, 52 | EncryptionAlgorithm: kmstypes.EncryptionAlgorithmSpecRsaesOaepSha256, 53 | }, 54 | }, 55 | } { 56 | t.Run(tc.Name, func(t *testing.T) { 57 | ciphertext, err := rsa.EncryptOAEP(tc.Hash, rand.Reader, pubKey, message, []byte("")) 58 | if err != nil { 59 | t.Fatalf("error encrypting message: %v", err) 60 | } 61 | 62 | plaintext, err := decrypter.Decrypt(rand.Reader, ciphertext, tc.Options) 63 | if err != nil { 64 | t.Fatalf("failed to decrypt message: %v", err) 65 | } 66 | 67 | if string(message) != string(plaintext) { 68 | t.Fatalf("got: %s, want: %s", string(plaintext), string(message)) 69 | } 70 | }) 71 | } 72 | } 73 | 74 | func TestLoadingDecryptionKey(t *testing.T) { 75 | client, aliases := testKMSClient(t) 76 | ctx := context.Background() 77 | 78 | if _, err := NewDecrypter(ctx, client, aliases.RSASignVerifyAlias); err == nil { 79 | t.Error("error should have occurred when creating a signer with a sign/verify key") 80 | } 81 | 82 | signer, err := NewDecrypter(ctx, client, aliases.RSAEncryptDecryptAlias) 83 | if err != nil { 84 | t.Errorf("unexpected error on a valid client creation: %v", err) 85 | } 86 | 87 | wantAlias := strings.TrimPrefix(aliases.RSAEncryptDecryptAlias, "alias/") 88 | if signer.KeyInfo().Alias != wantAlias { 89 | t.Errorf("want key info alias %s, got: %s", wantAlias, signer.KeyInfo().Alias) 90 | } 91 | 92 | if signer.KeyInfo().ARN != aliases.RSAEncryptDecryptARN { 93 | t.Errorf("want key info arn %s, got: %s", aliases.RSAEncryptDecryptARN, signer.KeyInfo().ARN) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /kms.go: -------------------------------------------------------------------------------- 1 | package awskms 2 | 3 | import ( 4 | "context" 5 | "crypto" 6 | "crypto/ecdsa" 7 | "crypto/rsa" 8 | "crypto/x509" 9 | "fmt" 10 | "strings" 11 | 12 | awsarn "github.com/aws/aws-sdk-go-v2/aws/arn" 13 | "github.com/aws/aws-sdk-go-v2/service/kms" 14 | ) 15 | 16 | var _ KMSClient = (*kms.Client)(nil) 17 | 18 | // KMSClient describes the KMS operations this module requires, this will 19 | // normally be satisfied by the aws-sdk-go-v2 *kms.Client 20 | type KMSClient interface { 21 | GetPublicKey(context.Context, *kms.GetPublicKeyInput, ...func(*kms.Options)) (*kms.GetPublicKeyOutput, error) 22 | Sign(context.Context, *kms.SignInput, ...func(*kms.Options)) (*kms.SignOutput, error) 23 | Decrypt(context.Context, *kms.DecryptInput, ...func(*kms.Options)) (*kms.DecryptOutput, error) 24 | } 25 | 26 | // KeyInfo contains information about the underlying KMS key. 27 | type KeyInfo struct { 28 | // ID contains the ID of the key. 29 | ID string 30 | // ARN contains the AWS Resource Name for the KMS key 31 | ARN string 32 | // Alias contains the key alias that was used to retrieve the key, if it was 33 | // retrieve by an alias. Otherwise, it will be empty. The alias/ prefix is 34 | // stripped. 35 | Alias string 36 | } 37 | 38 | // parsePubKeyResp loads the public key and the KeyInfo from the response 39 | func parsePubKeyResp(reqID string, resp *kms.GetPublicKeyOutput) (crypto.PublicKey, KeyInfo, error) { 40 | pub, err := parsePublicKey(resp.PublicKey) 41 | if err != nil { 42 | return nil, KeyInfo{}, err 43 | } 44 | 45 | // this KeyId field is always the ARN. Naming in KMS is fun. 46 | targetKeyID, err := extractKeyID(*resp.KeyId) 47 | if err != nil { 48 | return nil, KeyInfo{}, err 49 | } 50 | 51 | var alias string 52 | if strings.HasPrefix(reqID, "alias/") { 53 | alias = strings.TrimPrefix(reqID, "alias/") 54 | } 55 | 56 | return pub, KeyInfo{ 57 | ARN: *resp.KeyId, 58 | ID: targetKeyID, 59 | Alias: alias, 60 | }, err 61 | } 62 | 63 | func parsePublicKey(publicKey []byte) (crypto.PublicKey, error) { 64 | pubk, err := x509.ParsePKIXPublicKey(publicKey) 65 | if err != nil { 66 | return nil, fmt.Errorf("failed to parse public key: %w", err) 67 | } 68 | 69 | switch pubk.(type) { 70 | case *rsa.PublicKey: 71 | return pubk, nil 72 | case *ecdsa.PublicKey: 73 | return pubk, nil 74 | default: 75 | return nil, fmt.Errorf("public key is of unhandled type: %T", pubk) 76 | } 77 | } 78 | 79 | func extractKeyID(arn string) (string, error) { 80 | parn, err := awsarn.Parse(arn) 81 | if err != nil { 82 | return "", fmt.Errorf("parsing arn %s: %w", arn, err) 83 | } 84 | arnSegments := strings.Split(arn, ":") 85 | if len(arnSegments) != 6 { 86 | return "", fmt.Errorf("unexpected number of ARN segments: %s", arn) 87 | } 88 | 89 | targetKeyID := parn.Resource 90 | if !strings.HasPrefix(targetKeyID, "key/") { 91 | return "", fmt.Errorf("unexpected key ID format: %s", targetKeyID) 92 | } 93 | 94 | return strings.TrimPrefix(targetKeyID, "key/"), nil 95 | } 96 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/aws/aws-sdk-go-v2 v1.24.0 h1:890+mqQ+hTpNuw0gGP6/4akolQkSToDJgHfQE7AwGuk= 2 | github.com/aws/aws-sdk-go-v2 v1.24.0/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4= 3 | github.com/aws/aws-sdk-go-v2/config v1.26.2 h1:+RWLEIWQIGgrz2pBPAUoGgNGs1TOyF4Hml7hCnYj2jc= 4 | github.com/aws/aws-sdk-go-v2/config v1.26.2/go.mod h1:l6xqvUxt0Oj7PI/SUXYLNyZ9T/yBPn3YTQcJLLOdtR8= 5 | github.com/aws/aws-sdk-go-v2/credentials v1.16.13 h1:WLABQ4Cp4vXtXfOWOS3MEZKr6AAYUpMczLhgKtAjQ/8= 6 | github.com/aws/aws-sdk-go-v2/credentials v1.16.13/go.mod h1:Qg6x82FXwW0sJHzYruxGiuApNo31UEtJvXVSZAXeWiw= 7 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10 h1:w98BT5w+ao1/r5sUuiH6JkVzjowOKeOJRHERyy1vh58= 8 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10/go.mod h1:K2WGI7vUvkIv1HoNbfBA1bvIZ+9kL3YVmWxeKuLQsiw= 9 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.9 h1:v+HbZaCGmOwnTTVS86Fleq0vPzOd7tnJGbFhP0stNLs= 10 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.9/go.mod h1:Xjqy+Nyj7VDLBtCMkQYOw1QYfAEZCVLrfI0ezve8wd4= 11 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.9 h1:N94sVhRACtXyVcjXxrwK1SKFIJrA9pOJ5yu2eSHnmls= 12 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.9/go.mod h1:hqamLz7g1/4EJP+GH5NBhcUMLjW+gKLQabgyz6/7WAU= 13 | github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 h1:GrSw8s0Gs/5zZ0SX+gX4zQjRnRsMJDJ2sLur1gRBhEM= 14 | github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY= 15 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw= 16 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ= 17 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.9 h1:Nf2sHxjMJR8CSImIVCONRi4g0Su3J+TSTbS7G0pUeMU= 18 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.9/go.mod h1:idky4TER38YIjr2cADF1/ugFMKvZV7p//pVeV5LZbF0= 19 | github.com/aws/aws-sdk-go-v2/service/kms v1.27.7 h1:wN7AN7iOiAgT9HmdifZNSvbr6S7gSpLjSSOQHIaGmFc= 20 | github.com/aws/aws-sdk-go-v2/service/kms v1.27.7/go.mod h1:D9FVDkZjkZnnFHymJ3fPVz0zOUlNSd0xcIIVmmrAac8= 21 | github.com/aws/aws-sdk-go-v2/service/sso v1.18.5 h1:ldSFWz9tEHAwHNmjx2Cvy1MjP5/L9kNoR0skc6wyOOM= 22 | github.com/aws/aws-sdk-go-v2/service/sso v1.18.5/go.mod h1:CaFfXLYL376jgbP7VKC96uFcU8Rlavak0UlAwk1Dlhc= 23 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5 h1:2k9KmFawS63euAkY4/ixVNsYYwrwnd5fIvgEKkfZFNM= 24 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5/go.mod h1:W+nd4wWDVkSUIox9bacmkBP5NMFQeTJ/xqNabpzSR38= 25 | github.com/aws/aws-sdk-go-v2/service/sts v1.26.6 h1:HJeiuZ2fldpd0WqngyMR6KW7ofkXNLyOaHwEIGm39Cs= 26 | github.com/aws/aws-sdk-go-v2/service/sts v1.26.6/go.mod h1:XX5gh4CB7wAs4KhcF46G6C8a2i7eupU19dcAAE+EydU= 27 | github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM= 28 | github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= 29 | github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= 30 | -------------------------------------------------------------------------------- /decrypter.go: -------------------------------------------------------------------------------- 1 | package awskms 2 | 3 | import ( 4 | "context" 5 | "crypto" 6 | "fmt" 7 | "io" 8 | 9 | "github.com/aws/aws-sdk-go-v2/service/kms" 10 | kmstypes "github.com/aws/aws-sdk-go-v2/service/kms/types" 11 | ) 12 | 13 | var _ crypto.Decrypter = (*Decrypter)(nil) 14 | 15 | // Decrypter implents a crypto.Decrypter that uses a RSA key stored in AWS 16 | // It should be initialized via NewDecrypter 17 | type Decrypter struct { 18 | kms KMSClient 19 | key KeyInfo 20 | public crypto.PublicKey 21 | } 22 | 23 | // NewDecrypter will configure a new decrypter using the given KMS client, bound 24 | // to the given key. This requires successful connectivity to the KMS service, to 25 | // retrieve the public key. 26 | func NewDecrypter(ctx context.Context, kmssvc KMSClient, keyID string) (*Decrypter, error) { 27 | pkresp, err := kmssvc.GetPublicKey(ctx, &kms.GetPublicKeyInput{ 28 | KeyId: &keyID, 29 | }) 30 | if err != nil { 31 | return nil, fmt.Errorf("failed to fetch public key for %s: %w", keyID, err) 32 | } 33 | 34 | if pkresp.KeyUsage != kmstypes.KeyUsageTypeEncryptDecrypt { 35 | return nil, fmt.Errorf("key usage must be %s, not %s", kmstypes.KeyUsageTypeSignVerify, pkresp.KeyUsage) 36 | } 37 | 38 | pub, info, err := parsePubKeyResp(keyID, pkresp) 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | return &Decrypter{ 44 | kms: kmssvc, 45 | key: info, 46 | public: pub, 47 | }, nil 48 | } 49 | 50 | // KeyInfo returns information about the KMS key in use. 51 | func (d *Decrypter) KeyInfo() KeyInfo { 52 | return d.key 53 | } 54 | 55 | // Public returns the public key corresponding to the opaque, 56 | // private key. 57 | func (d *Decrypter) Public() crypto.PublicKey { 58 | return d.public 59 | } 60 | 61 | // DecrypterOpts implements crypto.DecrypterOpts for this Decrypter 62 | type DecrypterOpts struct { 63 | // Context sets the context for remote calls. 64 | Context context.Context 65 | // EncryptionAlgorithm indicates the encryption algorithm that was used. 66 | // If not set, defaults to types.EncryptionAlgorithmSpecRsaesOaepSha1 67 | EncryptionAlgorithm kmstypes.EncryptionAlgorithmSpec 68 | } 69 | 70 | // Decrypt decrypts msg. A *DecrypterOpts can be passed to customize the 71 | // algorithm in use. If opts are nil, EncryptionAlgorithmOaepSha256 will be 72 | // used. 73 | func (d *Decrypter) Decrypt(rand io.Reader, msg []byte, opts crypto.DecrypterOpts) (plaintext []byte, err error) { 74 | var ( 75 | ctx = context.Background() 76 | alg = kmstypes.EncryptionAlgorithmSpecRsaesOaepSha1 77 | ) 78 | 79 | if do, ok := opts.(*DecrypterOpts); ok { 80 | if do.Context != nil { 81 | ctx = do.Context 82 | } 83 | if do.EncryptionAlgorithm != "" { 84 | alg = do.EncryptionAlgorithm 85 | } 86 | } 87 | 88 | dresp, err := d.kms.Decrypt(ctx, &kms.DecryptInput{ 89 | KeyId: &d.key.ARN, 90 | CiphertextBlob: msg, 91 | EncryptionAlgorithm: alg, 92 | }) 93 | if err != nil { 94 | return nil, fmt.Errorf("decrypt operation failed: %w", err) 95 | } 96 | 97 | return dresp.Plaintext, nil 98 | } 99 | -------------------------------------------------------------------------------- /signer_test.go: -------------------------------------------------------------------------------- 1 | package awskms 2 | 3 | import ( 4 | "context" 5 | "crypto" 6 | "crypto/ecdsa" 7 | "crypto/rand" 8 | "crypto/rsa" 9 | "crypto/sha256" 10 | "strings" 11 | "testing" 12 | "time" 13 | ) 14 | 15 | func TestSignerRSA(t *testing.T) { 16 | client, aliases := testKMSClient(t) 17 | 18 | ctx := context.Background() 19 | ctx, cancel := context.WithTimeout(ctx, 5*time.Second) 20 | defer cancel() 21 | 22 | signer, err := NewSigner(ctx, client, aliases.RSASignVerifyAlias) 23 | if err != nil { 24 | t.Fatalf("failed to set up signer: %v", err) 25 | } 26 | 27 | pubKey, ok := signer.Public().(*rsa.PublicKey) 28 | if !ok { 29 | t.Fatalf("public key is not RSA key, it is a %T", signer.Public()) 30 | } 31 | 32 | message := []byte(`hello this is a message`) 33 | hash := sha256.Sum256(message) 34 | 35 | for _, tc := range []struct { 36 | Name string 37 | Options crypto.SignerOpts 38 | Verify func(pub *rsa.PublicKey, sig []byte) error 39 | }{ 40 | { 41 | Name: "RSA PKCS1 v1.5, unwrapped options", 42 | Options: crypto.SHA256, 43 | Verify: func(pub *rsa.PublicKey, sig []byte) error { 44 | return rsa.VerifyPKCS1v15(pub, crypto.SHA256, hash[:], sig) 45 | }, 46 | }, 47 | { 48 | Name: "RSA PKCS1 v1.5, wrapped options", 49 | Options: &SignerOpts{ 50 | Context: ctx, 51 | Options: crypto.SHA256, 52 | }, 53 | Verify: func(pub *rsa.PublicKey, sig []byte) error { 54 | return rsa.VerifyPKCS1v15(pub, crypto.SHA256, hash[:], sig) 55 | }, 56 | }, 57 | { 58 | Name: "RSA PSS, unwrapped options", 59 | Options: &rsa.PSSOptions{ 60 | Hash: crypto.SHA256, 61 | }, 62 | Verify: func(pub *rsa.PublicKey, sig []byte) error { 63 | return rsa.VerifyPSS(pub, crypto.SHA256, hash[:], sig, &rsa.PSSOptions{ 64 | Hash: crypto.SHA256, 65 | }) 66 | }, 67 | }, 68 | { 69 | Name: "RSA PSS, wrapped options", 70 | Options: &SignerOpts{ 71 | Context: ctx, 72 | Options: &rsa.PSSOptions{ 73 | Hash: crypto.SHA256, 74 | }, 75 | }, 76 | Verify: func(pub *rsa.PublicKey, sig []byte) error { 77 | return rsa.VerifyPSS(pub, crypto.SHA256, hash[:], sig, &rsa.PSSOptions{ 78 | Hash: crypto.SHA256, 79 | }) 80 | }, 81 | }, 82 | } { 83 | t.Run(tc.Name, func(t *testing.T) { 84 | sig, err := signer.Sign(rand.Reader, hash[:], tc.Options) 85 | if err != nil { 86 | t.Fatalf("error signing message: %v", err) 87 | } 88 | 89 | if err := tc.Verify(pubKey, sig); err != nil { 90 | t.Fatalf("error verifying message with public key: %v", err) 91 | } 92 | }) 93 | } 94 | } 95 | 96 | func TestSignerECDSA(t *testing.T) { 97 | client, aliases := testKMSClient(t) 98 | 99 | ctx := context.Background() 100 | ctx, cancel := context.WithTimeout(ctx, 5*time.Second) 101 | defer cancel() 102 | 103 | signer, err := NewSigner(ctx, client, aliases.ECSignVerifyAlias) 104 | if err != nil { 105 | t.Fatalf("failed to set up signer: %v", err) 106 | } 107 | 108 | pubKey, ok := signer.Public().(*ecdsa.PublicKey) 109 | if !ok { 110 | t.Fatalf("public key is not ECDSA key, it is a %T", signer.Public()) 111 | } 112 | 113 | message := []byte(`hello this is a message`) 114 | hash := sha256.Sum256(message) 115 | 116 | for _, tc := range []struct { 117 | Name string 118 | Options crypto.SignerOpts 119 | }{ 120 | { 121 | Name: "ECDSA", 122 | Options: crypto.SHA256, 123 | }, 124 | { 125 | Name: "ECDSA, wrapped options", 126 | Options: &SignerOpts{ 127 | Context: ctx, 128 | Options: crypto.SHA256, 129 | }, 130 | }, 131 | } { 132 | t.Run(tc.Name, func(t *testing.T) { 133 | sig, err := signer.Sign(rand.Reader, hash[:], tc.Options) 134 | if err != nil { 135 | t.Fatalf("error signing message: %v", err) 136 | } 137 | 138 | valid := ecdsa.VerifyASN1(pubKey, hash[:], sig) 139 | if !valid { 140 | t.Fatal("signature is not valid") 141 | } 142 | }) 143 | } 144 | } 145 | 146 | func TestLoadingSigningKey(t *testing.T) { 147 | client, aliases := testKMSClient(t) 148 | ctx := context.Background() 149 | 150 | if _, err := NewSigner(ctx, client, aliases.RSAEncryptDecryptAlias); err == nil { 151 | t.Error("error should have occurred when creating a signer with a encrypt/decrypt key") 152 | } 153 | 154 | signer, err := NewSigner(ctx, client, aliases.RSASignVerifyAlias) 155 | if err != nil { 156 | t.Errorf("unexpected error on a valid client creation: %v", err) 157 | } 158 | 159 | wantAlias := strings.TrimPrefix(aliases.RSASignVerifyAlias, "alias/") 160 | if signer.KeyInfo().Alias != wantAlias { 161 | t.Errorf("want key info alias %s, got: %s", wantAlias, signer.KeyInfo().Alias) 162 | } 163 | 164 | if signer.KeyInfo().ARN != aliases.RSASignVerifyARN { 165 | t.Errorf("want key info arn %s, got: %s", aliases.RSASignVerifyARN, signer.KeyInfo().ARN) 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /testdata/seed.yaml: -------------------------------------------------------------------------------- 1 | Aliases: 2 | - AliasName: alias/ec_sign_verify 3 | TargetKeyId: 3aa0759e-169c-4de4-bb74-38b02b319e9d 4 | - AliasName: alias/rsa_sign_verify 5 | TargetKeyId: cd7b6e4a-154a-4fb8-b013-63bb43c48cd8 6 | - AliasName: alias/rsa_encrypt_decrypt 7 | TargetKeyId: a1d398f5-2b1c-40d5-82f4-bb97696d6975 8 | 9 | Keys: 10 | Asymmetric: 11 | Ecc: 12 | - Metadata: 13 | KeyId: 3aa0759e-169c-4de4-bb74-38b02b319e9d 14 | KeyUsage: SIGN_VERIFY 15 | Description: ECC key with curve secp256r1, SignVerify 16 | # openssl ecparam -name prime256v1 -genkey -noout -out private-key.pem 17 | PrivateKeyPem: | 18 | -----BEGIN EC PRIVATE KEY----- 19 | MHcCAQEEIMAvJNxl9XlDBC5SUFJzZBx2kMHdDggcFKgwdZ2OwrXVoAoGCCqGSM49 20 | AwEHoUQDQgAEBFNF/PjHzC47b43940P95n4apsBx3Euvg7zuGCLhby/hOE8Wbe0T 21 | fDvaaHhGNGfo+t1AomB0Br1thNTa9KC2kg== 22 | -----END EC PRIVATE KEY----- 23 | Rsa: 24 | - Metadata: 25 | KeyId: cd7b6e4a-154a-4fb8-b013-63bb43c48cd8 26 | KeyUsage: SIGN_VERIFY 27 | Description: 2048b RSA key, SignVerify 28 | # openssl genrsa -out private-key.pem 2048 29 | PrivateKeyPem: | 30 | -----BEGIN PRIVATE KEY----- 31 | MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDCFDiTET/5mJBa 32 | 1R+KNMeKlhJOyKE2apR7uJpbqaG8FPBZZ3MRu36g/Og5mEKLz+tTrfQ8yOLvKz3u 33 | MXh5RTpuXrxnO7HbPE844Cr+GrRxX9gSsgwR3o89F89rHMu7hgknH954me5+F8x7 34 | Z1qVoyZdpNgpRad8YF5NFkX2+9BdtbJ04YAUp/hkxOrUy7i25epGFGNHEz7+/L9B 35 | HtKEkIPGZyeBnV8rAQq8an0yI1z1L57Zq5Nnjo3HKJllA2iI3YTmaV9RpI8FqkkA 36 | nMFW4b0Rd/7FjNZBv6gqSBfhHjmyKr1945UZCWq986Mw3kNERssIbrD/gJeP/bVk 37 | pOZbq6jXAgMBAAECggEAArSIc1hWtXJwdKuq1GDW9TIRUELQqHME3dKdaDCtnfMG 38 | xKBCBNnBspnD3OWn6836oEB4+o3sR9wo8VSEMuCJp3cvlhrJaF5nVkodn0wc/sWQ 39 | u/X6YBdjlLzrubj9iAAeyYpSrgirP9uyEHhI1XQgOUbucmjTpFKtjdKFKCOrbFrx 40 | SfP4laIewRwP430c0olfzU27V5B6e2UwNiDe+QLJ6zpjFtMHW5KaPaTG++CmHTTz 41 | 4nXJpVXYzc6ffrrEEKYE19dRgbhQMMtVYq7Fv6pzDixDk3joPyEHFhYO/JldEfJH 42 | YqPayZw8Ka0g8CNRhz0O2P9qhSwuDpKWXmdAoe4XMQKBgQDlQwhZDFS84gAvwCn1 43 | UkZcg3yseox0ufC8He7gHY5N06uHHjhhpiWnRyswsgX3JHTO4HQ/5O7wm++Qw29k 44 | arzje8Y1JC7Luje38D4V2ICCd+MGbZx/K7CVCQvlDqtEb3vlyM59YoXoCzqXOH1k 45 | /Et90gsw4Ba41CtrlcOPNXALBwKBgQDYtr/rl5xoJRRR1f9qp6+SnU+mvgEaLHpl 46 | sYbLD8fM2mgGne6JnGiKw0oNZyLdYOUaZuHStIImTbZ/Nt+ZOtQgi4U4vUYloQ3o 47 | 6jZ5fOxOdnZapE8j7pux/pW3DTOTgnrntCynNMGB4dya9V0ttqcQqjHAsYDJFZki 48 | t5YV8TRvsQKBgBAmtcrYNIwN++dhSVNwFWblGH5qL8T1aIFINpDxZBoVhGsDdQNk 49 | Ghj7yY4nrIXpupTseSQbM74drjXrnshbQK1iBeeugeF2YACEW0tcskj+uy6zwCex 50 | Y0JMndroOqaamAbrK6jnrPlY+Sq4HxVjNMpLZuBtmQwyXlC4/0ryf883AoGAN/kj 51 | otb6189T6zspiEOdTnIBI6EJqdf5GRD8LrB5u4hhu+vIs5RJUCXWl5We5KzlH6eZ 52 | BhIHV5tU2pCyueH+7hT7KMeCOa/aZuEvhrtkUXO5Z/nuUuqLHjMSOkBXpO7bjcL0 53 | kY9Ht6M9lmxvT6JQjjZXOVzfVZCVh+kBA9PT5lECgYBVP+sGxoPiMaooCMCtN1sZ 54 | dCujE3i/L9EOTmw5k/vPyHteEjuEMhfMdIsYodC76eidJ7rmP5w11a3GEyYuywA3 55 | itlYHDbgGkrUHhcDBliG/5k7EGLTvClOmLuGWA9x/w+4iYu5znmRpn+h8BtZHkUa 56 | vB+m6/oT34CrNQuI+JkWAw== 57 | -----END PRIVATE KEY----- 58 | - Metadata: 59 | KeyId: a1d398f5-2b1c-40d5-82f4-bb97696d6975 60 | KeyUsage: ENCRYPT_DECRYPT 61 | Description: 2048b RSA key, EncryptDecrypt 62 | # openssl genrsa -out private-key.pem 2048 63 | PrivateKeyPem: | 64 | -----BEGIN PRIVATE KEY----- 65 | MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCRztS7fXmeAsgU 66 | gOcDHgSHbj0Bo7jai+V/BRMFifCmAlR9y2KuXq1WENhnu7+r6eCz6ldiZ7ebCkeJ 67 | ILwVXT/wKo+gV+C6kKgSUGAZF9ejIhtZ3HavoYWqLytjXoZ2w6s3A0vwVO3EGnTQ 68 | N/mOxp7YxHNPgwWs8wz14rmvO3aSKqxoOZmBhNGvYD3X1MQHK4XgVTCwwMXV0JSl 69 | W7MeIWhW5fkS8dhocubGFYoHRbg8vezWnENU3Mms2QS0iOcUnTtemjhisRUAueV1 70 | LRDClcUe+2s0bMYjhm2IPtYtmqcLOkZju9bpmXTj39tVG6JWrb0pan9d9nWTovVq 71 | Uq/T+EIbAgMBAAECggEANGT8cLsv/qXFPAjt+mLJAdmNNo+cl3v55eDu6uicfysL 72 | LYDuXvZKl0jKWsxOnfoW8s/cwtG+WoohFpeavpqZpTXGErpBWQkuPBaB0cKNjGNv 73 | LKVaSNZj77mvf6+x5e9+EPCwUxZRskxHeaMDnFinMAG35fSRD4rvN1RXoS5e9zqv 74 | d1Mi833cmMzUEk351rqy+Idl7Ck5cSg1KgWkWqPNQw7I+jpM22ywzBqqMlRrDC3G 75 | 7UXgLCh7eHR5UmVAvg2jT0SbYFYPWOgEcfIWtxEWYWDhUwdUr9FsiG1LUY9k3TaL 76 | E6pBiBu/DLR5L9/MquUnNHZP5Sldbde6GlLF1kJMwQKBgQDHzFki5p5ap35N/7tG 77 | SKP148bl3f4lkvnk9UFZPw6Rv5eMzqPo3ONqrobvn/rSGqKS8+OJIyTG52WtTwpX 78 | enQ/ZPRopb7a/LJzl9PZpNd+wnwh6bEfxGQtdF5HKnQaF65ScsT8ZSjkqclW96Mo 79 | Vk+9OadKT5OC944KwmpODduo6wKBgQC60pegxpc9aPZXmfwuy4vp9YeMNARU4Yeh 80 | 8fKVLX3ZMMwQSp3+9maLR8M8GiCN5uAd3n7ACsuafcr7UGCtLW3o/Ed9+1PClQE7 81 | 4dfT2ycdqDqkc+zPME1yIIK0ovuFRSsz0GG9r1IdCc5yZz5KtQ7yzygEKp1Ftlqe 82 | aOSTVQN/kQKBgE9fy3mmDPsO14q9MfU2Ho0tNHNVwgXDYiunk5KVDoCwkNCLt2pn 83 | dqGHPGwuLkMR+cxPv/FXWOD8RNSZNg6Sw2P8A20fi7cL9QkfJDau2j2tngsLACcS 84 | qom0VVAIeZ90EPkIR2HN/nT5TteSAeugfoU6Rk+rVXsORgvYOMtRjhaJAoGALsSF 85 | PLQ6LB9Ye7mKjdRapCjY09o5s1357Cp7RajrtjEnBHUJdEgOh5NhZmxL9fnz5MZM 86 | CJsrOPOAmsUWToPu3cI5y2ndH/rZIjp5IwnbkusIMx7qCzvWR1moHrwmJsiX9hKW 87 | RTvcXHSV/8GksGL+6P2+0LwyPr9VlA5CGjfP9mECgYBEUoMnqeZeIWPRSsF/saoQ 88 | 2PfRtU2jhULVuHbHFX/c2DdwOfe9Mdpq1B03uwB2PMQ+qXC6nPDlu0OoHjv5dFpq 89 | 5QlnR8wvulQ4Jm5J0WHe6/tV+RjvSm4upEFCBYaN4FTq891kovWgK3YYKwAqInaJ 90 | asznVlDLHiRxrZbgcGLJwA== 91 | -----END PRIVATE KEY----- 92 | -------------------------------------------------------------------------------- /signer.go: -------------------------------------------------------------------------------- 1 | package awskms 2 | 3 | import ( 4 | "context" 5 | "crypto" 6 | "crypto/rsa" 7 | "fmt" 8 | "io" 9 | 10 | "github.com/aws/aws-sdk-go-v2/service/kms" 11 | kmstypes "github.com/aws/aws-sdk-go-v2/service/kms/types" 12 | ) 13 | 14 | var _ crypto.Signer = (*Signer)(nil) 15 | 16 | // Signer is a crypto.Signer that uses a AWS KMS backed key. It should be 17 | // initialized via NewSigner 18 | type Signer struct { 19 | kms KMSClient 20 | key KeyInfo 21 | public crypto.PublicKey 22 | // hashm maps the given crypto.hash to the alg for the KMS side. it will 23 | // depend on the key type 24 | hashm map[crypto.Hash]kmstypes.SigningAlgorithmSpec 25 | // psshashm explicitly maps hashes to their pss type. this is because we 26 | // offer this as an opt-in for RSA keys 27 | psshashm map[crypto.Hash]kmstypes.SigningAlgorithmSpec 28 | } 29 | 30 | // NewSigner will configure a new Signer using the given KMS client, bound to 31 | // the given key. This requires successful connectivity to the KMS service, to 32 | // retrieve the public key. 33 | func NewSigner(ctx context.Context, kmssvc KMSClient, keyID string) (*Signer, error) { 34 | pkresp, err := kmssvc.GetPublicKey(ctx, &kms.GetPublicKeyInput{ 35 | KeyId: &keyID, 36 | }) 37 | if err != nil { 38 | return nil, fmt.Errorf("failed to fetch public key for %s: %w", keyID, err) 39 | } 40 | 41 | if pkresp.KeyUsage != kmstypes.KeyUsageTypeSignVerify { 42 | return nil, fmt.Errorf("key usage must be %s, not %s", kmstypes.KeyUsageTypeSignVerify, pkresp.KeyUsage) 43 | } 44 | 45 | pub, info, err := parsePubKeyResp(keyID, pkresp) 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | s := &Signer{ 51 | kms: kmssvc, 52 | key: info, 53 | public: pub, 54 | } 55 | 56 | if err := s.setSigningHashes(pkresp.SigningAlgorithms); err != nil { 57 | return nil, err 58 | } 59 | 60 | return s, nil 61 | } 62 | 63 | // KeyInfo returns information about the KMS key in use. 64 | func (s *Signer) KeyInfo() KeyInfo { 65 | return s.key 66 | } 67 | 68 | // Public returns the public key corresponding to the opaque, 69 | // private key. 70 | func (s *Signer) Public() crypto.PublicKey { 71 | return s.public 72 | } 73 | 74 | // SignerOpts implements crypto.SignerOpts for this Signer. It can wrap a Base 75 | // set of options, as per the Sign method docs. 76 | type SignerOpts struct { 77 | // Context to use for remote calls. 78 | Context context.Context 79 | // Options to use to select algorithm etc. This can not be nil. 80 | Options crypto.SignerOpts 81 | } 82 | 83 | // HashFunc is unused - we need this to implement crypto.SignerOpts, but we will 84 | // use either the Base's SignerOpts, or treat it like no opts were passed. 85 | func (s *SignerOpts) HashFunc() crypto.Hash { 86 | panic("should not be called") 87 | } 88 | 89 | // Sign signs digest with the private key. By default, for an RSA key a PKCS#1 90 | // v1.5 signature, and for an EC key a DER-serialised, ASN.1 signature structure 91 | // will be returned. If the passed options are a *rsa.PSSOptions, the RSA key 92 | // will return a PSS signature. If a *SignerOpts is passed, the Base options 93 | // will be treated as if they were passed directly. 94 | // 95 | // Hash is required, as must correspond to a hash the KMS service supports. 96 | // 97 | // rand is unused. 98 | func (s *Signer) Sign(_ io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) { 99 | var ( 100 | ctx = context.Background() 101 | hm map[crypto.Hash]kmstypes.SigningAlgorithmSpec 102 | ) 103 | 104 | if so, ok := opts.(*SignerOpts); ok { 105 | if so.Context != nil { 106 | ctx = so.Context 107 | } 108 | if so.Options == nil { 109 | return nil, fmt.Errorf("the Options field on SignerOpts can not be nil") 110 | } 111 | opts = so.Options 112 | } 113 | 114 | switch opts.(type) { 115 | case *rsa.PSSOptions: 116 | if len(s.psshashm) < 1 { 117 | return nil, fmt.Errorf("key does not support pss") 118 | } 119 | hm = s.psshashm 120 | default: 121 | hm = s.hashm 122 | } 123 | 124 | alg, ok := hm[opts.HashFunc()] 125 | if !ok { 126 | return nil, fmt.Errorf("hash %v not supported", opts.HashFunc()) 127 | } 128 | 129 | sresp, err := s.kms.Sign(ctx, &kms.SignInput{ 130 | KeyId: &s.key.ARN, 131 | MessageType: kmstypes.MessageTypeDigest, 132 | SigningAlgorithm: alg, 133 | Message: digest, 134 | }) 135 | if err != nil { 136 | return nil, fmt.Errorf("sign operation failed: %w", err) 137 | } 138 | 139 | return sresp.Signature, nil 140 | } 141 | 142 | func (s *Signer) setSigningHashes(algorithms []kmstypes.SigningAlgorithmSpec) error { 143 | var ecdsa, pss, pkcs15 = make(map[crypto.Hash]kmstypes.SigningAlgorithmSpec), make(map[crypto.Hash]kmstypes.SigningAlgorithmSpec), make(map[crypto.Hash]kmstypes.SigningAlgorithmSpec) 144 | 145 | for _, a := range algorithms { 146 | switch a { 147 | case kmstypes.SigningAlgorithmSpecRsassaPssSha256: 148 | pss[crypto.SHA256] = kmstypes.SigningAlgorithmSpecRsassaPssSha256 149 | case kmstypes.SigningAlgorithmSpecRsassaPssSha384: 150 | pss[crypto.SHA384] = kmstypes.SigningAlgorithmSpecRsassaPssSha384 151 | case kmstypes.SigningAlgorithmSpecRsassaPssSha512: 152 | pss[crypto.SHA512] = kmstypes.SigningAlgorithmSpecRsassaPssSha512 153 | case kmstypes.SigningAlgorithmSpecRsassaPkcs1V15Sha256: 154 | pkcs15[crypto.SHA256] = kmstypes.SigningAlgorithmSpecRsassaPkcs1V15Sha256 155 | case kmstypes.SigningAlgorithmSpecRsassaPkcs1V15Sha384: 156 | pkcs15[crypto.SHA384] = kmstypes.SigningAlgorithmSpecRsassaPkcs1V15Sha384 157 | case kmstypes.SigningAlgorithmSpecRsassaPkcs1V15Sha512: 158 | pkcs15[crypto.SHA512] = kmstypes.SigningAlgorithmSpecRsassaPkcs1V15Sha512 159 | case kmstypes.SigningAlgorithmSpecEcdsaSha256: 160 | ecdsa[crypto.SHA256] = kmstypes.SigningAlgorithmSpecEcdsaSha256 161 | case kmstypes.SigningAlgorithmSpecEcdsaSha384: 162 | ecdsa[crypto.SHA384] = kmstypes.SigningAlgorithmSpecEcdsaSha384 163 | case kmstypes.SigningAlgorithmSpecEcdsaSha512: 164 | ecdsa[crypto.SHA512] = kmstypes.SigningAlgorithmSpecEcdsaSha512 165 | } 166 | } 167 | 168 | // always set the pss hashes, to handle the user explicitly opting in 169 | s.psshashm = pss 170 | 171 | // set up the defaults 172 | if len(ecdsa) > 0 { 173 | s.hashm = ecdsa 174 | return nil 175 | } 176 | if len(pkcs15) > 0 { 177 | s.hashm = pkcs15 178 | return nil 179 | } 180 | if len(pss) > 0 { 181 | s.hashm = pss 182 | return nil 183 | } 184 | 185 | return fmt.Errorf("no valid signing hashes found for key %s", s.key.ARN) 186 | } 187 | --------------------------------------------------------------------------------