1 package publisher
2
3 import (
4 "context"
5 "crypto/ecdsa"
6 "crypto/elliptic"
7 "crypto/rand"
8 "crypto/x509"
9 "crypto/x509/pkix"
10 "encoding/asn1"
11 "encoding/base64"
12 "encoding/json"
13 "fmt"
14 "math/big"
15 "net"
16 "net/http"
17 "net/http/httptest"
18 "net/url"
19 "strconv"
20 "strings"
21 "sync/atomic"
22 "testing"
23 "time"
24
25 ct "github.com/google/certificate-transparency-go"
26 "github.com/prometheus/client_golang/prometheus"
27
28 "github.com/letsencrypt/boulder/core"
29 "github.com/letsencrypt/boulder/issuance"
30 blog "github.com/letsencrypt/boulder/log"
31 "github.com/letsencrypt/boulder/metrics"
32 pubpb "github.com/letsencrypt/boulder/publisher/proto"
33 "github.com/letsencrypt/boulder/test"
34 )
35
36 func TestImplementation(t *testing.T) {
37 test.AssertImplementsGRPCServer(t, &Impl{}, pubpb.UnimplementedPublisherServer{})
38 }
39
40 var log = blog.UseMock()
41 var ctx = context.Background()
42
43 func getPort(srvURL string) (int, error) {
44 url, err := url.Parse(srvURL)
45 if err != nil {
46 return 0, err
47 }
48 _, portString, err := net.SplitHostPort(url.Host)
49 if err != nil {
50 return 0, err
51 }
52 port, err := strconv.ParseInt(portString, 10, 64)
53 if err != nil {
54 return 0, err
55 }
56 return int(port), nil
57 }
58
59 type testLogSrv struct {
60 *httptest.Server
61 submissions int64
62 }
63
64 func logSrv(k *ecdsa.PrivateKey) *testLogSrv {
65 testLog := &testLogSrv{}
66 m := http.NewServeMux()
67 m.HandleFunc("/ct/", func(w http.ResponseWriter, r *http.Request) {
68 decoder := json.NewDecoder(r.Body)
69 var jsonReq ctSubmissionRequest
70 err := decoder.Decode(&jsonReq)
71 if err != nil {
72 return
73 }
74 precert := false
75 if r.URL.Path == "/ct/v1/add-pre-chain" {
76 precert = true
77 }
78 sct := CreateTestingSignedSCT(jsonReq.Chain, k, precert, time.Now())
79 fmt.Fprint(w, string(sct))
80 atomic.AddInt64(&testLog.submissions, 1)
81 })
82
83 testLog.Server = httptest.NewUnstartedServer(m)
84 testLog.Server.Start()
85 return testLog
86 }
87
88
89 func lyingLogSrv(k *ecdsa.PrivateKey, timestamp time.Time) *testLogSrv {
90 testLog := &testLogSrv{}
91 m := http.NewServeMux()
92 m.HandleFunc("/ct/", func(w http.ResponseWriter, r *http.Request) {
93 decoder := json.NewDecoder(r.Body)
94 var jsonReq ctSubmissionRequest
95 err := decoder.Decode(&jsonReq)
96 if err != nil {
97 return
98 }
99 precert := false
100 if r.URL.Path == "/ct/v1/add-pre-chain" {
101 precert = true
102 }
103 sct := CreateTestingSignedSCT(jsonReq.Chain, k, precert, timestamp)
104 fmt.Fprint(w, string(sct))
105 atomic.AddInt64(&testLog.submissions, 1)
106 })
107
108 testLog.Server = httptest.NewUnstartedServer(m)
109 testLog.Server.Start()
110 return testLog
111 }
112
113 func errorBodyLogSrv() *httptest.Server {
114 m := http.NewServeMux()
115 m.HandleFunc("/ct/", func(w http.ResponseWriter, r *http.Request) {
116 w.WriteHeader(http.StatusBadRequest)
117 w.Write([]byte("well this isn't good now is it."))
118 })
119
120 server := httptest.NewUnstartedServer(m)
121 server.Start()
122 return server
123 }
124
125 func setup(t *testing.T) (*Impl, *x509.Certificate, *ecdsa.PrivateKey) {
126
127 chain1, err := issuance.LoadChain([]string{
128 "../test/hierarchy/int-r3-cross.cert.pem",
129 "../test/hierarchy/root-dst.cert.pem",
130 })
131 test.AssertNotError(t, err, "failed to load chain1.")
132
133
134 chain2, err := issuance.LoadChain([]string{
135 "../test/hierarchy/int-r3.cert.pem",
136 "../test/hierarchy/root-x1.cert.pem",
137 })
138 test.AssertNotError(t, err, "failed to load chain2.")
139
140
141 chain3, err := issuance.LoadChain([]string{
142 "../test/hierarchy/int-e1.cert.pem",
143 "../test/hierarchy/root-x2.cert.pem",
144 })
145 test.AssertNotError(t, err, "failed to load chain3.")
146
147
148 issuerBundles := map[issuance.IssuerNameID][]ct.ASN1Cert{
149 chain1[0].NameID(): GetCTBundleForChain(chain1),
150 chain2[0].NameID(): GetCTBundleForChain(chain2),
151 chain3[0].NameID(): GetCTBundleForChain(chain3),
152 }
153 pub := New(
154 issuerBundles,
155 "test-user-agent/1.0",
156 log,
157 metrics.NoopRegisterer)
158
159
160 leaf, err := core.LoadCert("../test/hierarchy/ee-r3.cert.pem")
161 test.AssertNotError(t, err, "unable to load leaf certificate.")
162
163 k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
164 test.AssertNotError(t, err, "Couldn't generate test key")
165
166 return pub, leaf, k
167 }
168
169 func addLog(t *testing.T, port int, pubKey *ecdsa.PublicKey) *Log {
170 uri := fmt.Sprintf("http://localhost:%d", port)
171 der, err := x509.MarshalPKIXPublicKey(pubKey)
172 test.AssertNotError(t, err, "Failed to marshal key")
173 newLog, err := NewLog(uri, base64.StdEncoding.EncodeToString(der), "test-user-agent/1.0", log)
174 test.AssertNotError(t, err, "Couldn't create log")
175 test.AssertEquals(t, newLog.uri, fmt.Sprintf("http://localhost:%d", port))
176 return newLog
177 }
178
179 func makePrecert(k *ecdsa.PrivateKey) (map[issuance.IssuerNameID][]ct.ASN1Cert, []byte, error) {
180 rootTmpl := x509.Certificate{
181 SerialNumber: big.NewInt(0),
182 Subject: pkix.Name{CommonName: "root"},
183 BasicConstraintsValid: true,
184 IsCA: true,
185 }
186 rootBytes, err := x509.CreateCertificate(rand.Reader, &rootTmpl, &rootTmpl, k.Public(), k)
187 if err != nil {
188 return nil, nil, err
189 }
190 root, err := x509.ParseCertificate(rootBytes)
191 if err != nil {
192 return nil, nil, err
193 }
194 precertTmpl := x509.Certificate{
195 SerialNumber: big.NewInt(0),
196 ExtraExtensions: []pkix.Extension{
197 {Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 3}, Critical: true, Value: []byte{0x05, 0x00}},
198 },
199 }
200 precert, err := x509.CreateCertificate(rand.Reader, &precertTmpl, root, k.Public(), k)
201 if err != nil {
202 return nil, nil, err
203 }
204 precertX509, err := x509.ParseCertificate(precert)
205 if err != nil {
206 return nil, nil, err
207 }
208 precertIssuerNameID := issuance.GetIssuerNameID(precertX509)
209 bundles := map[issuance.IssuerNameID][]ct.ASN1Cert{
210 precertIssuerNameID: {
211 ct.ASN1Cert{Data: rootBytes},
212 },
213 }
214 return bundles, precert, err
215 }
216
217 func TestTimestampVerificationFuture(t *testing.T) {
218 pub, _, k := setup(t)
219
220 server := lyingLogSrv(k, time.Now().Add(time.Hour))
221 defer server.Close()
222 port, err := getPort(server.URL)
223 test.AssertNotError(t, err, "Failed to get test server port")
224 testLog := addLog(t, port, &k.PublicKey)
225
226
227 issuerBundles, precert, err := makePrecert(k)
228 test.AssertNotError(t, err, "Failed to create test leaf")
229 pub.issuerBundles = issuerBundles
230
231 _, err = pub.SubmitToSingleCTWithResult(ctx, &pubpb.Request{LogURL: testLog.uri, LogPublicKey: testLog.logID, Der: precert, Precert: true})
232 if err == nil {
233 t.Fatal("Expected error for lying log server, got none")
234 }
235 if !strings.HasPrefix(err.Error(), "SCT Timestamp was too far in the future") {
236 t.Fatalf("Got wrong error: %s", err)
237 }
238 }
239
240 func TestTimestampVerificationPast(t *testing.T) {
241 pub, _, k := setup(t)
242
243 server := lyingLogSrv(k, time.Now().Add(-time.Hour))
244 defer server.Close()
245 port, err := getPort(server.URL)
246 test.AssertNotError(t, err, "Failed to get test server port")
247 testLog := addLog(t, port, &k.PublicKey)
248
249
250 issuerBundles, precert, err := makePrecert(k)
251 test.AssertNotError(t, err, "Failed to create test leaf")
252
253 pub.issuerBundles = issuerBundles
254
255 _, err = pub.SubmitToSingleCTWithResult(ctx, &pubpb.Request{LogURL: testLog.uri, LogPublicKey: testLog.logID, Der: precert, Precert: true})
256 if err == nil {
257 t.Fatal("Expected error for lying log server, got none")
258 }
259 if !strings.HasPrefix(err.Error(), "SCT Timestamp was too far in the past") {
260 t.Fatalf("Got wrong error: %s", err)
261 }
262 }
263
264 func TestLogCache(t *testing.T) {
265 cache := logCache{
266 logs: make(map[string]*Log),
267 }
268
269
270 _, err := cache.AddLog("www.test.com", "1234", "test-user-agent/1.0", log)
271 test.AssertError(t, err, "AddLog() with invalid base64 pk didn't error")
272
273
274 _, err = cache.AddLog(":", "", "test-user-agent/1.0", log)
275 test.AssertError(t, err, "AddLog() with an invalid log URI didn't error")
276
277
278 k1, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
279 test.AssertNotError(t, err, "ecdsa.GenerateKey() failed for k1")
280 der1, err := x509.MarshalPKIXPublicKey(&k1.PublicKey)
281 test.AssertNotError(t, err, "x509.MarshalPKIXPublicKey(der1) failed")
282 k1b64 := base64.StdEncoding.EncodeToString(der1)
283
284
285 k2, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
286 test.AssertNotError(t, err, "ecdsa.GenerateKey() failed for k2")
287 der2, err := x509.MarshalPKIXPublicKey(&k2.PublicKey)
288 test.AssertNotError(t, err, "x509.MarshalPKIXPublicKey(der2) failed")
289 k2b64 := base64.StdEncoding.EncodeToString(der2)
290
291
292 l1, err := cache.AddLog("http://log.one.example.com", k1b64, "test-user-agent/1.0", log)
293 test.AssertNotError(t, err, "cache.AddLog() failed for log 1")
294 test.AssertEquals(t, cache.Len(), 1)
295 test.AssertEquals(t, l1.uri, "http://log.one.example.com")
296 test.AssertEquals(t, l1.logID, k1b64)
297
298
299 l1, err = cache.AddLog("http://log.one.example.com", k1b64, "test-user-agent/1.0", log)
300 test.AssertNotError(t, err, "cache.AddLog() failed for second add of log 1")
301 test.AssertEquals(t, cache.Len(), 1)
302 test.AssertEquals(t, l1.uri, "http://log.one.example.com")
303 test.AssertEquals(t, l1.logID, k1b64)
304
305
306 l2, err := cache.AddLog("http://log.two.example.com", k2b64, "test-user-agent/1.0", log)
307 test.AssertNotError(t, err, "cache.AddLog() failed for log 2")
308 test.AssertEquals(t, cache.Len(), 2)
309 test.AssertEquals(t, l2.uri, "http://log.two.example.com")
310 test.AssertEquals(t, l2.logID, k2b64)
311 }
312
313 func TestLogErrorBody(t *testing.T) {
314 pub, leaf, k := setup(t)
315
316 srv := errorBodyLogSrv()
317 defer srv.Close()
318 port, err := getPort(srv.URL)
319 test.AssertNotError(t, err, "Failed to get test server port")
320
321 log.Clear()
322 logURI := fmt.Sprintf("http://localhost:%d", port)
323 pkDER, err := x509.MarshalPKIXPublicKey(&k.PublicKey)
324 test.AssertNotError(t, err, "Failed to marshal key")
325 pkB64 := base64.StdEncoding.EncodeToString(pkDER)
326 _, err = pub.SubmitToSingleCTWithResult(context.Background(), &pubpb.Request{
327 LogURL: logURI,
328 LogPublicKey: pkB64,
329 Der: leaf.Raw,
330 })
331 test.AssertError(t, err, "SubmitToSingleCTWithResult didn't fail")
332 test.AssertEquals(t, len(log.GetAllMatching("well this isn't good now is it")), 1)
333 }
334
335 func TestHTTPStatusMetric(t *testing.T) {
336 pub, leaf, k := setup(t)
337
338 badSrv := errorBodyLogSrv()
339 defer badSrv.Close()
340 port, err := getPort(badSrv.URL)
341 test.AssertNotError(t, err, "Failed to get test server port")
342 logURI := fmt.Sprintf("http://localhost:%d", port)
343
344 pkDER, err := x509.MarshalPKIXPublicKey(&k.PublicKey)
345 test.AssertNotError(t, err, "Failed to marshal key")
346 pkB64 := base64.StdEncoding.EncodeToString(pkDER)
347 _, err = pub.SubmitToSingleCTWithResult(context.Background(), &pubpb.Request{
348 LogURL: logURI,
349 LogPublicKey: pkB64,
350 Der: leaf.Raw,
351 })
352 test.AssertError(t, err, "SubmitToSingleCTWithResult didn't fail")
353 test.AssertMetricWithLabelsEquals(t, pub.metrics.submissionLatency, prometheus.Labels{
354 "log": logURI,
355 "status": "error",
356 "http_status": "400",
357 }, 1)
358
359 pub, leaf, k = setup(t)
360 pkDER, err = x509.MarshalPKIXPublicKey(&k.PublicKey)
361 test.AssertNotError(t, err, "Failed to marshal key")
362 pkB64 = base64.StdEncoding.EncodeToString(pkDER)
363 workingSrv := logSrv(k)
364 defer workingSrv.Close()
365 port, err = getPort(workingSrv.URL)
366 test.AssertNotError(t, err, "Failed to get test server port")
367 logURI = fmt.Sprintf("http://localhost:%d", port)
368
369 _, err = pub.SubmitToSingleCTWithResult(context.Background(), &pubpb.Request{
370 LogURL: logURI,
371 LogPublicKey: pkB64,
372 Der: leaf.Raw,
373 })
374 test.AssertNotError(t, err, "SubmitToSingleCTWithResult failed")
375 test.AssertMetricWithLabelsEquals(t, pub.metrics.submissionLatency, prometheus.Labels{
376 "log": logURI,
377 "status": "success",
378 "http_status": "",
379 }, 1)
380 }
381 func Test_GetCTBundleForChain(t *testing.T) {
382 chain, err := issuance.LoadChain([]string{
383 "../test/hierarchy/int-r3.cert.pem",
384 "../test/hierarchy/root-x1.cert.pem",
385 })
386 test.AssertNotError(t, err, "Failed to load chain.")
387 expect := []ct.ASN1Cert{{Data: chain[0].Raw}}
388 type args struct {
389 chain []*issuance.Certificate
390 }
391 tests := []struct {
392 name string
393 args args
394 want []ct.ASN1Cert
395 }{
396 {"Create a ct bundle with a single intermediate", args{chain}, expect},
397 }
398 for _, tt := range tests {
399 t.Run(tt.name, func(t *testing.T) {
400 bundle := GetCTBundleForChain(tt.args.chain)
401 test.AssertDeepEquals(t, bundle, tt.want)
402 })
403 }
404 }
405
View as plain text