Skip to content

Instantly share code, notes, and snippets.

@tkalus
Last active August 14, 2024 00:05
Show Gist options
  • Save tkalus/d1a18b7c92da274db930df25a2a52f1c to your computer and use it in GitHub Desktop.
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
#!/bin/sh
go mod init example.com/signer
go mod tidy
go test ./...
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)
}
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