1
2
3 package integration
4
5 import (
6 "context"
7 "crypto/ecdsa"
8 "crypto/elliptic"
9 "crypto/rand"
10 "crypto/x509"
11 "database/sql"
12 "errors"
13 "fmt"
14 "os"
15 "os/exec"
16 "strings"
17 "testing"
18 "time"
19
20 _ "github.com/go-sql-driver/mysql"
21 "github.com/letsencrypt/boulder/core"
22 "github.com/letsencrypt/boulder/sa"
23 "github.com/letsencrypt/boulder/test"
24 ocsp_helper "github.com/letsencrypt/boulder/test/ocsp/helper"
25 "github.com/letsencrypt/boulder/test/vars"
26 "golang.org/x/crypto/ocsp"
27 )
28
29
30
31 func getPrecertByName(db *sql.DB, name string) (*x509.Certificate, error) {
32 name = sa.ReverseName(name)
33
34
35 var der []byte
36 rows, err := db.Query(`
37 SELECT der
38 FROM issuedNames JOIN precertificates
39 USING (serial)
40 WHERE reversedName = ?
41 ORDER BY issuedNames.id DESC
42 LIMIT 1
43 `, name)
44 for rows.Next() {
45 err = rows.Scan(&der)
46 if err != nil {
47 return nil, err
48 }
49 }
50 if der == nil {
51 return nil, fmt.Errorf("no precertificate found for %q", name)
52 }
53
54 cert, err := x509.ParseCertificate(der)
55 if err != nil {
56 return nil, err
57 }
58
59 return cert, nil
60 }
61
62
63 func expectOCSP500(cert *x509.Certificate) error {
64 _, err := ocsp_helper.Req(cert, ocsp_helper.DefaultConfig)
65 if err == nil {
66 return errors.New("Expected error getting OCSP for certificate that failed status storage")
67 }
68
69 var statusCodeError ocsp_helper.StatusCodeError
70 if !errors.As(err, &statusCodeError) {
71 return fmt.Errorf("Got wrong kind of error for OCSP. Expected status code error, got %s", err)
72 } else if statusCodeError.Code != 500 {
73 return fmt.Errorf("Got wrong error status for OCSP. Expected 500, got %d", statusCodeError.Code)
74 }
75 return nil
76 }
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92 func TestIssuanceCertStorageFailed(t *testing.T) {
93 t.Parallel()
94 os.Setenv("DIRECTORY", "http://boulder.service.consul:4001/directory")
95
96 ctx := context.Background()
97
98
99
100 if os.Getenv("BOULDER_CONFIG_DIR") != "test/config-next" {
101 t.Skip("Skipping test because it requires the StoreLintingCertificateInsteadOfPrecertificate feature flag")
102 }
103
104 db, err := sql.Open("mysql", vars.DBConnSAIntegrationFullPerms)
105 test.AssertNotError(t, err, "failed to open db connection")
106
107 _, err = db.ExecContext(ctx, `DROP TRIGGER IF EXISTS fail_ready`)
108 test.AssertNotError(t, err, "failed to drop trigger")
109
110
111
112
113
114
115
116
117
118
119
120 _, err = db.ExecContext(ctx, `
121 CREATE TRIGGER fail_ready
122 BEFORE UPDATE ON certificateStatus
123 FOR EACH ROW BEGIN
124 DECLARE reversedName1 VARCHAR(255);
125 SELECT reversedName
126 INTO reversedName1
127 FROM issuedNames
128 WHERE serial = NEW.serial
129 AND reversedName LIKE "com.wantserror.%";
130 IF NEW.status = "good" AND reversedName1 != "" THEN
131 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Pretend there was an error updating the certificateStatus';
132 END IF;
133 END
134 `)
135 test.AssertNotError(t, err, "failed to create trigger")
136
137 defer db.ExecContext(ctx, `DROP TRIGGER IF EXISTS fail_ready`)
138
139 certKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
140 test.AssertNotError(t, err, "creating random cert key")
141
142
143 revokeMeDomain := "revokeme.wantserror.com"
144
145 _, err = authAndIssue(nil, certKey, []string{revokeMeDomain}, true)
146 test.AssertError(t, err, "expected authAndIssue to fail")
147
148 cert, err := getPrecertByName(db, revokeMeDomain)
149 test.AssertNotError(t, err, "failed to get certificate by name")
150
151 err = expectOCSP500(cert)
152 test.AssertNotError(t, err, "expected 500 error from OCSP")
153
154
155 config := fmt.Sprintf("%s/%s", os.Getenv("BOULDER_CONFIG_DIR"), "admin-revoker.json")
156 output, err := exec.Command("./bin/admin-revoker", "serial-revoke",
157 "-config", config,
158 core.SerialToString(cert.SerialNumber),
159 "0").CombinedOutput()
160 test.AssertNotError(t, err, fmt.Sprintf("revoking via admin-revoker: %s", string(output)))
161
162 _, err = ocsp_helper.Req(cert,
163 ocsp_helper.DefaultConfig.WithExpectStatus(ocsp.Revoked).WithExpectReason(ocsp.Unspecified))
164
165
166 blockMyKeyDomain := "blockmykey.wantserror.com"
167
168 _, err = authAndIssue(nil, certKey, []string{blockMyKeyDomain}, true)
169 test.AssertError(t, err, "expected authAndIssue to fail")
170
171 cert, err = getPrecertByName(db, blockMyKeyDomain)
172 test.AssertNotError(t, err, "failed to get certificate by name")
173
174 err = expectOCSP500(cert)
175 test.AssertNotError(t, err, "expected 500 error from OCSP")
176
177
178
179 revokeClient, err := makeClient()
180 test.AssertNotError(t, err, "creating second acme client")
181 res, err := authAndIssue(nil, certKey, []string{random_domain()}, true)
182 test.AssertNotError(t, err, "issuing second cert")
183
184 successfulCert := res.certs[0]
185 err = revokeClient.RevokeCertificate(
186 revokeClient.Account,
187 successfulCert,
188 certKey,
189 1,
190 )
191 test.AssertNotError(t, err, "revoking second certificate")
192
193 for i := 0; i < 300; i++ {
194 _, err = ocsp_helper.Req(successfulCert,
195 ocsp_helper.DefaultConfig.WithExpectStatus(ocsp.Revoked).WithExpectReason(ocsp.KeyCompromise))
196 if err == nil {
197 break
198 }
199 time.Sleep(15 * time.Millisecond)
200 }
201 test.AssertNotError(t, err, "expected status to eventually become revoked")
202
203
204 _, err = authAndIssue(nil, certKey, []string{"123.example.com"}, true)
205 test.AssertError(t, err, "expected authAndIssue to fail")
206 if !strings.Contains(err.Error(), "public key is forbidden") {
207 t.Errorf("expected issuance to be rejected with a bad pubkey")
208 }
209 }
210
View as plain text