File: options.go

package info (click to toggle)
golang-github-lestrrat-go-jwx 2.1.4-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,872 kB
  • sloc: sh: 222; makefile: 86; perl: 62
file content (312 lines) | stat: -rw-r--r-- 12,569 bytes parent folder | download
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)}
}