jwx

package
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 4, 2021 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package jwx provides a thin wrapper layer around gopkg.in/square/go-jose.v2 to provide JOSE related operations in the OpenID Connect context.

The features provided includes: - wrapper around jose.JSONWebKey and jose.JSONWebKeySet to provider convenient parsing and querying capabilities. - Claims interface to allow custom implementation of JWT claims, with a default map based implementation. - Expect functions to allow custom validation rules, with out-of-box implementations for standard claims. - KeySource functions to allow custom logic to locate a Key, with out-of-box implementation for common scenarios. - Encode and Decode functions to handle converting to and from a JWT/JWX token.

Index

Constants

View Source
const (
	HS256 = string(jose.HS256)
	HS384 = string(jose.HS384)
	HS512 = string(jose.HS512)
	RS256 = string(jose.RS256)
	RS384 = string(jose.RS384)
	RS512 = string(jose.RS512)
	PS256 = string(jose.PS256)
	PS384 = string(jose.PS384)
	PS512 = string(jose.PS512)
	ES256 = string(jose.ES256)
	ES384 = string(jose.ES384)
	ES512 = string(jose.ES512)
)

Signature algorithms

View Source
const (
	ED25519            = string(jose.ED25519)
	RSA1_5             = string(jose.RSA1_5)
	RSA_OAEP           = string(jose.RSA_OAEP)
	RSA_OAEP_256       = string(jose.RSA_OAEP_256)
	A128KW             = string(jose.A128KW)
	A192KW             = string(jose.A192KW)
	A256KW             = string(jose.A256KW)
	DIRECT             = string(jose.DIRECT)
	ECDH_ES            = string(jose.ECDH_ES)
	ECDH_ES_A128KW     = string(jose.ECDH_ES_A128KW)
	ECDH_ES_A192KW     = string(jose.ECDH_ES_A192KW)
	ECDH_ES_A256KW     = string(jose.ECDH_ES_A256KW)
	A128GCMKW          = string(jose.A128GCMKW)
	A192GCMKW          = string(jose.A192GCMKW)
	A256GCMKW          = string(jose.A256GCMKW)
	PBES2_HS256_A128KW = string(jose.PBES2_HS256_A128KW)
	PBES2_HS384_A192KW = string(jose.PBES2_HS384_A192KW)
	PBES2_HS512_A256KW = string(jose.PBES2_HS512_A256KW)
)

Key algorithms Also known as "encryption algorithm" in OIDC context.

View Source
const (
	A128CBC_HS256 = string(jose.A128CBC_HS256)
	A192CBC_HS384 = string(jose.A192CBC_HS384)
	A256CBC_HS512 = string(jose.A256CBC_HS512)
	A128GCM       = string(jose.A128GCM)
	A192GCM       = string(jose.A192GCM)
	A256GCM       = string(jose.A256GCM)
)

Encryption algorithms. Also known as "content encoding algorithm" in OIDC context.

View Source
const (
	ClaimJti = "jti"
	ClaimSub = "sub"
	ClaimAud = "aud"
	ClaimExp = "exp"
	ClaimNbf = "nbf"
	ClaimIat = "iat"
	ClaimIss = "iss"
)

Standard claim names

View Source
const (
	// UseSig indicates signature usage
	UseSig = "sig"
	// UseEnc indicates encryption usage
	UseEnc = "enc"
)

Variables

