aboutsummaryrefslogtreecommitdiff
path: root/pkg/storage/index/index.go
blob: 36a933458832350726bee85e9cabf5b6324287ad (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
91
92
93
94
95
96
97
98
99
100
101
102
103
// 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/silentct/internal/ioutil"
	"rgdd.se/silentct/internal/monitor"
	"rgdd.se/silentct/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
}