1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312
|
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 multipurpose 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 an error will occur.
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)}
}
|