-
-
Notifications
You must be signed in to change notification settings - Fork 163
/
Copy pathoptions.go
226 lines (208 loc) · 7.83 KB
/
options.go
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
package jws
import (
"github.com/lestrrat-go/jwx/v2/jwa"
"github.com/lestrrat-go/jwx/v2/jwk"
"github.com/lestrrat-go/option"
)
type identHeaders struct{}
type identInsecureNoSignature struct{}
// WithHeaders allows you to specify extra header values to include in the
// final JWS message
func WithHeaders(h Headers) SignOption {
return &signOption{option.New(identHeaders{}, h)}
}
// WithJSON specifies that the result of `jws.Sign()` is serialized in
// JSON format.
//
// If you pass multiple keys to `jws.Sign()`, it will fail unless
// you also pass this option.
func WithJSON(options ...WithJSONSuboption) SignVerifyParseOption {
var pretty bool
for _, option := range options {
//nolint:forcetypeassert
switch option.Ident() {
case identPretty{}:
pretty = option.Value().(bool)
}
}
format := fmtJSON
if pretty {
format = fmtJSONPretty
}
return &signVerifyParseOption{option.New(identSerialization{}, format)}
}
type withKey struct {
alg jwa.KeyAlgorithm
key interface{}
protected Headers
public Headers
}
// This exist as escape hatches to modify the header values after the fact
func (w *withKey) Protected(v Headers) Headers {
if w.protected == nil && v != nil {
w.protected = v
}
return w.protected
}
// WithKey is used to pass a static algorithm/key pair to either `jws.Sign()` or `jws.Verify()`.
//
// The `alg` parameter is the identifier for the signature algorithm that should be used.
// It is of type `jwa.KeyAlgorithm` but in reality you can only pass `jwa.SignatureAlgorithm`
// types. It is this way so that the value in `(jwk.Key).Algorithm()` can be directly
// passed to the option. If you specify other algorithm types such as `jwa.ContentEncryptionAlgorithm`,
// then you will get an error when `jws.Sign()` or `jws.Verify()` is executed.
//
// The `alg` parameter cannot be "none" (jwa.NoSignature) for security reasons.
// You will have to use a separate, more explicit option to allow the use of "none"
// algorithm.
//
// The algorithm specified in the `alg` parameter MUST be able to support
// the type of key you provided, otherwise an error is returned.
//
// Any of the followin is accepted for the `key` parameter:
// * A "raw" key (e.g. rsa.PrivateKey, ecdsa.PrivateKey, etc)
// * A crypto.Signer
// * A jwk.Key
//
// Note that due to technical reasons, this library is NOT able to differentiate
// between a valid/invalid key for given algorithm if the key implements crypto.Signer
// and the key is from an external library. For example, while we can tell that it is
// invalid to use `jwk.WithKey(jwa.RSA256, ecdsaPrivateKey)` because the key is
// presumably from `crypto/ecdsa` or this library, if you use a KMS wrapper
// that implements crypto.Signer that is outside of the go standard library or this
// library, we will not be able to properly catch the misuse of such keys --
// the output will happily generate an ECDSA signature even in the presence of
// `jwa.RSA256`
//
// A `crypto.Signer` is used when the private part of a key is
// kept in an inaccessible location, such as hardware.
// `crypto.Signer` is currently supported for RSA, ECDSA, and EdDSA
// family of algorithms. You may consider using `github.com/jwx-go/crypto-signer`
// if you would like to use keys stored in GCP/AWS KMS services.
//
// If the key is a jwk.Key and the key contains a key ID (`kid` field),
// then it is added to the protected header generated by the signature.
//
// `jws.WithKey()` can furher accept suboptions to change signing behavior
// when used with `jws.Sign()`. `jws.WithProtected()` and `jws.WithPublic()`
// can be passed to specify JWS headers that should be used whe signing.
//
// If the protected headers contain "b64" field, then the boolean value for the field
// is respected when serializing. That is, if you specify a header with
// `{"b64": false}`, then the payload is not base64 encoded.
//
// These suboptions are ignored when the `jws.WithKey()` option is used with `jws.Verify()`.
func WithKey(alg jwa.KeyAlgorithm, key interface{}, options ...WithKeySuboption) SignVerifyOption {
// Implementation note: this option is shared between Sign() and
// Verify(). As such we don't create a KeyProvider here because
// if used in Sign() we would be doing something else.
var protected, public Headers
for _, option := range options {
//nolint:forcetypeassert
switch option.Ident() {
case identProtectedHeaders{}:
protected = option.Value().(Headers)
case identPublicHeaders{}:
public = option.Value().(Headers)
}
}
return &signVerifyOption{
option.New(identKey{}, &withKey{
alg: alg,
key: key,
protected: protected,
public: public,
}),
}
}
// WithKeySet specifies a JWKS (jwk.Set) to use for verification.
//
// Because a JWKS can contain multiple keys and this library cannot tell
// which one of the keys should be used for verification, we by default
// require that both `alg` and `kid` fields in the JWS _and_ the
// key match before a key is considered to be used.
//
// There are ways to override this behavior, but they must be explicitly
// specified by the caller.
//
// To work with keys/JWS messages not having a `kid` field, you may specify
// the suboption `WithKeySetRequired` via `jws.WithKeySetSuboption(jws.WithKeySetRequireKid(false))`.
// This will allow the library to proceed without having to match the `kid` field.
//
// However, it will still check if the `alg` fields in the JWS message and the key(s)
// match. If you must work with JWS messages that do not have an `alg` field,
// you will need to use `jws.WithKeySetSuboption(jws.WithInferAlgorithm(true))`.
//
// See the documentation for `WithInferAlgorithm()` for more details.
func WithKeySet(set jwk.Set, options ...WithKeySetSuboption) VerifyOption {
requireKid := true
var useDefault, inferAlgorithm, multipleKeysPerKeyID bool
for _, option := range options {
//nolint:forcetypeassert
switch option.Ident() {
case identRequireKid{}:
requireKid = option.Value().(bool)
case identUseDefault{}:
useDefault = option.Value().(bool)
case identMultipleKeysPerKeyID{}:
multipleKeysPerKeyID = option.Value().(bool)
case identInferAlgorithmFromKey{}:
inferAlgorithm = option.Value().(bool)
}
}
return WithKeyProvider(&keySetProvider{
set: set,
requireKid: requireKid,
useDefault: useDefault,
multipleKeysPerKeyID: multipleKeysPerKeyID,
inferAlgorithm: inferAlgorithm,
})
}
func WithVerifyAuto(f jwk.Fetcher, options ...jwk.FetchOption) VerifyOption {
if f == nil {
f = jwk.FetchFunc(jwk.Fetch)
}
// the option MUST start with a "disallow no whitelist" to force
// users provide a whitelist
options = append(append([]jwk.FetchOption(nil), jwk.WithFetchWhitelist(allowNoneWhitelist)), options...)
return WithKeyProvider(jkuProvider{
fetcher: f,
options: options,
})
}
type withInsecureNoSignature struct {
protected Headers
}
// This exist as escape hatches to modify the header values after the fact
func (w *withInsecureNoSignature) Protected(v Headers) Headers {
if w.protected == nil && v != nil {
w.protected = v
}
return w.protected
}
// WithInsecureNoSignature creates an option that allows the user to use the
// "none" signature algorithm.
//
// Please note that this is insecure, and should never be used in production
// (this is exactly why specifying "none"/jwa.NoSignature to `jws.WithKey()`
// results in an error when `jws.Sign()` is called -- we do not allow using
// "none" by accident)
//
// TODO: create specific sub-option set for this option
func WithInsecureNoSignature(options ...WithKeySuboption) SignOption {
var protected Headers
for _, option := range options {
//nolint:forcetypeassert
switch option.Ident() {
case identProtectedHeaders{}:
protected = option.Value().(Headers)
}
}
return &signOption{
option.New(identInsecureNoSignature{},
&withInsecureNoSignature{
protected: protected,
},
),
}
}