1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package main
18
19 import (
20 "context"
21 "crypto/sha256"
22 "flag"
23 "net/http"
24 "time"
25
26 ct "github.com/google/certificate-transparency-go"
27 "github.com/google/certificate-transparency-go/client"
28 "github.com/google/certificate-transparency-go/ctutil"
29 "github.com/google/certificate-transparency-go/jsonclient"
30 "github.com/google/certificate-transparency-go/loglist3"
31 "github.com/google/certificate-transparency-go/scanner"
32 "github.com/google/certificate-transparency-go/x509"
33 "github.com/google/certificate-transparency-go/x509util"
34 "k8s.io/klog/v2"
35 )
36
37 var (
38 logURI = flag.String("log_uri", "https://ct.googleapis.com/pilot", "CT log base URI")
39 logList = flag.String("log_list", loglist3.AllLogListURL, "Location of master CT log list (URL or filename)")
40 inclusion = flag.Bool("inclusion", false, "Whether to do inclusion checking")
41 deadline = flag.Duration("deadline", 30*time.Second, "Timeout deadline for HTTP requests")
42 batchSize = flag.Int("batch_size", 1000, "Max number of entries to request at per call to get-entries")
43 numWorkers = flag.Int("num_workers", 2, "Number of concurrent matchers")
44 parallelFetch = flag.Int("parallel_fetch", 2, "Number of concurrent GetEntries fetches")
45 startIndex = flag.Int64("start_index", 0, "Log index to start scanning at")
46 )
47
48 func main() {
49 klog.InitFlags(nil)
50 flag.Parse()
51 ctx := context.Background()
52 klog.CopyStandardLogTo("WARNING")
53
54 hc := &http.Client{
55 Timeout: *deadline,
56 Transport: &http.Transport{
57 TLSHandshakeTimeout: 30 * time.Second,
58 ResponseHeaderTimeout: 30 * time.Second,
59 MaxIdleConnsPerHost: 10,
60 DisableKeepAlives: false,
61 MaxIdleConns: 100,
62 IdleConnTimeout: 90 * time.Second,
63 ExpectContinueTimeout: 1 * time.Second,
64 },
65 }
66 logClient, err := client.New(*logURI, hc, jsonclient.Options{UserAgent: "ct-go-sctscan/1.0"})
67 if err != nil {
68 klog.Exitf("Failed to create log client: %v", err)
69 }
70 llData, err := x509util.ReadFileOrURL(*logList, hc)
71 if err != nil {
72 klog.Exitf("Failed to read log list: %v", err)
73 }
74 ll, err := loglist3.NewFromJSON(llData)
75 if err != nil {
76 klog.Exitf("Failed to parse log list: %v", err)
77 }
78 klog.Warning("Performing validations via direct log queries")
79 logsByHash, err := ctutil.LogInfoByKeyHash(ll, hc)
80 if err != nil {
81 klog.Exitf("Failed to build log info map: %v", err)
82 }
83
84 scanOpts := scanner.ScannerOptions{
85 FetcherOptions: scanner.FetcherOptions{
86 BatchSize: *batchSize,
87 ParallelFetch: *parallelFetch,
88 StartIndex: *startIndex,
89 },
90 Matcher: EmbeddedSCTMatcher{},
91 NumWorkers: *numWorkers,
92 }
93 s := scanner.NewScanner(logClient, scanOpts)
94
95 if err := s.Scan(ctx,
96 func(entry *ct.RawLogEntry) {
97 checkCertWithEmbeddedSCT(ctx, logsByHash, *inclusion, entry)
98 },
99 func(entry *ct.RawLogEntry) {
100 klog.Errorf("Internal error: found pre-cert! %+v", entry)
101 }); err != nil {
102 klog.Exitf("Scan failed: %v", err)
103 }
104 }
105
106
107
108 type EmbeddedSCTMatcher struct{}
109
110
111 func (e EmbeddedSCTMatcher) CertificateMatches(cert *x509.Certificate) bool {
112 return len(cert.SCTList.SCTList) > 0
113 }
114
115
116
117 func (e EmbeddedSCTMatcher) PrecertificateMatches(*ct.Precertificate) bool {
118 return false
119 }
120
121
122
123 func checkCertWithEmbeddedSCT(ctx context.Context, logsByKey map[[sha256.Size]byte]*ctutil.LogInfo, checkInclusion bool, rawEntry *ct.RawLogEntry) {
124 entry, err := rawEntry.ToLogEntry()
125 if x509.IsFatal(err) {
126 klog.Errorf("[%d] Internal error: failed to parse cert in entry: %v", rawEntry.Index, err)
127 return
128 }
129
130 leaf := entry.X509Cert
131 if leaf == nil {
132 klog.Errorf("[%d] Internal error: no cert in entry", entry.Index)
133 return
134 }
135 if len(entry.Chain) == 0 {
136 klog.Errorf("[%d] No issuance chain found", entry.Index)
137 return
138 }
139 issuer, err := x509.ParseCertificate(entry.Chain[0].Data)
140 if err != nil {
141 klog.Errorf("[%d] Failed to parse issuer: %v", entry.Index, err)
142 }
143
144
145
146 merkleLeaf, err := ct.MerkleTreeLeafForEmbeddedSCT([]*x509.Certificate{leaf, issuer}, 0)
147 if err != nil {
148 klog.Errorf("[%d] Failed to build Merkle leaf: %v", entry.Index, err)
149 return
150 }
151
152 for i, sctData := range leaf.SCTList.SCTList {
153 sct, err := x509util.ExtractSCT(&sctData)
154 if err != nil {
155 klog.Errorf("[%d] Failed to deserialize SCT[%d] data: %v", entry.Index, i, err)
156 continue
157 }
158 logInfo := logsByKey[sct.LogID.KeyID]
159 if logInfo == nil {
160 klog.Infof("[%d] SCT[%d] for unknown logID: %x, cannot validate SCT", entry.Index, i, sct.LogID)
161 continue
162 }
163
164 if err := logInfo.VerifySCTSignature(*sct, *merkleLeaf); err != nil {
165 klog.Errorf("[%d] Failed to verify SCT[%d] signature from log %q: %v", entry.Index, i, logInfo.Description, err)
166 } else {
167 klog.V(1).Infof("[%d] Verified SCT[%d] against log %q", entry.Index, i, logInfo.Description)
168 }
169
170 if !checkInclusion {
171 continue
172 }
173
174 if index, err := logInfo.VerifyInclusionLatest(ctx, *merkleLeaf, sct.Timestamp); err != nil {
175
176 sth := logInfo.LastSTH()
177 if sth != nil {
178 delta := time.Duration(sth.Timestamp-sct.Timestamp) * time.Millisecond
179 if delta < logInfo.MMD {
180 klog.Warningf("[%d] Failed to verify SCT[%d] inclusion proof (%v), but Log's MMD has not passed %d -> %d < %v", entry.Index, i, err, sct.Timestamp, sth.Timestamp, logInfo.MMD)
181 continue
182 }
183 }
184 klog.Errorf("[%d] Failed to verify SCT[%d] inclusion proof: %v", entry.Index, i, err)
185 } else {
186 klog.V(1).Infof("[%d] Checked SCT[%d] inclusion against log %q, at index %d", entry.Index, i, logInfo.Description, index)
187 }
188 }
189 }
190
View as plain text