1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package integration
16
17 import (
18 "context"
19 "crypto/sha256"
20 "errors"
21 "fmt"
22 "math/rand"
23 "time"
24
25 "github.com/google/certificate-transparency-go/client"
26 "github.com/google/certificate-transparency-go/scanner"
27 "github.com/google/certificate-transparency-go/trillian/ctfe/configpb"
28 "github.com/google/certificate-transparency-go/x509"
29 "github.com/google/certificate-transparency-go/x509util"
30 "k8s.io/klog/v2"
31
32 ct "github.com/google/certificate-transparency-go"
33 )
34
35
36
37 type CopyChainGenerator struct {
38 start, limit time.Time
39 sourceRoots, targetRoots *x509util.PEMCertPool
40 certs, precerts chan []ct.ASN1Cert
41 }
42
43
44 type CopyChainOptions struct {
45
46 StartIndex int64
47
48 BufSize int
49
50 BatchSize int
51
52 ParallelFetch int
53 }
54
55
56
57
58
59
60
61 func NewCopyChainGenerator(ctx context.Context, client *client.LogClient, cfg *configpb.LogConfig, startIndex int64, bufSize int) (ChainGenerator, error) {
62 opts := CopyChainOptions{
63 StartIndex: startIndex,
64 BufSize: bufSize,
65 BatchSize: 500,
66 ParallelFetch: 2,
67 }
68 return NewCopyChainGeneratorFromOpts(ctx, client, cfg, opts)
69 }
70
71
72
73
74
75
76
77 func NewCopyChainGeneratorFromOpts(ctx context.Context, client *client.LogClient, cfg *configpb.LogConfig, opts CopyChainOptions) (ChainGenerator, error) {
78 var start, limit time.Time
79 var err error
80 if cfg.NotAfterStart != nil {
81 if err := cfg.NotAfterStart.CheckValid(); err != nil {
82 return nil, fmt.Errorf("failed to parse NotAfterStart: %v", err)
83 }
84 start = cfg.NotAfterStart.AsTime()
85 }
86 if cfg.NotAfterLimit != nil {
87 if err := cfg.NotAfterLimit.CheckValid(); err != nil {
88 return nil, fmt.Errorf("failed to parse NotAfterLimit: %v", err)
89 }
90 limit = cfg.NotAfterLimit.AsTime()
91 }
92
93 targetPool := x509util.NewPEMCertPool()
94 for _, pemFile := range cfg.RootsPemFile {
95 if err := targetPool.AppendCertsFromPEMFile(pemFile); err != nil {
96 return nil, fmt.Errorf("failed to read trusted roots for target log: %v", err)
97 }
98 }
99 if klog.V(4).Enabled() {
100 for _, cert := range targetPool.RawCertificates() {
101 klog.Infof("target root cert: %x Subject: %v", sha256.Sum256(cert.Raw), cert.Subject)
102 }
103 }
104
105 seenOverlap := false
106 srcRoots, err := client.GetAcceptedRoots(ctx)
107 if err != nil {
108 return nil, fmt.Errorf("failed to read trusted roots for source log: %v", err)
109 }
110 sourcePool := x509util.NewPEMCertPool()
111 for _, root := range srcRoots {
112 cert, err := x509.ParseCertificate(root.Data)
113 if x509.IsFatal(err) {
114 klog.Warningf("Failed to parse root certificate from source log: %v", err)
115 continue
116 }
117 klog.V(4).Infof("source log root cert: %x Subject: %v", sha256.Sum256(cert.Raw), cert.Subject)
118 sourcePool.AddCert(cert)
119 if targetPool.Included(cert) {
120 klog.V(3).Infof("source log root cert is accepted by target: %x Subject: %v", sha256.Sum256(cert.Raw), cert.Subject)
121 seenOverlap = true
122 }
123 }
124 if !seenOverlap {
125 return nil, fmt.Errorf("failed to find any overlap in accepted roots for target %s", cfg.Prefix)
126 }
127
128 startIndex := opts.StartIndex
129 if startIndex < 0 {
130
131 sth, err := client.GetSTH(ctx)
132 if err != nil {
133 return nil, fmt.Errorf("failed to get STH for source log: %v", err)
134 }
135 startIndex = rand.Int63n(int64(sth.TreeSize))
136 klog.Infof("starting CopyChainGenerator from index %d (of %d) in source log", startIndex, sth.TreeSize)
137 }
138
139 generator := &CopyChainGenerator{
140 start: start,
141 limit: limit,
142 targetRoots: targetPool,
143 sourceRoots: sourcePool,
144 certs: make(chan []ct.ASN1Cert, opts.BufSize),
145 precerts: make(chan []ct.ASN1Cert, opts.BufSize),
146 }
147
148
149 fetchOpts := scanner.FetcherOptions{
150 BatchSize: opts.BatchSize,
151 ParallelFetch: opts.ParallelFetch,
152 Continuous: true,
153 StartIndex: startIndex,
154 }
155 go func() {
156 certFetcher := scanner.NewFetcher(client, &fetchOpts)
157 if err := certFetcher.Run(ctx, func(batch scanner.EntryBatch) {
158 generator.processBatch(batch, generator.certs, ct.X509LogEntryType)
159 }); err != nil {
160 klog.Errorf("processBatch(): %v", err)
161 }
162 }()
163 go func() {
164 precertFetcher := scanner.NewFetcher(client, &fetchOpts)
165 if err := precertFetcher.Run(ctx, func(batch scanner.EntryBatch) {
166 generator.processBatch(batch, generator.precerts, ct.PrecertLogEntryType)
167 }); err != nil {
168 klog.Errorf("processBatch(): %v", err)
169 }
170 }()
171
172 return generator, nil
173 }
174
175
176
177 func (g *CopyChainGenerator) processBatch(batch scanner.EntryBatch, chains chan []ct.ASN1Cert, eType ct.LogEntryType) {
178 klog.V(2).Infof("processBatch(%d): examine batch [%d, %d)", eType, batch.Start, int(batch.Start)+len(batch.Entries))
179 for i, entry := range batch.Entries {
180 index := batch.Start + int64(i)
181 entry, err := ct.RawLogEntryFromLeaf(index, &entry)
182 if err != nil {
183 klog.Errorf("processBatch(%d): failed to build raw log entry %d: %v", eType, index, err)
184 continue
185 }
186 if entry.Leaf.TimestampedEntry.EntryType != eType {
187 klog.V(4).Infof("skip entry %d as EntryType=%d not %d", index, entry.Leaf.TimestampedEntry.EntryType, eType)
188 continue
189 }
190 root, err := x509.ParseCertificate(entry.Chain[len(entry.Chain)-1].Data)
191 if err != nil {
192 klog.V(3).Infof("skip entry %d as its root cannot be parsed to check accepted: %v", index, err)
193 continue
194 }
195 if !g.targetRoots.Included(root) {
196 klog.V(3).Infof("skip entry %d as its root is not accepted by target log", index)
197 continue
198 }
199 if !g.start.IsZero() || !g.limit.IsZero() {
200
201
202 cert, err := x509.ParseCertificate(entry.Cert.Data)
203 if x509.IsFatal(err) {
204 klog.V(3).Infof("skip entry %d as its leaf cannot be parsed to check NotAfter: %v", index, err)
205 continue
206 }
207 if !g.start.IsZero() && cert.NotAfter.Before(g.start) {
208 klog.V(3).Infof("skip entry %d as its NotAfter (%v) is before %v", index, cert.NotAfter, g.start)
209 continue
210 }
211 if !g.limit.IsZero() && !cert.NotAfter.Before(g.limit) {
212 klog.V(3).Infof("skip entry %d as its NotAfter (%v) is after %v", index, cert.NotAfter, g.limit)
213 continue
214 }
215 }
216
217 chain := make([]ct.ASN1Cert, len(entry.Chain)+1)
218 chain[0] = entry.Cert
219 copy(chain[1:], entry.Chain)
220 chains <- chain
221 }
222 }
223
224
225
226 func (g *CopyChainGenerator) CertChain() ([]ct.ASN1Cert, error) {
227 chain := <-g.certs
228 if len(chain) == 0 {
229 return nil, errors.New("no certs available")
230 }
231 return chain, nil
232 }
233
234
235
236 func (g *CopyChainGenerator) PreCertChain() ([]ct.ASN1Cert, []byte, error) {
237 prechain := <-g.precerts
238 if len(prechain) == 0 {
239 return nil, nil, errors.New("no precerts available")
240 }
241 tbs, err := buildLeafTBS(prechain[0].Data, nil)
242 if err != nil {
243 return nil, nil, fmt.Errorf("failed to build leaf TBSCertificate: %v", err)
244 }
245 return prechain, tbs, nil
246 }
247
View as plain text