| // Copyright 2022 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| package services_test |
| |
| import ( |
| "context" |
| "errors" |
| "fmt" |
| "testing" |
| |
| dpb "google.golang.org/protobuf/types/known/durationpb" |
| spb "google.golang.org/protobuf/types/known/structpb" |
| tpb "google.golang.org/protobuf/types/known/timestamppb" |
| wpb "google.golang.org/protobuf/types/known/wrapperspb" |
| "github.com/google/go-cmp/cmp" |
| "google.golang.org/protobuf/proto" |
| "google.golang.org/protobuf/testing/protocmp" |
| "github.com/google/tink/go/aead" |
| "github.com/google/tink/go/jwt" |
| "github.com/google/tink/go/signature" |
| "github.com/google/tink/testing/go/services" |
| pb "github.com/google/tink/testing/go/protos/testing_api_go_grpc" |
| ) |
| |
| func verifiedJWTFromResponse(response *pb.JwtVerifyResponse) (*pb.JwtToken, error) { |
| switch r := response.Result.(type) { |
| case *pb.JwtVerifyResponse_VerifiedJwt: |
| return r.VerifiedJwt, nil |
| case *pb.JwtVerifyResponse_Err: |
| return nil, errors.New(r.Err) |
| default: |
| return nil, fmt.Errorf("response.Result has unexpected type %T", r) |
| } |
| } |
| |
| func signedCompactJWTFromResponse(response *pb.JwtSignResponse) (string, error) { |
| switch r := response.Result.(type) { |
| case *pb.JwtSignResponse_SignedCompactJwt: |
| return r.SignedCompactJwt, nil |
| case *pb.JwtSignResponse_Err: |
| return "", errors.New(r.Err) |
| default: |
| return "", fmt.Errorf("response.Result has unexpected type %T", r) |
| } |
| } |
| |
| func jwkSetFromResponse(response *pb.JwtToJwkSetResponse) (string, error) { |
| switch r := response.Result.(type) { |
| case *pb.JwtToJwkSetResponse_JwkSet: |
| return r.JwkSet, nil |
| case *pb.JwtToJwkSetResponse_Err: |
| return "", errors.New(r.Err) |
| default: |
| return "", fmt.Errorf("response.Result has unexpected type %T", r) |
| } |
| } |
| |
| func keysetFromResponse(response *pb.JwtFromJwkSetResponse) ([]byte, error) { |
| switch r := response.Result.(type) { |
| case *pb.JwtFromJwkSetResponse_Keyset: |
| return r.Keyset, nil |
| case *pb.JwtFromJwkSetResponse_Err: |
| return nil, errors.New(r.Err) |
| default: |
| return nil, fmt.Errorf("response.Result has unexpected type %T", r) |
| } |
| } |
| |
| type jwtTestCase struct { |
| tag string |
| rawJWT *pb.JwtToken |
| validator *pb.JwtValidator |
| } |
| |
| func TestJWTComputeInvalidJWT(t *testing.T) { |
| for _, tc := range []jwtTestCase{ |
| { |
| tag: "nil rawJWT", |
| rawJWT: nil, |
| }, |
| { |
| tag: "invalid json array string", |
| rawJWT: &pb.JwtToken{ |
| CustomClaims: map[string]*pb.JwtClaimValue{ |
| "cc-array": &pb.JwtClaimValue{Kind: &pb.JwtClaimValue_JsonArrayValue{JsonArrayValue: "{35}"}}, |
| }, |
| }, |
| }, |
| { |
| tag: "invalid json object string", |
| rawJWT: &pb.JwtToken{ |
| CustomClaims: map[string]*pb.JwtClaimValue{ |
| "cc-object": &pb.JwtClaimValue{Kind: &pb.JwtClaimValue_JsonObjectValue{JsonObjectValue: `["o":"a"]`}}, |
| }, |
| }, |
| }, |
| } { |
| t.Run(tc.tag, func(t *testing.T) { |
| keysetService := &services.KeysetService{} |
| jwtService := &services.JWTService{} |
| ctx := context.Background() |
| template, err := proto.Marshal(jwt.HS256Template()) |
| if err != nil { |
| t.Fatalf("proto.Marshal(jwt.HS256Template()) failed: %v", err) |
| } |
| keyset, err := genKeyset(ctx, keysetService, template) |
| if err != nil { |
| t.Fatalf("genKeyset failed: %v", err) |
| } |
| signResponse, err := jwtService.ComputeMacAndEncode(ctx, &pb.JwtSignRequest{AnnotatedKeyset: &pb.AnnotatedKeyset{SerializedKeyset: keyset}, RawJwt: tc.rawJWT}) |
| if err != nil { |
| t.Fatalf("jwtService.ComputeMacAndEncode() err = %v, want nil", err) |
| } |
| if _, err := signedCompactJWTFromResponse(signResponse); err == nil { |
| t.Fatalf("JwtSignResponse: error = nil, want error") |
| } |
| }) |
| } |
| } |
| |
| func TestSuccessfulJwtMacCreation(t *testing.T) { |
| keysetService := &services.KeysetService{} |
| jwtService := &services.JWTService{} |
| ctx := context.Background() |
| |
| template, err := proto.Marshal(jwt.HS256Template()) |
| if err != nil { |
| t.Fatalf("proto.Marshal(jwt.HS256Template()) failed: %v, want nil", err) |
| } |
| |
| keyset, err := genKeyset(ctx, keysetService, template) |
| if err != nil { |
| t.Fatalf("genKeyset failed: %v", err) |
| } |
| |
| result, err := jwtService.CreateJwtMac(ctx, &pb.CreationRequest{AnnotatedKeyset: &pb.AnnotatedKeyset{SerializedKeyset: keyset}}) |
| if err != nil { |
| t.Fatalf("CreateJwtMac with good keyset failed with gRPC error: %v, want nil", err) |
| } |
| if result.GetErr() != "" { |
| t.Fatalf("CreateJwtMac with good keyset failed with result.GetErr() = %q, want empty string", result.GetErr()) |
| } |
| } |
| |
| func TestFailingJwtMacCreation(t *testing.T) { |
| keysetService := &services.KeysetService{} |
| jwtService := &services.JWTService{} |
| ctx := context.Background() |
| |
| // We use signature keys -- then we cannot create a JwtMac |
| template, err := proto.Marshal(aead.AES128GCMKeyTemplate()) |
| if err != nil { |
| t.Fatalf("proto.Marshal(signature.ECDSAP256KeyTemplate()) failed: %v", err) |
| } |
| |
| badKeyset, err := genKeyset(ctx, keysetService, template) |
| if err != nil { |
| t.Fatalf("genKeyset failed: %v", err) |
| } |
| |
| result, err := jwtService.CreateJwtMac(ctx, &pb.CreationRequest{AnnotatedKeyset: &pb.AnnotatedKeyset{SerializedKeyset: badKeyset}}) |
| if err != nil { |
| t.Fatalf("CreateJwtMac with bad keyset failed with gRPC error: %v", err) |
| } |
| if result.GetErr() == "" { |
| t.Fatalf("result.GetErr() of bad keyset after CreateJwtMac is empty, want not empty") |
| } |
| } |
| |
| func TestJWTComputeMACWithInvalidKeysetFails(t *testing.T) { |
| keysetService := &services.KeysetService{} |
| jwtService := &services.JWTService{} |
| ctx := context.Background() |
| template, err := proto.Marshal(aead.AES256GCMKeyTemplate()) |
| if err != nil { |
| t.Fatalf("proto.Marshal(jwt.AES256GCMKeyTemplate()) failed: %v", err) |
| } |
| keyset, err := genKeyset(ctx, keysetService, template) |
| if err != nil { |
| t.Fatalf("genKeyset failed: %v", err) |
| } |
| rawJWT := &pb.JwtToken{ |
| TypeHeader: &wpb.StringValue{Value: "JWT"}, |
| Issuer: &wpb.StringValue{Value: "issuer"}, |
| } |
| signResponse, err := jwtService.ComputeMacAndEncode(ctx, &pb.JwtSignRequest{AnnotatedKeyset: &pb.AnnotatedKeyset{SerializedKeyset: keyset}, RawJwt: rawJWT}) |
| if err != nil { |
| t.Fatalf("jwtService.ComputeMacAndEncode() err = %v, want nil", err) |
| } |
| if _, err := signedCompactJWTFromResponse(signResponse); err == nil { |
| t.Fatalf("JwtSignResponse: error = nil, want error") |
| } |
| } |
| |
| func TestJWTComputeAndVerifyMac(t *testing.T) { |
| for _, tc := range []jwtTestCase{ |
| { |
| tag: "all claims and custom claims", |
| rawJWT: &pb.JwtToken{ |
| TypeHeader: &wpb.StringValue{Value: "JWT"}, |
| Issuer: &wpb.StringValue{Value: "issuer"}, |
| Subject: &wpb.StringValue{Value: "subject"}, |
| JwtId: &wpb.StringValue{Value: "tink"}, |
| Audiences: []string{"audience"}, |
| Expiration: &tpb.Timestamp{Seconds: 123456}, |
| NotBefore: &tpb.Timestamp{Seconds: 12345}, |
| IssuedAt: &tpb.Timestamp{Seconds: 1234}, |
| CustomClaims: map[string]*pb.JwtClaimValue{ |
| "cc-null": &pb.JwtClaimValue{Kind: &pb.JwtClaimValue_NullValue{}}, |
| "cc-num": &pb.JwtClaimValue{Kind: &pb.JwtClaimValue_NumberValue{NumberValue: 5.67}}, |
| "cc-bool": &pb.JwtClaimValue{Kind: &pb.JwtClaimValue_BoolValue{BoolValue: true}}, |
| "cc-string": &pb.JwtClaimValue{Kind: &pb.JwtClaimValue_StringValue{StringValue: "foo bar"}}, |
| "cc-array": &pb.JwtClaimValue{Kind: &pb.JwtClaimValue_JsonArrayValue{JsonArrayValue: "[35]"}}, |
| "cc-object": &pb.JwtClaimValue{Kind: &pb.JwtClaimValue_JsonObjectValue{JsonObjectValue: `{"key":"val"}`}}, |
| }, |
| }, |
| validator: &pb.JwtValidator{ |
| ExpectedTypeHeader: &wpb.StringValue{Value: "JWT"}, |
| ExpectedIssuer: &wpb.StringValue{Value: "issuer"}, |
| ExpectedAudience: &wpb.StringValue{Value: "audience"}, |
| Now: &tpb.Timestamp{Seconds: 12345}, |
| ClockSkew: &dpb.Duration{Seconds: 0}, |
| }, |
| }, |
| { |
| tag: "without custom claims", |
| rawJWT: &pb.JwtToken{ |
| TypeHeader: &wpb.StringValue{Value: "JWT"}, |
| Issuer: &wpb.StringValue{Value: "issuer"}, |
| Subject: &wpb.StringValue{Value: "subject"}, |
| Audiences: []string{"audience"}, |
| }, |
| validator: &pb.JwtValidator{ |
| ExpectedTypeHeader: &wpb.StringValue{Value: "JWT"}, |
| ExpectedIssuer: &wpb.StringValue{Value: "issuer"}, |
| ExpectedAudience: &wpb.StringValue{Value: "audience"}, |
| AllowMissingExpiration: true, |
| }, |
| }, |
| { |
| tag: "without expiration", |
| rawJWT: &pb.JwtToken{ |
| Subject: &wpb.StringValue{Value: "subject"}, |
| }, |
| validator: &pb.JwtValidator{ |
| AllowMissingExpiration: true, |
| }, |
| }, |
| { |
| tag: "clock skew", |
| rawJWT: &pb.JwtToken{ |
| Expiration: &tpb.Timestamp{Seconds: 1234}, |
| }, |
| validator: &pb.JwtValidator{ |
| Now: &tpb.Timestamp{Seconds: 1235}, |
| ClockSkew: &dpb.Duration{Seconds: 2}, |
| }, |
| }, |
| } { |
| t.Run(tc.tag, func(t *testing.T) { |
| keysetService := &services.KeysetService{} |
| jwtService := &services.JWTService{} |
| ctx := context.Background() |
| template, err := proto.Marshal(jwt.HS256Template()) |
| if err != nil { |
| t.Fatalf("proto.Marshal(jwt.HS256Template()) failed: %v", err) |
| } |
| keyset, err := genKeyset(ctx, keysetService, template) |
| if err != nil { |
| t.Fatalf("genKeyset failed: %v", err) |
| } |
| |
| signResponse, err := jwtService.ComputeMacAndEncode(ctx, &pb.JwtSignRequest{AnnotatedKeyset: &pb.AnnotatedKeyset{SerializedKeyset: keyset}, RawJwt: tc.rawJWT}) |
| if err != nil { |
| t.Fatalf("jwtService.ComputeMacAndEncode() err = %v, want nil", err) |
| } |
| compact, err := signedCompactJWTFromResponse(signResponse) |
| if err != nil { |
| t.Fatalf("JwtSignResponse_Err: %v", err) |
| } |
| verifyResponse, err := jwtService.VerifyMacAndDecode(ctx, &pb.JwtVerifyRequest{AnnotatedKeyset: &pb.AnnotatedKeyset{SerializedKeyset: keyset}, SignedCompactJwt: compact, Validator: tc.validator}) |
| if err != nil { |
| t.Fatalf("jwtService.VerifyMacAndDecode() err = %v, want nil", err) |
| } |
| verifiedJWT, err := verifiedJWTFromResponse(verifyResponse) |
| if err != nil { |
| t.Fatalf("JwtVerifyResponse_Err: %v", err) |
| } |
| if !cmp.Equal(verifiedJWT, tc.rawJWT, protocmp.Transform()) { |
| t.Errorf("verifiedJWT doesn't match expected value: (+ got, - want) %v", cmp.Diff(verifiedJWT, tc.rawJWT, protocmp.Transform())) |
| } |
| }) |
| } |
| } |
| |
| func TestJWTVerifyMACFailures(t *testing.T) { |
| keysetService := &services.KeysetService{} |
| jwtService := &services.JWTService{} |
| ctx := context.Background() |
| template, err := proto.Marshal(jwt.HS256Template()) |
| if err != nil { |
| t.Fatalf("proto.Marshal(jwt.HS256Template()) failed: %v", err) |
| } |
| keyset, err := genKeyset(ctx, keysetService, template) |
| if err != nil { |
| t.Fatalf("genKeyset failed: %v", err) |
| } |
| rawJWT := &pb.JwtToken{ |
| TypeHeader: &wpb.StringValue{Value: "JWT"}, |
| Expiration: &tpb.Timestamp{Seconds: 123456}, |
| NotBefore: &tpb.Timestamp{Seconds: 12345}, |
| IssuedAt: &tpb.Timestamp{Seconds: 1234}, |
| } |
| signResponse, err := jwtService.ComputeMacAndEncode(ctx, &pb.JwtSignRequest{AnnotatedKeyset: &pb.AnnotatedKeyset{SerializedKeyset: keyset}, RawJwt: rawJWT}) |
| if err != nil { |
| t.Fatalf("jwtService.ComputeMacAndEncode() err = %v, want nil", err) |
| } |
| compact, err := signedCompactJWTFromResponse(signResponse) |
| if err != nil { |
| t.Fatalf("JwtSignResponse_Err: %v", err) |
| } |
| validator := &pb.JwtValidator{ |
| ExpectedTypeHeader: &wpb.StringValue{Value: "JWT"}, |
| Now: &tpb.Timestamp{Seconds: 12345}, |
| } |
| verifyResponse, err := jwtService.VerifyMacAndDecode(ctx, &pb.JwtVerifyRequest{AnnotatedKeyset: &pb.AnnotatedKeyset{SerializedKeyset: keyset}, SignedCompactJwt: compact, Validator: validator}) |
| if err != nil { |
| t.Fatalf("jwtService.VerifyMacAndDecode() err = %v, want nil", err) |
| } |
| if _, err := verifiedJWTFromResponse(verifyResponse); err != nil { |
| t.Fatalf("JwtVerifyResponse_Err: %v", err) |
| } |
| for _, tc := range []jwtTestCase{ |
| { |
| tag: "unexpected type header", |
| validator: &pb.JwtValidator{ |
| ExpectedTypeHeader: &wpb.StringValue{Value: "unexpected"}, |
| Now: &tpb.Timestamp{Seconds: 12345}, |
| }, |
| }, |
| { |
| tag: "expired token", |
| validator: &pb.JwtValidator{ |
| ExpectedTypeHeader: &wpb.StringValue{Value: "JWT"}, |
| Now: &tpb.Timestamp{Seconds: 999999999999}, |
| }, |
| }, |
| { |
| tag: "expect issued in the past", |
| validator: &pb.JwtValidator{ |
| ExpectedTypeHeader: &wpb.StringValue{Value: "JWT"}, |
| Now: &tpb.Timestamp{Seconds: 1233}, |
| ExpectIssuedInThePast: true, |
| }, |
| }, |
| } { |
| t.Run(tc.tag, func(t *testing.T) { |
| verifyResponse, err := jwtService.VerifyMacAndDecode(ctx, &pb.JwtVerifyRequest{AnnotatedKeyset: &pb.AnnotatedKeyset{SerializedKeyset: keyset}, SignedCompactJwt: compact, Validator: tc.validator}) |
| if err != nil { |
| t.Fatalf("jwtService.VerifyMacAndDecode() err = %v, want nil", err) |
| } |
| if _, err := verifiedJWTFromResponse(verifyResponse); err == nil { |
| t.Fatalf("JwtVerifyResponse_Err: nil, want error") |
| } |
| }) |
| } |
| } |
| |
| func TestSuccessfulJwtSignVerifyCreation(t *testing.T) { |
| keysetService := &services.KeysetService{} |
| jwtService := &services.JWTService{} |
| ctx := context.Background() |
| |
| template, err := proto.Marshal(jwt.ES256Template()) |
| if err != nil { |
| t.Fatalf("proto.Marshal(hybrid.ES256Template()) failed: %v", err) |
| } |
| |
| privateKeyset, err := genKeyset(ctx, keysetService, template) |
| if err != nil { |
| t.Fatalf("genKeyset failed: %v", err) |
| } |
| |
| result, err := jwtService.CreateJwtPublicKeySign(ctx, &pb.CreationRequest{AnnotatedKeyset: &pb.AnnotatedKeyset{SerializedKeyset: privateKeyset}}) |
| if err != nil { |
| t.Fatalf("CreateJwtPublicKeySign with good keyset failed with gRPC error: %v, want nil", err) |
| } |
| if result.GetErr() != "" { |
| t.Fatalf("CreateJwtPublicKeySign with good keyset failed with result.GetErr() = %q, want empty string", result.GetErr()) |
| } |
| } |
| |
| func TestSuccessfulJwtVerifyCreation(t *testing.T) { |
| keysetService := &services.KeysetService{} |
| jwtService := &services.JWTService{} |
| ctx := context.Background() |
| |
| template, err := proto.Marshal(jwt.ES256Template()) |
| if err != nil { |
| t.Fatalf("proto.Marshal(hybrid.ES256Template()) failed: %v", err) |
| } |
| |
| privateKeyset, err := genKeyset(ctx, keysetService, template) |
| if err != nil { |
| t.Fatalf("genKeyset failed: %v", err) |
| } |
| publicKeyset, err := pubKeyset(ctx, keysetService, privateKeyset) |
| if err != nil { |
| t.Fatalf("pubKeyset failed: %v", err) |
| } |
| |
| result, err := jwtService.CreateJwtPublicKeyVerify(ctx, &pb.CreationRequest{AnnotatedKeyset: &pb.AnnotatedKeyset{SerializedKeyset: publicKeyset}}) |
| if err != nil { |
| t.Fatalf("CreateJwtPublicKeyVerify with good keyset failed with gRPC error: %v", err) |
| } |
| if result.GetErr() != "" { |
| t.Fatalf("CreateJwtPublicKeyVerify with good keyset failed with result.GetErr() = %q, want empty string", result.GetErr()) |
| } |
| } |
| |
| func TestFailingJwtSignCreation(t *testing.T) { |
| keysetService := &services.KeysetService{} |
| jwtService := &services.JWTService{} |
| ctx := context.Background() |
| |
| // We use signature keys -- then we cannot create a hybrid encrypt |
| template, err := proto.Marshal(signature.ECDSAP256KeyTemplate()) |
| if err != nil { |
| t.Fatalf("proto.Marshal(signature.ECDSAP256KeyTemplate()) failed: %v", err) |
| } |
| |
| privateKeyset, err := genKeyset(ctx, keysetService, template) |
| if err != nil { |
| t.Fatalf("genKeyset failed: %v", err) |
| } |
| |
| result, err := jwtService.CreateJwtPublicKeySign(ctx, &pb.CreationRequest{AnnotatedKeyset: &pb.AnnotatedKeyset{SerializedKeyset: privateKeyset}}) |
| if err != nil { |
| t.Fatalf("CreateJwtPublicKeySign with bad keyset failed with gRPC error: %v", err) |
| } |
| if result.GetErr() == "" { |
| t.Fatalf("CreateJwtPublicKeySign with bad keyset succeeded") |
| } |
| } |
| |
| func TestFailingJwtVerifyCreation(t *testing.T) { |
| keysetService := &services.KeysetService{} |
| jwtService := &services.JWTService{} |
| ctx := context.Background() |
| |
| // We use signature keys -- then we cannot create a hybrid encrypt |
| template, err := proto.Marshal(signature.ECDSAP256KeyTemplate()) |
| if err != nil { |
| t.Fatalf("proto.Marshal(signature.ECDSAP256KeyTemplate()) failed: %v", err) |
| } |
| |
| privateKeyset, err := genKeyset(ctx, keysetService, template) |
| if err != nil { |
| t.Fatalf("genKeyset failed: %v", err) |
| } |
| publicKeyset, err := pubKeyset(ctx, keysetService, privateKeyset) |
| if err != nil { |
| t.Fatalf("pubKeyset failed: %v", err) |
| } |
| |
| result, err := jwtService.CreateJwtPublicKeyVerify(ctx, &pb.CreationRequest{AnnotatedKeyset: &pb.AnnotatedKeyset{SerializedKeyset: publicKeyset}}) |
| if err != nil { |
| t.Fatalf("CreateJwtPublicKeyVerify with good keyset failed with gRPC error: %v", err) |
| } |
| if result.GetErr() == "" { |
| t.Fatalf("CreateJwtPublicKeyVerify with bad keyset succeeded") |
| } |
| } |
| |
| func TestJWTPublicKeySignWithInvalidKeysetFails(t *testing.T) { |
| keysetService := &services.KeysetService{} |
| jwtService := &services.JWTService{} |
| |
| ctx := context.Background() |
| template, err := proto.Marshal(aead.AES256GCMKeyTemplate()) |
| if err != nil { |
| t.Fatalf("proto.Marshal(aead.AES256GCMKeyTemplate()) failed: %v", err) |
| } |
| privateKeyset, err := genKeyset(ctx, keysetService, template) |
| if err != nil { |
| t.Fatalf("genKeyset failed: %v", err) |
| } |
| rawJWT := &pb.JwtToken{ |
| Subject: &wpb.StringValue{Value: "tink-subject"}, |
| } |
| signResponse, err := jwtService.PublicKeySignAndEncode(ctx, &pb.JwtSignRequest{AnnotatedKeyset: &pb.AnnotatedKeyset{SerializedKeyset: privateKeyset}, RawJwt: rawJWT}) |
| if err != nil { |
| t.Fatalf("jwtService.PublicKeySignAndEncode() err = %v", err) |
| } |
| if _, err := signedCompactJWTFromResponse(signResponse); err == nil { |
| t.Fatalf("JwtSignResponse_Err: nil want error") |
| } |
| } |
| |
| func TestJWTPublicKeySignInvalidTokenFails(t *testing.T) { |
| keysetService := &services.KeysetService{} |
| jwtService := &services.JWTService{} |
| |
| ctx := context.Background() |
| template, err := proto.Marshal(jwt.ES256Template()) |
| if err != nil { |
| t.Fatalf("proto.Marshal(jwt.ES256Template()) failed: %v", err) |
| } |
| privateKeyset, err := genKeyset(ctx, keysetService, template) |
| if err != nil { |
| t.Fatalf("genKeyset failed: %v", err) |
| } |
| for _, tc := range []jwtTestCase{ |
| { |
| tag: "nil rawJWT", |
| rawJWT: nil, |
| }, |
| { |
| tag: "invalid json array string", |
| rawJWT: &pb.JwtToken{ |
| CustomClaims: map[string]*pb.JwtClaimValue{ |
| "cc-array": &pb.JwtClaimValue{Kind: &pb.JwtClaimValue_JsonArrayValue{JsonArrayValue: "{35}"}}, |
| }, |
| }, |
| }, |
| { |
| tag: "invalid json object string", |
| rawJWT: &pb.JwtToken{ |
| CustomClaims: map[string]*pb.JwtClaimValue{ |
| "cc-object": &pb.JwtClaimValue{Kind: &pb.JwtClaimValue_JsonObjectValue{JsonObjectValue: `["o":"a"]`}}, |
| }, |
| }, |
| }, |
| } { |
| t.Run(tc.tag, func(t *testing.T) { |
| signResponse, err := jwtService.PublicKeySignAndEncode(ctx, &pb.JwtSignRequest{AnnotatedKeyset: &pb.AnnotatedKeyset{SerializedKeyset: privateKeyset}, RawJwt: tc.rawJWT}) |
| if err != nil { |
| t.Fatalf("jwtService.PublicKeySignAndEncode() err = %v", err) |
| } |
| if _, err := signedCompactJWTFromResponse(signResponse); err == nil { |
| t.Fatalf("JwtSignResponse_Err: nil want error") |
| } |
| }) |
| } |
| } |
| |
| func TestJWTPublicKeyVerifyFails(t *testing.T) { |
| keysetService := &services.KeysetService{} |
| jwtService := &services.JWTService{} |
| |
| ctx := context.Background() |
| template, err := proto.Marshal(jwt.ES256Template()) |
| if err != nil { |
| t.Fatalf("proto.Marshal(jwt.ES256Template()) failed: %v", err) |
| } |
| privateKeyset, err := genKeyset(ctx, keysetService, template) |
| if err != nil { |
| t.Fatalf("genKeyset failed: %v", err) |
| } |
| publicKeyset, err := pubKeyset(ctx, keysetService, privateKeyset) |
| if err != nil { |
| t.Fatalf("pubKeyset failed: %v", err) |
| } |
| rawJWT := &pb.JwtToken{ |
| Subject: &wpb.StringValue{Value: "tink-subject"}, |
| } |
| signResponse, err := jwtService.PublicKeySignAndEncode(ctx, &pb.JwtSignRequest{AnnotatedKeyset: &pb.AnnotatedKeyset{SerializedKeyset: privateKeyset}, RawJwt: rawJWT}) |
| if err != nil { |
| t.Fatalf("jwtService.PublicKeySignAndEncode() err = %v", err) |
| } |
| compact, err := signedCompactJWTFromResponse(signResponse) |
| if err != nil { |
| t.Fatalf("JwtSignResponse_Err failed: %v", err) |
| } |
| validator := &pb.JwtValidator{ |
| ExpectedTypeHeader: &wpb.StringValue{Value: "JWT"}, |
| } |
| verifyResponse, err := jwtService.PublicKeyVerifyAndDecode(ctx, &pb.JwtVerifyRequest{AnnotatedKeyset: &pb.AnnotatedKeyset{SerializedKeyset: publicKeyset}, SignedCompactJwt: compact, Validator: validator}) |
| if err != nil { |
| t.Fatalf("jwtVerifySignature failed: %v", err) |
| } |
| if _, err := verifiedJWTFromResponse(verifyResponse); err == nil { |
| t.Fatalf("JwtVerifyResponse_Err: nil want error") |
| } |
| } |
| |
| func TestJWTPublicKeySignAndEncodeVerifyAndDecode(t *testing.T) { |
| keysetService := &services.KeysetService{} |
| jwtService := &services.JWTService{} |
| |
| ctx := context.Background() |
| template, err := proto.Marshal(jwt.ES256Template()) |
| if err != nil { |
| t.Fatalf("proto.Marshal(jwt.ES256Template()) failed: %v", err) |
| } |
| privateKeyset, err := genKeyset(ctx, keysetService, template) |
| if err != nil { |
| t.Fatalf("genKeyset failed: %v", err) |
| } |
| publicKeyset, err := pubKeyset(ctx, keysetService, privateKeyset) |
| if err != nil { |
| t.Fatalf("pubKeyset failed: %v", err) |
| } |
| rawJWT := &pb.JwtToken{ |
| Subject: &wpb.StringValue{Value: "tink-subject"}, |
| } |
| signResponse, err := jwtService.PublicKeySignAndEncode(ctx, &pb.JwtSignRequest{AnnotatedKeyset: &pb.AnnotatedKeyset{SerializedKeyset: privateKeyset}, RawJwt: rawJWT}) |
| if err != nil { |
| t.Fatalf("jwtService.PublicKeySignAndEncode() err = %v", err) |
| } |
| compact, err := signedCompactJWTFromResponse(signResponse) |
| if err != nil { |
| t.Fatalf("JwtSignResponse_Err failed: %v", err) |
| } |
| validator := &pb.JwtValidator{ |
| AllowMissingExpiration: true, |
| } |
| verifyResponse, err := jwtService.PublicKeyVerifyAndDecode(ctx, &pb.JwtVerifyRequest{AnnotatedKeyset: &pb.AnnotatedKeyset{SerializedKeyset: publicKeyset}, SignedCompactJwt: compact, Validator: validator}) |
| if err != nil { |
| t.Fatalf("jwtVerifySignature failed: %v", err) |
| } |
| verifiedJWT, err := verifiedJWTFromResponse(verifyResponse) |
| if err != nil { |
| t.Fatalf("JwtVerifyResponse_Err: %v", err) |
| } |
| if !cmp.Equal(verifiedJWT, rawJWT, protocmp.Transform()) { |
| t.Errorf("verifiedJWT doesn't match expected value: (+ got, - want) %v", cmp.Diff(verifiedJWT, rawJWT, protocmp.Transform())) |
| } |
| } |
| |
| func TestToJwkSetWithPrivateKeyFails(t *testing.T) { |
| keysetService := &services.KeysetService{} |
| jwtService := &services.JWTService{} |
| |
| ctx := context.Background() |
| template, err := proto.Marshal(jwt.ES256Template()) |
| if err != nil { |
| t.Fatalf("proto.Marshal(jwt.ES256Template()) failed: %v", err) |
| } |
| privateKeyset, err := genKeyset(ctx, keysetService, template) |
| if err != nil { |
| t.Fatalf("genKeyset failed: %v", err) |
| } |
| toJWKResponse, err := jwtService.ToJwkSet(ctx, &pb.JwtToJwkSetRequest{Keyset: privateKeyset}) |
| if err != nil { |
| t.Fatalf("jwtService.ToJwkSet() err = %v, want nil", err) |
| } |
| if _, err := jwkSetFromResponse(toJWKResponse); err == nil { |
| t.Fatalf("JwtToJwkSetResponse_Err: = nil, want error") |
| } |
| } |
| |
| func TestFromJwkSetPrivateKeyFails(t *testing.T) { |
| jwtService := &services.JWTService{} |
| ctx := context.Background() |
| jwkES256PublicKey := `{ |
| "keys":[{ |
| "kty":"EC", |
| "crv":"P-256", |
| "x":"wO6uIxh8SkKOO8VjZXNRTteRcwCPE4_4JElKyaa0fcQ", |
| "y":"7oRiYhnmkP6nqrdXWgtsWUWq5uFRLJkhyVFiWPRB278", |
| "d":"8oRinhnmkYjkqrdXWgtsWUWq5uFRLJkhyVFiWPRB278", |
| "use":"sig","alg":"ES256","key_ops":["verify"], |
| "kid":"EhuduQ"}] |
| }` |
| fromJWKResponse, err := jwtService.FromJwkSet(ctx, &pb.JwtFromJwkSetRequest{JwkSet: jwkES256PublicKey}) |
| if err != nil { |
| t.Fatalf("jwtService.FromJwkSet() err = %v, want nil", err) |
| } |
| if _, err := keysetFromResponse(fromJWKResponse); err == nil { |
| t.Fatalf("JwtFromJwkSetResponse_Err = nil, want error") |
| } |
| } |
| |
| func TestFromJwkToJwkSet(t *testing.T) { |
| jwtService := &services.JWTService{} |
| ctx := context.Background() |
| jwkES256PublicKey := `{ |
| "keys":[{ |
| "kty":"EC", |
| "crv":"P-256", |
| "x":"wO6uIxh8SkKOO8VjZXNRTteRcwCPE4_4JElKyaa0fcQ", |
| "y":"7oRiYhnmkP6nqrdXWgtsWUWq5uFRLJkhyVFiWPRB278", |
| "use":"sig","alg":"ES256","key_ops":["verify"], |
| "kid":"EhuduQ"}] |
| }` |
| fromJWKResponse, err := jwtService.FromJwkSet(ctx, &pb.JwtFromJwkSetRequest{JwkSet: jwkES256PublicKey}) |
| if err != nil { |
| t.Fatalf("jwtService.FromJwkSet() err = %v, want nil", err) |
| } |
| ks, err := keysetFromResponse(fromJWKResponse) |
| if err != nil { |
| t.Fatalf("JwtFromJwkSetResponse_Err: = %v, want nil", err) |
| } |
| toJWKResponse, err := jwtService.ToJwkSet(ctx, &pb.JwtToJwkSetRequest{Keyset: ks}) |
| if err != nil { |
| t.Fatalf("jwtService.ToJwkSet() err = %v, want nil", err) |
| } |
| jwkSet, err := jwkSetFromResponse(toJWKResponse) |
| if err != nil { |
| t.Fatalf("JwtToJwkSetResponse_Err: = %v, want nil", err) |
| } |
| got := &spb.Struct{} |
| if err := got.UnmarshalJSON([]byte(jwkSet)); err != nil { |
| t.Fatalf("got.UnmarshalJSON() err = %v, want nil", err) |
| } |
| want := &spb.Struct{} |
| if err := want.UnmarshalJSON([]byte(jwkES256PublicKey)); err != nil { |
| t.Fatalf("want.UnmarshalJSON() err = %v, want nil", err) |
| } |
| if !cmp.Equal(want, got, protocmp.Transform()) { |
| t.Errorf("mismatch in jwk sets: diff (-want,+got): %v", cmp.Diff(want, got, protocmp.Transform())) |
| } |
| } |