aboutsummaryrefslogtreecommitdiff
path: root/pkg/storage/storage.go
diff options
context:
space:
mode:
authorRasmus Dahlberg <rasmus@rgdd.se>2023-12-31 09:39:25 +0100
committerRasmus Dahlberg <rasmus@rgdd.se>2024-01-07 20:22:23 +0100
commite18d36ebae30536c77c61cd5da123991e0ca1629 (patch)
treebf4880c0019a6009ab1b671e23ef4a1a4a5e8e08 /pkg/storage/storage.go
parent54d980afcbd6f0011d6a162e0003587d26a3e311 (diff)
Add drafty prototype
Diffstat (limited to 'pkg/storage/storage.go')
-rw-r--r--pkg/storage/storage.go155
1 files changed, 155 insertions, 0 deletions
diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go
new file mode 100644
index 0000000..5e28aca
--- /dev/null
+++ b/pkg/storage/storage.go
@@ -0,0 +1,155 @@
+// Package storage manages an index of certificates, a dynamically updated log
+// list, and a monitor's state on the local file system in a single directory.
+package storage
+
+import (
+ "context"
+ "crypto/sha256"
+ "crypto/x509"
+ "fmt"
+ "net/http"
+ "path/filepath"
+ "time"
+
+ "github.com/google/certificate-transparency-go/client"
+ "github.com/google/certificate-transparency-go/jsonclient"
+ "gitlab.torproject.org/rgdd/ct/pkg/metadata"
+ "rgdd.se/silent-ct/internal/ioutil"
+ "rgdd.se/silent-ct/internal/monitor"
+ "rgdd.se/silent-ct/pkg/storage/index"
+ "rgdd.se/silent-ct/pkg/storage/loglist"
+)
+
+type Config struct {
+ Bootstrap bool // Whether a new storage should be bootstrapped in a non-existing directory
+ Directory string // Path to a directory where everything will be stored
+
+ // Optional
+ AlertDelay time.Duration // Time before alerting on certificates that are unaccounted for
+ StaticLogs []metadata.Log // Static logs to configure in loglist
+ RemoveLogs []metadata.LogKey // Keys of logs to omit in loglist
+ HTTPTimeout time.Duration // HTTP timeout used when bootstrapping logs
+}
+
+func (cfg *Config) CertificateIndexFile() string { return cfg.Directory + "/crt_index.json" }
+func (cfg *Config) LegitimateCertificateDirectory() string { return cfg.Directory + "/crt_trusted" }
+func (cfg *Config) DiscoveredCertificateDirectory() string { return cfg.Directory + "/crt_found" }
+func (cfg *Config) MetadataFile() string { return cfg.Directory + "/metadata.json" }
+func (cfg *Config) MetadataHistoryDirectory() string { return cfg.Directory + "/metadata_history" }
+func (cfg *Config) MonitorStateDirectory() string { return cfg.Directory + "/monitor_state" }
+func (cfg *Config) MonitorStateFile(logID [sha256.Size]byte) string {
+ return fmt.Sprintf("%s/%x.json", cfg.MonitorStateDirectory(), logID[:])
+}
+
+func (cfg *Config) directories() []string {
+ return []string{
+ cfg.Directory,
+ cfg.LegitimateCertificateDirectory(),
+ cfg.DiscoveredCertificateDirectory(),
+ cfg.MetadataHistoryDirectory(),
+ cfg.MonitorStateDirectory(),
+ }
+}
+
+func (cfg *Config) configure() error {
+ if cfg.Directory == "" {
+ return fmt.Errorf("directory is required")
+ }
+ if cfg.HTTPTimeout == 0 {
+ cfg.HTTPTimeout = 10 * time.Second
+ }
+
+ path, err := filepath.Abs(cfg.Directory)
+ if err != nil {
+ return err
+ }
+ cfg.Directory = path
+ if err := ioutil.DirectoriesExist(cfg.directories()); err != nil {
+ if !cfg.Bootstrap {
+ return err
+ }
+ return ioutil.CreateDirectories(cfg.directories())
+ }
+ return nil
+}
+
+type Storage struct {
+ Config
+ index.Index
+ loglist.LogList
+}
+
+func New(cfg Config) (Storage, error) {
+ err := cfg.configure()
+ if err != nil {
+ return Storage{}, err
+ }
+
+ s := Storage{Config: cfg}
+ if s.Index, err = index.New(index.Config{
+ PermitBootstrap: cfg.Bootstrap,
+ IndexFile: cfg.CertificateIndexFile(),
+ TrustDirectory: cfg.LegitimateCertificateDirectory(),
+ MatchDirectory: cfg.DiscoveredCertificateDirectory(),
+ AlertDelay: cfg.AlertDelay,
+ }); err != nil {
+ return Storage{}, err
+ }
+
+ if s.LogList, err = loglist.New(loglist.Config{
+ PermitBootstrap: cfg.Bootstrap,
+ MetadataFile: cfg.MetadataFile(),
+ HistoryDirectory: cfg.MetadataHistoryDirectory(),
+ StaticLogs: cfg.StaticLogs,
+ RemoveLogs: cfg.RemoveLogs,
+ }); err != nil {
+ return Storage{}, err
+ }
+
+ return s, err
+}
+
+func (s *Storage) BootstrapLog(ctx context.Context, log metadata.Log, skipBacklog bool) (monitor.State, error) {
+ storedState, err := s.GetMonitorState(log)
+ if err == nil {
+ return storedState, ErrorMonitorStateExists
+ }
+
+ key, err := x509.MarshalPKIXPublicKey(log.Key.Public)
+ if err != nil {
+ return monitor.State{}, err
+ }
+ cli, err := client.New(string(log.URL), &http.Client{}, jsonclient.Options{PublicKeyDER: key})
+ if err != nil {
+ return monitor.State{}, err
+ }
+
+ sctx, cancel := context.WithTimeout(ctx, s.Config.HTTPTimeout)
+ defer cancel()
+ sth, err := cli.GetSTH(sctx)
+ if err != nil {
+ return monitor.State{}, err
+ }
+ id, _ := log.Key.ID()
+ sth.LogID = id
+
+ state := monitor.State{SignedTreeHead: *sth}
+ if skipBacklog {
+ state.NextIndex = sth.TreeSize
+ }
+ return state, s.SetMonitorState(id, state)
+}
+
+func (s *Storage) SetMonitorState(logID [sha256.Size]byte, state monitor.State) error {
+ return ioutil.CommitJSON(s.MonitorStateFile(logID), state)
+}
+
+func (s *Storage) GetMonitorState(log metadata.Log) (monitor.State, error) {
+ id, err := log.Key.ID()
+ if err != nil {
+ return monitor.State{}, err
+ }
+
+ state := monitor.State{}
+ return state, ioutil.ReadJSON(s.MonitorStateFile(id), &state)
+}