harmonize jwtProfile and existing interfaces / functions
This commit is contained in:
parent
87b30dcd66
commit
a56a4a018a
14 changed files with 259 additions and 632 deletions
|
@ -7,7 +7,7 @@ import (
|
|||
"github.com/caos/oidc/pkg/utils"
|
||||
)
|
||||
|
||||
func DiscoveryHandler(c Configuration, s Signer) func(http.ResponseWriter, *http.Request) {
|
||||
func discoveryHandler(c Configuration, s Signer) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
Discover(w, CreateDiscoveryConfig(c, s))
|
||||
}
|
||||
|
|
31
pkg/op/op.go
31
pkg/op/op.go
|
@ -13,7 +13,6 @@ import (
|
|||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
"github.com/caos/oidc/pkg/oidc"
|
||||
"github.com/caos/oidc/pkg/rp"
|
||||
"github.com/caos/oidc/pkg/utils"
|
||||
)
|
||||
|
||||
|
@ -51,6 +50,7 @@ type OpenIDProvider interface {
|
|||
Decoder() utils.Decoder
|
||||
Encoder() utils.Encoder
|
||||
IDTokenHintVerifier() IDTokenHintVerifier
|
||||
JWTProfileVerifier() JWTProfileVerifier
|
||||
Crypto() Crypto
|
||||
DefaultLogoutRedirectURI() string
|
||||
Signer() Signer
|
||||
|
@ -72,9 +72,9 @@ func CreateRouter(o OpenIDProvider, interceptors ...HttpInterceptor) *mux.Router
|
|||
handlers.AllowedHeaders([]string{"authorization", "content-type"}),
|
||||
handlers.AllowedOriginValidator(allowAllOrigins),
|
||||
))
|
||||
router.HandleFunc(healthzEndpoint, Healthz)
|
||||
router.HandleFunc(readinessEndpoint, Ready(o.Probes()))
|
||||
router.HandleFunc(oidc.DiscoveryEndpoint, DiscoveryHandler(o, o.Signer()))
|
||||
router.HandleFunc(healthzEndpoint, healthzHandler)
|
||||
router.HandleFunc(readinessEndpoint, readyHandler(o.Probes()))
|
||||
router.HandleFunc(oidc.DiscoveryEndpoint, discoveryHandler(o, o.Signer()))
|
||||
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)))
|
||||
|
@ -131,8 +131,6 @@ func NewOpenIDProvider(ctx context.Context, config *Config, storage Storage, opO
|
|||
o.signer = NewDefaultSigner(ctx, storage, keyCh)
|
||||
go EnsureKey(ctx, storage, keyCh, o.timer, o.retry)
|
||||
|
||||
o.idTokenHintVerifier = NewIDTokenHintVerifier(config.Issuer, o)
|
||||
|
||||
o.httpHandler = CreateRouter(o, o.interceptors...)
|
||||
|
||||
o.decoder = schema.NewDecoder()
|
||||
|
@ -151,6 +149,7 @@ type openidProvider struct {
|
|||
storage Storage
|
||||
signer Signer
|
||||
idTokenHintVerifier IDTokenHintVerifier
|
||||
jwtProfileVerifier JWTProfileVerifier
|
||||
crypto Crypto
|
||||
httpHandler http.Handler
|
||||
decoder *schema.Decoder
|
||||
|
@ -205,9 +204,19 @@ func (o *openidProvider) Encoder() utils.Encoder {
|
|||
}
|
||||
|
||||
func (o *openidProvider) IDTokenHintVerifier() IDTokenHintVerifier {
|
||||
if o.idTokenHintVerifier == nil {
|
||||
o.idTokenHintVerifier = NewIDTokenHintVerifier(o.Issuer(), &openIDKeySet{o.Storage()})
|
||||
}
|
||||
return o.idTokenHintVerifier
|
||||
}
|
||||
|
||||
func (o *openidProvider) JWTProfileVerifier() JWTProfileVerifier {
|
||||
if o.jwtProfileVerifier == nil {
|
||||
o.jwtProfileVerifier = NewJWTProfileVerifier(o.Storage(), o.Issuer())
|
||||
}
|
||||
return o.jwtProfileVerifier
|
||||
}
|
||||
|
||||
func (o *openidProvider) Crypto() Crypto {
|
||||
return o.crypto
|
||||
}
|
||||
|
@ -231,19 +240,23 @@ func (o *openidProvider) HttpHandler() http.Handler {
|
|||
return o.httpHandler
|
||||
}
|
||||
|
||||
type openIDKeySet struct {
|
||||
Storage
|
||||
}
|
||||
|
||||
//VerifySignature implements the oidc.KeySet interface
|
||||
//providing an implementation for the keys stored in the OP Storage interface
|
||||
func (o *openidProvider) VerifySignature(ctx context.Context, jws *jose.JSONWebSignature) ([]byte, error) {
|
||||
func (o *openIDKeySet) VerifySignature(ctx context.Context, jws *jose.JSONWebSignature) ([]byte, error) {
|
||||
keyID := ""
|
||||
for _, sig := range jws.Signatures {
|
||||
keyID = sig.Header.KeyID
|
||||
break
|
||||
}
|
||||
keySet, err := o.Storage().GetKeySet(ctx)
|
||||
keySet, err := o.Storage.GetKeySet(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.New("error fetching keys")
|
||||
}
|
||||
payload, err, ok := rp.CheckKey(keyID, keySet.Keys, jws)
|
||||
payload, err, ok := oidc.CheckKey(keyID, jws, keySet.Keys...)
|
||||
if !ok {
|
||||
return nil, errors.New("invalid kid")
|
||||
}
|
||||
|
|
|
@ -10,11 +10,11 @@ import (
|
|||
|
||||
type ProbesFn func(context.Context) error
|
||||
|
||||
func Healthz(w http.ResponseWriter, r *http.Request) {
|
||||
func healthzHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ok(w)
|
||||
}
|
||||
|
||||
func Ready(probes []ProbesFn) func(w http.ResponseWriter, r *http.Request) {
|
||||
func readyHandler(probes []ProbesFn) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
Readiness(w, r, probes...)
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ type OPStorage interface {
|
|||
AuthorizeClientIDSecret(context.Context, string, string) error
|
||||
GetUserinfoFromScopes(context.Context, string, []string) (*oidc.Userinfo, error)
|
||||
GetUserinfoFromToken(context.Context, string, string) (*oidc.Userinfo, error)
|
||||
GetKeyByID(ctx context.Context, keyID string) (*jose.JSONWebKeySet, error)
|
||||
GetKeyByIDAndUserID(ctx context.Context, keyID, userID string) (*jose.JSONWebKey, error)
|
||||
}
|
||||
|
||||
type Storage interface {
|
||||
|
|
|
@ -4,12 +4,8 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
"github.com/caos/oidc/pkg/oidc"
|
||||
"github.com/caos/oidc/pkg/rp"
|
||||
"github.com/caos/oidc/pkg/utils"
|
||||
)
|
||||
|
||||
|
@ -20,11 +16,7 @@ type Exchanger interface {
|
|||
Signer() Signer
|
||||
Crypto() Crypto
|
||||
AuthMethodPostSupported() bool
|
||||
}
|
||||
|
||||
type VerifyExchanger interface {
|
||||
Exchanger
|
||||
ClientJWTVerifier() oidc.Verifier
|
||||
JWTProfileVerifier() JWTProfileVerifier
|
||||
}
|
||||
|
||||
func tokenHandler(exchanger Exchanger) func(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -34,16 +26,16 @@ func tokenHandler(exchanger Exchanger) func(w http.ResponseWriter, r *http.Reque
|
|||
CodeExchange(w, r, exchanger)
|
||||
return
|
||||
case string(oidc.GrantTypeBearer):
|
||||
ex, _ := exchanger.(VerifyExchanger)
|
||||
JWTExchange(w, r, ex)
|
||||
JWTProfile(w, r, exchanger)
|
||||
return
|
||||
case "excahnge":
|
||||
case "exchange":
|
||||
TokenExchange(w, r, exchanger)
|
||||
case "":
|
||||
RequestError(w, r, ErrInvalidRequest("grant_type missing"))
|
||||
return
|
||||
default:
|
||||
|
||||
RequestError(w, r, ErrInvalidRequest("grant_type not supported"))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -144,41 +136,13 @@ func AuthorizeCodeChallenge(ctx context.Context, tokenReq *oidc.AccessTokenReque
|
|||
return authReq, nil
|
||||
}
|
||||
|
||||
type ClientJWTVerifier struct {
|
||||
claims *oidc.JWTTokenRequest
|
||||
storage Storage
|
||||
issuer string
|
||||
}
|
||||
|
||||
func (c ClientJWTVerifier) Storage() Storage {
|
||||
return c.storage
|
||||
}
|
||||
|
||||
func (c ClientJWTVerifier) Issuer() string {
|
||||
return c.claims.Issuer
|
||||
}
|
||||
|
||||
func (c ClientJWTVerifier) ClientID() string {
|
||||
return c.issuer
|
||||
}
|
||||
|
||||
func (c ClientJWTVerifier) MaxAgeIAT() time.Duration {
|
||||
//TODO: define in conf/opts
|
||||
return 1 * time.Hour
|
||||
}
|
||||
|
||||
func (c ClientJWTVerifier) Offset() time.Duration {
|
||||
//TODO: define in conf/opts
|
||||
return time.Second
|
||||
}
|
||||
|
||||
func JWTExchange(w http.ResponseWriter, r *http.Request, exchanger VerifyExchanger) {
|
||||
assertion, err := ParseJWTTokenRequest(r, exchanger.Decoder())
|
||||
func JWTProfile(w http.ResponseWriter, r *http.Request, exchanger Exchanger) {
|
||||
assertion, err := ParseJWTProfileRequest(r, exchanger.Decoder())
|
||||
if err != nil {
|
||||
RequestError(w, r, err)
|
||||
}
|
||||
|
||||
claims, err := VerifyJWTAssertion(r.Context(), assertion, exchanger)
|
||||
claims, err := VerifyJWTAssertion(r.Context(), assertion, exchanger.JWTProfileVerifier())
|
||||
if err != nil {
|
||||
RequestError(w, r, err)
|
||||
return
|
||||
|
@ -192,70 +156,7 @@ func JWTExchange(w http.ResponseWriter, r *http.Request, exchanger VerifyExchang
|
|||
utils.MarshalJSON(w, resp)
|
||||
}
|
||||
|
||||
type JWTAssertionVerifier interface {
|
||||
Storage() Storage
|
||||
oidc.Verifier
|
||||
}
|
||||
|
||||
func VerifyJWTAssertion(ctx context.Context, assertion string, exchanger Exchanger) (*oidc.JWTTokenRequest, error) {
|
||||
verifier := &ClientJWTVerifier{
|
||||
storage: exchanger.Storage(),
|
||||
issuer: exchanger.Issuer(),
|
||||
claims: new(oidc.JWTTokenRequest),
|
||||
}
|
||||
payload, err := oidc.ParseToken(assertion, verifier.claims)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = oidc.CheckAudience(verifier.claims, verifier.issuer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = oidc.CheckExpiration(verifier.claims, verifier.Offset()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = oidc.CheckIssuedAt(verifier.claims, verifier.MaxAgeIAT(), verifier.Offset()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if verifier.claims.Issuer != verifier.claims.Subject {
|
||||
//TODO: implement delegation (openid core / oauth rfc)
|
||||
}
|
||||
verifier.Storage().GetClientByClientID(ctx, verifier.claims.Subject)
|
||||
|
||||
keySet := &ClientAssertionKeySet{exchanger.Storage(), verifier.claims.Subject}
|
||||
|
||||
if err = oidc.CheckSignature(ctx, assertion, payload, verifier.claims, nil, keySet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return verifier.claims, nil
|
||||
}
|
||||
|
||||
type ClientAssertionKeySet struct {
|
||||
Storage
|
||||
id string
|
||||
}
|
||||
|
||||
func (c *ClientAssertionKeySet) VerifySignature(ctx context.Context, jws *jose.JSONWebSignature) (payload []byte, err error) {
|
||||
keyID := ""
|
||||
for _, sig := range jws.Signatures {
|
||||
keyID = sig.Header.KeyID
|
||||
break
|
||||
}
|
||||
keySet, err := c.Storage.GetKeyByID(ctx, keyID)
|
||||
if err != nil {
|
||||
return nil, errors.New("error fetching keys")
|
||||
}
|
||||
payload, err, ok := rp.CheckKey(keyID, keySet.Keys, jws)
|
||||
if !ok {
|
||||
return nil, errors.New("invalid kid")
|
||||
}
|
||||
return payload, err
|
||||
}
|
||||
|
||||
func ParseJWTTokenRequest(r *http.Request, decoder utils.Decoder) (string, error) {
|
||||
func ParseJWTProfileRequest(r *http.Request, decoder utils.Decoder) (string, error) {
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
return "", ErrInvalidRequest("error parsing form")
|
||||
|
@ -267,7 +168,6 @@ func ParseJWTTokenRequest(r *http.Request, decoder utils.Decoder) (string, error
|
|||
if err != nil {
|
||||
return "", ErrInvalidRequest("error decoding form")
|
||||
}
|
||||
//TODO: validations
|
||||
return tokenReq.Token, nil
|
||||
}
|
||||
|
||||
|
|
99
pkg/op/verifier_jwt_profile.go
Normal file
99
pkg/op/verifier_jwt_profile.go
Normal file
|
@ -0,0 +1,99 @@
|
|||
package op
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
"github.com/caos/oidc/pkg/oidc"
|
||||
)
|
||||
|
||||
type JWTProfileVerifier interface {
|
||||
oidc.Verifier
|
||||
Storage() Storage
|
||||
}
|
||||
|
||||
type jwtProfileVerifier struct {
|
||||
storage Storage
|
||||
issuer string
|
||||
}
|
||||
|
||||
func NewJWTProfileVerifier(storage Storage, issuer string) JWTProfileVerifier {
|
||||
return &jwtProfileVerifier{
|
||||
storage: storage,
|
||||
issuer: issuer,
|
||||
}
|
||||
}
|
||||
|
||||
func (v *jwtProfileVerifier) Issuer() string {
|
||||
return v.issuer
|
||||
}
|
||||
|
||||
func (v *jwtProfileVerifier) Storage() Storage {
|
||||
return v.storage
|
||||
}
|
||||
|
||||
func (v *jwtProfileVerifier) MaxAgeIAT() time.Duration {
|
||||
//TODO: define in conf/opts
|
||||
return 1 * time.Hour
|
||||
}
|
||||
|
||||
func (v *jwtProfileVerifier) Offset() time.Duration {
|
||||
//TODO: define in conf/opts
|
||||
return time.Second
|
||||
}
|
||||
|
||||
func VerifyJWTAssertion(ctx context.Context, assertion string, v JWTProfileVerifier) (*oidc.JWTTokenRequest, error) {
|
||||
request := new(oidc.JWTTokenRequest)
|
||||
payload, err := oidc.ParseToken(assertion, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = oidc.CheckAudience(request, v.Issuer()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = oidc.CheckExpiration(request, v.Offset()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = oidc.CheckIssuedAt(request, v.MaxAgeIAT(), v.Offset()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if request.Issuer != request.Subject {
|
||||
//TODO: implement delegation (openid core / oauth rfc)
|
||||
}
|
||||
|
||||
keySet := &jwtProfileKeySet{v.Storage(), request.Subject}
|
||||
|
||||
if err = oidc.CheckSignature(ctx, assertion, payload, request, nil, keySet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return request, nil
|
||||
}
|
||||
|
||||
type jwtProfileKeySet struct {
|
||||
Storage
|
||||
userID string
|
||||
}
|
||||
|
||||
func (k *jwtProfileKeySet) VerifySignature(ctx context.Context, jws *jose.JSONWebSignature) (payload []byte, err error) {
|
||||
keyID := ""
|
||||
for _, sig := range jws.Signatures {
|
||||
keyID = sig.Header.KeyID
|
||||
break
|
||||
}
|
||||
key, err := k.Storage.GetKeyByIDAndUserID(ctx, keyID, k.userID)
|
||||
if err != nil {
|
||||
return nil, errors.New("error fetching keys")
|
||||
}
|
||||
payload, err, ok := oidc.CheckKey(keyID, jws, *key)
|
||||
if !ok {
|
||||
return nil, errors.New("invalid kid")
|
||||
}
|
||||
return payload, err
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue