age

Simple, secure encryption with UNIX-style composability.
git clone git://git.sgregoratto.me/age
Log | Files | Refs | README | LICENSE

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 }