Skip to content

Commit

Permalink
fix: v6, wrong public key encoding assumed
Browse files Browse the repository at this point in the history
The KMS GetPublicKey endpoint returns DER encoded key.
The library previously assumed PEM encoding.
  • Loading branch information
frncmx authored Jan 22, 2025
2 parents c4f25b8 + 6951014 commit 9cd0b27
Show file tree
Hide file tree
Showing 9 changed files with 230 additions and 50 deletions.
52 changes: 52 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: CI

on: [push]

env:
GO_VERSION: '1.23'


jobs:
lint:
name: Lint
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v5
with:
go-version: "${{ env.GO_VERSION }}"

- name: Mark source directory as safe
run: git config --global --add safe.directory $GITHUB_WORKSPACE

- name: go.mod check
run: |
go mod tidy
git diff --no-patch --exit-code go.mod go.sum
if [ $? -ne 0 ]; then
echo "Please run go mod tidy and commit the changes."
exit 1
fi
- uses: golangci/golangci-lint-action@v6
with:
version: v1.63

test:
name: Test
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "${{ env.GO_VERSION }}"
- name: Start LocalStack
uses: LocalStack/[email protected]
with:
image-tag: '4.0.3'
env:
LOCALSTACK_CI_PROJECT: "${{ env.CI_PROJECT }}"
- run: go test -race ./...
28 changes: 0 additions & 28 deletions .github/workflows/lint.yaml

This file was deleted.

14 changes: 0 additions & 14 deletions .github/workflows/test.yaml

This file was deleted.

4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

.PHONY: localstack
localstack:
docker run --rm -it -e SERVICES="kms" -p 4566:4566 localstack/localstack:4.0.3
19 changes: 19 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,17 @@ module github.com/spacelift-io/kmsjwt/v6

go 1.23

// All these versions asssumed the wrong key format returned by kms.GetPublicKey
retract (
v6.2.0
v6.1.0
v6.0.0
)

