Last active
August 14, 2024 00:05
-
-
Save tkalus/d1a18b7c92da274db930df25a2a52f1c to your computer and use it in GitHub Desktop.
Golang crypto.Signer from Cert/Key pair stored in AWS Secrets Manager with tests
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
go mod init example.com/signer | |
go mod tidy | |
go test ./... |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package signer | |
import ( | |
"context" | |
"crypto" | |
"crypto/tls" | |
"crypto/x509" | |
"fmt" | |
"io" | |
"github.com/aws/aws-sdk-go-v2/aws" | |
"github.com/aws/aws-sdk-go-v2/service/secretsmanager" | |
) | |
type Signer interface { | |
crypto.Signer | |
Certificate() tls.Certificate | |
} | |
type SMSigner struct { | |
cert tls.Certificate | |
} | |
func getSecret(ctx context.Context, client secretsmanager.Client, key string) ([]byte, error) { | |
input := &secretsmanager.GetSecretValueInput{ | |
SecretId: aws.String(key), | |
} | |
result, err := client.GetSecretValue(ctx, input) | |
if err != nil { | |
return nil, err | |
} | |
return result.SecretBinary, nil | |
} | |
// NewSigner takes AWS Secret Manager key paths and returns a crypto.Signer compatible struct with the addition of a tls.Certificate. | |
func NewSigner(ctx context.Context, awsConfig aws.Config, privateKeyPath, publicKeyPath string) (Signer, error) { | |
client := secretsmanager.NewFromConfig(awsConfig) | |
privkey, err := getSecret(ctx, *client, privateKeyPath) | |
if err != nil { | |
return nil, err | |
} | |
pubkey, err := getSecret(ctx, *client, publicKeyPath) | |
if err != nil { | |
return nil, err | |
} | |
cert, err := tls.X509KeyPair(pubkey, privkey) | |
if err != nil { | |
return nil, fmt.Errorf("problem loading key pair from AWSSM at %s: %v", publicKeyPath, err) | |
} | |
cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0]) | |
if err != nil { | |
return nil, fmt.Errorf("problem parsing certificate from AWSSM at %s: %v", publicKeyPath, err) | |
} | |
return &SMSigner{cert: cert}, nil | |
} | |
func (s *SMSigner) Certificate() tls.Certificate { | |
return s.cert | |
} | |
func (s *SMSigner) Public() crypto.PublicKey { | |
return s.cert.Leaf.PublicKey | |
} | |
func (s *SMSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { | |
return s.cert.PrivateKey.(crypto.Signer).Sign(rand, digest, opts) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package signer_test | |
import ( | |
"context" | |
"crypto" | |
"crypto/rand" | |
"crypto/sha256" | |
"testing" | |
"github.com/aws/aws-sdk-go-v2/aws" | |
"github.com/aws/aws-sdk-go-v2/service/secretsmanager" | |
"github.com/awsdocs/aws-doc-sdk-examples/gov2/testtools" | |
"github.com/madflojo/testcerts" | |
"github.com/stretchr/testify/assert" | |
"example.com/signer" | |
) | |
func TestSigner(t *testing.T) { | |
ca := testcerts.NewCA() | |
stubber := testtools.NewStubber() | |
stubber.Add(testtools.Stub{ | |
OperationName: *aws.String("GetSecretValue"), | |
Input: &secretsmanager.GetSecretValueInput{ | |
SecretId: aws.String("/signer/privatekey"), | |
}, | |
Output: &secretsmanager.GetSecretValueOutput{ | |
SecretBinary: ca.PrivateKey(), | |
SecretString: aws.String(string(ca.PrivateKey())), | |
}, | |
}) | |
stubber.Add(testtools.Stub{ | |
OperationName: *aws.String("GetSecretValue"), | |
Input: &secretsmanager.GetSecretValueInput{ | |
SecretId: aws.String("/signer/publickey"), | |
}, | |
Output: &secretsmanager.GetSecretValueOutput{ | |
SecretBinary: ca.PublicKey(), | |
SecretString: aws.String(string(ca.PublicKey())), | |
}, | |
}) | |
s, err := signer.NewSigner( | |
context.TODO(), | |
*stubber.SdkConfig, | |
"/signer/privatekey", | |
"/signer/publickey", | |
) | |
assert.NoError(t, err) | |
t.Run("NoErrors", func(t *testing.T) { | |
h := sha256.New() | |
h.Write([]byte("hello world")) | |
sum := h.Sum(nil) | |
resp, err := s.Sign(rand.Reader, sum, crypto.SHA256) | |
assert.NoError(t, err) | |
}) | |
testtools.ExitTest(stubber, t) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment