diff --git a/go.mod b/go.mod index d7acb37f..b4048334 100644 --- a/go.mod +++ b/go.mod @@ -70,6 +70,7 @@ require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/go-jose/go-jose/v3 v3.0.0 // indirect github.com/go-logr/logr v1.2.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-redis/redis/extra/rediscmd v0.2.0 // indirect diff --git a/go.sum b/go.sum index 1fe808f3..46ed02af 100644 --- a/go.sum +++ b/go.sum @@ -77,6 +77,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fxtlabs/primes v0.0.0-20150821004651-dad82d10a449 h1:HOYnhuVrhAVGKdg3rZapII640so7QfXQmkLkefUN/uM= github.com/fxtlabs/primes v0.0.0-20150821004651-dad82d10a449/go.mod h1:i+vbdOOivRRh2j+WwBkjZXloGN/+KAqfKDwNfUJeugc= +github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo= +github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -237,6 +239,7 @@ go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= diff --git a/go_dependencies.bzl b/go_dependencies.bzl index 770456d7..4b66537c 100644 --- a/go_dependencies.bzl +++ b/go_dependencies.bzl @@ -249,6 +249,12 @@ def go_dependencies(): sum = "h1:HOYnhuVrhAVGKdg3rZapII640so7QfXQmkLkefUN/uM=", version = "v0.0.0-20150821004651-dad82d10a449", ) + go_repository( + name = "com_github_go_jose_go_jose_v3", + importpath = "github.com/go-jose/go-jose/v3", + sum = "h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=", + version = "v3.0.0", + ) go_repository( name = "com_github_go_kit_log", importpath = "github.com/go-kit/log", diff --git a/pkg/jwt/BUILD.bazel b/pkg/jwt/BUILD.bazel index 05f22ba9..d70499ec 100644 --- a/pkg/jwt/BUILD.bazel +++ b/pkg/jwt/BUILD.bazel @@ -10,6 +10,7 @@ go_library( "ed25519_signature_validator.go", "generate_authorization_header.go", "hmac_sha_signature_validator.go", + "jwks_signature_validator.go", "rsa_sha_signature_validator.go", "signature_generator.go", "signature_validator.go", @@ -23,9 +24,11 @@ go_library( "//pkg/proto/configuration/jwt", "//pkg/random", "//pkg/util", + "@com_github_go_jose_go_jose_v3//:go-jose", "@com_github_jmespath_go_jmespath//:go-jmespath", "@org_golang_google_grpc//codes", "@org_golang_google_grpc//status", + "@org_golang_google_protobuf//encoding/protojson", ], ) @@ -38,6 +41,7 @@ go_test( "ed25519_signature_validator_test.go", "generate_authorization_header_test.go", "hmac_sha_signature_validator_test.go", + "jwks_signature_validator_test.go", "rsa_sha_signature_validator_test.go", ], deps = [ diff --git a/pkg/jwt/authorization_header_parser.go b/pkg/jwt/authorization_header_parser.go index 2f961428..fba4f797 100644 --- a/pkg/jwt/authorization_header_parser.go +++ b/pkg/jwt/authorization_header_parser.go @@ -102,11 +102,12 @@ func (a *AuthorizationHeaderParser) parseSingleAuthorizationHeader(header string // Perform signature validation. headerMessage := struct { Alg string `json:"alg"` + Kid string `json:"kid"` }{} if json.Unmarshal(decodedFields[0], &headerMessage) != nil { return unauthenticated } - if !a.signatureValidator.ValidateSignature(headerMessage.Alg, match[1], decodedFields[2]) { + if !a.signatureValidator.ValidateSignature(headerMessage.Alg, headerMessage.Kid, match[1], decodedFields[2]) { return unauthenticated } diff --git a/pkg/jwt/authorization_header_parser_test.go b/pkg/jwt/authorization_header_parser_test.go index 44367c97..17e32076 100644 --- a/pkg/jwt/authorization_header_parser_test.go +++ b/pkg/jwt/authorization_header_parser_test.go @@ -36,6 +36,7 @@ func TestAuthorizationHeaderParser(t *testing.T) { clock.EXPECT().Now().Return(time.Unix(1635747849, 0)) signatureValidator.EXPECT().ValidateSignature( "HS256", + "", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ", []byte{ 0x49, 0xf9, 0x4a, 0xc7, 0x04, 0x49, 0x48, 0xc7, @@ -59,6 +60,7 @@ func TestAuthorizationHeaderParser(t *testing.T) { clock.EXPECT().Now().Return(time.Unix(1635781700, 0)) signatureValidator.EXPECT().ValidateSignature( "HS256", + "", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ", []byte{ 0x69, 0xf2, 0xcf, 0x62, 0xca, 0x9a, 0xa4, 0x3c, @@ -102,6 +104,7 @@ func TestAuthorizationHeaderParser(t *testing.T) { clock.EXPECT().Now().Return(time.Unix(1635781778, 0)) signatureValidator.EXPECT().ValidateSignature( "HS256", + "", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwibmJmIjoxNjM1NzgxNzgwLCJleHAiOjE2MzU3ODE3OTJ9", []byte{ 0x9a, 0xf0, 0xa6, 0x11, 0xb2, 0x62, 0xcb, 0xec, @@ -199,6 +202,7 @@ func TestAuthorizationHeaderParser(t *testing.T) { clock.EXPECT().Now().Return(time.Unix(1636144433, 0)) signatureValidator.EXPECT().ValidateSignature( "HS256", + "", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb3JiaWRkZW5GaWVsZCI6Im9vcHMifQ", []byte{ 0xf1, 0x5c, 0xbc, 0x0c, 0x47, 0x71, 0x2d, 0x88, diff --git a/pkg/jwt/configuration.go b/pkg/jwt/configuration.go index 0a896cda..ffb6bde1 100644 --- a/pkg/jwt/configuration.go +++ b/pkg/jwt/configuration.go @@ -1,57 +1,30 @@ package jwt import ( - "crypto/ecdsa" - "crypto/ed25519" - "crypto/rsa" - "crypto/x509" - "encoding/pem" - "reflect" - "github.com/buildbarn/bb-storage/pkg/clock" "github.com/buildbarn/bb-storage/pkg/eviction" configuration "github.com/buildbarn/bb-storage/pkg/proto/configuration/jwt" "github.com/buildbarn/bb-storage/pkg/util" "github.com/jmespath/go-jmespath" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" + "google.golang.org/protobuf/encoding/protojson" ) // NewAuthorizationHeaderParserFromConfiguration creates a new HTTP // "Authorization" header parser based on options stored in a // configuration file. func NewAuthorizationHeaderParserFromConfiguration(config *configuration.AuthorizationHeaderParserConfiguration) (*AuthorizationHeaderParser, error) { - var signatureValidator SignatureValidator - switch key := config.Key.(type) { - case *configuration.AuthorizationHeaderParserConfiguration_HmacKey: - signatureValidator = NewHMACSHASignatureValidator(key.HmacKey) - case *configuration.AuthorizationHeaderParserConfiguration_PublicKey: - block, _ := pem.Decode([]byte(key.PublicKey)) - if block == nil { - return nil, status.Error(codes.InvalidArgument, "Public key does not use the PEM format") - } - parsedKey, err := x509.ParsePKIXPublicKey(block.Bytes) - if err != nil { - return nil, util.StatusWrapWithCode(err, codes.InvalidArgument, "Failed to parse public key") - } - switch convertedKey := parsedKey.(type) { - case *ecdsa.PublicKey: - var err error - signatureValidator, err = NewECDSASHASignatureValidator(convertedKey) - if err != nil { - return nil, err - } - case ed25519.PublicKey: - signatureValidator = NewEd25519SignatureValidator(convertedKey) - case *rsa.PublicKey: - signatureValidator = NewRSASHASignatureValidator(convertedKey) - default: - keyType := reflect.TypeOf(parsedKey) - return nil, status.Errorf(codes.InvalidArgument, "Unsupported public key type: %s/%s", keyType.PkgPath(), keyType.Name()) - } - default: - return nil, status.Error(codes.InvalidArgument, "No key type provided") + var err error + var jwksJson []byte + + jwksJson, err = protojson.Marshal(config.JwksInline) + if err != nil { + return nil, util.StatusWrap(err, "Failed to parse inline JWKS") + } + + signatureValidator, err := NewJWKSSignatureValidator(jwksJson) + if err != nil { + return nil, util.StatusWrap(err, "Failed to create signature validator") } evictionSet, err := eviction.NewSetFromConfiguration[string](config.CacheReplacementPolicy) diff --git a/pkg/jwt/ecdsa_sha_signature_generator_test.go b/pkg/jwt/ecdsa_sha_signature_generator_test.go index a24c8d87..17cdf7c9 100644 --- a/pkg/jwt/ecdsa_sha_signature_generator_test.go +++ b/pkg/jwt/ecdsa_sha_signature_generator_test.go @@ -33,6 +33,6 @@ f2EJfEoVNO/YidkVY+J35v8vQoAMS4rRGA== // Ensure that the generated signature is valid. signatureValidator, err := jwt.NewECDSASHASignatureValidator(&key.PublicKey) require.NoError(t, err) - require.True(t, signatureValidator.ValidateSignature("ES256", headerAndPayload, signature)) + require.True(t, signatureValidator.ValidateSignature("ES256", "", headerAndPayload, signature)) }) } diff --git a/pkg/jwt/ecdsa_sha_signature_validator.go b/pkg/jwt/ecdsa_sha_signature_validator.go index 8213e5c8..dfb8a07a 100644 --- a/pkg/jwt/ecdsa_sha_signature_validator.go +++ b/pkg/jwt/ecdsa_sha_signature_validator.go @@ -60,7 +60,7 @@ func NewECDSASHASignatureValidator(publicKey *ecdsa.PublicKey) (SignatureValidat }, nil } -func (sv *ecdsaSHASignatureValidator) ValidateSignature(algorithm, headerAndPayload string, signature []byte) bool { +func (sv *ecdsaSHASignatureValidator) ValidateSignature(algorithm, keyId, headerAndPayload string, signature []byte) bool { p := sv.parameters if algorithm != p.algorithm || len(signature) != 2*p.keySizeBytes { return false diff --git a/pkg/jwt/ecdsa_sha_signature_validator_test.go b/pkg/jwt/ecdsa_sha_signature_validator_test.go index be3e2035..443a5c0d 100644 --- a/pkg/jwt/ecdsa_sha_signature_validator_test.go +++ b/pkg/jwt/ecdsa_sha_signature_validator_test.go @@ -25,6 +25,7 @@ q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg== // Algorithm "HS256" uses HMAC; not ECDSA. Validation should fail. require.False(t, signatureValidator.ValidateSignature( "HS256", + "", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ", []byte{ 0xb3, 0x57, 0x72, 0xdf, 0xc5, 0xc6, 0x74, 0xba, @@ -36,6 +37,7 @@ q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg== // ECDSA with SHA-256, both with a valid and invalid signature. require.True(t, signatureValidator.ValidateSignature( "ES256", + "", "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0", []byte{ // R. @@ -51,6 +53,7 @@ q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg== })) require.False(t, signatureValidator.ValidateSignature( "ES256", + "", "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0", []byte{ // R. @@ -82,6 +85,7 @@ Pk9Yf9rIf374m5XP1U8q79dBhLSIuaojsvOT39UUcPJROSD1FqYLued0rXiooIii // 256-bit signatures. require.False(t, signatureValidator.ValidateSignature( "ES256", + "", "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0", []byte{ // R. @@ -99,6 +103,7 @@ Pk9Yf9rIf374m5XP1U8q79dBhLSIuaojsvOT39UUcPJROSD1FqYLued0rXiooIii // ECDSA with SHA-384, both with a valid and invalid signature. require.True(t, signatureValidator.ValidateSignature( "ES384", + "", "eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0", []byte{ // R. @@ -118,6 +123,7 @@ Pk9Yf9rIf374m5XP1U8q79dBhLSIuaojsvOT39UUcPJROSD1FqYLued0rXiooIii })) require.False(t, signatureValidator.ValidateSignature( "ES384", + "", "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0", []byte{ // R. @@ -153,6 +159,7 @@ ihmzIyMgyPuqu8IuyzMNx4G2jpoCKhRu9qPCQUMGDeCG1x3/n/OgkWNQANsB82x7 // ECDSA with SHA-512, both with a valid and invalid signature. require.True(t, signatureValidator.ValidateSignature( "ES512", + "", "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJmb28iOiJiYXIifQ", []byte{ // R. @@ -178,6 +185,7 @@ ihmzIyMgyPuqu8IuyzMNx4G2jpoCKhRu9qPCQUMGDeCG1x3/n/OgkWNQANsB82x7 })) require.False(t, signatureValidator.ValidateSignature( "ES512", + "", "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJmb28iOiJiYXIifQ", []byte{ // R. diff --git a/pkg/jwt/ed25519_signature_validator.go b/pkg/jwt/ed25519_signature_validator.go index 662a4f37..776ce3e9 100644 --- a/pkg/jwt/ed25519_signature_validator.go +++ b/pkg/jwt/ed25519_signature_validator.go @@ -22,7 +22,7 @@ func NewEd25519SignatureValidator(publicKey ed25519.PublicKey) SignatureValidato } } -func (sv *ed25519SignatureValidator) ValidateSignature(algorithm, headerAndPayload string, signature []byte) bool { +func (sv *ed25519SignatureValidator) ValidateSignature(algorithm, keyId, headerAndPayload string, signature []byte) bool { if algorithm != "EdDSA" { return false } diff --git a/pkg/jwt/ed25519_signature_validator_test.go b/pkg/jwt/ed25519_signature_validator_test.go index ca269b05..f8f179a3 100644 --- a/pkg/jwt/ed25519_signature_validator_test.go +++ b/pkg/jwt/ed25519_signature_validator_test.go @@ -22,6 +22,7 @@ MCowBQYDK2VwAyEA7fySb/9h7hVH8j1paD5IoLfXj4prjfNLwOPUYKvsTOc= // Algorithm "HS256" uses HMAC; not Ed25519. Validation should fail. require.False(t, signatureValidator.ValidateSignature( "HS256", + "", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ", []byte{ 0xb3, 0x57, 0x72, 0xdf, 0xc5, 0xc6, 0x74, 0xba, @@ -33,6 +34,7 @@ MCowBQYDK2VwAyEA7fySb/9h7hVH8j1paD5IoLfXj4prjfNLwOPUYKvsTOc= // Ed25519, both with a valid and invalid signature. require.True(t, signatureValidator.ValidateSignature( "EdDSA", + "", "eyJhbGciOiJFZERTQSJ9.eyJpZCI6MX0", []byte{ 0x44, 0x0c, 0x41, 0x01, 0x03, 0xc5, 0x3b, 0x1a, @@ -46,6 +48,7 @@ MCowBQYDK2VwAyEA7fySb/9h7hVH8j1paD5IoLfXj4prjfNLwOPUYKvsTOc= })) require.False(t, signatureValidator.ValidateSignature( "EdDSA", + "", "eyJhbGciOiJFZERTQSJ9.eyJpZCI6MX0", []byte{ 0x04, 0x16, 0xeb, 0x4f, 0xfc, 0x5d, 0x6f, 0x39, diff --git a/pkg/jwt/hmac_sha_signature_validator.go b/pkg/jwt/hmac_sha_signature_validator.go index 2d648da8..98b6c7bf 100644 --- a/pkg/jwt/hmac_sha_signature_validator.go +++ b/pkg/jwt/hmac_sha_signature_validator.go @@ -26,7 +26,7 @@ func NewHMACSHASignatureValidator(key []byte) SignatureValidator { } } -func (sv *hmacSHASignatureValidator) ValidateSignature(algorithm, headerAndPayload string, signature []byte) bool { +func (sv *hmacSHASignatureValidator) ValidateSignature(algorithm, keyId, headerAndPayload string, signature []byte) bool { // Determine the hashing function that was used to create the // signature. var hashFunc func() hash.Hash diff --git a/pkg/jwt/hmac_sha_signature_validator_test.go b/pkg/jwt/hmac_sha_signature_validator_test.go index 30a2efa7..5d2b21eb 100644 --- a/pkg/jwt/hmac_sha_signature_validator_test.go +++ b/pkg/jwt/hmac_sha_signature_validator_test.go @@ -13,6 +13,7 @@ func TestHMACSHASignatureValidator(t *testing.T) { // Algorithm "RS256" uses RSA; not HMAC. Validation should fail. require.False(t, signatureValidator.ValidateSignature( "RS256", + "", "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0", []byte{ 0x34, 0x75, 0x5a, 0x61, 0xed, 0xba, 0x31, 0xbb, 0x4e, @@ -49,6 +50,7 @@ func TestHMACSHASignatureValidator(t *testing.T) { // HMAC with SHA-256, both with a valid and invalid signature. require.True(t, signatureValidator.ValidateSignature( "HS256", + "", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ", []byte{ 0xb3, 0x57, 0x72, 0xdf, 0xc5, 0xc6, 0x74, 0xba, @@ -58,6 +60,7 @@ func TestHMACSHASignatureValidator(t *testing.T) { })) require.False(t, signatureValidator.ValidateSignature( "HS256", + "", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ", []byte{ 0x6d, 0x32, 0xc8, 0x2c, 0x25, 0xce, 0x4d, 0x54, @@ -69,6 +72,7 @@ func TestHMACSHASignatureValidator(t *testing.T) { // HMAC with SHA-384, both with a valid and invalid signature. require.True(t, signatureValidator.ValidateSignature( "HS384", + "", "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0", []byte{ 0x17, 0xf9, 0x9c, 0xc4, 0x9c, 0x91, 0xdf, 0x4e, @@ -80,6 +84,7 @@ func TestHMACSHASignatureValidator(t *testing.T) { })) require.False(t, signatureValidator.ValidateSignature( "HS384", + "", "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0", []byte{ 0xd9, 0xa6, 0x0a, 0x8f, 0x74, 0xc6, 0xe9, 0x94, @@ -93,6 +98,7 @@ func TestHMACSHASignatureValidator(t *testing.T) { // HMAC with SHA-512, both with a valid and invalid signature. require.True(t, signatureValidator.ValidateSignature( "HS512", + "", "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0", []byte{ 0xa7, 0xaa, 0x8f, 0x98, 0x7a, 0xed, 0xfa, 0x02, @@ -106,6 +112,7 @@ func TestHMACSHASignatureValidator(t *testing.T) { })) require.False(t, signatureValidator.ValidateSignature( "HS512", + "", "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0", []byte{ 0x9b, 0x19, 0x35, 0xa6, 0xb3, 0xe0, 0x9c, 0x3a, diff --git a/pkg/jwt/jwks_signature_validator.go b/pkg/jwt/jwks_signature_validator.go new file mode 100644 index 00000000..b3b3e2e2 --- /dev/null +++ b/pkg/jwt/jwks_signature_validator.go @@ -0,0 +1,68 @@ +package jwt + +import ( + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rsa" + "encoding/json" + "reflect" + + jose "github.com/go-jose/go-jose/v3" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +type jwksSignatureValidator struct { + validators map[string]SignatureValidator +} + +// FIXME +// NewJWKSignatureValidator creates a SignatureValidator... +func NewJWKSSignatureValidator(jwks []byte) (SignatureValidator, error) { + validators := make(map[string]SignatureValidator) + + var keySet jose.JSONWebKeySet + err := json.Unmarshal(jwks, &keySet) + if err != nil { + return nil, err + } + + for _, k := range keySet.Keys { + if !k.Valid() { + // Should this be fatal? + continue + } + + switch key := k.Key.(type) { + case *ecdsa.PublicKey: + val, err := NewECDSASHASignatureValidator(key) + if err != nil { + return nil, err + } + validators[k.KeyID] = val + case ed25519.PublicKey: + validators[k.KeyID] = NewEd25519SignatureValidator(key) + case *rsa.PublicKey: + validators[k.KeyID] = NewRSASHASignatureValidator(key) + case []byte: + validators[k.KeyID] = NewHMACSHASignatureValidator(key) + default: + keyType := reflect.TypeOf(k.Key) + return nil, status.Errorf(codes.InvalidArgument, "Unsupported public key type: %s/%s", keyType.PkgPath(), keyType.Name()) + } + } + + return &jwksSignatureValidator{ + validators: validators, + }, nil +} + +func (sv *jwksSignatureValidator) ValidateSignature(algorithm, keyId, headerAndPayload string, signature []byte) bool { + val, ok := sv.validators[keyId] + if !ok { + return false + } + + return val.ValidateSignature(algorithm, keyId, headerAndPayload, signature) +} diff --git a/pkg/jwt/jwks_signature_validator_test.go b/pkg/jwt/jwks_signature_validator_test.go new file mode 100644 index 00000000..0541d4df --- /dev/null +++ b/pkg/jwt/jwks_signature_validator_test.go @@ -0,0 +1,26 @@ +package jwt_test + +import ( + "testing" + + "github.com/buildbarn/bb-storage/pkg/jwt" + "github.com/stretchr/testify/require" +) + +func TestJWKSSignatureValidatorCreation(t *testing.T) { + key := []byte(`{ + "keys": [ + { + "kty": "RSA", + "n": "u1SU1LfVLPHCozMxH2Mo4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0_IzW7yWR7QkrmBL7jTKEn5u-qKhbwKfBstIs-bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyehkd3qqGElvW_VDL5AaWTg0nLVkjRo9z-40RQzuVaE8AkAFmxZzow3x-VJYKdjykkJ0iT9wCS0DRTXu269V264Vf_3jvredZiKRkgwlL9xNAwxXFg0x_XFw005UWVRIkdgcKWTjpBP2dPwVZ4WWC-9aGVd-Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbcmw", + "e": "AQAB", + "alg": "RS256", + "kid": "7c0b6913fe13820a333399ace426e70535a9a0bf", + "use": "sig" + } + ] + }`) + + _, err := jwt.NewJWKSSignatureValidator(key) + require.NoError(t, err) +} diff --git a/pkg/jwt/rsa_sha_signature_validator.go b/pkg/jwt/rsa_sha_signature_validator.go index 2575e13b..6f5748c5 100644 --- a/pkg/jwt/rsa_sha_signature_validator.go +++ b/pkg/jwt/rsa_sha_signature_validator.go @@ -27,7 +27,7 @@ func NewRSASHASignatureValidator(key *rsa.PublicKey) SignatureValidator { } } -func (sv *rsaSHASignatureValidator) ValidateSignature(algorithm, headerAndPayload string, signature []byte) bool { +func (sv *rsaSHASignatureValidator) ValidateSignature(algorithm, keyId, headerAndPayload string, signature []byte) bool { var hashType crypto.Hash var hasher hash.Hash switch algorithm { diff --git a/pkg/jwt/rsa_sha_signature_validator_test.go b/pkg/jwt/rsa_sha_signature_validator_test.go index 6f11e8c3..5d0f972b 100644 --- a/pkg/jwt/rsa_sha_signature_validator_test.go +++ b/pkg/jwt/rsa_sha_signature_validator_test.go @@ -28,6 +28,7 @@ mwIDAQAB // Algorithm "ES256" uses ECDSA; not RSA. Validation should fail. require.False(t, signatureValidator.ValidateSignature( "ES256", + "", "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0", []byte{ // R. @@ -45,6 +46,7 @@ mwIDAQAB // RSA with SHA-256, both with a valid and invalid signature. require.True(t, signatureValidator.ValidateSignature( "RS256", + "", "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0", []byte{ 0x34, 0x75, 0x5a, 0x61, 0xed, 0xba, 0x31, 0xbb, @@ -82,6 +84,7 @@ mwIDAQAB })) require.False(t, signatureValidator.ValidateSignature( "RS256", + "", "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0", []byte{ 0x25, 0x7e, 0x03, 0x4d, 0x2a, 0x4d, 0x94, 0xfc, @@ -121,6 +124,7 @@ mwIDAQAB // RSA with SHA-384, both with a valid and invalid signature. require.True(t, signatureValidator.ValidateSignature( "RS384", + "", "eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0", []byte{ 0xa3, 0x58, 0x42, 0xd7, 0x16, 0x1b, 0x26, 0x89, @@ -158,6 +162,7 @@ mwIDAQAB })) require.False(t, signatureValidator.ValidateSignature( "RS384", + "", "eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0", []byte{ 0x86, 0x0d, 0x70, 0xc5, 0x6d, 0x52, 0x1c, 0xa2, @@ -197,6 +202,7 @@ mwIDAQAB // RSA with SHA-512, both with a valid and invalid signature. require.True(t, signatureValidator.ValidateSignature( "RS512", + "", "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0", []byte{ 0x8d, 0x85, 0xb4, 0xe3, 0x32, 0xc3, 0x1d, 0xf4, @@ -234,6 +240,7 @@ mwIDAQAB })) require.False(t, signatureValidator.ValidateSignature( "RS512", + "", "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0", []byte{ 0x18, 0x6a, 0x31, 0xc7, 0xab, 0xea, 0x89, 0x80, diff --git a/pkg/jwt/signature_validator.go b/pkg/jwt/signature_validator.go index 5f1aab4c..df0e0917 100644 --- a/pkg/jwt/signature_validator.go +++ b/pkg/jwt/signature_validator.go @@ -4,5 +4,5 @@ package jwt // of a JWT. Implementations of this interface may use HMAC, ECDSA or // other algorithms. type SignatureValidator interface { - ValidateSignature(algorithm, headerAndPayload string, signature []byte) bool + ValidateSignature(algorithm, keyId, headerAndPayload string, signature []byte) bool } diff --git a/pkg/proto/configuration/jwt/BUILD.bazel b/pkg/proto/configuration/jwt/BUILD.bazel index 72329987..08c0a5a8 100644 --- a/pkg/proto/configuration/jwt/BUILD.bazel +++ b/pkg/proto/configuration/jwt/BUILD.bazel @@ -6,7 +6,10 @@ proto_library( name = "jwt_proto", srcs = ["jwt.proto"], visibility = ["//visibility:public"], - deps = ["//pkg/proto/configuration/eviction:eviction_proto"], + deps = [ + "//pkg/proto/configuration/eviction:eviction_proto", + "@com_google_protobuf//:struct_proto", + ], ) go_proto_library( diff --git a/pkg/proto/configuration/jwt/jwt.pb.go b/pkg/proto/configuration/jwt/jwt.pb.go index f671c239..b530d92f 100644 --- a/pkg/proto/configuration/jwt/jwt.pb.go +++ b/pkg/proto/configuration/jwt/jwt.pb.go @@ -10,6 +10,7 @@ import ( eviction "github.com/buildbarn/bb-storage/pkg/proto/configuration/eviction" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + structpb "google.golang.org/protobuf/types/known/structpb" reflect "reflect" sync "sync" ) @@ -26,15 +27,11 @@ type AuthorizationHeaderParserConfiguration struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // Types that are assignable to Key: - // - // *AuthorizationHeaderParserConfiguration_HmacKey - // *AuthorizationHeaderParserConfiguration_PublicKey - Key isAuthorizationHeaderParserConfiguration_Key `protobuf_oneof:"key"` - MaximumCacheSize int32 `protobuf:"varint,3,opt,name=maximum_cache_size,json=maximumCacheSize,proto3" json:"maximum_cache_size,omitempty"` - CacheReplacementPolicy eviction.CacheReplacementPolicy `protobuf:"varint,4,opt,name=cache_replacement_policy,json=cacheReplacementPolicy,proto3,enum=buildbarn.configuration.eviction.CacheReplacementPolicy" json:"cache_replacement_policy,omitempty"` - ClaimsValidationJmespathExpression string `protobuf:"bytes,5,opt,name=claims_validation_jmespath_expression,json=claimsValidationJmespathExpression,proto3" json:"claims_validation_jmespath_expression,omitempty"` - MetadataExtractionJmespathExpression string `protobuf:"bytes,6,opt,name=metadata_extraction_jmespath_expression,json=metadataExtractionJmespathExpression,proto3" json:"metadata_extraction_jmespath_expression,omitempty"` + MaximumCacheSize int32 `protobuf:"varint,3,opt,name=maximum_cache_size,json=maximumCacheSize,proto3" json:"maximum_cache_size,omitempty"` + CacheReplacementPolicy eviction.CacheReplacementPolicy `protobuf:"varint,4,opt,name=cache_replacement_policy,json=cacheReplacementPolicy,proto3,enum=buildbarn.configuration.eviction.CacheReplacementPolicy" json:"cache_replacement_policy,omitempty"` + ClaimsValidationJmespathExpression string `protobuf:"bytes,5,opt,name=claims_validation_jmespath_expression,json=claimsValidationJmespathExpression,proto3" json:"claims_validation_jmespath_expression,omitempty"` + MetadataExtractionJmespathExpression string `protobuf:"bytes,6,opt,name=metadata_extraction_jmespath_expression,json=metadataExtractionJmespathExpression,proto3" json:"metadata_extraction_jmespath_expression,omitempty"` + JwksInline *structpb.Struct `protobuf:"bytes,7,opt,name=jwks_inline,json=jwksInline,proto3" json:"jwks_inline,omitempty"` } func (x *AuthorizationHeaderParserConfiguration) Reset() { @@ -69,27 +66,6 @@ func (*AuthorizationHeaderParserConfiguration) Descriptor() ([]byte, []int) { return file_pkg_proto_configuration_jwt_jwt_proto_rawDescGZIP(), []int{0} } -func (m *AuthorizationHeaderParserConfiguration) GetKey() isAuthorizationHeaderParserConfiguration_Key { - if m != nil { - return m.Key - } - return nil -} - -func (x *AuthorizationHeaderParserConfiguration) GetHmacKey() []byte { - if x, ok := x.GetKey().(*AuthorizationHeaderParserConfiguration_HmacKey); ok { - return x.HmacKey - } - return nil -} - -func (x *AuthorizationHeaderParserConfiguration) GetPublicKey() string { - if x, ok := x.GetKey().(*AuthorizationHeaderParserConfiguration_PublicKey); ok { - return x.PublicKey - } - return "" -} - func (x *AuthorizationHeaderParserConfiguration) GetMaximumCacheSize() int32 { if x != nil { return x.MaximumCacheSize @@ -118,22 +94,11 @@ func (x *AuthorizationHeaderParserConfiguration) GetMetadataExtractionJmespathEx return "" } -type isAuthorizationHeaderParserConfiguration_Key interface { - isAuthorizationHeaderParserConfiguration_Key() -} - -type AuthorizationHeaderParserConfiguration_HmacKey struct { - HmacKey []byte `protobuf:"bytes,1,opt,name=hmac_key,json=hmacKey,proto3,oneof"` -} - -type AuthorizationHeaderParserConfiguration_PublicKey struct { - PublicKey string `protobuf:"bytes,2,opt,name=public_key,json=publicKey,proto3,oneof"` -} - -func (*AuthorizationHeaderParserConfiguration_HmacKey) isAuthorizationHeaderParserConfiguration_Key() { -} - -func (*AuthorizationHeaderParserConfiguration_PublicKey) isAuthorizationHeaderParserConfiguration_Key() { +func (x *AuthorizationHeaderParserConfiguration) GetJwksInline() *structpb.Struct { + if x != nil { + return x.JwksInline + } + return nil } var File_pkg_proto_configuration_jwt_jwt_proto protoreflect.FileDescriptor @@ -143,16 +108,14 @@ var file_pkg_proto_configuration_jwt_jwt_proto_rawDesc = []byte{ 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x77, 0x74, 0x2f, 0x6a, 0x77, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1b, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x62, 0x61, 0x72, 0x6e, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x2e, 0x6a, 0x77, 0x74, 0x1a, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x65, 0x76, - 0x69, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x65, 0x76, 0x69, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb9, 0x03, 0x0a, 0x26, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, - 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x50, 0x61, 0x72, - 0x73, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0x1b, 0x0a, 0x08, 0x68, 0x6d, 0x61, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0c, 0x48, 0x00, 0x52, 0x07, 0x68, 0x6d, 0x61, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x1f, 0x0a, - 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x48, 0x00, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x2c, + 0x2e, 0x6a, 0x77, 0x74, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x1a, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x65, 0x76, 0x69, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x65, 0x76, 0x69, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x22, 0xba, 0x03, 0x0a, 0x26, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x50, 0x61, 0x72, 0x73, 0x65, + 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2c, 0x0a, 0x12, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x63, 0x61, 0x63, 0x68, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x43, 0x61, 0x63, 0x68, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x72, 0x0a, 0x18, @@ -173,12 +136,16 @@ var file_pkg_proto_configuration_jwt_jwt_proto_rawDesc = []byte{ 0x61, 0x74, 0x68, 0x5f, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x24, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x78, 0x74, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x74, 0x68, - 0x45, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x05, 0x0a, 0x03, 0x6b, 0x65, - 0x79, 0x42, 0x3d, 0x5a, 0x3b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x62, 0x75, 0x69, 0x6c, 0x64, 0x62, 0x61, 0x72, 0x6e, 0x2f, 0x62, 0x62, 0x2d, 0x73, 0x74, 0x6f, - 0x72, 0x61, 0x67, 0x65, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x77, 0x74, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x45, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x38, 0x0a, 0x0b, 0x6a, 0x77, + 0x6b, 0x73, 0x5f, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x0a, 0x6a, 0x77, 0x6b, 0x73, 0x49, 0x6e, + 0x6c, 0x69, 0x6e, 0x65, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, + 0x42, 0x3d, 0x5a, 0x3b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, + 0x75, 0x69, 0x6c, 0x64, 0x62, 0x61, 0x72, 0x6e, 0x2f, 0x62, 0x62, 0x2d, 0x73, 0x74, 0x6f, 0x72, + 0x61, 0x67, 0x65, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x77, 0x74, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -197,14 +164,16 @@ var file_pkg_proto_configuration_jwt_jwt_proto_msgTypes = make([]protoimpl.Messa var file_pkg_proto_configuration_jwt_jwt_proto_goTypes = []interface{}{ (*AuthorizationHeaderParserConfiguration)(nil), // 0: buildbarn.configuration.jwt.AuthorizationHeaderParserConfiguration (eviction.CacheReplacementPolicy)(0), // 1: buildbarn.configuration.eviction.CacheReplacementPolicy + (*structpb.Struct)(nil), // 2: google.protobuf.Struct } var file_pkg_proto_configuration_jwt_jwt_proto_depIdxs = []int32{ 1, // 0: buildbarn.configuration.jwt.AuthorizationHeaderParserConfiguration.cache_replacement_policy:type_name -> buildbarn.configuration.eviction.CacheReplacementPolicy - 1, // [1:1] is the sub-list for method output_type - 1, // [1:1] is the sub-list for method input_type - 1, // [1:1] is the sub-list for extension type_name - 1, // [1:1] is the sub-list for extension extendee - 0, // [0:1] is the sub-list for field type_name + 2, // 1: buildbarn.configuration.jwt.AuthorizationHeaderParserConfiguration.jwks_inline:type_name -> google.protobuf.Struct + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name } func init() { file_pkg_proto_configuration_jwt_jwt_proto_init() } @@ -226,10 +195,6 @@ func file_pkg_proto_configuration_jwt_jwt_proto_init() { } } } - file_pkg_proto_configuration_jwt_jwt_proto_msgTypes[0].OneofWrappers = []interface{}{ - (*AuthorizationHeaderParserConfiguration_HmacKey)(nil), - (*AuthorizationHeaderParserConfiguration_PublicKey)(nil), - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/pkg/proto/configuration/jwt/jwt.proto b/pkg/proto/configuration/jwt/jwt.proto index 5a76ac9f..ec341d19 100644 --- a/pkg/proto/configuration/jwt/jwt.proto +++ b/pkg/proto/configuration/jwt/jwt.proto @@ -2,28 +2,19 @@ syntax = "proto3"; package buildbarn.configuration.jwt; +import "google/protobuf/struct.proto"; import "pkg/proto/configuration/eviction/eviction.proto"; option go_package = "github.com/buildbarn/bb-storage/pkg/proto/configuration/jwt"; -message AuthorizationHeaderParserConfiguration { - oneof key { - // Accept signatures using algorithms "HS256", "HS384" and "HS512". - // - // This field contains the shared key secret to validate the - // signature. - bytes hmac_key = 1; - // Accept signatures using algorithms "ES256", "ES384", "ES512", - // "EdDSA", "RS256", "RS384", or "RS512". - // - // This field contains the public key used to validate the - // signature. It should start with "-----BEGIN PUBLIC KEY-----". - // The public key type and curve size determine which exact - // algorithm is used. - string public_key = 2; - } +message AuthorizationHeaderParserConfiguration { + // Was `hmac_key`, instead use `jwks_inline`. + reserved 1; + // Was `public_key`, instead use `jwks_inline`. + reserved 2; + // Maximum number of validated tokens to cache in memory. This speeds // up successive requests made with the same token. int32 maximum_cache_size = 3; @@ -79,4 +70,8 @@ message AuthorizationHeaderParserConfiguration { // // `{}` string metadata_extraction_jmespath_expression = 6; + + // JSON Web Key Set (JWKS) that contains the public keys that can sign + // accepted JWTs. + google.protobuf.Struct jwks_inline = 7; }