aboutsummaryrefslogtreecommitdiff
path: root/cmd/silent-ctnode/main.go
blob: fec088cefc87e371a28cba77afe243bfb15c3fba (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
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))
}