Verify STH signatures
This commit is contained in:
parent
4b304fd192
commit
9558efc955
|
@ -7,7 +7,7 @@ import (
|
|||
"os"
|
||||
"bytes"
|
||||
"os/user"
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"sync"
|
||||
"strings"
|
||||
"path/filepath"
|
||||
|
@ -20,7 +20,8 @@ var batchSize = flag.Int("batch_size", 1000, "Max number of entries to request a
|
|||
var numWorkers = flag.Int("num_workers", 2, "Number of concurrent matchers")
|
||||
var parallelFetch = flag.Int("parallel_fetch", 2, "Number of concurrent GetEntries fetches")
|
||||
var script = flag.String("script", "", "Script to execute when a matching certificate is found")
|
||||
var logsFilename = flag.String("logs", "", "File containing log URLs")
|
||||
var logsFilename = flag.String("logs", "", "JSON file containing log URLs")
|
||||
var underwater = flag.Bool("underwater", false, "Monitor certificates from distrusted CAs")
|
||||
var noSave = flag.Bool("no_save", false, "Do not save a copy of matching certificates")
|
||||
var verbose = flag.Bool("verbose", false, "Be verbose")
|
||||
var allTime = flag.Bool("all_time", false, "Scan certs from all time, not just since last scan")
|
||||
|
@ -28,19 +29,6 @@ var stateDir string
|
|||
|
||||
var printMutex sync.Mutex
|
||||
|
||||
var defaultLogs = []string{
|
||||
"https://log.certly.io",
|
||||
"https://ct1.digicert-ct.com/log",
|
||||
"https://ct.googleapis.com/aviator",
|
||||
"https://ct.googleapis.com/pilot",
|
||||
"https://ct.googleapis.com/rocketeer",
|
||||
"https://ct.izenpe.com",
|
||||
"https://ct.ws.symantec.com",
|
||||
"https://vega.ws.symantec.com",
|
||||
"https://ctlog.api.venafi.com",
|
||||
"https://ct.wosign.com",
|
||||
}
|
||||
|
||||
func isRoot () bool {
|
||||
return os.Geteuid() == 0
|
||||
}
|
||||
|
@ -97,7 +85,7 @@ func defangLogUri (logUri string) string {
|
|||
func Main (argStateDir string, processCallback ctwatch.ProcessCallback) {
|
||||
stateDir = argStateDir
|
||||
|
||||
var logs []string
|
||||
var logs []ctwatch.LogInfo
|
||||
if *logsFilename != "" {
|
||||
logFile, err := os.Open(*logsFilename)
|
||||
if err != nil {
|
||||
|
@ -105,16 +93,16 @@ func Main (argStateDir string, processCallback ctwatch.ProcessCallback) {
|
|||
os.Exit(3)
|
||||
}
|
||||
defer logFile.Close()
|
||||
scanner := bufio.NewScanner(logFile)
|
||||
for scanner.Scan() {
|
||||
logs = append(logs, scanner.Text())
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%s: Error reading logs file: %s: %s\n", os.Args[0], *logsFilename, err)
|
||||
var logFileObj ctwatch.LogInfoFile
|
||||
if err := json.NewDecoder(logFile).Decode(&logFileObj); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%s: Error decoding logs file: %s: %s\n", os.Args[0], *logsFilename, err)
|
||||
os.Exit(3)
|
||||
}
|
||||
logs = logFileObj.Logs
|
||||
} else if *underwater {
|
||||
logs = ctwatch.UnderwaterLogs
|
||||
} else {
|
||||
logs = defaultLogs
|
||||
logs = ctwatch.DefaultLogs
|
||||
}
|
||||
|
||||
if err := os.Mkdir(stateDir, 0777); err != nil && !os.IsExist(err) {
|
||||
|
@ -131,7 +119,13 @@ func Main (argStateDir string, processCallback ctwatch.ProcessCallback) {
|
|||
|
||||
exitCode := 0
|
||||
|
||||
for _, logUri := range logs {
|
||||
for _, logInfo := range logs {
|
||||
logUri := logInfo.FullURI()
|
||||
logKey, err := logInfo.ParsedPublicKey()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%s: %s: Bad public key: %s\n", os.Args[0], logUri, err)
|
||||
os.Exit(3)
|
||||
}
|
||||
stateFilename := filepath.Join(stateDir, "sths", defangLogUri(logUri))
|
||||
prevSTH, err := ctwatch.ReadStateFile(stateFilename)
|
||||
if err != nil {
|
||||
|
@ -146,7 +140,7 @@ func Main (argStateDir string, processCallback ctwatch.ProcessCallback) {
|
|||
ParallelFetch: *parallelFetch,
|
||||
Quiet: !*verbose,
|
||||
}
|
||||
scanner := ctwatch.NewScanner(logUri, logClient, opts)
|
||||
scanner := ctwatch.NewScanner(logUri, logKey, logClient, opts)
|
||||
|
||||
latestSTH, err := scanner.GetSTH()
|
||||
if err != nil {
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
package ctwatch
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
)
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
func (info *LogInfo) FullURI () string {
|
||||
return "https://" + info.Url
|
||||
}
|
||||
|
||||
func (info *LogInfo) ParsedPublicKey () (crypto.PublicKey, error) {
|
||||
return x509.ParsePKIXPublicKey(info.Key)
|
||||
}
|
||||
|
||||
var DefaultLogs = []LogInfo{
|
||||
{
|
||||
Description: "Google 'Pilot' log",
|
||||
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfahLEimAoz2t01p3uMziiLOl/fHTDM0YDOhBRuiBARsV4UvxG2LdNgoIGLrtCzWE0J5APC2em4JlvR8EEEFMoA=="),
|
||||
Url: "ct.googleapis.com/pilot",
|
||||
MMD: 86400,
|
||||
},
|
||||
{
|
||||
Description: "Google 'Aviator' log",
|
||||
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1/TMabLkDpCjiupacAlP7xNi0I1JYP8bQFAHDG1xhtolSY1l4QgNRzRrvSe8liE+NPWHdjGxfx3JhTsN9x8/6Q=="),
|
||||
Url: "ct.googleapis.com/aviator",
|
||||
MMD: 86400,
|
||||
},
|
||||
{
|
||||
Description: "DigiCert Log Server",
|
||||
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAkbFvhu7gkAW6MHSrBlpE1n4+HCFRkC5OLAjgqhkTH+/uzSfSl8ois8ZxAD2NgaTZe1M9akhYlrYkes4JECs6A=="),
|
||||
Url: "ct1.digicert-ct.com/log",
|
||||
MMD: 86400,
|
||||
},
|
||||
{
|
||||
Description: "Google 'Rocketeer' log",
|
||||
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIFsYyDzBi7MxCAC/oJBXK7dHjG+1aLCOkHjpoHPqTyghLpzA9BYbqvnV16mAw04vUjyYASVGJCUoI3ctBcJAeg=="),
|
||||
Url: "ct.googleapis.com/rocketeer",
|
||||
MMD: 86400,
|
||||
},
|
||||
{
|
||||
Description: "Certly.IO log",
|
||||
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAECyPLhWKYYUgEc+tUXfPQB4wtGS2MNvXrjwFCCnyYJifBtd2Sk7Cu+Js9DNhMTh35FftHaHu6ZrclnNBKwmbbSA=="),
|
||||
Url: "log.certly.io",
|
||||
MMD: 86400,
|
||||
},
|
||||
{
|
||||
Description: "Izenpe log",
|
||||
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEJ2Q5DC3cUBj4IQCiDu0s6j51up+TZAkAEcQRF6tczw90rLWXkJMAW7jr9yc92bIKgV8vDXU4lDeZHvYHduDuvg=="),
|
||||
Url: "ct.izenpe.com",
|
||||
MMD: 86400,
|
||||
},
|
||||
{
|
||||
Description: "Symantec log",
|
||||
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEluqsHEYMG1XcDfy1lCdGV0JwOmkY4r87xNuroPS2bMBTP01CEDPwWJePa75y9CrsHEKqAy8afig1dpkIPSEUhg=="),
|
||||
Url: "ct.ws.symantec.com",
|
||||
MMD: 86400,
|
||||
},
|
||||
{
|
||||
Description: "Venafi log",
|
||||
Key: mustDecodeBase64("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAolpIHxdSlTXLo1s6H1OCdpSj/4DyHDc8wLG9wVmLqy1lk9fz4ATVmm+/1iN2Nk8jmctUKK2MFUtlWXZBSpym97M7frGlSaQXUWyA3CqQUEuIJOmlEjKTBEiQAvpfDjCHjlV2Be4qTM6jamkJbiWtgnYPhJL6ONaGTiSPm7Byy57iaz/hbckldSOIoRhYBiMzeNoA0DiRZ9KmfSeXZ1rB8y8X5urSW+iBzf2SaOfzBvDpcoTuAaWx2DPazoOl28fP1hZ+kHUYvxbcMjttjauCFx+JII0dmuZNIwjfeG/GBb9frpSX219k1O4Wi6OEbHEr8at/XQ0y7gTikOxBn/s5wQIDAQAB"),
|
||||
Url: "ctlog.api.venafi.com",
|
||||
MMD: 86400,
|
||||
},
|
||||
{
|
||||
Description: "Symantec 'Vega' log",
|
||||
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6pWeAv/u8TNtS4e8zf0ZF2L/lNPQWQc/Ai0ckP7IRzA78d0NuBEMXR2G3avTK0Zm+25ltzv9WWis36b4ztIYTQ=="),
|
||||
Url: "vega.ws.symantec.com",
|
||||
MMD: 86400,
|
||||
},
|
||||
{
|
||||
Description: "WoSign CT Log Server",
|
||||
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1+wvK3VPN7yjQ7qLZWY8fWrlDCqmwuUm/gx9TnzwOrzi0yLcAdAfbkOcXG6DrZwV9sSNYLUdu6NiaX7rp6oBmw=="),
|
||||
Url: "ct.wosign.com",
|
||||
MMD: 86400,
|
||||
},
|
||||
}
|
||||
|
||||
// Logs which monitor certs from distrusted roots
|
||||
var UnderwaterLogs = []LogInfo{
|
||||
{
|
||||
Description: "Google 'Submariner' log",
|
||||
Key: mustDecodeBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEOfifIGLUV1Voou9JLfA5LZreRLSUMOCeeic8q3Dw0fpRkGMWV0Gtq20fgHQweQJeLVmEByQj9p81uIW4QkWkTw=="),
|
||||
Url: "ct.googleapis.com/submariner",
|
||||
MMD: 86400,
|
||||
},
|
||||
}
|
||||
|
||||
func mustDecodeBase64 (str string) []byte {
|
||||
bytes, err := base64.StdEncoding.DecodeString(str)
|
||||
if err != nil {
|
||||
panic("MustDecodeBase64: " + err.Error())
|
||||
}
|
||||
return bytes
|
||||
}
|
18
scanner.go
18
scanner.go
|
@ -7,6 +7,8 @@ import (
|
|||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"crypto"
|
||||
"errors"
|
||||
|
||||
"github.com/google/certificate-transparency/go"
|
||||
"github.com/google/certificate-transparency/go/client"
|
||||
|
@ -44,6 +46,9 @@ type Scanner struct {
|
|||
// Base URI of CT log
|
||||
LogUri string
|
||||
|
||||
// Public key of the log
|
||||
publicKey crypto.PublicKey
|
||||
|
||||
// Client used to talk to the CT log instance
|
||||
logClient *client.LogClient
|
||||
|
||||
|
@ -168,7 +173,15 @@ func (s *Scanner) GetSTH() (*ct.SignedTreeHead, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO: Verify STH signature
|
||||
if s.publicKey != nil {
|
||||
verifier, err := ct.NewSignatureVerifier(s.publicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := verifier.VerifySTHSignature(*latestSth); err != nil {
|
||||
return nil, errors.New("STH signature is invalid: " + err.Error())
|
||||
}
|
||||
}
|
||||
return latestSth, nil
|
||||
}
|
||||
|
||||
|
@ -255,9 +268,10 @@ func (s *Scanner) Scan(startIndex int64, endIndex int64, processCert ProcessCall
|
|||
|
||||
// Creates a new Scanner instance using |client| to talk to the log, and taking
|
||||
// configuration options from |opts|.
|
||||
func NewScanner(logUri string, client *client.LogClient, opts ScannerOptions) *Scanner {
|
||||
func NewScanner(logUri string, publicKey crypto.PublicKey, client *client.LogClient, opts ScannerOptions) *Scanner {
|
||||
var scanner Scanner
|
||||
scanner.LogUri = logUri
|
||||
scanner.publicKey = publicKey
|
||||
scanner.logClient = client
|
||||
scanner.opts = opts
|
||||
return &scanner
|
||||
|
|
Loading…
Reference in New Issue