...

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

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

     1  // Copyright 2017 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
    16  
    17  import (
    18  	"context"
    19  	"crypto"
    20  	"encoding/json"
    21  	"fmt"
    22  	"io"
    23  	"net"
    24  	"net/http"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/google/certificate-transparency-go/client"
    29  	"github.com/google/certificate-transparency-go/jsonclient"
    30  	"github.com/google/certificate-transparency-go/tls"
    31  	"github.com/google/certificate-transparency-go/trillian/ctfe/configpb"
    32  	"github.com/google/certificate-transparency-go/x509"
    33  	"google.golang.org/protobuf/types/known/timestamppb"
    34  	"k8s.io/klog/v2"
    35  
    36  	ct "github.com/google/certificate-transparency-go"
    37  )
    38  
    39  func TestHammer_NotAfter(t *testing.T) {
    40  	keys := loadTestKeys(t)
    41  
    42  	s, lc := newFakeCTServer(t)
    43  	defer s.close()
    44  
    45  	now := time.Now()
    46  	notAfterStart := now.Add(-48 * time.Hour)
    47  	notAfterOverride := now.Add(23 * time.Hour)
    48  	notAfterLimit := now.Add(48 * time.Hour)
    49  
    50  	ctx := context.Background()
    51  	addChain := func(hs *hammerState) error { return hs.addChain(ctx) }
    52  	addPreChain := func(hs *hammerState) error { return hs.addPreChain(ctx) }
    53  
    54  	tests := []struct {
    55  		desc                                           string
    56  		fn                                             func(hs *hammerState) error
    57  		notAfterOverride, notAfterStart, notAfterLimit time.Time
    58  		// wantNotAfter is only checked if not zeroed
    59  		wantNotAfter time.Time
    60  	}{
    61  		{
    62  			desc: "nonTemporalAddChain",
    63  			fn:   addChain,
    64  		},
    65  		{
    66  			desc: "nonTemporalAddPreChain",
    67  			fn:   addPreChain,
    68  		},
    69  		{
    70  			desc:             "nonTemporalFixedAddChain",
    71  			fn:               addChain,
    72  			notAfterOverride: notAfterOverride,
    73  			wantNotAfter:     notAfterOverride,
    74  		},
    75  		{
    76  			desc:             "nonTemporalFixedAddPreChain",
    77  			fn:               addPreChain,
    78  			notAfterOverride: notAfterOverride,
    79  			wantNotAfter:     notAfterOverride,
    80  		},
    81  		{
    82  			desc:          "temporalAddChain",
    83  			fn:            addChain,
    84  			notAfterStart: notAfterStart,
    85  			notAfterLimit: notAfterLimit,
    86  		},
    87  		{
    88  			desc:          "temporalAddPreChain",
    89  			fn:            addPreChain,
    90  			notAfterStart: notAfterStart,
    91  			notAfterLimit: notAfterLimit,
    92  		},
    93  		{
    94  			desc:             "temporalFixedAddChain",
    95  			fn:               addChain,
    96  			notAfterOverride: notAfterOverride,
    97  			notAfterStart:    notAfterStart,
    98  			notAfterLimit:    notAfterLimit,
    99  			wantNotAfter:     notAfterOverride,
   100  		},
   101  		{
   102  			desc:             "temporalFixedAddPreChain",
   103  			fn:               addPreChain,
   104  			notAfterOverride: notAfterOverride,
   105  			notAfterStart:    notAfterStart,
   106  			notAfterLimit:    notAfterLimit,
   107  			wantNotAfter:     notAfterOverride,
   108  		},
   109  	}
   110  
   111  	for _, test := range tests {
   112  		t.Run(test.desc, func(t *testing.T) {
   113  			s.reset()
   114  
   115  			var startPB, limitPB *timestamppb.Timestamp
   116  			if ts := test.notAfterStart; ts.UnixNano() > 0 {
   117  				startPB = timestamppb.New(ts)
   118  			}
   119  			if ts := test.notAfterLimit; ts.UnixNano() > 0 {
   120  				limitPB = timestamppb.New(ts)
   121  			}
   122  			generator, err := NewSyntheticChainGenerator(keys.leafChain, keys.signer, test.notAfterOverride)
   123  			if err != nil {
   124  				t.Fatalf("Failed to build chain generator: %v", err)
   125  			}
   126  			hs, err := newHammerState(&HammerConfig{
   127  				ChainGenerator: generator,
   128  				ClientPool:     RandomPool{lc},
   129  				LogCfg: &configpb.LogConfig{
   130  					NotAfterStart: startPB,
   131  					NotAfterLimit: limitPB,
   132  				},
   133  			})
   134  			if err != nil {
   135  				t.Fatalf("newHammerState() returned err = %v", err)
   136  			}
   137  
   138  			if err := test.fn(hs); err != nil {
   139  				t.Fatalf("addChain() returned err = %v", err)
   140  			}
   141  			if got := len(s.addedCerts); got != 1 {
   142  				t.Fatalf("unexpected number of certs (%d) added to server", got)
   143  			}
   144  			got := s.addedCerts[0].NotAfter
   145  			temporal := startPB != nil || limitPB != nil
   146  			fixed := test.wantNotAfter.UnixNano() > 0
   147  			if fixed {
   148  				// Expect a fixed NotAfter in the generated cert.
   149  				delta := got.Sub(test.wantNotAfter)
   150  				if delta < 0 {
   151  					delta = -delta
   152  				}
   153  				if delta > time.Second {
   154  					t.Errorf("cert has NotAfter = %v, want = %v", got, test.wantNotAfter)
   155  				}
   156  			} else {
   157  				// For a temporal log, expect the NotAfter in the generated cert to be in range.
   158  				if temporal && (got.Before(test.notAfterStart) || got.After(test.notAfterLimit)) {
   159  					t.Errorf("cert has NotAfter = %v, want %v <= NotAfter <= %v", got, test.notAfterStart, test.notAfterLimit)
   160  				}
   161  			}
   162  		})
   163  	}
   164  }
   165  
   166  // fakeCTServer is a fake HTTP server that mimics a CT frontend.
   167  // It supports add-chain and add-pre-chain methods and saves the first certificate of the chain in
   168  // the addCerts field.
   169  // Callers should call reset() before usage to reset internal state and defer-call close() to ensure
   170  // the server is stopped and resources are freed.
   171  type fakeCTServer struct {
   172  	lis    net.Listener
   173  	server *http.Server
   174  
   175  	addedCerts []*x509.Certificate
   176  	sthNow     ct.SignedTreeHead
   177  
   178  	getConsistencyCalled bool
   179  }
   180  
   181  func (s *fakeCTServer) addChain(w http.ResponseWriter, req *http.Request) {
   182  	body, err := io.ReadAll(req.Body)
   183  	if err != nil {
   184  		writeErr(w, http.StatusInternalServerError, err)
   185  		return
   186  	}
   187  
   188  	addReq := &ct.AddChainRequest{}
   189  	if err := json.Unmarshal(body, addReq); err != nil {
   190  		writeErr(w, http.StatusBadRequest, err)
   191  		return
   192  	}
   193  
   194  	cert, err := x509.ParseCertificate(addReq.Chain[0])
   195  	if err != nil {
   196  		writeErr(w, http.StatusBadRequest, err)
   197  		return
   198  	}
   199  	s.addedCerts = append(s.addedCerts, cert)
   200  
   201  	dsBytes, err := tls.Marshal(tls.DigitallySigned{})
   202  	if err != nil {
   203  		writeErr(w, http.StatusInternalServerError, err)
   204  		return
   205  	}
   206  	resp := &ct.AddChainResponse{
   207  		SCTVersion: ct.V1,
   208  		Signature:  dsBytes,
   209  	}
   210  	respBytes, err := json.Marshal(resp)
   211  	if err != nil {
   212  		writeErr(w, http.StatusInternalServerError, err)
   213  		return
   214  	}
   215  
   216  	w.WriteHeader(http.StatusOK)
   217  	if _, err := w.Write(respBytes); err != nil {
   218  		klog.Errorf("Write(): %v", err)
   219  	}
   220  }
   221  
   222  func (s *fakeCTServer) close() {
   223  	if s.server != nil {
   224  		s.server.Close()
   225  	}
   226  	if s.lis != nil {
   227  		s.lis.Close()
   228  	}
   229  }
   230  
   231  func (s *fakeCTServer) reset() {
   232  	s.addedCerts = nil
   233  }
   234  
   235  func (s *fakeCTServer) serve() {
   236  	if err := s.server.Serve(s.lis); err != http.ErrServerClosed {
   237  		panic(err)
   238  	}
   239  }
   240  
   241  func (s *fakeCTServer) getSTH(w http.ResponseWriter, req *http.Request) {
   242  	resp := &ct.GetSTHResponse{
   243  		TreeSize:       s.sthNow.TreeSize,
   244  		Timestamp:      s.sthNow.Timestamp,
   245  		SHA256RootHash: []byte(s.sthNow.SHA256RootHash[:]),
   246  	}
   247  	var err error
   248  	resp.TreeHeadSignature, err = tls.Marshal(s.sthNow.TreeHeadSignature)
   249  	if err != nil {
   250  		writeErr(w, http.StatusInternalServerError, err)
   251  		return
   252  	}
   253  
   254  	respBytes, err := json.Marshal(resp)
   255  	if err != nil {
   256  		writeErr(w, http.StatusInternalServerError, err)
   257  		return
   258  	}
   259  
   260  	w.WriteHeader(http.StatusOK)
   261  	if _, err := w.Write(respBytes); err != nil {
   262  		klog.Errorf("Write(): %v", err)
   263  	}
   264  }
   265  
   266  func (s *fakeCTServer) getConsistency(w http.ResponseWriter, req *http.Request) {
   267  	cp := &ct.GetSTHConsistencyResponse{
   268  		Consistency: [][]byte{[]byte("bogus")},
   269  	}
   270  	respBytes, err := json.Marshal(cp)
   271  	if err != nil {
   272  		writeErr(w, http.StatusInternalServerError, err)
   273  		return
   274  	}
   275  
   276  	w.WriteHeader(http.StatusOK)
   277  	if _, err := w.Write(respBytes); err != nil {
   278  		klog.Errorf("Write(): %v", err)
   279  	}
   280  
   281  	s.getConsistencyCalled = true
   282  }
   283  
   284  func writeErr(w http.ResponseWriter, status int, err error) {
   285  	w.WriteHeader(status)
   286  	if _, err := io.WriteString(w, err.Error()); err != nil {
   287  		klog.Errorf("WriteString(): %v", err)
   288  	}
   289  }
   290  
   291  // newFakeCTServer creates and starts a fakeCTServer.
   292  // It returns the started server and a client to the same server.
   293  func newFakeCTServer(t *testing.T) (*fakeCTServer, *client.LogClient) {
   294  	s := &fakeCTServer{}
   295  
   296  	var err error
   297  	s.lis, err = net.Listen("tcp", "")
   298  	if err != nil {
   299  		s.close()
   300  		t.Fatalf("net.Listen() returned err = %v", err)
   301  	}
   302  
   303  	mux := http.NewServeMux()
   304  	mux.HandleFunc("/ct/v1/add-chain", s.addChain)
   305  	mux.HandleFunc("/ct/v1/add-pre-chain", s.addChain)
   306  	mux.HandleFunc("/ct/v1/get-sth", s.getSTH)
   307  	mux.HandleFunc("/ct/v1/get-sth-consistency", s.getConsistency)
   308  
   309  	s.server = &http.Server{Handler: mux}
   310  	go s.serve()
   311  
   312  	lc, err := client.New(fmt.Sprintf("http://%s", s.lis.Addr()), nil, jsonclient.Options{})
   313  	if err != nil {
   314  		t.Fatalf("client.New() returned err = %v", err)
   315  	}
   316  
   317  	return s, lc
   318  }
   319  
   320  // testKeys contains all keys and associated signer required for hammer tests.
   321  type testKeys struct {
   322  	caChain, leafChain []ct.ASN1Cert
   323  	caCert, leafCert   *x509.Certificate
   324  	signer             crypto.Signer
   325  }
   326  
   327  // loadTestKeys loads the test keys from the testdata/ directory.
   328  func loadTestKeys(t *testing.T) *testKeys {
   329  	t.Helper()
   330  
   331  	const testdataPath = "../testdata/"
   332  
   333  	caChain, err := GetChain(testdataPath, "int-ca.cert")
   334  	if err != nil {
   335  		t.Fatalf("GetChain() returned err = %v", err)
   336  	}
   337  	leafChain, err := GetChain(testdataPath, "leaf01.chain")
   338  	if err != nil {
   339  		t.Fatalf("GetChain() returned err = %v", err)
   340  	}
   341  	caCert, err := x509.ParseCertificate(caChain[0].Data)
   342  	if err != nil {
   343  		t.Fatalf("x509.ParseCertificate() returned err = %v", err)
   344  	}
   345  	leafCert, err := x509.ParseCertificate(leafChain[0].Data)
   346  	if err != nil {
   347  		t.Fatalf("x509.ParseCertificate() returned err = %v", err)
   348  	}
   349  	signer, err := MakeSigner(testdataPath)
   350  	if err != nil {
   351  		t.Fatalf("MakeSigner() returned err = %v", err)
   352  	}
   353  
   354  	return &testKeys{
   355  		caChain:   caChain,
   356  		leafChain: leafChain,
   357  		caCert:    caCert,
   358  		leafCert:  leafCert,
   359  		signer:    signer,
   360  	}
   361  }
   362  
   363  func TestChooseCertToAdd(t *testing.T) {
   364  	for _, test := range []struct {
   365  		desc    string
   366  		dupeInN int
   367  		wantNew bool
   368  		wantOld bool
   369  	}{
   370  		{
   371  			desc:    "all new",
   372  			dupeInN: 0,
   373  			wantNew: true,
   374  		},
   375  		{
   376  			desc:    "all old",
   377  			dupeInN: 1,
   378  			wantOld: true,
   379  		},
   380  		{
   381  			desc:    "mix",
   382  			dupeInN: 2,
   383  			wantNew: true,
   384  			wantOld: true,
   385  		},
   386  	} {
   387  		t.Run(test.desc, func(t *testing.T) {
   388  			state := hammerState{cfg: &HammerConfig{DuplicateChance: test.dupeInN}}
   389  			var gotNew, gotOld bool
   390  			for i := 0; i < 100; i++ {
   391  				switch state.chooseCertToAdd() {
   392  				case NewCert:
   393  					gotNew = true
   394  				case FirstCert, LastCert:
   395  					gotOld = true
   396  				}
   397  			}
   398  			if gotNew && !test.wantNew {
   399  				t.Errorf("got NewCert but expected none")
   400  			}
   401  			if !gotNew && test.wantNew {
   402  				t.Errorf("got no NewCerts but expected some")
   403  			}
   404  			if gotOld && !test.wantOld {
   405  				t.Errorf("got First/Last cert but expected none")
   406  			}
   407  			if !gotOld && test.wantOld {
   408  				t.Errorf("got no First/Last cert but expected some")
   409  			}
   410  		})
   411  	}
   412  }
   413  
   414  func TestStrictSTHConsistencySize(t *testing.T) {
   415  	ctx := context.Background()
   416  
   417  	for _, test := range []struct {
   418  		name       string
   419  		strict     bool
   420  		sthNowSize uint64
   421  		wantSkip   bool
   422  	}{
   423  		{name: "strict", strict: true, wantSkip: true},
   424  		{name: "relaxed_too_small", sthNowSize: 1, wantSkip: true},
   425  		{name: "relaxed_invent_size", sthNowSize: 10, wantSkip: false},
   426  	} {
   427  		t.Run(test.name, func(t *testing.T) {
   428  			s, lc := newFakeCTServer(t)
   429  			defer s.close()
   430  
   431  			s.sthNow.TreeSize = test.sthNowSize
   432  
   433  			hs, err := newHammerState(&HammerConfig{
   434  				StrictSTHConsistencySize: test.strict,
   435  				ClientPool:               RandomPool{lc},
   436  				LogCfg:                   &configpb.LogConfig{},
   437  			})
   438  			if err != nil {
   439  				t.Fatalf("Failed to create HammerState: %v", err)
   440  			}
   441  
   442  			err = hs.getSTHConsistency(ctx)
   443  			_, gotSkip := err.(errSkip)
   444  			if gotSkip != test.wantSkip {
   445  				t.Fatalf("got err %v, wanted Skip=%v", err, test.wantSkip)
   446  			}
   447  			if err != nil && !gotSkip {
   448  				t.Fatalf("got unexpected err %v", err)
   449  			}
   450  			if test.wantSkip {
   451  				return
   452  			}
   453  
   454  			if !s.getConsistencyCalled {
   455  				t.Fatal("hammer failed to request a consistency proof for invented tree size")
   456  			}
   457  		})
   458  	}
   459  }
   460  

View as plain text