support filtering
This commit is contained in:
parent
cd4d796a7c
commit
6f019acd7b
|
@ -99,6 +99,16 @@ func defaultWatchListPathIfExists() string {
|
|||
return ""
|
||||
}
|
||||
}
|
||||
func defaultKeyListPath() string {
|
||||
return filepath.Join(defaultConfigDir(), "keylist")
|
||||
}
|
||||
func defaultKeyListPathIfExists() string {
|
||||
if fileExists(defaultKeyListPath()) {
|
||||
return defaultKeyListPath()
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
func defaultScriptDir() string {
|
||||
return filepath.Join(defaultConfigDir(), "hooks.d")
|
||||
}
|
||||
|
@ -124,6 +134,15 @@ func readWatchListFile(filename string) (monitor.WatchList, error) {
|
|||
return monitor.ReadWatchList(file)
|
||||
}
|
||||
|
||||
func readKeyListFile(filename string) (monitor.KeyList, error) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, simplifyError(err)
|
||||
}
|
||||
defer file.Close()
|
||||
return monitor.ReadKeyList(file)
|
||||
}
|
||||
|
||||
func readEmailFile(filename string) ([]string, error) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
|
@ -166,6 +185,7 @@ func main() {
|
|||
verbose bool
|
||||
version bool
|
||||
watchlist string
|
||||
keylist string
|
||||
}
|
||||
flag.IntVar(&flags.batchSize, "batch_size", 1000, "Max number of entries to request per call to get-entries (advanced)")
|
||||
flag.Func("email", "Email address to contact when matching certificate is discovered (repeatable)", appendFunc(&flags.email))
|
||||
|
@ -179,6 +199,7 @@ func main() {
|
|||
flag.BoolVar(&flags.verbose, "verbose", false, "Be verbose")
|
||||
flag.BoolVar(&flags.version, "version", false, "Print version and exit")
|
||||
flag.StringVar(&flags.watchlist, "watchlist", defaultWatchListPathIfExists(), "File containing domain names to watch")
|
||||
flag.StringVar(&flags.keylist, "keylist", defaultKeyListPathIfExists(), "File containing known key information")
|
||||
flag.Parse()
|
||||
|
||||
if flags.version {
|
||||
|
@ -242,6 +263,15 @@ func main() {
|
|||
config.WatchList = watchlist
|
||||
}
|
||||
|
||||
if flags.keylist != "" {
|
||||
keylist, err := readKeyListFile(flags.keylist)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%s: error reading keylist from %q: %s\n", programName, flags.keylist, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
config.KeyList = keylist
|
||||
}
|
||||
|
||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||
defer stop()
|
||||
|
||||
|
|
|
@ -115,6 +115,11 @@ You can use Cert Spotter to detect:
|
|||
certspotter reads the watch list only when starting up, so you must restart
|
||||
certspotter if you change it.
|
||||
|
||||
-keylist *PATH*
|
||||
|
||||
: File containing known key information, one per line. A line consist of a
|
||||
identifier and the sha256 of the public key separated by semi colon
|
||||
|
||||
# NOTIFICATIONS
|
||||
|
||||
When certspotter detects a certificate matching your watchlist, or encounters
|
||||
|
|
|
@ -18,6 +18,7 @@ type Config struct {
|
|||
State StateProvider
|
||||
StartAtEnd bool
|
||||
WatchList WatchList
|
||||
KeyList KeyList
|
||||
Verbose bool
|
||||
HealthCheckInterval time.Duration
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
|
||||
type DiscoveredCert struct {
|
||||
WatchItem WatchItem
|
||||
KeyItem KeyItem
|
||||
LogEntry *LogEntry
|
||||
Info *certspotter.CertInfo
|
||||
Chain []ct.ASN1Cert // first entry is the leaf certificate or precertificate
|
||||
|
@ -172,5 +173,9 @@ func certNotificationText(cert *DiscoveredCert, paths *certPaths) string {
|
|||
}
|
||||
|
||||
func certNotificationSummary(cert *DiscoveredCert) string {
|
||||
return fmt.Sprintf("Certificate Discovered for %s", cert.WatchItem)
|
||||
keytext := ""
|
||||
if cert.KeyItem.String() != "" {
|
||||
keytext = fmt.Sprintf(" (known as %s)", cert.KeyItem.String())
|
||||
}
|
||||
return fmt.Sprintf("Certificate Discovered for %s%s", cert.WatchItem, keytext)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
// Copyright (C) 2016, 2023 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 monitor
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/hex"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
"software.sslmate.com/src/certspotter"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type KeyItem struct {
|
||||
domain string
|
||||
keyinfo string
|
||||
}
|
||||
|
||||
type KeyList []KeyItem
|
||||
|
||||
func ParseKeyItem(str string) (KeyItem, error) {
|
||||
fields := strings.Split(str, ";")
|
||||
if len(fields) == 0 {
|
||||
return KeyItem{}, fmt.Errorf("empty domain")
|
||||
}
|
||||
if len(fields) == 1 {
|
||||
return KeyItem{}, fmt.Errorf("empty key info")
|
||||
}
|
||||
return KeyItem{
|
||||
domain: fields[0],
|
||||
keyinfo: fields[1],
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ReadKeyList(reader io.Reader) (KeyList, error) {
|
||||
items := make(KeyList, 0, 50)
|
||||
scanner := bufio.NewScanner(reader)
|
||||
lineNo := 0
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
lineNo++
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
item, err := ParseKeyItem(line)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w on line %d", err, lineNo)
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
return items, scanner.Err()
|
||||
}
|
||||
|
||||
func (item KeyItem) String() string {
|
||||
return item.domain
|
||||
}
|
||||
|
||||
func (list KeyList) Matches(certInfo *certspotter.CertInfo) (bool, KeyItem) {
|
||||
|
||||
for _, item := range list {
|
||||
// better would be to convert the input and compare the bytes (no uppercase/lower-case issue)
|
||||
pub := sha256.Sum256(certInfo.TBS.PublicKey.FullBytes)
|
||||
sum := hex.EncodeToString(pub[:])
|
||||
if sum == item.keyinfo {
|
||||
return true, item
|
||||
}
|
||||
}
|
||||
return false, KeyItem{}
|
||||
}
|
|
@ -91,11 +91,13 @@ func processCertificate(ctx context.Context, config *Config, entry *LogEntry, ce
|
|||
if !matched {
|
||||
return nil
|
||||
}
|
||||
matched, keyItem := config.KeyList.Matches(certInfo)
|
||||
|
||||
cert := &DiscoveredCert{
|
||||
WatchItem: watchItem,
|
||||
LogEntry: entry,
|
||||
Info: certInfo,
|
||||
KeyItem: keyItem,
|
||||
Chain: chain,
|
||||
TBSSHA256: sha256.Sum256(certInfo.TBS.Raw),
|
||||
SHA256: sha256.Sum256(chain[0]),
|
||||
|
|
Loading…
Reference in New Issue