cmd/age,cmd/age-keygen: normalize errors, warnings, and hints

This commit is contained in:
Filippo Valsorda
2021-06-14 11:46:00 +02:00
committed by Filippo Valsorda
parent fb97277f8d
commit 0703f86521
3 changed files with 89 additions and 58 deletions

View File

@@ -70,10 +70,10 @@ func main() {
flag.StringVar(&outFlag, "output", "", "output to `FILE` (default stdout)")
flag.Parse()
if len(flag.Args()) != 0 && !convertFlag {
log.Fatalf("age-keygen takes no arguments")
errorf("too many arguments")
}
if len(flag.Args()) > 1 && convertFlag {
log.Fatalf("Too many arguments")
errorf("too many arguments")
}
if versionFlag {
if Version != "" {
@@ -92,11 +92,11 @@ func main() {
if outFlag != "" {
f, err := os.OpenFile(outFlag, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600)
if err != nil {
log.Fatalf("Failed to open output file %q: %v", outFlag, err)
errorf("failed to open output file %q: %v", outFlag, err)
}
defer func() {
if err := f.Close(); err != nil {
log.Fatalf("Failed to close output file %q: %v", outFlag, err)
errorf("failed to close output file %q: %v", outFlag, err)
}
}()
out = f
@@ -106,7 +106,7 @@ func main() {
if inFile := flag.Arg(0); inFile != "" && inFile != "-" {
f, err := os.Open(inFile)
if err != nil {
log.Fatalf("Failed to open input file %q: %v", inFile, err)
errorf("failed to open input file %q: %v", inFile, err)
}
defer f.Close()
in = f
@@ -116,7 +116,7 @@ func main() {
convert(in, out)
} else {
if fi, err := out.Stat(); err == nil && fi.Mode().IsRegular() && fi.Mode().Perm()&0004 != 0 {
fmt.Fprintf(os.Stderr, "Warning: writing secret key to a world-readable file.\n")
warning("writing secret key to a world-readable file")
}
generate(out)
}
@@ -125,7 +125,7 @@ func main() {
func generate(out *os.File) {
k, err := age.GenerateX25519Identity()
if err != nil {
log.Fatalf("Internal error: %v", err)
errorf("internal error: %v", err)
}
if !term.IsTerminal(int(out.Fd())) {
@@ -140,16 +140,24 @@ func generate(out *os.File) {
func convert(in io.Reader, out io.Writer) {
ids, err := age.ParseIdentities(in)
if err != nil {
log.Fatalf("Failed to parse input: %v", err)
errorf("failed to parse input: %v", err)
}
if len(ids) == 0 {
log.Fatalf("No identities found in the input")
errorf("no identities found in the input")
}
for _, id := range ids {
id, ok := id.(*age.X25519Identity)
if !ok {
log.Fatalf("Internal error: unexpected identity type: %T", id)
errorf("internal error: unexpected identity type: %T", id)
}
fmt.Fprintf(out, "%s\n", id.Recipient())
}
}
func errorf(format string, v ...interface{}) {
log.Printf("age-keygen: error: "+format, v...)
}
func warning(msg string) {
log.Printf("age-keygen: warning: " + msg)
}

View File

@@ -12,7 +12,7 @@ import (
"flag"
"fmt"
"io"
_log "log"
"log"
"os"
"runtime/debug"
"strings"
@@ -78,7 +78,7 @@ Example:
var Version string
func main() {
_log.SetFlags(0)
log.SetFlags(0)
flag.Usage = func() { fmt.Fprintf(os.Stderr, "%s\n", usage) }
if len(os.Args) == 1 {
@@ -127,47 +127,47 @@ func main() {
}
if flag.NArg() > 1 {
logFatalf("Error: too many arguments: %q.\n"+
"Note that the input file must be specified after all flags.", flag.Args())
errorWithHint(fmt.Sprintf("too many arguments: %q", flag.Args()),
"note that the input file must be specified after all flags")
}
switch {
case decryptFlag:
if encryptFlag {
logFatalf("Error: -e/--encrypt can't be used with -d/--decrypt.")
errorf("-e/--encrypt can't be used with -d/--decrypt")
}
if armorFlag {
logFatalf("Error: -a/--armor can't be used with -d/--decrypt.\n" +
"Note that armored files are detected automatically.")
errorWithHint("-a/--armor can't be used with -d/--decrypt",
"note that armored files are detected automatically")
}
if passFlag {
logFatalf("Error: -p/--passphrase can't be used with -d/--decrypt.\n" +
"Note that password protected files are detected automatically.")
errorWithHint("-p/--passphrase can't be used with -d/--decrypt",
"note that password protected files are detected automatically")
}
if len(recipientFlags) > 0 {
logFatalf("Error: -r/--recipient can't be used with -d/--decrypt.\n" +
"Did you mean to use -i/--identity to specify a private key?")
errorWithHint("-r/--recipient can't be used with -d/--decrypt",
"did you mean to use -i/--identity to specify a private key?")
}
if len(recipientsFileFlags) > 0 {
logFatalf("Error: -R/--recipients-file can't be used with -d/--decrypt.\n" +
"Did you mean to use -i/--identity to specify a private key?")
errorWithHint("-R/--recipients-file can't be used with -d/--decrypt",
"did you mean to use -i/--identity to specify a private key?")
}
default: // encrypt
if len(identityFlags) > 0 && !encryptFlag {
logFatalf("Error: -i/--identity can't be used in encryption mode unless symmetric encryption is explicitly selected with -e/--encrypt.\n" +
"Did you forget to specify -d/--decrypt?")
errorWithHint("-i/--identity can't be used in encryption mode unless symmetric encryption is explicitly selected with -e/--encrypt",
"did you forget to specify -d/--decrypt?")
}
if len(recipientFlags)+len(recipientsFileFlags)+len(identityFlags) == 0 && !passFlag {
logFatalf("Error: missing recipients.\n" +
"Did you forget to specify -r/--recipient, -R/--recipients-file or -p/--passphrase?")
errorWithHint("missing recipients",
"did you forget to specify -r/--recipient, -R/--recipients-file or -p/--passphrase?")
}
if len(recipientFlags) > 0 && passFlag {
logFatalf("Error: -p/--passphrase can't be combined with -r/--recipient.")
errorf("-p/--passphrase can't be combined with -r/--recipient")
}
if len(recipientsFileFlags) > 0 && passFlag {
logFatalf("Error: -p/--passphrase can't be combined with -R/--recipients-file.")
errorf("-p/--passphrase can't be combined with -R/--recipients-file")
}
if len(identityFlags) > 0 && passFlag {
logFatalf("Error: -p/--passphrase can't be combined with -i/--identity.")
errorf("-p/--passphrase can't be combined with -i/--identity")
}
}
@@ -176,7 +176,7 @@ func main() {
if name := flag.Arg(0); name != "" && name != "-" {
f, err := os.Open(name)
if err != nil {
logFatalf("Error: failed to open input file %q: %v", name, err)
errorf("failed to open input file %q: %v", name, err)
}
defer f.Close()
in = f
@@ -187,7 +187,7 @@ func main() {
f := newLazyOpener(name)
defer func() {
if err := f.Close(); err != nil {
logFatalf("Error: failed to close output file %q: %v", name, err)
errorf("failed to close output file %q: %v", name, err)
}
}()
out = f
@@ -198,8 +198,9 @@ func main() {
} else if !armorFlag {
// If the output wouldn't be armored, refuse to send binary to
// the terminal unless explicitly requested with "-o -".
logFatalf("Error: refusing to output binary to the terminal.\n" +
`Did you mean to use -a/--armor? Force with "-o -".`)
errorWithHint("refusing to output binary to the terminal",
"did you mean to use -a/--armor?",
`force anyway with "-o -"`)
}
}
if in == os.Stdin && term.IsTerminal(int(os.Stdin.Fd())) {
@@ -217,7 +218,7 @@ func main() {
case passFlag:
pass, err := passphrasePromptForEncryption()
if err != nil {
logFatalf("Error: %v", err)
errorf("%v", err)
}
encryptPass(pass, in, out, armorFlag)
default:
@@ -237,6 +238,7 @@ func passphrasePromptForEncryption() (string, error) {
words = append(words, randomWord())
}
p = strings.Join(words, "-")
// TODO: consider printing this to the terminal, instead of stderr.
fmt.Fprintf(os.Stderr, "Using the autogenerated passphrase %q.\n", p)
} else {
confirm, err := readPassphrase("Confirm passphrase:")
@@ -254,26 +256,31 @@ func encryptKeys(keys, files, identities []string, in io.Reader, out io.Writer,
var recipients []age.Recipient
for _, arg := range keys {
r, err := parseRecipient(arg)
if err, ok := err.(gitHubRecipientError); ok {
errorWithHint(err.Error(), "instead, use recipient files like",
" curl -O https://github.com/"+err.username+".keys",
" age -R "+err.username+".keys")
}
if err != nil {
logFatalf("Error: %v", err)
errorf("%v", err)
}
recipients = append(recipients, r)
}
for _, name := range files {
recs, err := parseRecipientsFile(name)
if err != nil {
logFatalf("Error: failed to parse recipient file %q: %v", name, err)
errorf("failed to parse recipient file %q: %v", name, err)
}
recipients = append(recipients, recs...)
}
for _, name := range identities {
ids, err := parseIdentitiesFile(name)
if err != nil {
logFatalf("Error reading %q: %v", name, err)
errorf("reading %q: %v", name, err)
}
r, err := identitiesToRecipients(ids)
if err != nil {
logFatalf("Internal error processing %q: %v", name, err)
errorf("internal error processing %q: %v", name, err)
}
recipients = append(recipients, r...)
}
@@ -283,7 +290,7 @@ func encryptKeys(keys, files, identities []string, in io.Reader, out io.Writer,
func encryptPass(pass string, in io.Reader, out io.Writer, armor bool) {
r, err := age.NewScryptRecipient(pass)
if err != nil {
logFatalf("Error: %v", err)
errorf("%v", err)
}
encrypt([]age.Recipient{r}, in, out, armor)
}
@@ -293,20 +300,20 @@ func encrypt(recipients []age.Recipient, in io.Reader, out io.Writer, withArmor
a := armor.NewWriter(out)
defer func() {
if err := a.Close(); err != nil {
logFatalf("Error: %v", err)
errorf("%v", err)
}
}()
out = a
}
w, err := age.Encrypt(out, recipients...)
if err != nil {
logFatalf("Error: %v", err)
errorf("%v", err)
}
if _, err := io.Copy(w, in); err != nil {
logFatalf("Error: %v", err)
errorf("%v", err)
}
if err := w.Close(); err != nil {
logFatalf("Error: %v", err)
errorf("%v", err)
}
}
@@ -320,7 +327,7 @@ func decrypt(keys []string, in io.Reader, out io.Writer) {
for _, name := range keys {
ids, err := parseIdentitiesFile(name)
if err != nil {
logFatalf("Error reading %q: %v", name, err)
errorf("reading %q: %v", name, err)
}
identities = append(identities, ids...)
}
@@ -334,10 +341,10 @@ func decrypt(keys []string, in io.Reader, out io.Writer) {
r, err := age.Decrypt(in, identities...)
if err != nil {
logFatalf("Error: %v", err)
errorf("%v", err)
}
if _, err := io.Copy(out, r); err != nil {
logFatalf("Error: %v", err)
errorf("%v", err)
}
}
@@ -401,8 +408,19 @@ func (l *lazyOpener) Close() error {
return nil
}
func logFatalf(format string, v ...interface{}) {
_log.Printf(format, v...)
_log.Fatalf("[ Did age not do what you expected? Could an error be more useful?" +
" Tell us: https://filippo.io/age/report ]")
func errorf(format string, v ...interface{}) {
log.Printf("age: error: "+format, v...)
log.Fatalf("age: report unexpected or unhelpful errors at https://filippo.io/age/report")
}
func warningf(format string, v ...interface{}) {
log.Printf("age: warning: "+format, v...)
}
func errorWithHint(error string, hints ...string) {
log.Printf("age: error: %s", error)
for _, hint := range hints {
log.Printf("age: hint: %s", hint)
}
log.Fatalf("age: report unexpected or unhelpful errors at https://filippo.io/age/report")
}

View File

@@ -12,7 +12,6 @@ import (
"fmt"
"io"
"io/ioutil"
"log"
"os"
"strings"
@@ -26,6 +25,14 @@ import (
// stdinInUse is set in main. It's a singleton like os.Stdin.
var stdinInUse bool
type gitHubRecipientError struct {
username string
}
func (gitHubRecipientError) Error() string {
return `"github:" recipients were removed from the design`
}
func parseRecipient(arg string) (age.Recipient, error) {
switch {
case strings.HasPrefix(arg, "age1"):
@@ -34,8 +41,7 @@ func parseRecipient(arg string) (age.Recipient, error) {
return agessh.ParseRecipient(arg)
case strings.HasPrefix(arg, "github:"):
name := strings.TrimPrefix(arg, "github:")
return nil, fmt.Errorf(`"github:" recipients were removed from the design.`+"\n"+
"Instead, use recipient files like\n\n curl -O https://github.com/%s.keys\n age -R %s.keys\n\n", name, name)
return nil, gitHubRecipientError{name}
}
return nil, fmt.Errorf("unknown recipient type: %q", arg)
@@ -76,7 +82,7 @@ func parseRecipientsFile(name string) ([]age.Recipient, error) {
if err != nil {
if t, ok := sshKeyType(line); ok {
// Skip unsupported but valid SSH public keys with a warning.
log.Printf("Warning: recipients file %q: ignoring unsupported SSH key of type %q at line %d", name, t, n)
warningf("recipients file %q: ignoring unsupported SSH key of type %q at line %d", name, t, n)
continue
}
// Hide the error since it might unintentionally leak the contents
@@ -166,7 +172,7 @@ func parseIdentitiesFile(name string) ([]age.Identity, error) {
return string(pass), nil
},
NoMatchWarning: func() {
fmt.Fprintf(os.Stderr, "Warning: encrypted identity file %q didn't match file's recipients\n", name)
warningf("encrypted identity file %q didn't match file's recipients", name)
},
}}, nil
@@ -203,8 +209,7 @@ func parseSSHIdentity(name string, pemBytes []byte) ([]age.Identity, error) {
}
}
passphrasePrompt := func() ([]byte, error) {
prompt := fmt.Sprintf("Enter passphrase for %q:", name)
pass, err := readPassphrase(prompt)
pass, err := readPassphrase(fmt.Sprintf("Enter passphrase for %q:", name))
if err != nil {
return nil, fmt.Errorf("could not read passphrase for %q: %v", name, err)
}