Skeleton, mnemonic input, derivation, and the ssh pub and priv commands (closes #1)
All checks were successful
check / check (push) Successful in 5s
All checks were successful
check / check (push) Successful in 5s
The module, the script entrypoints and Makefile, Docker-only linting, the mnemonic sources in the specified order with their refusals, the BIP-85 derivation, and keyfunc ssh pub and priv with the README test vectors as tests. Two review rounds; the second passed with no findings. Model: opus-5 (implementation and review); fable-5-1 (landing)
This commit was merged in pull request #5.
This commit is contained in:
63
internal/sshkey/sshkey.go
Normal file
63
internal/sshkey/sshkey.go
Normal file
@@ -0,0 +1,63 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user