...

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

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

     1  // Copyright 2018 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/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  // CopyChainGenerator creates certificate chains by copying suitable examples
    36  // from a source log.
    37  type CopyChainGenerator struct {
    38  	start, limit             time.Time
    39  	sourceRoots, targetRoots *x509util.PEMCertPool
    40  	certs, precerts          chan []ct.ASN1Cert
    41  }
    42  
    43  // CopyChainOptions describes the parameters for a CopyChainGenerator instance.
    44  type CopyChainOptions struct {
    45  	// StartIndex indicates where to start scanning; negative value implies starting from a random position.
    46  	StartIndex int64
    47  	// BufSize is the number of buffered chains to hold.
    48  	BufSize int
    49  	// BatchSize indicates how many entries should be requested from the source log at a time.
    50  	BatchSize int
    51  	// ParallelFetch indicates how many parallel entry fetchers to run.
    52  	ParallelFetch int
    53  }
    54  
    55  // NewCopyChainGenerator builds a certificate chain generator that sources
    56  // chains from another source log, starting at startIndex (or a random index in
    57  // the current tree size if startIndex is negative).  This function starts
    58  // background goroutines that scan the log; cancelling the context will
    59  // terminate these goroutines (after that the [Pre]CertChain() entrypoints will
    60  // permanently fail).
    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  // NewCopyChainGeneratorFromOpts builds a certificate chain generator that
    72  // sources chains from another source log, starting at opts.StartIndex (or a
    73  // random index in the current tree size if this is negative).  This function
    74  // starts background goroutines that scan the log; cancelling the context will
    75  // terminate these goroutines (after that the [Pre]CertChain() entrypoints
    76  // will permanently fail).
    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  		// Pick a random start point in the source log's tree.
   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  	// Start two goroutines to scan the source log for certs and precerts respectively.
   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  // processBatch extracts chains of the desired type from a batch of entries and sends
   176  // them down the channel.  May block on the channel consumer.
   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  			// Target log has NotAfter boundaries, so we need to parse the leaf cert to check
   201  			// whether it complies with them.
   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  // CertChain returns a new cert chain taken from the source log.
   225  // This may block until a suitable cert has been discovered in the source log.
   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  // PreCertChain returns a new precert chain taken from the source log.
   235  // This may block until a suitable precert has been discovered in the source log.
   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