// Copyright 2017 The OPA Authors.  All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.

package topdown

import (
	"crypto"
	"crypto/ecdsa"
	"crypto/hmac"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/x509"
	"encoding/hex"
	"encoding/json"
	"encoding/pem"
	"fmt"
	"math/big"
	"strconv"
	"strings"
	"time"

	"github.com/pkg/errors"

	"github.com/open-policy-agent/opa/ast"
	"github.com/open-policy-agent/opa/topdown/builtins"
	"github.com/open-policy-agent/opa/topdown/internal/jwx/jwk"
	"github.com/open-policy-agent/opa/topdown/internal/jwx/jws"
)

var (
	jwtEncKey = ast.StringTerm("enc")
	jwtCtyKey = ast.StringTerm("cty")
	jwtAlgKey = ast.StringTerm("alg")
	jwtIssKey = ast.StringTerm("iss")
	jwtExpKey = ast.StringTerm("exp")
	jwtNbfKey = ast.StringTerm("nbf")
	jwtAudKey = ast.StringTerm("aud")
)

// JSONWebToken represent the 3 parts (header, payload & signature) of
//              a JWT in Base64.
type JSONWebToken struct {
	header        string
	payload       string
	signature     string
	decodedHeader ast.Object
}

// decodeHeader populates the decodedHeader field.
func (token *JSONWebToken) decodeHeader() (err error) {
	var h ast.Value
	if h, err = builtinBase64UrlDecode(ast.String(token.header)); err != nil {
		return fmt.Errorf("JWT header had invalid encoding: %v", err)
	}
	if token.decodedHeader, err = validateJWTHeader(string(h.(ast.String))); err != nil {
		return err
	}
	return
}

// Implements JWT decoding/validation based on RFC 7519 Section 7.2:
// https://tools.ietf.org/html/rfc7519#section-7.2
// It does no data validation, it merely checks that the given string
// represents a structurally valid JWT. It supports JWTs using JWS compact
// serialization.
func builtinJWTDecode(a ast.Value) (ast.Value, error) {
	token, err := decodeJWT(a)
	if err != nil {
		return nil, err
	}

	if err = token.decodeHeader(); err != nil {
		return nil, err
	}

	p, err := builtinBase64UrlDecode(ast.String(token.payload))
	if err != nil {
		return nil, fmt.Errorf("JWT payload had invalid encoding: %v", err)
	}

	if cty := token.decodedHeader.Get(jwtCtyKey); cty != nil {
		ctyVal := string(cty.Value.(ast.String))
		// It is possible for the contents of a token to be another
		// token as a result of nested signing or encryption. To handle
		// the case where we are given a token such as this, we check
		// the content type and recurse on the payload if the content
		// is "JWT".
		// When the payload is itself another encoded JWT, then its
		// contents are quoted (behavior of https://jwt.io/). To fix
		// this, remove leading and trailing quotes.
		if ctyVal == "JWT" {
			p, err = builtinTrim(p, ast.String(`"'`))
			if err != nil {
				panic("not reached")
			}
			return builtinJWTDecode(p)
		}
	}

	payload, err := extractJSONObject(string(p.(ast.String)))
	if err != nil {
		return nil, err
	}

	s, err := builtinBase64UrlDecode(ast.String(token.signature))
	if err != nil {
		return nil, fmt.Errorf("JWT signature had invalid encoding: %v", err)
	}
	sign := hex.EncodeToString([]byte(s.(ast.String)))

	arr := make(ast.Array, 3)
	arr[0] = ast.NewTerm(token.decodedHeader)
	arr[1] = ast.NewTerm(payload)
	arr[2] = ast.StringTerm(sign)

	return arr, nil
}

// Implements RS256 JWT signature verification
func builtinJWTVerifyRS256(a ast.Value, b ast.Value) (ast.Value, error) {
	return builtinJWTVerifyRSA(a, b, func(publicKey *rsa.PublicKey, digest []byte, signature []byte) error {
		return rsa.VerifyPKCS1v15(
			publicKey,
			crypto.SHA256,
			digest,
			signature)
	})
}

