// Copyright 2019 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package submission import ( "bytes" "context" "encoding/hex" "fmt" "os" "strings" "testing" "time" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/client" "github.com/google/certificate-transparency-go/loglist3" "github.com/google/certificate-transparency-go/testdata" "github.com/google/certificate-transparency-go/tls" "github.com/google/trillian/monitoring" ) // stubLogListRefresher produces error on each Refresh call. type stubLogListRefresher struct { } func (llr stubLogListRefresher) Refresh() (*LogListData, error) { return nil, fmt.Errorf("stub Log List Refresher produces no Log List") } func (llr stubLogListRefresher) LastJSON() []byte { return nil } func (llr stubLogListRefresher) Source() string { return "stub" } func stubLogListManager() *LogListManager { return NewLogListManager(stubLogListRefresher{}, nil) } var imf monitoring.InertMetricFactory func TestProxyRefreshLLErr(t *testing.T) { p := NewProxy(stubLogListManager(), GetDistributorBuilder(ChromeCTPolicy, NewStubLogClient, imf), imf) _, err := p.llWatcher.RefreshLogList(context.Background()) if err == nil { t.Errorf("p.RefreshLogList() on stubLogListRefresher expected to get error, got none") } } func TestProxyBrokenDistributor(t *testing.T) { p := NewProxy(stubLogListManager(), GetDistributorBuilder(ChromeCTPolicy, newNoLogClient, imf), imf) _, err := p.llWatcher.RefreshLogList(context.Background()) if err == nil { t.Errorf("p.RefreshLogList() on brokenDistributorFactory expected to get error, got none") } } // Stub for AddLogCLient interface type stubNoRootsLogClient struct { logURL string } func (m stubNoRootsLogClient) AddChain(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) { return nil, fmt.Errorf("log %q has no roots", m.logURL) } func (m stubNoRootsLogClient) AddPreChain(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) { return nil, fmt.Errorf("log %q has no roots", m.logURL) } func (m stubNoRootsLogClient) GetAcceptedRoots(ctx context.Context) ([]ct.ASN1Cert, error) { return nil, fmt.Errorf("stubNoRootsLogClient cannot provide roots") } func buildStubNoRootsLogClient(log *loglist3.Log) (client.AddLogClient, error) { return stubNoRootsLogClient{logURL: log.URL}, nil } func TestProxyInitState(t *testing.T) { f, err := createTempFile(testdata.SampleLogList3) if err != nil { t.Fatalf("createTempFile(%q) = (_, %q), want (_, nil)", testdata.SampleLogList3, err) } defer os.Remove(f) llr := NewLogListRefresher(f) p := NewProxy(NewLogListManager(llr, nil), GetDistributorBuilder(ChromeCTPolicy, buildStubNoRootsLogClient, imf), imf) p.Run(context.Background(), 100*time.Millisecond, time.Hour) ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() Init: for { select { case <-ctx.Done(): t.Fatalf("p.Run() expected to send init signal, got none") case b, ok := <-p.Init: if !ok { t.Fatalf("p.Run() expected to send 'true' init signal via Init channel, but channel is closed") } if b != true { t.Fatalf("p.Run() expected to send 'true' init signal, got false") } break Init } } sampleLogListUpdate := strings.Replace(testdata.SampleLogList3, "ct.googleapis.com/racketeer/", "ct.googleapis.com/racketeer/v2/", 1) if err := os.WriteFile(f, []byte(sampleLogListUpdate), 0644); err != nil { t.Fatalf("unable to update Log-list data file: %q", err) } ctx, cancel = context.WithTimeout(context.Background(), time.Second) defer cancel() for { select { case <-ctx.Done(): return case _, ok := <-p.Init: if ok { t.Fatalf("p.Refresh() after initial p.Run() sent signal into Init-channel, expected none") } } } } // Helper func building slice of N AssignedSCTs. func buildAssignedSCTs(t *testing.T, n int) []*AssignedSCT { rawSCT := testdata.TestCertProof var sct ct.SignedCertificateTimestamp _, err := tls.Unmarshal(rawSCT, &sct) if err != nil { t.Fatalf("failed to tls-unmarshal test certificate proof: %s", err) } sampleLogURL := "ct.googleapis.com/racketeer/" assignedSCT := AssignedSCT{LogURL: sampleLogURL, SCT: &sct} assignedSCTs := make([]*AssignedSCT, n) for i := 0; i < n; i++ { assignedSCTs[i] = &assignedSCT } return assignedSCTs } // Helper func building slice of 1 AssignedSCTs containing one nil. func buildNilAssignedSCT() []*AssignedSCT { sampleLogURL := "ct.googleapis.com/racketeer/" assignedNilSCT := AssignedSCT{LogURL: sampleLogURL, SCT: nil} assignedSCTs := []*AssignedSCT{&assignedNilSCT} return assignedSCTs } func decodeValidHex(t *testing.T, s string) []byte { b, err := hex.DecodeString(s) if err != nil { t.Fatalf("Unable to hex-Decode %s", s) return nil } return b } func TestASN1MarshalSCT(t *testing.T) { tests := []struct { name string scts []*AssignedSCT want []byte wantErr bool }{ { name: "OneSCT", scts: buildAssignedSCTs(t, 1), want: decodeValidHex(t, "047a0078007600df1c2ec11500945247a96168325ddc5c7959e8f7c6d388fc002e0bbd3f74d7640000013ddb27ded900000403004730450220606e10ae5c2d5a1b0aed49dc4937f48de71a4e9784e9c208dfbfe9ef536cf7f2022100beb29c72d7d06d61d06bdb38a069469aa86fe12e18bb7cc45689a2c0187ef5a5"), wantErr: false, }, { name: "NoSCT", scts: buildAssignedSCTs(t, 0), want: nil, wantErr: true, }, { name: "TwoSCTs", scts: buildAssignedSCTs(t, 2), want: decodeValidHex(t, "0481f200f0007600df1c2ec11500945247a96168325ddc5c7959e8f7c6d388fc002e0bbd3f74d7640000013ddb27ded900000403004730450220606e10ae5c2d5a1b0aed49dc4937f48de71a4e9784e9c208dfbfe9ef536cf7f2022100beb29c72d7d06d61d06bdb38a069469aa86fe12e18bb7cc45689a2c0187ef5a5007600df1c2ec11500945247a96168325ddc5c7959e8f7c6d388fc002e0bbd3f74d7640000013ddb27ded900000403004730450220606e10ae5c2d5a1b0aed49dc4937f48de71a4e9784e9c208dfbfe9ef536cf7f2022100beb29c72d7d06d61d06bdb38a069469aa86fe12e18bb7cc45689a2c0187ef5a5"), wantErr: false, }, { name: "NilSCT", scts: buildNilAssignedSCT(), want: nil, wantErr: true, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { got, err := ASN1MarshalSCTs(test.scts) if (err != nil) && !test.wantErr { t.Fatalf("ASN1MarshalSCT() returned error %v", err) } if (err == nil) && test.wantErr { t.Fatalf("ASN1MarshalSCT() expected to return error, got none") } if !(bytes.Equal(got, test.want)) { t.Fatalf("ASN1MarshalSCT() returned unexpected output: \n got: %x\nwant: %x", got, test.want) } }) } }