1
0
mirror of https://github.com/taigrr/arc synced 2025-01-18 04:33:13 -08:00
arc/args.go
2016-05-24 21:26:50 +09:00

297 lines
8.1 KiB
Go

// Copyright (C) 2016 - Will Glozer. All rights reserved.
package main
import (
"bytes"
"errors"
"fmt"
"os"
"github.com/jessevdk/go-flags"
"golang.org/x/crypto/ssh/terminal"
)
type Args struct {
OperationMode `group:"Archive Operation Mode"`
OperationModifier `group:"Archive Operation Modifiers"`
KeyManagementMode `group:"Key Management Mode"`
SecurityOptions `group:"Archive Security Options"`
PasswordOptions `group:"Password Options"`
KeyManagementOptions `group:"Key Generation Options"`
MiscOpts `group:"Misc Options"`
Positional `positional-args:"true" required:"0"`
}
type OperationMode struct {
Create bool `short:"c" long:"create" description:"create new archive"`
List bool `short:"t" long:"list" description:"list archive contents"`
Extract bool `short:"x" long:"extract" description:"extract from archive"`
}
type OperationModifier struct {
File string `short:"f" long:"file" description:"archive file"`
Shards []string ` long:"shard" description:"archive shard"`
}
type SecurityOptions struct {
Password bool `long:"password" description:"derive key from password"`
Key string `long:"key" description:"derive key from ECDH exchange"`
Threshold int `long:"threshold" description:"random key with SSS threshold"`
}
type KeyManagementMode struct {
Keygen bool `long:"keygen" description:"generate key pair"`
}
type KeyManagementOptions struct {
Private string `long:"private" description:"private key file"`
Public string `long:"public" description:"public key file"`
}
type PasswordOptions struct {
Iterations uint32 `long:"iterations" description:"argon2 iterations"`
Memory uint32 `long:"memory" description:"argon2 memory use"`
}
type MiscOpts struct {
Help bool `short:"h" long:"help" description:"show this help message"`
Verbose []bool `short:"v" long:"verbose" description:"generate verbose output"`
}
type Positional struct {
Names []string `positional-arg-name:"names"`
}
func NewCommand() (*Cmd, error) {
args, err := ParseArgs(os.Args[1:]...)
if err != nil {
return nil, err
}
c := &Cmd{
Verbose: len(args.Verbose),
Names: args.Names,
}
var mode int
switch {
case args.Create:
c.Op = c.Create
mode = os.O_EXCL | os.O_CREATE | os.O_WRONLY
case args.List:
c.Op = c.List
mode = os.O_RDONLY
case args.Extract:
c.Op = c.Extract
mode = os.O_RDONLY
case args.Keygen:
c.Op = c.Keygen
}
switch {
case args.Password:
c.Archiver, err = args.PreparePasswordArchive(mode)
case args.Key != "":
c.Archiver, err = args.PrepareCurve448Archive(mode)
case len(args.Shards) > 0:
c.Archiver, err = args.PrepareShardArchive(mode)
case args.Keygen:
c.Public, c.Private, err = args.PrepareKeygen()
}
return c, err
}
func ParseArgs(arg ...string) (*Args, error) {
args := &Args{
PasswordOptions: PasswordOptions{
Iterations: 3,
Memory: 16,
},
}
parser := flags.NewParser(args, flags.PassDoubleDash)
parser.Usage = "[OPTIONS]"
if _, err := parser.Parse(); err != nil {
return nil, err
}
if args.Help {
b := bytes.Buffer{}
parser.WriteHelp(&b)
return nil, errors.New(b.String())
}
err := args.Validate()
return args, err
}
func (a *Args) Validate() error {
switch {
case !a.Create && !a.List && !a.Extract && !a.Keygen:
return fmt.Errorf("must specify one of -c, -t, -x, --keygen")
case a.Create && (a.List || a.Extract || a.Keygen):
return fmt.Errorf("can't combine -c, --create with other operations")
case a.List && (a.Create || a.Extract || a.Keygen):
return fmt.Errorf("can't combine -t, --list with other operations")
case a.Extract && (a.Create || a.List || a.Keygen):
return fmt.Errorf("can't combine -x, --extract with other operations")
case a.Keygen && (a.Create || a.Extract || a.List):
return fmt.Errorf("can't combine --keygen with other operations")
case a.Create && !a.Password && a.Key == "" && len(a.Shards) == 0:
return fmt.Errorf("create requires --password, --key, or --shard")
case a.List && !a.Password && a.Key == "" && len(a.Shards) == 0:
return fmt.Errorf("list requires --password, --key, or --shard")
case a.Extract && !a.Password && a.Key == "" && len(a.Shards) == 0:
return fmt.Errorf("extract requires --password, --key, or --shard")
case a.Password && a.Key != "":
return fmt.Errorf("can't combine --password with --key")
case a.Password && len(a.Shards) > 0:
return fmt.Errorf("can't combine --password with --shard")
case a.Key != "" && len(a.Shards) > 0:
return fmt.Errorf("can't combine --key with --shard")
case len(a.Shards) > 255:
return fmt.Errorf("can't use more than 255 shards")
case a.Create && len(a.Shards) > 0 && len(a.Shards) < 2:
return fmt.Errorf("can't use less than 2 shards")
case a.Create && len(a.Shards) > 0 && a.Threshold <= 1:
return fmt.Errorf("--threshold must be > 1")
case a.Create && len(a.Shards) > 0 && a.Threshold > len(a.Shards):
return fmt.Errorf("--threshold must be <= %d", len(a.Shards))
case !a.Keygen && (a.Password || a.Key != "") && a.File == "":
return fmt.Errorf("must provide -f, --file")
case !a.Keygen && !a.Password && a.Key == "" && a.File == "" && len(a.Shards) == 0:
return fmt.Errorf("must provide -f, --file or --shard")
case !a.Keygen && a.File != "" && len(a.Shards) > 0:
return fmt.Errorf("can't combine -f, --file and --shard")
case a.Keygen && (a.Public == "" || a.Private == ""):
return fmt.Errorf("keygen requires --public and --private")
case a.Create && len(a.Names) == 0:
return fmt.Errorf("no files or directories specified")
}
return nil
}
func (a *Args) PreparePasswordArchive(mode int) (Archiver, error) {
file, err := os.OpenFile(a.File, mode, 0600)
if err != nil {
return nil, err
}
password, err := ReadPassword()
if err != nil {
return nil, err
}
return NewPasswordArchive(password, a.Iterations, a.Memory, file), nil
}
func (a *Args) PrepareCurve448Archive(mode int) (Archiver, error) {
var publicKey PublicKey
var privateKey PrivateKey
var err error
if mode&os.O_CREATE == os.O_CREATE {
err = a.LoadPublicKey(&publicKey)
} else {
err = a.LoadPrivateKey(&privateKey)
}
if err != nil {
return nil, fmt.Errorf("file %s: %s", a.Key, err)
}
file, err := os.OpenFile(a.File, mode, 0600)
if err != nil {
return nil, err
}
return NewCurve448Archive(&publicKey, &privateKey, file), nil
}
func (a *Args) PrepareShardArchive(mode int) (Archiver, error) {
files := make([]File, len(a.Shards))
for i, path := range a.Shards {
file, err := os.OpenFile(path, mode, 0600)
if err != nil {
return nil, err
}
files[i] = file
}
return NewShardArchive(a.Threshold, files), nil
}
func (a *Args) PrepareKeygen() (public *KeyContainer, private *KeyContainer, err error) {
mode := os.O_EXCL | os.O_CREATE | os.O_WRONLY
public, err = a.OpenPublicKeyContainer(a.Public, mode)
if err != nil {
return nil, nil, fmt.Errorf("can't create public key: %s", err)
}
private, err = a.OpenPrivateKeyContainer(a.Private, mode)
if err != nil {
return nil, nil, fmt.Errorf("can't create private key: %s", err)
}
return public, private, err
}
func (a *Args) LoadPublicKey(key *PublicKey) error {
c, err := a.OpenPublicKeyContainer(a.Key, os.O_RDONLY)
if err != nil {
return err
}
defer c.Close()
return c.ReadPublicKey(key)
}
func (a *Args) LoadPrivateKey(key *PrivateKey) error {
c, err := a.OpenPrivateKeyContainer(a.Key, os.O_RDONLY)
if err != nil {
return err
}
defer c.Close()
return c.ReadPrivateKey(key)
}
func (a *Args) OpenPublicKeyContainer(path string, mode int) (*KeyContainer, error) {
file, err := os.OpenFile(path, mode, 0600)
if err != nil {
return nil, err
}
return NewKeyContainer(file, []byte(""), 1, 8), nil
}
func (a *Args) OpenPrivateKeyContainer(path string, mode int) (*KeyContainer, error) {
file, err := os.OpenFile(path, mode, 0600)
if err != nil {
return nil, err
}
password, err := ReadPassword()
if err != nil {
return nil, err
}
return NewKeyContainer(file, password, a.Iterations, a.Memory), nil
}
func ReadPassword() ([]byte, error) {
fmt.Print("password: ")
b, err := terminal.ReadPassword(int(os.Stdin.Fd()))
fmt.Print("\n")
return b, err
}