// Implements PS256 JWT signature verification
func builtinJWTVerifyPS256(a ast.Value, b ast.Value) (ast.Value, error) {
	return builtinJWTVerifyRSA(a, b, func(publicKey *rsa.PublicKey, digest []byte, signature []byte) error {
		return rsa.VerifyPSS(
			publicKey,
			crypto.SHA256,
			digest,
			signature,
			nil)
	})
}

// Implements RSA JWT signature verification.
func builtinJWTVerifyRSA(a ast.Value, b ast.Value, verify func(publicKey *rsa.PublicKey, digest []byte, signature []byte) error) (ast.Value, error) {
	return builtinJWTVerify(a, b, func(publicKey interface{}, digest []byte, signature []byte) error {
		publicKeyRsa, ok := publicKey.(*rsa.PublicKey)
		if !ok {
			return fmt.Errorf("incorrect public key type")
		}
		return verify(publicKeyRsa, digest, signature)
	})
}

// Implements ES256 JWT signature verification.
func builtinJWTVerifyES256(a ast.Value, b ast.Value) (ast.Value, error) {
	return builtinJWTVerify(a, b, func(publicKey interface{}, digest []byte, signature []byte) error {
		publicKeyEcdsa, ok := publicKey.(*ecdsa.PublicKey)
		if !ok {
			return fmt.Errorf("incorrect public key type")
		}
		r, s := &big.Int{}, &big.Int{}
		n := len(signature) / 2
		r.SetBytes(signature[:n])
		s.SetBytes(signature[n:])
		if ecdsa.Verify(publicKeyEcdsa, digest, r, s) {
			return nil
		}
		return fmt.Errorf("ECDSA signature verification error")
	})
}

// getKeyFromCertOrJWK returns the public key found in a X.509 certificate or JWK key(s).
// A valid PEM block is never valid JSON (and vice versa), hence can try parsing both.
func getKeyFromCertOrJWK(certificate string) ([]interface{}, error) {
	if block, rest := pem.Decode([]byte(certificate)); block != nil {
		if len(rest) > 0 {
			return nil, fmt.Errorf("extra data after a PEM certificate block")
		}

		if block.Type == "CERTIFICATE" {
			cert, err := x509.ParseCertificate(block.Bytes)
			if err != nil {
				return nil, errors.Wrap(err, "failed to parse a PEM certificate")
			}

			return []interface{}{cert.PublicKey}, nil
		}

		if block.Type == "PUBLIC KEY" {
			key, err := x509.ParsePKIXPublicKey(block.Bytes)
			if err != nil {
				return nil, errors.Wrap(err, "failed to parse a PEM public key")
			}

			return []interface{}{key}, nil
		}

		return nil, fmt.Errorf("failed to extract a Key from the PEM certificate")
	}

	jwks, err := jwk.ParseString(certificate)
	if err != nil {
		return nil, errors.Wrap(err, "failed to parse a JWK key (set)")
	}

	var keys []interface{}
	for _, k := range jwks.Keys {
		key, err := k.Materialize()
		if err != nil {
			return nil, err
		}
		keys = append(keys, key)
	}

	return keys, nil
}

// Implements JWT signature verification.
func builtinJWTVerify(a ast.Value, b ast.Value, verify func(publicKey interface{}, digest []byte, signature []byte) error) (ast.Value, error) {
	token, err := decodeJWT(a)
	if err != nil {
		return nil, err
	}

	s, err := builtins.StringOperand(b, 2)
	if err != nil {
		return nil, err
	}

	keys, err := getKeyFromCertOrJWK(string(s))
	if err != nil {
		return nil, err
	}

	signature, err := token.decodeSignature()
	if err != nil {
		return nil, err
	}

	// Validate the JWT signature
	for _, key := range keys {
		err = verify(key,
			getInputSHA([]byte(token.header+"."+token.payload)),
			[]byte(signature))

		if err == nil {
			return ast.Boolean(true), nil
		}
	}

	// None of the keys worked, return false
	return ast.Boolean(false), nil
}