View Source
var (
	ErrInvalidJwxToken   = errors.New("invalid jwt/jwe token")
	ErrNoVerificationKey = errors.New("failed to resolve key to verify signature")
	ErrNoDecryptionKey   = errors.New("failed to resolve decryption key")
)
View Source
var (
	ErrNoSigningKey    = errors.New("failed to resolve signing key")
	ErrNoEncryptionKey = errors.New("failed to resolve encryption key")
)
View Source
var (
	// SignatureKeyById returns a KeySource which find a signature Key by its key id.
	SignatureKeyById = func(kid string, jwks *KeySet) KeySource {
		return func() (*Key, Algs, bool) {
			key, ok := jwks.KeyById(kid)
			if !ok || key.Use() != UseSig {
				return nil, Algs{}, false
			}
			return key, Algs{Sig: key.Alg()}, true
		}
	}
	// SignatureKeyByAlg returns a KeySource which finds a signature Key by algorithm.
	SignatureKeyByAlg = func(alg string, jwks *KeySet) KeySource {
		if IsNone(alg) {
			return SkipKeySource
		}
		return func() (*Key, Algs, bool) {
			key, ok := jwks.KeyForSigning(alg)
			if !ok {
				return nil, Algs{}, false
			}
			return key, Algs{Sig: alg}, true
		}
	}
	// EncryptionKeyByAlg returns a KeySource which finds a encryption Key by algorithm.
	EncryptionKeyByAlg = func(encryptAlg, encodeAlg string, jwks *KeySet) KeySource {
		if IsNone(encryptAlg) || IsNone(encodeAlg) {
			return SkipKeySource
		}
		return func() (*Key, Algs, bool) {
			key, ok := jwks.KeyForEncryption(encryptAlg)
			if !ok {
				return nil, Algs{}, false
			}
			return key, Algs{Encrypt: encryptAlg, Encode: encodeAlg}, true
		}
	}
	// SkipKeySource returns a KeySource that whose Algs return value IsNone, and shall be skipped.
	SkipKeySource KeySource = func() (*Key, Algs, bool) {
		return nil, Algs{}, true
	}
)
View Source
var (
	ErrAbsentJti   = errors.New("jti claim is absent")
	ErrInvalidSub  = errors.New("sub claim is invalid")
	ErrInvalidIss  = errors.New("iss claim is invalid")
	ErrInvalidAud  = errors.New("aud claim is invalid")
	ErrExpExpired  = errors.New("exp claim is invalid because token has expired")
	ErrIatInFuture = errors.New("iat claim is invalid because token is issued in future")
	ErrNbfTooSoon  = errors.New("nbf claim is invalid because token is used too soon")
)
View Source
var (
	// ExpectJti expects the "jti" claim is present and non-empty. If "jti"
	// is not present, ErrAbsentJti is returned.
	ExpectJti Expect = func(c Claims) error {
		if v, ok := c.Get(ClaimJti); ok {
			if jti, ok := v.(string); ok && len(jti) > 0 {
				return nil
			}
		}
		return ErrAbsentJti
	}
	// ExpectIss expects the "iss" claim to be the same as issuer. If invalid,
	// ErrInvalidIss is returned.
	ExpectIss = func(issuer string) Expect {
		return func(c Claims) error {
			if v, ok := c.Get(ClaimIss); ok {
				if iss, ok := v.(string); ok && iss == issuer {
					return nil
				}
			}
			return ErrInvalidIss
		}
	}
	// ExpectSub returns an Expect rule to check if the subject is present
	// and is one of the expected subject values. If "sub" is not present, or
	// is not one of the legal values, ErrInvalidSub is returned.
	ExpectSub = func(subjects ...string) Expect {
		return func(c Claims) error {
			if v, ok := c.Get(ClaimSub); ok {
				if sub, ok := v.(string); ok {
					for _, each := range subjects {
						if each == sub {
							return nil
						}
					}
				}
			}
			return ErrInvalidSub
		}
	}
	// ExpectAud returns an Expect rule to check if the audience is present and that
	// one of the audience values is among the expected audiences. If condition is not
	// met, ErrInvalidAud is returned.
	ExpectAud = func(audiences ...string) Expect {
		expected := internal.NewSet(audiences...)
		return func(c Claims) error {
			if v, ok := c.Get(ClaimAud); ok && v != nil {
				if aud, ok := v.([]string); ok {
					if internal.NewSet(aud...).ContainsAll(expected) {
						return nil
					}
				}
			}
			return ErrInvalidAud
		}
	}
	// ExpectTime returns an Expect rule to check the time related claims "exp", "iat" and "nbf", if
	// they are available as time.Time. The rule considers a leeway in order to slack the clock. The
	// leeway must be a positive time.Duration, otherwise its absolute value is used.
	//
	// For "exp" claim, if current time is beyond the indicated expiry plus leeway, ErrExpExpired is returned;
	// For "iat" claim, if issued at time is beyond current time plus leeway, ErrIatInFuture is returned;
	// For "nbf" claim, if not before time is beyond current time plus leeway, ErrNbfTooSoon is returned.
	//
	// When time related claim is not present, or is not returned as time.Time by Claims, the validation is skipped.
	ExpectTime = func(leeway time.Duration) Expect {
		if leeway < 0 {
			leeway = -leeway
		}

		now := time.Now()

		return func(c Claims) error {
			if v, ok := c.Get(ClaimExp); ok {
				if exp, ok := v.(time.Time); ok && !exp.IsZero() {
					if now.After(exp.Add(leeway)) {
						return ErrExpExpired
					}
				}
			}

			if v, ok := c.Get(ClaimIat); ok {
				if iat, ok := v.(time.Time); ok && !iat.IsZero() {
					if iat.After(now.Add(leeway)) {
						return ErrIatInFuture
					}
				}
			}

			if v, ok := c.Get(ClaimNbf); ok {
				if nbf, ok := v.(time.Time); ok && !nbf.IsZero() {
					if nbf.After(now.Add(leeway)) {
						return ErrNbfTooSoon
					}
				}
			}

			return nil
		}
	}
)
View Source
var (
	// ErrInvalidSignatureAlg indicates the used signature algorithm is invalid.
	ErrInvalidSignatureAlg = errors.New("signature algorithm is invalid")
	// ErrInvalidEncryptionAlg indicates the used encryption algorithm is invalid.
	ErrInvalidEncryptionAlg = errors.New("encryption algorithm is invalid")
	// ErrInvalidEncryptionEnc indicates the used encryption encoding is invalid.
	ErrInvalidEncryptionEnc = errors.New("encryption encoding is invalid")

	// ValidSignatureAlg is a validation function that checks if the given string is a valid JWA
	// signature algorithm. This function rejects empty string or "none".
	ValidSignatureAlg = func(s string) error {
		switch s {
		case HS256, HS384, HS512,
			RS256, RS384, RS512,
			PS256, PS384, PS512,
			ES256, ES384, ES512:
			return nil
		default:
			return ErrInvalidSignatureAlg
		}
	}
	// ValidOptionalSignatureAlg is a validation function that checks if the given string is a valid JWA
	// signature algorithm. This function accepts empty string or "none".
	ValidOptionalSignatureAlg = func(s string) error {
		if IsNone(s) {
			return nil
		}
		return ValidSignatureAlg(s)
	}
	// ValidEncryptionAlg is a validation function that checks if the given string is a valid JWA
	// encryption algorithm. This function rejects empty string or "none".
	ValidEncryptionAlg = func(s string) error {
		switch s {
		case ED25519,
			RSA1_5, RSA_OAEP, RSA_OAEP_256,
			A128KW, A192KW, A256KW,
			DIRECT,
			ECDH_ES, ECDH_ES_A128KW, ECDH_ES_A192KW, ECDH_ES_A256KW,
			A128GCMKW, A192GCMKW, A256GCMKW,
			PBES2_HS256_A128KW, PBES2_HS384_A192KW, PBES2_HS512_A256KW:
			return nil
		default:
			return ErrInvalidEncryptionAlg
		}
	}
	// ValidEncryptionAlgOptional is a validation function that checks if the given string is a valid JWA
	// encryption algorithm. This function accepts empty string or "none".
	ValidOptionalEncryptionAlg = func(s string) error {
		if IsNone(s) {
			return nil
		}
		return ValidEncryptionAlg(s)
	}
	// ValidEncryptionEnc is a validation function that checks if the given string is a valid JWA
	// encryption encoding. This function rejects empty string or "none".
	ValidEncryptionEnc = func(s string) error {
		switch s {
		case A128CBC_HS256, A192CBC_HS384, A256CBC_HS512,
			A128GCM, A192GCM, A256GCM:
			return nil
		default:
			return ErrInvalidEncryptionEnc
		}
	}
	// ValidEncryptionEncOptional is a validation function that checks if the given string is a valid JWA
	// encryption encoding. This function accepts empty string or "none".
	ValidOptionalEncryptionEnc = func(s string) error {
		if IsNone(s) {
			return nil
		}
		return ValidEncryptionEnc(s)
	}
)

