diff --git a/man/certspotter-script.md b/man/certspotter-script.md index fc266af..34ce437 100644 --- a/man/certspotter-script.md +++ b/man/certspotter-script.md @@ -138,6 +138,14 @@ The following environment variables are set for `malformed_cert` events: : A human-readable string describing why the certificate is malformed. +`ENTRY_FILENAME` + +: Path to a file containing the JSON log entry. The file contains a JSON object with two fields, `leaf_input` and `extra_data`, as described in RFC 6962 Section 4.6. + +`TEXT_FILENAME` + +: Path to a file containing a text message describing the malformed certificate. This file contains the same text that certspotter uses in emails. + # JSON FILE FORMAT Unless `-no_save` is used, certspotter saves a JSON file for every discovered certificate diff --git a/monitor/malformed.go b/monitor/malformed.go index ef66f05..b8a7efd 100644 --- a/monitor/malformed.go +++ b/monitor/malformed.go @@ -15,8 +15,30 @@ import ( ) type malformedLogEntry struct { - Entry *logEntry - Error string + Entry *logEntry + Error string + EntryPath string + TextPath string +} + +func (malformed *malformedLogEntry) entryJSON() any { + return struct { + LeafInput []byte `json:"leaf_input"` + ExtraData []byte `json:"extra_data"` + }{ + LeafInput: malformed.Entry.LeafInput, + ExtraData: malformed.Entry.ExtraData, + } +} + +func (malformed *malformedLogEntry) save() error { + if err := writeJSONFile(malformed.EntryPath, malformed.entryJSON(), 0666); err != nil { + return err + } + if err := writeTextFile(malformed.TextPath, malformed.Text(), 0666); err != nil { + return err + } + return nil } func (malformed *malformedLogEntry) Environ() []string { @@ -27,6 +49,8 @@ func (malformed *malformedLogEntry) Environ() []string { "ENTRY_INDEX=" + fmt.Sprint(malformed.Entry.Index), "LEAF_HASH=" + malformed.Entry.LeafHash.Base64String(), "PARSE_ERROR=" + malformed.Error, + "ENTRY_FILENAME=" + malformed.EntryPath, + "TEXT_FILENAME=" + malformed.TextPath, "CERT_PARSEABLE=no", // backwards compat with pre-0.15.0; not documented } } diff --git a/monitor/monitor.go b/monitor/monitor.go index b9b9912..e7b6109 100644 --- a/monitor/monitor.go +++ b/monitor/monitor.go @@ -74,11 +74,12 @@ func monitorLog(ctx context.Context, config *Config, ctlog *loglist.Log, logClie defer cancel() var ( - stateDirPath = filepath.Join(config.StateDir, "logs", ctlog.LogID.Base64URLString()) - stateFilePath = filepath.Join(stateDirPath, "state.json") - sthsDirPath = filepath.Join(stateDirPath, "unverified_sths") + stateDirPath = filepath.Join(config.StateDir, "logs", ctlog.LogID.Base64URLString()) + stateFilePath = filepath.Join(stateDirPath, "state.json") + sthsDirPath = filepath.Join(stateDirPath, "unverified_sths") + malformedDirPath = filepath.Join(stateDirPath, "malformed_entries") ) - for _, dirPath := range []string{stateDirPath, sthsDirPath} { + for _, dirPath := range []string{stateDirPath, sthsDirPath, malformedDirPath} { if err := os.Mkdir(dirPath, 0777); err != nil && !errors.Is(err, fs.ErrExist) { return fmt.Errorf("error creating state directory: %w", err) } diff --git a/monitor/process.go b/monitor/process.go index 8bed372..6115d06 100644 --- a/monitor/process.go +++ b/monitor/process.go @@ -157,12 +157,18 @@ func processCertificate(ctx context.Context, config *Config, entry *logEntry, ce } func processMalformedLogEntry(ctx context.Context, config *Config, entry *logEntry, parseError error) error { - // TODO-4: save the malformed entry (in get-entries format) in the state directory so user can inspect it - + dirPath := filepath.Join(config.StateDir, "logs", entry.Log.LogID.Base64URLString(), "malformed_entries") malformed := &malformedLogEntry{ - Entry: entry, - Error: parseError.Error(), + Entry: entry, + Error: parseError.Error(), + EntryPath: filepath.Join(dirPath, fmt.Sprintf("%d.json", entry.Index)), + TextPath: filepath.Join(dirPath, fmt.Sprintf("%d.txt", entry.Index)), } + + if err := malformed.save(); err != nil { + return fmt.Errorf("error saving malformed log entry %d in %s (%q): %w", entry.Index, entry.Log.URL, parseError, err) + } + if err := notify(ctx, config, malformed); err != nil { return fmt.Errorf("error notifying about malformed log entry %d in %s (%q): %w", entry.Index, entry.Log.URL, parseError, err) }