Retrieve log list from certspotter.org at startup instead of embedding in source

The list of logs changes far too frequently (with annual shards and operators
dropping out of the ecosystem) to continue embedding in the source code.

Breaking change: the -logs option now expects a
JSON file in the v2 log list format, as documented at
<https://www.certificate-transparency.org/known-logs> and
<https://www.gstatic.com/ct/log_list/v2/log_list_schema.json>.

You can now specify an HTTPS URL to -logs in addition to a file path.

Breaking change: the -underwater option has been removed; if you want
this behavior then specify https://loglist.certspotter.org/underwater.json
as your log list.
This commit is contained in:
Andrew Ayer 2020-04-29 11:38:26 -04:00
parent 43fe09e1f2
commit 185445e158
6 changed files with 80 additions and 373 deletions

9
README
View File

@ -93,10 +93,11 @@ COMMAND LINE FLAGS
-all_time -all_time
Scan for certificates from all time, not just those logged since Scan for certificates from all time, not just those logged since
the previous run of Cert Spotter. the previous run of Cert Spotter.
-logs FILENAME -logs FILENAME_OR_URL
JSON file containing logs to scan, in the format documented at Filename of HTTPS URL of a JSON file containing logs to monitor, in the format
<https://www.certificate-transparency.org/known-logs>. documented at <https://www.certificate-transparency.org/known-logs>.
Default: use the logs trusted by Chromium. Default: https://loglist.certspotter.org/monitor.json which includes the union
of active logs recognized by Chrome and Apple.
-state_dir PATH -state_dir PATH
Directory for storing state. Default: ~/.certspotter Directory for storing state. Default: ~/.certspotter
-verbose -verbose

View File

