commit 0940f184fb8fdf8f729361a79443ddd4e62f6bb9
parent 37d95cc84a986bf2582b3daf9a5aa5db009ecfe1
Author: Filippo Valsorda <hi@filippo.io>
Date: Sun, 6 Oct 2019 23:16:20 -0400
cmd/age: add a prototype of the command line tool
Diffstat:
13 files changed, 266 insertions(+), 11 deletions(-)
diff --git a/Dockerfile.gofuzz b/Dockerfile.gofuzz
@@ -1,9 +0,0 @@
-FROM golang:1.12-alpine3.10
-RUN apk add --no-cache git
-RUN go get github.com/dvyukov/go-fuzz/...
-ADD . $GOPATH/src/github.com/FiloSottile/age/
-WORKDIR $GOPATH/src/github.com/FiloSottile/age
-RUN GO111MODULE=on go mod vendor
-RUN go-fuzz-build ./internal/format
-VOLUME /workdir
-ENTRYPOINT ["go-fuzz", "-workdir", "/workdir", "-bin", "format-fuzz.zip"]
diff --git a/cmd/age/age.go b/cmd/age/age.go
@@ -0,0 +1,106 @@
+// Copyright 2019 Google LLC
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd
+
+package main
+
+import (
+ "crypto/rand"
+ "flag"
+ "fmt"
+ "io"
+ "log"
+ "os"
+ "time"
+
+ "github.com/FiloSottile/age/internal/age"
+)
+
+func main() {
+ log.SetFlags(0)
+
+ generateFlag := flag.Bool("generate", false, "generate a new age key pair")
+ decryptFlag := flag.Bool("d", false, "decrypt the input")
+ flag.Parse()
+
+ switch {
+ case *generateFlag:
+ if *decryptFlag {
+ log.Fatalf("Invalid flag combination")
+ }
+ generate()
+ case *decryptFlag:
+ if *generateFlag {
+ log.Fatalf("Invalid flag combination")
+ }
+ decrypt()
+ default:
+ encrypt()
+ }
+}
+
+func generate() {
+ if len(flag.Args()) != 0 {
+ log.Fatalf("-generate takes no arguments")
+ }
+
+ key := make([]byte, 32)
+ if _, err := rand.Read(key); err != nil {
+ log.Fatalf("Internal error: %v", err)
+ }
+ k, err := age.NewX25519Identity(key)
+ if err != nil {
+ log.Fatalf("Internal error: %v", err)
+ }
+
+ fmt.Printf("# created: %s\n", time.Now().Format(time.RFC3339))
+ fmt.Printf("# %s\n", k.Recipient())
+ fmt.Printf("%s\n", k)
+}
+
+func encrypt() {
+ var recipients []age.Recipient
+ for _, arg := range flag.Args() {
+ r, err := parseRecipient(arg)
+ if err != nil {
+ log.Fatalf("Error: %v", err)
+ }
+ recipients = append(recipients, r)
+ }
+ if len(recipients) == 0 {
+ log.Fatalf("Missing recipients!")
+ }
+
+ w, err := age.Encrypt(os.Stdout, recipients...)
+ if err != nil {
+ log.Fatalf("Error initializing encryption: %v", err)
+ }
+ if _, err := io.Copy(w, os.Stdin); err != nil {
+ log.Fatalf("Error encrypting the input: %v", err)
+ }
+ if err := w.Close(); err != nil {
+ log.Fatalf("Error finalizing encryption: %v", err)
+ }
+}
+
+func decrypt() {
+ var identities []age.Identity
+ // TODO: use the default location if no arguments are provided.
+ for _, name := range flag.Args() {
+ ids, err := parseIdentitiesFile(name)
+ if err != nil {
+ log.Fatalf("Error: %v", err)
+ }
+ identities = append(identities, ids...)
+ }
+
+ r, err := age.Decrypt(os.Stdin, identities...)
+ if err != nil {
+ log.Fatalf("Error initializing decryption: %v", err)
+ }
+ if _, err := io.Copy(os.Stdout, r); err != nil {
+ log.Fatalf("Error decrypting the input: %v", err)
+ }
+}
diff --git a/cmd/age/parse.go b/cmd/age/parse.go
@@ -0,0 +1,52 @@
+// Copyright 2019 Google LLC
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd
+
+package main
+
+import (
+ "bufio"
+ "fmt"
+ "os"
+ "strings"
+
+ "github.com/FiloSottile/age/internal/age"
+)
+
+func parseRecipient(arg string) (age.Recipient, error) {
+ if strings.HasPrefix(arg, "pubkey:") {
+ return age.ParseX25519Recipient(arg)
+ }
+ return nil, fmt.Errorf("unknown recipient type: %s", arg)
+}
+
+func parseIdentitiesFile(name string) ([]age.Identity, error) {
+ f, err := os.Open(name)
+ if err != nil {
+ return nil, fmt.Errorf("failed to open file: %v", err)
+ }
+
+ var ids []age.Identity
+ scanner := bufio.NewScanner(f)
+ for scanner.Scan() {
+ line := scanner.Text()
+ if strings.HasPrefix(line, "#") || line == "" {
+ continue
+ }
+ i, err := age.ParseX25519Identity(line)
+ if err != nil {
+ return nil, fmt.Errorf("malformed secret keys file %q: %v", name, err)
+ }
+ ids = append(ids, i)
+ }
+ if err := scanner.Err(); err != nil {
+ return nil, fmt.Errorf("failed to read %q: %v", name, err)
+ }
+
+ if len(ids) == 0 {
+ return nil, fmt.Errorf("no secret keys found in %q", name)
+ }
+ return ids, nil
+}
diff --git a/internal/age/age.go b/internal/age/age.go
@@ -1,3 +1,9 @@
+// Copyright 2019 Google LLC
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd
+
package age
import (
diff --git a/internal/age/age_test.go b/internal/age/age_test.go
@@ -1,3 +1,9 @@
+// Copyright 2019 Google LLC
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd
+
package age_test
import (
diff --git a/internal/age/primitives.go b/internal/age/primitives.go
@@ -1,3 +1,9 @@
+// Copyright 2019 Google LLC
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd
+
package age
import (
diff --git a/internal/age/recipients_test.go b/internal/age/recipients_test.go
@@ -1,3 +1,9 @@
+// Copyright 2019 Google LLC
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd
+
package age_test
import (
diff --git a/internal/age/scrypt.go b/internal/age/scrypt.go
@@ -1,3 +1,9 @@
+// Copyright 2019 Google LLC
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd
+
package age
import (
diff --git a/internal/age/ssh.go b/internal/age/ssh.go
@@ -1,3 +1,9 @@
+// Copyright 2019 Google LLC
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd
+
package age
import (
diff --git a/internal/age/x25519.go b/internal/age/x25519.go
@@ -1,3 +1,9 @@
+// Copyright 2019 Google LLC
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd
+
package age
import (
@@ -6,6 +12,7 @@ import (
"errors"
"fmt"
"io"
+ "strings"
"github.com/FiloSottile/age/internal/format"
"golang.org/x/crypto/chacha20poly1305"
@@ -32,6 +39,22 @@ func NewX25519Recipient(publicKey []byte) (*X25519Recipient, error) {
return r, nil
}
+func ParseX25519Recipient(s string) (*X25519Recipient, error) {
+ if !strings.HasPrefix(s, "pubkey:") {
+ return nil, fmt.Errorf("malformed recipient: %s", s)
+ }
+ pubKey := strings.TrimPrefix(s, "pubkey:")
+ k, err := format.DecodeString(pubKey)
+ if err != nil {
+ return nil, fmt.Errorf("malformed recipient: %s", s)
+ }
+ r, err := NewX25519Recipient(k)
+ if err != nil {
+ return nil, fmt.Errorf("malformed recipient: %s", s)
+ }
+ return r, nil
+}
+
func (r *X25519Recipient) Wrap(fileKey []byte) (*format.Recipient, error) {
var ephemeral, ourPublicKey [32]byte
if _, err := rand.Read(ephemeral[:]); err != nil {
@@ -65,6 +88,10 @@ func (r *X25519Recipient) Wrap(fileKey []byte) (*format.Recipient, error) {
return l, nil
}
+func (r *X25519Recipient) String() string {
+ return "pubkey:" + format.EncodeToString(r.theirPublicKey[:])
+}
+
type X25519Identity struct {
secretKey, ourPublicKey [32]byte
}
@@ -83,6 +110,22 @@ func NewX25519Identity(secretKey []byte) (*X25519Identity, error) {
return i, nil
}
+func ParseX25519Identity(s string) (*X25519Identity, error) {
+ if !strings.HasPrefix(s, "AGE_SECRET_KEY_") {
+ return nil, fmt.Errorf("malformed secret key: %s", s)
+ }
+ privKey := strings.TrimPrefix(s, "AGE_SECRET_KEY_")
+ k, err := format.DecodeString(privKey)
+ if err != nil {
+ return nil, fmt.Errorf("malformed secret key: %s", s)
+ }
+ r, err := NewX25519Identity(k)
+ if err != nil {
+ return nil, fmt.Errorf("malformed secret key: %s", s)
+ }
+ return r, nil
+}
+
func (i *X25519Identity) Unwrap(block *format.Recipient) ([]byte, error) {
if block.Type != "X25519" {
return nil, errors.New("wrong recipient block type")
@@ -121,3 +164,13 @@ func (i *X25519Identity) Unwrap(block *format.Recipient) ([]byte, error) {
}
return fileKey, nil
}
+
+func (i *X25519Identity) Recipient() *X25519Recipient {
+ r := &X25519Recipient{}
+ r.theirPublicKey = i.ourPublicKey
+ return r
+}
+
+func (i *X25519Identity) String() string {
+ return "AGE_SECRET_KEY_" + format.EncodeToString(i.secretKey[:])
+}
diff --git a/internal/format/format.go b/internal/format/format.go
@@ -1,3 +1,9 @@
+// Copyright 2019 Google LLC
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd
+
package format
import (
diff --git a/internal/stream/stream.go b/internal/stream/stream.go
@@ -1,3 +1,9 @@
+// Copyright 2019 Google LLC
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd
+
package stream
import (
@@ -156,8 +162,7 @@ func (w *Writer) Write(p []byte) (n int, err error) {
total := len(p)
for len(p) > 0 {
- free := ChunkSize - len(w.unwritten)
- freeBuf := w.buf[len(w.unwritten) : len(w.unwritten)+free]
+ freeBuf := w.buf[len(w.unwritten):ChunkSize]
n := copy(freeBuf, p)
p = p[n:]
w.unwritten = w.unwritten[:len(w.unwritten)+n]
diff --git a/internal/stream/stream_test.go b/internal/stream/stream_test.go
@@ -1,3 +1,9 @@
+// Copyright 2019 Google LLC
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd
+
package stream_test
import (