729921e16e
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
305 lines
8.6 KiB
Go
305 lines
8.6 KiB
Go
package cybersecurity
|
|
|
|
import (
|
|
"bytes"
|
|
"testing"
|
|
)
|
|
|
|
// --- GenerateIdentity ---
|
|
|
|
func TestGenerateIdentity(t *testing.T) {
|
|
t.Run("genera keypairs con longitudes correctas", func(t *testing.T) {
|
|
id, err := GenerateIdentity()
|
|
if err != nil {
|
|
t.Fatalf("GenerateIdentity() error = %v", err)
|
|
}
|
|
if len(id.SignPub) != 32 {
|
|
t.Errorf("SignPub len = %d, want 32", len(id.SignPub))
|
|
}
|
|
if len(id.SignPriv) != 64 {
|
|
t.Errorf("SignPriv len = %d, want 64", len(id.SignPriv))
|
|
}
|
|
if len(id.KexPub) != 32 {
|
|
t.Errorf("KexPub len = %d, want 32", len(id.KexPub))
|
|
}
|
|
if len(id.KexPriv) != 32 {
|
|
t.Errorf("KexPriv len = %d, want 32", len(id.KexPriv))
|
|
}
|
|
})
|
|
|
|
t.Run("dos llamadas producen identidades distintas", func(t *testing.T) {
|
|
id1, err1 := GenerateIdentity()
|
|
id2, err2 := GenerateIdentity()
|
|
if err1 != nil || err2 != nil {
|
|
t.Fatal("GenerateIdentity() error inesperado")
|
|
}
|
|
if bytes.Equal(id1.SignPub, id2.SignPub) {
|
|
t.Error("SignPub idénticos en dos identidades distintas")
|
|
}
|
|
if bytes.Equal(id1.KexPub, id2.KexPub) {
|
|
t.Error("KexPub idénticos en dos identidades distintas")
|
|
}
|
|
})
|
|
}
|
|
|
|
// --- SealAEAD / OpenAEAD ---
|
|
|
|
func TestSealOpenAEAD(t *testing.T) {
|
|
key := make([]byte, 32)
|
|
for i := range key {
|
|
key[i] = byte(i + 1)
|
|
}
|
|
plaintext := []byte("mensaje secreto del bus de mensajería")
|
|
aad := []byte("room:sala-general")
|
|
|
|
t.Run("round-trip con aad", func(t *testing.T) {
|
|
nonce, ct, err := SealAEAD(key, plaintext, aad)
|
|
if err != nil {
|
|
t.Fatalf("SealAEAD error = %v", err)
|
|
}
|
|
got, err := OpenAEAD(key, nonce, ct, aad)
|
|
if err != nil {
|
|
t.Fatalf("OpenAEAD error = %v", err)
|
|
}
|
|
if !bytes.Equal(got, plaintext) {
|
|
t.Errorf("got %q, want %q", got, plaintext)
|
|
}
|
|
})
|
|
|
|
t.Run("round-trip sin aad (nil)", func(t *testing.T) {
|
|
nonce, ct, err := SealAEAD(key, plaintext, nil)
|
|
if err != nil {
|
|
t.Fatalf("SealAEAD error = %v", err)
|
|
}
|
|
got, err := OpenAEAD(key, nonce, ct, nil)
|
|
if err != nil {
|
|
t.Fatalf("OpenAEAD error = %v", err)
|
|
}
|
|
if !bytes.Equal(got, plaintext) {
|
|
t.Errorf("got %q, want %q", got, plaintext)
|
|
}
|
|
})
|
|
|
|
t.Run("error con clave de longitud incorrecta", func(t *testing.T) {
|
|
_, _, err := SealAEAD(key[:16], plaintext, nil)
|
|
if err == nil {
|
|
t.Error("esperaba error con clave de 16 bytes, got nil")
|
|
}
|
|
})
|
|
|
|
t.Run("error de autenticacion con ciphertext modificado", func(t *testing.T) {
|
|
nonce, ct, err := SealAEAD(key, plaintext, aad)
|
|
if err != nil {
|
|
t.Fatalf("SealAEAD error = %v", err)
|
|
}
|
|
ct[0] ^= 0xFF // corromper el primer byte
|
|
_, err = OpenAEAD(key, nonce, ct, aad)
|
|
if err == nil {
|
|
t.Error("esperaba error de autenticación con ciphertext corrupto, got nil")
|
|
}
|
|
})
|
|
|
|
t.Run("error de autenticacion con aad distinto", func(t *testing.T) {
|
|
nonce, ct, err := SealAEAD(key, plaintext, aad)
|
|
if err != nil {
|
|
t.Fatalf("SealAEAD error = %v", err)
|
|
}
|
|
_, err = OpenAEAD(key, nonce, ct, []byte("room:otra-sala"))
|
|
if err == nil {
|
|
t.Error("esperaba error de autenticación con aad distinto, got nil")
|
|
}
|
|
})
|
|
|
|
t.Run("nonces distintos en llamadas sucesivas", func(t *testing.T) {
|
|
n1, _, err1 := SealAEAD(key, plaintext, nil)
|
|
n2, _, err2 := SealAEAD(key, plaintext, nil)
|
|
if err1 != nil || err2 != nil {
|
|
t.Fatal("SealAEAD error inesperado")
|
|
}
|
|
if bytes.Equal(n1, n2) {
|
|
t.Error("nonces iguales en dos llamadas sucesivas (no aleatorios)")
|
|
}
|
|
})
|
|
}
|
|
|
|
// --- SealKeyBox / OpenKeyBox ---
|
|
|
|
func TestSealOpenKeyBox(t *testing.T) {
|
|
t.Run("round-trip con identidad generada", func(t *testing.T) {
|
|
id, err := GenerateIdentity()
|
|
if err != nil {
|
|
t.Fatalf("GenerateIdentity error = %v", err)
|
|
}
|
|
roomKey := make([]byte, 32)
|
|
for i := range roomKey {
|
|
roomKey[i] = byte(i + 42)
|
|
}
|
|
|
|
sealed, err := SealKeyBox(id.KexPub, roomKey)
|
|
if err != nil {
|
|
t.Fatalf("SealKeyBox error = %v", err)
|
|
}
|
|
|
|
opened, err := OpenKeyBox(id.KexPub, id.KexPriv, sealed)
|
|
if err != nil {
|
|
t.Fatalf("OpenKeyBox error = %v", err)
|
|
}
|
|
if !bytes.Equal(opened, roomKey) {
|
|
t.Errorf("got %x, want %x", opened, roomKey)
|
|
}
|
|
})
|
|
|
|
t.Run("error con recipientKexPub de longitud incorrecta", func(t *testing.T) {
|
|
_, err := SealKeyBox(make([]byte, 16), []byte("secret"))
|
|
if err == nil {
|
|
t.Error("esperaba error con kexPub de 16 bytes, got nil")
|
|
}
|
|
})
|
|
|
|
t.Run("error al abrir con clave equivocada", func(t *testing.T) {
|
|
id, _ := GenerateIdentity()
|
|
other, _ := GenerateIdentity()
|
|
sealed, err := SealKeyBox(id.KexPub, []byte("roomkey"))
|
|
if err != nil {
|
|
t.Fatalf("SealKeyBox error = %v", err)
|
|
}
|
|
_, err = OpenKeyBox(other.KexPub, other.KexPriv, sealed)
|
|
if err == nil {
|
|
t.Error("esperaba error al abrir con keypair distinto, got nil")
|
|
}
|
|
})
|
|
|
|
t.Run("error con mensaje truncado", func(t *testing.T) {
|
|
id, _ := GenerateIdentity()
|
|
_, err := OpenKeyBox(id.KexPub, id.KexPriv, []byte("corto"))
|
|
if err == nil {
|
|
t.Error("esperaba error con sealedMsg truncado, got nil")
|
|
}
|
|
})
|
|
}
|
|
|
|
// --- SignEd25519 / VerifyEd25519 ---
|
|
|
|
func TestSignVerifyEd25519(t *testing.T) {
|
|
t.Run("firma y verificacion exitosa", func(t *testing.T) {
|
|
id, err := GenerateIdentity()
|
|
if err != nil {
|
|
t.Fatalf("GenerateIdentity error = %v", err)
|
|
}
|
|
msg := []byte("evento:room_key_rotation:v2")
|
|
sig := SignEd25519(id.SignPriv, msg)
|
|
if len(sig) != 64 {
|
|
t.Errorf("sig len = %d, want 64", len(sig))
|
|
}
|
|
if !VerifyEd25519(id.SignPub, msg, sig) {
|
|
t.Error("VerifyEd25519 devolvió false para firma válida")
|
|
}
|
|
})
|
|
|
|
t.Run("firma es determinista (misma entrada, misma firma)", func(t *testing.T) {
|
|
id, _ := GenerateIdentity()
|
|
msg := []byte("determinismo criptografico")
|
|
sig1 := SignEd25519(id.SignPriv, msg)
|
|
sig2 := SignEd25519(id.SignPriv, msg)
|
|
if !bytes.Equal(sig1, sig2) {
|
|
t.Error("Ed25519 debe ser determinista: mismas entradas deben producir misma firma")
|
|
}
|
|
})
|
|
|
|
t.Run("falla con mensaje modificado", func(t *testing.T) {
|
|
id, _ := GenerateIdentity()
|
|
msg := []byte("mensaje original")
|
|
sig := SignEd25519(id.SignPriv, msg)
|
|
modified := []byte("mensaje modificado")
|
|
if VerifyEd25519(id.SignPub, modified, sig) {
|
|
t.Error("VerifyEd25519 devolvió true para mensaje modificado")
|
|
}
|
|
})
|
|
|
|
t.Run("falla con clave publica incorrecta", func(t *testing.T) {
|
|
id1, _ := GenerateIdentity()
|
|
id2, _ := GenerateIdentity()
|
|
msg := []byte("autenticidad del remitente")
|
|
sig := SignEd25519(id1.SignPriv, msg)
|
|
if VerifyEd25519(id2.SignPub, msg, sig) {
|
|
t.Error("VerifyEd25519 devolvió true con clave pública de otra identidad")
|
|
}
|
|
})
|
|
|
|
t.Run("falla con firma corrupta", func(t *testing.T) {
|
|
id, _ := GenerateIdentity()
|
|
msg := []byte("integridad")
|
|
sig := SignEd25519(id.SignPriv, msg)
|
|
sig[0] ^= 0xFF
|
|
if VerifyEd25519(id.SignPub, msg, sig) {
|
|
t.Error("VerifyEd25519 devolvió true con firma corrupta")
|
|
}
|
|
})
|
|
}
|
|
|
|
// --- Integración: flujo completo megolm-reducido ---
|
|
|
|
func TestE2EMessagingFlow(t *testing.T) {
|
|
t.Run("flujo completo: generar identidad, distribuir clave de sala, cifrar y firmar mensaje", func(t *testing.T) {
|
|
// Servidor genera identidad
|
|
server, err := GenerateIdentity()
|
|
if err != nil {
|
|
t.Fatalf("GenerateIdentity server: %v", err)
|
|
}
|
|
// Usuario genera identidad
|
|
user, err := GenerateIdentity()
|
|
if err != nil {
|
|
t.Fatalf("GenerateIdentity user: %v", err)
|
|
}
|
|
|
|
// Servidor genera clave de sala y la distribuye al usuario cifrada con su KexPub
|
|
roomKey := make([]byte, 32)
|
|
for i := range roomKey {
|
|
roomKey[i] = byte(i)
|
|
}
|
|
sealedKey, err := SealKeyBox(user.KexPub, roomKey)
|
|
if err != nil {
|
|
t.Fatalf("SealKeyBox: %v", err)
|
|
}
|
|
|
|
// Usuario desella la clave de sala
|
|
gotRoomKey, err := OpenKeyBox(user.KexPub, user.KexPriv, sealedKey)
|
|
if err != nil {
|
|
t.Fatalf("OpenKeyBox: %v", err)
|
|
}
|
|
if !bytes.Equal(gotRoomKey, roomKey) {
|
|
t.Fatal("clave de sala distribuida no coincide")
|
|
}
|
|
|
|
// Usuario cifra un mensaje con la clave de sala
|
|
plainMsg := []byte("hola sala, este es mi primer mensaje cifrado e2e")
|
|
aad := []byte("room:sala-secreta:seq:1")
|
|
nonce, ct, err := SealAEAD(gotRoomKey, plainMsg, aad)
|
|
if err != nil {
|
|
t.Fatalf("SealAEAD: %v", err)
|
|
}
|
|
|
|
// Usuario firma el ciphertext para autenticación del remitente
|
|
sig := SignEd25519(user.SignPriv, ct)
|
|
|
|
// Receptor verifica firma del remitente
|
|
if !VerifyEd25519(user.SignPub, ct, sig) {
|
|
t.Fatal("verificación de firma del remitente falló")
|
|
}
|
|
|
|
// Receptor descifra el mensaje
|
|
decrypted, err := OpenAEAD(gotRoomKey, nonce, ct, aad)
|
|
if err != nil {
|
|
t.Fatalf("OpenAEAD: %v", err)
|
|
}
|
|
if !bytes.Equal(decrypted, plainMsg) {
|
|
t.Errorf("mensaje descifrado %q != original %q", decrypted, plainMsg)
|
|
}
|
|
|
|
// Servidor tiene distinta identidad que el usuario (las claves no se confunden)
|
|
if bytes.Equal(server.SignPub, user.SignPub) {
|
|
t.Error("server y user tienen la misma clave pública de firma")
|
|
}
|
|
})
|
|
}
|