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
- Variables
- func Decode(jwx string, verifyJwks *KeySet, decryptJwks *KeySet, hint Algs, ...) error
- func Encode(sig KeySource, enc KeySource, payload interface{}) ([]byte, error)
- func EncodeToString(sig KeySource, enc KeySource, payload interface{}) (string, error)
- func IsNone(alg string) bool
- func ValidateClaims(c Claims, expectations ...Expect) error
- type Algs
- type Claims
- type Expect
- type Key
- type KeySet
- func (s *KeySet) Count() int
- func (s *KeySet) KeyById(kid string) (*Key, bool)
- func (s *KeySet) KeyForEncryption(alg string) (*Key, bool)
- func (s *KeySet) KeyForSigning(alg string) (*Key, bool)
- func (s *KeySet) MarshalJSON() ([]byte, error)
- func (s *KeySet) ToPublic() *KeySet
- func (s *KeySet) UnmarshalJSON(bytes []byte) error
- type KeySource
Constants ¶
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
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.
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.
const ( ClaimJti = "jti" ClaimSub = "sub" ClaimAud = "aud" ClaimExp = "exp" ClaimNbf = "nbf" ClaimIat = "iat" ClaimIss = "iss" )
Standard claim names
const ( // UseSig indicates signature usage UseSig = "sig" // UseEnc indicates encryption usage UseEnc = "enc" )
Variables ¶
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") )
var ( ErrNoSigningKey = errors.New("failed to resolve signing key") ErrNoEncryptionKey = errors.New("failed to resolve encryption key") )
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 } )
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") )
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 } } )
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 ¶
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 ¶
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 ¶
EncodeToString is a convenience wrapper around Encode. It returns the encoded result in string format.
func IsNone ¶
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 ¶
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 ¶
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 ¶
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 Key ¶
type Key struct {
// contains filtered or unexported fields
}
Key provides the features of a JSONWebKey.
func GenerateEncryptionKey ¶
GenerateEncryptionKey generates an encryption Key with the given kid and algorithm.
func GenerateSignatureKey ¶
GenerateSignatureKey generates a signature Key with the given kid and algorithm.
func (*Key) IsPublic ¶
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 ¶
IsSymmetric returns true if the underlying key uses symmetric algorithms (i.e. HS256)
type KeySet ¶
type KeySet struct {
// contains filtered or unexported fields
}
KeySet is a set of Key. Also known as JSONWebKey Set.
func ReadKeySet ¶
ReadKeySet create new KeySet with data from the reader
func (*KeySet) KeyForEncryption ¶
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 ¶
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 (*KeySet) ToPublic ¶
ToPublic returns a new KeySet with only public asymmetric keys so that it is read to be shared.