package security import ( "crypto/ed25519" "crypto/rand" "os" "path/filepath" "strings" "testing" "golang.org/x/crypto/ssh" ) func TestGenerateOrLoadSSHKey_Generates(t *testing.T) { dir := t.TempDir() keyPEM, pubLine, err := GenerateOrLoadSSHKey(dir) if err != nil { t.Fatalf("generate: %v", err) } // Private key file exists with mode 0600. keyPath := filepath.Join(dir, sshKeyFile) info, err := os.Stat(keyPath) if err != nil { t.Fatalf("stat key: %v", err) } if info.Mode().Perm() != SSHKeyMode { t.Errorf("key mode = %04o, want %04o", info.Mode().Perm(), SSHKeyMode) } // Public key file exists with mode 0644. pubPath := filepath.Join(dir, sshPubFile) info, err = os.Stat(pubPath) if err != nil { t.Fatalf("stat pub: %v", err) } if info.Mode().Perm() != SSHPubMode { t.Errorf("pub mode = %04o, want %04o", info.Mode().Perm(), SSHPubMode) } // Public key line is ssh-ed25519 format. if !strings.HasPrefix(string(pubLine), "ssh-ed25519 ") { t.Errorf("pub line = %q, want ssh-ed25519 prefix", string(pubLine)) } // Private key PEM parses with ssh.ParsePrivateKey (PKCS8). signer, err := ssh.ParsePrivateKey(keyPEM) if err != nil { t.Fatalf("parse private key: %v", err) } if signer.PublicKey().Type() != "ssh-ed25519" { t.Errorf("signer key type = %q, want ssh-ed25519", signer.PublicKey().Type()) } } func TestGenerateOrLoadSSHKey_IdempotentLoad(t *testing.T) { dir := t.TempDir() // First call generates. keyPEM1, pubLine1, err := GenerateOrLoadSSHKey(dir) if err != nil { t.Fatalf("first generate: %v", err) } // Second call loads existing. keyPEM2, pubLine2, err := GenerateOrLoadSSHKey(dir) if err != nil { t.Fatalf("second load: %v", err) } if string(keyPEM1) != string(keyPEM2) { t.Error("key was regenerated on second call (D-036 idempotency violation)") } if string(pubLine1) != string(pubLine2) { t.Error("pub was regenerated on second call (D-036 idempotency violation)") } } func TestGenerateOrLoadSSHKey_EmptyDir(t *testing.T) { _, _, err := GenerateOrLoadSSHKey("") if err == nil { t.Error("expected error for empty dir") } } func TestGenerateOrLoadSSHKey_CreatesDir(t *testing.T) { dir := filepath.Join(t.TempDir(), "nested", "ssh-dir") if _, _, err := GenerateOrLoadSSHKey(dir); err != nil { t.Fatalf("generate with nested dir: %v", err) } if _, err := os.Stat(dir); err != nil { t.Errorf("nested dir not created: %v", err) } } func TestSSHFingerprintSHA256_Ed25519(t *testing.T) { pub, _, err := ed25519.GenerateKey(rand.Reader) if err != nil { t.Fatalf("ed25519 gen: %v", err) } sshPub, err := ssh.NewPublicKey(pub) if err != nil { t.Fatalf("new pubkey: %v", err) } got := SSHFingerprintSHA256(sshPub) // Canonical form: SHA256: followed by unpadded base64. if !strings.HasPrefix(got, "SHA256:") { t.Fatalf("fingerprint = %q, want SHA256: prefix", got) } // Must match the reference implementation exactly. want := ssh.FingerprintSHA256(sshPub) if got != want { t.Errorf("SSHFingerprintSHA256 = %q, want %q", got, want) } } func TestSSHFingerprintSHA256_StableAcrossCalls(t *testing.T) { pub, _, err := ed25519.GenerateKey(rand.Reader) if err != nil { t.Fatalf("ed25519 gen: %v", err) } sshPub, err := ssh.NewPublicKey(pub) if err != nil { t.Fatalf("new pubkey: %v", err) } a := SSHFingerprintSHA256(sshPub) b := SSHFingerprintSHA256(sshPub) if a != b { t.Errorf("fingerprint not stable: %q vs %q", a, b) } }