package main import ( "errors" "flag" "fmt" "os" "strings" "rgdd.se/silent-ct/internal/flagopt" "rgdd.se/silent-ct/internal/ioutil" "rgdd.se/silent-ct/internal/logger" "rgdd.se/silent-ct/pkg/crtutil" "rgdd.se/silent-ct/pkg/policy" "rgdd.se/silent-ct/pkg/submission" ) const usage = ` silentct-mac is a utility that helps allowlist legitimately issued certificates while monitoring Certificate Transparency logs. One or more certificate chains are bundled with a message authentication code, such that the silentct-mon tool can fetch them over an insecure channel or from untrusted intermediary storage. Usage: silentct-mac [Options] -n NAME -s SECRET CRT-FILE [CRT-FILE ...] Options: -n, --name Name of the system that allowlists certificates -o, --output Filename to write allowlisted certificates to (default: stdout) -s, --secret Shared secret between the allowlisting system and its monitor -v, --verbosity Leveled logging output (default: NOTICE) ` type config struct { // Options name string output string secret string verbosity string // Extracted log *logger.Logger files []string } func configure(cmd string, args []string) (cfg config, err error) { fs := flag.NewFlagSet(cmd, flag.ContinueOnError) fs.Usage = func() {} flagopt.StringOpt(fs, &cfg.name, "name", "n", "") flagopt.StringOpt(fs, &cfg.output, "output", "o", "") flagopt.StringOpt(fs, &cfg.secret, "secret", "s", "") flagopt.StringOpt(fs, &cfg.verbosity, "verbosity", "v", logger.LevelNotice.String()) if err = fs.Parse(args); err != nil { return cfg, err } // Options if cfg.name == "" { return cfg, fmt.Errorf("node name is required") } if cfg.secret == "" { return cfg, fmt.Errorf("node secret is required") } lv, err := logger.NewLevel(cfg.verbosity) if err != nil { return cfg, fmt.Errorf("invalid verbosity: %v", err) } cfg.log = logger.New(logger.Config{Level: lv, File: os.Stderr}) // Arguments cfg.files = fs.Args() if len(cfg.files) == 0 { return cfg, fmt.Errorf("at least one certificate chain file is required") } return cfg, err } func main() { cfg, err := configure(os.Args[0], os.Args[1:]) if err != nil { if errors.Is(err, flag.ErrHelp) { fmt.Fprintf(os.Stdout, "%s", usage[1:]) os.Exit(0) } if !strings.Contains(err.Error(), "flag provided but not defined") { fmt.Fprintf(os.Stdout, "%v\n", err) } os.Exit(1) } var chains [][]byte for i, path := range cfg.files { b, err := ioutil.ReadData(path) if err != nil { cfg.log.Dief("file %d: %v\n", i, err) } if _, err := crtutil.CertificateChainFromPEM(b); err != nil { cfg.log.Dief("file %d: %v\n", i, err) } chains = append(chains, b) } node, err := policy.NewNode(cfg.name, cfg.secret, "http://www.example.org/unused", nil) if err != nil { cfg.log.Dief("api: %v\n", err) } s, err := submission.New(node, chains) if err != nil { cfg.log.Dief("api: %v\n", err) } fp := os.Stdout if cfg.output != "" { if fp, err = os.OpenFile(cfg.output, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644); err != nil { cfg.log.Dief("output: %v\n", err) } } fmt.Fprintf(fp, "%s", string(s)) }