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}