313 lines
12 KiB
Go
313 lines
12 KiB
Go
package jwt
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/lestrrat-go/jwx/v2/jwa"
|
|
"github.com/lestrrat-go/jwx/v2/jwe"
|
|
"github.com/lestrrat-go/jwx/v2/jwk"
|
|
"github.com/lestrrat-go/jwx/v2/jws"
|
|
"github.com/lestrrat-go/option"
|
|
)
|
|
|
|
type identInsecureNoSignature struct{}
|
|
type identKey struct{}
|
|
type identKeySet struct{}
|
|
type identTypedClaim struct{}
|
|
type identVerifyAuto struct{}
|
|
|
|
func toSignOptions(options ...Option) ([]jws.SignOption, error) {
|
|
soptions := make([]jws.SignOption, 0, len(options))
|
|
for _, option := range options {
|
|
//nolint:forcetypeassert
|
|
switch option.Ident() {
|
|
case identInsecureNoSignature{}:
|
|
soptions = append(soptions, jws.WithInsecureNoSignature())
|
|
case identKey{}:
|
|
wk := option.Value().(*withKey) // this always succeeds
|
|
var wksoptions []jws.WithKeySuboption
|
|
for _, subopt := range wk.options {
|
|
wksopt, ok := subopt.(jws.WithKeySuboption)
|
|
if !ok {
|
|
return nil, fmt.Errorf(`expected optional arguments in jwt.WithKey to be jws.WithKeySuboption, but got %T`, subopt)
|
|
}
|
|
wksoptions = append(wksoptions, wksopt)
|
|
}
|
|
|
|
soptions = append(soptions, jws.WithKey(wk.alg, wk.key, wksoptions...))
|
|
case identSignOption{}:
|
|
sigOpt := option.Value().(jws.SignOption) // this always succeeds
|
|
soptions = append(soptions, sigOpt)
|
|
}
|
|
}
|
|
return soptions, nil
|
|
}
|
|
|
|
func toEncryptOptions(options ...Option) ([]jwe.EncryptOption, error) {
|
|
soptions := make([]jwe.EncryptOption, 0, len(options))
|
|
for _, option := range options {
|
|
//nolint:forcetypeassert
|
|
switch option.Ident() {
|
|
case identKey{}:
|
|
wk := option.Value().(*withKey) // this always succeeds
|
|
var wksoptions []jwe.WithKeySuboption
|
|
for _, subopt := range wk.options {
|
|
wksopt, ok := subopt.(jwe.WithKeySuboption)
|
|
if !ok {
|
|
return nil, fmt.Errorf(`expected optional arguments in jwt.WithKey to be jwe.WithKeySuboption, but got %T`, subopt)
|
|
}
|
|
wksoptions = append(wksoptions, wksopt)
|
|
}
|
|
|
|
soptions = append(soptions, jwe.WithKey(wk.alg, wk.key, wksoptions...))
|
|
case identEncryptOption{}:
|
|
encOpt := option.Value().(jwe.EncryptOption) // this always succeeds
|
|
soptions = append(soptions, encOpt)
|
|
}
|
|
}
|
|
return soptions, nil
|
|
}
|
|
|
|
func toVerifyOptions(options ...Option) ([]jws.VerifyOption, error) {
|
|
voptions := make([]jws.VerifyOption, 0, len(options))
|
|
for _, option := range options {
|
|
//nolint:forcetypeassert
|
|
switch option.Ident() {
|
|
case identKey{}:
|
|
wk := option.Value().(*withKey) // this always succeeds
|
|
var wksoptions []jws.WithKeySuboption
|
|
for _, subopt := range wk.options {
|
|
wksopt, ok := subopt.(jws.WithKeySuboption)
|
|
if !ok {
|
|
return nil, fmt.Errorf(`expected optional arguments in jwt.WithKey to be jws.WithKeySuboption, but got %T`, subopt)
|
|
}
|
|
wksoptions = append(wksoptions, wksopt)
|
|
}
|
|
|
|
voptions = append(voptions, jws.WithKey(wk.alg, wk.key, wksoptions...))
|
|
case identKeySet{}:
|
|
wks := option.Value().(*withKeySet) // this always succeeds
|
|
var wkssoptions []jws.WithKeySetSuboption
|
|
for _, subopt := range wks.options {
|
|
wkssopt, ok := subopt.(jws.WithKeySetSuboption)
|
|
if !ok {
|
|
return nil, fmt.Errorf(`expected optional arguments in jwt.WithKey to be jws.WithKeySetSuboption, but got %T`, subopt)
|
|
}
|
|
wkssoptions = append(wkssoptions, wkssopt)
|
|
}
|
|
|
|
voptions = append(voptions, jws.WithKeySet(wks.set, wkssoptions...))
|
|
case identVerifyAuto{}:
|
|
// this one doesn't need conversion. just get the stored option
|
|
voptions = append(voptions, option.Value().(jws.VerifyOption))
|
|
case identKeyProvider{}:
|
|
kp, ok := option.Value().(jws.KeyProvider)
|
|
if !ok {
|
|
return nil, fmt.Errorf(`expected jws.KeyProvider, got %T`, option.Value())
|
|
}
|
|
voptions = append(voptions, jws.WithKeyProvider(kp))
|
|
}
|
|
}
|
|
return voptions, nil
|
|
}
|
|
|
|
type withKey struct {
|
|
alg jwa.KeyAlgorithm
|
|
key interface{}
|
|
options []Option
|
|
}
|
|
|
|
// WithKey is a multi-purpose option. It can be used for either jwt.Sign, jwt.Parse (and
|
|
// its siblings), and jwt.Serializer methods. For signatures, please see the documentation
|
|
// for `jws.WithKey` for more details. For encryption, please see the documentation
|
|
// for `jwe.WithKey`.
|
|
//
|
|
// It is the caller's responsibility to match the suboptions to the operation that they
|
|
// are performing. For example, you are not allowed to do this, because the operation
|
|
// is to generate a signature, and yet you are passing options for jwe:
|
|
//
|
|
// jwt.Sign(token, jwt.WithKey(alg, key, jweOptions...))
|
|
//
|
|
// In the above example, the creation of the option via `jwt.WithKey()` will work, but
|
|
// when `jwt.Sign()` is called, the fact that you passed JWE suboptions will be
|
|
// detected, and it will be an error.
|
|
func WithKey(alg jwa.KeyAlgorithm, key interface{}, suboptions ...Option) SignEncryptParseOption {
|
|
return &signEncryptParseOption{option.New(identKey{}, &withKey{
|
|
alg: alg,
|
|
key: key,
|
|
options: suboptions,
|
|
})}
|
|
}
|
|
|
|
type withKeySet struct {
|
|
set jwk.Set
|
|
options []interface{}
|
|
}
|
|
|
|
// WithKeySet forces the Parse method to verify the JWT message
|
|
// using one of the keys in the given key set.
|
|
//
|
|
// Key IDs (`kid`) in the JWS message and the JWK in the given `jwk.Set`
|
|
// must match in order for the key to be a candidate to be used for
|
|
// verification.
|
|
//
|
|
// This is for security reasons. If you must disable it, you can do so by
|
|
// specifying `jws.WithRequireKid(false)` in the suboptions. But we don't
|
|
// recommend it unless you know exactly what the security implications are
|
|
//
|
|
// When using this option, keys MUST have a proper 'alg' field
|
|
// set. This is because we need to know the exact algorithm that
|
|
// you (the user) wants to use to verify the token. We do NOT
|
|
// trust the token's headers, because they can easily be tampered with.
|
|
//
|
|
// However, there _is_ a workaround if you do understand the risks
|
|
// of allowing a library to automatically choose a signature verification strategy,
|
|
// and you do not mind the verification process having to possibly
|
|
// attempt using multiple times before succeeding to verify. See
|
|
// `jws.InferAlgorithmFromKey` option
|
|
//
|
|
// If you have only one key in the set, and are sure you want to
|
|
// use that key, you can use the `jwt.WithDefaultKey` option.
|
|
func WithKeySet(set jwk.Set, options ...interface{}) ParseOption {
|
|
return &parseOption{option.New(identKeySet{}, &withKeySet{
|
|
set: set,
|
|
options: options,
|
|
})}
|
|
}
|
|
|
|
// WithIssuer specifies that expected issuer value. If not specified,
|
|
// the value of issuer is not verified at all.
|
|
func WithIssuer(s string) ValidateOption {
|
|
return WithValidator(issuerClaimValueIs(s))
|
|
}
|
|
|
|
// WithSubject specifies that expected subject value. If not specified,
|
|
// the value of subject is not verified at all.
|
|
func WithSubject(s string) ValidateOption {
|
|
return WithValidator(ClaimValueIs(SubjectKey, s))
|
|
}
|
|
|
|
// WithJwtID specifies that expected jti value. If not specified,
|
|
// the value of jti is not verified at all.
|
|
func WithJwtID(s string) ValidateOption {
|
|
return WithValidator(ClaimValueIs(JwtIDKey, s))
|
|
}
|
|
|
|
// WithAudience specifies that expected audience value.
|
|
// `Validate()` will return true if one of the values in the `aud` element
|
|
// matches this value. If not specified, the value of `aud` is not
|
|
// verified at all.
|
|
func WithAudience(s string) ValidateOption {
|
|
return WithValidator(audienceClaimContainsString(s))
|
|
}
|
|
|
|
// WithClaimValue specifies the expected value for a given claim
|
|
func WithClaimValue(name string, v interface{}) ValidateOption {
|
|
return WithValidator(ClaimValueIs(name, v))
|
|
}
|
|
|
|
type claimPair struct {
|
|
Name string
|
|
Value interface{}
|
|
}
|
|
|
|
// WithTypedClaim allows a private claim to be parsed into the object type of
|
|
// your choice. It works much like the RegisterCustomField, but the effect
|
|
// is only applicable to the jwt.Parse function call which receives this option.
|
|
//
|
|
// While this can be extremely useful, this option should be used with caution:
|
|
// There are many caveats that your entire team/user-base needs to be aware of,
|
|
// and therefore in general its use is discouraged. Only use it when you know
|
|
// what you are doing, and you document its use clearly for others.
|
|
//
|
|
// First and foremost, this is a "per-object" option. Meaning that given the same
|
|
// serialized format, it is possible to generate two objects whose internal
|
|
// representations may differ. That is, if you parse one _WITH_ the option,
|
|
// and the other _WITHOUT_, their internal representation may completely differ.
|
|
// This could potentially lead to problems.
|
|
//
|
|
// Second, specifying this option will slightly slow down the decoding process
|
|
// as it needs to consult multiple definitions sources (global and local), so
|
|
// be careful if you are decoding a large number of tokens, as the effects will stack up.
|
|
//
|
|
// Finally, this option will also NOT work unless the tokens themselves support such
|
|
// parsing mechanism. For example, while tokens obtained from `jwt.New()` and
|
|
// `openid.New()` will respect this option, if you provide your own custom
|
|
// token type, it will need to implement the TokenWithDecodeCtx interface.
|
|
func WithTypedClaim(name string, object interface{}) ParseOption {
|
|
return &parseOption{option.New(identTypedClaim{}, claimPair{Name: name, Value: object})}
|
|
}
|
|
|
|
// WithRequiredClaim specifies that the claim identified the given name
|
|
// must exist in the token. Only the existence of the claim is checked:
|
|
// the actual value associated with that field is not checked.
|
|
func WithRequiredClaim(name string) ValidateOption {
|
|
return WithValidator(IsRequired(name))
|
|
}
|
|
|
|
// WithMaxDelta specifies that given two claims `c1` and `c2` that represent time, the difference in
|
|
// time.Duration must be less than equal to the value specified by `d`. If `c1` or `c2` is the
|
|
// empty string, the current time (as computed by `time.Now` or the object passed via
|
|
// `WithClock()`) is used for the comparison.
|
|
//
|
|
// `c1` and `c2` are also assumed to be required, therefore not providing either claim in the
|
|
// token will result in an error.
|
|
//
|
|
// Because there is no way of reliably knowing how to parse private claims, we currently only
|
|
// support `iat`, `exp`, and `nbf` claims.
|
|
//
|
|
// If the empty string is passed to c1 or c2, then the current time (as calculated by time.Now() or
|
|
// the clock object provided via WithClock()) is used.
|
|
//
|
|
// For example, in order to specify that `exp` - `iat` should be less than 10*time.Second, you would write
|
|
//
|
|
// jwt.Validate(token, jwt.WithMaxDelta(10*time.Second, jwt.ExpirationKey, jwt.IssuedAtKey))
|
|
//
|
|
// If AcceptableSkew of 2 second is specified, the above will return valid for any value of
|
|
// `exp` - `iat` between 8 (10-2) and 12 (10+2).
|
|
func WithMaxDelta(dur time.Duration, c1, c2 string) ValidateOption {
|
|
return WithValidator(MaxDeltaIs(c1, c2, dur))
|
|
}
|
|
|
|
// WithMinDelta is almost exactly the same as WithMaxDelta, but force validation to fail if
|
|
// the difference between time claims are less than dur.
|
|
//
|
|
// For example, in order to specify that `exp` - `iat` should be greater than 10*time.Second, you would write
|
|
//
|
|
// jwt.Validate(token, jwt.WithMinDelta(10*time.Second, jwt.ExpirationKey, jwt.IssuedAtKey))
|
|
//
|
|
// The validation would fail if the difference is less than 10 seconds.
|
|
func WithMinDelta(dur time.Duration, c1, c2 string) ValidateOption {
|
|
return WithValidator(MinDeltaIs(c1, c2, dur))
|
|
}
|
|
|
|
// WithVerifyAuto specifies that the JWS verification should be attempted
|
|
// by using the data available in the JWS message. Currently only verification
|
|
// method available is to use the keys available in the JWKS URL pointed
|
|
// in the `jku` field.
|
|
//
|
|
// The first argument should either be `nil`, or your custom jwk.Fetcher
|
|
// object, which tells how the JWKS should be fetched. Leaving it to
|
|
// `nil` is equivalent to specifying that `jwk.Fetch` should be used.
|
|
//
|
|
// You can further pass options to customize the fetching behavior.
|
|
//
|
|
// One notable difference in the option available via the `jwt`
|
|
// package and the `jws.Verify()` or `jwk.Fetch()` functions is that
|
|
// by default all fetching is disabled unless you explicitly whitelist urls.
|
|
// Therefore, when you use this option you WILL have to specify at least
|
|
// the `jwk.WithFetchWhitelist()` suboption: as:
|
|
//
|
|
// jwt.Parse(data, jwt.WithVerifyAuto(nil, jwk.WithFetchWhitelist(...)))
|
|
//
|
|
// See the list of available options that you can pass to `jwk.Fetch()`
|
|
// in the `jwk` package
|
|
func WithVerifyAuto(f jwk.Fetcher, options ...jwk.FetchOption) ParseOption {
|
|
return &parseOption{option.New(identVerifyAuto{}, jws.WithVerifyAuto(f, options...))}
|
|
}
|
|
|
|
func WithInsecureNoSignature() SignOption {
|
|
return &signEncryptParseOption{option.New(identInsecureNoSignature{}, nil)}
|
|
}
|