From 9353b9aa47be65c9cd483a251cfd3aabc193c3ec Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Thu, 13 Oct 2022 17:47:14 +0200 Subject: Add terminal UI Intentionally backwards-compatible with HARICA's onion-csr tool, see: https://github.com/HARICA-official/onion-csr --- internal/options/options.go | 56 +++++++++++++++++++++++++++++++++++ internal/options/options_test.go | 64 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 internal/options/options.go create mode 100644 internal/options/options_test.go (limited to 'internal') diff --git a/internal/options/options.go b/internal/options/options.go new file mode 100644 index 0000000..da63fc2 --- /dev/null +++ b/internal/options/options.go @@ -0,0 +1,56 @@ +// Package options provides parsing of user-provided onion-csr options +package options + +import ( + "encoding/hex" + "flag" + "fmt" + "os" +) + +const usage = `Usage: + + onion-csr -h + onion-csr -d HS_DIR -n NONCE + +Options: + + -h, --help: Output usage message and exit + -d, --hs-dir: Path to hidden service directory + -n, --ca-nonce: Nonce provided by a certificate authority in hex +` + +// Options is a collection of onion-csr options +type Options struct { + HSDir string + CANonce []byte + + caNonce string +} + +// New parses user-supplied onion-csr options +func New(cmd string, args []string) (opts Options, err error) { + fs := flag.NewFlagSet(cmd, flag.ContinueOnError) + fs.Usage = func() { fmt.Fprintf(os.Stderr, "%s", usage) } + stringOpt(fs, &opts.HSDir, "hs-dir", "d", "") + stringOpt(fs, &opts.caNonce, "ca-nonce", "n", "") + if err = fs.Parse(args); err != nil { + return opts, err + } + + if opts.HSDir == "" { + return opts, fmt.Errorf("-d, --hs-dir: must not be an empty string") + } + if opts.CANonce, err = hex.DecodeString(opts.caNonce); err != nil { + return opts, fmt.Errorf("-n, --ca-nonce: %v", err) + } + if len(opts.CANonce) == 0 { + return opts, fmt.Errorf("-n, --ca-nonce: must not be empty string") + } + return opts, err +} + +func stringOpt(fs *flag.FlagSet, opt *string, short, long, value string) { + fs.StringVar(opt, short, value, "") + fs.StringVar(opt, long, value, "") +} diff --git a/internal/options/options_test.go b/internal/options/options_test.go new file mode 100644 index 0000000..fda189c --- /dev/null +++ b/internal/options/options_test.go @@ -0,0 +1,64 @@ +package options + +import ( + "fmt" + "os" + "reflect" + "strings" + "syscall" + "testing" +) + +func TestNew(t *testing.T) { + setup(t) + defer teardown(t) + + none := Options{} + ok := Options{ + HSDir: "/path/to/hsdir", + CANonce: make([]byte, 10), + caNonce: "00000000000000000000", + } + for _, table := range []struct { + desc string + args string + want Options + }{ + {"no options", "", none}, + {"unknown option --extra", "--extra value", none}, + {"unknown extra value", "extra", none}, + {"help option", "-h", none}, + {"missing --hs-dir", fmt.Sprintf("-n %x", ok.CANonce), none}, + {"missing --ca-nonce", fmt.Sprintf("-d %s", ok.HSDir), none}, + {"invalid --ca-nonce", fmt.Sprintf("-d %s -n 0q", ok.HSDir), none}, + {"valid", fmt.Sprintf("-d %s -n %x", ok.HSDir, ok.CANonce), ok}, + {"valid", fmt.Sprintf("--hs-dir %s --ca-nonce %x", ok.HSDir, ok.CANonce), ok}, + } { + opts, err := New("onion-csr", strings.Split(table.args, " ")) + if got, want := err != nil, table.desc != "valid"; got != want { + t.Errorf("%s: got error %v but wanted %v: %v", table.desc, got, want, err) + } + if err != nil { + continue + } + if got, want := opts, table.want; !reflect.DeepEqual(got, want) { + t.Errorf("%s: got options\n%v\nbut wanted\n%v\n", table.desc, got, want) + } + } +} + +func setup(t *testing.T) { + t.Helper() + var err error + if os.Stderr, err = os.CreateTemp("", "onion-csr@rgdd.se"); err != nil { + t.Fatalf("create temp file: %v", err) + } +} + +func teardown(t *testing.T) { + t.Helper() + if err := os.Remove(os.Stderr.Name()); err != nil { + t.Fatalf("remove temp file: %v", err) + } + os.Stderr = os.NewFile(uintptr(syscall.Stderr), "/dev/stderr") +} -- cgit v1.2.3