mirror of
https://github.com/sonr-io/crypto.git
synced 2026-08-02 23:41:37 +00:00
No commit suggestions generated
This commit is contained in:
@@ -0,0 +1,523 @@
|
||||
package secure
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestZeroize(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
data []byte
|
||||
}{
|
||||
{"empty slice", []byte{}},
|
||||
{"single byte", []byte{0xFF}},
|
||||
{"small slice", []byte{1, 2, 3, 4, 5}},
|
||||
{"large slice", make([]byte, 1024)},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Fill with non-zero data
|
||||
for i := range tt.data {
|
||||
tt.data[i] = byte(i%256 + 1)
|
||||
}
|
||||
|
||||
// Store original for verification
|
||||
original := make([]byte, len(tt.data))
|
||||
copy(original, tt.data)
|
||||
|
||||
// Zeroize
|
||||
Zeroize(tt.data)
|
||||
|
||||
// Verify all bytes are zero
|
||||
for i, b := range tt.data {
|
||||
if b != 0 {
|
||||
t.Errorf("Byte at index %d not zeroed: got %d, want 0", i, b)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify original data was actually non-zero (for non-empty slices)
|
||||
if len(original) > 0 {
|
||||
hasNonZero := false
|
||||
for _, b := range original {
|
||||
if b != 0 {
|
||||
hasNonZero = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasNonZero {
|
||||
t.Error("Test data was already all zeros - invalid test")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecureBytes(t *testing.T) {
|
||||
t.Run("NewSecureBytes", func(t *testing.T) {
|
||||
sb := NewSecureBytes(32)
|
||||
if sb == nil {
|
||||
t.Fatal("NewSecureBytes returned nil")
|
||||
}
|
||||
if sb.Size() != 32 {
|
||||
t.Errorf("Size() = %d, want 32", sb.Size())
|
||||
}
|
||||
if sb.IsEmpty() {
|
||||
t.Error("NewSecureBytes should not be empty")
|
||||
}
|
||||
sb.Clear()
|
||||
})
|
||||
|
||||
t.Run("zero size", func(t *testing.T) {
|
||||
sb := NewSecureBytes(0)
|
||||
if sb.Size() != 0 {
|
||||
t.Errorf("Size() = %d, want 0", sb.Size())
|
||||
}
|
||||
if !sb.IsEmpty() {
|
||||
t.Error("Zero-size SecureBytes should be empty")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FromBytes", func(t *testing.T) {
|
||||
original := []byte{1, 2, 3, 4, 5}
|
||||
sb := FromBytes(original)
|
||||
|
||||
if sb.Size() != len(original) {
|
||||
t.Errorf("Size() = %d, want %d", sb.Size(), len(original))
|
||||
}
|
||||
|
||||
retrieved := sb.Bytes()
|
||||
if !bytes.Equal(retrieved, original) {
|
||||
t.Error("Retrieved bytes don't match original")
|
||||
}
|
||||
|
||||
// Verify independence - modifying original shouldn't affect SecureBytes
|
||||
original[0] = 99
|
||||
retrieved2 := sb.Bytes()
|
||||
if retrieved2[0] == 99 {
|
||||
t.Error("SecureBytes was affected by external modification")
|
||||
}
|
||||
|
||||
sb.Clear()
|
||||
})
|
||||
|
||||
t.Run("Bytes returns copy", func(t *testing.T) {
|
||||
sb := NewSecureBytes(16)
|
||||
|
||||
bytes1 := sb.Bytes()
|
||||
bytes2 := sb.Bytes()
|
||||
|
||||
// Should be equal content
|
||||
if !bytes.Equal(bytes1, bytes2) {
|
||||
t.Error("Multiple Bytes() calls returned different content")
|
||||
}
|
||||
|
||||
// Should be different slices
|
||||
if len(bytes1) > 0 && &bytes1[0] == &bytes2[0] {
|
||||
t.Error("Bytes() returned same underlying array")
|
||||
}
|
||||
|
||||
// Modifying returned slice shouldn't affect SecureBytes
|
||||
if len(bytes1) > 0 {
|
||||
bytes1[0] = 0xFF
|
||||
bytes3 := sb.Bytes()
|
||||
if bytes3[0] == 0xFF {
|
||||
t.Error("External modification affected SecureBytes")
|
||||
}
|
||||
}
|
||||
|
||||
sb.Clear()
|
||||
})
|
||||
|
||||
t.Run("Clear", func(t *testing.T) {
|
||||
data := []byte{1, 2, 3, 4, 5}
|
||||
sb := FromBytes(data)
|
||||
|
||||
if sb.IsEmpty() {
|
||||
t.Error("SecureBytes should not be empty before Clear")
|
||||
}
|
||||
|
||||
sb.Clear()
|
||||
|
||||
if !sb.IsEmpty() {
|
||||
t.Error("SecureBytes should be empty after Clear")
|
||||
}
|
||||
|
||||
if sb.Size() != 0 {
|
||||
t.Error("Size should be 0 after Clear")
|
||||
}
|
||||
|
||||
bytes := sb.Bytes()
|
||||
if bytes != nil {
|
||||
t.Error("Bytes() should return nil after Clear")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("CopyTo", func(t *testing.T) {
|
||||
sb := NewSecureBytes(10)
|
||||
data := []byte{1, 2, 3, 4, 5}
|
||||
|
||||
err := sb.CopyTo(data)
|
||||
if err != nil {
|
||||
t.Fatalf("CopyTo failed: %v", err)
|
||||
}
|
||||
|
||||
retrieved := sb.Bytes()
|
||||
if !bytes.Equal(retrieved[:len(data)], data) {
|
||||
t.Error("CopyTo didn't copy data correctly")
|
||||
}
|
||||
|
||||
// Test overflow
|
||||
largeData := make([]byte, 20)
|
||||
err = sb.CopyTo(largeData)
|
||||
if err == nil {
|
||||
t.Error("CopyTo should fail with oversized data")
|
||||
}
|
||||
|
||||
sb.Clear()
|
||||
|
||||
// Test copy to finalized
|
||||
err = sb.CopyTo(data)
|
||||
if err == nil {
|
||||
t.Error("CopyTo should fail on finalized SecureBytes")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestSecureString(t *testing.T) {
|
||||
t.Run("basic operations", func(t *testing.T) {
|
||||
original := "sensitive data"
|
||||
ss := NewSecureString(original)
|
||||
|
||||
if ss.String() != original {
|
||||
t.Error("String() doesn't match original")
|
||||
}
|
||||
|
||||
if ss.IsEmpty() {
|
||||
t.Error("SecureString should not be empty")
|
||||
}
|
||||
|
||||
ss.Clear()
|
||||
|
||||
if !ss.IsEmpty() {
|
||||
t.Error("SecureString should be empty after Clear")
|
||||
}
|
||||
|
||||
if ss.String() != "" {
|
||||
t.Error("String() should return empty string after Clear")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("empty string", func(t *testing.T) {
|
||||
ss := NewSecureString("")
|
||||
if !ss.IsEmpty() {
|
||||
t.Error("Empty SecureString should report as empty")
|
||||
}
|
||||
ss.Clear()
|
||||
})
|
||||
}
|
||||
|
||||
func TestSecureBuffer(t *testing.T) {
|
||||
t.Run("basic operations", func(t *testing.T) {
|
||||
sb := NewSecureBuffer(100)
|
||||
|
||||
if sb.Size() != 0 {
|
||||
t.Error("New buffer should have size 0")
|
||||
}
|
||||
|
||||
if sb.Capacity() != 100 {
|
||||
t.Errorf("Capacity() = %d, want 100", sb.Capacity())
|
||||
}
|
||||
|
||||
// Write data
|
||||
data1 := []byte("hello")
|
||||
err := sb.Write(data1)
|
||||
if err != nil {
|
||||
t.Fatalf("Write failed: %v", err)
|
||||
}
|
||||
|
||||
if sb.Size() != len(data1) {
|
||||
t.Errorf("Size() = %d, want %d", sb.Size(), len(data1))
|
||||
}
|
||||
|
||||
// Write more data
|
||||
data2 := []byte(" world")
|
||||
err = sb.Write(data2)
|
||||
if err != nil {
|
||||
t.Fatalf("Second write failed: %v", err)
|
||||
}
|
||||
|
||||
expected := append(data1, data2...)
|
||||
result := sb.Read()
|
||||
if !bytes.Equal(result, expected) {
|
||||
t.Errorf("Read() = %q, want %q", string(result), string(expected))
|
||||
}
|
||||
|
||||
sb.Clear()
|
||||
})
|
||||
|
||||
t.Run("overflow protection", func(t *testing.T) {
|
||||
sb := NewSecureBuffer(10)
|
||||
|
||||
// Fill to capacity
|
||||
data := make([]byte, 10)
|
||||
err := sb.Write(data)
|
||||
if err != nil {
|
||||
t.Fatalf("Write to capacity failed: %v", err)
|
||||
}
|
||||
|
||||
// Try to overflow
|
||||
err = sb.Write([]byte{1})
|
||||
if err == nil {
|
||||
t.Error("Write should fail on buffer overflow")
|
||||
}
|
||||
|
||||
sb.Clear()
|
||||
})
|
||||
|
||||
t.Run("reset", func(t *testing.T) {
|
||||
sb := NewSecureBuffer(50)
|
||||
|
||||
data := []byte("test data")
|
||||
err := sb.Write(data)
|
||||
if err != nil {
|
||||
t.Fatalf("Write failed: %v", err)
|
||||
}
|
||||
|
||||
if sb.Size() == 0 {
|
||||
t.Error("Buffer should not be empty before reset")
|
||||
}
|
||||
|
||||
sb.Reset()
|
||||
|
||||
if sb.Size() != 0 {
|
||||
t.Error("Buffer should be empty after reset")
|
||||
}
|
||||
|
||||
if sb.Capacity() != 50 {
|
||||
t.Error("Capacity should be preserved after reset")
|
||||
}
|
||||
|
||||
// Should be able to write again
|
||||
err = sb.Write([]byte("new data"))
|
||||
if err != nil {
|
||||
t.Error("Should be able to write after reset")
|
||||
}
|
||||
|
||||
sb.Clear()
|
||||
})
|
||||
}
|
||||
|
||||
func TestZeroizeMultiple(t *testing.T) {
|
||||
slice1 := []byte{1, 2, 3}
|
||||
slice2 := []byte{4, 5, 6}
|
||||
slice3 := []byte{7, 8, 9}
|
||||
|
||||
ZeroizeMultiple(slice1, slice2, slice3)
|
||||
|
||||
slices := [][]byte{slice1, slice2, slice3}
|
||||
for i, slice := range slices {
|
||||
for j, b := range slice {
|
||||
if b != 0 {
|
||||
t.Errorf("Slice %d, byte %d not zeroed: got %d", i, j, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecureCompare(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
a, b []byte
|
||||
expected bool
|
||||
}{
|
||||
{"equal slices", []byte{1, 2, 3}, []byte{1, 2, 3}, true},
|
||||
{"different content", []byte{1, 2, 3}, []byte{1, 2, 4}, false},
|
||||
{"different length", []byte{1, 2, 3}, []byte{1, 2}, false},
|
||||
{"both empty", []byte{}, []byte{}, true},
|
||||
{"one empty", []byte{1}, []byte{}, false},
|
||||
{"both nil", nil, nil, true},
|
||||
{"one nil", []byte{1}, nil, false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := SecureCompare(tt.a, tt.b)
|
||||
if result != tt.expected {
|
||||
t.Errorf("SecureCompare() = %v, want %v", result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecureRandom(t *testing.T) {
|
||||
sizes := []int{0, 1, 16, 32, 1024}
|
||||
|
||||
for _, size := range sizes {
|
||||
t.Run(fmt.Sprintf("%d bytes", size), func(t *testing.T) {
|
||||
data := make([]byte, size)
|
||||
err := SecureRandom(data)
|
||||
if err != nil {
|
||||
t.Fatalf("SecureRandom failed: %v", err)
|
||||
}
|
||||
|
||||
if size == 0 {
|
||||
return // Nothing to verify for empty slice
|
||||
}
|
||||
|
||||
// For non-zero sizes, verify we got some randomness
|
||||
// (Note: there's a tiny chance this could fail with truly random data)
|
||||
allZeros := true
|
||||
allSame := true
|
||||
first := data[0]
|
||||
|
||||
for _, b := range data {
|
||||
if b != 0 {
|
||||
allZeros = false
|
||||
}
|
||||
if b != first {
|
||||
allSame = false
|
||||
}
|
||||
}
|
||||
|
||||
if size > 1 {
|
||||
if allZeros {
|
||||
t.Error("SecureRandom returned all zeros (suspicious)")
|
||||
}
|
||||
if allSame {
|
||||
t.Error("SecureRandom returned all same values (suspicious)")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Test that finalizers work correctly (this is tricky to test reliably)
|
||||
func TestFinalizers(t *testing.T) {
|
||||
t.Run("SecureBytes finalizer", func(t *testing.T) {
|
||||
// Create a SecureBytes and let it go out of scope
|
||||
func() {
|
||||
sb := NewSecureBytes(32)
|
||||
// Fill with test data
|
||||
data := make([]byte, 32)
|
||||
for i := range data {
|
||||
data[i] = byte(i + 1)
|
||||
}
|
||||
sb.CopyTo(data)
|
||||
// sb goes out of scope here
|
||||
}()
|
||||
|
||||
// Force garbage collection
|
||||
runtime.GC()
|
||||
runtime.GC()
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
// We can't easily verify the finalizer ran, but this tests that
|
||||
// the finalizer doesn't cause a panic
|
||||
})
|
||||
|
||||
t.Run("SecureString finalizer", func(t *testing.T) {
|
||||
func() {
|
||||
ss := NewSecureString("test data")
|
||||
_ = ss.String()
|
||||
// ss goes out of scope here
|
||||
}()
|
||||
|
||||
runtime.GC()
|
||||
runtime.GC()
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
})
|
||||
|
||||
t.Run("SecureBuffer finalizer", func(t *testing.T) {
|
||||
func() {
|
||||
sb := NewSecureBuffer(64)
|
||||
sb.Write([]byte("test data"))
|
||||
// sb goes out of scope here
|
||||
}()
|
||||
|
||||
runtime.GC()
|
||||
runtime.GC()
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkZeroize(b *testing.B) {
|
||||
sizes := []int{32, 256, 1024, 4096}
|
||||
|
||||
for _, size := range sizes {
|
||||
b.Run(fmt.Sprintf("%dB", size), func(b *testing.B) {
|
||||
data := make([]byte, size)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
// Fill with data
|
||||
for j := range data {
|
||||
data[j] = byte(j)
|
||||
}
|
||||
// Zeroize
|
||||
Zeroize(data)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSecureCompare(b *testing.B) {
|
||||
sizes := []int{16, 32, 256, 1024}
|
||||
|
||||
for _, size := range sizes {
|
||||
b.Run(fmt.Sprintf("%dB", size), func(b *testing.B) {
|
||||
a := make([]byte, size)
|
||||
b_slice := make([]byte, size)
|
||||
|
||||
// Fill with identical data
|
||||
for i := range a {
|
||||
a[i] = byte(i)
|
||||
b_slice[i] = byte(i)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
SecureCompare(a, b_slice)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSecureBytes(b *testing.B) {
|
||||
b.Run("NewSecureBytes", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
sb := NewSecureBytes(32)
|
||||
sb.Clear()
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("Bytes", func(b *testing.B) {
|
||||
sb := NewSecureBytes(32)
|
||||
defer sb.Clear()
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
data := sb.Bytes()
|
||||
_ = data
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("CopyTo", func(b *testing.B) {
|
||||
sb := NewSecureBytes(32)
|
||||
defer sb.Clear()
|
||||
|
||||
testData := make([]byte, 16)
|
||||
for i := range testData {
|
||||
testData[i] = byte(i)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
sb.CopyTo(testData)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user