package sshmanager import ( "crypto/ed25519" "crypto/rand" "crypto/x509" "encoding/pem" "fmt" "os" "path/filepath" "strings" ) 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}) pubKey = fmt.Sprintf("%s %s", strings.TrimSpace(string(pub)), "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) } return privPath, pubPath, strings.TrimSpace(string(data)), 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 }