package sshmanager import ( "crypto/ed25519" "crypto/rand" "crypto/x509" "encoding/pem" "fmt" "os" "path/filepath" "strings" "golang.org/x/crypto/ssh" ) const ServerKeyLabel = "server" func EnsureServerKey(sshDir string) (privPath, pubPath string, pubKey string, err error) { if err := os.MkdirAll(sshDir, 0700); err != nil { return "", "", "", fmt.Errorf("creating ssh dir: %w", err) } privPath = filepath.Join(sshDir, "id_ed25519") pubPath = filepath.Join(sshDir, "id_ed25519.pub") if _, err := os.Stat(privPath); os.IsNotExist(err) { pub, priv, err := ed25519.GenerateKey(rand.Reader) if err != nil { return "", "", "", fmt.Errorf("generating ed25519 key: %w", err) } privFile, err := os.OpenFile(privPath, os.O_CREATE|os.O_WRONLY, 0600) if err != nil { return "", "", "", fmt.Errorf("creating private key file: %w", err) } defer privFile.Close() privBytes, err := x509.MarshalPKCS8PrivateKey(priv) if err != nil { return "", "", "", fmt.Errorf("marshaling private key: %w", err) } pem.Encode(privFile, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}) sshPubKey, err := ssh.NewPublicKey(pub) if err != nil { return "", "", "", fmt.Errorf("wrapping public key: %w", err) } pubKey = strings.TrimSpace(string(ssh.MarshalAuthorizedKey(sshPubKey))) + " syncserver" if err := os.WriteFile(pubPath, []byte(pubKey), 0644); err != nil { return "", "", "", fmt.Errorf("writing public key: %w", err) } return privPath, pubPath, pubKey, nil } else if err != nil { return "", "", "", fmt.Errorf("checking private key: %w", err) } data, err := os.ReadFile(pubPath) if err != nil { return "", "", "", fmt.Errorf("reading public key: %w", err) } pubKey = strings.TrimSpace(string(data)) if _, err := Fingerprint(pubKey); err != nil { sshPubKey, err := PublicKeyFromPrivateKeyFile(privPath) if err != nil { return "", "", "", fmt.Errorf("recovering public key: %w", err) } pubKey = sshPubKey + " syncserver" if err := os.WriteFile(pubPath, []byte(pubKey), 0644); err != nil { return "", "", "", fmt.Errorf("rewriting public key: %w", err) } } return privPath, pubPath, pubKey, nil } func ReadPrivateKey(path string) ([]byte, error) { data, err := os.ReadFile(path) if err != nil { return nil, err } block, _ := pem.Decode(data) if block == nil { return nil, fmt.Errorf("no PEM block found") } return block.Bytes, nil }