Functions

func Decode

func Decode(jwx string, verifyJwks *KeySet, decryptJwks *KeySet, hint Algs, dest interface{}) error

Decode decodes the claims of the given JWT/JWE token into the provided destination object.

The decoding process is driven by both the token and the caller input. The hint algorithms suggests whether to perform decryption operation and/or signature verification operations, or not. When the algorithm is not IsNone, the corresponding stage is performed. Keys will be resolved against the verification and/or decryption key sets based on values present in the JWS/JWE headers. The "kid" header is given precedence to the "alg" header. In the end, the decrypted and verified payload is deserialized into the destination object as JSON.

func Encode

func Encode(sig KeySource, enc KeySource, payload interface{}) ([]byte, error)

Encode encodes the given payload to a JWT or a JWE token.

The payload is normally serialized into JSON before performing signing and/or encryption operations. However, the serialization can be skipped by providing the payload as []byte, json.RawMessage or string, which indicates to the function that the payload is already serialized.

The signature KeySource sig and encryption KeySource enc controls the signing and encryption operations. Both stage can be skipped by providing nil or SkipKeySource as the KeySource for the corresponding stage. Normal usages would be to skip none or just one of the stages. However, it is fine to skip both stages, which simply reduces this function to a JSON encoding function.

func EncodeToString

func EncodeToString(sig KeySource, enc KeySource, payload interface{}) (string, error)

EncodeToString is a convenience wrapper around Encode. It returns the encoded result in string format.

func IsNone

func IsNone(alg string) bool

IsNone returns true if the algorithm is empty or has value "none". Algorithm values that are none should be treated as absent, and use defaults if necessary.

func ValidateClaims

