// Package sshkey turns derived bytes into an ed25519 SSH key. package sshkey import ( "crypto/ed25519" "encoding/pem" "errors" "fmt" "strings" "golang.org/x/crypto/ssh" ) // Application is the number this key type occupies in the derivation // path. It spells SSH the way BIP-85 spells RSA, as the ASCII codes of // the letters written out. const Application = 838372 // ErrSize is returned when the derived bytes are not the length an // ed25519 seed has to be. var ErrSize = errors.New("an ed25519 key needs 32 derived bytes") // Key is one ed25519 SSH key. type Key struct { private ed25519.PrivateKey } // New makes a key whose ed25519 seed is the derived bytes. func New(derived []byte) (*Key, error) { if len(derived) != ed25519.SeedSize { return nil, fmt.Errorf("%w, got %d", ErrSize, len(derived)) } return &Key{private: ed25519.NewKeyFromSeed(derived)}, nil } // Line returns the public key as one authorized_keys line, without a // trailing newline. func (k *Key) Line(comment string) (string, error) { public, err := ssh.NewPublicKey(k.private.Public()) if err != nil { return "", fmt.Errorf("encoding the public key: %w", err) } line := strings.TrimSpace(string(ssh.MarshalAuthorizedKey(public))) if comment != "" { line += " " + comment } return line, nil } // Block returns the unencrypted private key in the OpenSSH format that // ssh reads, ending in a newline. func (k *Key) Block(comment string) (string, error) { block, err := ssh.MarshalPrivateKey(k.private, comment) if err != nil { return "", fmt.Errorf("encoding the private key: %w", err) } return string(pem.EncodeToMemory(block)), nil }