Verify STH signatures
This commit is contained in:
parent
4b304fd192
commit
9558efc955
|
@ -7,7 +7,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"bytes"
|
"bytes"
|
||||||
"os/user"
|
"os/user"
|
||||||
"bufio"
|
"encoding/json"
|
||||||
"sync"
|
"sync"
|
||||||
"strings"
|
"strings"
|
||||||
"path/filepath"
|
"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 numWorkers = flag.Int("num_workers", 2, "Number of concurrent matchers")
|
||||||
var parallelFetch = flag.Int("parallel_fetch", 2, "Number of concurrent GetEntries fetches")
|
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 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 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 allTime = flag.Bool("all_time", false, "Scan certs from all time, not just since last scan")
|
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 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 {
|
func isRoot () bool {
|
||||||
return os.Geteuid() == 0
|
return os.Geteuid() == 0
|
||||||
}
|
}
|
||||||
|
@ -97,7 +85,7 @@ func defangLogUri (logUri string) string {
|
||||||
func Main (argStateDir string, processCallback ctwatch.ProcessCallback) {
|
func Main (argStateDir string, processCallback ctwatch.ProcessCallback) {
|
||||||
stateDir = argStateDir
|
stateDir = argStateDir
|
||||||
|
|
||||||
var logs []string
|
var logs []ctwatch.LogInfo
|
||||||
if *logsFilename != "" {
|
if *logsFilename != "" {
|
||||||
logFile, err := os.Open(*logsFilename)
|
logFile, err := os.Open(*logsFilename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -105,16 +93,16 @@ func Main (argStateDir string, processCallback ctwatch.ProcessCallback) {
|
||||||
os.Exit(3)
|
os.Exit(3)
|
||||||
}
|
}
|
||||||
defer logFile.Close()
|
defer logFile.Close()
|
||||||
scanner := bufio.NewScanner(logFile)
|
var logFileObj ctwatch.LogInfoFile
|
||||||
for scanner.Scan() {
|
if err := json.NewDecoder(logFile).Decode(&logFileObj); err != nil {
|
||||||
logs = append(logs, scanner.Text())
|
fmt.Fprintf(os.Stderr, "%s: Error decoding logs file: %s: %s\n", os.Args[0], *logsFilename, err)
|
||||||
}
|
|
||||||
if err := scanner.Err(); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "%s: Error reading logs file: %s: %s\n", os.Args[0], *logsFilename, err)
|
|
||||||
os.Exit(3)
|
os.Exit(3)
|
||||||
}
|
}
|
||||||
|
logs = logFileObj.Logs
|
||||||
|
} else if *underwater {
|
||||||
|
logs = ctwatch.UnderwaterLogs
|
||||||
} else {
|
} else {
|
||||||
logs = defaultLogs
|
logs = ctwatch.DefaultLogs
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.Mkdir(stateDir, 0777); err != nil && !os.IsExist(err) {
|
if err := os.Mkdir(stateDir, 0777); err != nil && !os.IsExist(err) {
|
||||||
|
@ -131,7 +119,13 @@ func Main (argStateDir string, processCallback ctwatch.ProcessCallback) {
|
||||||
|
|
||||||
exitCode := 0
|
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))
|
stateFilename := filepath.Join(stateDir, "sths", defangLogUri(logUri))
|
||||||
prevSTH, err := ctwatch.ReadStateFile(stateFilename)
|
prevSTH, err := ctwatch.ReadStateFile(stateFilename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -146,7 +140,7 @@ func Main (argStateDir string, processCallback ctwatch.ProcessCallback) {
|
||||||
ParallelFetch: *parallelFetch,
|
ParallelFetch: *parallelFetch,
|
||||||
Quiet: !*verbose,
|
Quiet: !*verbose,
|
||||||
}
|
}
|
||||||
scanner := ctwatch.NewScanner(logUri, logClient, opts)
|
scanner := ctwatch.NewScanner(logUri, logKey, logClient, opts)
|
||||||
|
|
||||||
latestSTH, err := scanner.GetSTH()
|
latestSTH, err := scanner.GetSTH()
|
||||||
if err != nil {
|
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"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
"crypto"
|
||||||
|
"errors"
|
||||||
|
|
||||||
"github.com/google/certificate-transparency/go"
|
"github.com/google/certificate-transparency/go"
|
||||||
"github.com/google/certificate-transparency/go/client"
|
"github.com/google/certificate-transparency/go/client"
|
||||||
|
@ -44,6 +46,9 @@ type Scanner struct {
|
||||||
// Base URI of CT log
|
// Base URI of CT log
|
||||||
LogUri string
|
LogUri string
|
||||||
|
|
||||||
|
// Public key of the log
|
||||||
|
publicKey crypto.PublicKey
|
||||||
|
|
||||||
// Client used to talk to the CT log instance
|
// Client used to talk to the CT log instance
|
||||||
logClient *client.LogClient
|
logClient *client.LogClient
|
||||||
|
|
||||||
|
@ -168,7 +173,15 @@ func (s *Scanner) GetSTH() (*ct.SignedTreeHead, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
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
|
// Creates a new Scanner instance using |client| to talk to the log, and taking
|
||||||
// configuration options from |opts|.
|
// 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
|
var scanner Scanner
|
||||||
scanner.LogUri = logUri
|
scanner.LogUri = logUri
|
||||||
|
scanner.publicKey = publicKey
|
||||||
scanner.logClient = client
|
scanner.logClient = client
|
||||||
scanner.opts = opts
|
scanner.opts = opts
|
||||||
return &scanner
|
return &scanner
|
||||||
|
|
Loading…
Reference in New Issue