aboutsummaryrefslogtreecommitdiff
path: root/pkg/policy/wildcard.go
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/policy/wildcard.go')
-rw-r--r--pkg/policy/wildcard.go83
1 files changed, 83 insertions, 0 deletions
diff --git a/pkg/policy/wildcard.go b/pkg/policy/wildcard.go
new file mode 100644
index 0000000..58b0d17
--- /dev/null
+++ b/pkg/policy/wildcard.go
@@ -0,0 +1,83 @@
+package policy
+
+import (
+ "encoding/json"
+ "fmt"
+ "strings"
+ "time"
+
+ "rgdd.se/silent-ct/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
+}