forked from adamlaska/boulder
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrevocation_test.go
More file actions
247 lines (216 loc) · 8.68 KB
/
revocation_test.go
File metadata and controls
247 lines (216 loc) · 8.68 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
// +build integration
package integration
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"io/ioutil"
"net/http"
"os"
"strings"
"testing"
"time"
"github.com/letsencrypt/boulder/test"
ocsp_helper "github.com/letsencrypt/boulder/test/ocsp/helper"
"golang.org/x/crypto/ocsp"
)
// isPrecert returns true if the provided cert has an extension with the OID
// equal to OIDExtensionCTPoison.
func isPrecert(cert *x509.Certificate) bool {
for _, ext := range cert.Extensions {
if ext.Id.Equal(OIDExtensionCTPoison) {
return true
}
}
return false
}
// TestPrecertificateRevocation tests that a precertificate without a matching
// certificate can be revoked using all of the available RFC 8555 revocation
// authentication mechansims.
func TestPrecertificateRevocation(t *testing.T) {
t.Parallel()
// Create a base account to use for revocation tests.
os.Setenv("DIRECTORY", "http://boulder:4001/directory")
c, err := makeClient("mailto:example@letsencrypt.org")
test.AssertNotError(t, err, "creating acme client")
// Create a specific key for CSRs so that it is possible to test revocation
// with the cert key.
certKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
test.AssertNotError(t, err, "creating random cert key")
// Create a second account to test revocation with an equally authorized account
otherAccount, err := makeClient()
test.AssertNotError(t, err, "creating second acme client")
// Preauthorize a specific domain with the other account before it has been
// added to the ct-test-srv reject list.
preAuthDomain := random_domain()
_, err = authAndIssue(otherAccount, nil, []string{preAuthDomain})
test.AssertNotError(t, err, "preauthorizing second acme client")
testCases := []struct {
name string
domain string
revokeClient *client
revokeKey crypto.Signer
}{
{
name: "revocation by certificate key",
revokeKey: certKey,
},
{
name: "revocation by owner account key",
revokeKey: c.Account.PrivateKey,
},
{
name: "equivalently authorized account key",
revokeClient: otherAccount,
revokeKey: otherAccount.Account.PrivateKey,
domain: preAuthDomain,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// If the test case didn't specify a domain make one up randomly
if tc.domain == "" {
tc.domain = random_domain()
}
// If the test case didn't specify a different client to use for
// revocation use c.
if tc.revokeClient == nil {
tc.revokeClient = c
}
// Make sure the ct-test-srv will reject issuance for the domain
err := ctAddRejectHost(tc.domain)
test.AssertNotError(t, err, "adding ct-test-srv reject host")
// Issue a certificate for the name using the `c` client. It should fail
// because not enough SCTs can be collected, leaving a precert without
// a matching final cert.
_, err = authAndIssue(c, certKey, []string{tc.domain})
test.AssertError(t, err, "expected error from authAndIssue, was nil")
if !strings.Contains(err.Error(), "urn:ietf:params:acme:error:serverInternal") ||
!strings.Contains(err.Error(), "SCT embedding") {
t.Fatal(err)
}
// Try to find a precertificate matching the domain from one of the
// configured ct-test-srv instances.
cert, err := ctFindRejection([]string{tc.domain})
if err != nil || cert == nil {
t.Fatalf("couldn't find rejected precert for %q", tc.domain)
}
// To be confident that we're testing the right thing also verify that the
// rejection is a poisoned precertificate.
if !isPrecert(cert) {
t.Fatal("precert was missing poison extension")
}
// To start with the precertificate should have a Good OCSP response.
ocspConfig := ocsp_helper.DefaultConfig.WithExpectStatus(ocsp.Good)
_, err = ocsp_helper.ReqDER(cert.Raw, ocspConfig)
test.AssertNotError(t, err, "requesting OCSP for precert")
// Revoke the precertificate using the specified key and client
err = tc.revokeClient.RevokeCertificate(
tc.revokeClient.Account,
cert,
tc.revokeKey,
ocsp.Unspecified)
test.AssertNotError(t, err, "revoking precert")
// Check the OCSP response for the precertificate again. It should now be
// revoked.
ocspConfig = ocsp_helper.DefaultConfig.WithExpectStatus(ocsp.Revoked)
_, err = ocsp_helper.ReqDER(cert.Raw, ocspConfig)
test.AssertNotError(t, err, "requesting OCSP for revoked precert")
})
}
}
func TestRevokeWithKeyCompromise(t *testing.T) {
t.Parallel()
os.Setenv("DIRECTORY", "http://boulder:4001/directory")
c, err := makeClient("mailto:example@letsencrypt.org")
test.AssertNotError(t, err, "creating acme client")
certKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
test.AssertNotError(t, err, "failed to generate cert key")
res, err := authAndIssue(c, certKey, []string{random_domain()})
test.AssertNotError(t, err, "authAndIssue failed")
cert := res.certs[0]
err = c.RevokeCertificate(
c.Account,
cert,
c.Account.PrivateKey,
ocsp.KeyCompromise,
)
test.AssertNotError(t, err, "failed to revoke certificate")
// attempt to create a new account using the blacklisted key
_, err = c.NewAccount(certKey, false, true)
test.AssertError(t, err, "NewAccount didn't fail with a blacklisted key")
test.AssertEquals(t, err.Error(), `acme: error code 400 "urn:ietf:params:acme:error:badPublicKey": public key is forbidden`)
// Check the OCSP response. It should be revoked with reason = 1 (keyCompromise)
ocspConfig := ocsp_helper.DefaultConfig.WithExpectStatus(ocsp.Revoked)
response, err := ocsp_helper.ReqDER(cert.Raw, ocspConfig)
test.AssertNotError(t, err, "requesting OCSP for revoked cert")
test.AssertEquals(t, response.RevocationReason, 1)
}
func TestBadKeyRevoker(t *testing.T) {
t.Parallel()
os.Setenv("DIRECTORY", "http://boulder:4001/directory")
cA, err := makeClient("mailto:bad-key-revoker-revoker@letsencrypt.org", "mailto:bad-key-revoker-revoker-2@letsencrypt.org")
test.AssertNotError(t, err, "creating acme client")
cB, err := makeClient("mailto:bad-key-revoker-revoker-2@letsencrypt.org")
test.AssertNotError(t, err, "creating acme client")
cC, err := makeClient("mailto:bad-key-revoker-revokee@letsencrypt.org", "mailto:bad-key-revoker-revokee-2@letsencrypt.org")
test.AssertNotError(t, err, "creating acme client")
cD, err := makeClient("mailto:bad-key-revoker-revokee-2@letsencrypt.org", "mailto:bad-key-revoker-revokee@letsencrypt.org")
test.AssertNotError(t, err, "creating acme client")
cE, err := makeClient()
test.AssertNotError(t, err, "creating acme client")
certKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
test.AssertNotError(t, err, "failed to generate cert key")
badCert, err := authAndIssue(cA, certKey, []string{random_domain()})
test.AssertNotError(t, err, "authAndIssue failed")
certs := []*x509.Certificate{}
for _, c := range []*client{cA, cB, cC, cD, cE} {
for i := 0; i < 2; i++ {
cert, err := authAndIssue(c, certKey, []string{random_domain()})
test.AssertNotError(t, err, "authAndIssue failed")
certs = append(certs, cert.certs[0])
}
}
err = cA.RevokeCertificate(
cA.Account,
badCert.certs[0],
cA.Account.PrivateKey,
ocsp.KeyCompromise,
)
test.AssertNotError(t, err, "failed to revoke certificate")
ocspConfig := ocsp_helper.DefaultConfig.WithExpectStatus(ocsp.Revoked)
_, err = ocsp_helper.ReqDER(badCert.certs[0].Raw, ocspConfig)
test.AssertNotError(t, err, "ReqDER failed")
for _, cert := range certs {
for i := 0; i < 5; i++ {
_, err = ocsp_helper.ReqDER(cert.Raw, ocspConfig)
if err == nil {
break
}
if i == 5 {
t.Fatal("timed out waiting for revoked OCSP status")
}
time.Sleep(time.Second)
}
}
countResp, err := http.Get("http://boulder:9381/count?to=bad-key-revoker-revokee@letsencrypt.org")
test.AssertNotError(t, err, "mail-test-srv GET /count failed")
defer func() { _ = countResp.Body.Close() }()
body, err := ioutil.ReadAll(countResp.Body)
test.AssertNotError(t, err, "failed to read body")
test.AssertEquals(t, string(body), "1\n")
otherCountResp, err := http.Get("http://boulder:9381/count?to=bad-key-revoker-revokee-2@letsencrypt.org")
test.AssertNotError(t, err, "mail-test-srv GET /count failed")
defer func() { _ = otherCountResp.Body.Close() }()
body, err = ioutil.ReadAll(otherCountResp.Body)
test.AssertNotError(t, err, "failed to read body")
test.AssertEquals(t, string(body), "1\n")
zeroCountResp, err := http.Get("http://boulder:9381/count?to=bad-key-revoker-revoker@letsencrypt.org")
test.AssertNotError(t, err, "mail-test-srv GET /count failed")
defer func() { _ = zeroCountResp.Body.Close() }()
body, err = ioutil.ReadAll(zeroCountResp.Body)
test.AssertNotError(t, err, "failed to read body")
test.AssertEquals(t, string(body), "0\n")
}