Skip to content

Commit 9f79781

Browse files
authored
Add a a 5min tolerance to clock skew (#211)
1 parent b649b67 commit 9f79781

File tree

2 files changed

+51
-10
lines changed

2 files changed

+51
-10
lines changed

auth/auth.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,14 @@ const (
3333
idTokenCertURL = "https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com"
3434
issuerPrefix = "https://securetoken.google.com/"
3535
tokenExpSeconds = 3600
36+
clockSkewSeconds = 300
3637
)
3738

3839
var reservedClaims = []string{
3940
"acr", "amr", "at_hash", "aud", "auth_time", "azp", "cnf", "c_hash",
4041
"exp", "firebase", "iat", "iss", "jti", "nbf", "nonce", "sub",
4142
}
4243

43-
var clk clock = &systemClock{}
44-
4544
// Token represents a decoded Firebase ID token.
4645
//
4746
// Token provides typed accessors to the common JWT fields such as Audience (aud) and Expiry (exp).
@@ -67,6 +66,7 @@ type Client struct {
6766
projectID string
6867
signer cryptoSigner
6968
version string
69+
clock clock
7070
}
7171

7272
type signer interface {
@@ -136,6 +136,7 @@ func NewClient(ctx context.Context, conf *internal.AuthConfig) (*Client, error)
136136
projectID: conf.ProjectID,
137137
signer: signer,
138138
version: "Go/Admin/" + conf.Version,
139+
clock: &systemClock{},
139140
}, nil
140141
}
141142

@@ -186,7 +187,7 @@ func (c *Client) CustomTokenWithClaims(ctx context.Context, uid string, devClaim
186187
return "", fmt.Errorf("developer claims %q are reserved and cannot be specified", strings.Join(disallowed, ", "))
187188
}
188189

