diff --git a/pkg/op/authrequest.go b/pkg/op/authrequest.go index 743da68..ba93c5a 100644 --- a/pkg/op/authrequest.go +++ b/pkg/op/authrequest.go @@ -36,6 +36,18 @@ type AuthorizeValidator interface { //Deprecated: ValidationAuthorizer exists for historical compatibility. Use ValidationAuthorizer itself type ValidationAuthorizer AuthorizeValidator +func authorizeHandler(authorizer Authorizer) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + Authorize(w, r, authorizer) + } +} + +func authorizeCallbackHandler(authorizer Authorizer) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + AuthorizeCallback(w, r, authorizer) + } +} + //Authorize handles the authorization request, including //parsing, validating, storing and finally redirecting to the login handler func Authorize(w http.ResponseWriter, r *http.Request, authorizer Authorizer) { @@ -152,7 +164,7 @@ func ValidateAuthReqIDTokenHint(ctx context.Context, idTokenHint string, verifie if idTokenHint == "" { return "", nil } - claims, err := verifier.VerifyIDToken(ctx, idTokenHint) + claims, err := VerifyIDTokenHint(ctx, idTokenHint, verifier) if err != nil { return "", ErrInvalidRequest("The id_token_hint is invalid. If you have any questions, you may contact the administrator of the application.") } diff --git a/pkg/op/default_op.go b/pkg/op/default_op.go index fbf34bc..9cbc03c 100644 --- a/pkg/op/default_op.go +++ b/pkg/op/default_op.go @@ -279,12 +279,11 @@ func (p *DefaultOP) ClientJWTVerifier() rp.Verifier { return p.verifier } -func (p *DefaultOP) HandleReady(w http.ResponseWriter, r *http.Request) { - probes := []ProbesFn{ +func (p *DefaultOP) Probes() []ProbesFn { + return []ProbesFn{ ReadySigner(p.Signer()), ReadyStorage(p.Storage()), } - Readiness(w, r, probes...) } func (p *DefaultOP) HandleKeys(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/op/keys.go b/pkg/op/keys.go index 8e2052b..ba4d41d 100644 --- a/pkg/op/keys.go +++ b/pkg/op/keys.go @@ -10,6 +10,12 @@ type KeyProvider interface { Storage() Storage } +func keysHandler(k KeyProvider) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + Keys(w, r, k) + } +} + func Keys(w http.ResponseWriter, r *http.Request, k KeyProvider) { keySet, err := k.Storage().GetKeySet(r.Context()) if err != nil { diff --git a/pkg/op/op.go b/pkg/op/op.go index eaba685..2a65320 100644 --- a/pkg/op/op.go +++ b/pkg/op/op.go @@ -16,15 +16,9 @@ const ( type OpenIDProvider interface { Configuration - HandleReady(w http.ResponseWriter, r *http.Request) - HandleDiscovery(w http.ResponseWriter, r *http.Request) - HandleAuthorize(w http.ResponseWriter, r *http.Request) - HandleAuthorizeCallback(w http.ResponseWriter, r *http.Request) - HandleExchange(w http.ResponseWriter, r *http.Request) - HandleUserinfo(w http.ResponseWriter, r *http.Request) - //HandleEndSession(w http.ResponseWriter, r *http.Request) HandleKeys(w http.ResponseWriter, r *http.Request) HttpHandler() http.Handler + Authorizer SessionEnder Signer() Signer Probes() []ProbesFn @@ -47,12 +41,12 @@ func CreateRouter(o OpenIDProvider, interceptors ...HttpInterceptor) *mux.Router router.HandleFunc(healthzEndpoint, Healthz) router.HandleFunc(readinessEndpoint, Ready(o.Probes())) router.HandleFunc(oidc.DiscoveryEndpoint, DiscoveryHandler(o, o.Signer())) - router.Handle(o.AuthorizationEndpoint().Relative(), intercept(o.HandleAuthorize)) - router.Handle(o.AuthorizationEndpoint().Relative()+"/{id}", intercept(o.HandleAuthorizeCallback)) - router.Handle(o.TokenEndpoint().Relative(), intercept(o.HandleExchange)) - router.HandleFunc(o.UserinfoEndpoint().Relative(), o.HandleUserinfo) - router.Handle(o.EndSessionEndpoint().Relative(), intercept(EndSessionHandler(o))) - router.HandleFunc(o.KeysEndpoint().Relative(), o.HandleKeys) + router.Handle(o.AuthorizationEndpoint().Relative(), intercept(authorizeHandler(o))) + router.Handle(o.AuthorizationEndpoint().Relative()+"/{id}", intercept(authorizeCallbackHandler(o))) + router.Handle(o.TokenEndpoint().Relative(), intercept(tokenHandler(o))) + router.HandleFunc(o.UserinfoEndpoint().Relative(), userinfoHandler(o)) + router.Handle(o.EndSessionEndpoint().Relative(), intercept(endSessionHandler(o))) + router.HandleFunc(o.KeysEndpoint().Relative(), keysHandler(o)) return router } diff --git a/pkg/op/session.go b/pkg/op/session.go index 30dd5ea..abbc114 100644 --- a/pkg/op/session.go +++ b/pkg/op/session.go @@ -16,7 +16,7 @@ type SessionEnder interface { DefaultLogoutRedirectURI() string } -func EndSessionHandler(ender SessionEnder) func(http.ResponseWriter, *http.Request) { +func endSessionHandler(ender SessionEnder) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { EndSession(w, r, ender) } diff --git a/pkg/op/tokenrequest.go b/pkg/op/tokenrequest.go index 7c8e663..09ffd67 100644 --- a/pkg/op/tokenrequest.go +++ b/pkg/op/tokenrequest.go @@ -27,6 +27,26 @@ type VerifyExchanger interface { ClientJWTVerifier() rp.Verifier } +func tokenHandler(exchanger Exchanger) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + switch r.FormValue("grant_type") { + case string(oidc.GrantTypeCode): + CodeExchange(w, r, exchanger) + return + case string(oidc.GrantTypeBearer): + JWTExchange(w, r, exchanger) + return + case "excahnge": + TokenExchange(w, r, exchanger) + case "": + RequestError(w, r, ErrInvalidRequest("grant_type missing")) + return + default: + + } + } +} + func CodeExchange(w http.ResponseWriter, r *http.Request, exchanger Exchanger) { tokenReq, err := ParseAccessTokenRequest(r, exchanger.Decoder()) if err != nil { diff --git a/pkg/op/userinfo.go b/pkg/op/userinfo.go index 88ba955..36ecd4a 100644 --- a/pkg/op/userinfo.go +++ b/pkg/op/userinfo.go @@ -15,6 +15,12 @@ type UserinfoProvider interface { Storage() Storage } +func userinfoHandler(userinfoProvider UserinfoProvider) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + Userinfo(w, r, userinfoProvider) + } +} + func Userinfo(w http.ResponseWriter, r *http.Request, userinfoProvider UserinfoProvider) { accessToken, err := getAccessToken(r, userinfoProvider.Decoder()) if err != nil { diff --git a/pkg/op/verifier.go b/pkg/op/verifier.go new file mode 100644 index 0000000..863428c --- /dev/null +++ b/pkg/op/verifier.go @@ -0,0 +1,73 @@ +package op + +import ( + "context" + + "github.com/caos/oidc/pkg/oidc" +) + +type IDTokenHintVerifier interface { +} + +//VerifyIDToken validates the id token according to +//https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation +func VerifyIDTokenHint(ctx context.Context, token string, v IDTokenHintVerifier) (*oidc.IDTokenClaims, error) { + claims := new(oidc.IDTokenClaims) + + decrypted, err := oidc.DecryptToken(token) + if err != nil { + return nil, err + } + payload, err := oidc.ParseToken(decrypted, claims) + if err != nil { + return nil, err + } + //2, check issuer (exact match) + if err := oidc.CheckIssuer(claims.GetIssuer(), v); err != nil { + return nil, err + } + + //3. check aud (aud must contain client_id, all aud strings must be allowed) + if err = oidc.CheckAudience(claims.GetAudience(), v); err != nil { + return nil, err + } + + if err = oidc.CheckAuthorizedParty(claims.GetAudience(), claims.GetAuthorizedParty(), v); err != nil { + return nil, err + } + + //6. check signature by keys + //7. check alg default is rs256 + //8. check if alg is mac based (hs...) -> audience contains client_id. for validation use utf-8 representation of your client_secret + if err = oidc.CheckSignature(ctx, decrypted, payload, claims, v.SupportedSignAlgs(), v.KeySet()); err != nil { + return nil, err + } + + //9. check exp before now + if err = oidc.CheckExpiration(claims.GetExpiration(), v); err != nil { + return nil, err + } + + //10. check iat duration is optional (can be checked) + if err = oidc.CheckIssuedAt(claims.GetIssuedAt(), v); err != nil { + return nil, err + } + + /* + //11. check nonce (check if optional possible) id_token.nonce == sentNonce + if err = oidc.CheckNonce(claims.GetNonce()); err != nil { + return nil, err + } + */ + + //12. if acr requested check acr + if err = oidc.CheckAuthorizationContextClassReference(claims.GetAuthenticationContextClassReference(), v); err != nil { + return nil, err + } + + //13. if auth_time requested check if auth_time is less than max age + if err = oidc.CheckAuthTime(claims.GetAuthTime(), v); err != nil { + return nil, err + } + return claims, nil +} diff --git a/pkg/rp/default_verifier.go b/pkg/rp/default_verifier.go index 2820e39..45d8373 100644 --- a/pkg/rp/default_verifier.go +++ b/pkg/rp/default_verifier.go @@ -145,7 +145,7 @@ func (v *DefaultVerifier) Verify(ctx context.Context, accessToken, idTokenString //Verify implements the `VerifyIDToken` method of the `Verifier` interface //according to https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation func (v *DefaultVerifier) VerifyIDToken(ctx context.Context, idTokenString string) (*oidc.IDTokenClaims, error) { - return VerifyIDToken(ctx, idTokenString, v) + return VerifywIDToken(ctx, idTokenString, v) } func (v *DefaultVerifier) now() time.Time { diff --git a/pkg/rp/verifier.go b/pkg/rp/verifier.go index 61caed1..27d30cb 100644 --- a/pkg/rp/verifier.go +++ b/pkg/rp/verifier.go @@ -65,7 +65,7 @@ func VerifyIDToken(ctx context.Context, token string, v IDTokenVerifier) (*oidc. //6. check signature by keys //7. check alg default is rs256 //8. check if alg is mac based (hs...) -> audience contains client_id. for validation use utf-8 representation of your client_secret - if err = oidc.CheckSignature(ctx, decrypted, payload, claims, v); err != nil { + if err = oidc.CheckSignature(ctx, decrypted, payload, claims, v.SupportedSignAlgs(), v.KeySet()); err != nil { return nil, err }