Files
mcias/internal/crypto/crypto_test.go
Kyle Isom e63d9863b6 Implement dashboard and audit log templates, add paginated audit log support
- Added `web/templates/{dashboard,audit,base,accounts,account_detail}.html` for a consistent UI.
- Implemented new audit log endpoint (`GET /v1/audit`) with filtering and pagination via `ListAuditEventsPaged`.
- Extended `AuditQueryParams`, added `AuditEventView` for joined actor/target usernames.
- Updated configuration (`goimports` preference), linting rules, and E2E tests.
- No logic changes to existing APIs.
2026-03-11 14:05:08 -07:00

263 lines
7.1 KiB
Go

package crypto
import (
"bytes"
"crypto/ed25519"
"testing"
)
// TestGenerateEd25519KeyPair verifies that key generation returns valid,
// distinct keys and that the public key is derivable from the private key.
func TestGenerateEd25519KeyPair(t *testing.T) {
pub1, priv1, err := GenerateEd25519KeyPair()
if err != nil {
t.Fatalf("GenerateEd25519KeyPair: %v", err)
}
pub2, priv2, err := GenerateEd25519KeyPair()
if err != nil {
t.Fatalf("GenerateEd25519KeyPair second call: %v", err)
}
// Keys should be different across calls.
if bytes.Equal(priv1, priv2) {
t.Error("two calls produced identical private keys")
}
if bytes.Equal(pub1, pub2) {
t.Error("two calls produced identical public keys")
}
// Public key must be extractable from private key.
derived, ok := priv1.Public().(ed25519.PublicKey)
if !ok {
t.Fatal("priv1.Public() did not return ed25519.PublicKey")
}
if !bytes.Equal(derived, pub1) {
t.Error("public key derived from private key does not match generated public key")
}
}
// TestEd25519PEMRoundTrip verifies that a private key can be encoded to PEM
// and decoded back to the identical key.
func TestEd25519PEMRoundTrip(t *testing.T) {
_, priv, err := GenerateEd25519KeyPair()
if err != nil {
t.Fatalf("GenerateEd25519KeyPair: %v", err)
}
pem, err := MarshalPrivateKeyPEM(priv)
if err != nil {
t.Fatalf("MarshalPrivateKeyPEM: %v", err)
}
if len(pem) == 0 {
t.Fatal("MarshalPrivateKeyPEM returned empty PEM")
}
decoded, err := ParsePrivateKeyPEM(pem)
if err != nil {
t.Fatalf("ParsePrivateKeyPEM: %v", err)
}
if !bytes.Equal(priv, decoded) {
t.Error("decoded private key does not match original")
}
}
// TestParsePrivateKeyPEMErrors validates error cases.
func TestParsePrivateKeyPEMErrors(t *testing.T) {
// Empty input
if _, err := ParsePrivateKeyPEM([]byte{}); err == nil {
t.Error("expected error for empty PEM, got nil")
}
// Wrong PEM type (using a fake RSA block header)
fakePEM := []byte("-----BEGIN RSA PRIVATE KEY-----\nYWJj\n-----END RSA PRIVATE KEY-----\n")
if _, err := ParsePrivateKeyPEM(fakePEM); err == nil {
t.Error("expected error for wrong PEM type, got nil")
}
// Corrupt DER inside valid PEM block
corruptPEM := []byte("-----BEGIN PRIVATE KEY-----\nYWJj\n-----END PRIVATE KEY-----\n")
if _, err := ParsePrivateKeyPEM(corruptPEM); err == nil {
t.Error("expected error for corrupt DER, got nil")
}
}
// TestSealOpenAESGCMRoundTrip verifies that sealed data can be opened.
func TestSealOpenAESGCMRoundTrip(t *testing.T) {
key := make([]byte, 32)
for i := range key {
key[i] = byte(i)
}
plaintext := []byte("hello world secret data")
ct, nonce, err := SealAESGCM(key, plaintext)
if err != nil {
t.Fatalf("SealAESGCM: %v", err)
}
if len(ct) == 0 || len(nonce) == 0 {
t.Fatal("SealAESGCM returned empty ciphertext or nonce")
}
got, err := OpenAESGCM(key, nonce, ct)
if err != nil {
t.Fatalf("OpenAESGCM: %v", err)
}
if !bytes.Equal(got, plaintext) {
t.Errorf("decrypted = %q, want %q", got, plaintext)
}
}
// TestSealNoncesAreUnique verifies that repeated seals produce different nonces.
func TestSealNoncesAreUnique(t *testing.T) {
key := make([]byte, 32)
plaintext := []byte("same plaintext")
_, nonce1, err := SealAESGCM(key, plaintext)
if err != nil {
t.Fatalf("SealAESGCM (1): %v", err)
}
_, nonce2, err := SealAESGCM(key, plaintext)
if err != nil {
t.Fatalf("SealAESGCM (2): %v", err)
}
if bytes.Equal(nonce1, nonce2) {
t.Error("two seals of the same plaintext produced identical nonces — crypto/rand may be broken")
}
}
// TestOpenAESGCMWrongKey verifies that decryption with the wrong key fails.
func TestOpenAESGCMWrongKey(t *testing.T) {
key := make([]byte, 32)
wrongKey := make([]byte, 32)
wrongKey[0] = 0xFF
ct, nonce, err := SealAESGCM(key, []byte("secret"))
if err != nil {
t.Fatalf("SealAESGCM: %v", err)
}
if _, err := OpenAESGCM(wrongKey, nonce, ct); err == nil {
t.Error("expected error when opening with wrong key, got nil")
}
}
// TestOpenAESGCMTamperedCiphertext verifies that tampering is detected.
func TestOpenAESGCMTamperedCiphertext(t *testing.T) {
key := make([]byte, 32)
ct, nonce, err := SealAESGCM(key, []byte("secret"))
if err != nil {
t.Fatalf("SealAESGCM: %v", err)
}
// Flip one bit in the ciphertext.
ct[0] ^= 0x01
if _, err := OpenAESGCM(key, nonce, ct); err == nil {
t.Error("expected error for tampered ciphertext, got nil")
}
}
// TestOpenAESGCMWrongKeySize verifies that keys with wrong size are rejected.
func TestOpenAESGCMWrongKeySize(t *testing.T) {
if _, _, err := SealAESGCM([]byte("short"), []byte("data")); err == nil {
t.Error("expected error for short key in Seal, got nil")
}
if _, err := OpenAESGCM([]byte("short"), make([]byte, 12), []byte("data")); err == nil {
t.Error("expected error for short key in Open, got nil")
}
}
// TestDeriveKey verifies that DeriveKey produces consistent, non-empty output.
func TestDeriveKey(t *testing.T) {
salt, err := NewSalt()
if err != nil {
t.Fatalf("NewSalt: %v", err)
}
key1, err := DeriveKey("my-passphrase", salt)
if err != nil {
t.Fatalf("DeriveKey: %v", err)
}
if len(key1) != 32 {
t.Errorf("DeriveKey returned %d bytes, want 32", len(key1))
}
// Same inputs → same output (deterministic).
key2, err := DeriveKey("my-passphrase", salt)
if err != nil {
t.Fatalf("DeriveKey (2): %v", err)
}
if !bytes.Equal(key1, key2) {
t.Error("DeriveKey is not deterministic")
}
// Different passphrase → different key.
key3, err := DeriveKey("different-passphrase", salt)
if err != nil {
t.Fatalf("DeriveKey (3): %v", err)
}
if bytes.Equal(key1, key3) {
t.Error("different passphrases produced the same key")
}
// Different salt → different key.
salt2, err := NewSalt()
if err != nil {
t.Fatalf("NewSalt (2): %v", err)
}
key4, err := DeriveKey("my-passphrase", salt2)
if err != nil {
t.Fatalf("DeriveKey (4): %v", err)
}
if bytes.Equal(key1, key4) {
t.Error("different salts produced the same key")
}
}
// TestDeriveKeyErrors verifies invalid input rejection.
func TestDeriveKeyErrors(t *testing.T) {
// Short salt
if _, err := DeriveKey("passphrase", []byte("short")); err == nil {
t.Error("expected error for short salt, got nil")
}
// Empty passphrase
salt, _ := NewSalt()
if _, err := DeriveKey("", salt); err == nil {
t.Error("expected error for empty passphrase, got nil")
}
}
// TestNewSaltUniqueness verifies that two salts are different.
func TestNewSaltUniqueness(t *testing.T) {
s1, err := NewSalt()
if err != nil {
t.Fatalf("NewSalt (1): %v", err)
}
s2, err := NewSalt()
if err != nil {
t.Fatalf("NewSalt (2): %v", err)
}
if bytes.Equal(s1, s2) {
t.Error("two NewSalt calls returned identical salts")
}
}
// TestRandomBytes verifies length and uniqueness.
func TestRandomBytes(t *testing.T) {
b1, err := RandomBytes(32)
if err != nil {
t.Fatalf("RandomBytes: %v", err)
}
if len(b1) != 32 {
t.Errorf("RandomBytes returned %d bytes, want 32", len(b1))
}
b2, err := RandomBytes(32)
if err != nil {
t.Fatalf("RandomBytes (2): %v", err)
}
if bytes.Equal(b1, b2) {
t.Error("two RandomBytes calls returned identical values")
}
}