// Implements HS256 (secret) JWT signature verification
func builtinJWTVerifyHS256(a ast.Value, b ast.Value) (ast.Value, error) {
	// Decode the JSON Web Token
	token, err := decodeJWT(a)
	if err != nil {
		return nil, err
	}

	// Process Secret input
	astSecret, err := builtins.StringOperand(b, 2)
	if err != nil {
		return nil, err
	}
	secret := string(astSecret)

	mac := hmac.New(sha256.New, []byte(secret))
	_, err = mac.Write([]byte(token.header + "." + token.payload))
	if err != nil {
		return nil, err
	}

	signature, err := token.decodeSignature()
	if err != nil {
		return nil, err
	}

	return ast.Boolean(hmac.Equal([]byte(signature), mac.Sum(nil))), nil
}

// -- Full JWT verification and decoding --

// Verification constraints. See tokens_test.go for unit tests.

// tokenConstraints holds decoded JWT verification constraints.
type tokenConstraints struct {
	// The set of asymmetric keys we can verify with.
	keys []interface{}

	// The single symmetric key we will verify with.
	secret string

	// The algorithm that must be used to verify.
	// If "", any algorithm is acceptable.
	alg string

	// The required issuer.
	// If "", any issuer is acceptable.
	iss string

	// The required audience.
	// If "", no audience is acceptable.
	aud string

	// The time to validate against, or -1 if no constraint set.
	// (If unset, the current time will be used.)
	time int64
}

// tokenConstraintHandler is the handler type for JWT verification constraints.
type tokenConstraintHandler func(value ast.Value, parameters *tokenConstraints) (err error)

// tokenConstraintTypes maps known JWT verification constraints to handlers.
var tokenConstraintTypes = map[string]tokenConstraintHandler{
	"cert": tokenConstraintCert,
	"secret": func(value ast.Value, constraints *tokenConstraints) (err error) {
		return tokenConstraintString("secret", value, &constraints.secret)
	},
	"alg": func(value ast.Value, constraints *tokenConstraints) (err error) {
		return tokenConstraintString("alg", value, &constraints.alg)
	},
	"iss": func(value ast.Value, constraints *tokenConstraints) (err error) {
		return tokenConstraintString("iss", value, &constraints.iss)
	},
	"aud": func(value ast.Value, constraints *tokenConstraints) (err error) {
		return tokenConstraintString("aud", value, &constraints.aud)
	},
	"time": tokenConstraintTime,
}

// tokenConstraintCert handles the `cert` constraint.
func tokenConstraintCert(value ast.Value, constraints *tokenConstraints) (err error) {
	var s ast.String
	var ok bool
	if s, ok = value.(ast.String); !ok {
		return fmt.Errorf("cert constraint: must be a string")
	}

	constraints.keys, err = getKeyFromCertOrJWK(string(s))
	return
}

// tokenConstraintTime handles the `time` constraint.
func tokenConstraintTime(value ast.Value, constraints *tokenConstraints) (err error) {
	var time ast.Number
	var ok bool
	if time, ok = value.(ast.Number); !ok {
		err = fmt.Errorf("token time constraint: must be a number")
		return
	}
	var timeFloat float64
	if timeFloat, err = strconv.ParseFloat(string(time), 64); err != nil {
		err = fmt.Errorf("token time constraint: %v", err)
		return
	}
	if timeFloat < 0 {
		err = fmt.Errorf("token time constraint: must not be negative")
		return
	}
	constraints.time = int64(timeFloat)
	return
}

// tokenConstraintString handles string constraints.
func tokenConstraintString(name string, value ast.Value, where *string) (err error) {
	var av ast.String
	var ok bool
	if av, ok = value.(ast.String); !ok {
		err = fmt.Errorf("%s constraint: must be a string", name)
		return
	}
	*where = string(av)
	return
}

