aboutsummaryrefslogtreecommitdiff
path: root/pkg/monitor/matcher.go
blob: fa3a894fda5ac28cb53baf1f6cf8d8d0b36af90f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
package monitor

import (
	"fmt"
	"strings"

	ct "github.com/google/certificate-transparency-go"
)

type Matcher interface {
	Match(leafInput, extraData []byte) (bool, error)
}

// MatchAll matches all certificates
type MatchAll struct{}

func (m *MatchAll) Match(leafInput, extraData []byte) (bool, error) {
	return true, nil
}

// MatchWildcards matches a list of wildcards, see the MatchWildcard type
type MatchWildcards []MatchWildcard

func (m *MatchWildcards) Match(leafInput, extraData []byte) (bool, error) {
	sans, err := getSANs(ct.LeafEntry{LeafInput: leafInput, ExtraData: extraData})
	if err != nil {
		return false, err
	}
	return m.match(sans), nil
}

func (m *MatchWildcards) match(sans []string) bool {
	for _, mw := range (*m)[:] {
		if mw.match(sans) {
			return true
		}
	}
	return false
}

// MatchWildcard exclude matches for `.*<Exclude>\.<Wildcard>`, but will
// otherwise match on any `.*\.<Wildcard>` as well as SANs equal to <Wildcard>.
//
// For example, let <Wildcard> be example.org and Exclude be [foo, bar].  Then
// example.org and www.example.org would match, whereas foo.example.org,
// sub.foo.example.org, and bar.example.org. would not match.
type MatchWildcard struct {
	Wildcard string   `json:"wildcard"`
	Excludes []string `json:"excludes"`
}

func (m *MatchWildcard) match(sans []string) bool {
	for _, san := range sans {
		if san == m.Wildcard {
			return true
		}
		if strings.HasSuffix(san, "."+m.Wildcard) && !m.exclude(san) {
			return true
		}
	}
	return false
}

func (m *MatchWildcard) exclude(san string) bool {
	for _, exclude := range m.Excludes {
		suffix := exclude + "." + m.Wildcard
		if strings.HasSuffix(san, suffix) {
			return true
		}
	}
	return false
}

func getSANs(entry ct.LeafEntry) ([]string, error) {
	// Warning: here be dragons, parsing of DNS names in certificates...
	e, err := ct.LogEntryFromLeaf(0, &entry)
	if err != nil {
		return nil, fmt.Errorf("parse leaf: %v", err)
	}
	if e.Precert == nil && e.X509Cert == nil {
		return nil, fmt.Errorf("neither precertificate nor certificate in leaf")
	}
	if e.Precert != nil && e.X509Cert != nil {
		return nil, fmt.Errorf("both certificate and precertificate in leaf")
	}
	if e.Precert != nil {
		return e.Precert.TBSCertificate.DNSNames, nil
	}
	return e.X509Cert.DNSNames, nil
}