189-
now := clk.Now().Unix()
190+
now := c.clock.Now().Unix()
190191
info := &jwtInfo{
191192
header: jwtHeader{Algorithm: "RS256", Type: "JWT"},
192193
payload: &customToken{
@@ -262,9 +263,9 @@ func (c *Client) VerifyIDToken(ctx context.Context, idToken string) (*Token, err
262263
} else if payload.Issuer != issuer {
263264
err = fmt.Errorf("ID token has invalid 'iss' (issuer) claim; expected %q but got %q; %s; %s",
264265
issuer, payload.Issuer, projectIDMsg, verifyTokenMsg)
265-
} else if payload.IssuedAt > clk.Now().Unix() {
266+
} else if (payload.IssuedAt - clockSkewSeconds) > c.clock.Now().Unix() {
266267
err = fmt.Errorf("ID token issued at future timestamp: %d", payload.IssuedAt)
267-
} else if payload.Expires < clk.Now().Unix() {
268+
} else if (payload.Expires + clockSkewSeconds) < c.clock.Now().Unix() {
268269
err = fmt.Errorf("ID token has expired at: %d", payload.Expires)
269270
} else if payload.Subject == "" {
270271
err = fmt.Errorf("ID token has empty 'sub' (subject) claim; %s", verifyTokenMsg)

auth/auth_test.go

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ var (
4444
var defaultTestOpts = []option.ClientOption{
4545
option.WithCredentialsFile("../testdata/service_account.json"),
4646
}
47+
var testClock = &mockClock{now: time.Now()}
4748

4849
func TestMain(m *testing.M) {
4950
var (
@@ -74,6 +75,7 @@ func TestMain(m *testing.M) {
7475

7576
ks = &fileKeySource{FilePath: "../testdata/public_certs.json"}
7677
}
78+
7779
client, err = NewClient(ctx, &internal.AuthConfig{
7880
Creds: creds,
7981
Opts: opts,
@@ -83,6 +85,7 @@ func TestMain(m *testing.M) {
8385
log.Fatalln(err)
8486
}
8587
client.keySource = ks
88+
client.clock = testClock
8689

8790
testGetUserResponse, err = ioutil.ReadFile("../testdata/get_user.json")
8891
if err != nil {
@@ -289,6 +292,35 @@ func TestVerifyIDToken(t *testing.T) {
289292
}
290293
}
291294

295+
func TestVerifyIDTokenClockSkew(t *testing.T) {
296+
now := testClock.Now().Unix()
297+
cases := []struct {
298+
name string
299+
token string
300+
}{
301+
{"FutureToken", getIDToken(mockIDTokenPayload{"iat": now + clockSkewSeconds - 1})},
302+
{"ExpiredToken", getIDToken(mockIDTokenPayload{
303+
"iat": now - 1000,
304+
"exp": now - clockSkewSeconds + 1,
305+
})},
306+
}
307+
308+
for _, tc := range cases {
309+
t.Run(tc.name, func(t *testing.T) {
310+
ft, err := client.VerifyIDToken(ctx, tc.token)
311+
if err != nil {
312+
t.Errorf("VerifyIDToken(%q) = (%q, %v); want = (token, nil)", tc.name, ft, err)
313+
}
314+
if ft.Claims["admin"] != true {
315+
t.Errorf("Claims['admin'] = %v; want = true", ft.Claims["admin"])
316+
}
317+
if ft.UID != ft.Subject {
318+
t.Errorf("UID = %q; Sub = %q; want UID = Sub", ft.UID, ft.Subject)
319+
}
320+
})
321+
}
322+
}
323+
292324
func TestVerifyIDTokenInvalidSignature(t *testing.T) {
293325
parts := strings.Split(testIDToken, ".")
294326
token := fmt.Sprintf("%s:%s:invalidsignature", parts[0], parts[1])
@@ -298,7 +330,7 @@ func TestVerifyIDTokenInvalidSignature(t *testing.T) {
298330
}
299331

300332
func TestVerifyIDTokenError(t *testing.T) {
301-
now := time.Now().Unix()
333+
now := testClock.Now().Unix()
302334
cases := []struct {
303335
name string
304336
token string
@@ -310,10 +342,10 @@ func TestVerifyIDTokenError(t *testing.T) {
310342
{"EmptySubject", getIDToken(mockIDTokenPayload{"sub": ""})},
311343
{"IntSubject", getIDToken(mockIDTokenPayload{"sub": 10})},
312344
{"LongSubject", getIDToken(mockIDTokenPayload{"sub": strings.Repeat("a", 129)})},
313-
{"FutureToken", getIDToken(mockIDTokenPayload{"iat": now + 1000})},
345+
{"FutureToken", getIDToken(mockIDTokenPayload{"iat": now + clockSkewSeconds + 1})},
314346
{"ExpiredToken", getIDToken(mockIDTokenPayload{
315347
"iat": now - 1000,
316-
"exp": now - 100,
348+
"exp": now - clockSkewSeconds - 1,
317349
})},
318350
{"EmptyToken", ""},
319351
{"BadFormatToken", "foobar"},
@@ -419,6 +451,14 @@ func verifyCustomToken(ctx context.Context, token string, expected map[string]in
419451
t.Errorf("Subject: %q; want: %q", payload.Sub, email)
420452
}
421453

454+
now := testClock.Now().Unix()
455+
if payload.Exp != now+3600 {
456+
t.Errorf("Exp: %d; want: %d", payload.Exp, now+3600)
457+
}
458+
if payload.Iat != now {
459+
t.Errorf("Iat: %d; want: %d", payload.Iat, now)
460+
}
461+
422462
for k, v := range expected {
423463
if payload.Claims[k] != v {
424464
t.Errorf("Claim[%q]: %v; want: %v", k, payload.Claims[k], v)
@@ -434,8 +474,8 @@ func getIDTokenWithKid(kid string, p mockIDTokenPayload) string {
434474
pCopy := mockIDTokenPayload{
435475
"aud": client.projectID,
436476
"iss": "https://securetoken.google.com/" + client.projectID,
437-
"iat": time.Now().Unix() - 100,
438-
"exp": time.Now().Unix() + 3600,
477+
"iat": testClock.Now().Unix() - 100,
478+
"exp": testClock.Now().Unix() + 3600,
439479
"sub": "1234567890",
440480
"admin": true,
441481
}

0 commit comments

Comments
 (0)