package index import ( "encoding/json" "fmt" "time" "github.com/google/certificate-transparency-go/x509" "rgdd.se/silentct/pkg/crtutil" ) type CertificateID string func (crtID *CertificateID) Set(crt x509.Certificate) { *crtID = CertificateID(crtutil.UniqueID(crt)) } type CertificateInfo struct { ObservedAt time.Time `json:"observed_at"` StoredAt string `json:"stored_at"` } // index is an in-memory index of certificates type index struct { Alerting map[CertificateID][]CertificateInfo `json:"alerting"` // Certificates that were not marked as "good" on time Legitimate map[CertificateID][]CertificateInfo `json:"legitimate"` // Certificates that are considered "good" Pending map[CertificateID][]CertificateInfo `json:"pending"` // Certificates that have yet to be marked as "good" } func newIndex() index { return index{ Alerting: make(map[CertificateID][]CertificateInfo), Legitimate: make(map[CertificateID][]CertificateInfo), Pending: make(map[CertificateID][]CertificateInfo), } } func (ix *index) JSONUnmarshal(b []byte) error { type internal index if err := json.Unmarshal(b, (*internal)(ix)); err != nil { return err } for i, m := range []map[CertificateID][]CertificateInfo{ix.Alerting, ix.Legitimate, ix.Pending} { if m == nil { return fmt.Errorf("dictionary named %q is not in the index", []string{"alerting", "legitimate", "pending"}[i]) } } return nil } func (ix *index) triggerAlerts(delay time.Duration) []CertificateInfo { var alerts []CertificateInfo for key, certInfos := range ix.Pending { certInfo := certInfos[0] if time.Since(certInfo.ObservedAt) < delay { continue } alerts = append(alerts, certInfo) ix.Alerting[key] = certInfos delete(ix.Pending, key) } return alerts } func (ix *index) addChain(crtID CertificateID, path string) bool { if _, ok := ix.Legitimate[crtID]; ok { return false // we already marked this certificate as "good" } entry := CertificateInfo{ObservedAt: time.Now(), StoredAt: path} crtInfos := []CertificateInfo{entry} if v, ok := ix.Alerting[crtID]; ok { crtInfos = append(crtInfos, v...) delete(ix.Alerting, crtID) // no longer alerting } else if v, ok := ix.Pending[crtID]; ok { crtInfos = append(crtInfos, v...) delete(ix.Pending, crtID) // no longer pending } ix.Legitimate[crtID] = crtInfos return true // index updated such that this certificate is marked as "good" } func (ix *index) addEntry(crtID CertificateID, path string) bool { crtInfo := CertificateInfo{ObservedAt: time.Now(), StoredAt: path} if _, ok := ix.Legitimate[crtID]; ok { return add(ix.Legitimate, crtID, crtInfo) } else if _, ok := ix.Alerting[crtID]; ok { return add(ix.Alerting, crtID, crtInfo) } return add(ix.Pending, crtID, crtInfo) } func add(m map[CertificateID][]CertificateInfo, key CertificateID, value CertificateInfo) bool { crtInfos, ok := m[key] if !ok { m[key] = []CertificateInfo{value} return true } for _, crtInfo := range crtInfos { if value.StoredAt == crtInfo.StoredAt { return false // duplicate } } crtInfos = append(crtInfos, value) m[key] = crtInfos return true }