diff --git a/cmd/age-keygen/keygen.go b/cmd/age-keygen/keygen.go index 0b4797d..9303233 100644 --- a/cmd/age-keygen/keygen.go +++ b/cmd/age-keygen/keygen.go @@ -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) +} diff --git a/cmd/age/age.go b/cmd/age/age.go index 32ac118..0079f11 100644 --- a/cmd/age/age.go +++ b/cmd/age/age.go @@ -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") } diff --git a/cmd/age/parse.go b/cmd/age/parse.go index 0ae87e9..1121590 100644 --- a/cmd/age/parse.go +++ b/cmd/age/parse.go @@ -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) }