// parseTokenConstraints parses the constraints argument.
func parseTokenConstraints(a ast.Value) (constraints tokenConstraints, err error) {
	constraints.time = -1
	var o ast.Object
	var ok bool
	if o, ok = a.(ast.Object); !ok {
		err = fmt.Errorf("token constraints must be object")
		return
	}
	if err = o.Iter(func(k *ast.Term, v *ast.Term) (err error) {
		var handler tokenConstraintHandler
		var ok bool
		name := string(k.Value.(ast.String))
		if handler, ok = tokenConstraintTypes[name]; ok {
			if err = handler(v.Value, &constraints); err != nil {
				return
			}
		} else {
			// Anything unknown is rejected.
			err = fmt.Errorf("unknown token validation constraint: %s", name)
			return
		}
		return
	}); err != nil {
		return
	}
	return
}

// validate validates the constraints argument.
func (constraints *tokenConstraints) validate() (err error) {
	keys := 0
	if constraints.keys != nil {
		keys++
	}
	if constraints.secret != "" {
		keys++
	}
	if keys > 1 {
		err = fmt.Errorf("duplicate key constraints")
		return
	}
	if keys < 1 {
		err = fmt.Errorf("no key constraint")
		return
	}
	return
}

// verify verifies a JWT using the constraints and the algorithm from the header
func (constraints *tokenConstraints) verify(kid, alg, header, payload, signature string) error {
	// Construct the payload
	plaintext := []byte(header)
	plaintext = append(plaintext, []byte(".")...)
	plaintext = append(plaintext, payload...)
	// Look up the algorithm
	var ok bool
	var a tokenAlgorithm
	a, ok = tokenAlgorithms[alg]
	if !ok {
		return fmt.Errorf("unknown JWS algorithm: %s", alg)
	}
	// If we're configured with asymmetric key(s) then only trust that
	if constraints.keys != nil {
		verified := false
		for _, key := range constraints.keys {
			err := a.verify(key, a.hash, plaintext, []byte(signature))
			if err == nil {
				verified = true
				break
			}
		}
		if !verified {
			return errSignatureNotVerified
		}
		return nil
	}
	if constraints.secret != "" {
		return a.verify([]byte(constraints.secret), a.hash, plaintext, []byte(signature))
	}
	// (*tokenConstraints)validate() should prevent this happening
	return errors.New("unexpectedly found no keys to trust")
}

// validAudience checks the audience of the JWT.
// It returns true if it meets the constraints and false otherwise.
func (constraints *tokenConstraints) validAudience(aud ast.Value) (valid bool) {
	var ok bool
	var s ast.String
	if s, ok = aud.(ast.String); ok {
		return string(s) == constraints.aud
	}
	var a ast.Array
	if a, ok = aud.(ast.Array); ok {
		for _, t := range a {
			if s, ok = t.Value.(ast.String); ok {
				if string(s) == constraints.aud {
					return true
				}
			} else {
				// Ill-formed aud claim
				return false
			}
		}
	}
	return false
}

// JWT algorithms

type tokenVerifyFunction func(key interface{}, hash crypto.Hash, payload []byte, signature []byte) (err error)
type tokenVerifyAsymmetricFunction func(key interface{}, hash crypto.Hash, digest []byte, signature []byte) (err error)

// jwtAlgorithm describes a JWS 'alg' value
type tokenAlgorithm struct {
	hash   crypto.Hash
	verify tokenVerifyFunction
}

// tokenAlgorithms is the known JWT algorithms
var tokenAlgorithms = map[string]tokenAlgorithm{
	"RS256": {crypto.SHA256, verifyAsymmetric(verifyRSAPKCS)},
	"RS384": {crypto.SHA384, verifyAsymmetric(verifyRSAPKCS)},
	"RS512": {crypto.SHA512, verifyAsymmetric(verifyRSAPKCS)},
	"PS256": {crypto.SHA256, verifyAsymmetric(verifyRSAPSS)},
	"PS384": {crypto.SHA384, verifyAsymmetric(verifyRSAPSS)},
	"PS512": {crypto.SHA512, verifyAsymmetric(verifyRSAPSS)},
	"ES256": {crypto.SHA256, verifyAsymmetric(verifyECDSA)},
	"ES384": {crypto.SHA384, verifyAsymmetric(verifyECDSA)},
	"ES512": {crypto.SHA512, verifyAsymmetric(verifyECDSA)},
	"HS256": {crypto.SHA256, verifyHMAC},
	"HS384": {crypto.SHA384, verifyHMAC},
	"HS512": {crypto.SHA512, verifyHMAC},
}

