// Package index provides an index of locally stored certificates. If a method // succeeds, the index and the data that it tracks has been persisted to disk. // If a method does not succeed, restore from the persisted index on disk. package index import ( "crypto/sha256" "fmt" "time" "rgdd.se/silent-ct/internal/ioutil" "rgdd.se/silent-ct/internal/monitor" "rgdd.se/silent-ct/pkg/crtutil" ) type Config struct { PermitBootstrap bool // Create a new index if a valid one does not exist on disk yet IndexFile string // Path to an index file that can be read/written TrustDirectory string // Absolute path to an existing directory where legitimate certificates are stored MatchDirectory string // Absolute path to an existing directory where matching certificates are stored // Optional AlertDelay time.Duration // Time before alerting on certificates that are unaccounted for } type Index struct { mem index cfg Config } func New(cfg Config) (Index, error) { ix := Index{cfg: cfg} if err := ioutil.DirectoriesExist([]string{cfg.TrustDirectory, cfg.MatchDirectory}); err != nil { return Index{}, err } if err := ioutil.ReadJSON(cfg.IndexFile, &ix.mem); err != nil { if !cfg.PermitBootstrap { return Index{}, err } ix.mem = newIndex() if err := ioutil.CommitJSON(cfg.IndexFile, ix.mem); err != nil { return Index{}, err } } return ix, ix.Validate() } func (ix *Index) AddChain(node string, pem []byte) error { chain, err := crtutil.CertificateChainFromPEM(pem) if err != nil { return err } var crtID CertificateID crtID.Set(chain[0]) path := fmt.Sprintf("%s/%s-%s.pem", ix.cfg.TrustDirectory, node, crtID) if !ix.mem.addChain(crtID, path) { return nil // duplicate } if ioutil.CommitData(path, pem); err != nil { return err } return ioutil.CommitJSON(ix.cfg.IndexFile, ix.mem) } func (ix *Index) AddEntries(logID [sha256.Size]byte, entries []monitor.LogEntry) error { addEntry := func(entry monitor.LogEntry) error { crt, err := crtutil.CertificateFromLogEntry(entry.LeafData, entry.ExtraData) if err != nil { return err } var crtID CertificateID crtID.Set(crt) path := fmt.Sprintf("%s/%x-%d.json", ix.cfg.MatchDirectory, logID[:], entry.LeafIndex) if !ix.mem.addEntry(crtID, path) { return nil // duplicate } return ioutil.CommitJSON(path, entry) } for _, entry := range entries { if err := addEntry(entry); err != nil { return err } } return ioutil.CommitJSON(ix.cfg.IndexFile, ix.mem) } func (ix *Index) TriggerAlerts() ([]CertificateInfo, error) { alerts := ix.mem.triggerAlerts(ix.cfg.AlertDelay) if len(alerts) == 0 { return []CertificateInfo{}, nil } return alerts, ioutil.CommitJSON(ix.cfg.IndexFile, ix.mem) } func (index *Index) Validate() error { return nil // FIXME: check that the index is populated with valid values }