From 7c5de96503feb0bf2044a617f0c1f2edcf1a0cbd Mon Sep 17 00:00:00 2001 From: Guillaume GILL Date: Wed, 27 Oct 2021 18:50:30 +0200 Subject: [PATCH] Auth: Omit all base64 paddings in JWT tokens for the JWT auth (#35602) Omitting all base64 paddings (=) in JWT tokens. Fixes #34496 --- pkg/services/auth/jwt/auth.go | 11 +++++++++++ pkg/services/auth/jwt/auth_test.go | 31 ++++++++++++++++++++++-------- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/pkg/services/auth/jwt/auth.go b/pkg/services/auth/jwt/auth.go index 753a188fd02..469eb6ce129 100644 --- a/pkg/services/auth/jwt/auth.go +++ b/pkg/services/auth/jwt/auth.go @@ -2,7 +2,9 @@ package jwt import ( "context" + "encoding/base64" "errors" + "strings" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/remotecache" @@ -55,9 +57,18 @@ type AuthService struct { expectRegistered jwt.Expected } +// Sanitize JWT base64 strings to remove paddings everywhere +func sanitizeJWT(jwtToken string) string { + // JWT can be compact, JSON flatened or JSON general + // In every cases, parts are base64 strings without padding + // The padding char (=) should never interfer with data + return strings.ReplaceAll(jwtToken, string(base64.StdPadding), "") +} + func (s *AuthService) Verify(ctx context.Context, strToken string) (models.JWTClaims, error) { s.log.Debug("Parsing JSON Web Token") + strToken = sanitizeJWT(strToken) token, err := jwt.ParseSigned(strToken) if err != nil { return nil, err diff --git a/pkg/services/auth/jwt/auth_test.go b/pkg/services/auth/jwt/auth_test.go index dcca7373553..0d45165b046 100644 --- a/pkg/services/auth/jwt/auth_test.go +++ b/pkg/services/auth/jwt/auth_test.go @@ -3,12 +3,14 @@ package jwt import ( "context" "crypto/x509" + "encoding/base64" "encoding/json" "encoding/pem" "io/ioutil" "net/http" "net/http/httptest" "os" + "strings" "testing" "time" @@ -35,9 +37,9 @@ type configureFunc func(*testing.T, *setting.Cfg) type scenarioFunc func(*testing.T, scenarioContext) type cachingScenarioFunc func(*testing.T, cachingScenarioContext) -func TestVerifyUsingPKIXPublicKeyFile(t *testing.T) { - subject := "foo-subj" +const subject = "foo-subj" +func TestVerifyUsingPKIXPublicKeyFile(t *testing.T) { key := rsaKeys[0] unknownKey := rsaKeys[1] @@ -77,8 +79,6 @@ func TestVerifyUsingJWKSetFile(t *testing.T) { cfg.JWTAuthJWKSetFile = file.Name() } - subject := "foo-subj" - scenario(t, "verifies a token signed with a key from the set", func(t *testing.T, sc scenarioContext) { token := sign(t, &jwKeys[0], jwt.Claims{Subject: subject}) verifiedClaims, err := sc.authJWTSvc.Verify(sc.ctx, token) @@ -101,8 +101,6 @@ func TestVerifyUsingJWKSetFile(t *testing.T) { } func TestVerifyUsingJWKSetURL(t *testing.T) { - subject := "foo-subj" - t.Run("should refuse to start with non-https URL", func(t *testing.T) { var err error @@ -139,8 +137,6 @@ func TestVerifyUsingJWKSetURL(t *testing.T) { } func TestCachingJWKHTTPResponse(t *testing.T) { - subject := "foo-subj" - jwkCachingScenario(t, "caches the jwk response", func(t *testing.T, sc cachingScenarioContext) { for i := 0; i < 5; i++ { token := sign(t, &jwKeys[0], jwt.Claims{Subject: subject}) @@ -359,6 +355,25 @@ func jwkCachingScenario(t *testing.T, desc string, fn cachingScenarioFunc, cbs . }) } +func TestBase64Paddings(t *testing.T) { + key := rsaKeys[0] + + scenario(t, "verifies a token with base64 padding (non compliant rfc7515#section-2 but accepted)", func(t *testing.T, sc scenarioContext) { + token := sign(t, key, jwt.Claims{ + Subject: subject, + }) + var tokenParts []string + for i, part := range strings.Split(token, ".") { + // Create parts with different padding numbers to test multiple cases. + tokenParts = append(tokenParts, part+strings.Repeat(string(base64.StdPadding), i)) + } + token = strings.Join(tokenParts, ".") + verifiedClaims, err := sc.authJWTSvc.Verify(sc.ctx, token) + require.NoError(t, err) + assert.Equal(t, verifiedClaims["sub"], subject) + }, configurePKIXPublicKeyFile) +} + func scenario(t *testing.T, desc string, fn scenarioFunc, cbs ...configureFunc) { t.Helper()