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 = ` A utility that generates a submission of one or more certificate chains. The generated submission is protected by a message authentication code. Usage: silent-ctnode --help silent-ctnode [Options] -n NAME -s SECRET FILE [FILE ...] Options: -h, --help: Output usage message and exit -v, --verbosity Leveled logging output (default: NOTICE) -n, --name: Name of the node generating the submission -s, --secret: Shared secret between the node and its monitor -o, --output: File to write submission to (default: stdout) Each trailing FILE argument must contain a single certificate chain. ` type config struct { // Options verbosity string name string secret string output 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.verbosity, "verbosity", "v", logger.LevelNotice.String()) flagopt.StringOpt(fs, &cfg.name, "name", "n", "") flagopt.StringOpt(fs, &cfg.secret, "secret", "s", "") flagopt.StringOpt(fs, &cfg.output, "output", "o", "") if err = fs.Parse(args); err != nil { return cfg, err } // Options lv, err := logger.NewLevel(cfg.verbosity) if err != nil { return cfg, fmt.Errorf("invalid verbosity: %v", err) } if cfg.name == "" { return cfg, fmt.Errorf("node name is required") } if cfg.secret == "" { return cfg, fmt.Errorf("node secret is required") } 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.Stderr, "%s", usage[1:]) os.Exit(0) } if !strings.Contains(err.Error(), "flag provided but not defined") { fmt.Fprintf(os.Stderr, "%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)) }