1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package integration
18
19 import (
20 "bufio"
21 "bytes"
22 "context"
23 "crypto"
24 cryptorand "crypto/rand"
25 "crypto/sha256"
26 "encoding/pem"
27 "fmt"
28 "math/rand"
29 "net"
30 "net/http"
31 "os"
32 "path/filepath"
33 "reflect"
34 "regexp"
35 "strconv"
36 "strings"
37 "time"
38
39 "github.com/google/certificate-transparency-go/client"
40 "github.com/google/certificate-transparency-go/jsonclient"
41 "github.com/google/certificate-transparency-go/trillian/ctfe"
42 "github.com/google/certificate-transparency-go/trillian/ctfe/configpb"
43 "github.com/google/certificate-transparency-go/x509"
44 "github.com/google/certificate-transparency-go/x509/pkix"
45 "github.com/google/trillian"
46 "github.com/google/trillian/crypto/keyspb"
47 "github.com/kylelemons/godebug/pretty"
48 "github.com/transparency-dev/merkle"
49 "github.com/transparency-dev/merkle/proof"
50 "github.com/transparency-dev/merkle/rfc6962"
51 "golang.org/x/net/context/ctxhttp"
52 "google.golang.org/grpc"
53 "google.golang.org/grpc/credentials/insecure"
54 "google.golang.org/protobuf/types/known/fieldmaskpb"
55
56 ct "github.com/google/certificate-transparency-go"
57 )
58
59 const (
60 reqStatsRE = `^http_reqs{ep="(\w+)",logid="(\d+)"} ([.\d]+)$`
61 rspStatsRE = `^http_rsps{ep="(\w+)",logid="(\d+)",rc="(\d+)"} (?P<val>[.\d]+)$`
62 )
63
64
65
66
67
68
69 var DefaultTransport = &http.Transport{
70 Proxy: http.ProxyFromEnvironment,
71 DialContext: (&net.Dialer{
72 Timeout: 30 * time.Second,
73 KeepAlive: 30 * time.Second,
74 }).DialContext,
75 MaxIdleConns: 1000,
76 MaxIdleConnsPerHost: 1000,
77 IdleConnTimeout: 90 * time.Second,
78 TLSHandshakeTimeout: 10 * time.Second,
79 ExpectContinueTimeout: 1 * time.Second,
80 }
81
82
83 type ClientPool interface {
84
85 Next() *client.LogClient
86 }
87
88
89 type RandomPool []*client.LogClient
90
91 var _ ClientPool = &RandomPool{}
92
93
94 func (p RandomPool) Next() *client.LogClient {
95 if len(p) == 0 {
96 return nil
97 }
98 return p[rand.Intn(len(p))]
99 }
100
101
102 func NewRandomPool(servers string, pubKey *keyspb.PublicKey, prefix string) (ClientPool, error) {
103 opts := jsonclient.Options{
104 PublicKeyDER: pubKey.GetDer(),
105 UserAgent: "ct-go-integrationtest/1.0",
106 }
107
108 hc := &http.Client{Transport: DefaultTransport}
109
110 var pool RandomPool
111 for _, s := range strings.Split(servers, ",") {
112 c, err := client.New(fmt.Sprintf("http://%s/%s", s, prefix), hc, opts)
113 if err != nil {
114 return nil, fmt.Errorf("failed to create LogClient instance: %v", err)
115 }
116 pool = append(pool, c)
117 }
118 return &pool, nil
119 }
120
121
122 type testInfo struct {
123 prefix string
124 cfg *configpb.LogConfig
125 metricsServers string
126 adminServer string
127 stats *logStats
128 pool ClientPool
129 hasher merkle.LogHasher
130 }
131
132 func (t *testInfo) checkStats() error {
133 return t.stats.check(t.cfg, t.metricsServers)
134 }
135
136 func (t *testInfo) client() *client.LogClient {
137 return t.pool.Next()
138 }
139
140
141 func (t *testInfo) awaitTreeSize(ctx context.Context, size uint64, exact bool, mmd time.Duration) (*ct.SignedTreeHead, error) {
142 var sth *ct.SignedTreeHead
143 deadline := time.Now().Add(mmd)
144 for sth == nil || sth.TreeSize < size {
145 if time.Now().After(deadline) {
146 return nil, fmt.Errorf("deadline for STH inclusion expired (MMD=%v)", mmd)
147 }
148 time.Sleep(200 * time.Millisecond)
149 var err error
150 sth, err = t.client().GetSTH(ctx)
151 if t.stats != nil {
152 t.stats.expect(ctfe.GetSTHName, 200)
153 }
154 if err != nil {
155 return nil, fmt.Errorf("failed to get STH: %v", err)
156 }
157 }
158 if exact && sth.TreeSize != size {
159 return nil, fmt.Errorf("sth.TreeSize=%d; want: %d", sth.TreeSize, size)
160 }
161 return sth, nil
162 }
163
164
165
166 func (t *testInfo) checkInclusionOf(ctx context.Context, chain []ct.ASN1Cert, sct *ct.SignedCertificateTimestamp, sth *ct.SignedTreeHead) error {
167 leaf := ct.MerkleTreeLeaf{
168 Version: ct.V1,
169 LeafType: ct.TimestampedEntryLeafType,
170 TimestampedEntry: &ct.TimestampedEntry{
171 Timestamp: sct.Timestamp,
172 EntryType: ct.X509LogEntryType,
173 X509Entry: &(chain[0]),
174 Extensions: sct.Extensions,
175 },
176 }
177 leafHash, err := ct.LeafHashForLeaf(&leaf)
178 if err != nil {
179 return fmt.Errorf("ct.LeafHashForLeaf(leaf[%d])=(nil,%v); want (_,nil)", 0, err)
180 }
181 rsp, err := t.client().GetProofByHash(ctx, leafHash[:], sth.TreeSize)
182 t.stats.expect(ctfe.GetProofByHashName, 200)
183 if err != nil {
184 return fmt.Errorf("got GetProofByHash(sct[%d],size=%d)=(nil,%v); want (_,nil)", 0, sth.TreeSize, err)
185 }
186 if err := proof.VerifyInclusion(t.hasher, uint64(rsp.LeafIndex), sth.TreeSize, leafHash[:], rsp.AuditPath, sth.SHA256RootHash[:]); err != nil {
187 return fmt.Errorf("got VerifyInclusion(%d, %d,...)=%v", 0, sth.TreeSize, err)
188 }
189 return nil
190 }
191
192
193 func (t *testInfo) checkInclusionOfPreCert(ctx context.Context, tbs []byte, issuer *x509.Certificate, sct *ct.SignedCertificateTimestamp, sth *ct.SignedTreeHead) error {
194 leaf := ct.MerkleTreeLeaf{
195 Version: ct.V1,
196 LeafType: ct.TimestampedEntryLeafType,
197 TimestampedEntry: &ct.TimestampedEntry{
198 Timestamp: sct.Timestamp,
199 EntryType: ct.PrecertLogEntryType,
200 PrecertEntry: &ct.PreCert{
201 IssuerKeyHash: sha256.Sum256(issuer.RawSubjectPublicKeyInfo),
202 TBSCertificate: tbs,
203 },
204 Extensions: sct.Extensions,
205 },
206 }
207 leafHash, err := ct.LeafHashForLeaf(&leaf)
208 if err != nil {
209 return fmt.Errorf("ct.LeafHashForLeaf(precertLeaf)=(nil,%v); want (_,nil)", err)
210 }
211 rsp, err := t.client().GetProofByHash(ctx, leafHash[:], sth.TreeSize)
212 t.stats.expect(ctfe.GetProofByHashName, 200)
213 if err != nil {
214 return fmt.Errorf("got GetProofByHash(sct, size=%d)=nil,%v", sth.TreeSize, err)
215 }
216 fmt.Printf("%s: Inclusion proof leaf %d @ %d -> root %d = %x\n", t.prefix, rsp.LeafIndex, sct.Timestamp, sth.TreeSize, rsp.AuditPath)
217 if err := proof.VerifyInclusion(t.hasher, uint64(rsp.LeafIndex), sth.TreeSize, leafHash[:], rsp.AuditPath, sth.SHA256RootHash[:]); err != nil {
218 return fmt.Errorf("got VerifyInclusion(%d,%d,...)=%v; want nil", rsp.LeafIndex, sth.TreeSize, err)
219 }
220 if err := t.checkStats(); err != nil {
221 return fmt.Errorf("stats check failed: %v", err)
222 }
223 return nil
224 }
225
226
227 func (t *testInfo) checkPreCertEntry(ctx context.Context, precertIndex int64, tbs []byte) error {
228 precertEntries, err := t.client().GetEntries(ctx, precertIndex, precertIndex)
229 t.stats.expect(ctfe.GetEntriesName, 200)
230 if err != nil {
231 return fmt.Errorf("got GetEntries(%d,%d)=(nil,%v); want (_,nil)", precertIndex, precertIndex, err)
232 }
233 if len(precertEntries) != 1 {
234 return fmt.Errorf("len(entries)=%d; want %d", len(precertEntries), 1)
235 }
236 leaf := precertEntries[0].Leaf
237 ts := leaf.TimestampedEntry
238 fmt.Printf("%s: Entry[%d] = {Index:%d Leaf:{Version:%v TS:{EntryType:%v Timestamp:%v}}}\n",
239 t.prefix, precertIndex, precertEntries[0].Index, leaf.Version, ts.EntryType, timeFromMS(ts.Timestamp))
240
241 if ts.EntryType != ct.PrecertLogEntryType {
242 return fmt.Errorf("leaf[%d].ts.EntryType=%v; want PrecertLogEntryType", precertIndex, ts.EntryType)
243 }
244 if !bytes.Equal(ts.PrecertEntry.TBSCertificate, tbs) {
245 return fmt.Errorf("leaf[%d].ts.PrecertEntry differs from originally uploaded cert", precertIndex)
246 }
247 if err := t.checkStats(); err != nil {
248 return fmt.Errorf("stats check failed: %v", err)
249 }
250 return nil
251 }
252
253
254
255
256
257 func RunCTIntegrationForLog(cfg *configpb.LogConfig, servers, metricsServers, testdir string, mmd time.Duration, stats *logStats) error {
258 ctx := context.Background()
259 pool, err := NewRandomPool(servers, cfg.PublicKey, cfg.Prefix)
260 if err != nil {
261 return fmt.Errorf("failed to create pool: %v", err)
262 }
263 t := testInfo{
264 prefix: cfg.Prefix,
265 cfg: cfg,
266 metricsServers: metricsServers,
267 stats: stats,
268 pool: pool,
269 hasher: rfc6962.DefaultHasher,
270 }
271
272 if err := t.checkStats(); err != nil {
273 return fmt.Errorf("stats check failed: %v", err)
274 }
275
276
277 roots, err := t.client().GetAcceptedRoots(ctx)
278 t.stats.expect(ctfe.GetRootsName, 200)
279 if err != nil {
280 return fmt.Errorf("got GetAcceptedRoots()=(nil,%v); want (_,nil)", err)
281 }
282 if len(roots) > 2 {
283 return fmt.Errorf("len(GetAcceptedRoots())=%d; want <=2", len(roots))
284 }
285
286
287 sth0, err := t.client().GetSTH(ctx)
288 t.stats.expect(ctfe.GetSTHName, 200)
289 if err != nil {
290 return fmt.Errorf("got GetSTH()=(nil,%v); want (_,nil)", err)
291 }
292 if sth0.Version != 0 {
293 return fmt.Errorf("sth.Version=%v; want V1(0)", sth0.Version)
294 }
295 if sth0.TreeSize != 0 {
296 return fmt.Errorf("sth.TreeSize=%d; want 0", sth0.TreeSize)
297 }
298 fmt.Printf("%s: Got STH(time=%q, size=%d): roothash=%x\n", t.prefix, timeFromMS(sth0.Timestamp), sth0.TreeSize, sth0.SHA256RootHash)
299
300
301 var scts [21]*ct.SignedCertificateTimestamp
302 var chain [21][]ct.ASN1Cert
303 chain[0], err = GetChain(testdir, "int-ca.cert")
304 if err != nil {
305 return fmt.Errorf("failed to load certificate: %v", err)
306 }
307 issuer, err := x509.ParseCertificate(chain[0][0].Data)
308 if err != nil {
309 return fmt.Errorf("failed to parse int-ca.cert: %v", err)
310 }
311 scts[0], err = t.client().AddChain(ctx, chain[0])
312 t.stats.expect(ctfe.AddChainName, 200)
313 if err != nil {
314 return fmt.Errorf("got AddChain(int-ca.cert)=(nil,%v); want (_,nil)", err)
315 }
316
317 fmt.Printf("%s: Uploaded int-ca.cert to %v log, got SCT(time=%q)\n", t.prefix, scts[0].SCTVersion, timeFromMS(scts[0].Timestamp))
318
319
320 sth1, err := t.awaitTreeSize(ctx, 1, true, mmd)
321 if err != nil {
322 return fmt.Errorf("AwaitTreeSize(1)=(nil,%v); want (_,nil)", err)
323 }
324 fmt.Printf("%s: Got STH(time=%q, size=%d): roothash=%x\n", t.prefix, timeFromMS(sth1.Timestamp), sth1.TreeSize, sth1.SHA256RootHash)
325 if err := t.checkStats(); err != nil {
326 return fmt.Errorf("stats check failed: %v", err)
327 }
328 if err := t.checkInclusionOf(ctx, chain[0], scts[0], sth1); err != nil {
329 return err
330 }
331
332
333 var sctCopy *ct.SignedCertificateTimestamp
334 sctCopy, err = t.client().AddChain(ctx, chain[0])
335 if err != nil {
336 return fmt.Errorf("got re-AddChain(int-ca.cert)=(nil,%v); want (_,nil)", err)
337 }
338 t.stats.expect(ctfe.AddChainName, 200)
339 if scts[0].Timestamp != sctCopy.Timestamp {
340 return fmt.Errorf("got sct @ %v; want @ %v", sctCopy, scts[0])
341 }
342
343
344 chain[1], err = GetChain(testdir, "leaf01.chain")
345 if err != nil {
346 return fmt.Errorf("failed to load certificate: %v", err)
347 }
348 scts[1], err = t.client().AddChain(ctx, chain[1])
349 t.stats.expect(ctfe.AddChainName, 200)
350 if err != nil {
351 return fmt.Errorf("got AddChain(leaf01)=(nil,%v); want (_,nil)", err)
352 }
353 fmt.Printf("%s: Uploaded cert01.chain to %v log, got SCT(time=%q)\n", t.prefix, scts[1].SCTVersion, timeFromMS(scts[1].Timestamp))
354 sth2, err := t.awaitTreeSize(ctx, 2, true, mmd)
355 if err != nil {
356 return fmt.Errorf("failed to get STH for size=1: %v", err)
357 }
358 fmt.Printf("%s: Got STH(time=%q, size=%d): roothash=%x\n", t.prefix, timeFromMS(sth2.Timestamp), sth2.TreeSize, sth2.SHA256RootHash)
359
360
361 proof12, err := t.client().GetSTHConsistency(ctx, 1, 2)
362 t.stats.expect(ctfe.GetSTHConsistencyName, 200)
363 if err != nil {
364 return fmt.Errorf("got GetSTHConsistency(1, 2)=(nil,%v); want (_,nil)", err)
365 }
366
367
368
369
370
371
372
373 if len(proof12) != 1 {
374 return fmt.Errorf("len(proof12)=%d; want 1", len(proof12))
375 }
376 if err := t.checkCTConsistencyProof(sth1, sth2, proof12); err != nil {
377 return fmt.Errorf("got CheckCTConsistencyProof(sth1,sth2,proof12)=%v; want nil", err)
378 }
379 if err := t.checkStats(); err != nil {
380 return fmt.Errorf("stats check failed: %v", err)
381 }
382
383
384 proof02, err := t.client().GetSTHConsistency(ctx, 0, 2)
385 t.stats.expect(ctfe.GetSTHConsistencyName, 200)
386 if err != nil {
387 return fmt.Errorf("got GetSTHConsistency(0, 2)=(nil,%v); want (_,nil)", err)
388 }
389 if len(proof02) != 0 {
390 return fmt.Errorf("len(proof02)=%d; want 0", len(proof02))
391 }
392 if err := t.checkStats(); err != nil {
393 return fmt.Errorf("stats check failed: %v", err)
394 }
395
396
397 atLeast := 4
398 count := atLeast + rand.Intn(20-atLeast)
399 for i := 2; i <= count; i++ {
400 filename := fmt.Sprintf("leaf%02d.chain", i)
401 chain[i], err = GetChain(testdir, filename)
402 if err != nil {
403 return fmt.Errorf("failed to load certificate: %v", err)
404 }
405 scts[i], err = t.client().AddChain(ctx, chain[i])
406 t.stats.expect(ctfe.AddChainName, 200)
407 if err != nil {
408 return fmt.Errorf("got AddChain(leaf%02d)=(nil,%v); want (_,nil)", i, err)
409 }
410 }
411 fmt.Printf("%s: Uploaded leaf02-leaf%02d to log, got SCTs\n", t.prefix, count)
412 if err := t.checkStats(); err != nil {
413 return fmt.Errorf("stats check failed: %v", err)
414 }
415
416
417 treeSize := 1 + count
418 sthN, err := t.awaitTreeSize(ctx, uint64(treeSize), true, mmd)
419 if err != nil {
420 return fmt.Errorf("AwaitTreeSize(%d)=(nil,%v); want (_,nil)", treeSize, err)
421 }
422 fmt.Printf("%s: Got STH(time=%q, size=%d): roothash=%x\n", t.prefix, timeFromMS(sthN.Timestamp), sthN.TreeSize, sthN.SHA256RootHash)
423
424
425 proof2N, err := t.client().GetSTHConsistency(ctx, 2, uint64(treeSize))
426 t.stats.expect(ctfe.GetSTHConsistencyName, 200)
427 if err != nil {
428 return fmt.Errorf("got GetSTHConsistency(2, %d)=(nil,%v); want (_,nil)", treeSize, err)
429 }
430 fmt.Printf("%s: Proof size 2->%d: %x\n", t.prefix, treeSize, proof2N)
431 if err := t.checkCTConsistencyProof(sth2, sthN, proof2N); err != nil {
432 return fmt.Errorf("got CheckCTConsistencyProof(sth2,sthN,proof2N)=%v; want nil", err)
433 }
434
435
436 entries, err := t.client().GetEntries(ctx, 1, int64(count))
437 t.stats.expect(ctfe.GetEntriesName, 200)
438 if err != nil {
439 return fmt.Errorf("got GetEntries(1,%d)=(nil,%v); want (_,nil)", count, err)
440 }
441 if len(entries) < count {
442 return fmt.Errorf("len(entries)=%d; want %d", len(entries), count)
443 }
444 gotHashes := make(map[[sha256.Size]byte]bool)
445 wantHashes := make(map[[sha256.Size]byte]bool)
446 for i, entry := range entries {
447 leaf := entry.Leaf
448 ts := leaf.TimestampedEntry
449 if leaf.Version != 0 {
450 return fmt.Errorf("leaf[%d].Version=%v; want V1(0)", i, leaf.Version)
451 }
452 if leaf.LeafType != ct.TimestampedEntryLeafType {
453 return fmt.Errorf("leaf[%d].Version=%v; want TimestampedEntryLeafType", i, leaf.LeafType)
454 }
455
456 if ts.EntryType != ct.X509LogEntryType {
457 return fmt.Errorf("leaf[%d].ts.EntryType=%v; want X509LogEntryType", i, ts.EntryType)
458 }
459
460
461 gotHashes[sha256.Sum256(ts.X509Entry.Data)] = true
462 wantHashes[sha256.Sum256(chain[i+1][0].Data)] = true
463 }
464 if diff := pretty.Compare(gotHashes, wantHashes); diff != "" {
465 return fmt.Errorf("retrieved cert hashes don't match uploaded cert hashes, diff:\n%v", diff)
466 }
467 fmt.Printf("%s: Got entries [1:%d+1]\n", t.prefix, count)
468 if err := t.checkStats(); err != nil {
469 return fmt.Errorf("stats check failed: %v", err)
470 }
471
472
473 for i := 1; i <= count; i++ {
474 if err := t.checkInclusionOf(ctx, chain[i], scts[i], sthN); err != nil {
475 return err
476 }
477 }
478 fmt.Printf("%s: Got inclusion proofs [1:%d+1]\n", t.prefix, count)
479 if err := t.checkStats(); err != nil {
480 return fmt.Errorf("stats check failed: %v", err)
481 }
482
483
484 corruptChain := make([]ct.ASN1Cert, len(chain[1]))
485 copy(corruptChain, chain[1])
486 corruptAt := len(corruptChain[0].Data) - 3
487 corruptChain[0].Data[corruptAt] = corruptChain[0].Data[corruptAt] + 1
488 if sct, err := t.client().AddChain(ctx, corruptChain); err == nil {
489 return fmt.Errorf("got AddChain(corrupt-cert)=(%+v,nil); want (nil,error)", sct)
490 }
491 t.stats.expect(ctfe.AddChainName, 400)
492 fmt.Printf("%s: AddChain(corrupt-cert)=nil,%v\n", t.prefix, err)
493
494
495 if sct, err := t.client().AddChain(ctx, chain[1][0:0]); err == nil {
496 return fmt.Errorf("got AddChain(leaf-only)=(%+v,nil); want (nil,error)", sct)
497 }
498 t.stats.expect(ctfe.AddChainName, 400)
499 fmt.Printf("%s: AddChain(leaf-only)=nil,%v\n", t.prefix, err)
500 if err := t.checkStats(); err != nil {
501 return fmt.Errorf("stats check failed: %v", err)
502 }
503
504
505 signer, err := MakeSigner(testdir)
506 if err != nil {
507 return fmt.Errorf("failed to retrieve signer for re-signing: %v", err)
508 }
509 generator, err := NewSyntheticChainGenerator(chain[1], signer, time.Time{})
510 if err != nil {
511 return fmt.Errorf("failed to create chain generator: %v", err)
512 }
513
514 prechain, tbs, err := generator.PreCertChain()
515 if err != nil {
516 return fmt.Errorf("failed to build pre-certificate: %v", err)
517 }
518 precertSCT, err := t.client().AddPreChain(ctx, prechain)
519 t.stats.expect(ctfe.AddPreChainName, 200)
520 if err != nil {
521 return fmt.Errorf("got AddPreChain()=(nil,%v); want (_,nil)", err)
522 }
523 fmt.Printf("%s: Uploaded precert to %v log, got SCT(time=%q)\n", t.prefix, precertSCT.SCTVersion, timeFromMS(precertSCT.Timestamp))
524 treeSize++
525 sthN1, err := t.awaitTreeSize(ctx, uint64(treeSize), true, mmd)
526 if err != nil {
527 return fmt.Errorf("AwaitTreeSize(%d)=(nil,%v); want (_,nil)", treeSize, err)
528 }
529 fmt.Printf("%s: Got STH(time=%q, size=%d): roothash=%x\n", t.prefix, timeFromMS(sthN1.Timestamp), sthN1.TreeSize, sthN1.SHA256RootHash)
530
531
532 precertIndex := int64(count + 1)
533 if err := t.checkPreCertEntry(ctx, precertIndex, tbs); err != nil {
534 return fmt.Errorf("failed to check pre-cert entry: %v", err)
535 }
536
537
538 if err := t.checkInclusionOfPreCert(ctx, tbs, issuer, precertSCT, sthN1); err != nil {
539 return fmt.Errorf("failed to check inclusion of pre-cert entry: %v", err)
540 }
541
542
543 if rsp, err := t.client().GetSTHConsistency(ctx, 2, 299); err == nil {
544 return fmt.Errorf("got GetSTHConsistency(2,299)=(%+v,nil); want (nil,_)", rsp)
545 }
546 t.stats.expect(ctfe.GetSTHConsistencyName, 400)
547 fmt.Printf("%s: GetSTHConsistency(2,299)=(nil,_)\n", t.prefix)
548
549
550 wrong := sha256.Sum256([]byte("simply wrong"))
551 if rsp, err := t.client().GetProofByHash(ctx, wrong[:], sthN1.TreeSize); err == nil {
552 return fmt.Errorf("got GetProofByHash(wrong, size=%d)=(%v,nil); want (nil,_)", sthN1.TreeSize, rsp)
553 } else if rspErr, ok := err.(client.RspError); ok {
554 if rspErr.StatusCode != http.StatusNotFound {
555 return fmt.Errorf("got GetProofByHash(wrong)=_, %d; want (nil, 404)", rspErr.StatusCode)
556 }
557 } else {
558 return fmt.Errorf("got GetProofByHash(wrong)=%+v (%T); want (client.RspError)", err, err)
559 }
560 t.stats.expect(ctfe.GetProofByHashName, 404)
561 fmt.Printf("%s: GetProofByHash(wrong,%d)=(nil,_)\n", t.prefix, sthN1.TreeSize)
562
563
564 preIssuerChain, preTBS, err := makePreIssuerPrecertChain(chain[1], issuer, signer)
565 if err != nil {
566 return fmt.Errorf("failed to build pre-issued pre-certificate: %v", err)
567 }
568 preIssuerCertSCT, err := pool.Next().AddPreChain(ctx, preIssuerChain)
569 stats.expect(ctfe.AddPreChainName, 200)
570 if err != nil {
571 return fmt.Errorf("got AddPreChain()=(nil,%v); want (_,nil)", err)
572 }
573 fmt.Printf("%s: Uploaded pre-issued precert to %v log, got SCT(time=%q)\n", t.prefix, precertSCT.SCTVersion, timeFromMS(precertSCT.Timestamp))
574 treeSize++
575 sthN2, err := t.awaitTreeSize(ctx, uint64(treeSize), true, mmd)
576 if err != nil {
577 return fmt.Errorf("AwaitTreeSize(%d)=(nil,%v); want (_,nil)", treeSize, err)
578 }
579 fmt.Printf("%s: Got STH(time=%q, size=%d): roothash=%x\n", t.prefix, timeFromMS(sthN2.Timestamp), sthN2.TreeSize, sthN2.SHA256RootHash)
580
581
582 preIssuerCertIndex := int64(count + 2)
583 if err := t.checkPreCertEntry(ctx, preIssuerCertIndex, preTBS); err != nil {
584 return fmt.Errorf("failed to check pre-issued pre-cert entry: %v", err)
585 }
586
587
588 if err := t.checkInclusionOfPreCert(ctx, preTBS, issuer, preIssuerCertSCT, sthN2); err != nil {
589 return fmt.Errorf("failed to check inclusion of pre-cert entry: %v", err)
590 }
591
592
593 if err := t.checkStats(); err != nil {
594 return fmt.Errorf("stats check failed: %v", err)
595 }
596 return nil
597 }
598
599
600
601
602
603
604 func RunCTLifecycleForLog(cfg *configpb.LogConfig, servers, metricsServers, adminServer string, testDir string, mmd time.Duration, stats *logStats) error {
605
606 caChain, err := GetChain(testDir, "int-ca.cert")
607 if err != nil {
608 return err
609 }
610 leafChain, err := GetChain(testDir, "leaf01.chain")
611 if err != nil {
612 return err
613 }
614 signer, err := MakeSigner(testDir)
615 if err != nil {
616 return err
617 }
618 generator, err := NewSyntheticChainGenerator(leafChain, signer, time.Time{})
619 if err != nil {
620 return err
621 }
622
623 ctx := context.Background()
624 pool, err := NewRandomPool(servers, cfg.PublicKey, cfg.Prefix)
625 if err != nil {
626 return fmt.Errorf("failed to create pool: %v", err)
627 }
628 t := testInfo{
629 prefix: cfg.Prefix,
630 cfg: cfg,
631 metricsServers: metricsServers,
632 adminServer: adminServer,
633 stats: stats,
634 pool: pool,
635 hasher: rfc6962.DefaultHasher,
636 }
637
638 if err := t.checkStats(); err != nil {
639 return fmt.Errorf("stats check failed: %v", err)
640 }
641
642
643 roots, err := t.client().GetAcceptedRoots(ctx)
644 t.stats.expect(ctfe.GetRootsName, 200)
645 if err != nil {
646 return fmt.Errorf("got GetAcceptedRoots()=(nil,%v); want (_,nil)", err)
647 }
648 if got := len(roots); got != 1 {
649 return fmt.Errorf("len(GetAcceptedRoots())=%d; want 1", got)
650 }
651
652
653 sth0, err := t.client().GetSTH(ctx)
654 t.stats.expect(ctfe.GetSTHName, 200)
655 if err != nil {
656 return fmt.Errorf("got GetSTH()=(nil,%v); want (_,nil)", err)
657 }
658 if sth0.Version != 0 {
659 return fmt.Errorf("sth.Version=%v; want V1(0)", sth0.Version)
660 }
661 if sth0.TreeSize != 0 {
662 return fmt.Errorf("sth.TreeSize=%d; want 0", sth0.TreeSize)
663 }
664 fmt.Printf("%s: Got STH(time=%q, size=%d): roothash=%x\n", t.prefix, timeFromMS(sth0.Timestamp), sth0.TreeSize, sth0.SHA256RootHash)
665
666
667
668 atLeast := 2000
669 count := atLeast + rand.Intn(3000-atLeast)
670 fmt.Printf("%s: Starting upload of %d certificates ....\n", t.prefix, count)
671 for i := 1; i <= count; i++ {
672 chain, err := generator.CertChain()
673 if err != nil {
674 return err
675 }
676 _, err = t.client().AddChain(ctx, chain)
677 t.stats.expect(ctfe.AddChainName, 200)
678 if err != nil {
679 return fmt.Errorf("got AddChain(int-ca.cert)=(nil,%v); want (_,nil)", err)
680 }
681 }
682 fmt.Printf("%s: Upload of %d certificates complete\n", t.prefix, count)
683
684
685 ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
686 defer cancel()
687 if err := setTreeState(ctx, t.adminServer, t.cfg.LogId, trillian.TreeState_DRAINING); err != nil {
688 return fmt.Errorf("setTreeState(DRAINING)=%v, want: nil", err)
689 }
690
691
692
693 sth1, err := t.client().GetSTH(ctx)
694 t.stats.expect(ctfe.GetSTHName, 200)
695 if err != nil {
696 return fmt.Errorf("got GetSTH(DRAINING)=(nil,%v); want (_,nil)", err)
697 }
698 if sth1.Version != 0 {
699 return fmt.Errorf("sth.Version=%v; want V1(0)", sth1.Version)
700 }
701 fmt.Printf("%s: Got STH(time=%q, size=%d): roothash=%x\n", t.prefix, timeFromMS(sth1.Timestamp), sth1.TreeSize, sth1.SHA256RootHash)
702
703
704 sth2, err := t.awaitTreeSize(context.Background(), uint64(count), true, mmd)
705 if err != nil {
706 return err
707 }
708 fmt.Printf("%s: Got STH(time=%q, size=%d): roothash=%x\n", t.prefix, timeFromMS(sth2.Timestamp), sth2.TreeSize, sth2.SHA256RootHash)
709
710
711 proof, err := t.client().GetSTHConsistency(ctx, sth1.TreeSize, sth2.TreeSize)
712 t.stats.expect(ctfe.GetSTHConsistencyName, 200)
713 if err != nil {
714 return err
715 }
716 if err := t.checkCTConsistencyProof(sth1, sth2, proof); err != nil {
717 return err
718 }
719 fmt.Printf("%s: VerifiedConsistency(time=%q, size1=%d, size2=%d): final roothash=%x\n", t.prefix, timeFromMS(sth2.Timestamp), sth1.TreeSize, sth2.TreeSize, sth2.SHA256RootHash)
720
721
722 _, err = t.client().AddChain(ctx, caChain)
723 t.stats.expect(ctfe.AddChainName, 403)
724 if err == nil || !strings.Contains(err.Error(), "403") {
725 return fmt.Errorf("got AddChain(DRAINING: int-ca.cert)=(nil,%v); want (_,err inc. 403)", err)
726 }
727
728
729
730 ctx, cancel = context.WithTimeout(context.Background(), time.Second*5)
731 defer cancel()
732 if err := setTreeState(ctx, t.adminServer, t.cfg.LogId, trillian.TreeState_ACTIVE); err != nil {
733 return fmt.Errorf("setTreeState(ACTIVE)=%v, want: nil", err)
734 }
735 _, err = t.client().AddChain(ctx, caChain)
736 t.stats.expect(ctfe.AddChainName, 200)
737 if err != nil {
738 return fmt.Errorf("got AddChain(ACTIVE: int-ca.cert)=(nil,%v); want (_,nil)", err)
739 }
740
741
742 sthCaCert, err := t.awaitTreeSize(context.Background(), uint64(count+1), true, mmd)
743 if err != nil {
744 return err
745 }
746 fmt.Printf("%s: Got STH(time=%q, size=%d): roothash=%x\n", t.prefix, timeFromMS(sthCaCert.Timestamp), sthCaCert.TreeSize, sthCaCert.SHA256RootHash)
747
748
749 ctx, cancel = context.WithTimeout(context.Background(), time.Second*5)
750 defer cancel()
751 if err := setTreeState(ctx, t.adminServer, t.cfg.LogId, trillian.TreeState_FROZEN); err != nil {
752 return fmt.Errorf("setTreeState(FROZEN)=%v, want: nil", err)
753 }
754
755
756
757 _, err = t.client().AddChain(ctx, caChain)
758 t.stats.expect(ctfe.AddChainName, 403)
759 if err == nil || !strings.Contains(err.Error(), "403") {
760 return fmt.Errorf("got AddChain(FROZEN: int-ca.cert)=(nil,%v); want (_,err inc. 403)", err)
761 }
762
763
764
765 sth3, err := t.client().GetSTH(ctx)
766 t.stats.expect(ctfe.GetSTHName, 200)
767 if err != nil {
768 return fmt.Errorf("got GetSTH(FROZEN)=(nil,%v); want (_,nil)", err)
769 }
770
771
772
773
774 if sth2.TreeSize+1 != sth3.TreeSize {
775 return fmt.Errorf("sth3 got TreeSize=%d, want: %d", sth3.TreeSize, sth2.TreeSize)
776 }
777
778
779 if err := t.checkStats(); err != nil {
780 return fmt.Errorf("stats check failed: %v", err)
781 }
782
783 return nil
784 }
785
786
787 func timeFromMS(ts uint64) time.Time {
788 secs := int64(ts / 1000)
789 msecs := int64(ts % 1000)
790 return time.Unix(secs, msecs*1000000)
791 }
792
793
794 func GetChain(dir, path string) ([]ct.ASN1Cert, error) {
795 certdata, err := os.ReadFile(filepath.Join(dir, path))
796 if err != nil {
797 return nil, fmt.Errorf("failed to load certificate: %v", err)
798 }
799 return CertsFromPEM(certdata), nil
800 }
801
802
803 func CertsFromPEM(data []byte) []ct.ASN1Cert {
804 var chain []ct.ASN1Cert
805 for {
806 var block *pem.Block
807 block, data = pem.Decode(data)
808 if block == nil {
809 break
810 }
811 if block.Type == "CERTIFICATE" {
812 chain = append(chain, ct.ASN1Cert{Data: block.Bytes})
813 }
814 }
815 return chain
816 }
817
818
819 func (t *testInfo) checkCTConsistencyProof(sth1, sth2 *ct.SignedTreeHead, pf [][]byte) error {
820 return proof.VerifyConsistency(t.hasher, sth1.TreeSize, sth2.TreeSize, pf, sth1.SHA256RootHash[:], sth2.SHA256RootHash[:])
821 }
822
823
824
825 func buildNewPrecertData(cert, issuer *x509.Certificate, signer crypto.Signer) ([]byte, error) {
826
827 randData := make([]byte, 128)
828 if _, err := cryptorand.Read(randData); err != nil {
829 return nil, fmt.Errorf("failed to read random data: %v", err)
830 }
831 cert.SubjectKeyId = randData
832
833
834 cert.ExtraExtensions = append(cert.ExtraExtensions, pkix.Extension{
835 Id: x509.OIDExtensionCTPoison,
836 Critical: true,
837 Value: []byte{0x05, 0x00},
838 })
839
840
841 cert.AuthorityKeyId = issuer.SubjectKeyId
842 data, err := x509.CreateCertificate(cryptorand.Reader, cert, issuer, cert.PublicKey, signer)
843 if err != nil {
844 return nil, fmt.Errorf("failed to CreateCertificate: %v", err)
845 }
846 return data, nil
847 }
848
849
850 func MakeSigner(testDir string) (crypto.Signer, error) {
851 fileName := filepath.Join(testDir, "int-ca.privkey.pem")
852 keyPEM, err := os.ReadFile(fileName)
853 if err != nil {
854 return nil, fmt.Errorf("error reading file %q: %w", fileName, err)
855 }
856
857 block, _ := pem.Decode(keyPEM)
858 decPEM, err := x509.DecryptPEMBlock(block, []byte("babelfish"))
859 if err != nil {
860 return nil, fmt.Errorf("failed to decrypt file %q: %w", fileName, err)
861 }
862
863 key, err := x509.ParseECPrivateKey(decPEM)
864 if err != nil {
865 return nil, fmt.Errorf("failed to load private key for re-signing: %v", err)
866 }
867 return key, nil
868 }
869
870
871 type logStats struct {
872 logID int64
873 reqs map[string]int
874 rsps map[string]map[string]int
875
876 }
877
878 func newLogStats(logID int64) *logStats {
879 stats := logStats{
880 logID: logID,
881 reqs: make(map[string]int),
882 rsps: make(map[string]map[string]int),
883 }
884 for _, ep := range ctfe.Entrypoints {
885 stats.rsps[string(ep)] = make(map[string]int)
886 }
887 return &stats
888 }
889
890 func (ls *logStats) expect(ep ctfe.EntrypointName, rc int) {
891 if ls == nil {
892 return
893 }
894 ls.reqs[string(ep)]++
895 ls.rsps[string(ep)][strconv.Itoa(rc)]++
896 }
897
898 func (ls *logStats) fromServer(ctx context.Context, servers string) (*logStats, error) {
899 reqsRE := regexp.MustCompile(reqStatsRE)
900 rspsRE := regexp.MustCompile(rspStatsRE)
901
902 got := newLogStats(int64(ls.logID))
903 for _, s := range strings.Split(servers, ",") {
904 httpReq, err := http.NewRequest(http.MethodGet, "http://"+s+"/metrics", nil)
905 if err != nil {
906 return nil, fmt.Errorf("failed to build GET request: %v", err)
907 }
908 c := new(http.Client)
909
910 httpRsp, err := ctxhttp.Do(ctx, c, httpReq)
911 if err != nil {
912 return nil, fmt.Errorf("getting stats failed: %v", err)
913 }
914 defer httpRsp.Body.Close()
915 if httpRsp.StatusCode != http.StatusOK {
916 return nil, fmt.Errorf("got HTTP Status %q", httpRsp.Status)
917 }
918
919 scanner := bufio.NewScanner(httpRsp.Body)
920 for scanner.Scan() {
921 line := scanner.Text()
922 m := reqsRE.FindStringSubmatch(line)
923 if m != nil {
924 if m[2] == strconv.FormatInt(ls.logID, 10) {
925 if val, err := strconv.ParseFloat(m[3], 64); err == nil {
926 ep := m[1]
927 got.reqs[ep] += int(val)
928 }
929 }
930 continue
931 }
932 m = rspsRE.FindStringSubmatch(line)
933 if m != nil {
934 if m[2] == strconv.FormatInt(ls.logID, 10) {
935 if val, err := strconv.ParseFloat(m[4], 64); err == nil {
936 ep := m[1]
937 rc := m[3]
938 got.rsps[ep][rc] += int(val)
939 }
940 }
941 continue
942 }
943 }
944 }
945
946 return got, nil
947 }
948
949 func (ls *logStats) check(cfg *configpb.LogConfig, servers string) error {
950 if ls == nil {
951 return nil
952 }
953 ctx := context.Background()
954 got, err := ls.fromServer(ctx, servers)
955 if err != nil {
956 return err
957 }
958
959 if !reflect.DeepEqual(got.reqs, ls.reqs) {
960 return fmt.Errorf("got reqs %+v; want %+v", got.reqs, ls.reqs)
961 }
962 if !reflect.DeepEqual(got.rsps, ls.rsps) {
963 return fmt.Errorf("got rsps %+v; want %+v", got.rsps, ls.rsps)
964 }
965 return nil
966 }
967
968 func setTreeState(ctx context.Context, adminServer string, logID int64, state trillian.TreeState) error {
969 treeStateMask := &fieldmaskpb.FieldMask{
970 Paths: []string{"tree_state"},
971 }
972
973 req := &trillian.UpdateTreeRequest{
974 Tree: &trillian.Tree{
975 TreeId: logID,
976 TreeState: state,
977 },
978 UpdateMask: treeStateMask,
979 }
980
981 conn, err := grpc.Dial(adminServer, grpc.WithTransportCredentials(insecure.NewCredentials()))
982 if err != nil {
983 return err
984 }
985 defer conn.Close()
986
987 adminClient := trillian.NewTrillianAdminClient(conn)
988 _, err = adminClient.UpdateTree(ctx, req)
989 if err != nil {
990 return err
991 }
992 return nil
993 }
994
995
996
997 func NotAfterForLog(c *configpb.LogConfig) (time.Time, error) {
998 if c.NotAfterStart == nil && c.NotAfterLimit == nil {
999 return time.Now().Add(24 * time.Hour), nil
1000 }
1001
1002 if c.NotAfterStart != nil {
1003 if err := c.NotAfterStart.CheckValid(); err != nil {
1004 return time.Time{}, fmt.Errorf("failed to parse NotAfterStart: %v", err)
1005 }
1006 start := c.NotAfterStart.AsTime()
1007 if c.NotAfterLimit == nil {
1008 return start.Add(24 * time.Hour), nil
1009 }
1010
1011 if err := c.NotAfterLimit.CheckValid(); err != nil {
1012 return time.Time{}, fmt.Errorf("failed to parse NotAfterLimit: %v", err)
1013 }
1014 limit := c.NotAfterLimit.AsTime()
1015 midDelta := limit.Sub(start) / 2
1016 return start.Add(midDelta), nil
1017 }
1018
1019 if err := c.NotAfterLimit.CheckValid(); err != nil {
1020 return time.Time{}, fmt.Errorf("failed to parse NotAfterLimit: %v", err)
1021 }
1022 limit := c.NotAfterLimit.AsTime()
1023 return limit.Add(-1 * time.Hour), nil
1024 }
1025
View as plain text