1
2
3 package integration
4
5 import (
6 "crypto"
7 "crypto/ecdsa"
8 "crypto/elliptic"
9 "crypto/rand"
10 "crypto/x509"
11 "fmt"
12 "io"
13 "net/http"
14 "strings"
15 "testing"
16 "time"
17
18 "github.com/eggsampler/acme/v3"
19 "golang.org/x/crypto/ocsp"
20
21 "github.com/letsencrypt/boulder/test"
22 ocsp_helper "github.com/letsencrypt/boulder/test/ocsp/helper"
23 )
24
25
26
27 func isPrecert(cert *x509.Certificate) bool {
28 for _, ext := range cert.Extensions {
29 if ext.Id.Equal(OIDExtensionCTPoison) {
30 return true
31 }
32 }
33 return false
34 }
35
36
37
38
39
40 func TestRevocation(t *testing.T) {
41 t.Parallel()
42
43 type authMethod string
44 var (
45 byAccount authMethod = "byAccount"
46 byAuth authMethod = "byAuth"
47 byKey authMethod = "byKey"
48 )
49
50 type certKind string
51 var (
52 finalcert certKind = "cert"
53 precert certKind = "precert"
54 )
55
56 type testCase struct {
57 method authMethod
58 reason int
59 kind certKind
60 }
61
62 var testCases []testCase
63 for _, kind := range []certKind{precert, finalcert} {
64 for _, reason := range []int{ocsp.Unspecified, ocsp.KeyCompromise} {
65 for _, method := range []authMethod{byAccount, byAuth, byKey} {
66 testCases = append(testCases, testCase{
67 method: method,
68 reason: reason,
69 kind: kind,
70
71
72
73
74
75
76
77 })
78 }
79 }
80 }
81
82 for _, tc := range testCases {
83 name := fmt.Sprintf("%s_%d_%s", tc.kind, tc.reason, tc.method)
84 t.Run(name, func(t *testing.T) {
85 issueClient, err := makeClient()
86 test.AssertNotError(t, err, "creating acme client")
87
88 certKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
89 test.AssertNotError(t, err, "creating random cert key")
90
91 domain := random_domain()
92
93
94 var cert *x509.Certificate
95 switch tc.kind {
96 case finalcert:
97 res, err := authAndIssue(issueClient, certKey, []string{domain}, true)
98 test.AssertNotError(t, err, "authAndIssue failed")
99 cert = res.certs[0]
100
101 case precert:
102
103
104 err := ctAddRejectHost(domain)
105 test.AssertNotError(t, err, "adding ct-test-srv reject host")
106
107 _, err = authAndIssue(issueClient, certKey, []string{domain}, true)
108 test.AssertError(t, err, "expected error from authAndIssue, was nil")
109 if !strings.Contains(err.Error(), "urn:ietf:params:acme:error:serverInternal") ||
110 !strings.Contains(err.Error(), "SCT embedding") {
111 t.Fatal(err)
112 }
113
114
115 cert, err = ctFindRejection([]string{domain})
116 if err != nil || cert == nil {
117 t.Fatalf("couldn't find rejected precert for %q", domain)
118 }
119
120 if !isPrecert(cert) {
121 t.Fatal("precert was missing poison extension")
122 }
123
124 default:
125 t.Fatalf("unrecognized cert kind %q", tc.kind)
126 }
127
128
129 ocspConfig := ocsp_helper.DefaultConfig.WithExpectStatus(ocsp.Good)
130 _, err = ocsp_helper.ReqDER(cert.Raw, ocspConfig)
131 test.AssertNotError(t, err, "requesting OCSP for precert")
132
133
134 var revokeClient *client
135 var revokeKey crypto.Signer
136 switch tc.method {
137 case byAccount:
138
139
140 revokeClient = issueClient
141 revokeKey = revokeClient.PrivateKey
142
143 case byAuth:
144
145
146
147
148 revokeClient, err = makeClient()
149 test.AssertNotError(t, err, "creating second acme client")
150 _, _ = authAndIssue(revokeClient, certKey, []string{domain}, true)
151 revokeKey = revokeClient.PrivateKey
152
153 case byKey:
154
155
156 revokeClient, err = makeClient()
157 test.AssertNotError(t, err, "creating second acme client")
158 revokeKey = certKey
159
160 default:
161 t.Fatalf("unrecognized revocation method %q", tc.method)
162 }
163
164
165 err = revokeClient.RevokeCertificate(
166 revokeClient.Account,
167 cert,
168 revokeKey,
169 tc.reason,
170 )
171
172 test.AssertNotError(t, err, "revocation should have succeeded")
173
174
175
176
177
178
179 ocspConfig = ocsp_helper.DefaultConfig.WithExpectStatus(ocsp.Revoked)
180 switch tc.method {
181 case byAuth:
182 ocspConfig = ocspConfig.WithExpectReason(ocsp.CessationOfOperation)
183 case byKey:
184 ocspConfig = ocspConfig.WithExpectReason(ocsp.KeyCompromise)
185 default:
186 ocspConfig = ocspConfig.WithExpectReason(tc.reason)
187 }
188 _, err = ocsp_helper.ReqDER(cert.Raw, ocspConfig)
189 test.AssertNotError(t, err, "requesting OCSP for revoked cert")
190 })
191 }
192 }
193
194
195
196
197
198
199
200 func TestReRevocation(t *testing.T) {
201 t.Parallel()
202
203 type authMethod string
204 var (
205 byAccount authMethod = "byAccount"
206 byKey authMethod = "byKey"
207 )
208
209 type testCase struct {
210 method1 authMethod
211 reason1 int
212 method2 authMethod
213 reason2 int
214 expectError bool
215 }
216
217 testCases := []testCase{
218 {method1: byAccount, reason1: 0, method2: byAccount, reason2: 0, expectError: true},
219 {method1: byAccount, reason1: 1, method2: byAccount, reason2: 1, expectError: true},
220 {method1: byAccount, reason1: 0, method2: byKey, reason2: 1, expectError: false},
221 {method1: byAccount, reason1: 1, method2: byKey, reason2: 1, expectError: true},
222 {method1: byKey, reason1: 1, method2: byKey, reason2: 1, expectError: true},
223 }
224
225 for i, tc := range testCases {
226 t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
227 issueClient, err := makeClient()
228 test.AssertNotError(t, err, "creating acme client")
229
230 certKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
231 test.AssertNotError(t, err, "creating random cert key")
232
233
234 domain := random_domain()
235 res, err := authAndIssue(issueClient, certKey, []string{domain}, true)
236 test.AssertNotError(t, err, "authAndIssue failed")
237 cert := res.certs[0]
238
239
240 ocspConfig := ocsp_helper.DefaultConfig.WithExpectStatus(ocsp.Good)
241 _, err = ocsp_helper.ReqDER(cert.Raw, ocspConfig)
242 test.AssertNotError(t, err, "requesting OCSP for precert")
243
244
245 var revokeClient *client
246 var revokeKey crypto.Signer
247 switch tc.method1 {
248 case byAccount:
249
250
251 revokeClient = issueClient
252 revokeKey = revokeClient.PrivateKey
253
254 case byKey:
255
256
257 revokeClient, err = makeClient()
258 test.AssertNotError(t, err, "creating second acme client")
259 revokeKey = certKey
260
261 default:
262 t.Fatalf("unrecognized revocation method %q", tc.method1)
263 }
264
265
266 err = revokeClient.RevokeCertificate(
267 revokeClient.Account,
268 cert,
269 revokeKey,
270 tc.reason1,
271 )
272 test.AssertNotError(t, err, "initial revocation should have succeeded")
273
274
275
276 ocspConfig = ocsp_helper.DefaultConfig.WithExpectStatus(ocsp.Revoked).WithExpectReason(tc.reason1)
277 _, err = ocsp_helper.ReqDER(cert.Raw, ocspConfig)
278 test.AssertNotError(t, err, "requesting OCSP for revoked cert")
279
280
281 switch tc.method2 {
282 case byAccount:
283
284
285 revokeClient = issueClient
286 revokeKey = revokeClient.PrivateKey
287
288 case byKey:
289
290
291 revokeClient, err = makeClient()
292 test.AssertNotError(t, err, "creating second acme client")
293 revokeKey = certKey
294
295 default:
296 t.Fatalf("unrecognized revocation method %q", tc.method2)
297 }
298
299
300 err = revokeClient.RevokeCertificate(
301 revokeClient.Account,
302 cert,
303 revokeKey,
304 tc.reason2,
305 )
306
307 switch tc.expectError {
308 case true:
309 test.AssertError(t, err, "second revocation should have failed")
310
311
312
313 ocspConfig = ocsp_helper.DefaultConfig.WithExpectStatus(ocsp.Revoked).WithExpectReason(tc.reason1)
314 _, err = ocsp_helper.ReqDER(cert.Raw, ocspConfig)
315 test.AssertNotError(t, err, "requesting OCSP for revoked cert")
316
317 case false:
318 test.AssertNotError(t, err, "second revocation should have succeeded")
319
320
321
322 ocspConfig = ocsp_helper.DefaultConfig.WithExpectStatus(ocsp.Revoked).WithExpectStatus(tc.reason2)
323 _, err = ocsp_helper.ReqDER(cert.Raw, ocspConfig)
324 test.AssertNotError(t, err, "requesting OCSP for revoked cert")
325 }
326 })
327 }
328 }
329
330 func TestRevokeWithKeyCompromiseBlocksKey(t *testing.T) {
331 t.Parallel()
332
333 type authMethod string
334 var (
335 byAccount authMethod = "byAccount"
336 byKey authMethod = "byKey"
337 )
338
339
340
341
342 for _, method := range []authMethod{byKey, byAccount} {
343 c, err := makeClient("mailto:example@letsencrypt.org")
344 test.AssertNotError(t, err, "creating acme client")
345
346 certKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
347 test.AssertNotError(t, err, "failed to generate cert key")
348
349 res, err := authAndIssue(c, certKey, []string{random_domain()}, true)
350 test.AssertNotError(t, err, "authAndIssue failed")
351 cert := res.certs[0]
352
353
354
355 switch method {
356 case byAccount:
357 err = c.RevokeCertificate(c.Account, cert, c.PrivateKey, ocsp.KeyCompromise)
358 case byKey:
359 err = c.RevokeCertificate(acme.Account{}, cert, certKey, ocsp.KeyCompromise)
360 }
361 test.AssertNotError(t, err, "failed to revoke certificate")
362
363
364 ocspConfig := ocsp_helper.DefaultConfig.WithExpectStatus(ocsp.Revoked).WithExpectReason(ocsp.KeyCompromise)
365 _, err = ocsp_helper.ReqDER(cert.Raw, ocspConfig)
366 test.AssertNotError(t, err, "requesting OCSP for revoked cert")
367
368
369
370
371 _, err = c.NewAccount(certKey, false, true)
372 switch method {
373 case byAccount:
374 test.AssertNotError(t, err, "NewAccount failed with a non-blocklisted key")
375 case byKey:
376 test.AssertError(t, err, "NewAccount didn't fail with a blocklisted key")
377 test.AssertEquals(t, err.Error(), `acme: error code 400 "urn:ietf:params:acme:error:badPublicKey": public key is forbidden`)
378 }
379 }
380 }
381
382 func TestBadKeyRevoker(t *testing.T) {
383
384
385
386
387
388 revokerClient, err := makeClient("mailto:revoker@letsencrypt.org", "mailto:shared@letsencrypt.org")
389 test.AssertNotError(t, err, "creating acme client")
390 revokeeClient, err := makeClient("mailto:shared@letsencrypt.org", "mailto:revokee@letsencrypt.org")
391 test.AssertNotError(t, err, "creating acme client")
392 noContactClient, err := makeClient()
393 test.AssertNotError(t, err, "creating acme client")
394
395 certKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
396 test.AssertNotError(t, err, "failed to generate cert key")
397
398 res, err := authAndIssue(revokerClient, certKey, []string{random_domain()}, true)
399 test.AssertNotError(t, err, "authAndIssue failed")
400 badCert := res.certs[0]
401 t.Logf("Generated to-be-revoked cert with serial %x", badCert.SerialNumber)
402
403 certs := []*x509.Certificate{}
404 for _, c := range []*client{revokerClient, revokeeClient, noContactClient} {
405 cert, err := authAndIssue(c, certKey, []string{random_domain()}, true)
406 t.Logf("TestBadKeyRevoker: Issued cert with serial %x", cert.certs[0].SerialNumber)
407 test.AssertNotError(t, err, "authAndIssue failed")
408 certs = append(certs, cert.certs[0])
409 }
410
411 err = revokerClient.RevokeCertificate(
412 acme.Account{},
413 badCert,
414 certKey,
415 ocsp.KeyCompromise,
416 )
417 test.AssertNotError(t, err, "failed to revoke certificate")
418
419 ocspConfig := ocsp_helper.DefaultConfig.WithExpectStatus(ocsp.Revoked).WithExpectReason(ocsp.KeyCompromise)
420 _, err = ocsp_helper.ReqDER(badCert.Raw, ocspConfig)
421 test.AssertNotError(t, err, "ReqDER failed")
422
423 for _, cert := range certs {
424 for i := 0; i < 5; i++ {
425 t.Logf("TestBadKeyRevoker: Requesting OCSP for cert with serial %x (attempt %d)", cert.SerialNumber, i)
426 _, err := ocsp_helper.ReqDER(cert.Raw, ocspConfig)
427 if err != nil {
428 t.Logf("TestBadKeyRevoker: Got bad response: %s", err.Error())
429 if i >= 4 {
430 t.Fatal("timed out waiting for correct OCSP status")
431 }
432 time.Sleep(time.Second)
433 continue
434 }
435 break
436 }
437 }
438
439 revokeeCount, err := http.Get("http://boulder.service.consul:9381/count?to=revokee@letsencrypt.org&from=bad-key-revoker@test.org")
440 test.AssertNotError(t, err, "mail-test-srv GET /count failed")
441 defer func() { _ = revokeeCount.Body.Close() }()
442 body, err := io.ReadAll(revokeeCount.Body)
443 test.AssertNotError(t, err, "failed to read body")
444 test.AssertEquals(t, string(body), "1\n")
445
446 revokerCount, err := http.Get("http://boulder.service.consul:9381/count?to=revoker@letsencrypt.org&from=bad-key-revoker@test.org")
447 test.AssertNotError(t, err, "mail-test-srv GET /count failed")
448 defer func() { _ = revokerCount.Body.Close() }()
449 body, err = io.ReadAll(revokerCount.Body)
450 test.AssertNotError(t, err, "failed to read body")
451 test.AssertEquals(t, string(body), "1\n")
452
453 sharedCount, err := http.Get("http://boulder.service.consul:9381/count?to=shared@letsencrypt.org&from=bad-key-revoker@test.org")
454 test.AssertNotError(t, err, "mail-test-srv GET /count failed")
455 defer func() { _ = sharedCount.Body.Close() }()
456 body, err = io.ReadAll(sharedCount.Body)
457 test.AssertNotError(t, err, "failed to read body")
458 test.AssertEquals(t, string(body), "1\n")
459 }
460
461 func TestBadKeyRevokerByAccount(t *testing.T) {
462
463
464
465
466 revokerClient, err := makeClient("mailto:revoker-moz@letsencrypt.org", "mailto:shared-moz@letsencrypt.org")
467 test.AssertNotError(t, err, "creating acme client")
468 revokeeClient, err := makeClient("mailto:shared-moz@letsencrypt.org", "mailto:revokee-moz@letsencrypt.org")
469 test.AssertNotError(t, err, "creating acme client")
470 noContactClient, err := makeClient()
471 test.AssertNotError(t, err, "creating acme client")
472
473 certKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
474 test.AssertNotError(t, err, "failed to generate cert key")
475
476 res, err := authAndIssue(revokerClient, certKey, []string{random_domain()}, true)
477 test.AssertNotError(t, err, "authAndIssue failed")
478 badCert := res.certs[0]
479 t.Logf("Generated to-be-revoked cert with serial %x", badCert.SerialNumber)
480
481 certs := []*x509.Certificate{}
482 for _, c := range []*client{revokerClient, revokeeClient, noContactClient} {
483 cert, err := authAndIssue(c, certKey, []string{random_domain()}, true)
484 t.Logf("TestBadKeyRevokerByAccount: Issued cert with serial %x", cert.certs[0].SerialNumber)
485 test.AssertNotError(t, err, "authAndIssue failed")
486 certs = append(certs, cert.certs[0])
487 }
488
489 err = revokerClient.RevokeCertificate(
490 revokerClient.Account,
491 badCert,
492 revokerClient.PrivateKey,
493 ocsp.KeyCompromise,
494 )
495 test.AssertNotError(t, err, "failed to revoke certificate")
496
497 ocspConfig := ocsp_helper.DefaultConfig.WithExpectStatus(ocsp.Revoked).WithExpectReason(ocsp.KeyCompromise)
498 _, err = ocsp_helper.ReqDER(badCert.Raw, ocspConfig)
499 test.AssertNotError(t, err, "ReqDER failed")
500
501 ocspConfig = ocsp_helper.DefaultConfig.WithExpectStatus(ocsp.Good)
502 for _, cert := range certs {
503 for i := 0; i < 5; i++ {
504 t.Logf("TestBadKeyRevoker: Requesting OCSP for cert with serial %x (attempt %d)", cert.SerialNumber, i)
505 _, err := ocsp_helper.ReqDER(cert.Raw, ocspConfig)
506 if err != nil {
507 t.Logf("TestBadKeyRevoker: Got bad response: %s", err.Error())
508 if i >= 4 {
509 t.Fatal("timed out waiting for correct OCSP status")
510 }
511 time.Sleep(time.Second)
512 continue
513 }
514 break
515 }
516 }
517
518 revokeeCount, err := http.Get("http://boulder.service.consul:9381/count?to=revokee-moz@letsencrypt.org&from=bad-key-revoker@test.org")
519 test.AssertNotError(t, err, "mail-test-srv GET /count failed")
520 defer func() { _ = revokeeCount.Body.Close() }()
521 body, err := io.ReadAll(revokeeCount.Body)
522 test.AssertNotError(t, err, "failed to read body")
523 test.AssertEquals(t, string(body), "0\n")
524
525 revokerCount, err := http.Get("http://boulder.service.consul:9381/count?to=revoker-moz@letsencrypt.org&from=bad-key-revoker@test.org")
526 test.AssertNotError(t, err, "mail-test-srv GET /count failed")
527 defer func() { _ = revokerCount.Body.Close() }()
528 body, err = io.ReadAll(revokerCount.Body)
529 test.AssertNotError(t, err, "failed to read body")
530 test.AssertEquals(t, string(body), "0\n")
531
532 sharedCount, err := http.Get("http://boulder.service.consul:9381/count?to=shared-moz@letsencrypt.org&from=bad-key-revoker@test.org")
533 test.AssertNotError(t, err, "mail-test-srv GET /count failed")
534 defer func() { _ = sharedCount.Body.Close() }()
535 body, err = io.ReadAll(sharedCount.Body)
536 test.AssertNotError(t, err, "failed to read body")
537 test.AssertEquals(t, string(body), "0\n")
538 }
539
View as plain text