Skip to content

Commit

Permalink
add support for alg:none
Browse files Browse the repository at this point in the history
  • Loading branch information
narg95 committed May 11, 2021
1 parent 6f55110 commit ca2ed20
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 2 deletions.
4 changes: 4 additions & 0 deletions authorize_request_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ func (f *Fosite) authorizeRequestParametersFromOpenIDConnectRequest(request *Aut
return nil, errorsx.WithStack(ErrInvalidRequestObject.WithHintf("The request object uses signing algorithm '%s', but the requested OAuth 2.0 Client enforces signing algorithm '%s'.", t.Header["alg"], oidcClient.GetRequestObjectSigningAlgorithm()))
}

if t.Method == jwt.SigningMethodNone {
return jwt.UnsafeAllowNoneSignatureType, nil
}

switch t.Method {
case jose.RS256, jose.RS384, jose.RS512:
key, err := f.findClientPublicJWK(oidcClient, t, true)
Expand Down
20 changes: 20 additions & 0 deletions authorize_request_handler_oidc_request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ func mustGenerateHSAssertion(t *testing.T, claims jwt.MapClaims) string {
return tokenString
}

func mustGenerateNoneAssertion(t *testing.T, claims jwt.MapClaims) string {
token := jwt.NewWithClaims(jwt.SigningMethodNone, claims)
tokenString, err := token.SignedString(jwt.UnsafeAllowNoneSignatureType)
require.NoError(t, err)
return tokenString
}

func TestAuthorizeRequestParametersFromOpenIDConnectRequest(t *testing.T) {
key, err := rsa.GenerateKey(rand.Reader, 1024)
if err != nil {
Expand All @@ -73,6 +80,7 @@ func TestAuthorizeRequestParametersFromOpenIDConnectRequest(t *testing.T) {

validRequestObject := mustGenerateAssertion(t, jwt.MapClaims{"scope": "foo", "foo": "bar", "baz": "baz", "response_type": "token", "response_mode": "post_form"}, key, "kid-foo")
validRequestObjectWithoutKid := mustGenerateAssertion(t, jwt.MapClaims{"scope": "foo", "foo": "bar", "baz": "baz"}, key, "")
validNoneRequestObject := mustGenerateNoneAssertion(t, jwt.MapClaims{"scope": "foo", "foo": "bar", "baz": "baz", "state": "some-state"})

var reqH http.HandlerFunc = func(rw http.ResponseWriter, r *http.Request) {
rw.Write([]byte(validRequestObject))
Expand Down Expand Up @@ -182,6 +190,18 @@ func TestAuthorizeRequestParametersFromOpenIDConnectRequest(t *testing.T) {
client: &DefaultOpenIDConnectClient{JSONWebKeysURI: reqJWK.URL, RequestObjectSigningAlgorithm: "RS256", RequestURIs: []string{reqTS.URL}},
expectForm: url.Values{"response_type": {"token"}, "response_mode": {"post_form"}, "scope": {"foo openid"}, "request_uri": {reqTS.URL}, "foo": {"bar"}, "baz": {"baz"}},
},
{
d: "should pass when request object uses algorithm none",
form: url.Values{"scope": {"openid"}, "request": {validNoneRequestObject}},
client: &DefaultOpenIDConnectClient{JSONWebKeysURI: reqJWK.URL, RequestObjectSigningAlgorithm: "none"},
expectForm: url.Values{"state": {"some-state"}, "scope": {"foo openid"}, "request": {validNoneRequestObject}, "foo": {"bar"}, "baz": {"baz"}},
},
{
d: "should pass when request object uses algorithm none and the client did not explicitly allow any algorithm",
form: url.Values{"scope": {"openid"}, "request": {validNoneRequestObject}},
client: &DefaultOpenIDConnectClient{JSONWebKeysURI: reqJWK.URL},
expectForm: url.Values{"state": {"some-state"}, "scope": {"foo openid"}, "request": {validNoneRequestObject}, "foo": {"bar"}, "baz": {"baz"}},
},
} {
t.Run(fmt.Sprintf("case=%d/description=%s", k, tc.d), func(t *testing.T) {
req := &AuthorizeRequest{
Expand Down
20 changes: 20 additions & 0 deletions client_authentication_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ func mustGenerateHSAssertion(t *testing.T, claims jwt.MapClaims, key *rsa.Privat
return tokenString
}

func mustGenerateNoneAssertion(t *testing.T, claims jwt.MapClaims, key *rsa.PrivateKey, kid string) string {
token := jwt.NewWithClaims(jwt.SigningMethodNone, claims)
tokenString, err := token.SignedString(jwt.UnsafeAllowNoneSignatureType)
require.NoError(t, err)
return tokenString
}

// returns an http basic authorization header, encoded using application/x-www-form-urlencoded
func clientBasicAuthHeader(clientID, clientSecret string) http.Header {
creds := url.QueryEscape(clientID) + ":" + url.QueryEscape(clientSecret)
Expand Down Expand Up @@ -401,6 +408,19 @@ func TestAuthenticateClient(t *testing.T) {
r: new(http.Request),
expectErr: ErrInvalidClient,
},
{
d: "should fail because JWT algorithm is none",
client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: rsaJwks, TokenEndpointAuthMethod: "private_key_jwt"},
form: url.Values{"client_id": []string{"bar"}, "client_assertion": {mustGenerateNoneAssertion(t, jwt.MapClaims{
"sub": "bar",
"exp": time.Now().Add(time.Hour).Unix(),
"iss": "bar",
"jti": "12345",
"aud": "token-url",
}, rsaKey, "kid-foo")}, "client_assertion_type": []string{at}},
r: new(http.Request),
expectErr: ErrInvalidClient,
},
{
d: "should pass with proper assertion when JWKs URI is set",
client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeysURI: ts.URL, TokenEndpointAuthMethod: "private_key_jwt"},
Expand Down
38 changes: 36 additions & 2 deletions token/jwt/token.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package jwt

import (
"encoding/base64"
"encoding/json"
"fmt"
"reflect"

Expand All @@ -22,6 +24,14 @@ type Token struct {
valid bool
}

const (
SigningMethodNone = jose.SignatureAlgorithm("none")
// This key should be use to correctly sign and verify alg:none JWT tokens
UnsafeAllowNoneSignatureType unsafeNoneMagicConstant = "none signing method allowed"
)

type unsafeNoneMagicConstant string

// Valid informs if the token was verified against a given verification key
// and claims are valid
func (t *Token) Valid() bool {
Expand Down Expand Up @@ -58,6 +68,11 @@ func (t *Token) toJoseHeader() map[jose.HeaderKey]interface{} {
//
// > Get the complete, signed token
func (t *Token) SignedString(k interface{}) (rawToken string, err error) {
if _, ok := k.(unsafeNoneMagicConstant); ok {
rawToken, err = unsignedToken(t)
return

}
var signer jose.Signer
key := jose.SigningKey{
Algorithm: t.Method,
Expand All @@ -83,6 +98,21 @@ func (t *Token) SignedString(k interface{}) (rawToken string, err error) {
return
}

func unsignedToken(t *Token) (string, error) {
t.Header["alg"] = "none"
hbytes, err := json.Marshal(&t.Header)
if err != nil {
return "", errorsx.WithStack(err)
}
bbytes, err := json.Marshal(&t.Claims)
if err != nil {
return "", errorsx.WithStack(err)
}
h := base64.RawURLEncoding.EncodeToString(hbytes)
b := base64.RawURLEncoding.EncodeToString(bbytes)
return fmt.Sprintf("%v.%v.", h, b), nil
}

func newToken(parsedToken *jwt.JSONWebToken, claims MapClaims) (*Token, error) {
token := &Token{Claims: claims}
if len(parsedToken.Headers) != 1 {
Expand Down Expand Up @@ -164,8 +194,12 @@ func ParseWithClaims(rawToken string, claims MapClaims, keyFunc Keyfunc) (*Token
verificationKey = pointer(verificationKey)

// verify signature with returned key
if err := parsedToken.Claims(verificationKey, &claims); err != nil {
return nil, &ValidationError{Errors: ValidationErrorSignatureInvalid, text: err.Error()}
_, validNoneKey := verificationKey.(*unsafeNoneMagicConstant)
isSignedToken := !(token.Method == SigningMethodNone && validNoneKey)
if isSignedToken {
if err := parsedToken.Claims(verificationKey, &claims); err != nil {
return nil, &ValidationError{Errors: ValidationErrorSignatureInvalid, text: err.Error()}
}
}

// Validate claims
Expand Down
17 changes: 17 additions & 0 deletions token/jwt/token_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package jwt

import (
"errors"
"strings"
"testing"
"time"

Expand All @@ -28,3 +29,19 @@ func TestParseInvalidReturnsToken(t *testing.T) {
require.NotZero(t, verr.Errors&ValidationErrorExpired, "%+v", verr)
require.NotEmpty(t, ptoken)
}

func TestUnsignedToken(t *testing.T) {
key := UnsafeAllowNoneSignatureType
token := NewWithClaims(SigningMethodNone, MapClaims{
"aud": "foo",
"exp": time.Now().UTC().Add(time.Hour).Unix(),
"iat": time.Now().UTC().Unix(),
"sub": "nestor",
})
rawToken, err := token.SignedString(key)
require.NoError(t, err)
require.NotEmpty(t, rawToken)
parts := strings.Split(rawToken, ".")
require.Len(t, parts, 3)
require.Empty(t, parts[2])
}

0 comments on commit ca2ed20

Please sign in to comment.