Copyright 2020 Google LLC. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.

package idtoken

import (
	
	
	
	
	
	
	
	
	
	
	
	
	

	htransport 
)

const (
	es256KeySize      int    = 32
	googleIAPCertsURL string = "https://www.gstatic.com/iap/verify/public_key-jwk"
	googleSACertsURL  string = "https://www.googleapis.com/oauth2/v3/certs"
)

var (
now aliases time.Now for testing.
	now = time.Now
)
Payload represents a decoded payload of an ID Token.
type Payload struct {
	Issuer   string                 `json:"iss"`
	Audience string                 `json:"aud"`
	Expires  int64                  `json:"exp"`
	IssuedAt int64                  `json:"iat"`
	Subject  string                 `json:"sub,omitempty"`
	Claims   map[string]interface{} `json:"-"`
}
jwt represents the segments of a jwt and exposes convenience methods for working with the different segments.
jwtHeader represents a parted jwt's header segment.
type jwtHeader struct {
	Algorithm string `json:"alg"`
	Type      string `json:"typ"`
	KeyID     string `json:"kid"`
}
certResponse represents a list jwks. It is the format returned from known Google cert endpoints.
type certResponse struct {
	Keys []jwk `json:"keys"`
}
jwk is a simplified representation of a standard jwk. It only includes the fields used by Google's cert endpoints.
type jwk struct {
	Alg string `json:"alg"`
	Crv string `json:"crv"`
	Kid string `json:"kid"`
	Kty string `json:"kty"`
	Use string `json:"use"`
	E   string `json:"e"`
	N   string `json:"n"`
	X   string `json:"x"`
	Y   string `json:"y"`
}
Validator provides a way to validate Google ID Tokens with a user provided http.Client.
type Validator struct {
	client *cachingClient
}
NewValidator creates a Validator that uses the options provided to configure a the internal http.Client that will be used to make requests to fetch JWKs.
func ( context.Context,  ...ClientOption) (*Validator, error) {
	, ,  := htransport.NewClient(, ...)
	if  != nil {
		return nil, 
	}
	return &Validator{client: newCachingClient()}, nil
}
Validate is used to validate the provided idToken with a known Google cert URL. If audience is not empty the audience claim of the Token is validated. Upon successful validation a parsed token Payload is returned allowing the caller to validate any additional claims.
func ( *Validator) ( context.Context,  string,  string) (*Payload, error) {
	return .validate(, , )
}
Validate is used to validate the provided idToken with a known Google cert URL. If audience is not empty the audience claim of the Token is validated. Upon successful validation a parsed token Payload is returned allowing the caller to validate any additional claims.
TODO(codyoss): consider adding a check revoked version of the api. See: https://pkg.go.dev/firebase.google.com/go/auth?tab=doc#Client.VerifyIDTokenAndCheckRevoked
	return defaultValidator.validate(, , )
}

func ( *Validator) ( context.Context,  string,  string) (*Payload, error) {
	,  := parseJWT()
	if  != nil {
		return nil, 
	}
	,  := .parsedHeader()
	if  != nil {
		return nil, 
	}
	,  := .parsedPayload()
	if  != nil {
		return nil, 
	}
	,  := .decodedSignature()
	if  != nil {
		return nil, 
	}

	if  != "" && .Audience !=  {
		return nil, fmt.Errorf("idtoken: audience provided does not match aud claim in the JWT")
	}

	if now().Unix() > .Expires {
		return nil, fmt.Errorf("idtoken: token expired")
	}

	switch .Algorithm {
	case "RS256":
		if  := .validateRS256(, .KeyID, .hashedContent(), );  != nil {
			return nil, 
		}
	case "ES256":
		if  := .validateES256(, .KeyID, .hashedContent(), );  != nil {
			return nil, 
		}
	default:
		return nil, fmt.Errorf("idtoken: expected JWT signed with RS256 or ES256 but found %q", .Algorithm)
	}

	return , nil
}

