...

Source file src/github.com/google/certificate-transparency-go/trillian/integration/ct_integration.go

Documentation: github.com/google/certificate-transparency-go/trillian/integration

     1  // Copyright 2016 Google LLC. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package integration holds test-only code for running tests on
    16  // an integrated system of the CT personality and a Trillian log.
    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  // DefaultTransport is a http Transport more suited for use in the hammer
    65  // context.
    66  // In particular it increases the number of reusable connections to the same
    67  // host. This helps to prevent starvation of ports through TIME_WAIT when
    68  // using the hammer with a high number of parallel chain submissions.
    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  // ClientPool describes an entity which produces LogClient instances.
    83  type ClientPool interface {
    84  	// Next returns the next LogClient instance to be used.
    85  	Next() *client.LogClient
    86  }
    87  
    88  // RandomPool holds a collection of CT LogClient instances.
    89  type RandomPool []*client.LogClient
    90  
    91  var _ ClientPool = &RandomPool{}
    92  
    93  // Next picks a random client from the pool.
    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  // NewRandomPool creates a pool which returns a random client from list of servers.
   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  // testInfo holds per-test information.
   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  // awaitTreeSize loops until the an STH is retrieved that is the specified size (or larger, if exact is false).
   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  // checkInclusionOf checks that a given certificate chain and associated SCT are included
   165  // under a signed tree head.
   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  // checkInclusionOfPreCert checks a pre-cert is included at given index.
   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  // checkPreCertEntry retrieves a pre-cert from a known index and checks it.
   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  // RunCTIntegrationForLog tests against the log with configuration cfg, with a set
   254  // of comma-separated server addresses given by servers, assuming that testdir holds
   255  // a variety of test data files.
   256  // nolint: gocyclo
   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  	// Stage 0: get accepted roots, which should just be the fake CA.
   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  	// Stage 1: get the STH, which should be empty.
   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  	// Stage 2: add a single cert (the intermediate CA), get an SCT.
   301  	var scts [21]*ct.SignedCertificateTimestamp // 0=int-ca, 1-20=leaves
   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  	// Display the SCT
   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  	// Keep getting the STH until tree size becomes 1 and check the cert is included.
   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  	// Stage 2.5: add the same cert, expect an SCT with the same timestamp as before.
   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  	// Stage 3: add a second cert, wait for tree size = 2
   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  	// Stage 4: get a consistency proof from size 1-> size 2.
   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  	//                 sth2
   367  	//                 / \
   368  	//  sth1   =>      a b
   369  	//    |            | |
   370  	//   d0           d0 d1
   371  	// So consistency proof is [b] and we should have:
   372  	//   sth2 == SHA256(0x01 | sth1 | b)
   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  	// Stage 4.5: get a consistency proof from size 0-> size 2, which should be empty.
   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  	// Stage 5: add certificates 2, 3, 4, 5,...N, for some random N in [4,20]
   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  	// Stage 6: keep getting the STH until tree size becomes 1 + N (allows for int-ca.cert).
   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  	// Stage 7: get a consistency proof from 2->(1+N).
   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  	// Stage 8: get entries [1, N] (start at 1 to skip int-ca.cert)
   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  		// The certificates might not be sequenced in the order they were uploaded, so
   460  		// compare the set of hashes.
   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  	// Stage 9: get an audit proof for each certificate we have an SCT for.
   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  	// Stage 10: attempt to upload a corrupt certificate.
   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  	// Stage 11: attempt to upload a certificate without chain.
   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  	// Stage 12: build and add a pre-certificate.
   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  	// Stage 13: retrieve and check pre-cert.
   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  	// Stage 14: get an inclusion proof for the precert.
   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  	// Stage 15: invalid consistency proof
   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  	// Stage 16: invalid inclusion proof; expect a client.RspError{404}.
   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  	// Stage 17: build and add a pre-certificate signed by a pre-issuer.
   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  	// Stage 18: retrieve and check pre-issued pre-cert.
   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  	// Stage 19: get an inclusion proof for the pre-issued precert.
   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  	// Final stats check.
   593  	if err := t.checkStats(); err != nil {
   594  		return fmt.Errorf("stats check failed: %v", err)
   595  	}
   596  	return nil
   597  }
   598  
   599  // RunCTLifecycleForLog does a simple log lifecycle test. The log
   600  // is assumed to be newly created when this test runs. A random number of
   601  // entries are then submitted to build up a queue. The log is set to
   602  // DRAINING state and the test checks that all the entries are integrated
   603  // into the tree and we can verify a consistency proof to the latest entry.
   604  func RunCTLifecycleForLog(cfg *configpb.LogConfig, servers, metricsServers, adminServer string, testDir string, mmd time.Duration, stats *logStats) error {
   605  	// Retrieve the test data.
   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  	// Stage 0: get accepted roots, which should just be the fake CA.
   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  	// Stage 1: get the STH, which should be empty.
   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  	// Stage 2: add certificates 2, 3, 4, 5,...N, for some random N with
   667  	// at least 2000 so the queue builds up a bit.
   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  	// Stage 3: Set the log to DRAINING using the admin server.
   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  	// Stage 4a: Get an updated STH. We'll use this point for a consistency
   692  	// proof later.
   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  	// Stage 4b: Wait for the queue to drain and everything to be integrated.
   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  	// Stage 5. Get a consistency proof from sth1 to sth2 and verify it.
   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  	// Stage 6. Try to submit a chain and it should be rejected with 403.
   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  	// Stage 7a - Set the log state back to ACTIVE and submit again. This should
   729  	// be accepted.
   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  	// Stage 7b: Wait for that new certificate to be integrated.
   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  	// Stage 8 - Set the log to FROZEN using the admin server.
   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  	// Stage 9 - Try to upload the pre-cert again and it should be rejected
   756  	// with FORBIDDEN status.
   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  	// Stage 10 - Obtain latest STH and check it hasn't increased in size since
   764  	// the last submission.
   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  	// We know that anything queued was integrated so is should be impossible
   772  	// that the tree has grown. There is one extra certificate from the test
   773  	// that we could submit in Stage 7a.
   774  	if sth2.TreeSize+1 != sth3.TreeSize {
   775  		return fmt.Errorf("sth3 got TreeSize=%d, want: %d", sth3.TreeSize, sth2.TreeSize)
   776  	}
   777  
   778  	// Final stats check.
   779  	if err := t.checkStats(); err != nil {
   780  		return fmt.Errorf("stats check failed: %v", err)
   781  	}
   782  
   783  	return nil
   784  }
   785  
   786  // timeFromMS converts a timestamp in milliseconds (as used in CT) to a time.Time.
   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  // GetChain retrieves a certificate from a file of the given name and directory.
   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  // CertsFromPEM loads X.509 certificates from the provided PEM-encoded data.
   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  // checkCTConsistencyProof checks the given consistency proof.
   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  // buildNewPrecertData creates a new pre-certificate based on the given template cert (which is
   824  // modified)
   825  func buildNewPrecertData(cert, issuer *x509.Certificate, signer crypto.Signer) ([]byte, error) {
   826  	// Randomize the subject key ID.
   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  	// Add the CT poison extension.
   834  	cert.ExtraExtensions = append(cert.ExtraExtensions, pkix.Extension{
   835  		Id:       x509.OIDExtensionCTPoison,
   836  		Critical: true,
   837  		Value:    []byte{0x05, 0x00}, // ASN.1 NULL
   838  	})
   839  
   840  	// Create a fresh certificate, signed by the issuer.
   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  // MakeSigner creates a signer using the private key in the test directory.
   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  // Track HTTP requests/responses so we can check the stats exported by the log.
   871  type logStats struct {
   872  	logID int64
   873  	reqs  map[string]int            // entrypoint =>count
   874  	rsps  map[string]map[string]int // entrypoint => status => count
   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  	// Now compare accumulated actual stats with what we expect to see.
   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  // NotAfterForLog returns a NotAfter time to be used for certs submitted
   996  // to the given log instance, allowing for any temporal shard configuration.
   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