age.go (3467B)
1 // Copyright 2019 Google LLC 2 // 3 // Use of this source code is governed by a BSD-style 4 // license that can be found in the LICENSE file or at 5 // https://developers.google.com/open-source/licenses/bsd 6 7 package main 8 9 import ( 10 "crypto/rand" 11 "flag" 12 "fmt" 13 "io" 14 "log" 15 "os" 16 "path/filepath" 17 "time" 18 19 "github.com/FiloSottile/age/internal/age" 20 ) 21 22 func main() { 23 var ( 24 gen = flag.Bool("G", false, "generate a new age key pair") 25 dec = flag.Bool("D", false, "decrypt the input") 26 help = flag.Bool("h", false, "show this help") 27 inFile = flag.String("i", "", "output file") 28 outFile = flag.String("o", "", "input file") 29 30 in = os.Stdin 31 out = os.Stdout 32 err error 33 progname = filepath.Base(os.Args[0]) 34 ) 35 36 log.SetFlags(0) 37 log.SetPrefix(progname + ": ") 38 39 flag.Usage = func() { 40 fmt.Fprintf(os.Stderr, 41 `usage: %s [-o file] -G 42 %s [-i file] [-o file] recipient ... 43 %s [-i file] [-o file] -D [identity-file ... ] 44 `, progname, progname, progname) 45 os.Exit(1) 46 } 47 flag.Parse() 48 args := flag.Args() 49 50 if *help { 51 flag.Usage() 52 } 53 54 if *dec && *gen { 55 log.Printf("multiple modes selected") 56 flag.Usage() 57 } 58 59 if *inFile != "" { 60 if *gen { 61 log.Printf("-g takes no input") 62 flag.Usage() 63 } 64 65 in, err = os.Open(*inFile) 66 if err != nil { 67 log.Fatalf("cannot open file %q: %v", *inFile, err) 68 } 69 } 70 71 if *outFile != "" { 72 out, err = os.Create(*outFile) 73 if err != nil { 74 log.Fatalf("cannot open file %q: %v", *outFile, err) 75 } 76 } 77 78 switch { 79 case *gen: 80 if len(args) != 0 { 81 log.Printf("-g takes no arguments") 82 flag.Usage() 83 } 84 generate(out) 85 case *dec: 86 decrypt(in, out, args) 87 default: 88 encrypt(in, out, args) 89 } 90 } 91 92 func generate(out *os.File) { 93 key := make([]byte, 32) 94 if _, err := rand.Read(key); err != nil { 95 log.Fatalf("cannot get random bytes: %v", err) 96 } 97 k, err := age.NewX25519Identity(key) 98 if err != nil { 99 log.Fatalf("cannot create new identity: %v", err) 100 } 101 102 fmt.Fprintf(out, "# created: %s\n", time.Now().Format(time.RFC3339)) 103 fmt.Fprintf(out, "# %s\n", k.Recipient()) 104 fmt.Fprintf(out, "%s\n", k) 105 } 106 107 func encrypt(in, out *os.File, args []string) { 108 var recipients []age.Recipient 109 for _, arg := range args { 110 r, err := parseRecipient(arg) 111 if err != nil { 112 log.Fatalf("cannot parse recipient %q: %v", arg, err) 113 } 114 recipients = append(recipients, r) 115 } 116 if len(recipients) == 0 { 117 log.Printf("no recipients") 118 } 119 120 w, err := age.Encrypt(out, recipients...) 121 if err != nil { 122 log.Fatalf("cannot initialize encryption: %v", err) 123 } 124 if _, err := io.Copy(w, in); err != nil { 125 log.Fatalf("cannot encrypt input: %v", err) 126 } 127 if err := w.Close(); err != nil { 128 log.Fatalf("cannot close output: %v", err) 129 } 130 } 131 132 func decrypt(in, out *os.File, args []string) { 133 var identities []age.Identity 134 // TODO: use the default location if no arguments are provided. 135 for _, name := range args { 136 var ( 137 ids []age.Identity 138 err error 139 ) 140 141 // TODO: smarter detection logic than looking for .ssh/* in the path. 142 if filepath.Base(filepath.Dir(name)) == ".ssh" { 143 ids, err = parseSSHIdentity(name) 144 } else { 145 ids, err = parseIdentitiesFile(name) 146 } 147 if err != nil { 148 log.Fatalf("cannot parse identity %q: %v", name, err) 149 } 150 identities = append(identities, ids...) 151 } 152 153 r, err := age.Decrypt(in, identities...) 154 if err != nil { 155 log.Fatalf("cannot initialize encryption: %v", err) 156 } 157 if _, err := io.Copy(out, r); err != nil { 158 log.Fatalf("cannot decrypt input: %v", err) 159 } 160 }