func ValidateClaims(c Claims, expectations ...Expect) error

ValidateClaims runs the Claims against a series of Expect rules. Any error is returned immediately.

Types

type Algs

type Algs struct {
	// Sig is the signature algorithm
	Sig string
	// Encrypt is the encryption algorithm
	Encrypt string
	// Encode is the encryption encoding
	Encode string
}

Algs is a pack of algorithms

type Claims

type Claims interface {
	// Get returns the top level claim by its name. For standard claim names, Get needs
	// to return compatible values as follows:
	//
	//	jti: string
	// 	iss: string
	//	sub: string
	//	aud: []string, or nil
	//	exp: time.Time
	//	nbf: time.Time
	//	iat: time.Time
	//
	// The above compatible return values will ensure Claims work well with ValidateClaims and
	// the out-of-box Expect rules.
	Get(name string) (interface{}, bool)
}

Claims is JWT claims.

func NewJWTClaims

func NewJWTClaims(claims jwt.Claims) Claims

NewMapClaims returns a wrapper implementation of Claims around jwt.Claims. This implementation suits the use case where the user simply want to adapt jwt.Claims.

func NewMapClaims

func NewMapClaims(claims map[string]interface{}) Claims

NewMapClaims returns a new map based implementation of Claims. This implementation store all claims in a generic map, which is necessary when dynamic claims are expected.

type Expect

type Expect func(c Claims) error

Expect expects a Claims to conform to certain characteristics.

type Key

type Key struct {
	// contains filtered or unexported fields
}

Key provides the features of a JSONWebKey.

func GenerateEncryptionKey

func GenerateEncryptionKey(kid string, alg string, bits int) *Key

GenerateEncryptionKey generates an encryption Key with the given kid and algorithm.

func GenerateSignatureKey

func GenerateSignatureKey(kid string, alg string, bits int) *Key

GenerateSignatureKey generates a signature Key with the given kid and algorithm.

func (*Key) Alg

func (k *Key) Alg() string

Alg returns the algorithm of the key.

func (*Key) Id

func (k *Key) Id() string

Id returns the id of the key.

func (*Key) IsPublic

func (k *Key) IsPublic() bool

IsPublic returns true if the underlying key only has the public portion of the non-symmetric keys. This method applies only to non-symmetric keys (as in IsSymmetric returns false), its return true is irrelevant for symmetric keys.

func (*Key) IsSymmetric

func (k *Key) IsSymmetric() bool

IsSymmetric returns true if the underlying key uses symmetric algorithms (i.e. HS256)

func (*Key) Raw

func (k *Key) Raw() interface{}

Raw returns the underlying cryptographic key.

func (*Key) ToPublic

func (k *Key) ToPublic() *Key

ToPublic returns a new Key with only the public portion of the underlying key. This method shall return the same Key if the key is already public or is symmetric.

func (*Key) Use

func (k *Key) Use() string

Use returns the expected usage of the key.

type KeySet

type KeySet struct {
	// contains filtered or unexported fields
}

KeySet is a set of Key. Also known as JSONWebKey Set.

func NewKeySet

func NewKeySet(keys ...*Key) *KeySet

NewKeySet creates a new key set with the given keys.

func ReadKeySet

func ReadKeySet(reader io.Reader) (*KeySet, error)

ReadKeySet create new KeySet with data from the reader

func (*KeySet) Count

func (s *KeySet) Count() int

Count returns the number of keys in the set.

func (*KeySet) KeyById

func (s *KeySet) KeyById(kid string) (*Key, bool)

KeyById finds a Key by its id value.

func (*KeySet) KeyForEncryption

func (s *KeySet) KeyForEncryption(alg string) (*Key, bool)

KeyForEncryption find a key for encryption with the given algorithm. The returned key may be a private key, in which case, caller needs to convert to a public key before use.

If multiple encryption keys with the same algorithm exists in the set, a rotation factor is computed to pick one based on the current time.

func (*KeySet) KeyForSigning

func (s *KeySet) KeyForSigning(alg string) (*Key, bool)

KeyForSigning find a key for signing with the given algorithm. If multiple signing keys with the same algorithm exists in the set, a rotation factor is computed to pick one based on the current time.

func (*KeySet) MarshalJSON

func (s *KeySet) MarshalJSON() ([]byte, error)

func (*KeySet) ToPublic

func (s *KeySet) ToPublic() *KeySet

ToPublic returns a new KeySet with only public asymmetric keys so that it is read to be shared.

func (*KeySet) UnmarshalJSON

func (s *KeySet) UnmarshalJSON(bytes []byte) error

type KeySource

type KeySource func() (*Key, Algs, bool)

KeySource is a function that can produce a Key and its corresponding algorithm specs.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL