...

Source file src/github.com/google/certificate-transparency-go/ctutil/sctscan/sctscan.go

Documentation: github.com/google/certificate-transparency-go/ctutil/sctscan

     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  // sctscan is a utility to scan a CT log and check embedded SCTs (Signed Certificate
    16  // Timestamps) in certificates in the log.
    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  // EmbeddedSCTMatcher implements the scanner.Matcher interface by matching just certificates
   107  // that have embedded SCTs.
   108  type EmbeddedSCTMatcher struct{}
   109  
   110  // CertificateMatches identifies certificates in the log that have the SCTList extension.
   111  func (e EmbeddedSCTMatcher) CertificateMatches(cert *x509.Certificate) bool {
   112  	return len(cert.SCTList.SCTList) > 0
   113  }
   114  
   115  // PrecertificateMatches identifies those precertificates in the log that are of
   116  // interest: none.
   117  func (e EmbeddedSCTMatcher) PrecertificateMatches(*ct.Precertificate) bool {
   118  	return false
   119  }
   120  
   121  // checkCertWithEmbeddedSCT is the callback that the scanner invokes for each cert found by the matcher.
   122  // Here, we only expect to get certificates that have embedded SCT lists.
   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  	// Build a Merkle leaf that corresponds to the embedded SCTs.  We can use the same
   145  	// leaf for all of the SCTs, as long as the timestamp field gets updated.
   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  			// Inclusion failure may be OK if the SCT is within the Log's MMD
   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