cmd/age: initial support for SSH identities and recipients

Signed-off-by: Matt Layher <mdlayher@gmail.com>
This commit is contained in:
Matt Layher
2019-10-07 18:09:22 -04:00
committed by Filippo Valsorda
parent 7f61cf23bf
commit dd0939ffaa
3 changed files with 81 additions and 3 deletions

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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])