Raw
 1package userbasekeys
 2
 3import (
 4	"bytes"
 5	"crypto/ed25519"
 6	"encoding/pem"
 7	"errors"
 8
 9	"filippo.io/age"
10	"golang.org/x/crypto/ssh"
11)
12
13var (
14	ErrIdentity  = errors.New("not an age X25519 identity")
15	ErrRecipient = errors.New("not an age X25519 recipient")
16	ErrKeyFile   = errors.New("not an OpenSSH private key file")
17	ErrEncrypted = errors.New("private key file is passphrase-protected")
18	ErrKeyType   = errors.New("private key is not ed25519")
19)
20
21const openSSHBlock = "OPENSSH PRIVATE KEY"
22
23func ParseIdentity(s string) (*age.X25519Identity, error) {
24	id, err := age.ParseX25519Identity(s)
25	if err != nil {
26		return nil, ErrIdentity
27	}
28	return id, nil
29}
30
31func ParseRecipient(s string) (*age.X25519Recipient, error) {
32	r, err := age.ParseX25519Recipient(s)
33	if err != nil || r.String() != s {
34		return nil, ErrRecipient
35	}
36	return r, nil
37}
38
39func ParseSigner(keyFile []byte) (ssh.Signer, error) {
40	block, rest := pem.Decode(keyFile)
41	if block == nil || block.Type != openSSHBlock || len(bytes.TrimSpace(rest)) != 0 || !bytes.HasPrefix(bytes.TrimSpace(keyFile), []byte("-----BEGIN ")) {
42		return nil, ErrKeyFile
43	}
44	key, err := ssh.ParseRawPrivateKey(keyFile)
45	var missing *ssh.PassphraseMissingError
46	switch {
47	case errors.As(err, &missing):
48		return nil, ErrEncrypted
49	case err != nil:
50		return nil, ErrKeyFile
51	}
52	ed, ok := key.(*ed25519.PrivateKey)
53	if !ok {
54		return nil, ErrKeyType
55	}
56	signer, err := ssh.NewSignerFromKey(*ed)
57	if err != nil {
58		return nil, ErrKeyFile
59	}
60	return signer, nil
61}