require (
github.com/aws/aws-sdk-go-v2 v1.32.7
github.com/aws/aws-sdk-go-v2/config v1.28.7
github.com/aws/aws-sdk-go-v2/credentials v1.17.48
github.com/aws/aws-sdk-go-v2/service/kms v1.37.8
github.com/franela/goblin v0.0.0-20211003143422-0a4f594942bf
github.com/golang-jwt/jwt/v4 v4.5.1
Expand All @@ -13,14 +22,24 @@ require (
)

require (
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.24.8 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.3 // indirect
github.com/aws/smithy-go v1.22.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/kr/pretty v0.3.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.8.1 // indirect
github.com/stretchr/objx v0.5.2 // indirect
golang.org/x/net v0.33.0 // indirect
golang.org/x/text v0.21.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
36 changes: 35 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
@@ -1,13 +1,32 @@
github.com/aws/aws-sdk-go-v2 v1.32.7 h1:ky5o35oENWi0JYWUZkB7WYvVPP+bcRF5/Iq7JWSb5Rw=
github.com/aws/aws-sdk-go-v2 v1.32.7/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U=
github.com/aws/aws-sdk-go-v2/config v1.28.7 h1:GduUnoTXlhkgnxTD93g1nv4tVPILbdNQOzav+Wpg7AE=
github.com/aws/aws-sdk-go-v2/config v1.28.7/go.mod h1:vZGX6GVkIE8uECSUHB6MWAUsd4ZcG2Yq/dMa4refR3M=
github.com/aws/aws-sdk-go-v2/credentials v1.17.48 h1:IYdLD1qTJ0zanRavulofmqut4afs45mOWEI+MzZtTfQ=
github.com/aws/aws-sdk-go-v2/credentials v1.17.48/go.mod h1:tOscxHN3CGmuX9idQ3+qbkzrjVIx32lqDSU1/0d/qXs=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22 h1:kqOrpojG71DxJm/KDPO+Z/y1phm1JlC8/iT+5XRmAn8=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22/go.mod h1:NtSFajXVVL8TA2QNngagVZmUtXciyrHOt7xgz4faS/M=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26 h1:I/5wmGMffY4happ8NOCuIUEWGUvvFp5NSeQcXl9RHcI=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26/go.mod h1:FR8f4turZtNy6baO0KJ5FJUmXH/cSkI9fOngs0yl6mA=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26 h1:zXFLuEuMMUOvEARXFUVJdfqZ4bvvSgdGRq/ATcrQxzM=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26/go.mod h1:3o2Wpy0bogG1kyOPrgkXA8pgIfEEv0+m19O9D5+W8y8=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7 h1:8eUsivBQzZHqe/3FE+cqwfH+0p5Jo8PFM/QYQSmeZ+M=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7/go.mod h1:kLPQvGUmxn/fqiCrDeohwG33bq2pQpGeY62yRO6Nrh0=
github.com/aws/aws-sdk-go-v2/service/kms v1.37.8 h1:KbLZjYqhQ9hyB4HwXiheiflTlYQa0+Fz0Ms/rh5f3mk=
github.com/aws/aws-sdk-go-v2/service/kms v1.37.8/go.mod h1:ANs9kBhK4Ghj9z1W+bsr3WsNaPF71qkgd6eE6Ekol/Y=
github.com/aws/aws-sdk-go-v2/service/sso v1.24.8 h1:CvuUmnXI7ebaUAhbJcDy9YQx8wHR69eZ9I7q5hszt/g=
github.com/aws/aws-sdk-go-v2/service/sso v1.24.8/go.mod h1:XDeGv1opzwm8ubxddF0cgqkZWsyOtw4lr6dxwmb6YQg=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7 h1:F2rBfNAL5UyswqoeWv9zs74N/NanhK16ydHW1pahX6E=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7/go.mod h1:JfyQ0g2JG8+Krq0EuZNnRwX0mU0HrwY/tG6JNfcqh4k=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.3 h1:Xgv/hyNgvLda/M9l9qxXc4UFSgppnRczLxlMs5Ae/QY=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.3/go.mod h1:5Gn+d+VaaRgsjewpMvGazt0WfcFO+Md4wLOuBfGR9Bc=
github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro=
github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/franela/goblin v0.0.0-20211003143422-0a4f594942bf h1:NrF81UtW8gG2LBGkXFQFqlfNnvMt9WdB46sfdJY4oqc=
Expand All @@ -22,14 +41,26 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg=
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/onsi/ginkgo/v2 v2.22.1 h1:QW7tbJAUDyVDVOM5dFa7qaybo+CRfR7bemlQUN6Z8aM=
github.com/onsi/ginkgo/v2 v2.22.1/go.mod h1:S6aTpoRsSq2cZOd+pssHAlKW/Q/jZt6cPrPlnj4a1xM=
github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
Expand All @@ -42,7 +73,10 @@ golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
13 changes: 9 additions & 4 deletions kmsjwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package kmsjwt
import (
"context"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"sync"

Expand Down Expand Up @@ -88,17 +89,21 @@ func (k *KMSJWT) getPublicKey(ctx context.Context) (*rsa.PublicKey, error) {
return k.publicKey, nil
}

out, err := k.api.GetPublicKey(ctx, &kms.GetPublicKeyInput{KeyId: aws.String(k.keyID)})
response, err := k.api.GetPublicKey(ctx, &kms.GetPublicKeyInput{KeyId: aws.String(k.keyID)})
if err != nil {
return nil, errors.Wrap(err, "could not retrieve public key")
}

publicKey, err := jwt.ParseRSAPublicKeyFromPEM(out.PublicKey)
publicKey, err := x509.ParsePKIXPublicKey(response.PublicKey)
if err != nil {
return nil, errors.Wrap(err, "could not parse public key")
}

k.publicKey = publicKey
var ok bool
k.publicKey, ok = publicKey.(*rsa.PublicKey)
if !ok {
return nil, errors.Errorf("public key type assertion: cannot assert %T as %T", publicKey, k.publicKey)
}

return publicKey, nil
return k.publicKey, nil
}
13 changes: 10 additions & 3 deletions kmsjwt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"crypto/rsa"
"encoding/base64"
"encoding/hex"
"encoding/pem"
"errors"
"os"
"testing"
Expand Down Expand Up @@ -124,10 +125,16 @@ func TestKMSJWT(t *testing.T) {
var publicKey *rsa.PublicKey

g.BeforeEach(func() {
publicKeyBytes, err = os.ReadFile("testdata/rsa.public")
var pemEncodedBytes []byte
pemEncodedBytes, err = os.ReadFile("testdata/rsa.public")
require.NoError(t, err)

publicKey, err = jwt.ParseRSAPublicKeyFromPEM(publicKeyBytes)
// KMS API returns DER encoded bytes.
var decoded *pem.Block
decoded, _ = pem.Decode(pemEncodedBytes)
publicKeyBytes = decoded.Bytes

publicKey, err = jwt.ParseRSAPublicKeyFromPEM(pemEncodedBytes)
require.NoError(t, err)

signature = expectedSignature
Expand Down Expand Up @@ -158,7 +165,7 @@ func TestKMSJWT(t *testing.T) {
})

g.It("returns an error", func() {
Expect(err).To(MatchError("could not parse public key: invalid key: Key must be a PEM encoded PKCS1 or PKCS8 key"))
Expect(err).To(MatchError(ContainSubstring("could not parse public key")))
})
})

Expand Down
101 changes: 101 additions & 0 deletions ksmjwt_localstack_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package kmsjwt_test

import (
"context"
"crypto/rsa"
"crypto/x509"
"testing"

"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/kms"
"github.com/aws/aws-sdk-go-v2/service/kms/types"
"github.com/golang-jwt/jwt/v4"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/spacelift-io/kmsjwt/v6"
)

func TestWithLocalStack(t *testing.T) {
const in = "sign me, please"
ctx := context.Background()
client := newClient(t, ctx)
keyID := client.CreateKey(t, ctx)
publicKey := client.GetPublicKey(t, ctx, keyID)

t.Run("new", func(t *testing.T) {
signer := kmsjwt.New(client.KMS, keyID)

signature, err := signer.Sign(in, ctx)
require.NoError(t, err, "sign")

err = signer.Verify(in, signature, ctx)
assert.NoError(t, err, "verify")
})

t.Run("new with public key", func(t *testing.T) {
signer := kmsjwt.NewWithPublicKey(client.KMS, keyID, publicKey)

signature, err := signer.Sign(in, ctx)
require.NoError(t, err, "sign")

err = signer.Verify(in, signature, ctx)
assert.NoError(t, err, "verify")
})

t.Run("RFC compliance", func(t *testing.T) {
signer := kmsjwt.New(client.KMS, keyID)

signature, err := signer.Sign(in, ctx)
require.NoError(t, err, "sign")

builtinSigner := jwt.GetSigningMethod(signer.Alg())
require.NotNil(t, builtinSigner, "unknown algorithm")

err = builtinSigner.Verify(in, signature, publicKey)
assert.NoError(t, err, "verify")
})
}

func newClient(t *testing.T, ctx context.Context) Client {
t.Helper()

cfg, err := config.LoadDefaultConfig(ctx,
config.WithRegion("eu-west-1"),
config.WithBaseEndpoint("http://localhost:4566"),
config.WithCredentialsProvider(
credentials.NewStaticCredentialsProvider("dummy", "dummy", "dummy"),
),
)
require.NoError(t, err, "load AWS config")

return Client{KMS: kms.NewFromConfig(cfg)}
}

type Client struct {
KMS *kms.Client
}

func (c Client) CreateKey(t *testing.T, ctx context.Context) (id string) {
t.Helper()
result, err := c.KMS.CreateKey(ctx, &kms.CreateKeyInput{
KeySpec: types.KeySpecRsa4096,
KeyUsage: types.KeyUsageTypeSignVerify,
})
require.NoError(t, err, "creating KMS key")
return *result.KeyMetadata.KeyId
}

func (c Client) GetPublicKey(t *testing.T, ctx context.Context, id string) *rsa.PublicKey {
t.Helper()
response, err := c.KMS.GetPublicKey(ctx, &kms.GetPublicKeyInput{
KeyId: &id,
})
require.NoError(t, err, "get KMS public key")

key, err := x509.ParsePKIXPublicKey(response.PublicKey)
require.NoError(t, err, "parsing fetched pubic key")

return key.(*rsa.PublicKey)
}

0 comments on commit 9cd0b27

Please sign in to comment.