Borg/pkg/stmf/stmf_test.go
Snider b3755da69d feat: Add STMF form encryption and SMSG secure message packages
STMF (Sovereign Form Encryption):
- X25519 ECDH + ChaCha20-Poly1305 hybrid encryption
- Go library (pkg/stmf/) with encrypt/decrypt and HTTP middleware
- WASM module for client-side browser encryption
- JavaScript wrapper with TypeScript types (js/borg-stmf/)
- PHP library for server-side decryption (php/borg-stmf/)
- Full cross-platform interoperability (Go <-> PHP)

SMSG (Secure Message):
- Password-based ChaCha20-Poly1305 message encryption
- Support for attachments, metadata, and PKI reply keys
- WASM bindings for browser-based decryption

Demos:
- index.html: Form encryption demo with modern dark UI
- support-reply.html: Decrypt password-protected messages
- examples/smsg-reply/: CLI tool for creating encrypted replies

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-27 00:49:07 +00:00

382 lines
9.7 KiB
Go

package stmf
import (
"encoding/base64"
"testing"
)
func TestKeyPairGeneration(t *testing.T) {
kp, err := GenerateKeyPair()
if err != nil {
t.Fatalf("GenerateKeyPair failed: %v", err)
}
// X25519 keys are 32 bytes
if len(kp.PublicKey()) != 32 {
t.Errorf("Public key length = %d, want 32", len(kp.PublicKey()))
}
if len(kp.PrivateKey()) != 32 {
t.Errorf("Private key length = %d, want 32", len(kp.PrivateKey()))
}
// Base64 encoding should work
pubB64 := kp.PublicKeyBase64()
privB64 := kp.PrivateKeyBase64()
if pubB64 == "" || privB64 == "" {
t.Error("Base64 encoding returned empty string")
}
// Should be able to decode back
pubBytes, err := base64.StdEncoding.DecodeString(pubB64)
if err != nil {
t.Errorf("Failed to decode public key base64: %v", err)
}
if len(pubBytes) != 32 {
t.Errorf("Decoded public key length = %d, want 32", len(pubBytes))
}
}
func TestLoadKeyPair(t *testing.T) {
// Generate a keypair
original, err := GenerateKeyPair()
if err != nil {
t.Fatalf("GenerateKeyPair failed: %v", err)
}
// Load it back from bytes
loaded, err := LoadKeyPair(original.PrivateKey())
if err != nil {
t.Fatalf("LoadKeyPair failed: %v", err)
}
// Public keys should match
if string(loaded.PublicKey()) != string(original.PublicKey()) {
t.Error("Loaded public key doesn't match original")
}
}
func TestLoadKeyPairBase64(t *testing.T) {
original, err := GenerateKeyPair()
if err != nil {
t.Fatalf("GenerateKeyPair failed: %v", err)
}
loaded, err := LoadKeyPairBase64(original.PrivateKeyBase64())
if err != nil {
t.Fatalf("LoadKeyPairBase64 failed: %v", err)
}
if loaded.PublicKeyBase64() != original.PublicKeyBase64() {
t.Error("Loaded public key doesn't match original")
}
}
func TestEncryptDecryptRoundTrip(t *testing.T) {
// Generate server keypair
serverKP, err := GenerateKeyPair()
if err != nil {
t.Fatalf("GenerateKeyPair failed: %v", err)
}
// Create form data
formData := NewFormData().
AddField("email", "test@example.com").
AddFieldWithType("password", "secret123", "password").
SetMetadata("origin", "https://example.com")
// Encrypt with server's public key
encrypted, err := Encrypt(formData, serverKP.PublicKey())
if err != nil {
t.Fatalf("Encrypt failed: %v", err)
}
// Decrypt with server's private key
decrypted, err := Decrypt(encrypted, serverKP.PrivateKey())
if err != nil {
t.Fatalf("Decrypt failed: %v", err)
}
// Verify fields
if decrypted.Get("email") != "test@example.com" {
t.Errorf("email = %q, want %q", decrypted.Get("email"), "test@example.com")
}
if decrypted.Get("password") != "secret123" {
t.Errorf("password = %q, want %q", decrypted.Get("password"), "secret123")
}
// Verify metadata
if decrypted.Metadata["origin"] != "https://example.com" {
t.Errorf("origin = %q, want %q", decrypted.Metadata["origin"], "https://example.com")
}
// Verify field type preserved
pwField := decrypted.GetField("password")
if pwField == nil {
t.Error("password field not found")
} else if pwField.Type != "password" {
t.Errorf("password type = %q, want %q", pwField.Type, "password")
}
}
func TestEncryptDecryptBase64RoundTrip(t *testing.T) {
serverKP, err := GenerateKeyPair()
if err != nil {
t.Fatalf("GenerateKeyPair failed: %v", err)
}
formData := NewFormData().
AddField("username", "johndoe").
AddField("action", "login")
// Encrypt to base64
encryptedB64, err := EncryptBase64(formData, serverKP.PublicKey())
if err != nil {
t.Fatalf("EncryptBase64 failed: %v", err)
}
// Should be valid base64
if _, err := base64.StdEncoding.DecodeString(encryptedB64); err != nil {
t.Fatalf("Encrypted output is not valid base64: %v", err)
}
// Decrypt from base64
decrypted, err := DecryptBase64(encryptedB64, serverKP.PrivateKey())
if err != nil {
t.Fatalf("DecryptBase64 failed: %v", err)
}
if decrypted.Get("username") != "johndoe" {
t.Errorf("username = %q, want %q", decrypted.Get("username"), "johndoe")
}
}
func TestEncryptMapRoundTrip(t *testing.T) {
serverKP, err := GenerateKeyPair()
if err != nil {
t.Fatalf("GenerateKeyPair failed: %v", err)
}
input := map[string]string{
"name": "John Doe",
"email": "john@example.com",
"phone": "+1234567890",
}
encrypted, err := EncryptMap(input, serverKP.PublicKey())
if err != nil {
t.Fatalf("EncryptMap failed: %v", err)
}
output, err := DecryptToMap(encrypted, serverKP.PrivateKey())
if err != nil {
t.Fatalf("DecryptToMap failed: %v", err)
}
for key, want := range input {
if got := output[key]; got != want {
t.Errorf("%s = %q, want %q", key, got, want)
}
}
}
func TestMultipleEncryptionsAreDifferent(t *testing.T) {
// Each encryption should use a different ephemeral key
serverKP, err := GenerateKeyPair()
if err != nil {
t.Fatalf("GenerateKeyPair failed: %v", err)
}
formData := NewFormData().AddField("test", "value")
enc1, err := Encrypt(formData, serverKP.PublicKey())
if err != nil {
t.Fatalf("First Encrypt failed: %v", err)
}
enc2, err := Encrypt(formData, serverKP.PublicKey())
if err != nil {
t.Fatalf("Second Encrypt failed: %v", err)
}
// Encryptions should be different (different ephemeral keys)
if string(enc1) == string(enc2) {
t.Error("Two encryptions of same data produced identical output (should use different ephemeral keys)")
}
// But both should decrypt to the same value
dec1, _ := Decrypt(enc1, serverKP.PrivateKey())
dec2, _ := Decrypt(enc2, serverKP.PrivateKey())
if dec1.Get("test") != dec2.Get("test") {
t.Error("Decrypted values don't match")
}
}
func TestDecryptWithWrongKey(t *testing.T) {
serverKP, err := GenerateKeyPair()
if err != nil {
t.Fatalf("GenerateKeyPair failed: %v", err)
}
wrongKP, err := GenerateKeyPair()
if err != nil {
t.Fatalf("GenerateKeyPair for wrong key failed: %v", err)
}
formData := NewFormData().AddField("secret", "data")
encrypted, err := Encrypt(formData, serverKP.PublicKey())
if err != nil {
t.Fatalf("Encrypt failed: %v", err)
}
// Decrypting with wrong key should fail
_, err = Decrypt(encrypted, wrongKP.PrivateKey())
if err == nil {
t.Error("Decrypt with wrong key should have failed")
}
}
func TestValidatePayload(t *testing.T) {
serverKP, err := GenerateKeyPair()
if err != nil {
t.Fatalf("GenerateKeyPair failed: %v", err)
}
formData := NewFormData().AddField("test", "value")
encrypted, err := Encrypt(formData, serverKP.PublicKey())
if err != nil {
t.Fatalf("Encrypt failed: %v", err)
}
// Valid payload should pass validation
if err := ValidatePayload(encrypted); err != nil {
t.Errorf("ValidatePayload failed for valid payload: %v", err)
}
// Invalid data should fail
if err := ValidatePayload([]byte("not a valid payload")); err == nil {
t.Error("ValidatePayload should fail for invalid data")
}
}
func TestGetPayloadInfo(t *testing.T) {
serverKP, err := GenerateKeyPair()
if err != nil {
t.Fatalf("GenerateKeyPair failed: %v", err)
}
formData := NewFormData().AddField("test", "value")
encrypted, err := Encrypt(formData, serverKP.PublicKey())
if err != nil {
t.Fatalf("Encrypt failed: %v", err)
}
info, err := GetPayloadInfo(encrypted)
if err != nil {
t.Fatalf("GetPayloadInfo failed: %v", err)
}
if info.Version != Version {
t.Errorf("Version = %q, want %q", info.Version, Version)
}
if info.Algorithm != "x25519-chacha20poly1305" {
t.Errorf("Algorithm = %q, want %q", info.Algorithm, "x25519-chacha20poly1305")
}
if info.EphemeralPK == "" {
t.Error("EphemeralPK is empty")
}
}
func TestFormDataMethods(t *testing.T) {
fd := NewFormData()
// Test AddField
fd.AddField("name", "John")
if fd.Get("name") != "John" {
t.Error("AddField/Get failed")
}
// Test AddFieldWithType
fd.AddFieldWithType("password", "secret", "password")
field := fd.GetField("password")
if field == nil || field.Type != "password" {
t.Error("AddFieldWithType failed to preserve type")
}
// Test AddFile
fd.AddFile("doc", "base64data", "document.pdf", "application/pdf")
fileField := fd.GetField("doc")
if fileField == nil {
t.Error("AddFile failed")
} else {
if fileField.Filename != "document.pdf" {
t.Error("Filename not preserved")
}
if fileField.MimeType != "application/pdf" {
t.Error("MimeType not preserved")
}
}
// Test SetMetadata
fd.SetMetadata("origin", "https://test.com")
if fd.Metadata["origin"] != "https://test.com" {
t.Error("SetMetadata failed")
}
// Test GetAll with multiple values
fd.AddField("tag", "one")
fd.AddField("tag", "two")
tags := fd.GetAll("tag")
if len(tags) != 2 {
t.Errorf("GetAll returned %d values, want 2", len(tags))
}
// Test ToMap
m := fd.ToMap()
if m["name"] != "John" {
t.Error("ToMap failed")
}
}
func TestFileFieldRoundTrip(t *testing.T) {
serverKP, err := GenerateKeyPair()
if err != nil {
t.Fatalf("GenerateKeyPair failed: %v", err)
}
// Simulate file upload with base64 content
fileContent := base64.StdEncoding.EncodeToString([]byte("Hello, World!"))
formData := NewFormData().
AddField("description", "My document").
AddFile("upload", fileContent, "hello.txt", "text/plain")
encrypted, err := Encrypt(formData, serverKP.PublicKey())
if err != nil {
t.Fatalf("Encrypt failed: %v", err)
}
decrypted, err := Decrypt(encrypted, serverKP.PrivateKey())
if err != nil {
t.Fatalf("Decrypt failed: %v", err)
}
uploadField := decrypted.GetField("upload")
if uploadField == nil {
t.Fatal("upload field not found")
}
if uploadField.Type != "file" {
t.Errorf("Type = %q, want %q", uploadField.Type, "file")
}
if uploadField.Filename != "hello.txt" {
t.Errorf("Filename = %q, want %q", uploadField.Filename, "hello.txt")
}
if uploadField.MimeType != "text/plain" {
t.Errorf("MimeType = %q, want %q", uploadField.MimeType, "text/plain")
}
if uploadField.Value != fileContent {
t.Error("File content not preserved")
}
}