From f77fa561933de0e372b71e8de2aae2f6741e2bbd Mon Sep 17 00:00:00 2001 From: Anders Eknert Date: Thu, 15 Aug 2019 19:35:16 +0200 Subject: [PATCH] Honour nbf claim if present in ID token --- oidc.go | 1 + verify.go | 13 ++++++++++++- verify_test.go | 28 ++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/oidc.go b/oidc.go index 15632bb7..508b39d3 100644 --- a/oidc.go +++ b/oidc.go @@ -321,6 +321,7 @@ type idToken struct { Audience audience `json:"aud"` Expiry jsonTime `json:"exp"` IssuedAt jsonTime `json:"iat"` + NotBefore *jsonTime `json:"nbf"` Nonce string `json:"nonce"` AtHash string `json:"at_hash"` ClaimNames map[string]string `json:"_claim_names"` diff --git a/verify.go b/verify.go index bb8a3b6f..ff7555db 100644 --- a/verify.go +++ b/verify.go @@ -270,10 +270,21 @@ func (v *IDTokenVerifier) Verify(ctx context.Context, rawIDToken string) (*IDTok if v.config.Now != nil { now = v.config.Now } + nowTime := now() - if t.Expiry.Before(now()) { + if t.Expiry.Before(nowTime) { return nil, fmt.Errorf("oidc: token is expired (Token Expiry: %v)", t.Expiry) } + + // If nbf claim is provided in token, ensure that it is indeed in the past. + if token.NotBefore != nil { + nbfTime := time.Time(*token.NotBefore) + leeway := 1 * time.Minute + + if nowTime.Add(leeway).Before(nbfTime) { + return nil, fmt.Errorf("oidc: current time %v before the nbf (not before) time: %v", nowTime, nbfTime) + } + } } switch len(jws.Signatures) { diff --git a/verify_test.go b/verify_test.go index 5eaacac2..d2fffa9c 100644 --- a/verify_test.go +++ b/verify_test.go @@ -107,6 +107,34 @@ func TestVerify(t *testing.T) { }, signKey: newRSAKey(t), }, + { + name: "nbf in future", + idToken: `{"iss":"https://foo","nbf":` + strconv.FormatInt(time.Now().Add(time.Hour).Unix(), 10) + + `,"exp":` + strconv.FormatInt(time.Now().Add(time.Hour).Unix(), 10) + `}`, + config: Config{ + SkipClientIDCheck: true, + }, + signKey: newRSAKey(t), + wantErr: true, + }, + { + name: "nbf in past", + idToken: `{"iss":"https://foo","nbf":` + strconv.FormatInt(time.Now().Add(-time.Hour).Unix(), 10) + + `,"exp":` + strconv.FormatInt(time.Now().Add(time.Hour).Unix(), 10) + `}`, + config: Config{ + SkipClientIDCheck: true, + }, + signKey: newRSAKey(t), + }, + { + name: "nbf in future within clock skew tolerance", + idToken: `{"iss":"https://foo","nbf":` + strconv.FormatInt(time.Now().Add(30*time.Second).Unix(), 10) + + `,"exp":` + strconv.FormatInt(time.Now().Add(time.Hour).Unix(), 10) + `}`, + config: Config{ + SkipClientIDCheck: true, + }, + signKey: newRSAKey(t), + }, } for _, test := range tests { t.Run(test.name, test.run)