// errSignatureNotVerified is returned when a signature cannot be verified.
var errSignatureNotVerified = errors.New("signature not verified")

func verifyHMAC(key interface{}, hash crypto.Hash, payload []byte, signature []byte) (err error) {
	macKey, ok := key.([]byte)
	if !ok {
		return fmt.Errorf("incorrect symmetric key type")
	}
	mac := hmac.New(hash.New, macKey)
	if _, err = mac.Write([]byte(payload)); err != nil {
		return
	}
	if !hmac.Equal(signature, mac.Sum([]byte{})) {
		err = errSignatureNotVerified
	}
	return
}

func verifyAsymmetric(verify tokenVerifyAsymmetricFunction) tokenVerifyFunction {
	return func(key interface{}, hash crypto.Hash, payload []byte, signature []byte) (err error) {
		h := hash.New()
		h.Write(payload)
		return verify(key, hash, h.Sum([]byte{}), signature)
	}
}

func verifyRSAPKCS(key interface{}, hash crypto.Hash, digest []byte, signature []byte) (err error) {
	publicKeyRsa, ok := key.(*rsa.PublicKey)
	if !ok {
		return fmt.Errorf("incorrect public key type")
	}
	if err = rsa.VerifyPKCS1v15(publicKeyRsa, hash, digest, signature); err != nil {
		err = errSignatureNotVerified
	}
	return
}

func verifyRSAPSS(key interface{}, hash crypto.Hash, digest []byte, signature []byte) (err error) {
	publicKeyRsa, ok := key.(*rsa.PublicKey)
	if !ok {
		return fmt.Errorf("incorrect public key type")
	}
	if err = rsa.VerifyPSS(publicKeyRsa, hash, digest, signature, nil); err != nil {
		err = errSignatureNotVerified
	}
	return
}

func verifyECDSA(key interface{}, hash crypto.Hash, digest []byte, signature []byte) (err error) {
	publicKeyEcdsa, ok := key.(*ecdsa.PublicKey)
	if !ok {
		return fmt.Errorf("incorrect public key type")
	}
	r, s := &big.Int{}, &big.Int{}
	n := len(signature) / 2
	r.SetBytes(signature[:n])
	s.SetBytes(signature[n:])
	if ecdsa.Verify(publicKeyEcdsa, digest, r, s) {
		return nil
	}
	return errSignatureNotVerified
}

// JWT header parsing and parameters. See tokens_test.go for unit tests.

// tokenHeaderType represents a recognized JWT header field
// tokenHeader is a parsed JWT header
type tokenHeader struct {
	alg     string
	kid     string
	typ     string
	cty     string
	crit    map[string]bool
	unknown []string
}

// tokenHeaderHandler handles a JWT header parameters
type tokenHeaderHandler func(header *tokenHeader, value ast.Value) (err error)

// tokenHeaderTypes maps known JWT header parameters to handlers
var tokenHeaderTypes = map[string]tokenHeaderHandler{
	"alg": func(header *tokenHeader, value ast.Value) (err error) {
		return tokenHeaderString("alg", &header.alg, value)
	},
	"kid": func(header *tokenHeader, value ast.Value) (err error) {
		return tokenHeaderString("kid", &header.kid, value)
	},
	"typ": func(header *tokenHeader, value ast.Value) (err error) {
		return tokenHeaderString("typ", &header.typ, value)
	},
	"cty": func(header *tokenHeader, value ast.Value) (err error) {
		return tokenHeaderString("cty", &header.cty, value)
	},
	"crit": tokenHeaderCrit,
}

