-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathconfig.go
336 lines (297 loc) · 12.2 KB
/
config.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
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
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
nonce string
tag string
keyID *string
}
// NewSignConfig generates a default configuration.
func NewSignConfig() *SignConfig {
return &SignConfig{
signAlg: true,
signCreated: true,
fakeCreated: 0,
expires: 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
}
// 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
}
// 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
}
// 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: 1,000 ms.
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,000 ms.
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
}
// 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
}
// 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
}
// 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
}
// 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
}