change verifier interfaces

This commit is contained in:
Livio Amstutz 2020-09-11 10:45:07 +02:00
parent 3777f1436d
commit 143ff3482c
11 changed files with 274 additions and 179 deletions

View file

@ -28,35 +28,47 @@ type Claims interface {
}
var (
ErrParse = errors.New("")
ErrIssuerInvalid = errors.New("issuer does not match")
ErrAudience = errors.New("audience is not valid")
ErrAzpMissing = errors.New("authorized party is not set. If Token is valid for multiple audiences, azp must not be empty")
ErrAzpInvalid = errors.New("authorized party is not valid")
ErrParse = errors.New("")
ErrIssuerInvalid = errors.New("issuer does not match")
ErrAudience = errors.New("audience is not valid")
ErrAzpMissing = errors.New("authorized party is not set. If Token is valid for multiple audiences, azp must not be empty")
ErrAzpInvalid = errors.New("authorized party is not valid")
ErrSignatureMissing = errors.New("id_token does not contain a signature")
ErrSignatureMultiple = errors.New("id_token contains multiple signatures")
ErrSignatureUnsupportedAlg = errors.New("signature algorithm not supported")
ErrSignatureInvalidPayload = errors.New("signature does not match Payload")
ErrExpired = errors.New("token has expired")
ErrIatInFuture = errors.New("issuedAt of token is in the future")
ErrIatToOld = errors.New("issuedAt of token is to old")
//
//ErrNonceInvalid = func(expected, actual string) *validationError {
// return ValidationError("nonce does not match. Expected: %s, got: %s", expected, actual)
//}
ErrAcrInvalid = errors.New("acr is invalid")
ErrAuthTimeNotPresent = errors.New("claim `auth_time` of token is missing")
ErrAuthTimeToOld = errors.New("auth time of token is to old")
ErrAtHash = errors.New("at_hash does not correspond to access token")
ErrExpired = errors.New("token has expired")
ErrIatInFuture = errors.New("issuedAt of token is in the future")
ErrIatToOld = errors.New("issuedAt of token is to old")
ErrNonceInvalid = errors.New("nonce does not match")
ErrAcrInvalid = errors.New("acr is invalid")
ErrAuthTimeNotPresent = errors.New("claim `auth_time` of token is missing")
ErrAuthTimeToOld = errors.New("auth time of token is to old")
ErrAtHash = errors.New("at_hash does not correspond to access token")
)
type Verifier interface {
Issuer() string
MaxAgeIAT() time.Duration
Offset() time.Duration
}
type verifierConfig struct {
issuer string
clientID string
nonce string
ignoreAudience bool
ignoreExpiration bool
//iat *iatConfig
acr ACRVerifier
maxAge time.Duration
supportedSignAlgs []string
// httpClient *http.Client
now time.Time
}
//ACRVerifier specifies the function to be used by the `DefaultVerifier` for validating the acr claim
type ACRVerifier func(string) error
@ -77,43 +89,30 @@ func ParseToken(tokenString string, claims interface{}) ([]byte, error) {
return payload, err
}
type Verifier interface {
Issuer() string
ClientID() string
SupportedSignAlgs() []string
KeySet() KeySet
ACR() ACRVerifier
MaxAge() time.Duration
MaxAgeIAT() time.Duration
Offset() time.Duration
}
func CheckIssuer(issuer string, i Verifier) error {
if i.Issuer() != issuer {
return fmt.Errorf("%w: Expected: %s, got: %s", ErrIssuerInvalid, i.Issuer(), issuer)
func CheckIssuer(claims Claims, issuer string) error {
if claims.GetIssuer() != issuer {
return fmt.Errorf("%w: Expected: %s, got: %s", ErrIssuerInvalid, issuer, claims.GetIssuer())
}
return nil
}
func CheckAudience(audiences []string, i Verifier) error {
if !utils.Contains(audiences, i.ClientID()) {
return fmt.Errorf("%w: Audience must contain client_id %q", ErrAudience, i.ClientID())
func CheckAudience(claims Claims, clientID string) error {
if !utils.Contains(claims.GetAudience(), clientID) {
return fmt.Errorf("%w: Audience must contain client_id %q", ErrAudience, clientID)
}
//TODO: check aud trusted
return nil
}
//4. if multiple aud strings --> check if azp
//5. if azp --> check azp == client_id
func CheckAuthorizedParty(audiences []string, authorizedParty string, v Verifier) error {
if len(audiences) > 1 {
if authorizedParty == "" {
func CheckAuthorizedParty(claims Claims, clientID string) error {
if len(claims.GetAudience()) > 1 {
if claims.GetAuthorizedParty() == "" {
return ErrAzpMissing
}
}
if authorizedParty != "" && authorizedParty != v.ClientID() {
return fmt.Errorf("%w: azp %q must be equal to client_id %q", ErrAzpInvalid, authorizedParty, v.ClientID())
if claims.GetAuthorizedParty() != "" && claims.GetAuthorizedParty() != clientID {
return fmt.Errorf("%w: azp %q must be equal to client_id %q", ErrAzpInvalid, claims.GetAuthorizedParty(), clientID)
}
return nil
}
@ -151,59 +150,59 @@ func CheckSignature(ctx context.Context, token string, payload []byte, claims Cl
return nil
}
func CheckExpiration(expiration time.Time, v Verifier) error {
expiration = expiration.Round(time.Second)
if !time.Now().UTC().Add(v.Offset()).Before(expiration) {
func CheckExpiration(claims Claims, offset time.Duration) error {
expiration := claims.GetExpiration().Round(time.Second)
if !time.Now().UTC().Add(offset).Before(expiration) {
return ErrExpired
}
return nil
}
func CheckIssuedAt(issuedAt time.Time, v Verifier) error {
issuedAt = issuedAt.Round(time.Second)
offset := time.Now().UTC().Add(v.Offset()).Round(time.Second)
if issuedAt.After(offset) {
return fmt.Errorf("%w: (iat: %v, now with offset: %v)", ErrIatInFuture, issuedAt, offset)
func CheckIssuedAt(claims Claims, maxAgeIAT, offset time.Duration) error {
issuedAt := claims.GetIssuedAt().Round(time.Second)
nowWithOffset := time.Now().UTC().Add(offset).Round(time.Second)
if issuedAt.After(nowWithOffset) {
return fmt.Errorf("%w: (iat: %v, now with offset: %v)", ErrIatInFuture, issuedAt, nowWithOffset)
}
if v.MaxAgeIAT() == 0 {
if maxAgeIAT == 0 {
return nil
}
maxAge := time.Now().UTC().Add(-v.MaxAgeIAT()).Round(time.Second)
maxAge := time.Now().UTC().Add(-maxAgeIAT).Round(time.Second)
if issuedAt.Before(maxAge) {
return fmt.Errorf("%w: must not be older than %v, but was %v (%v to old)", ErrIatToOld, maxAge, issuedAt, maxAge.Sub(issuedAt))
}
return nil
}
/*
func (v *DefaultVerifier) CheckNonce(nonce string) error {
if v.config.nonce == "" {
func CheckNonce(claims Claims, nonce string) error {
if nonce == "" {
return nil
}
if v.config.nonce != nonce {
return ErrNonceInvalid(v.config.nonce, nonce)
if claims.GetNonce() != nonce {
return fmt.Errorf("%w: expected %q but was %q", ErrNonceInvalid, nonce, claims.GetNonce())
}
return nil
}*/
func CheckAuthorizationContextClassReference(acr string, v Verifier) error {
if v.ACR() != nil {
if err := v.ACR()(acr); err != nil {
}
func CheckAuthorizationContextClassReference(claims Claims, acr ACRVerifier) error {
if acr != nil {
if err := acr(claims.GetAuthenticationContextClassReference()); err != nil {
return fmt.Errorf("%w: %v", ErrAcrInvalid, err)
}
}
return nil
}
func CheckAuthTime(authTime time.Time, v Verifier) error {
if v.MaxAge() == 0 {
func CheckAuthTime(claims Claims, maxAge time.Duration) error {
if maxAge == 0 {
return nil
}
if authTime.IsZero() {
if claims.GetAuthTime().IsZero() {
return ErrAuthTimeNotPresent
}
authTime = authTime.Round(time.Second)
maxAge := time.Now().UTC().Add(-v.MaxAge()).Round(time.Second)
if authTime.Before(maxAge) {
return fmt.Errorf("%w: must not be older than %v, but was %v (%v to old)", ErrAuthTimeToOld, maxAge, authTime, maxAge.Sub(authTime))
authTime := claims.GetAuthTime().Round(time.Second)
maxAuthTime := time.Now().UTC().Add(-maxAge).Round(time.Second)
if authTime.Before(maxAuthTime) {
return fmt.Errorf("%w: must not be older than %v, but was %v (%v to old)", ErrAuthTimeToOld, maxAge, authTime, maxAuthTime.Sub(authTime))
}
return nil
}