From e18d36ebae30536c77c61cd5da123991e0ca1629 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Sun, 31 Dec 2023 09:39:25 +0100 Subject: Add drafty prototype --- pkg/storage/storage.go | 155 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 pkg/storage/storage.go (limited to 'pkg/storage/storage.go') 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) +} -- cgit v1.2.3