116 lines
3 KiB
Go
116 lines
3 KiB
Go
|
|
package credential
|
||
|
|
|
||
|
|
import (
|
||
|
|
"crypto/ed25519"
|
||
|
|
"os"
|
||
|
|
"path/filepath"
|
||
|
|
"runtime"
|
||
|
|
"testing"
|
||
|
|
|
||
|
|
"golang.org/x/crypto/ssh"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestGenerateSSHKey_CreatesFiles(t *testing.T) {
|
||
|
|
dir := t.TempDir()
|
||
|
|
keyPath := filepath.Join(dir, "test_ed25519.key")
|
||
|
|
|
||
|
|
if err := GenerateSSHKey(keyPath); err != nil {
|
||
|
|
t.Fatalf("GenerateSSHKey() error = %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Private key must exist.
|
||
|
|
privInfo, err := os.Stat(keyPath)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("private key file missing: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check permissions on non-Windows (Windows does not support Unix permission bits).
|
||
|
|
if runtime.GOOS != "windows" {
|
||
|
|
if got := privInfo.Mode().Perm(); got != 0o600 {
|
||
|
|
t.Errorf("private key permissions = %04o, want 0600", got)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Public key must exist.
|
||
|
|
pubPath := keyPath + ".pub"
|
||
|
|
pubInfo, err := os.Stat(pubPath)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("public key file missing: %v", err)
|
||
|
|
}
|
||
|
|
if runtime.GOOS != "windows" {
|
||
|
|
if got := pubInfo.Mode().Perm(); got != 0o644 {
|
||
|
|
t.Errorf("public key permissions = %04o, want 0644", got)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Private key must be parseable as an OpenSSH ed25519 key.
|
||
|
|
privPEM, err := os.ReadFile(keyPath)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("read private key: %v", err)
|
||
|
|
}
|
||
|
|
privKey, err := ssh.ParseRawPrivateKey(privPEM)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("parse private key: %v", err)
|
||
|
|
}
|
||
|
|
if _, ok := privKey.(*ed25519.PrivateKey); !ok {
|
||
|
|
t.Errorf("private key type = %T, want *ed25519.PrivateKey", privKey)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Public key must be parseable as authorized_keys line.
|
||
|
|
pubBytes, err := os.ReadFile(pubPath)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("read public key: %v", err)
|
||
|
|
}
|
||
|
|
pubKey, _, _, rest, err := ssh.ParseAuthorizedKey(pubBytes)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("parse public key: %v", err)
|
||
|
|
}
|
||
|
|
if pubKey == nil {
|
||
|
|
t.Fatal("expected non-nil public key")
|
||
|
|
}
|
||
|
|
if len(rest) > 0 {
|
||
|
|
t.Errorf("unexpected trailing bytes after public key: %d bytes", len(rest))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestGenerateSSHKey_OverwritesExisting(t *testing.T) {
|
||
|
|
dir := t.TempDir()
|
||
|
|
keyPath := filepath.Join(dir, "test_ed25519.key")
|
||
|
|
|
||
|
|
// Generate twice; second call must not error and must produce a different key.
|
||
|
|
if err := GenerateSSHKey(keyPath); err != nil {
|
||
|
|
t.Fatalf("first GenerateSSHKey() error = %v", err)
|
||
|
|
}
|
||
|
|
first, err := os.ReadFile(keyPath)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("read first key: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if err = GenerateSSHKey(keyPath); err != nil {
|
||
|
|
t.Fatalf("second GenerateSSHKey() error = %v", err)
|
||
|
|
}
|
||
|
|
second, err := os.ReadFile(keyPath)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("read second key: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Two independently generated Ed25519 keys must differ.
|
||
|
|
if string(first) == string(second) {
|
||
|
|
t.Error("expected overwritten key to differ from original")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestGenerateSSHKey_CreatesDirectory(t *testing.T) {
|
||
|
|
dir := t.TempDir()
|
||
|
|
// Nested directory that does not yet exist.
|
||
|
|
keyPath := filepath.Join(dir, "subdir", ".ssh", "picoclaw_ed25519.key")
|
||
|
|
|
||
|
|
if err := GenerateSSHKey(keyPath); err != nil {
|
||
|
|
t.Fatalf("GenerateSSHKey() error = %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if _, err := os.Stat(keyPath); err != nil {
|
||
|
|
t.Fatalf("private key not created: %v", err)
|
||
|
|
}
|
||
|
|
}
|