age

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

commit dd0939ffaa6930317004111d9592dc0982a72a5b
parent 7f61cf23bf90509d3cabab43f7bc25619b89552d
Author: Matt Layher <mdlayher@gmail.com>
Date:   Mon,  7 Oct 2019 18:09:22 -0400

cmd/age: initial support for SSH identities and recipients

Signed-off-by: Matt Layher <mdlayher@gmail.com>

Diffstat:
Mcmd/age/age.go | 13++++++++++++-
Mcmd/age/parse.go | 33+++++++++++++++++++++++++++++++--
Minternal/age/ssh.go | 38++++++++++++++++++++++++++++++++++++++
3 files changed, 81 insertions(+), 3 deletions(-)

diff --git a/cmd/age/age.go b/cmd/age/age.go @@ -13,6 +13,7 @@ import ( "io" "log" "os" + "path/filepath" "time" "github.com/FiloSottile/age/internal/age" @@ -89,7 +90,17 @@ 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) + var ( + ids []age.Identity + err error + ) + + // TODO: smarter detection logic than looking for .ssh/* in the path. + if filepath.Base(filepath.Dir(name)) == ".ssh" { + ids, err = parseSSHIdentity(name) + } else { + ids, err = parseIdentitiesFile(name) + } if err != nil { log.Fatalf("Error: %v", err) } diff --git a/cmd/age/parse.go b/cmd/age/parse.go @@ -9,6 +9,8 @@ package main import ( "bufio" "fmt" + "io" + "io/ioutil" "os" "strings" @@ -16,10 +18,14 @@ import ( ) func parseRecipient(arg string) (age.Recipient, error) { - if strings.HasPrefix(arg, "pubkey:") { + switch { + case strings.HasPrefix(arg, "pubkey:"): return age.ParseX25519Recipient(arg) + case strings.HasPrefix(arg, "ssh-"): + return age.ParseSSHRecipient(arg) } - return nil, fmt.Errorf("unknown recipient type: %s", arg) + + return nil, fmt.Errorf("unknown recipient type: %q", arg) } func parseIdentitiesFile(name string) ([]age.Identity, error) { @@ -50,3 +56,26 @@ func parseIdentitiesFile(name string) ([]age.Identity, error) { } return ids, nil } + +func parseSSHIdentity(name string) ([]age.Identity, error) { + f, err := os.Open(name) + if err != nil { + return nil, fmt.Errorf("failed to open file: %v", err) + } + defer f.Close() + + // Don't allow unbounded reads. + // TODO: support for multiple keys in the same stream, such as user.keys + // on GitHub. + pemBytes, err := ioutil.ReadAll(io.LimitReader(f, 1<<20)) + if err != nil { + return nil, fmt.Errorf("failed to read %q: %v", name, err) + } + + id, err := age.ParseSSHIdentity(pemBytes) + if err != nil { + return nil, fmt.Errorf("malformed SSH identity in %q: %v", name, err) + } + + return []age.Identity{id}, nil +} diff --git a/internal/age/ssh.go b/internal/age/ssh.go @@ -160,6 +160,28 @@ func NewSSHEd25519Recipient(pk ssh.PublicKey) (*SSHEd25519Recipient, error) { return r, nil } +func ParseSSHRecipient(s string) (Recipient, error) { + pubKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(s)) + if err != nil { + return nil, fmt.Errorf("malformed SSH recipient: %q: %v", s, err) + } + + var r Recipient + switch t := pubKey.Type(); t { + case "ssh-rsa": + r, err = NewSSHRSARecipient(pubKey) + case "ssh-ed25519": + r, err = NewSSHEd25519Recipient(pubKey) + default: + return nil, fmt.Errorf("unknown SSH recipient type: %q", t) + } + if err != nil { + return nil, fmt.Errorf("malformed SSH recipient: %q: %v", s, err) + } + + return r, nil +} + var curve25519P, _ = new(big.Int).SetString("57896044618658097711785492504343953926634992332820282019728792003956564819949", 10) func ed25519PublicKeyToCurve25519(pk ed25519.PublicKey) []byte { @@ -260,6 +282,22 @@ func NewSSHEd25519Identity(key ed25519.PrivateKey) (*SSHEd25519Identity, error) return i, nil } +func ParseSSHIdentity(pemBytes []byte) (Identity, error) { + k, err := ssh.ParseRawPrivateKey(pemBytes) + if err != nil { + return nil, err + } + + switch k := k.(type) { + case *ed25519.PrivateKey: + return NewSSHEd25519Identity(*k) + case *rsa.PrivateKey: + return NewSSHRSAIdentity(k) + } + + return nil, fmt.Errorf("unsupported SSH identity type: %T", k) +} + func ed25519PrivateKeyToCurve25519(pk ed25519.PrivateKey) []byte { h := sha512.New() h.Write(pk[:32])