aboutsummaryrefslogtreecommitdiff
path: root/pkg/policy/wildcard.go
blob: c67e1d9d061c7dcfecb24ed346f2f12a3726de72 (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
package policy

import (
	"encoding/json"
	"fmt"
	"strings"
	"time"

	"rgdd.se/silentct/pkg/crtutil"
)

// Wildcards implement the monitor.Matcher interface for a list of wildcards.
//
// Warning: parsing of SANs in certificates is hard.  This matcher depends on
// the parsing defined in github.com/google/certificate-transparency-go/x509.
type Wildcards []Wildcard

func (w *Wildcards) Match(leafData, extraData []byte) (bool, error) {
	crt, err := crtutil.CertificateFromLogEntry(leafData, extraData)
	if err != nil {
		return false, err
	}
	return w.match(crt.DNSNames, crt.NotAfter), nil
}

func (w *Wildcards) match(sans []string, notAfter time.Time) bool {
	for _, wildcard := range *w {
		if wildcard.Match(sans, notAfter) {
			return true
		}
	}
	return false
}

// Wildcard matches any string that ends with `Wildcard`, unless:
//
//  1. `Excludes[i] + "." + Wildcard` is a longer suffix match, or
//  2. the certificate expired before the BootstrapAt timestamp.
type Wildcard struct {
	BootstrapAt time.Time `json:"bootstrap_at"`
	Wildcard    string    `json:"wildcard"`
	Excludes    []string  `json:"excludes,omitempty"`
}

func (w *Wildcard) UnmarshalJSON(data []byte) error {
	type internal Wildcard
	if err := json.Unmarshal(data, (*internal)(w)); err != nil {
		return err
	}
	return w.Validate()
}

func (w *Wildcard) Validate() error {
	if w.BootstrapAt.IsZero() {
		return fmt.Errorf("bootstrap time is required")
	}
	if len(w.Wildcard) == 0 {
		return fmt.Errorf("wildcard is required")
	}
	return nil
}

func (w *Wildcard) Match(sans []string, expiresAt time.Time) bool {
	for _, san := range sans {
		if san == w.Wildcard {
			return w.BootstrapAt.Before(expiresAt)
		}
		if strings.HasSuffix(san, "."+w.Wildcard) && !w.exclude(san) {
			return w.BootstrapAt.Before(expiresAt)
		}
	}
	return false
}

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