package signing import ( "crypto/ed25519" "encoding/hex" "os" "path/filepath" "testing" ) func generateTestKeyPair(t *testing.T) (string, string) { t.Helper() pub, priv, err := GenerateKeyPair() if err != nil { t.Fatal(err) } return pub, priv } func TestGenerateKeyPair(t *testing.T) { pub, priv, err := GenerateKeyPair() if err != nil { t.Fatal(err) } // Public key should be 32 bytes = 64 hex chars if len(pub) != 64 { t.Errorf("expected 64 hex chars for public key, got %d", len(pub)) } // Private key should be 64 bytes = 128 hex chars if len(priv) != 128 { t.Errorf("expected 128 hex chars for private key, got %d", len(priv)) } // Keys should be valid hex if _, err := hex.DecodeString(pub); err != nil { t.Errorf("public key is not valid hex: %v", err) } if _, err := hex.DecodeString(priv); err != nil { t.Errorf("private key is not valid hex: %v", err) } } func TestNewVerifier(t *testing.T) { pub, _ := generateTestKeyPair(t) v, err := NewVerifier(pub) if err != nil { t.Fatal(err) } if v == nil { t.Fatal("verifier should not be nil") } } func TestNewVerifierInvalid(t *testing.T) { // Invalid hex _, err := NewVerifier("not-hex") if err == nil { t.Fatal("expected error for invalid hex") } // Wrong length _, err = NewVerifier("abcd") if err == nil { t.Fatal("expected error for wrong key length") } } func TestNewSigner(t *testing.T) { _, priv := generateTestKeyPair(t) s, err := NewSigner(priv) if err != nil { t.Fatal(err) } if s == nil { t.Fatal("signer should not be nil") } } func TestSignAndVerifyBytes(t *testing.T) { pub, priv := generateTestKeyPair(t) signer, err := NewSigner(priv) if err != nil { t.Fatal(err) } verifier, err := NewVerifier(pub) if err != nil { t.Fatal(err) } message := []byte("KubeSolo OS update v1.0.0") sig := signer.SignBytes(message) // Verify should succeed if err := verifier.VerifyBytes(message, sig); err != nil { t.Errorf("verification should succeed: %v", err) } // Tampered message should fail tampered := []byte("KubeSolo OS update v1.0.1") if err := verifier.VerifyBytes(tampered, sig); err == nil { t.Error("verification should fail for tampered message") } // Tampered signature should fail badSig := make([]byte, len(sig)) copy(badSig, sig) badSig[0] ^= 0xff if err := verifier.VerifyBytes(message, badSig); err == nil { t.Error("verification should fail for tampered signature") } } func TestSignAndVerifyFile(t *testing.T) { pub, priv := generateTestKeyPair(t) dir := t.TempDir() signer, err := NewSigner(priv) if err != nil { t.Fatal(err) } verifier, err := NewVerifier(pub) if err != nil { t.Fatal(err) } // Create a test file filePath := filepath.Join(dir, "test-image.gz") content := []byte("fake OS image content for signing test") if err := os.WriteFile(filePath, content, 0o644); err != nil { t.Fatal(err) } // Sign sigPath := filePath + ".sig" if err := signer.SignFile(filePath, sigPath); err != nil { t.Fatal(err) } // Verify signature file was created if _, err := os.Stat(sigPath); err != nil { t.Fatalf("signature file not created: %v", err) } // Verify if err := verifier.VerifyFile(filePath, sigPath); err != nil { t.Errorf("verification should succeed: %v", err) } } func TestVerifyFileTampered(t *testing.T) { pub, priv := generateTestKeyPair(t) dir := t.TempDir() signer, err := NewSigner(priv) if err != nil { t.Fatal(err) } verifier, err := NewVerifier(pub) if err != nil { t.Fatal(err) } // Create and sign a file filePath := filepath.Join(dir, "test-image.gz") if err := os.WriteFile(filePath, []byte("original content"), 0o644); err != nil { t.Fatal(err) } sigPath := filePath + ".sig" if err := signer.SignFile(filePath, sigPath); err != nil { t.Fatal(err) } // Tamper with the file if err := os.WriteFile(filePath, []byte("tampered content"), 0o644); err != nil { t.Fatal(err) } // Verification should fail if err := verifier.VerifyFile(filePath, sigPath); err == nil { t.Error("verification should fail for tampered file") } } func TestVerifyFileWrongKey(t *testing.T) { _, priv := generateTestKeyPair(t) otherPub, _ := generateTestKeyPair(t) // different key pair dir := t.TempDir() signer, err := NewSigner(priv) if err != nil { t.Fatal(err) } wrongVerifier, err := NewVerifier(otherPub) if err != nil { t.Fatal(err) } // Create and sign filePath := filepath.Join(dir, "test.gz") if err := os.WriteFile(filePath, []byte("test content"), 0o644); err != nil { t.Fatal(err) } sigPath := filePath + ".sig" if err := signer.SignFile(filePath, sigPath); err != nil { t.Fatal(err) } // Verify with wrong key should fail if err := wrongVerifier.VerifyFile(filePath, sigPath); err == nil { t.Error("verification should fail with wrong public key") } } func TestNewVerifierFromFile(t *testing.T) { pub, _ := generateTestKeyPair(t) dir := t.TempDir() keyFile := filepath.Join(dir, "pubkey.hex") if err := os.WriteFile(keyFile, []byte(pub+"\n"), 0o644); err != nil { t.Fatal(err) } v, err := NewVerifierFromFile(keyFile) if err != nil { t.Fatal(err) } if v == nil { t.Fatal("verifier should not be nil") } } func TestNewSignerFromFile(t *testing.T) { _, priv := generateTestKeyPair(t) dir := t.TempDir() keyFile := filepath.Join(dir, "privkey.hex") if err := os.WriteFile(keyFile, []byte(priv+"\n"), 0o644); err != nil { t.Fatal(err) } s, err := NewSignerFromFile(keyFile) if err != nil { t.Fatal(err) } if s == nil { t.Fatal("signer should not be nil") } } func TestSignerPublicKeyHex(t *testing.T) { pub, priv := generateTestKeyPair(t) signer, err := NewSigner(priv) if err != nil { t.Fatal(err) } got := signer.PublicKeyHex() if got != pub { t.Errorf("public key mismatch: got %s, want %s", got, pub) } } func TestDecodeSignature(t *testing.T) { // Create a valid signature _, priv, _ := ed25519.GenerateKey(nil) message := []byte("test") rawSig := ed25519.Sign(priv, message) hexSig := hex.EncodeToString(rawSig) // Raw signature (64 bytes) decoded, err := decodeSignature(rawSig) if err != nil { t.Fatalf("raw sig decode failed: %v", err) } if len(decoded) != ed25519.SignatureSize { t.Errorf("expected %d bytes, got %d", ed25519.SignatureSize, len(decoded)) } // Hex-encoded signature decoded, err = decodeSignature([]byte(hexSig)) if err != nil { t.Fatalf("hex sig decode failed: %v", err) } if len(decoded) != ed25519.SignatureSize { t.Errorf("expected %d bytes, got %d", ed25519.SignatureSize, len(decoded)) } // Hex with trailing newline decoded, err = decodeSignature([]byte(hexSig + "\n")) if err != nil { t.Fatalf("hex sig with newline decode failed: %v", err) } if len(decoded) != ed25519.SignatureSize { t.Errorf("expected %d bytes, got %d", ed25519.SignatureSize, len(decoded)) } // Invalid data _, err = decodeSignature([]byte("not valid")) if err == nil { t.Error("expected error for invalid signature data") } } func TestTrimWhitespace(t *testing.T) { tests := []struct { input string expect string }{ {"hello", "hello"}, {" hello ", "hello"}, {"hello\n", "hello"}, {"\thello\r\n", "hello"}, {" he llo ", "hello"}, } for _, tt := range tests { got := trimWhitespace(tt.input) if got != tt.expect { t.Errorf("trimWhitespace(%q) = %q, want %q", tt.input, got, tt.expect) } } }