package keygen import ( "os" "path/filepath" "strings" "testing" "golang.org/x/crypto/ssh" ) func TestGenerateED25519(t *testing.T) { r, err := Generate(AlgoED25519, "test@dial", nil) if err != nil { t.Fatal(err) } // Private key must parse back as an OpenSSH key. if _, err := ssh.ParseRawPrivateKey(r.PrivatePEM); err != nil { t.Fatalf("private key does not parse: %v", err) } // Public line must parse and carry the comment. pk, comment, _, _, err := ssh.ParseAuthorizedKey(r.PublicLine) if err != nil { t.Fatalf("public key does not parse: %v", err) } if pk.Type() != ssh.KeyAlgoED25519 { t.Errorf("expected ed25519, got %s", pk.Type()) } if comment != "test@dial" { t.Errorf("comment = %q, want test@dial", comment) } } func TestGenerateWithPassphrase(t *testing.T) { r, err := Generate(AlgoED25519, "c", []byte("hunter2")) if err != nil { t.Fatal(err) } if _, err := ssh.ParseRawPrivateKey(r.PrivatePEM); err == nil { t.Error("encrypted key should not parse without passphrase") } if _, err := ssh.ParseRawPrivateKeyWithPassphrase(r.PrivatePEM, []byte("hunter2")); err != nil { t.Errorf("key should parse with passphrase: %v", err) } } func TestGenerateRSA(t *testing.T) { r, err := Generate(AlgoRSA, "c", nil) if err != nil { t.Fatal(err) } pk, _, _, _, err := ssh.ParseAuthorizedKey(r.PublicLine) if err != nil { t.Fatal(err) } if pk.Type() != ssh.KeyAlgoRSA { t.Errorf("expected rsa, got %s", pk.Type()) } } func TestWriteKeyPairPermsAndNoOverwrite(t *testing.T) { dir := t.TempDir() r, _ := Generate(AlgoED25519, "c", nil) priv, pub, err := WriteKeyPair(dir, "mykey", r) if err != nil { t.Fatal(err) } fi, _ := os.Stat(priv) if fi.Mode().Perm() != 0o600 { t.Errorf("private key perms = %o, want 600", fi.Mode().Perm()) } pfi, _ := os.Stat(pub) if pfi.Mode().Perm() != 0o644 { t.Errorf("public key perms = %o, want 644", pfi.Mode().Perm()) } // Second write must refuse to overwrite. if _, _, err := WriteKeyPair(dir, "mykey", r); err == nil { t.Error("expected refusal to overwrite existing key") } } func TestAppendHostEntryBackupAndDup(t *testing.T) { dir := t.TempDir() cfg := filepath.Join(dir, "config") if err := os.WriteFile(cfg, []byte("Host existing\n HostName e\n"), 0o600); err != nil { t.Fatal(err) } backup, err := AppendHostEntry(cfg, HostEntry{ Alias: "newhost", HostName: "10.0.0.1", User: "ben", Port: "22", IdentityFile: "~/.ssh/mykey", Tags: []string{"prod"}, }) if err != nil { t.Fatal(err) } if backup == "" { t.Error("expected a backup path") } if _, err := os.Stat(backup); err != nil { t.Errorf("backup not written: %v", err) } b, _ := os.ReadFile(cfg) got := string(b) for _, want := range []string{"# tag: prod", "Host newhost", "HostName 10.0.0.1", "IdentitiesOnly yes"} { if !strings.Contains(got, want) { t.Errorf("config missing %q\n%s", want, got) } } // Port 22 is the default and should be omitted. if strings.Contains(got, "Port 22") { t.Error("default port 22 should not be written") } // Appending the same alias must be refused. if _, err := AppendHostEntry(cfg, HostEntry{Alias: "newhost", HostName: "x"}); err == nil { t.Error("expected duplicate-alias refusal") } } func TestValidateRejectsInjection(t *testing.T) { dir := t.TempDir() cfg := filepath.Join(dir, "config") if _, err := AppendHostEntry(cfg, HostEntry{Alias: "a b"}); err == nil { t.Error("alias with space should be rejected") } if _, err := AppendHostEntry(cfg, HostEntry{Alias: "ok", HostName: "x\n ProxyCommand evil"}); err == nil { t.Error("newline injection should be rejected") } }