func ( *Validator) ( context.Context,  string,  []byte,  []byte) error {
	,  := .client.getCert(, googleSACertsURL)
	if  != nil {
		return 
	}
	,  := findMatchingKey(, )
	if  != nil {
		return 
	}
	,  := decode(.N)
	if  != nil {
		return 
	}
	,  := decode(.E)
	if  != nil {
		return 
	}

	 := &rsa.PublicKey{
		N: new(big.Int).SetBytes(),
		E: int(new(big.Int).SetBytes().Int64()),
	}
	return rsa.VerifyPKCS1v15(, crypto.SHA256, , )
}

func ( *Validator) ( context.Context,  string,  []byte,  []byte) error {
	,  := .client.getCert(, googleIAPCertsURL)
	if  != nil {
		return 
	}
	,  := findMatchingKey(, )
	if  != nil {
		return 
	}
	,  := decode(.X)
	if  != nil {
		return 
	}
	,  := decode(.Y)
	if  != nil {
		return 
	}

	 := &ecdsa.PublicKey{
		Curve: elliptic.P256(),
		X:     new(big.Int).SetBytes(),
		Y:     new(big.Int).SetBytes(),
	}
	 := big.NewInt(0).SetBytes([:es256KeySize])
	 := big.NewInt(0).SetBytes([es256KeySize:])
	if  := ecdsa.Verify(, , , ); ! {
		return fmt.Errorf("idtoken: ES256 signature not valid")
	}
	return nil
}

func ( *certResponse,  string) (*jwk, error) {
	if  == nil {
		return nil, fmt.Errorf("idtoken: cert response is nil")
	}
	for ,  := range .Keys {
		if .Kid ==  {
			return &, nil
		}
	}
	return nil, fmt.Errorf("idtoken: could not find matching cert keyId for the token provided")
}

func ( string) (*jwt, error) {
	 := strings.Split(, ".")
	if len() != 3 {
		return nil, fmt.Errorf("idtoken: invalid token, token must have three segments; found %d", len())
	}
	return &jwt{
		header:    [0],
		payload:   [1],
		signature: [2],
	}, nil
}
decodedHeader base64 decodes the header segment.
func ( *jwt) () ([]byte, error) {
	,  := decode(.header)
	if  != nil {
		return nil, fmt.Errorf("idtoken: unable to decode JWT header: %v", )
	}
	return , nil
}
decodedPayload base64 payload the header segment.
func ( *jwt) () ([]byte, error) {
	,  := decode(.payload)
	if  != nil {
		return nil, fmt.Errorf("idtoken: unable to decode JWT payload: %v", )
	}
	return , nil
}
decodedPayload base64 payload the header segment.
func ( *jwt) () ([]byte, error) {
	,  := decode(.signature)
	if  != nil {
		return nil, fmt.Errorf("idtoken: unable to decode JWT signature: %v", )
	}
	return , nil
}
parsedHeader returns a struct representing a JWT header.
func ( *jwt) () (jwtHeader, error) {
	var  jwtHeader
	,  := .decodedHeader()
	if  != nil {
		return , 
	}
	 = json.Unmarshal(, &)
	if  != nil {
		return , fmt.Errorf("idtoken: unable to unmarshal JWT header: %v", )
	}
	return , nil
}
parsedPayload returns a struct representing a JWT payload.
func ( *jwt) () (*Payload, error) {
	var  Payload
	,  := .decodedPayload()
	if  != nil {
		return nil, 
	}
	if  := json.Unmarshal(, &);  != nil {
		return nil, fmt.Errorf("idtoken: unable to unmarshal JWT payload: %v", )
	}
	if  := json.Unmarshal(, &.Claims);  != nil {
		return nil, fmt.Errorf("idtoken: unable to unmarshal JWT payload claims: %v", )
	}
	return &, nil
}
hashedContent gets the SHA256 checksum for verification of the JWT.
func ( *jwt) () []byte {
	 := .header + "." + .payload
	 := sha256.Sum256([]byte())
	return [:]
}

func ( *jwt) () string {
	return fmt.Sprintf("%s.%s.%s", .header, .payload, .signature)
}

func ( string) ([]byte, error) {
	return base64.RawURLEncoding.DecodeString()