From 6151cb26dafe5b01ec96c371d4d8c9fa6b4c1ddf Mon Sep 17 00:00:00 2001 From: Andrew Ayer Date: Tue, 6 May 2025 14:19:25 -0400 Subject: [PATCH] Cache issuer certificates retrieved from static-ct-api logs --- monitor/fsstate.go | 17 +++++++++++++++++ monitor/monitor.go | 31 ++++++++++++++++++++++--------- monitor/state.go | 8 ++++++++ monitor/statedir.go | 2 +- 4 files changed, 48 insertions(+), 10 deletions(-) diff --git a/monitor/fsstate.go b/monitor/fsstate.go index d7d9d04..970f4fe 100644 --- a/monitor/fsstate.go +++ b/monitor/fsstate.go @@ -93,6 +93,23 @@ func (s *FilesystemState) RemoveSTH(ctx context.Context, logID LogID, sth *cttyp return removeSTHFromDir(sthsDirPath, sth) } +func (s *FilesystemState) StoreIssuer(ctx context.Context, fingerprint *[32]byte, issuer []byte) error { + filePath := filepath.Join(s.StateDir, "issuers", hex.EncodeToString(fingerprint[:])) + return writeFile(filePath, issuer, 0666) +} + +func (s *FilesystemState) LoadIssuer(ctx context.Context, fingerprint *[32]byte) ([]byte, error) { + filePath := filepath.Join(s.StateDir, "issuers", hex.EncodeToString(fingerprint[:])) + issuer, err := os.ReadFile(filePath) + if errors.Is(err, fs.ErrNotExist) { + return nil, nil + } else if err != nil { + return nil, err + } else { + return issuer, err + } +} + func (s *FilesystemState) NotifyCert(ctx context.Context, cert *DiscoveredCert) error { var notifiedPath string var paths *certPaths diff --git a/monitor/monitor.go b/monitor/monitor.go index 98df2a8..bbb6e3e 100644 --- a/monitor/monitor.go +++ b/monitor/monitor.go @@ -142,22 +142,34 @@ func (client *logClient) ReconstructTree(ctx context.Context, sth *cttypes.Signe } type issuerGetter struct { + state StateProvider logGetter ctclient.IssuerGetter } -func (ig *issuerGetter) GetIssuer(ctx context.Context, fingerprint *[32]byte) (issuer []byte, err error) { - // TODO-2 check cache - err = withRetry(ctx, 7, func() error { +func (ig *issuerGetter) GetIssuer(ctx context.Context, fingerprint *[32]byte) ([]byte, error) { + if issuer, err := ig.state.LoadIssuer(ctx, fingerprint); err != nil { + log.Printf("error loading cached issuer %x (issuer will be retrieved from log instead): %s", *fingerprint, err) + } else if issuer != nil { + return issuer, nil + } + + var issuer []byte + if err := withRetry(ctx, 7, func() error { + var err error issuer, err = ig.logGetter.GetIssuer(ctx, fingerprint) return err - }) - if err == nil { - // TODO-2 insert into cache + }); err != nil { + return nil, err } - return + + if err := ig.state.StoreIssuer(ctx, fingerprint, issuer); err != nil { + log.Printf("error caching issuer %x (issuer will be re-retrieved from log in the future): %s", *fingerprint, err) + } + + return issuer, nil } -func newLogClient(ctlog *loglist.Log) (ctclient.Log, ctclient.IssuerGetter, error) { +func newLogClient(config *Config, ctlog *loglist.Log) (ctclient.Log, ctclient.IssuerGetter, error) { switch { case ctlog.IsRFC6962(): logURL, err := url.Parse(ctlog.URL) @@ -186,6 +198,7 @@ func newLogClient(ctlog *loglist.Log) (ctclient.Log, ctclient.IssuerGetter, erro log: ctlog, client: client, }, &issuerGetter{ + state: config.State, logGetter: client, }, nil default: @@ -194,7 +207,7 @@ func newLogClient(ctlog *loglist.Log) (ctclient.Log, ctclient.IssuerGetter, erro } func monitorLogContinously(ctx context.Context, config *Config, ctlog *loglist.Log) (returnedErr error) { - client, issuerGetter, err := newLogClient(ctlog) + client, issuerGetter, err := newLogClient(config, ctlog) if err != nil { return err } diff --git a/monitor/state.go b/monitor/state.go index 6c5d062..be2da47 100644 --- a/monitor/state.go +++ b/monitor/state.go @@ -63,6 +63,14 @@ type StateProvider interface { // Remove an STH so it is no longer returned by LoadSTHs. RemoveSTH(context.Context, LogID, *cttypes.SignedTreeHead) error + // Store a DER-encoded issuer certificate with the given fingerprint for + // retrieval by LoadIssuer. Returns nil if the issuer has already been stored. + StoreIssuer(context.Context, *[32]byte, []byte) error + + // Retrieve a DER-encoded issuer certificate previously stored with StoreIssuer. + // Returns nil, nil if this issuer certificate has not been stored. + LoadIssuer(context.Context, *[32]byte) ([]byte, error) + // Called when a certificate matching the watch list is discovered. NotifyCert(context.Context, *DiscoveredCert) error diff --git a/monitor/statedir.go b/monitor/statedir.go index bffd044..534f48d 100644 --- a/monitor/statedir.go +++ b/monitor/statedir.go @@ -145,7 +145,7 @@ func prepareStateDir(stateDir string) error { return fmt.Errorf("%s was created by a newer version of certspotter; upgrade to the latest version of certspotter or remove this directory to start from scratch", stateDir) } - for _, subdir := range []string{"certs", "logs", "healthchecks"} { + for _, subdir := range []string{"certs", "logs", "healthchecks", "issuers"} { if err := os.Mkdir(filepath.Join(stateDir, subdir), 0777); err != nil && !errors.Is(err, fs.ErrExist) { return err }