1
2
3
4
5
6
7
8
9
10
11
12
13
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
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
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
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
167
168
169
170
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
292
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
321 type testKeys struct {
322 caChain, leafChain []ct.ASN1Cert
323 caCert, leafCert *x509.Certificate
324 signer crypto.Signer
325 }
326
327
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