-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathconfig.go
More file actions
419 lines (372 loc) · 15.8 KB
/
config.go
File metadata and controls
419 lines (372 loc) · 15.8 KB
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
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
package httpsign
import (
"fmt"
"log"
"net/http"
"os"
"time"
)
// SignConfig contains additional configuration for the signer.
type SignConfig struct {
signAlg bool
signCreated bool
fakeCreated int64
expires int64
expiresAfter int64
nonce string
tag string
keyID *string
maxBodySize int64
schemeFromRequest func(*http.Request) string
}
// NewSignConfig generates a default configuration.
func NewSignConfig() *SignConfig {
return &SignConfig{
signAlg: true,
signCreated: true,
fakeCreated: 0,
expires: 0,
expiresAfter: 0,
nonce: "",
tag: "", // we disallow an empty tag
keyID: nil,
}
}
// SignAlg indicates that an "alg" signature parameters must be generated and signed (default: true).
func (c *SignConfig) SignAlg(b bool) *SignConfig {
c.signAlg = b
return c
}
// SignCreated indicates that a "created" signature parameters must be generated and signed (default: true).
func (c *SignConfig) SignCreated(b bool) *SignConfig {
c.signCreated = b
return c
}
// setFakeCreated indicates that the specified Unix timestamp must be used instead of the current time
// (default: 0, meaning use current time). Only used for testing.
func (c *SignConfig) setFakeCreated(ts int64) *SignConfig {
c.fakeCreated = ts
return c
}
// SetExpires adds an "expires" parameter containing an expiration deadline, as Unix time.
// Default: 0 (do not add the parameter).
func (c *SignConfig) SetExpires(expires int64) *SignConfig {
c.expires = expires
return c
}
// SetExpiresAfter sets the "expires" parameter to createdTime + delay (seconds).
// Use this for a relative validity window instead of an absolute timestamp.
// Default: 0 (do not add the parameter).
func (c *SignConfig) SetExpiresAfter(delay int64) *SignConfig {
if delay > 0 {
c.expiresAfter = delay
}
return c
}
// SetNonce adds a "nonce" string parameter whose content should be unique per signed message.
// Default: empty string (do not add the parameter).
func (c *SignConfig) SetNonce(nonce string) *SignConfig {
c.nonce = nonce
return c
}
// SetTag adds a "tag" string parameter that defines a per-application or per-protocol signature
// tag, to mitigate cross-protocol attacks.
func (c *SignConfig) SetTag(tag string) *SignConfig {
c.tag = tag
return c
}
// SetKeyID configures a keyid value that will be included as a signature parameter.
func (c *SignConfig) SetKeyID(keyID string) *SignConfig {
c.keyID = &keyID
return c
}
// SetMaxBodySize sets the maximum message body size in bytes when reading for trailers.
// Default: 0 (no limit).
func (c *SignConfig) SetMaxBodySize(maxBytes int64) *SignConfig {
c.maxBodySize = maxBytes
return c
}
// SetSchemeFromRequest sets a function to derive the @scheme value from the request.
// The callback should return a string, typically "http" or "https".
// Use this when behind a TLS-terminating reverse proxy where req.TLS is nil;
// the callback can read X-Forwarded-Proto or similar headers.
// Default: nil (use req.TLS to determine scheme).
func (c *SignConfig) SetSchemeFromRequest(f func(*http.Request) string) *SignConfig {
c.schemeFromRequest = f
return c
}
// VerifyConfig contains additional configuration for the verifier.
type VerifyConfig struct {
verifyCreated bool
notNewerThan time.Duration
notOlderThan time.Duration
allowedAlgs []string
rejectExpired bool
keyID *string
dateWithin time.Duration
allowedTags []string
maxBodySize int64
schemeFromRequest func(*http.Request) string
nonceValidator func(string) error
}
// SetNonceValidator sets a callback to validate the nonce parameter during verification.
// When the signature includes a nonce, the callback is invoked; returning an error fails verification.
// Use this for replay prevention: the callback should check that the nonce has not been seen before
// (e.g. store in a cache or database) and return an error if it has. Nonce uniqueness requires
// application-layer state—the library does not track seen nonces. Default: nil (no validation).
func (v *VerifyConfig) SetNonceValidator(f func(string) error) *VerifyConfig {
v.nonceValidator = f
return v
}
// SetNotNewerThan sets the window for messages that appear to be newer than the current time,
// which can only happen if clocks are out of sync. Default: 2 seconds.
// This is a security parameter; tune it for your deployment.
func (v *VerifyConfig) SetNotNewerThan(notNewerThan time.Duration) *VerifyConfig {
v.notNewerThan = notNewerThan
return v
}
// SetNotOlderThan sets the window for messages that are older than the current time,
// because of network latency. Default: 10 seconds.
// Without nonce validation, this is the only replay defense—signatures can be replayed
// within the window. For sensitive operations (financial, privileged), reduce this value
// or use SetNonceValidator. This is a security parameter; tune it for your deployment.
func (v *VerifyConfig) SetNotOlderThan(notOlderThan time.Duration) *VerifyConfig {
v.notOlderThan = notOlderThan
return v
}
// SetVerifyCreated indicates that the "created" parameter must be within some time window,
// defined by NotNewerThan and NotOlderThan. Default: true.
func (v *VerifyConfig) SetVerifyCreated(verifyCreated bool) *VerifyConfig {
v.verifyCreated = verifyCreated
return v
}
// SetRejectExpired indicates that expired messages (according to the "expires" parameter) must fail verification.
// Default: true.
func (v *VerifyConfig) SetRejectExpired(rejectExpired bool) *VerifyConfig {
v.rejectExpired = rejectExpired
return v
}
// SetAllowedAlgs defines the allowed values of the "alg" parameter.
// This is useful if the actual algorithm used in verification is taken from the message - not a recommended practice.
// Default: an empty list, signifying all values are accepted.
func (v *VerifyConfig) SetAllowedAlgs(allowedAlgs []string) *VerifyConfig {
v.allowedAlgs = allowedAlgs
return v
}
// SetKeyID defines how to verify the keyid parameter, if one exists. If this value is a non-nil string,
// the signature verifies only if the value is the same as was specified here.
// Default: nil.
func (v *VerifyConfig) SetKeyID(keyID string) *VerifyConfig {
v.keyID = &keyID
return v
}
// SetVerifyDateWithin indicates that the Date header should be verified if it exists, and its value
// must be within a certain time duration (positive or negative) of the Created signature parameter.
// This verification is only available if the Created field itself is verified.
// Default: 0, meaning no verification of the Date header.
func (v *VerifyConfig) SetVerifyDateWithin(d time.Duration) *VerifyConfig {
v.dateWithin = d
return v
}
// SetAllowedTags defines the allowed values of the "tag" parameter.
// Default: an empty list, signifying all values are accepted.
func (v *VerifyConfig) SetAllowedTags(allowedTags []string) *VerifyConfig {
v.allowedTags = allowedTags
return v
}
// SetMaxBodySize sets the maximum message body size in bytes when reading for trailers.
// Default: 0 (no limit).
func (v *VerifyConfig) SetMaxBodySize(maxBytes int64) *VerifyConfig {
v.maxBodySize = maxBytes
return v
}
// SetSchemeFromRequest sets a function to derive the @scheme value from the request.
// The callback should return a string, typically "http" or "https".
// Use this when behind a TLS-terminating reverse proxy where req.TLS is nil;
// the callback can read X-Forwarded-Proto or similar headers.
// When the function returns a non-empty string, it overrides the default (req.TLS).
// Default: nil (use req.TLS to determine scheme).
func (v *VerifyConfig) SetSchemeFromRequest(f func(*http.Request) string) *VerifyConfig {
v.schemeFromRequest = f
return v
}
// NewVerifyConfig generates a default configuration.
func NewVerifyConfig() *VerifyConfig {
return &VerifyConfig{
verifyCreated: true,
notNewerThan: 2 * time.Second,
notOlderThan: 10 * time.Second,
rejectExpired: true,
allowedAlgs: []string{},
keyID: nil,
dateWithin: 0, // meaning no constraint
allowedTags: nil, // no constraint
}
}
// HandlerConfig contains additional configuration for the HTTP message handler wrapper.
// Either or both of fetchVerifier and fetchSigner may be nil for the corresponding operation
// to be skipped.
type HandlerConfig struct {
reqNotVerified func(w http.ResponseWriter,
r *http.Request, logger *log.Logger, err error)
fetchVerifier func(r *http.Request) (sigName string, verifier *Verifier)
fetchSigner func(res http.Response, r *http.Request) (sigName string, signer *Signer)
logger *log.Logger
computeDigest bool
digestSchemesSend []string
digestSchemesRecv []string
maxBodySize int64
}
// NewHandlerConfig generates a default configuration. When verification or respectively,
// signing is required, the respective "fetch" callback must be supplied.
func NewHandlerConfig() *HandlerConfig {
return &HandlerConfig{
reqNotVerified: defaultReqNotVerified,
fetchVerifier: nil,
fetchSigner: nil,
logger: log.New(os.Stderr, "httpsign: ", log.LstdFlags|log.Lmsgprefix),
computeDigest: true,
digestSchemesSend: []string{DigestSha256},
digestSchemesRecv: []string{DigestSha256, DigestSha512},
}
}
func defaultReqNotVerified(w http.ResponseWriter, _ *http.Request, logger *log.Logger, err error) {
w.WriteHeader(http.StatusUnauthorized)
if err == nil { // should not happen
_, _ = fmt.Fprintf(w, "Unknown error")
} else {
if logger != nil {
logger.Println("Could not verify request signature: " + err.Error())
}
_, _ = fmt.Fprintln(w, "Could not verify request signature") // For security reasons, do not print error
}
}
// SetReqNotVerified defines a callback to be called when a request fails to verify. The default
// callback sends an unsigned 401 status code with a generic error message. For production, you
// probably need to sign it.
func (h *HandlerConfig) SetReqNotVerified(f func(w http.ResponseWriter, r *http.Request, l *log.Logger,
err error)) *HandlerConfig {
h.reqNotVerified = f
return h
}
// SetFetchVerifier defines a callback that looks at the incoming request and provides
// a Verifier structure. In the simplest case, the signature name is a constant, and the key ID
// and key value are fetched based on the sender's identity, which in turn is gleaned
// from a header or query parameter. If a Verifier cannot be determined, the function should return Verifier as nil.
func (h *HandlerConfig) SetFetchVerifier(f func(r *http.Request) (sigName string, verifier *Verifier)) *HandlerConfig {
h.fetchVerifier = f
return h
}
// SetFetchSigner defines a callback that looks at the incoming request and the response, just before it is sent,
// and provides
// a Signer structure. In the simplest case, the signature name is a constant, and the key ID
// and key value are fetched based on the sender's identity. To simplify this logic,
// it is recommended to use the request's ctx (Context) member
// to store this information. If a Signer cannot be determined, the function should return Signer as nil.
func (h *HandlerConfig) SetFetchSigner(f func(res http.Response, r *http.Request) (sigName string, signer *Signer)) *HandlerConfig {
h.fetchSigner = f
return h
}
// SetLogger defines a logger for cases where an error cannot be returned. The default logger prints to stderr.
// Set to nil to prevent logging.
func (h *HandlerConfig) SetLogger(l *log.Logger) *HandlerConfig {
h.logger = l
return h
}
// SetComputeDigest when set to its default value (true), this flag indicates that
// if the Content-Digest header is in the set of covered components but the header itself is missing,
// the header value will be computed
// and added to the message before sending it; conversely in received messages, if Content-Digest is covered, the digest
// will be computed and validated. Setting the flag to false inhibits this behavior.
func (h *HandlerConfig) SetComputeDigest(b bool) *HandlerConfig {
h.computeDigest = b
return h
}
// SetDigestSchemesSend defines the scheme(s) (cryptographic hash algorithms) to be used to generate the message digest.
// It only needs to be set if a Content-Digest header is signed. Default: DigestSha256
func (h *HandlerConfig) SetDigestSchemesSend(s []string) *HandlerConfig {
h.digestSchemesSend = s
return h
}
// SetDigestSchemesRecv defines the cryptographic algorithms to accept when receiving the
// Content-Digest header. Any recognized algorithm's digest must be correct, but the overall header is valid if at least
// one accepted digest is included. Default: DigestSha256, DigestSha512.
func (h *HandlerConfig) SetDigestSchemesRecv(s []string) *HandlerConfig {
h.digestSchemesRecv = s
return h
}
// SetMaxBodySize sets the maximum message body size in bytes when computing or validating Content-Digest,
// and when buffering the response body for signing in WrapHandler. When exceeded, WrapHandler returns HTTP 500.
// Default: 0 (no limit).
func (h *HandlerConfig) SetMaxBodySize(maxBytes int64) *HandlerConfig {
h.maxBodySize = maxBytes
return h
}
// ClientConfig contains additional configuration for the HTTP client-side wrapper.
// Signing and verification may either be skipped, independently.
type ClientConfig struct {
signatureName string
signer *Signer
verifier *Verifier
fetchVerifier func(res *http.Response, req *http.Request) (sigName string, verifier *Verifier)
computeDigest bool
digestSchemesSend []string
digestSchemesRecv []string
maxBodySize int64
}
// NewClientConfig creates a new, default ClientConfig.
func NewClientConfig() *ClientConfig {
return &ClientConfig{
computeDigest: true,
digestSchemesSend: []string{DigestSha256},
digestSchemesRecv: []string{DigestSha256, DigestSha512},
}
}
// SetSignatureName sets the signature name to be used for signing or verification.
func (c *ClientConfig) SetSignatureName(s string) *ClientConfig {
c.signatureName = s
return c
}
// SetSigner defines a signer for outgoing requests.
func (c *ClientConfig) SetSigner(s *Signer) *ClientConfig {
c.signer = s
return c
}
// SetVerifier defines a verifier for incoming responses.
func (c *ClientConfig) SetVerifier(v *Verifier) *ClientConfig {
c.verifier = v
return c
}
// SetFetchVerifier defines a function that fetches a verifier which may be customized for the incoming response.
func (c *ClientConfig) SetFetchVerifier(fv func(res *http.Response, req *http.Request) (sigName string, verifier *Verifier)) *ClientConfig {
c.fetchVerifier = fv
return c
}
// SetComputeDigest when set to its default value (true), this flag indicates that
// if the Content-Digest header is in the set of covered components but the header itself is missing,
// the header value will be computed
// and added to the message before sending it; conversely in received messages, if Content-Digest is covered, the digest
// will be computed and validated. Setting the flag to false inhibits this behavior.
func (c *ClientConfig) SetComputeDigest(b bool) *ClientConfig {
c.computeDigest = b
return c
}
// SetDigestSchemesSend defines the cryptographic algorithms to use when generating the
// Content-Digest header. Default: DigestSha256.
func (c *ClientConfig) SetDigestSchemesSend(s []string) *ClientConfig {
c.digestSchemesSend = s
return c
}
// SetDigestSchemesRecv defines the cryptographic algorithms to accept when receiving the
// Content-Digest header. Any recognized algorithm's digest must be correct, but the overall header is valid if at least
// one accepted digest is included. Default: DigestSha256, DigestSha512.
func (c *ClientConfig) SetDigestSchemesRecv(s []string) *ClientConfig {
c.digestSchemesRecv = s
return c
}
// SetMaxBodySize sets the maximum message body size in bytes when computing or validating Content-Digest.
// Default: 0 (no limit).
func (c *ClientConfig) SetMaxBodySize(maxBytes int64) *ClientConfig {
c.maxBodySize = maxBytes
return c
}