// tokenHeaderCrit handles the 'crit' header parameter
func tokenHeaderCrit(header *tokenHeader, value ast.Value) (err error) {
	var ok bool
	var v ast.Array
	if v, ok = value.(ast.Array); !ok {
		err = fmt.Errorf("crit: must be a list")
		return
	}
	header.crit = map[string]bool{}
	for _, t := range v {
		var tv ast.String
		if tv, ok = t.Value.(ast.String); !ok {
			err = fmt.Errorf("crit: must be a list of strings")
			return
		}
		header.crit[string(tv)] = true
	}
	if len(header.crit) == 0 {
		err = fmt.Errorf("crit: must be a nonempty list") // 'MUST NOT' use the empty list
		return
	}
	return
}

// tokenHeaderString handles string-format JWT header parameters
func tokenHeaderString(name string, where *string, value ast.Value) (err error) {
	var ok bool
	var v ast.String
	if v, ok = value.(ast.String); !ok {
		err = fmt.Errorf("%s: must be a string", name)
		return
	}
	*where = string(v)
	return
}

// parseTokenHeader parses the JWT header.
func parseTokenHeader(token *JSONWebToken) (header tokenHeader, err error) {
	header.unknown = []string{}
	if err = token.decodedHeader.Iter(func(k *ast.Term, v *ast.Term) (err error) {
		ks := string(k.Value.(ast.String))
		var ok bool
		var handler tokenHeaderHandler
		if handler, ok = tokenHeaderTypes[ks]; ok {
			if err = handler(&header, v.Value); err != nil {
				return
			}
		} else {
			header.unknown = append(header.unknown, ks)
		}
		return
	}); err != nil {
		return
	}
	return
}

// validTokenHeader returns true if the JOSE header is valid, otherwise false.
func (header *tokenHeader) valid() bool {
	// RFC7515 s4.1.1 alg MUST be present
	if header.alg == "" {
		return false
	}
	// RFC7515 4.1.11 JWS is invalid if there is a critical parameter that we did not recognize
	for _, u := range header.unknown {
		if header.crit[u] {
			return false
		}
	}
	return true
}

func commonBuiltinJWTEncodeSign(inputHeaders, jwsPayload, jwkSrc string) (v ast.Value, err error) {

	keys, err := jwk.ParseString(jwkSrc)
	if err != nil {
		return nil, err
	}
	key, err := keys.Keys[0].Materialize()
	if err != nil {
		return nil, err
	}
	if jwk.GetKeyTypeFromKey(key) != keys.Keys[0].GetKeyType() {
		return nil, fmt.Errorf("JWK derived key type and keyType parameter do not match")
	}

	standardHeaders := &jws.StandardHeaders{}
	jwsHeaders := []byte(inputHeaders)
	err = json.Unmarshal(jwsHeaders, standardHeaders)
	if err != nil {
		return nil, err
	}
	alg := standardHeaders.GetAlgorithm()

	if (standardHeaders.Type == "" || standardHeaders.Type == "JWT") && !json.Valid([]byte(jwsPayload)) {
		return nil, fmt.Errorf("type is JWT but payload is not JSON")
	}

	// process payload and sign
	var jwsCompact []byte
	jwsCompact, err = jws.SignLiteral([]byte(jwsPayload), alg, key, jwsHeaders)
	if err != nil {
		return nil, err
	}
	return ast.String(jwsCompact[:]), nil

}

func builtinJWTEncodeSign(a ast.Value, b ast.Value, c ast.Value) (v ast.Value, err error) {

	jwkSrc := c.String()

	inputHeaders := a.String()

	jwsPayload := b.String()

	return commonBuiltinJWTEncodeSign(inputHeaders, jwsPayload, jwkSrc)

}

func builtinJWTEncodeSignRaw(a ast.Value, b ast.Value, c ast.Value) (v ast.Value, err error) {

	jwkSrc, err := builtins.StringOperand(c, 1)
	if err != nil {
		return nil, err
	}
	inputHeaders, err := builtins.StringOperand(a, 1)
	if err != nil {
		return nil, err
	}
	jwsPayload, err := builtins.StringOperand(b, 1)
	if err != nil {
		return nil, err
	}
	return commonBuiltinJWTEncodeSign(string(inputHeaders), string(jwsPayload), string(jwkSrc))
}

