Auth: Omit all base64 paddings in JWT tokens for the JWT auth (#35602)

Omitting all base64 paddings (=) in JWT tokens.

Fixes #34496
This commit is contained in:
Guillaume GILL 2021-10-27 18:50:30 +02:00 committed by GitHub
parent 00ffe1a4fd
commit 7c5de96503
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 34 additions and 8 deletions

View File

@ -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

View File

@ -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()