@ -11,6 +11,7 @@ package cmd
import ( import (
"bytes" "bytes"
"crypto/x509"
"flag" "flag"
"fmt" "fmt"
"log" "log"
@ -21,13 +22,15 @@ import (
"software.sslmate.com/src/certspotter" "software.sslmate.com/src/certspotter"
"software.sslmate.com/src/certspotter/ct" "software.sslmate.com/src/certspotter/ct"
"software.sslmate.com/src/certspotter/loglist"
) )
const defaultLogList = "https://loglist.certspotter.org/monitor.json"
var batchSize = flag.Int("batch_size", 1000, "Max number of entries to request at per call to get-entries (advanced)") var batchSize = flag.Int("batch_size", 1000, "Max number of entries to request at per call to get-entries (advanced)")
var numWorkers = flag.Int("num_workers", 2, "Number of concurrent matchers (advanced)") var numWorkers = flag.Int("num_workers", 2, "Number of concurrent matchers (advanced)")
var script = flag.String("script", "", "Script to execute when a matching certificate is found") var script = flag.String("script", "", "Script to execute when a matching certificate is found")
var logsFilename = flag.String("logs", "", "JSON file containing log information") var logsURL = flag.String("logs", defaultLogList, "File path or URL of JSON list of logs to monitor")
var underwater = flag.Bool("underwater", false, "Monitor certificates from distrusted CAs instead of trusted CAs")
var noSave = flag.Bool("no_save", false, "Do not save a copy of matching certificates") var noSave = flag.Bool("no_save", false, "Do not save a copy of matching certificates")
var verbose = flag.Bool("verbose", false, "Be verbose") var verbose = flag.Bool("verbose", false, "Be verbose")
var startAtEnd = flag.Bool("start_at_end", false, "Start monitoring logs from the end rather than the beginning") var startAtEnd = flag.Bool("start_at_end", false, "Start monitoring logs from the end rather than the beginning")
@ -81,18 +84,12 @@ func LogEntry(info *certspotter.EntryInfo) {
} }
} }
func loadLogList() ([]certspotter.LogInfo, error) { func loadLogList() ([]*loglist.Log, error) {
if *logsFilename != "" { list, err := loglist.Load(*logsURL)
var logFileObj certspotter.LogInfoFile if err != nil {
if err := readJSONFile(*logsFilename, &logFileObj); err != nil { return nil, fmt.Errorf("Error loading log list: %s", err)
return nil, fmt.Errorf("Error reading logs file: %s: %s", *logsFilename, err)
}
return logFileObj.Logs, nil
} else if *underwater {
return certspotter.UnderwaterLogs, nil
} else {
return certspotter.DefaultLogs, nil
} }
return list.AllLogs(), nil
} }
type logHandle struct { type logHandle struct {
@ -102,14 +99,14 @@ type logHandle struct {
verifiedSTH *ct.SignedTreeHead verifiedSTH *ct.SignedTreeHead
} }
func makeLogHandle(logInfo *certspotter.LogInfo) (*logHandle, error) { func makeLogHandle(logInfo *loglist.Log) (*logHandle, error) {
ctlog := new(logHandle) ctlog := new(logHandle)
logKey, err := logInfo.ParsedPublicKey() logKey, err := x509.ParsePKIXPublicKey(logInfo.Key)
if err != nil { if err != nil {
return nil, fmt.Errorf("Bad public key: %s", err) return nil, fmt.Errorf("Bad public key: %s", err)
} }
ctlog.scanner = certspotter.NewScanner(logInfo.FullURI(), logInfo.ID(), logKey, &certspotter.ScannerOptions{ ctlog.scanner = certspotter.NewScanner(logInfo.URL, logInfo.LogID, logKey, &certspotter.ScannerOptions{
BatchSize: *batchSize, BatchSize: *batchSize,
NumWorkers: *numWorkers, NumWorkers: *numWorkers,
Quiet: !*verbose, Quiet: !*verbose,
@ -134,7 +131,7 @@ func makeLogHandle(logInfo *certspotter.LogInfo) (*logHandle, error) {
return nil, fmt.Errorf("Error loading legacy STH: %s", err) return nil, fmt.Errorf("Error loading legacy STH: %s", err)
} }
if legacySTH != nil { if legacySTH != nil {
log.Print(logInfo.Url, ": Initializing log state from legacy state directory") log.Print(logInfo.URL, ": Initializing log state from legacy state directory")
ctlog.tree, err = ctlog.scanner.MakeCollapsedMerkleTree(legacySTH) ctlog.tree, err = ctlog.scanner.MakeCollapsedMerkleTree(legacySTH)
if err != nil { if err != nil {
return nil, fmt.Errorf("Error reconstructing Merkle Tree for legacy STH: %s", err) return nil, fmt.Errorf("Error reconstructing Merkle Tree for legacy STH: %s", err)
@ -243,59 +240,59 @@ func (ctlog *logHandle) scan(processCallback certspotter.ProcessCallback) error
return nil return nil
} }
func processLog(logInfo *certspotter.LogInfo, processCallback certspotter.ProcessCallback) int { func processLog(logInfo *loglist.Log, processCallback certspotter.ProcessCallback) int {
ctlog, err := makeLogHandle(logInfo) ctlog, err := makeLogHandle(logInfo)
if err != nil { if err != nil {
log.Print(logInfo.Url, ": ", err) log.Print(logInfo.URL, ": ", err)
return 1 return 1
} }
if err := ctlog.refresh(); err != nil { if err := ctlog.refresh(); err != nil {
log.Print(logInfo.Url, ": ", err) log.Print(logInfo.URL, ": ", err)
return 1 return 1
} }
if err := ctlog.audit(); err != nil { if err := ctlog.audit(); err != nil {
log.Print(logInfo.Url, ": ", err) log.Print(logInfo.URL, ": ", err)
return 1 return 1
} }
if *allTime { if *allTime {
ctlog.tree = certspotter.EmptyCollapsedMerkleTree() ctlog.tree = certspotter.EmptyCollapsedMerkleTree()
if *verbose { if *verbose {
log.Printf("%s: Scanning all %d entries in the log because -all_time option specified", logInfo.Url, ctlog.verifiedSTH.TreeSize) log.Printf("%s: Scanning all %d entries in the log because -all_time option specified", logInfo.URL, ctlog.verifiedSTH.TreeSize)
} }
} else if ctlog.tree != nil { } else if ctlog.tree != nil {
if *verbose { if *verbose {
log.Printf("%s: Existing log; scanning %d new entries since previous scan", logInfo.Url, ctlog.verifiedSTH.TreeSize-ctlog.tree.GetSize()) log.Printf("%s: Existing log; scanning %d new entries since previous scan", logInfo.URL, ctlog.verifiedSTH.TreeSize-ctlog.tree.GetSize())
} }
} else if *startAtEnd { } else if *startAtEnd {
ctlog.tree, err = ctlog.scanner.MakeCollapsedMerkleTree(ctlog.verifiedSTH) ctlog.tree, err = ctlog.scanner.MakeCollapsedMerkleTree(ctlog.verifiedSTH)
if err != nil { if err != nil {
log.Print("%s: Error reconstructing Merkle Tree: %s", logInfo.Url, err) log.Print("%s: Error reconstructing Merkle Tree: %s", logInfo.URL, err)
return 1 return 1
} }
if *verbose { if *verbose {
log.Printf("%s: New log; not scanning %d existing entries because -start_at_end option was specified", logInfo.Url, ctlog.verifiedSTH.TreeSize) log.Printf("%s: New log; not scanning %d existing entries because -start_at_end option was specified", logInfo.URL, ctlog.verifiedSTH.TreeSize)
} }
} else { } else {
ctlog.tree = certspotter.EmptyCollapsedMerkleTree() ctlog.tree = certspotter.EmptyCollapsedMerkleTree()
if *verbose { if *verbose {
log.Printf("%s: New log; scanning all %d entries in the log (use the -start_at_end option to scan new logs from the end rather than the beginning)", logInfo.Url, ctlog.verifiedSTH.TreeSize) log.Printf("%s: New log; scanning all %d entries in the log (use the -start_at_end option to scan new logs from the end rather than the beginning)", logInfo.URL, ctlog.verifiedSTH.TreeSize)
} }
} }
if err := ctlog.state.StoreTree(ctlog.tree); err != nil { if err := ctlog.state.StoreTree(ctlog.tree); err != nil {
log.Printf("%s: Error storing tree: %s\n", logInfo.Url, err) log.Printf("%s: Error storing tree: %s\n", logInfo.URL, err)
return 1 return 1
} }
if err := ctlog.scan(processCallback); err != nil { if err := ctlog.scan(processCallback); err != nil {
log.Print(logInfo.Url, ": ", err) log.Print(logInfo.URL, ": ", err)
return 1 return 1
} }
if *verbose { if *verbose {
log.Printf("%s: Final log size = %d, final root hash = %x", logInfo.Url, ctlog.verifiedSTH.TreeSize, ctlog.verifiedSTH.SHA256RootHash) log.Printf("%s: Final log size = %d, final root hash = %x", logInfo.URL, ctlog.verifiedSTH.TreeSize, ctlog.verifiedSTH.SHA256RootHash)
} }
return 0 return 0
@ -330,10 +327,10 @@ func Main(statePath string, processCallback certspotter.ProcessCallback) int {
} }
processLogResults := make(chan int) processLogResults := make(chan int)
for i := range logs { for _, logInfo := range logs {
go func(logInfo *certspotter.LogInfo) { go func(logInfo *loglist.Log) {
processLogResults <- processLog(logInfo, processCallback) processLogResults <- processLog(logInfo, processCallback)
}(&logs[i]) }(logInfo)
} }
exitCode := 0 exitCode := 0

View File

@ -21,16 +21,16 @@ import (
"strconv" "strconv"
"strings" "strings"
"software.sslmate.com/src/certspotter"
"software.sslmate.com/src/certspotter/ct" "software.sslmate.com/src/certspotter/ct"
"software.sslmate.com/src/certspotter/loglist"
) )
type State struct { type State struct {
path string path string
} }
func legacySTHFilename(logInfo *certspotter.LogInfo) string { func legacySTHFilename(logInfo *loglist.Log) string {
return strings.Replace(strings.Replace(logInfo.FullURI(), "://", "_", 1), "/", "_", -1) return strings.Replace(strings.Replace(logInfo.URL, "://", "_", 1), "/", "_", -1)
} }
func readVersionFile(statePath string) (int, error) { func readVersionFile(statePath string) (int, error) {
@ -161,11 +161,11 @@ func (state *State) SaveCert(isPrecert bool, certs [][]byte) (bool, string, erro
return false, path, nil return false, path, nil
} }
func (state *State) OpenLogState(logInfo *certspotter.LogInfo) (*LogState, error) { func (state *State) OpenLogState(logInfo *loglist.Log) (*LogState, error) {
return OpenLogState(filepath.Join(state.path, "logs", base64.RawURLEncoding.EncodeToString(logInfo.ID()))) return OpenLogState(filepath.Join(state.path, "logs", base64.RawURLEncoding.EncodeToString(logInfo.LogID)))
} }
func (state *State) GetLegacySTH(logInfo *certspotter.LogInfo) (*ct.SignedTreeHead, error) { func (state *State) GetLegacySTH(logInfo *loglist.Log) (*ct.SignedTreeHead, error) {
sth, err := readSTHFile(filepath.Join(state.path, "legacy_sths", legacySTHFilename(logInfo))) sth, err := readSTHFile(filepath.Join(state.path, "legacy_sths", legacySTHFilename(logInfo)))
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
@ -176,7 +176,7 @@ func (state *State) GetLegacySTH(logInfo *certspotter.LogInfo) (*ct.SignedTreeHe
} }
return sth, nil return sth, nil
} }
func (state *State) RemoveLegacySTH(logInfo *certspotter.LogInfo) error { func (state *State) RemoveLegacySTH(logInfo *loglist.Log) error {
err := os.Remove(filepath.Join(state.path, "legacy_sths", legacySTHFilename(logInfo))) err := os.Remove(filepath.Join(state.path, "legacy_sths", legacySTHFilename(logInfo)))
os.Remove(filepath.Join(state.path, "legacy_sths")) os.Remove(filepath.Join(state.path, "legacy_sths"))
return err return err

View File

@ -13,26 +13,33 @@ import (
"software.sslmate.com/src/certspotter" "software.sslmate.com/src/certspotter"
"software.sslmate.com/src/certspotter/ct" "software.sslmate.com/src/certspotter/ct"
"software.sslmate.com/src/certspotter/ct/client" "software.sslmate.com/src/certspotter/ct/client"
"software.sslmate.com/src/certspotter/loglist"
"bytes" "bytes"
"crypto/sha256" "crypto/sha256"
"crypto/x509"
"encoding/pem" "encoding/pem"
"flag" "flag"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
) )
const defaultLogList = "https://loglist.certspotter.org/submit.json"
var verbose = flag.Bool("v", false, "Enable verbose output") var verbose = flag.Bool("v", false, "Enable verbose output")
var logsURL = flag.String("logs", defaultLogList, "File path or URL of JSON list of logs to submit to")
type Certificate struct { type Certificate struct {
Subject []byte Subject []byte
Issuer []byte Issuer []byte
Raw []byte Raw []byte
Expiration time.Time
} }
func (cert *Certificate) Fingerprint() [32]byte { func (cert *Certificate) Fingerprint() [32]byte {
@ -62,10 +69,16 @@ func parseCertificate(data []byte) (*Certificate, error) {
return nil, err return nil, err
} }
validity, err := tbs.ParseValidity()
if err != nil {
return nil, err
}
return &Certificate{ return &Certificate{
Subject: tbs.Subject.FullBytes, Subject: tbs.Subject.FullBytes,
Issuer: tbs.Issuer.FullBytes, Issuer: tbs.Issuer.FullBytes,
Raw: data, Raw: data,
Expiration: validity.NotAfter,
}, nil }, nil
} }
@ -101,19 +114,19 @@ func (certs *CertificateBunch) FindBySubject(subject []byte) *Certificate {
} }
type Log struct { type Log struct {
info certspotter.LogInfo *loglist.Log
verify *ct.SignatureVerifier *ct.SignatureVerifier
client *client.LogClient *client.LogClient
} }
func (ctlog *Log) SubmitChain(chain Chain) (*ct.SignedCertificateTimestamp, error) { func (ctlog *Log) SubmitChain(chain Chain) (*ct.SignedCertificateTimestamp, error) {
rawCerts := chain.GetRawCerts() rawCerts := chain.GetRawCerts()
sct, err := ctlog.client.AddChain(rawCerts) sct, err := ctlog.AddChain(rawCerts)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := certspotter.VerifyX509SCT(sct, rawCerts[0], ctlog.verify); err != nil { if err := certspotter.VerifyX509SCT(sct, rawCerts[0], ctlog.SignatureVerifier); err != nil {
return nil, fmt.Errorf("Bad SCT signature: %s", err) return nil, fmt.Errorf("Bad SCT signature: %s", err)
} }
return sct, nil return sct, nil
@ -137,20 +150,25 @@ func main() {
log.Fatalf("Error reading stdin: %s", err) log.Fatalf("Error reading stdin: %s", err)
} }
logs := make([]Log, 0, len(certspotter.OpenLogs)) list, err := loglist.Load(*logsURL)
for _, loginfo := range certspotter.OpenLogs {
pubkey, err := loginfo.ParsedPublicKey()
if err != nil { if err != nil {
log.Fatalf("%s: Failed to parse log public key: %s", loginfo.Url, err) log.Fatalf("Error loading log list: %s", err)
} }
verify, err := ct.NewSignatureVerifier(pubkey)
var logs []Log
for _, ctlog := range list.AllLogs() {
pubkey, err := x509.ParsePKIXPublicKey(ctlog.Key)
if err != nil { if err != nil {
log.Fatalf("%s: Failed to create signature verifier for log: %s", loginfo.Url, err) log.Fatalf("%s: Failed to parse log public key: %s", ctlog.URL, err)
}
verifier, err := ct.NewSignatureVerifier(pubkey)
if err != nil {
log.Fatalf("%s: Failed to create signature verifier for log: %s", ctlog.URL, err)
} }
logs = append(logs, Log{ logs = append(logs, Log{
info: loginfo, Log: ctlog,
verify: verify, SignatureVerifier: verifier,
client: client.New(loginfo.FullURI()), LogClient: client.New(strings.TrimRight(ctlog.URL, "/")),
}) })
} }
@ -186,15 +204,18 @@ func main() {
continue continue
} }
for _, ctlog := range logs { for _, ctlog := range logs {
if !ctlog.AcceptsExpiration(chain[0].Expiration) {
continue
}
wg.Add(1) wg.Add(1)
go func(fingerprint [32]byte, ctlog Log) { go func(fingerprint [32]byte, ctlog Log) {
sct, err := ctlog.SubmitChain(chain) sct, err := ctlog.SubmitChain(chain)
if err != nil { if err != nil {
log.Printf("%x (%s): %s: Submission Error: %s", fingerprint, cn, ctlog.info.Url, err) log.Printf("%x (%s): %s: Submission Error: %s", fingerprint, cn, ctlog.URL, err)
atomic.AddUint32(&submitErrors, 1) atomic.AddUint32(&submitErrors, 1)
} else if *verbose { } else if *verbose {
timestamp := time.Unix(int64(sct.Timestamp)/1000, int64(sct.Timestamp%1000)*1000000) timestamp := time.Unix(int64(sct.Timestamp)/1000, int64(sct.Timestamp%1000)*1000000)
log.Printf("%x (%s): %s: Submitted at %s", fingerprint, cn, ctlog.info.Url, timestamp) log.Printf("%x (%s): %s: Submitted at %s", fingerprint, cn, ctlog.URL, timestamp)
} }
wg.Done() wg.Done()
}(fingerprint, ctlog) }(fingerprint, ctlog)

313
logs.go
View File

@ -1,313 +0,0 @@
// Copyright (C) 2016 Opsmate, Inc.
//
// This Source Code Form is subject to the terms of the Mozilla
// Public License, v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// This software is distributed WITHOUT A WARRANTY OF ANY KIND.
// See the Mozilla Public License for details.
package certspotter
import (
"crypto"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"flag"
"time"
)
var http_flag = flag.Bool("http", false, "Connect to CT logs over http instead of https, useful for testing")
type LogInfoFile struct {
Logs []LogInfo `json:"logs"`
}
type LogInfo struct {
Description string `json:"description"`
Key []byte `json:"key"`
Url string `json:"url"`
MMD int `json:"maximum_merge_delay"`
CertExpiryBegin *time.Time `json:"cert_expiry_begin"`
CertExpiryEnd *time.Time `json:"cert_expiry_end"`
}
func (info *LogInfo) FullURI() string {
if *http_flag {
return "http://" + info.Url
}
return "https://" + info.Url
}
func (info *LogInfo) ParsedPublicKey() (crypto.PublicKey, error) {
if info.Key != nil {
return x509.ParsePKIXPublicKey(info.Key)
} else {
return nil, nil
}
}
func (info *LogInfo) ID() []byte {
sum := sha256.Sum256(info.Key)
return sum[:]
}
var DefaultLogs = []LogInfo{
{
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfahLEimAoz2t01p3uMziiLOl/fHTDM0YDOhBRuiBARsV4UvxG2LdNgoIGLrtCzWE0J5APC2em4JlvR8EEEFMoA=="),
Url: "ct.googleapis.com/pilot",
MMD: 86400,
},
{
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1/TMabLkDpCjiupacAlP7xNi0I1JYP8bQFAHDG1xhtolSY1l4QgNRzRrvSe8liE+NPWHdjGxfx3JhTsN9x8/6Q=="),
Url: "ct.googleapis.com/aviator",
MMD: 86400,
},
{
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAkbFvhu7gkAW6MHSrBlpE1n4+HCFRkC5OLAjgqhkTH+/uzSfSl8ois8ZxAD2NgaTZe1M9akhYlrYkes4JECs6A=="),
Url: "ct1.digicert-ct.com/log",
MMD: 86400,
},
{
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIFsYyDzBi7MxCAC/oJBXK7dHjG+1aLCOkHjpoHPqTyghLpzA9BYbqvnV16mAw04vUjyYASVGJCUoI3ctBcJAeg=="),
Url: "ct.googleapis.com/rocketeer",
MMD: 86400,
},
{
Key: mustDecodeBase64("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv7UIYZopMgTTJWPp2IXhhuAf1l6a9zM7gBvntj5fLaFm9pVKhKYhVnno94XuXeN8EsDgiSIJIj66FpUGvai5samyetZhLocRuXhAiXXbDNyQ4KR51tVebtEq2zT0mT9liTtGwiksFQccyUsaVPhsHq9gJ2IKZdWauVA2Fm5x9h8B9xKn/L/2IaMpkIYtd967TNTP/dLPgixN1PLCLaypvurDGSVDsuWabA3FHKWL9z8wr7kBkbdpEhLlg2H+NAC+9nGKx+tQkuhZ/hWR65aX+CNUPy2OB9/u2rNPyDydb988LENXoUcMkQT0dU3aiYGkFAY0uZjD2vH97TM20xYtNQIDAQAB"),
Url: "ctserver.cnnic.cn",
MMD: 86400,
},
{
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAETtK8v7MICve56qTHHDhhBOuV4IlUaESxZryCfk9QbG9co/CqPvTsgPDbCpp6oFtyAHwlDhnvr7JijXRD9Cb2FA=="),
Url: "ct.googleapis.com/icarus",
MMD: 86400,
},
{
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEmyGDvYXsRJsNyXSrYc9DjHsIa2xzb4UR7ZxVoV6mrc9iZB7xjI6+NrOiwH+P/xxkRmOFG6Jel20q37hTh58rA=="),
Url: "ct.googleapis.com/skydiver",
MMD: 86400,
},
{
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEjicnerZVCXTrbEuUhGW85BXx6lrYfA43zro/bAna5ymW00VQb94etBzSg4j/KS/Oqf/fNN51D8DMGA2ULvw3AQ=="),
Url: "ctlog-gen2.api.venafi.com",
MMD: 86400,
},
{
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7+R9dC4VFbbpuyOL+yy14ceAmEf7QGlo/EmtYU6DRzwat43f/3swtLr/L8ugFOOt1YU/RFmMjGCL17ixv66MZw=="),
Url: "mammoth.ct.comodo.com",
MMD: 86400,
},
{
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8m/SiQ8/xfiHHqtls9m7FyOMBg4JVZY9CgiixXGz0akvKD6DEL8S0ERmFe9U4ZiA0M4kbT5nmuk3I85Sk4bagA=="),
Url: "sabre.ct.comodo.com",
MMD: 86400,
},
{
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEI3MQm+HzXvaYa2mVlhB4zknbtAT8cSxakmBoJcBKGqGwYS0bhxSpuvABM1kdBTDpQhXnVdcq+LSiukXJRpGHVg=="),
Url: "ct.googleapis.com/logs/argon2019",
MMD: 86400,
CertExpiryBegin: makeTime(1546300800),
CertExpiryEnd: makeTime(1577836800),
},
{
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6Tx2p1yKY4015NyIYvdrk36es0uAc1zA4PQ+TGRY+3ZjUTIYY9Wyu+3q/147JG4vNVKLtDWarZwVqGkg6lAYzA=="),
Url: "ct.googleapis.com/logs/argon2020",
MMD: 86400,
CertExpiryBegin: makeTime(1577836800),
CertExpiryEnd: makeTime(1609459200),
},
{
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAETeBmZOrzZKo4xYktx9gI2chEce3cw/tbr5xkoQlmhB18aKfsxD+MnILgGNl0FOm0eYGilFVi85wLRIOhK8lxKw=="),
Url: "ct.googleapis.com/logs/argon2021",
MMD: 86400,
CertExpiryBegin: makeTime(1609459200),
CertExpiryEnd: makeTime(1640995200),
},
{
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEzF05L2a4TH/BLgOhNKPoioYCrkoRxvcmajeb8Dj4XQmNY+gxa4Zmz3mzJTwe33i0qMVp+rfwgnliQ/bM/oFmhA=="),
Url: "ct2.digicert-ct.com/log",
MMD: 86400,
},
{
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEkZHz1v5r8a9LmXSMegYZAg4UW+Ug56GtNfJTDNFZuubEJYgWf4FcC5D+ZkYwttXTDSo4OkanG9b3AI4swIQ28g=="),
Url: "ct.cloudflare.com/logs/nimbus2019",
MMD: 86400,
CertExpiryBegin: makeTime(1546300800),
CertExpiryEnd: makeTime(1577836800),
},
{
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE01EAhx4o0zPQrXTcYjgCt4MVFsT0Pwjzb1RwrM0lhWDlxAYPP6/gyMCXNkOn/7KFsjL7rwk78tHMpY8rXn8AYg=="),
Url: "ct.cloudflare.com/logs/nimbus2020",
MMD: 86400,
CertExpiryBegin: makeTime(1577836800),
CertExpiryEnd: makeTime(1609459200),
},
{
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExpon7ipsqehIeU1bmpog9TFo4Pk8+9oN8OYHl1Q2JGVXnkVFnuuvPgSo2Ep+6vLffNLcmEbxOucz03sFiematg=="),
Url: "ct.cloudflare.com/logs/nimbus2021",
MMD: 86400,
CertExpiryBegin: makeTime(1609459200),
CertExpiryEnd: makeTime(1640995200),
},
{
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAESLJHTlAycmJKDQxIv60pZG8g33lSYxYpCi5gteI6HLevWbFVCdtZx+m9b+0LrwWWl/87mkNN6xE0M4rnrIPA/w=="),
Url: "ct.cloudflare.com/logs/nimbus2022",
MMD: 86400,
CertExpiryBegin: makeTime(1640995200),
CertExpiryEnd: makeTime(1672531200),
},
{
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEi/8tkhjLRp0SXrlZdTzNkTd6HqmcmXiDJz3fAdWLgOhjmv4mohvRhwXul9bgW0ODgRwC9UGAgH/vpGHPvIS1qA=="),
Url: "ct.cloudflare.com/logs/nimbus2023",
MMD: 86400,
CertExpiryBegin: makeTime(1672531200),
CertExpiryEnd: makeTime(1704067200),
},
{
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEkZd/ow8X+FSVWAVSf8xzkFohcPph/x6pS1JHh7g1wnCZ5y/8Hk6jzJxs6t3YMAWz2CPd4VkCdxwKexGhcFxD9A=="),
Url: "yeti2019.ct.digicert.com/log",
MMD: 86400,
CertExpiryBegin: makeTime(1546300800),
CertExpiryEnd: makeTime(1577836800),
},
{
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEURAG+Zo0ac3n37ifZKUhBFEV6jfcCzGIRz3tsq8Ca9BP/5XUHy6ZiqsPaAEbVM0uI3Tm9U24RVBHR9JxDElPmg=="),
Url: "yeti2020.ct.digicert.com/log",
MMD: 86400,
CertExpiryBegin: makeTime(1577836800),
CertExpiryEnd: makeTime(1609459200),
},
{
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6J4EbcpIAl1+AkSRsbhoY5oRTj3VoFfaf1DlQkfi7Rbe/HcjfVtrwN8jaC+tQDGjF+dqvKhWJAQ6Q6ev6q9Mew=="),
Url: "yeti2021.ct.digicert.com/log",
MMD: 86400,
CertExpiryBegin: makeTime(1609459200),
CertExpiryEnd: makeTime(1640995200),
},
{
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEn/jYHd77W1G1+131td5mEbCdX/1v/KiYW5hPLcOROvv+xA8Nw2BDjB7y+RGyutD2vKXStp/5XIeiffzUfdYTJg=="),
Url: "yeti2022.ct.digicert.com/log",
MMD: 86400,
CertExpiryBegin: makeTime(1640995200),
CertExpiryEnd: makeTime(1672531200),
},
{
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4hHIyMVIrR9oShgbQMYEk8WX1lmkfFKB448Gn93KbsZnnwljDHY6MQqEnWfKGgMOq0gh3QK48c5ZB3UKSIFZ4g=="),
Url: "nessie2020.ct.digicert.com/log",
MMD: 86400,
CertExpiryBegin: makeTime(1577836800),
CertExpiryEnd: makeTime(1609459200),
},
{
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9o7AiwrbGBIX6Lnc47I6OfLMdZnRzKoP5u072nBi6vpIOEooktTi1gNwlRPzGC2ySGfuc1xLDeaA/wSFGgpYFg=="),
Url: "nessie2021.ct.digicert.com/log",
MMD: 86400,
CertExpiryBegin: makeTime(1609459200),
CertExpiryEnd: makeTime(1640995200),
},
{
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEJyTdaAMoy/5jvg4RR019F2ihEV1McclBKMe2okuX7MCv/C87v+nxsfz1Af+p+0lADGMkmNd5LqZVqxbGvlHYcQ=="),
Url: "nessie2022.ct.digicert.com/log",
MMD: 86400,
CertExpiryBegin: makeTime(1640995200),
CertExpiryEnd: makeTime(1672531200),
},
{
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEXu8iQwSCRSf2CbITGpUpBtFVt8+I0IU0d1C36Lfe1+fbwdaI0Z5FktfM2fBoI1bXBd18k2ggKGYGgdZBgLKTg=="),
Url: "nessie2023.ct.digicert.com/log",
MMD: 86400,
CertExpiryBegin: makeTime(1672531200),
CertExpiryEnd: makeTime(1704067200),
},
{
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfQ0DsdWYitzwFTvG3F4Nbj8Nv5XIVYzQpkyWsU4nuSYlmcwrAp6m092fsdXEw6w1BAeHlzaqrSgNfyvZaJ9y0Q=="),
Url: "yeti2023.ct.digicert.com/log",
MMD: 86400,
CertExpiryBegin: makeTime(1672531200),
CertExpiryEnd: makeTime(1704067200),
},
{
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeIPc6fGmuBg6AJkv/z7NFckmHvf/OqmjchZJ6wm2qN200keRDg352dWpi7CHnSV51BpQYAj1CQY5JuRAwrrDwg=="),
Url: "ct.googleapis.com/logs/argon2022",
MMD: 86400,
CertExpiryBegin: makeTime(1640995200),
CertExpiryEnd: makeTime(1672531200),
},
{
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0JCPZFJOQqyEti5M8j13ALN3CAVHqkVM4yyOcKWCu2yye5yYeqDpEXYoALIgtM3TmHtNlifmt+4iatGwLpF3eA=="),
Url: "ct.googleapis.com/logs/argon2023",
MMD: 86400,
CertExpiryBegin: makeTime(1672531200),
CertExpiryEnd: makeTime(1704067200),
},
{
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEchY+C+/vzj5g3ZXLY3q5qY1Kb2zcYYCmRV4vg6yU84WI0KV00HuO/8XuQqLwLZPjwtCymeLhQunSxgAnaXSuzg=="),
Url: "ct.googleapis.com/logs/xenon2023",
MMD: 86400,
CertExpiryBegin: makeTime(1672531200),
CertExpiryEnd: makeTime(1704067200),
},
{
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfzb42Zdr/h7hgqgDCo1vrNJqGqbcUvJGJEER9DDqp19W/wFSB0l166hD+U5cAXchpH8ZkBNUuvOHS0OnJ4oJrQ=="),
Url: "oak.ct.letsencrypt.org/2020",
MMD: 86400,
CertExpiryBegin: makeTime(1577836800),
CertExpiryEnd: makeTime(1609977600),
},
{
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAELsYzGMNwo8rBIlaklBIdmD2Ofn6HkfrjK0Ukz1uOIUC6Lm0jTITCXhoIdjs7JkyXnwuwYiJYiH7sE1YeKu8k9w=="),
Url: "oak.ct.letsencrypt.org/2021",
MMD: 86400,
CertExpiryBegin: makeTime(1609459200),
CertExpiryEnd: makeTime(1641513600),
},
{
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEhjyxDVIjWt5u9sB/o2S8rcGJ2pdZTGA8+IpXhI/tvKBjElGE5r3de4yAfeOPhqTqqc+o7vPgXnDgu/a9/B+RLg=="),
Url: "oak.ct.letsencrypt.org/2022",
MMD: 86400,
CertExpiryBegin: makeTime(1640995200),
CertExpiryEnd: makeTime(1673049600),
},
}
// Logs which monitor certs from distrusted roots
var UnderwaterLogs = []LogInfo{
{
Description: "Google 'Submariner' log",
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEOfifIGLUV1Voou9JLfA5LZreRLSUMOCeeic8q3Dw0fpRkGMWV0Gtq20fgHQweQJeLVmEByQj9p81uIW4QkWkTw=="),
Url: "ct.googleapis.com/submariner",
MMD: 86400,
},
}
// Logs which accept submissions from anyone
var OpenLogs = []LogInfo{
{
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfahLEimAoz2t01p3uMziiLOl/fHTDM0YDOhBRuiBARsV4UvxG2LdNgoIGLrtCzWE0J5APC2em4JlvR8EEEFMoA=="),
Url: "ct.googleapis.com/pilot",
MMD: 86400,
},
{
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIFsYyDzBi7MxCAC/oJBXK7dHjG+1aLCOkHjpoHPqTyghLpzA9BYbqvnV16mAw04vUjyYASVGJCUoI3ctBcJAeg=="),
Url: "ct.googleapis.com/rocketeer",
MMD: 86400,
},
{
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAELPXCMfVjQ2oWSgrewu4fIW4Sfh3lco90CwKZ061pvAI1eflh6c8ACE90pKM0muBDHCN+j0HV7scco4KKQPqq4A=="),
Url: "dodo.ct.comodo.com",
MMD: 86400,
},
}
func mustDecodeBase64(str string) []byte {
bytes, err := base64.StdEncoding.DecodeString(str)
if err != nil {
panic("MustDecodeBase64: " + err.Error())
}
return bytes
}
func makeTime(seconds int64) *time.Time {
t := time.Unix(seconds, 0)
return &t
}

View File

@ -19,6 +19,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"log" "log"
"strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
@ -315,7 +316,7 @@ func NewScanner(logUri string, logId []byte, publicKey crypto.PublicKey, opts *S
scanner.LogUri = logUri scanner.LogUri = logUri
scanner.LogId = logId scanner.LogId = logId
scanner.publicKey = publicKey scanner.publicKey = publicKey
scanner.logClient = client.New(logUri) scanner.logClient = client.New(strings.TrimRight(logUri, "/"))
scanner.opts = *opts scanner.opts = *opts
return &scanner return &scanner
} }