package userbasekeys import ( "bytes" "crypto/ed25519" "encoding/pem" "errors" "filippo.io/age" "golang.org/x/crypto/ssh" ) var ( ErrIdentity = errors.New("not an age X25519 identity") ErrRecipient = errors.New("not an age X25519 recipient") ErrKeyFile = errors.New("not an OpenSSH private key file") ErrEncrypted = errors.New("private key file is passphrase-protected") ErrKeyType = errors.New("private key is not ed25519") ) const openSSHBlock = "OPENSSH PRIVATE KEY" func ParseIdentity(s string) (*age.X25519Identity, error) { id, err := age.ParseX25519Identity(s) if err != nil { return nil, ErrIdentity } return id, nil } func ParseRecipient(s string) (*age.X25519Recipient, error) { r, err := age.ParseX25519Recipient(s) if err != nil || r.String() != s { return nil, ErrRecipient } return r, nil } func ParseSigner(keyFile []byte) (ssh.Signer, error) { block, rest := pem.Decode(keyFile) if block == nil || block.Type != openSSHBlock || len(bytes.TrimSpace(rest)) != 0 || !bytes.HasPrefix(bytes.TrimSpace(keyFile), []byte("-----BEGIN ")) { return nil, ErrKeyFile } key, err := ssh.ParseRawPrivateKey(keyFile) var missing *ssh.PassphraseMissingError switch { case errors.As(err, &missing): return nil, ErrEncrypted case err != nil: return nil, ErrKeyFile } ed, ok := key.(*ed25519.PrivateKey) if !ok { return nil, ErrKeyType } signer, err := ssh.NewSignerFromKey(*ed) if err != nil { return nil, ErrKeyFile } return signer, nil }