1 package wfe2
2
3 import (
4 "context"
5 "crypto"
6 "crypto/dsa"
7 "crypto/ecdsa"
8 "crypto/elliptic"
9 "crypto/rsa"
10 "fmt"
11 "net/http"
12 "os"
13 "strings"
14 "testing"
15
16 "github.com/letsencrypt/boulder/core"
17 corepb "github.com/letsencrypt/boulder/core/proto"
18 "github.com/letsencrypt/boulder/goodkey"
19 bgrpc "github.com/letsencrypt/boulder/grpc"
20 "github.com/letsencrypt/boulder/grpc/noncebalancer"
21 "github.com/letsencrypt/boulder/mocks"
22 noncepb "github.com/letsencrypt/boulder/nonce/proto"
23 "github.com/letsencrypt/boulder/probs"
24 sapb "github.com/letsencrypt/boulder/sa/proto"
25 "github.com/letsencrypt/boulder/test"
26 "github.com/letsencrypt/boulder/web"
27 "github.com/prometheus/client_golang/prometheus"
28
29 "google.golang.org/grpc"
30 "gopkg.in/go-jose/go-jose.v2"
31 )
32
33
34
35 func sigAlgForKey(t *testing.T, key interface{}) jose.SignatureAlgorithm {
36 var sigAlg jose.SignatureAlgorithm
37 var err error
38
39
40
41
42 switch k := key.(type) {
43 case rsa.PublicKey:
44 sigAlg, err = sigAlgorithmForKey(&jose.JSONWebKey{Key: &k})
45 case ecdsa.PublicKey:
46 sigAlg, err = sigAlgorithmForKey(&jose.JSONWebKey{Key: &k})
47 default:
48 sigAlg, err = sigAlgorithmForKey(&jose.JSONWebKey{Key: k})
49 }
50 test.Assert(t, err == nil, fmt.Sprintf("Error getting signature algorithm for key %#v", key))
51 return sigAlg
52 }
53
54
55
56 func keyAlgForKey(t *testing.T, key interface{}) string {
57 switch key.(type) {
58 case *rsa.PrivateKey, rsa.PrivateKey:
59 return "RSA"
60 case *ecdsa.PrivateKey, ecdsa.PrivateKey:
61 return "ECDSA"
62 }
63 t.Fatalf("Can't figure out keyAlgForKey: %#v", key)
64 return ""
65 }
66
67
68
69 func pubKeyForKey(t *testing.T, privKey interface{}) interface{} {
70 switch k := privKey.(type) {
71 case *rsa.PrivateKey:
72 return k.PublicKey
73 case *ecdsa.PrivateKey:
74 return k.PublicKey
75 }
76 t.Fatalf("Unable to get public key for private key %#v", privKey)
77 return nil
78 }
79
80
81
82
83 type requestSigner struct {
84 t *testing.T
85 nonceService jose.NonceSource
86 }
87
88
89
90
91
92 func (rs requestSigner) embeddedJWK(
93 privateKey interface{},
94 url string,
95 req string) (*jose.JSONWebSignature, *jose.JSONWebKey, string) {
96
97 var publicKey interface{}
98 if privateKey == nil {
99 signer := loadKey(rs.t, []byte(test1KeyPrivatePEM))
100 privateKey = signer
101 publicKey = signer.Public()
102 } else {
103 publicKey = pubKeyForKey(rs.t, privateKey)
104 }
105
106 signerKey := jose.SigningKey{
107 Key: privateKey,
108 Algorithm: sigAlgForKey(rs.t, publicKey),
109 }
110
111 opts := &jose.SignerOptions{
112 NonceSource: rs.nonceService,
113 EmbedJWK: true,
114 }
115 if url != "" {
116 opts.ExtraHeaders = map[jose.HeaderKey]interface{}{
117 "url": url,
118 }
119 }
120
121 signer, err := jose.NewSigner(signerKey, opts)
122 test.AssertNotError(rs.t, err, "Failed to make signer")
123
124 jws, err := signer.Sign([]byte(req))
125 test.AssertNotError(rs.t, err, "Failed to sign req")
126
127 body := jws.FullSerialize()
128 parsedJWS, err := jose.ParseSigned(body)
129 test.AssertNotError(rs.t, err, "Failed to parse generated JWS")
130
131 return parsedJWS, parsedJWS.Signatures[0].Header.JSONWebKey, body
132 }
133
134
135
136
137
138 func (rs requestSigner) byKeyID(
139 keyID int64,
140 privateKey interface{},
141 url string,
142 req string) (*jose.JSONWebSignature, *jose.JSONWebKey, string) {
143
144 if privateKey == nil {
145 privateKey = loadKey(rs.t, []byte(test1KeyPrivatePEM))
146 }
147
148 jwk := &jose.JSONWebKey{
149 Key: privateKey,
150 Algorithm: keyAlgForKey(rs.t, privateKey),
151 KeyID: fmt.Sprintf("http://localhost/acme/acct/%d", keyID),
152 }
153
154 signerKey := jose.SigningKey{
155 Key: jwk,
156 Algorithm: jose.RS256,
157 }
158
159 opts := &jose.SignerOptions{
160 NonceSource: rs.nonceService,
161 ExtraHeaders: map[jose.HeaderKey]interface{}{
162 "url": url,
163 },
164 }
165
166 signer, err := jose.NewSigner(signerKey, opts)
167 test.AssertNotError(rs.t, err, "Failed to make signer")
168 jws, err := signer.Sign([]byte(req))
169 test.AssertNotError(rs.t, err, "Failed to sign req")
170
171 body := jws.FullSerialize()
172 parsedJWS, err := jose.ParseSigned(body)
173 test.AssertNotError(rs.t, err, "Failed to parse generated JWS")
174
175 return parsedJWS, jwk, body
176 }
177
178
179
180 func (rs requestSigner) missingNonce() *jose.JSONWebSignature {
181 privateKey := loadKey(rs.t, []byte(test1KeyPrivatePEM))
182 jwk := &jose.JSONWebKey{
183 Key: privateKey,
184 Algorithm: keyAlgForKey(rs.t, privateKey),
185 KeyID: "http://localhost/acme/acct/1",
186 }
187 signerKey := jose.SigningKey{
188 Key: jwk,
189 Algorithm: jose.RS256,
190 }
191
192 opts := &jose.SignerOptions{
193 ExtraHeaders: map[jose.HeaderKey]interface{}{
194 "url": "https://example.com/acme/foo",
195 },
196 }
197
198 signer, err := jose.NewSigner(signerKey, opts)
199 test.AssertNotError(rs.t, err, "Failed to make signer")
200 jws, err := signer.Sign([]byte(""))
201 test.AssertNotError(rs.t, err, "Failed to sign req")
202
203 return jws
204 }
205
206
207 func (rs requestSigner) invalidNonce() *jose.JSONWebSignature {
208 privateKey := loadKey(rs.t, []byte(test1KeyPrivatePEM))
209 jwk := &jose.JSONWebKey{
210 Key: privateKey,
211 Algorithm: keyAlgForKey(rs.t, privateKey),
212 KeyID: "http://localhost/acme/acct/1",
213 }
214 signerKey := jose.SigningKey{
215 Key: jwk,
216 Algorithm: jose.RS256,
217 }
218
219 opts := &jose.SignerOptions{
220 NonceSource: badNonceProvider{},
221 ExtraHeaders: map[jose.HeaderKey]interface{}{
222 "url": "https://example.com/acme/foo",
223 },
224 }
225
226 signer, err := jose.NewSigner(signerKey, opts)
227 test.AssertNotError(rs.t, err, "Failed to make signer")
228 jws, err := signer.Sign([]byte(""))
229 test.AssertNotError(rs.t, err, "Failed to sign req")
230
231 body := jws.FullSerialize()
232 parsedJWS, err := jose.ParseSigned(body)
233 test.AssertNotError(rs.t, err, "Failed to parse generated JWS")
234
235 return parsedJWS
236 }
237
238
239
240 func (rs requestSigner) malformedNonce() *jose.JSONWebSignature {
241 privateKey := loadKey(rs.t, []byte(test1KeyPrivatePEM))
242 jwk := &jose.JSONWebKey{
243 Key: privateKey,
244 Algorithm: keyAlgForKey(rs.t, privateKey),
245 KeyID: "http://localhost/acme/acct/1",
246 }
247 signerKey := jose.SigningKey{
248 Key: jwk,
249 Algorithm: jose.RS256,
250 }
251
252 opts := &jose.SignerOptions{
253 NonceSource: badNonceProvider{malformed: true},
254 ExtraHeaders: map[jose.HeaderKey]interface{}{
255 "url": "https://example.com/acme/foo",
256 },
257 }
258
259 signer, err := jose.NewSigner(signerKey, opts)
260 test.AssertNotError(rs.t, err, "Failed to make signer")
261 jws, err := signer.Sign([]byte(""))
262 test.AssertNotError(rs.t, err, "Failed to sign req")
263
264 body := jws.FullSerialize()
265 parsedJWS, err := jose.ParseSigned(body)
266 test.AssertNotError(rs.t, err, "Failed to parse generated JWS")
267
268 return parsedJWS
269 }
270
271
272
273 func (rs requestSigner) shortNonce() *jose.JSONWebSignature {
274 privateKey := loadKey(rs.t, []byte(test1KeyPrivatePEM))
275 jwk := &jose.JSONWebKey{
276 Key: privateKey,
277 Algorithm: keyAlgForKey(rs.t, privateKey),
278 KeyID: "http://localhost/acme/acct/1",
279 }
280 signerKey := jose.SigningKey{
281 Key: jwk,
282 Algorithm: jose.RS256,
283 }
284
285 opts := &jose.SignerOptions{
286 NonceSource: badNonceProvider{shortNonce: true},
287 ExtraHeaders: map[jose.HeaderKey]interface{}{
288 "url": "https://example.com/acme/foo",
289 },
290 }
291
292 signer, err := jose.NewSigner(signerKey, opts)
293 test.AssertNotError(rs.t, err, "Failed to make signer")
294 jws, err := signer.Sign([]byte(""))
295 test.AssertNotError(rs.t, err, "Failed to sign req")
296
297 body := jws.FullSerialize()
298 parsedJWS, err := jose.ParseSigned(body)
299 test.AssertNotError(rs.t, err, "Failed to parse generated JWS")
300
301 return parsedJWS
302 }
303
304 func TestRejectsNone(t *testing.T) {
305 noneJWSBody := `
306 {
307 "header": {
308 "alg": "none",
309 "jwk": {
310 "kty": "RSA",
311 "n": "vrjT",
312 "e": "AQAB"
313 }
314 },
315 "payload": "aGkK",
316 "signature": "ghTIjrhiRl2pQ09vAkUUBbF5KziJdhzOTB-okM9SPRzU8Hyj0W1H5JA1Zoc-A-LuJGNAtYYHWqMw1SeZbT0l9FHcbMPeWDaJNkHS9jz5_g_Oyol8vcrWur2GDtB2Jgw6APtZKrbuGATbrF7g41Wijk6Kk9GXDoCnlfOQOhHhsrFFcWlCPLG-03TtKD6EBBoVBhmlp8DRLs7YguWRZ6jWNaEX-1WiRntBmhLqoqQFtvZxCBw_PRuaRw_RZBd1x2_BNYqEdOmVNC43UHMSJg3y_3yrPo905ur09aUTscf-C_m4Sa4M0FuDKn3bQ_pFrtz-aCCq6rcTIyxYpDqNvHMT2Q"
317 }
318 `
319 noneJWS, err := jose.ParseSigned(noneJWSBody)
320 if err != nil {
321 t.Fatal("Unable to parse noneJWS")
322 }
323 noneJWK := noneJWS.Signatures[0].Header.JSONWebKey
324
325 err = checkAlgorithm(noneJWK, noneJWS.Signatures[0].Header)
326 if err == nil {
327 t.Fatalf("checkAlgorithm did not reject JWS with alg: 'none'")
328 }
329 if err.Error() != "JWS signature header contains unsupported algorithm \"none\", expected one of RS256, ES256, ES384 or ES512" {
330 t.Fatalf("checkAlgorithm rejected JWS with alg: 'none', but for wrong reason: %#v", err)
331 }
332 }
333
334 func TestRejectsHS256(t *testing.T) {
335 hs256JWSBody := `
336 {
337 "header": {
338 "alg": "HS256",
339 "jwk": {
340 "kty": "RSA",
341 "n": "vrjT",
342 "e": "AQAB"
343 }
344 },
345 "payload": "aGkK",
346 "signature": "ghTIjrhiRl2pQ09vAkUUBbF5KziJdhzOTB-okM9SPRzU8Hyj0W1H5JA1Zoc-A-LuJGNAtYYHWqMw1SeZbT0l9FHcbMPeWDaJNkHS9jz5_g_Oyol8vcrWur2GDtB2Jgw6APtZKrbuGATbrF7g41Wijk6Kk9GXDoCnlfOQOhHhsrFFcWlCPLG-03TtKD6EBBoVBhmlp8DRLs7YguWRZ6jWNaEX-1WiRntBmhLqoqQFtvZxCBw_PRuaRw_RZBd1x2_BNYqEdOmVNC43UHMSJg3y_3yrPo905ur09aUTscf-C_m4Sa4M0FuDKn3bQ_pFrtz-aCCq6rcTIyxYpDqNvHMT2Q"
347 }
348 `
349
350 hs256JWS, err := jose.ParseSigned(hs256JWSBody)
351 if err != nil {
352 t.Fatal("Unable to parse hs256JWSBody")
353 }
354 hs256JWK := hs256JWS.Signatures[0].Header.JSONWebKey
355
356 err = checkAlgorithm(hs256JWK, hs256JWS.Signatures[0].Header)
357 if err == nil {
358 t.Fatalf("checkAlgorithm did not reject JWS with alg: 'HS256'")
359 }
360 expected := "JWS signature header contains unsupported algorithm \"HS256\", expected one of RS256, ES256, ES384 or ES512"
361 if err.Error() != expected {
362 t.Fatalf("checkAlgorithm rejected JWS with alg: 'none', but for wrong reason: got %q, wanted %q", err.Error(), expected)
363 }
364 }
365
366 func TestCheckAlgorithm(t *testing.T) {
367 testCases := []struct {
368 key jose.JSONWebKey
369 jws jose.JSONWebSignature
370 expectedErr string
371 }{
372 {
373 jose.JSONWebKey{},
374 jose.JSONWebSignature{
375 Signatures: []jose.Signature{
376 {
377 Header: jose.Header{
378 Algorithm: "RS256",
379 },
380 },
381 },
382 },
383 "JWK contains unsupported key type (expected RSA, or ECDSA P-256, P-384, or P-521)",
384 },
385 {
386 jose.JSONWebKey{
387 Algorithm: "HS256",
388 Key: &rsa.PublicKey{},
389 },
390 jose.JSONWebSignature{
391 Signatures: []jose.Signature{
392 {
393 Header: jose.Header{
394 Algorithm: "HS256",
395 },
396 },
397 },
398 },
399 "JWS signature header contains unsupported algorithm \"HS256\", expected one of RS256, ES256, ES384 or ES512",
400 },
401 {
402 jose.JSONWebKey{
403 Algorithm: "ES256",
404 Key: &dsa.PublicKey{},
405 },
406 jose.JSONWebSignature{
407 Signatures: []jose.Signature{
408 {
409 Header: jose.Header{
410 Algorithm: "ES512",
411 },
412 },
413 },
414 },
415 "JWK contains unsupported key type (expected RSA, or ECDSA P-256, P-384, or P-521)",
416 },
417 {
418 jose.JSONWebKey{
419 Algorithm: "RS256",
420 Key: &rsa.PublicKey{},
421 },
422 jose.JSONWebSignature{
423 Signatures: []jose.Signature{
424 {
425 Header: jose.Header{
426 Algorithm: "ES512",
427 },
428 },
429 },
430 },
431 "JWS signature header algorithm \"ES512\" does not match expected algorithm \"RS256\" for JWK",
432 },
433 {
434 jose.JSONWebKey{
435 Algorithm: "HS256",
436 Key: &rsa.PublicKey{},
437 },
438 jose.JSONWebSignature{
439 Signatures: []jose.Signature{
440 {
441 Header: jose.Header{
442 Algorithm: "RS256",
443 },
444 },
445 },
446 },
447 "JWK key header algorithm \"HS256\" does not match expected algorithm \"RS256\" for JWK",
448 },
449 }
450 for i, tc := range testCases {
451 err := checkAlgorithm(&tc.key, tc.jws.Signatures[0].Header)
452 if tc.expectedErr != "" && err.Error() != tc.expectedErr {
453 t.Errorf("TestCheckAlgorithm %d: Expected %q, got %q", i, tc.expectedErr, err)
454 }
455 }
456 }
457
458 func TestCheckAlgorithmSuccess(t *testing.T) {
459 jwsRS256 := &jose.JSONWebSignature{
460 Signatures: []jose.Signature{
461 {
462 Header: jose.Header{
463 Algorithm: "RS256",
464 },
465 },
466 },
467 }
468 goodJSONWebKeyRS256 := &jose.JSONWebKey{
469 Algorithm: "RS256",
470 Key: &rsa.PublicKey{},
471 }
472 err := checkAlgorithm(goodJSONWebKeyRS256, jwsRS256.Signatures[0].Header)
473 test.AssertNotError(t, err, "RS256 key: Expected nil error")
474
475 badJSONWebKeyRS256 := &jose.JSONWebKey{
476 Algorithm: "ObviouslyWrongButNotZeroValue",
477 Key: &rsa.PublicKey{},
478 }
479 err = checkAlgorithm(badJSONWebKeyRS256, jwsRS256.Signatures[0].Header)
480 test.AssertError(t, err, "RS256 key: Expected nil error")
481 test.AssertContains(t, err.Error(), "JWK key header algorithm \"ObviouslyWrongButNotZeroValue\" does not match expected algorithm \"RS256\" for JWK")
482
483 jwsES256 := &jose.JSONWebSignature{
484 Signatures: []jose.Signature{
485 {
486 Header: jose.Header{
487 Algorithm: "ES256",
488 },
489 },
490 },
491 }
492 goodJSONWebKeyES256 := &jose.JSONWebKey{
493 Algorithm: "ES256",
494 Key: &ecdsa.PublicKey{
495 Curve: elliptic.P256(),
496 },
497 }
498 err = checkAlgorithm(goodJSONWebKeyES256, jwsES256.Signatures[0].Header)
499 test.AssertNotError(t, err, "ES256 key: Expected nil error")
500
501 badJSONWebKeyES256 := &jose.JSONWebKey{
502 Algorithm: "ObviouslyWrongButNotZeroValue",
503 Key: &ecdsa.PublicKey{
504 Curve: elliptic.P256(),
505 },
506 }
507 err = checkAlgorithm(badJSONWebKeyES256, jwsES256.Signatures[0].Header)
508 test.AssertError(t, err, "ES256 key: Expected nil error")
509 test.AssertContains(t, err.Error(), "JWK key header algorithm \"ObviouslyWrongButNotZeroValue\" does not match expected algorithm \"ES256\" for JWK")
510 }
511
512 func TestValidPOSTRequest(t *testing.T) {
513 wfe, _, _ := setupWFE(t)
514
515 dummyContentLength := []string{"pretty long, idk, maybe a nibble or two?"}
516
517 testCases := []struct {
518 Name string
519 Headers map[string][]string
520 Body *string
521 HTTPStatus int
522 ProblemDetail string
523 ErrorStatType string
524 EnforceContentType bool
525 }{
526
527 {
528 Name: "POST without a Content-Length header",
529 Headers: nil,
530 HTTPStatus: http.StatusLengthRequired,
531 ProblemDetail: "missing Content-Length header",
532 ErrorStatType: "ContentLengthRequired",
533 },
534
535 {
536 Name: "POST with a Replay-Nonce HTTP header",
537 Headers: map[string][]string{
538 "Content-Length": dummyContentLength,
539 "Replay-Nonce": {"ima-misplaced-nonce"},
540 "Content-Type": {expectedJWSContentType},
541 },
542 HTTPStatus: http.StatusBadRequest,
543 ProblemDetail: "HTTP requests should NOT contain Replay-Nonce header. Use JWS nonce field",
544 ErrorStatType: "ReplayNonceOutsideJWS",
545 },
546
547 {
548 Name: "POST with an empty POST body",
549 Headers: map[string][]string{
550 "Content-Length": dummyContentLength,
551 "Content-Type": {expectedJWSContentType},
552 },
553 HTTPStatus: http.StatusBadRequest,
554 ProblemDetail: "No body on POST",
555 ErrorStatType: "NoPOSTBody",
556 },
557 {
558 Name: "POST without a Content-Type header",
559 Headers: map[string][]string{
560 "Content-Length": dummyContentLength,
561 },
562 HTTPStatus: http.StatusUnsupportedMediaType,
563 ProblemDetail: fmt.Sprintf(
564 "No Content-Type header on POST. Content-Type must be %q",
565 expectedJWSContentType),
566 ErrorStatType: "NoContentType",
567 EnforceContentType: true,
568 },
569 {
570 Name: "POST with an invalid Content-Type header",
571 Headers: map[string][]string{
572 "Content-Length": dummyContentLength,
573 "Content-Type": {"fresh.and.rare"},
574 },
575 HTTPStatus: http.StatusUnsupportedMediaType,
576 ProblemDetail: fmt.Sprintf(
577 "Invalid Content-Type header on POST. Content-Type must be %q",
578 expectedJWSContentType),
579 ErrorStatType: "WrongContentType",
580 EnforceContentType: true,
581 },
582 }
583
584 for _, tc := range testCases {
585 input := &http.Request{
586 Method: "POST",
587 URL: mustParseURL("/"),
588 Header: tc.Headers,
589 }
590 t.Run(tc.Name, func(t *testing.T) {
591 prob := wfe.validPOSTRequest(input)
592 test.Assert(t, prob != nil, "No error returned for invalid POST")
593 test.AssertEquals(t, prob.Type, probs.MalformedProblem)
594 test.AssertEquals(t, prob.HTTPStatus, tc.HTTPStatus)
595 test.AssertEquals(t, prob.Detail, tc.ProblemDetail)
596 test.AssertMetricWithLabelsEquals(
597 t, wfe.stats.httpErrorCount, prometheus.Labels{"type": tc.ErrorStatType}, 1)
598 })
599 }
600 }
601
602 func TestEnforceJWSAuthType(t *testing.T) {
603 wfe, _, signer := setupWFE(t)
604
605 testKeyIDJWS, _, _ := signer.byKeyID(1, nil, "", "")
606 testEmbeddedJWS, _, _ := signer.embeddedJWK(nil, "", "")
607
608
609 conflictJWSBody := `
610 {
611 "header": {
612 "alg": "RS256",
613 "jwk": {
614 "e": "AQAB",
615 "kty": "RSA",
616 "n": "ppbqGaMFnnq9TeMUryR6WW4Lr5WMgp46KlBXZkNaGDNQoifWt6LheeR5j9MgYkIFU7Z8Jw5-bpJzuBeEVwb-yHGh4Umwo_qKtvAJd44iLjBmhBSxq-OSe6P5hX1LGCByEZlYCyoy98zOtio8VK_XyS5VoOXqchCzBXYf32ksVUTrtH1jSlamKHGz0Q0pRKIsA2fLqkE_MD3jP6wUDD6ExMw_tKYLx21lGcK41WSrRpDH-kcZo1QdgCy2ceNzaliBX1eHmKG0-H8tY4tPQudk-oHQmWTdvUIiHO6gSKMGDZNWv6bq74VTCsRfUEAkuWhqUhgRSGzlvlZ24wjHv5Qdlw"
617 }
618 },
619 "protected": "eyJub25jZSI6ICJibTl1WTJVIiwgInVybCI6ICJodHRwOi8vbG9jYWxob3N0L3Rlc3QiLCAia2lkIjogInRlc3RrZXkifQ",
620 "payload": "Zm9v",
621 "signature": "ghTIjrhiRl2pQ09vAkUUBbF5KziJdhzOTB-okM9SPRzU8Hyj0W1H5JA1Zoc-A-LuJGNAtYYHWqMw1SeZbT0l9FHcbMPeWDaJNkHS9jz5_g_Oyol8vcrWur2GDtB2Jgw6APtZKrbuGATbrF7g41Wijk6Kk9GXDoCnlfOQOhHhsrFFcWlCPLG-03TtKD6EBBoVBhmlp8DRLs7YguWRZ6jWNaEX-1WiRntBmhLqoqQFtvZxCBw_PRuaRw_RZBd1x2_BNYqEdOmVNC43UHMSJg3y_3yrPo905ur09aUTscf-C_m4Sa4M0FuDKn3bQ_pFrtz-aCCq6rcTIyxYpDqNvHMT2Q"
622 }
623 `
624
625 conflictJWS, err := jose.ParseSigned(conflictJWSBody)
626 if err != nil {
627 t.Fatal("Unable to parse conflict JWS")
628 }
629
630 testCases := []struct {
631 Name string
632 JWS *jose.JSONWebSignature
633 ExpectedAuthType jwsAuthType
634 ExpectedResult *probs.ProblemDetails
635 ErrorStatType string
636 }{
637 {
638 Name: "Key ID and embedded JWS",
639 JWS: conflictJWS,
640 ExpectedAuthType: invalidAuthType,
641 ExpectedResult: &probs.ProblemDetails{
642 Type: probs.MalformedProblem,
643 Detail: "jwk and kid header fields are mutually exclusive",
644 HTTPStatus: http.StatusBadRequest,
645 },
646 ErrorStatType: "JWSAuthTypeInvalid",
647 },
648 {
649 Name: "Key ID when expected is embedded JWK",
650 JWS: testKeyIDJWS,
651 ExpectedAuthType: embeddedJWK,
652 ExpectedResult: &probs.ProblemDetails{
653 Type: probs.MalformedProblem,
654 Detail: "No embedded JWK in JWS header",
655 HTTPStatus: http.StatusBadRequest,
656 },
657 ErrorStatType: "JWSAuthTypeWrong",
658 },
659 {
660 Name: "Embedded JWK when expected is Key ID",
661 JWS: testEmbeddedJWS,
662 ExpectedAuthType: embeddedKeyID,
663 ExpectedResult: &probs.ProblemDetails{
664 Type: probs.MalformedProblem,
665 Detail: "No Key ID in JWS header",
666 HTTPStatus: http.StatusBadRequest,
667 },
668 ErrorStatType: "JWSAuthTypeWrong",
669 },
670 {
671 Name: "Key ID when expected is KeyID",
672 JWS: testKeyIDJWS,
673 ExpectedAuthType: embeddedKeyID,
674 ExpectedResult: nil,
675 },
676 {
677 Name: "Embedded JWK when expected is embedded JWK",
678 JWS: testEmbeddedJWS,
679 ExpectedAuthType: embeddedJWK,
680 ExpectedResult: nil,
681 },
682 }
683
684 for _, tc := range testCases {
685 t.Run(tc.Name, func(t *testing.T) {
686 wfe.stats.joseErrorCount.Reset()
687 prob := wfe.enforceJWSAuthType(tc.JWS.Signatures[0].Header, tc.ExpectedAuthType)
688 if tc.ExpectedResult == nil && prob != nil {
689 t.Fatalf("Expected nil result, got %#v", prob)
690 } else {
691 test.AssertMarshaledEquals(t, prob, tc.ExpectedResult)
692 }
693 if tc.ErrorStatType != "" {
694 test.AssertMetricWithLabelsEquals(
695 t, wfe.stats.joseErrorCount, prometheus.Labels{"type": tc.ErrorStatType}, 1)
696 }
697 })
698 }
699 }
700
701 type badNonceProvider struct {
702 malformed bool
703 shortNonce bool
704 }
705
706 func (b badNonceProvider) Nonce() (string, error) {
707 if b.malformed {
708 return "im-a-nonce", nil
709 }
710 if b.shortNonce {
711
712
713
714 return "woww", nil
715 }
716 if os.Getenv("BOULDER_CONFIG_DIR") != "test/config-next" {
717
718 return "mlol3ov77I5Ui-cdaY_k8IcjK58FvbG0y_BCRrx5rGQ8rjA", nil
719 }
720 return "mlolmlol3ov77I5Ui-cdaY_k8IcjK58FvbG0y_BCRrx5rGQ8rjA", nil
721 }
722
723 func TestValidNonce(t *testing.T) {
724 wfe, _, signer := setupWFE(t)
725
726 goodJWS, _, _ := signer.embeddedJWK(nil, "", "")
727
728 testCases := []struct {
729 Name string
730 JWS *jose.JSONWebSignature
731 ExpectedResult *probs.ProblemDetails
732 ErrorStatType string
733
734 SkipConfigNext bool
735
736 SkipConfig bool
737 }{
738 {
739 Name: "No nonce in JWS",
740 JWS: signer.missingNonce(),
741 ExpectedResult: &probs.ProblemDetails{
742 Type: probs.BadNonceProblem,
743 Detail: "JWS has no anti-replay nonce",
744 HTTPStatus: http.StatusBadRequest,
745 },
746 ErrorStatType: "JWSMissingNonce",
747 },
748 {
749 Name: "Malformed nonce in JWS",
750 JWS: signer.malformedNonce(),
751 ExpectedResult: &probs.ProblemDetails{
752 Type: probs.BadNonceProblem,
753 Detail: "JWS has an invalid anti-replay nonce: \"im-a-nonce\"",
754 HTTPStatus: http.StatusBadRequest,
755 },
756 ErrorStatType: "JWSMalformedNonce",
757 },
758 {
759 Name: "Canned nonce shorter than prefixLength in JWS",
760 JWS: signer.shortNonce(),
761 ExpectedResult: &probs.ProblemDetails{
762 Type: probs.BadNonceProblem,
763 Detail: "JWS has an invalid anti-replay nonce: \"woww\"",
764 HTTPStatus: http.StatusBadRequest,
765 },
766 ErrorStatType: "JWSMalformedNonce",
767 },
768 {
769 Name: "Invalid nonce in JWS (test/config)",
770 JWS: signer.invalidNonce(),
771 ExpectedResult: &probs.ProblemDetails{
772 Type: probs.BadNonceProblem,
773 Detail: "JWS has an invalid anti-replay nonce: \"mlol3ov77I5Ui-cdaY_k8IcjK58FvbG0y_BCRrx5rGQ8rjA\"",
774 HTTPStatus: http.StatusBadRequest,
775 },
776 ErrorStatType: "JWSInvalidNonce",
777 SkipConfigNext: true,
778 },
779 {
780 Name: "Invalid nonce in JWS (test/config-next)",
781 JWS: signer.invalidNonce(),
782 ExpectedResult: &probs.ProblemDetails{
783 Type: probs.BadNonceProblem,
784 Detail: "JWS has an invalid anti-replay nonce: \"mlolmlol3ov77I5Ui-cdaY_k8IcjK58FvbG0y_BCRrx5rGQ8rjA\"",
785 HTTPStatus: http.StatusBadRequest,
786 },
787 ErrorStatType: "JWSInvalidNonce",
788 SkipConfig: true,
789 },
790 {
791 Name: "Valid nonce in JWS",
792 JWS: goodJWS,
793 ExpectedResult: nil,
794 },
795 }
796
797 for _, tc := range testCases {
798 t.Run(tc.Name, func(t *testing.T) {
799
800 if os.Getenv("BOULDER_CONFIG_DIR") == "test/config-next" {
801 if tc.SkipConfigNext {
802 t.Skip("Skipping test in config-next")
803 }
804 } else if tc.SkipConfig {
805 t.Skip("Skipping test in config")
806 }
807 wfe.stats.joseErrorCount.Reset()
808 prob := wfe.validNonce(context.Background(), tc.JWS.Signatures[0].Header)
809 if tc.ExpectedResult == nil && prob != nil {
810 t.Fatalf("Expected nil result, got %#v", prob)
811 } else {
812 test.AssertMarshaledEquals(t, prob, tc.ExpectedResult)
813 }
814 if tc.ErrorStatType != "" {
815 test.AssertMetricWithLabelsEquals(
816 t, wfe.stats.joseErrorCount, prometheus.Labels{"type": tc.ErrorStatType}, 1)
817 }
818 })
819 }
820 }
821
822
823
824 type noBackendsNonceRedeemer struct{}
825
826 func (n noBackendsNonceRedeemer) Redeem(ctx context.Context, _ *noncepb.NonceMessage, opts ...grpc.CallOption) (*noncepb.ValidMessage, error) {
827 return nil, noncebalancer.ErrNoBackendsMatchPrefix.Err()
828 }
829
830 func TestValidNonce_NoMatchingBackendFound(t *testing.T) {
831
832 if os.Getenv("BOULDER_CONFIG_DIR") != "test/config-next" {
833 t.Skip("Skipping test in config")
834 }
835 wfe, _, signer := setupWFE(t)
836 goodJWS, _, _ := signer.embeddedJWK(nil, "", "")
837 wfe.rnc = noBackendsNonceRedeemer{}
838
839
840
841 prob := wfe.validNonce(context.Background(), goodJWS.Signatures[0].Header)
842 test.Assert(t, prob != nil, "Expected error for valid nonce with no backend")
843 test.AssertEquals(t, prob.Type, probs.BadNonceProblem)
844 test.AssertEquals(t, prob.HTTPStatus, http.StatusBadRequest)
845 test.AssertContains(t, prob.Detail, "JWS has an invalid anti-replay nonce")
846 test.AssertMetricWithLabelsEquals(t, wfe.stats.nonceNoMatchingBackendCount, prometheus.Labels{}, 1)
847 }
848
849 func (rs requestSigner) signExtraHeaders(
850 headers map[jose.HeaderKey]interface{}) (*jose.JSONWebSignature, string) {
851 privateKey := loadKey(rs.t, []byte(test1KeyPrivatePEM))
852
853 signerKey := jose.SigningKey{
854 Key: privateKey,
855 Algorithm: sigAlgForKey(rs.t, privateKey.Public()),
856 }
857
858 opts := &jose.SignerOptions{
859 NonceSource: rs.nonceService,
860 EmbedJWK: true,
861 ExtraHeaders: headers,
862 }
863
864 signer, err := jose.NewSigner(signerKey, opts)
865 test.AssertNotError(rs.t, err, "Failed to make signer")
866
867 jws, err := signer.Sign([]byte(""))
868 test.AssertNotError(rs.t, err, "Failed to sign req")
869
870 body := jws.FullSerialize()
871 parsedJWS, err := jose.ParseSigned(body)
872 test.AssertNotError(rs.t, err, "Failed to parse generated JWS")
873
874 return parsedJWS, body
875 }
876
877 func TestValidPOSTURL(t *testing.T) {
878 wfe, _, signer := setupWFE(t)
879
880
881 noHeadersJWS, noHeadersJWSBody := signer.signExtraHeaders(nil)
882 noHeadersRequest := makePostRequestWithPath("test-path", noHeadersJWSBody)
883
884
885 noURLHeaders := map[jose.HeaderKey]interface{}{
886 "nifty": "swell",
887 }
888 noURLHeaderJWS, noURLHeaderJWSBody := signer.signExtraHeaders(noURLHeaders)
889 noURLHeaderRequest := makePostRequestWithPath("test-path", noURLHeaderJWSBody)
890
891
892 wrongURLHeaders := map[jose.HeaderKey]interface{}{
893 "url": "foobar",
894 }
895 wrongURLHeaderJWS, wrongURLHeaderJWSBody := signer.signExtraHeaders(wrongURLHeaders)
896 wrongURLHeaderRequest := makePostRequestWithPath("test-path", wrongURLHeaderJWSBody)
897
898 correctURLHeaderJWS, _, correctURLHeaderJWSBody := signer.embeddedJWK(nil, "http://localhost/test-path", "")
899 correctURLHeaderRequest := makePostRequestWithPath("test-path", correctURLHeaderJWSBody)
900
901 testCases := []struct {
902 Name string
903 JWS *jose.JSONWebSignature
904 Request *http.Request
905 ExpectedResult *probs.ProblemDetails
906 ErrorStatType string
907 }{
908 {
909 Name: "No extra headers in JWS",
910 JWS: noHeadersJWS,
911 Request: noHeadersRequest,
912 ExpectedResult: &probs.ProblemDetails{
913 Type: probs.MalformedProblem,
914 Detail: "JWS header parameter 'url' required",
915 HTTPStatus: http.StatusBadRequest,
916 },
917 ErrorStatType: "JWSNoExtraHeaders",
918 },
919 {
920 Name: "No URL header in JWS",
921 JWS: noURLHeaderJWS,
922 Request: noURLHeaderRequest,
923 ExpectedResult: &probs.ProblemDetails{
924 Type: probs.MalformedProblem,
925 Detail: "JWS header parameter 'url' required",
926 HTTPStatus: http.StatusBadRequest,
927 },
928 ErrorStatType: "JWSMissingURL",
929 },
930 {
931 Name: "Wrong URL header in JWS",
932 JWS: wrongURLHeaderJWS,
933 Request: wrongURLHeaderRequest,
934 ExpectedResult: &probs.ProblemDetails{
935 Type: probs.MalformedProblem,
936 Detail: "JWS header parameter 'url' incorrect. Expected \"http://localhost/test-path\" got \"foobar\"",
937 HTTPStatus: http.StatusBadRequest,
938 },
939 ErrorStatType: "JWSMismatchedURL",
940 },
941 {
942 Name: "Correct URL header in JWS",
943 JWS: correctURLHeaderJWS,
944 Request: correctURLHeaderRequest,
945 ExpectedResult: nil,
946 },
947 }
948
949 for _, tc := range testCases {
950 t.Run(tc.Name, func(t *testing.T) {
951 tc.Request.Header.Add("Content-Type", expectedJWSContentType)
952 wfe.stats.joseErrorCount.Reset()
953 prob := wfe.validPOSTURL(tc.Request, tc.JWS.Signatures[0].Header)
954 if tc.ExpectedResult == nil && prob != nil {
955 t.Fatalf("Expected nil result, got %#v", prob)
956 } else {
957 test.AssertMarshaledEquals(t, prob, tc.ExpectedResult)
958 }
959 if tc.ErrorStatType != "" {
960 test.AssertMetricWithLabelsEquals(
961 t, wfe.stats.joseErrorCount, prometheus.Labels{"type": tc.ErrorStatType}, 1)
962 }
963 })
964 }
965 }
966
967 func (rs requestSigner) multiSigJWS() (*jose.JSONWebSignature, string) {
968 privateKeyA := loadKey(rs.t, []byte(test1KeyPrivatePEM))
969 privateKeyB := loadKey(rs.t, []byte(test2KeyPrivatePEM))
970
971 signerKeyA := jose.SigningKey{
972 Key: privateKeyA,
973 Algorithm: sigAlgForKey(rs.t, privateKeyA.Public()),
974 }
975
976 signerKeyB := jose.SigningKey{
977 Key: privateKeyB,
978 Algorithm: sigAlgForKey(rs.t, privateKeyB.Public()),
979 }
980
981 opts := &jose.SignerOptions{
982 NonceSource: rs.nonceService,
983 EmbedJWK: true,
984 }
985
986 signer, err := jose.NewMultiSigner([]jose.SigningKey{signerKeyA, signerKeyB}, opts)
987 test.AssertNotError(rs.t, err, "Failed to make multi signer")
988
989 jws, err := signer.Sign([]byte(""))
990 test.AssertNotError(rs.t, err, "Failed to sign req")
991
992 body := jws.FullSerialize()
993 parsedJWS, err := jose.ParseSigned(body)
994 test.AssertNotError(rs.t, err, "Failed to parse generated JWS")
995
996 return parsedJWS, body
997 }
998
999 func TestParseJWSRequest(t *testing.T) {
1000 wfe, _, signer := setupWFE(t)
1001
1002 _, tooManySigsJWSBody := signer.multiSigJWS()
1003
1004 _, _, validJWSBody := signer.embeddedJWK(nil, "http://localhost/test-path", "")
1005 validJWSRequest := makePostRequestWithPath("test-path", validJWSBody)
1006
1007 missingSigsJWSBody := `{"payload":"Zm9x","protected":"eyJhbGciOiJSUzI1NiIsImp3ayI6eyJrdHkiOiJSU0EiLCJuIjoicW5BUkxyVDdYejRnUmNLeUxkeWRtQ3ItZXk5T3VQSW1YNFg0MHRoazNvbjI2RmtNem5SM2ZSanM2NmVMSzdtbVBjQlo2dU9Kc2VVUlU2d0FhWk5tZW1vWXgxZE12cXZXV0l5aVFsZUhTRDdROHZCcmhSNnVJb080akF6SlpSLUNoelp1U0R0N2lITi0zeFVWc3B1NVhHd1hVX01WSlpzaFR3cDRUYUZ4NWVsSElUX09iblR2VE9VM1hoaXNoMDdBYmdaS21Xc1ZiWGg1cy1DcklpY1U0T2V4SlBndW5XWl9ZSkp1ZU9LbVR2bkxsVFY0TXpLUjJvWmxCS1oyN1MwLVNmZFZfUUR4X3lkbGU1b01BeUtWdGxBVjM1Y3lQTUlzWU53Z1VHQkNkWV8yVXppNWVYMGxUYzdNUFJ3ejZxUjFraXAtaTU5VmNHY1VRZ3FIVjZGeXF3IiwiZSI6IkFRQUIifSwia2lkIjoiIiwibm9uY2UiOiJyNHpuenZQQUVwMDlDN1JwZUtYVHhvNkx3SGwxZVBVdmpGeXhOSE1hQnVvIiwidXJsIjoiaHR0cDovL2xvY2FsaG9zdC9hY21lL25ldy1yZWcifQ"}`
1008 missingSigsJWSRequest := makePostRequestWithPath("test-path", missingSigsJWSBody)
1009
1010 unprotectedHeadersJWSBody := `
1011 {
1012 "header": {
1013 "alg": "RS256",
1014 "kid": "unprotected key id"
1015 },
1016 "protected": "eyJub25jZSI6ICJibTl1WTJVIiwgInVybCI6ICJodHRwOi8vbG9jYWxob3N0L3Rlc3QiLCAia2lkIjogInRlc3RrZXkifQ",
1017 "payload": "Zm9v",
1018 "signature": "PKWWclRsiHF4bm-nmpxDez6Y_3Mdtu263YeYklbGYt1EiMOLiKY_dr_EqhUUKAKEWysFLO-hQLXVU7kVkHeYWQFFOA18oFgcZgkSF2Pr3DNZrVj9e2gl0eZ2i2jk6X5GYPt1lIfok_DrL92wrxEKGcrmxqXXGm0JgP6Al2VGapKZK2HaYbCHoGvtzNmzUX9rC21sKewq5CquJRvTmvQp5bmU7Q9KeafGibFr0jl6IA3W5LBGgf6xftuUtEVEbKmKaKtaG7tXsQH1mIVOPUZZoLWz9sWJSFLmV0QSXm3ZHV0DrOhLfcADbOCoQBMeGdseBQZuUO541A3BEKGv2Aikjw"
1019 }
1020 `
1021
1022 wrongSignaturesFieldJWSBody := `
1023 {
1024 "protected": "eyJub25jZSI6ICJibTl1WTJVIiwgInVybCI6ICJodHRwOi8vbG9jYWxob3N0L3Rlc3QiLCAia2lkIjogInRlc3RrZXkifQ",
1025 "payload": "Zm9v",
1026 "signatures": ["PKWWclRsiHF4bm-nmpxDez6Y_3Mdtu263YeYklbGYt1EiMOLiKY_dr_EqhUUKAKEWysFLO-hQLXVU7kVkHeYWQFFOA18oFgcZgkSF2Pr3DNZrVj9e2gl0eZ2i2jk6X5GYPt1lIfok_DrL92wrxEKGcrmxqXXGm0JgP6Al2VGapKZK2HaYbCHoGvtzNmzUX9rC21sKewq5CquJRvTmvQp5bmU7Q9KeafGibFr0jl6IA3W5LBGgf6xftuUtEVEbKmKaKtaG7tXsQH1mIVOPUZZoLWz9sWJSFLmV0QSXm3ZHV0DrOhLfcADbOCoQBMeGdseBQZuUO541A3BEKGv2Aikjw"]
1027 }
1028 `
1029
1030 testCases := []struct {
1031 Name string
1032 Request *http.Request
1033 ExpectedProblem *probs.ProblemDetails
1034 ErrorStatType string
1035 }{
1036 {
1037 Name: "Invalid POST request",
1038
1039 Request: &http.Request{
1040 Method: "POST",
1041 URL: mustParseURL("/"),
1042 },
1043 ExpectedProblem: &probs.ProblemDetails{
1044 Type: probs.MalformedProblem,
1045 Detail: "missing Content-Length header",
1046 HTTPStatus: http.StatusLengthRequired,
1047 },
1048 },
1049 {
1050 Name: "Invalid JWS in POST body",
1051 Request: makePostRequestWithPath("test-path", `{`),
1052 ExpectedProblem: &probs.ProblemDetails{
1053 Type: probs.MalformedProblem,
1054 Detail: "Parse error reading JWS",
1055 HTTPStatus: http.StatusBadRequest,
1056 },
1057 ErrorStatType: "JWSUnmarshalFailed",
1058 },
1059 {
1060 Name: "Too few signatures in JWS",
1061 Request: missingSigsJWSRequest,
1062 ExpectedProblem: &probs.ProblemDetails{
1063 Type: probs.MalformedProblem,
1064 Detail: "POST JWS not signed",
1065 HTTPStatus: http.StatusBadRequest,
1066 },
1067 ErrorStatType: "JWSEmptySignature",
1068 },
1069 {
1070 Name: "Too many signatures in JWS",
1071 Request: makePostRequestWithPath("test-path", tooManySigsJWSBody),
1072 ExpectedProblem: &probs.ProblemDetails{
1073 Type: probs.MalformedProblem,
1074 Detail: "JWS \"signatures\" field not allowed. Only the \"signature\" field should contain a signature",
1075 HTTPStatus: http.StatusBadRequest,
1076 },
1077 ErrorStatType: "JWSMultiSig",
1078 },
1079 {
1080 Name: "Unprotected JWS headers",
1081 Request: makePostRequestWithPath("test-path", unprotectedHeadersJWSBody),
1082 ExpectedProblem: &probs.ProblemDetails{
1083 Type: probs.MalformedProblem,
1084 Detail: "JWS \"header\" field not allowed. All headers must be in \"protected\" field",
1085 HTTPStatus: http.StatusBadRequest,
1086 },
1087 ErrorStatType: "JWSUnprotectedHeaders",
1088 },
1089 {
1090 Name: "Unsupported signatures field in JWS",
1091 Request: makePostRequestWithPath("test-path", wrongSignaturesFieldJWSBody),
1092 ExpectedProblem: &probs.ProblemDetails{
1093 Type: probs.MalformedProblem,
1094 Detail: "JWS \"signatures\" field not allowed. Only the \"signature\" field should contain a signature",
1095 HTTPStatus: http.StatusBadRequest,
1096 },
1097 ErrorStatType: "JWSMultiSig",
1098 },
1099 {
1100 Name: "Valid JWS in POST request",
1101 Request: validJWSRequest,
1102 ExpectedProblem: nil,
1103 },
1104 {
1105 Name: "POST body too large",
1106 Request: makePostRequestWithPath("test-path",
1107 fmt.Sprintf(`{"a":"%s"}`, strings.Repeat("a", 50000))),
1108 ExpectedProblem: &probs.ProblemDetails{
1109 Type: probs.UnauthorizedProblem,
1110 Detail: "request body too large",
1111 HTTPStatus: http.StatusForbidden,
1112 },
1113 },
1114 }
1115
1116 for _, tc := range testCases {
1117 t.Run(tc.Name, func(t *testing.T) {
1118 wfe.stats.joseErrorCount.Reset()
1119 _, prob := wfe.parseJWSRequest(tc.Request)
1120 if tc.ExpectedProblem == nil && prob != nil {
1121 t.Fatalf("Expected nil problem, got %#v\n", prob)
1122 } else {
1123 test.AssertMarshaledEquals(t, prob, tc.ExpectedProblem)
1124 }
1125 if tc.ErrorStatType != "" {
1126 test.AssertMetricWithLabelsEquals(
1127 t, wfe.stats.joseErrorCount, prometheus.Labels{"type": tc.ErrorStatType}, 1)
1128 }
1129 })
1130 }
1131 }
1132
1133 func TestExtractJWK(t *testing.T) {
1134 wfe, _, signer := setupWFE(t)
1135
1136 keyIDJWS, _, _ := signer.byKeyID(1, nil, "", "")
1137 goodJWS, goodJWK, _ := signer.embeddedJWK(nil, "", "")
1138
1139 testCases := []struct {
1140 Name string
1141 JWS *jose.JSONWebSignature
1142 ExpectedKey *jose.JSONWebKey
1143 ExpectedProblem *probs.ProblemDetails
1144 }{
1145 {
1146 Name: "JWS with wrong auth type (Key ID vs embedded JWK)",
1147 JWS: keyIDJWS,
1148 ExpectedProblem: &probs.ProblemDetails{
1149 Type: probs.MalformedProblem,
1150 Detail: "No embedded JWK in JWS header",
1151 HTTPStatus: http.StatusBadRequest,
1152 },
1153 },
1154 {
1155 Name: "Valid JWS with embedded JWK",
1156 JWS: goodJWS,
1157 ExpectedKey: goodJWK,
1158 },
1159 }
1160
1161 for _, tc := range testCases {
1162 t.Run(tc.Name, func(t *testing.T) {
1163 jwkHeader, prob := wfe.extractJWK(tc.JWS.Signatures[0].Header)
1164 if tc.ExpectedProblem == nil && prob != nil {
1165 t.Fatalf("Expected nil problem, got %#v\n", prob)
1166 } else if tc.ExpectedProblem == nil {
1167 test.AssertMarshaledEquals(t, jwkHeader, tc.ExpectedKey)
1168 } else {
1169 test.AssertMarshaledEquals(t, prob, tc.ExpectedProblem)
1170 }
1171 })
1172 }
1173 }
1174
1175 func (rs requestSigner) specifyKeyID(keyID string) (*jose.JSONWebSignature, string) {
1176 privateKey := loadKey(rs.t, []byte(test1KeyPrivatePEM))
1177
1178 if keyID == "" {
1179 keyID = "this is an invalid non-numeric key ID"
1180 }
1181
1182 jwk := &jose.JSONWebKey{
1183 Key: privateKey,
1184 Algorithm: "RSA",
1185 KeyID: keyID,
1186 }
1187
1188 signerKey := jose.SigningKey{
1189 Key: jwk,
1190 Algorithm: jose.RS256,
1191 }
1192
1193 opts := &jose.SignerOptions{
1194 NonceSource: rs.nonceService,
1195 ExtraHeaders: map[jose.HeaderKey]interface{}{
1196 "url": "http://localhost",
1197 },
1198 }
1199
1200 signer, err := jose.NewSigner(signerKey, opts)
1201 test.AssertNotError(rs.t, err, "Failed to make signer")
1202
1203 jws, err := signer.Sign([]byte(""))
1204 test.AssertNotError(rs.t, err, "Failed to sign req")
1205
1206 body := jws.FullSerialize()
1207 parsedJWS, err := jose.ParseSigned(body)
1208 test.AssertNotError(rs.t, err, "Failed to parse generated JWS")
1209
1210 return parsedJWS, body
1211 }
1212
1213 func TestLookupJWK(t *testing.T) {
1214 wfe, _, signer := setupWFE(t)
1215
1216 embeddedJWS, _, embeddedJWSBody := signer.embeddedJWK(nil, "", "")
1217 invalidKeyIDJWS, invalidKeyIDJWSBody := signer.specifyKeyID("https://acme-99.lettuceencrypt.org/acme/reg/1")
1218
1219 errorIDJWS, _, errorIDJWSBody := signer.byKeyID(100, nil, "", "")
1220
1221 missingIDJWS, _, missingIDJWSBody := signer.byKeyID(102, nil, "", "")
1222
1223 deactivatedIDJWS, _, deactivatedIDJWSBody := signer.byKeyID(3, nil, "", "")
1224
1225 wfe.LegacyKeyIDPrefix = "https://acme-v00.lettuceencrypt.org/acme/reg/"
1226 legacyKeyIDJWS, legacyKeyIDJWSBody := signer.specifyKeyID(wfe.LegacyKeyIDPrefix + "1")
1227
1228 nonNumericKeyIDJWS, nonNumericKeyIDJWSBody := signer.specifyKeyID(wfe.LegacyKeyIDPrefix + "abcd")
1229
1230 validJWS, validKey, validJWSBody := signer.byKeyID(1, nil, "", "")
1231 validAccountPB, _ := wfe.sa.GetRegistration(context.Background(), &sapb.RegistrationID{Id: 1})
1232 validAccount, _ := bgrpc.PbToRegistration(validAccountPB)
1233
1234
1235
1236 testCases := []struct {
1237 Name string
1238 JWS *jose.JSONWebSignature
1239 Request *http.Request
1240 ExpectedProblem *probs.ProblemDetails
1241 ExpectedKey *jose.JSONWebKey
1242 ExpectedAccount *core.Registration
1243 ErrorStatType string
1244 }{
1245 {
1246 Name: "JWS with wrong auth type (embedded JWK vs Key ID)",
1247 JWS: embeddedJWS,
1248 Request: makePostRequestWithPath("test-path", embeddedJWSBody),
1249 ExpectedProblem: &probs.ProblemDetails{
1250 Type: probs.MalformedProblem,
1251 Detail: "No Key ID in JWS header",
1252 HTTPStatus: http.StatusBadRequest,
1253 },
1254 ErrorStatType: "JWSAuthTypeWrong",
1255 },
1256 {
1257 Name: "JWS with invalid key ID URL",
1258 JWS: invalidKeyIDJWS,
1259 Request: makePostRequestWithPath("test-path", invalidKeyIDJWSBody),
1260 ExpectedProblem: &probs.ProblemDetails{
1261 Type: probs.MalformedProblem,
1262 Detail: "KeyID header contained an invalid account URL: \"https://acme-99.lettuceencrypt.org/acme/reg/1\"",
1263 HTTPStatus: http.StatusBadRequest,
1264 },
1265 ErrorStatType: "JWSInvalidKeyID",
1266 },
1267 {
1268 Name: "JWS with non-numeric account ID in key ID URL",
1269 JWS: nonNumericKeyIDJWS,
1270 Request: makePostRequestWithPath("test-path", nonNumericKeyIDJWSBody),
1271 ExpectedProblem: &probs.ProblemDetails{
1272 Type: probs.MalformedProblem,
1273 Detail: "Malformed account ID in KeyID header URL: \"https://acme-v00.lettuceencrypt.org/acme/reg/abcd\"",
1274 HTTPStatus: http.StatusBadRequest,
1275 },
1276 ErrorStatType: "JWSInvalidKeyID",
1277 },
1278 {
1279 Name: "JWS with account ID that causes GetRegistration error",
1280 JWS: errorIDJWS,
1281 Request: makePostRequestWithPath("test-path", errorIDJWSBody),
1282 ExpectedProblem: &probs.ProblemDetails{
1283 Type: probs.ServerInternalProblem,
1284 Detail: "Error retrieving account \"http://localhost/acme/acct/100\"",
1285 HTTPStatus: http.StatusInternalServerError,
1286 },
1287 ErrorStatType: "JWSKeyIDLookupFailed",
1288 },
1289 {
1290 Name: "JWS with account ID that doesn't exist",
1291 JWS: missingIDJWS,
1292 Request: makePostRequestWithPath("test-path", missingIDJWSBody),
1293 ExpectedProblem: &probs.ProblemDetails{
1294 Type: probs.AccountDoesNotExistProblem,
1295 Detail: "Account \"http://localhost/acme/acct/102\" not found",
1296 HTTPStatus: http.StatusBadRequest,
1297 },
1298 ErrorStatType: "JWSKeyIDNotFound",
1299 },
1300 {
1301 Name: "JWS with account ID that is deactivated",
1302 JWS: deactivatedIDJWS,
1303 Request: makePostRequestWithPath("test-path", deactivatedIDJWSBody),
1304 ExpectedProblem: &probs.ProblemDetails{
1305 Type: probs.UnauthorizedProblem,
1306 Detail: "Account is not valid, has status \"deactivated\"",
1307 HTTPStatus: http.StatusForbidden,
1308 },
1309 ErrorStatType: "JWSKeyIDAccountInvalid",
1310 },
1311 {
1312 Name: "Valid JWS with legacy account ID",
1313 JWS: legacyKeyIDJWS,
1314 Request: makePostRequestWithPath("test-path", legacyKeyIDJWSBody),
1315 ExpectedKey: validKey,
1316 ExpectedAccount: &validAccount,
1317 },
1318 {
1319 Name: "Valid JWS with valid account ID",
1320 JWS: validJWS,
1321 Request: makePostRequestWithPath("test-path", validJWSBody),
1322 ExpectedKey: validKey,
1323 ExpectedAccount: &validAccount,
1324 },
1325 }
1326 for _, tc := range testCases {
1327 t.Run(tc.Name, func(t *testing.T) {
1328 wfe.stats.joseErrorCount.Reset()
1329 inputLogEvent := newRequestEvent()
1330 jwkHeader, acct, prob := wfe.lookupJWK(tc.JWS.Signatures[0].Header, context.Background(), tc.Request, inputLogEvent)
1331 if tc.ExpectedProblem == nil && prob != nil {
1332 t.Fatalf("Expected nil problem, got %#v\n", prob)
1333 } else if tc.ExpectedProblem == nil {
1334 inThumb, _ := tc.ExpectedKey.Thumbprint(crypto.SHA256)
1335 outThumb, _ := jwkHeader.Thumbprint(crypto.SHA256)
1336 test.AssertDeepEquals(t, inThumb, outThumb)
1337 test.AssertMarshaledEquals(t, acct, tc.ExpectedAccount)
1338 test.AssertEquals(t, inputLogEvent.Requester, acct.ID)
1339 } else {
1340 test.AssertMarshaledEquals(t, prob, tc.ExpectedProblem)
1341 }
1342 if tc.ErrorStatType != "" {
1343 test.AssertMetricWithLabelsEquals(
1344 t, wfe.stats.joseErrorCount, prometheus.Labels{"type": tc.ErrorStatType}, 1)
1345 }
1346 })
1347 }
1348 }
1349
1350 func TestValidJWSForKey(t *testing.T) {
1351 wfe, _, signer := setupWFE(t)
1352
1353 payload := `{ "test": "payload" }`
1354 testURL := "http://localhost/test"
1355 goodJWS, goodJWK, _ := signer.embeddedJWK(nil, testURL, payload)
1356
1357
1358 badSigJWSBody := `{"payload":"Zm9x","protected":"eyJhbGciOiJSUzI1NiIsImp3ayI6eyJrdHkiOiJSU0EiLCJuIjoicW5BUkxyVDdYejRnUmNLeUxkeWRtQ3ItZXk5T3VQSW1YNFg0MHRoazNvbjI2RmtNem5SM2ZSanM2NmVMSzdtbVBjQlo2dU9Kc2VVUlU2d0FhWk5tZW1vWXgxZE12cXZXV0l5aVFsZUhTRDdROHZCcmhSNnVJb080akF6SlpSLUNoelp1U0R0N2lITi0zeFVWc3B1NVhHd1hVX01WSlpzaFR3cDRUYUZ4NWVsSElUX09iblR2VE9VM1hoaXNoMDdBYmdaS21Xc1ZiWGg1cy1DcklpY1U0T2V4SlBndW5XWl9ZSkp1ZU9LbVR2bkxsVFY0TXpLUjJvWmxCS1oyN1MwLVNmZFZfUUR4X3lkbGU1b01BeUtWdGxBVjM1Y3lQTUlzWU53Z1VHQkNkWV8yVXppNWVYMGxUYzdNUFJ3ejZxUjFraXAtaTU5VmNHY1VRZ3FIVjZGeXF3IiwiZSI6IkFRQUIifSwia2lkIjoiIiwibm9uY2UiOiJyNHpuenZQQUVwMDlDN1JwZUtYVHhvNkx3SGwxZVBVdmpGeXhOSE1hQnVvIiwidXJsIjoiaHR0cDovL2xvY2FsaG9zdC9hY21lL25ldy1yZWcifQ","signature":"jcTdxSygm_cvD7KbXqsxgnoPApCTSkV4jolToSOd2ciRkg5W7Yl0ZKEEKwOc-dYIbQiwGiDzisyPCicwWsOUA1WSqHylKvZ3nxSMc6KtwJCW2DaOqcf0EEjy5VjiZJUrOt2c-r6b07tbn8sfOJKwlF2lsOeGi4s-rtvvkeQpAU-AWauzl9G4bv2nDUeCviAZjHx_PoUC-f9GmZhYrbDzAvXZ859ktM6RmMeD0OqPN7bhAeju2j9Gl0lnryZMtq2m0J2m1ucenQBL1g4ZkP1JiJvzd2cAz5G7Ftl2YeJJyWhqNd3qq0GVOt1P11s8PTGNaSoM0iR9QfUxT9A6jxARtg"}`
1359 badJWS, err := jose.ParseSigned(badSigJWSBody)
1360 test.AssertNotError(t, err, "error loading badSigJWS body")
1361
1362
1363 wrongAlgJWS := &jose.JSONWebSignature{
1364 Signatures: []jose.Signature{
1365 {
1366 Header: jose.Header{
1367 Algorithm: "HS256",
1368 },
1369 },
1370 },
1371 }
1372
1373
1374 wrongURLHeaders := map[jose.HeaderKey]interface{}{
1375 "url": "foobar",
1376 }
1377 wrongURLHeaderJWS, _ := signer.signExtraHeaders(wrongURLHeaders)
1378
1379
1380 badJSONJWS, _, _ := signer.embeddedJWK(nil, testURL, `{`)
1381
1382 testCases := []struct {
1383 Name string
1384 JWS bJSONWebSignature
1385 JWK *jose.JSONWebKey
1386 Body string
1387 ExpectedProblem *probs.ProblemDetails
1388 ErrorStatType string
1389
1390 SkipConfig bool
1391
1392 SkipConfigNext bool
1393 }{
1394 {
1395 Name: "JWS with an invalid algorithm",
1396 JWS: bJSONWebSignature{wrongAlgJWS},
1397 JWK: goodJWK,
1398 ExpectedProblem: &probs.ProblemDetails{
1399 Type: probs.BadSignatureAlgorithmProblem,
1400 Detail: "JWS signature header contains unsupported algorithm \"HS256\", expected one of RS256, ES256, ES384 or ES512",
1401 HTTPStatus: http.StatusBadRequest,
1402 },
1403 ErrorStatType: "JWSAlgorithmCheckFailed",
1404 },
1405 {
1406 Name: "JWS with an invalid nonce (test/config)",
1407 JWS: bJSONWebSignature{signer.invalidNonce()},
1408 JWK: goodJWK,
1409 ExpectedProblem: &probs.ProblemDetails{
1410 Type: probs.BadNonceProblem,
1411 Detail: "JWS has an invalid anti-replay nonce: \"mlol3ov77I5Ui-cdaY_k8IcjK58FvbG0y_BCRrx5rGQ8rjA\"",
1412 HTTPStatus: http.StatusBadRequest,
1413 },
1414 ErrorStatType: "JWSInvalidNonce",
1415 SkipConfigNext: true,
1416 },
1417 {
1418 Name: "JWS with an invalid nonce (test/config-next)",
1419 JWS: bJSONWebSignature{signer.invalidNonce()},
1420 JWK: goodJWK,
1421 ExpectedProblem: &probs.ProblemDetails{
1422 Type: probs.BadNonceProblem,
1423 Detail: "JWS has an invalid anti-replay nonce: \"mlolmlol3ov77I5Ui-cdaY_k8IcjK58FvbG0y_BCRrx5rGQ8rjA\"",
1424 HTTPStatus: http.StatusBadRequest,
1425 },
1426 ErrorStatType: "JWSInvalidNonce",
1427 SkipConfig: true,
1428 },
1429 {
1430 Name: "JWS with broken signature",
1431 JWS: bJSONWebSignature{badJWS},
1432 JWK: badJWS.Signatures[0].Header.JSONWebKey,
1433 ExpectedProblem: &probs.ProblemDetails{
1434 Type: probs.MalformedProblem,
1435 Detail: "JWS verification error",
1436 HTTPStatus: http.StatusBadRequest,
1437 },
1438 ErrorStatType: "JWSVerifyFailed",
1439 },
1440 {
1441 Name: "JWS with incorrect URL",
1442 JWS: bJSONWebSignature{wrongURLHeaderJWS},
1443 JWK: wrongURLHeaderJWS.Signatures[0].Header.JSONWebKey,
1444 ExpectedProblem: &probs.ProblemDetails{
1445 Type: probs.MalformedProblem,
1446 Detail: "JWS header parameter 'url' incorrect. Expected \"http://localhost/test\" got \"foobar\"",
1447 HTTPStatus: http.StatusBadRequest,
1448 },
1449 ErrorStatType: "JWSMismatchedURL",
1450 },
1451 {
1452 Name: "Valid JWS with invalid JSON in the protected body",
1453 JWS: bJSONWebSignature{badJSONJWS},
1454 JWK: goodJWK,
1455 ExpectedProblem: &probs.ProblemDetails{
1456 Type: probs.MalformedProblem,
1457 Detail: "Request payload did not parse as JSON",
1458 HTTPStatus: http.StatusBadRequest,
1459 },
1460 ErrorStatType: "JWSBodyUnmarshalFailed",
1461 },
1462 {
1463 Name: "Good JWS and JWK",
1464 JWS: bJSONWebSignature{goodJWS},
1465 JWK: goodJWK,
1466 },
1467 }
1468
1469 for _, tc := range testCases {
1470
1471 t.Run(tc.Name, func(t *testing.T) {
1472
1473 if os.Getenv("BOULDER_CONFIG_DIR") == "test/config-next" {
1474 if tc.SkipConfigNext {
1475 t.Skip("Skipping test in config-next")
1476 }
1477 } else if tc.SkipConfig {
1478 t.Skip("Skipping test in config")
1479 }
1480 wfe.stats.joseErrorCount.Reset()
1481 request := makePostRequestWithPath("test", tc.Body)
1482 outPayload, prob := wfe.validJWSForKey(context.Background(), &tc.JWS, tc.JWK, request)
1483 if tc.ExpectedProblem == nil && prob != nil {
1484 t.Fatalf("Expected nil problem, got %#v\n", prob)
1485 } else if tc.ExpectedProblem == nil {
1486 test.AssertEquals(t, string(outPayload), payload)
1487 } else {
1488 test.AssertMarshaledEquals(t, prob, tc.ExpectedProblem)
1489 }
1490 if tc.ErrorStatType != "" {
1491 test.AssertMetricWithLabelsEquals(
1492 t, wfe.stats.joseErrorCount, prometheus.Labels{"type": tc.ErrorStatType}, 1)
1493 }
1494 })
1495 }
1496 }
1497
1498 func TestValidPOSTForAccount(t *testing.T) {
1499 wfe, _, signer := setupWFE(t)
1500
1501 validJWS, _, validJWSBody := signer.byKeyID(1, nil, "http://localhost/test", `{"test":"passed"}`)
1502 validAccountPB, _ := wfe.sa.GetRegistration(context.Background(), &sapb.RegistrationID{Id: 1})
1503 validAccount, _ := bgrpc.PbToRegistration(validAccountPB)
1504
1505
1506 _, _, missingJWSBody := signer.byKeyID(102, nil, "http://localhost/test", "{}")
1507
1508
1509 key3 := loadKey(t, []byte(test3KeyPrivatePEM))
1510 _, _, deactivatedJWSBody := signer.byKeyID(3, key3, "http://localhost/test", "{}")
1511
1512 _, _, embeddedJWSBody := signer.embeddedJWK(nil, "http://localhost/test", `{"test":"passed"}`)
1513
1514 testCases := []struct {
1515 Name string
1516 Request *http.Request
1517 ExpectedProblem *probs.ProblemDetails
1518 ExpectedPayload string
1519 ExpectedAcct *core.Registration
1520 ExpectedJWS *jose.JSONWebSignature
1521 ErrorStatType string
1522 }{
1523 {
1524 Name: "Invalid JWS",
1525 Request: makePostRequestWithPath("test", "foo"),
1526 ExpectedProblem: &probs.ProblemDetails{
1527 Type: probs.MalformedProblem,
1528 Detail: "Parse error reading JWS",
1529 HTTPStatus: http.StatusBadRequest,
1530 },
1531 ErrorStatType: "JWSUnmarshalFailed",
1532 },
1533 {
1534 Name: "Embedded Key JWS",
1535 Request: makePostRequestWithPath("test", embeddedJWSBody),
1536 ExpectedProblem: &probs.ProblemDetails{
1537 Type: probs.MalformedProblem,
1538 Detail: "No Key ID in JWS header",
1539 HTTPStatus: http.StatusBadRequest,
1540 },
1541 ErrorStatType: "JWSAuthTypeWrong",
1542 },
1543 {
1544 Name: "JWS signed by account that doesn't exist",
1545 Request: makePostRequestWithPath("test", missingJWSBody),
1546 ExpectedProblem: &probs.ProblemDetails{
1547 Type: probs.AccountDoesNotExistProblem,
1548 Detail: "Account \"http://localhost/acme/acct/102\" not found",
1549 HTTPStatus: http.StatusBadRequest,
1550 },
1551 ErrorStatType: "JWSKeyIDNotFound",
1552 },
1553 {
1554 Name: "JWS signed by account that's deactivated",
1555 Request: makePostRequestWithPath("test", deactivatedJWSBody),
1556 ExpectedProblem: &probs.ProblemDetails{
1557 Type: probs.UnauthorizedProblem,
1558 Detail: "Account is not valid, has status \"deactivated\"",
1559 HTTPStatus: http.StatusForbidden,
1560 },
1561 ErrorStatType: "JWSKeyIDAccountInvalid",
1562 },
1563 {
1564 Name: "Valid JWS for account",
1565 Request: makePostRequestWithPath("test", validJWSBody),
1566 ExpectedPayload: `{"test":"passed"}`,
1567 ExpectedAcct: &validAccount,
1568 ExpectedJWS: validJWS,
1569 },
1570 }
1571
1572 for _, tc := range testCases {
1573 t.Run(tc.Name, func(t *testing.T) {
1574 wfe.stats.joseErrorCount.Reset()
1575 inputLogEvent := newRequestEvent()
1576 outPayload, jws, acct, prob := wfe.validPOSTForAccount(tc.Request, context.Background(), inputLogEvent)
1577 if tc.ExpectedProblem == nil && prob != nil {
1578 t.Fatalf("Expected nil problem, got %#v\n", prob)
1579 } else if tc.ExpectedProblem == nil {
1580 test.AssertEquals(t, string(outPayload), tc.ExpectedPayload)
1581 test.AssertMarshaledEquals(t, acct, tc.ExpectedAcct)
1582 test.AssertMarshaledEquals(t, jws, tc.ExpectedJWS)
1583 } else {
1584 test.AssertMarshaledEquals(t, prob, tc.ExpectedProblem)
1585 }
1586 if tc.ErrorStatType != "" {
1587 test.AssertMetricWithLabelsEquals(
1588 t, wfe.stats.joseErrorCount, prometheus.Labels{"type": tc.ErrorStatType}, 1)
1589 }
1590 })
1591 }
1592 }
1593
1594
1595
1596
1597
1598 func TestValidPOSTAsGETForAccount(t *testing.T) {
1599 wfe, _, signer := setupWFE(t)
1600
1601
1602
1603 _, _, invalidPayloadRequest := signer.byKeyID(1, nil, "http://localhost/test", "{}")
1604
1605 _, _, validRequest := signer.byKeyID(1, nil, "http://localhost/test", "")
1606
1607 testCases := []struct {
1608 Name string
1609 Request *http.Request
1610 ExpectedProblem *probs.ProblemDetails
1611 ExpectedLogEvent web.RequestEvent
1612 }{
1613 {
1614 Name: "Non-empty JWS payload",
1615 Request: makePostRequestWithPath("test", invalidPayloadRequest),
1616 ExpectedProblem: probs.Malformed("POST-as-GET requests must have an empty payload"),
1617 ExpectedLogEvent: web.RequestEvent{},
1618 },
1619 {
1620 Name: "Valid POST-as-GET",
1621 Request: makePostRequestWithPath("test", validRequest),
1622 ExpectedLogEvent: web.RequestEvent{
1623 Method: "POST-as-GET",
1624 },
1625 },
1626 }
1627
1628 for _, tc := range testCases {
1629 ev := newRequestEvent()
1630 _, prob := wfe.validPOSTAsGETForAccount(
1631 tc.Request,
1632 context.Background(),
1633 ev)
1634 if tc.ExpectedProblem == nil && prob != nil {
1635 t.Fatalf("Expected nil problem, got %#v\n", prob)
1636 } else if tc.ExpectedProblem != nil {
1637 test.AssertMarshaledEquals(t, prob, tc.ExpectedProblem)
1638 }
1639 test.AssertMarshaledEquals(t, *ev, tc.ExpectedLogEvent)
1640 }
1641 }
1642
1643 type mockSADifferentStoredKey struct {
1644 sapb.StorageAuthorityReadOnlyClient
1645 }
1646
1647
1648
1649 func (sa mockSADifferentStoredKey) GetRegistration(_ context.Context, _ *sapb.RegistrationID, _ ...grpc.CallOption) (*corepb.Registration, error) {
1650 return &corepb.Registration{
1651 Key: []byte(test2KeyPublicJSON),
1652 Status: string(core.StatusValid),
1653 }, nil
1654 }
1655
1656 func TestValidPOSTForAccountSwappedKey(t *testing.T) {
1657 wfe, fc, signer := setupWFE(t)
1658 wfe.sa = &mockSADifferentStoredKey{mocks.NewStorageAuthorityReadOnly(fc)}
1659 wfe.accountGetter = wfe.sa
1660 event := newRequestEvent()
1661
1662 payload := `{"resource":"ima-payload"}`
1663
1664 _, _, body := signer.byKeyID(1, nil, "http://localhost:4001/test", payload)
1665 request := makePostRequestWithPath("test", body)
1666
1667
1668
1669
1670 _, _, _, prob := wfe.validPOSTForAccount(request, ctx, event)
1671 test.Assert(t, prob != nil, "No error returned for request signed by wrong key")
1672 test.AssertEquals(t, prob.Type, probs.MalformedProblem)
1673 test.AssertEquals(t, prob.Detail, "JWS verification error")
1674 }
1675
1676 func TestValidSelfAuthenticatedPOSTGoodKeyErrors(t *testing.T) {
1677 wfe, _, signer := setupWFE(t)
1678
1679 timeoutErrCheckFunc := func(ctx context.Context, keyHash []byte) (bool, error) {
1680 return false, context.DeadlineExceeded
1681 }
1682
1683 kp, err := goodkey.NewKeyPolicy(&goodkey.Config{}, timeoutErrCheckFunc)
1684 test.AssertNotError(t, err, "making key policy")
1685
1686 wfe.keyPolicy = kp
1687
1688 _, _, validJWSBody := signer.embeddedJWK(nil, "http://localhost/test", `{"test":"passed"}`)
1689 request := makePostRequestWithPath("test", validJWSBody)
1690
1691 _, _, prob := wfe.validSelfAuthenticatedPOST(context.Background(), request)
1692 test.AssertEquals(t, prob.Type, probs.ServerInternalProblem)
1693
1694 badKeyCheckFunc := func(ctx context.Context, keyHash []byte) (bool, error) {
1695 return false, fmt.Errorf("oh no: %w", goodkey.ErrBadKey)
1696 }
1697
1698 kp, err = goodkey.NewKeyPolicy(&goodkey.Config{}, badKeyCheckFunc)
1699 test.AssertNotError(t, err, "making key policy")
1700
1701 wfe.keyPolicy = kp
1702
1703 _, _, validJWSBody = signer.embeddedJWK(nil, "http://localhost/test", `{"test":"passed"}`)
1704 request = makePostRequestWithPath("test", validJWSBody)
1705
1706 _, _, prob = wfe.validSelfAuthenticatedPOST(context.Background(), request)
1707 test.AssertEquals(t, prob.Type, probs.BadPublicKeyProblem)
1708 }
1709
1710 func TestValidSelfAuthenticatedPOST(t *testing.T) {
1711 wfe, _, signer := setupWFE(t)
1712
1713 _, validKey, validJWSBody := signer.embeddedJWK(nil, "http://localhost/test", `{"test":"passed"}`)
1714
1715 _, _, keyIDJWSBody := signer.byKeyID(1, nil, "http://localhost/test", `{"test":"passed"}`)
1716
1717 testCases := []struct {
1718 Name string
1719 Request *http.Request
1720 ExpectedProblem *probs.ProblemDetails
1721 ExpectedPayload string
1722 ExpectedJWK *jose.JSONWebKey
1723 ErrorStatType string
1724 }{
1725 {
1726 Name: "Invalid JWS",
1727 Request: makePostRequestWithPath("test", "foo"),
1728 ExpectedProblem: &probs.ProblemDetails{
1729 Type: probs.MalformedProblem,
1730 Detail: "Parse error reading JWS",
1731 HTTPStatus: http.StatusBadRequest,
1732 },
1733 ErrorStatType: "JWSUnmarshalFailed",
1734 },
1735 {
1736 Name: "JWS with key ID",
1737 Request: makePostRequestWithPath("test", keyIDJWSBody),
1738 ExpectedProblem: &probs.ProblemDetails{
1739 Type: probs.MalformedProblem,
1740 Detail: "No embedded JWK in JWS header",
1741 HTTPStatus: http.StatusBadRequest,
1742 },
1743 ErrorStatType: "JWSAuthTypeWrong",
1744 },
1745 {
1746 Name: "Valid JWS",
1747 Request: makePostRequestWithPath("test", validJWSBody),
1748 ExpectedPayload: `{"test":"passed"}`,
1749 ExpectedJWK: validKey,
1750 },
1751 }
1752
1753 for _, tc := range testCases {
1754 t.Run(tc.Name, func(t *testing.T) {
1755 wfe.stats.joseErrorCount.Reset()
1756 outPayload, jwk, prob := wfe.validSelfAuthenticatedPOST(context.Background(), tc.Request)
1757 if tc.ExpectedProblem == nil && prob != nil {
1758 t.Fatalf("Expected nil problem, got %#v\n", prob)
1759 } else if tc.ExpectedProblem == nil {
1760 inThumb, _ := tc.ExpectedJWK.Thumbprint(crypto.SHA256)
1761 outThumb, _ := jwk.Thumbprint(crypto.SHA256)
1762 test.AssertDeepEquals(t, inThumb, outThumb)
1763 test.AssertEquals(t, string(outPayload), tc.ExpectedPayload)
1764 } else {
1765 test.AssertMarshaledEquals(t, prob, tc.ExpectedProblem)
1766 }
1767 if tc.ErrorStatType != "" {
1768 test.AssertMetricWithLabelsEquals(
1769 t, wfe.stats.joseErrorCount, prometheus.Labels{"type": tc.ErrorStatType}, 1)
1770 }
1771 })
1772 }
1773 }
1774
1775 func TestMatchJWSURLs(t *testing.T) {
1776 wfe, _, signer := setupWFE(t)
1777
1778 noURLJWS, _, _ := signer.embeddedJWK(nil, "", "")
1779 urlAJWS, _, _ := signer.embeddedJWK(nil, "example.com", "")
1780 urlBJWS, _, _ := signer.embeddedJWK(nil, "example.org", "")
1781
1782 testCases := []struct {
1783 Name string
1784 Outer *jose.JSONWebSignature
1785 Inner *jose.JSONWebSignature
1786 ExpectedProblem *probs.ProblemDetails
1787 ErrorStatType string
1788 }{
1789 {
1790 Name: "Outer JWS without URL",
1791 Outer: noURLJWS,
1792 Inner: urlAJWS,
1793 ExpectedProblem: &probs.ProblemDetails{
1794 Type: probs.MalformedProblem,
1795 Detail: "Outer JWS header parameter 'url' required",
1796 HTTPStatus: http.StatusBadRequest,
1797 },
1798 ErrorStatType: "KeyRolloverOuterJWSNoURL",
1799 },
1800 {
1801 Name: "Inner JWS without URL",
1802 Outer: urlAJWS,
1803 Inner: noURLJWS,
1804 ExpectedProblem: &probs.ProblemDetails{
1805 Type: probs.MalformedProblem,
1806 Detail: "Inner JWS header parameter 'url' required",
1807 HTTPStatus: http.StatusBadRequest,
1808 },
1809 ErrorStatType: "KeyRolloverInnerJWSNoURL",
1810 },
1811 {
1812 Name: "Inner and outer JWS without URL",
1813 Outer: noURLJWS,
1814 Inner: noURLJWS,
1815 ExpectedProblem: &probs.ProblemDetails{
1816 Type: probs.MalformedProblem,
1817
1818 Detail: "Outer JWS header parameter 'url' required",
1819 HTTPStatus: http.StatusBadRequest,
1820 },
1821 ErrorStatType: "KeyRolloverOuterJWSNoURL",
1822 },
1823 {
1824 Name: "Mismatched inner and outer JWS URLs",
1825 Outer: urlAJWS,
1826 Inner: urlBJWS,
1827 ExpectedProblem: &probs.ProblemDetails{
1828 Type: probs.MalformedProblem,
1829 Detail: "Outer JWS 'url' value \"example.com\" does not match inner JWS 'url' value \"example.org\"",
1830 HTTPStatus: http.StatusBadRequest,
1831 },
1832 ErrorStatType: "KeyRolloverMismatchedURLs",
1833 },
1834 {
1835 Name: "Matching inner and outer JWS URLs",
1836 Outer: urlAJWS,
1837 Inner: urlAJWS,
1838 },
1839 }
1840
1841 for _, tc := range testCases {
1842 t.Run(tc.Name, func(t *testing.T) {
1843 wfe.stats.joseErrorCount.Reset()
1844 prob := wfe.matchJWSURLs(tc.Outer.Signatures[0].Header, tc.Inner.Signatures[0].Header)
1845 if prob != nil && tc.ExpectedProblem == nil {
1846 t.Errorf("matchJWSURLs failed. Expected no problem, got %#v", prob)
1847 } else {
1848 test.AssertMarshaledEquals(t, prob, tc.ExpectedProblem)
1849 }
1850 if tc.ErrorStatType != "" {
1851 test.AssertMetricWithLabelsEquals(
1852 t, wfe.stats.joseErrorCount, prometheus.Labels{"type": tc.ErrorStatType}, 1)
1853 }
1854 })
1855 }
1856 }
1857
View as plain text