checkpoint mciassrv
This commit is contained in:
259
internal/crypto/crypto_test.go
Normal file
259
internal/crypto/crypto_test.go
Normal file
@@ -0,0 +1,259 @@
|
||||
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 := priv1.Public().(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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user