// Implements full JWT decoding, validation and verification.
func builtinJWTDecodeVerify(a ast.Value, b ast.Value) (v ast.Value, err error) {
	// io.jwt.decode_verify(string, constraints, [valid, header, payload])
	//
	// If valid is true then the signature verifies and all constraints are met.
	// If valid is false then either the signature did not verify or some constrain
	// was not met.
	//
	// Decoding errors etc are returned as errors.
	arr := make(ast.Array, 3)
	arr[0] = ast.BooleanTerm(false) // by default, not verified
	arr[1] = ast.NewTerm(ast.NewObject())
	arr[2] = ast.NewTerm(ast.NewObject())
	var constraints tokenConstraints
	if constraints, err = parseTokenConstraints(b); err != nil {
		return
	}
	if err = constraints.validate(); err != nil {
		return
	}
	var token *JSONWebToken
	var p ast.Value
	for {
		// RFC7519 7.2 #1-2 split into parts
		if token, err = decodeJWT(a); err != nil {
			return
		}
		// RFC7519 7.2 #3, #4, #6
		if err = token.decodeHeader(); err != nil {
			return
		}
		// RFC7159 7.2 #5 (and RFC7159 5.2 #5) validate header fields
		var header tokenHeader
		if header, err = parseTokenHeader(token); err != nil {
			return
		}
		if !header.valid() {
			return arr, nil
		}
		// Check constraints that impact signature verification.
		if constraints.alg != "" && constraints.alg != header.alg {
			return arr, nil
		}
		// RFC7159 7.2 #7 verify the signature
		var signature string
		if signature, err = token.decodeSignature(); err != nil {
			return
		}
		if err = constraints.verify(header.kid, header.alg, token.header, token.payload, signature); err != nil {
			if err == errSignatureNotVerified {
				return arr, nil
			}
			return
		}
		// RFC7159 7.2 #9-10 decode the payload
		if p, err = builtinBase64UrlDecode(ast.String(token.payload)); err != nil {
			return nil, fmt.Errorf("JWT payload had invalid encoding: %v", err)
		}
		// RFC7159 7.2 #8 and 5.2 cty
		if strings.ToUpper(header.cty) == "JWT" {
			// Nested JWT, go round again
			a = p
			continue
		} else {
			// Non-nested JWT (or we've reached the bottom of the nesting).
			break
		}
	}
	var payload ast.Object
	if payload, err = extractJSONObject(string(p.(ast.String))); err != nil {
		return
	}
	// Check registered claim names against constraints or environment
	// RFC7159 4.1.1 iss
	if constraints.iss != "" {
		if iss := payload.Get(jwtIssKey); iss != nil {
			issVal := string(iss.Value.(ast.String))
			if constraints.iss != issVal {
				return arr, nil
			}
		}
	}
	// RFC7159 4.1.3 aud
	if aud := payload.Get(jwtAudKey); aud != nil {
		if !constraints.validAudience(aud.Value) {
			return arr, nil
		}
	} else {
		if constraints.aud != "" {
			return arr, nil
		}
	}
	// RFC7159 4.1.4 exp
	if exp := payload.Get(jwtExpKey); exp != nil {
		var expVal int64
		if expVal, err = strconv.ParseInt(string(exp.Value.(ast.Number)), 10, 64); err != nil {
			err = fmt.Errorf("parsing 'exp' JWT claim: %v", err)
			return
		}
		if constraints.time < 0 {
			constraints.time = time.Now().UnixNano()
		}
		// constraints.time is in nanoseconds but expVal is in seconds
		if constraints.time/1000000000 >= expVal {
			return arr, nil
		}
	}
	// RFC7159 4.1.5 nbf
	if nbf := payload.Get(jwtNbfKey); nbf != nil {
		var nbfVal int64
		if nbfVal, err = strconv.ParseInt(string(nbf.Value.(ast.Number)), 10, 64); err != nil {
			err = fmt.Errorf("parsing 'nbf' JWT claim: %v", err)
			return
		}
		if constraints.time < 0 {
			constraints.time = time.Now().UnixNano()
		}
		// constraints.time is in nanoseconds but nbfVal is in seconds
		if constraints.time/1000000000 < nbfVal {
			return arr, nil
		}
	}
	// Format the result
	arr[0] = ast.BooleanTerm(true)
	arr[1] = ast.NewTerm(token.decodedHeader)
	arr[2] = ast.NewTerm(payload)
	return arr, nil
}

