You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
DateKeys/cmd/datekeys/author.go

162 lines
4.6 KiB

package main
import (
"errors"
"fmt"
"io"
"os"
"strings"
"g.activething.com/go/DateKeys/authorkey"
)
// maxPassFile bounds the file of a passphrase.
const maxPassFile = 4 << 10
// readPass returns the passphrase in file, one line without its line ending,
// or in the standard input when file is "-". The CLI takes no passphrase on
// the command line, where the shell history keeps it, nor from the
// environment, where other processes can read it.
func readPass(cmd, file string, stdin io.Reader) (string, error) {
var r io.Reader = stdin
if file == "-" {
// A terminal would echo the passphrase and keep it in the scrollback.
if f, ok := stdin.(*os.File); ok {
if info, err := f.Stat(); err == nil && info.Mode()&os.ModeCharDevice != 0 {
return "", fmt.Errorf("%s: the passphrase would be read from a terminal and shown on screen: pipe it in, or use a file", cmd)
}
}
} else {
f, err := os.Open(file)
if err != nil {
return "", err
}
defer f.Close()
r = f
}
b, err := io.ReadAll(io.LimitReader(r, maxPassFile+1))
defer clear(b)
if err != nil {
return "", err
}
if len(b) > maxPassFile {
return "", fmt.Errorf("%s: the passphrase file is longer than %d bytes", cmd, maxPassFile)
}
s := strings.TrimSuffix(strings.TrimSuffix(string(b), "\n"), "\r")
if s == "" {
return "", fmt.Errorf("%s: the passphrase is empty", cmd)
}
return s, nil
}
// loadAuthorKey reads the key file path. An encrypted one needs passFile.
func loadAuthorKey(cmd, path, passFile string, stdin io.Reader) (*authorkey.Key, error) {
var pass string
if passFile != "" {
var err error
if pass, err = readPass(cmd, passFile, stdin); err != nil {
return nil, err
}
}
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
k, err := authorkey.Read(f, pass)
if err != nil {
if pass == "" && strings.Contains(err.Error(), "passphrase") {
return nil, fmt.Errorf("%s: %s is encrypted: give its passphrase with -pass-file FILE, or - for the standard input", cmd, path)
}
return nil, fmt.Errorf("%s: %s: %w", cmd, path, err)
}
return k, nil
}
// author runs "datekeys author keygen|public" (spec v0.11, §29.12).
func author(args []string, stdout, stderr io.Writer, stdin io.Reader) error {
if len(args) == 0 {
return errUsage
}
switch args[0] {
case "keygen":
return authorKeygen(args[1:], stdout, stderr, stdin)
case "public":
return authorPublic(args[1:], stdout, stdin)
}
return errUsage
}
func authorKeygen(args []string, stdout, stderr io.Writer, stdin io.Reader) error {
fs := newFlags("author keygen")
out := fs.String("out", "", "new file for the secret key; never overwritten")
passFile := fs.String("pass-file", "", "file with the passphrase that encrypts the key, or - for the standard input")
plain := fs.Bool("plain", false, "write the key without encryption, as text anyone who reads the file can use")
if err := parse(fs, args); err != nil {
return err
}
switch {
case *out == "":
return errors.New("author keygen: -out is required")
case *plain == (*passFile != ""):
return errors.New("author keygen: give -pass-file, to encrypt the key, or -plain, to write it as text, and not both")
}
if err := checkNew(*out); err != nil {
return err
}
var pass string
if !*plain {
var err error
if pass, err = readPass("author keygen", *passFile, stdin); err != nil {
return err
}
}
k, err := authorkey.Generate()
if err != nil {
return err
}
defer k.Clear()
err = writeAtomic(*out, func(w io.Writer) error {
if *plain {
b := authorkey.Marshal(k)
defer clear(b)
_, err := w.Write(b)
return err
}
return authorkey.Encrypt(w, k, pass)
})
if err != nil {
return err
}
pub, err := authorkey.PublicString(k.Public())
if err != nil {
return err
}
fmt.Fprintln(stdout, pub)
fmt.Fprintf(stderr, "Secret key written to %s: keep it, and its passphrase, secret. The line above is the public key: give it to whoever must know your signature.\n", *out)
return nil
}
func authorPublic(args []string, stdout io.Writer, stdin io.Reader) error {
fs := newFlags("author public")
key := fs.String("key", "", "file with the secret key")
passFile := fs.String("pass-file", "", "file with the passphrase of an encrypted key, or - for the standard input")
if err := parse(fs, args); err != nil {
return err
}
if *key == "" {
return errors.New("author public: -key is required")
}
k, err := loadAuthorKey("author public", *key, *passFile, stdin)
if err != nil {
return err
}
defer k.Clear()
pub, err := authorkey.PublicString(k.Public())
if err != nil {
return err
}
fmt.Fprintln(stdout, pub)
return nil
}

Powered by TurnKey Linux.