// -- Utilities --

func decodeJWT(a ast.Value) (*JSONWebToken, error) {
	// Parse the JSON Web Token
	astEncode, err := builtins.StringOperand(a, 1)
	if err != nil {
		return nil, err
	}

	encoding := string(astEncode)
	if !strings.Contains(encoding, ".") {
		return nil, errors.New("encoded JWT had no period separators")
	}

	parts := strings.Split(encoding, ".")
	if len(parts) != 3 {
		return nil, fmt.Errorf("encoded JWT must have 3 sections, found %d", len(parts))
	}

	return &JSONWebToken{header: parts[0], payload: parts[1], signature: parts[2]}, nil
}

func (token *JSONWebToken) decodeSignature() (string, error) {
	decodedSignature, err := builtinBase64UrlDecode(ast.String(token.signature))
	if err != nil {
		return "", err
	}

	signatureAst, err := builtins.StringOperand(decodedSignature, 1)
	if err != nil {
		return "", err
	}
	return string(signatureAst), err
}

// Extract, validate and return the JWT header as an ast.Object.
func validateJWTHeader(h string) (ast.Object, error) {
	header, err := extractJSONObject(h)
	if err != nil {
		return nil, fmt.Errorf("bad JWT header: %v", err)
	}

	// There are two kinds of JWT tokens, a JSON Web Signature (JWS) and
	// a JSON Web Encryption (JWE). The latter is very involved, and we
	// won't support it for now.
	// This code checks which kind of JWT we are dealing with according to
	// RFC 7516 Section 9: https://tools.ietf.org/html/rfc7516#section-9
	if header.Get(jwtEncKey) != nil {
		return nil, errors.New("JWT is a JWE object, which is not supported")
	}

	return header, nil
}

func extractJSONObject(s string) (ast.Object, error) {
	// XXX: This code relies on undocumented behavior of Go's
	// json.Unmarshal using the last occurrence of duplicate keys in a JSON
	// Object. If duplicate keys are present in a JWT, the last must be
	// used or the token rejected. Since detecting duplicates is tantamount
	// to parsing it ourselves, we're relying on the Go implementation
	// using the last occurring instance of the key, which is the behavior
	// as of Go 1.8.1.
	v, err := builtinJSONUnmarshal(ast.String(s))
	if err != nil {
		return nil, fmt.Errorf("invalid JSON: %v", err)
	}

	o, ok := v.(ast.Object)
	if !ok {
		return nil, errors.New("decoded JSON type was not an Object")
	}

	return o, nil
}

// getInputSha returns the SHA256 checksum of the input
func getInputSHA(input []byte) (hash []byte) {
	hasher := sha256.New()
	hasher.Write(input)
	return hasher.Sum(nil)
}

func init() {
	RegisterFunctionalBuiltin1(ast.JWTDecode.Name, builtinJWTDecode)
	RegisterFunctionalBuiltin2(ast.JWTVerifyRS256.Name, builtinJWTVerifyRS256)
	RegisterFunctionalBuiltin2(ast.JWTVerifyPS256.Name, builtinJWTVerifyPS256)
	RegisterFunctionalBuiltin2(ast.JWTVerifyES256.Name, builtinJWTVerifyES256)
	RegisterFunctionalBuiltin2(ast.JWTVerifyHS256.Name, builtinJWTVerifyHS256)
	RegisterFunctionalBuiltin2(ast.JWTDecodeVerify.Name, builtinJWTDecodeVerify)
	RegisterFunctionalBuiltin3(ast.JWTEncodeSignRaw.Name, builtinJWTEncodeSignRaw)
	RegisterFunctionalBuiltin3(ast.JWTEncodeSign.Name, builtinJWTEncodeSign)
}
