1+ package security
2+
3+ import (
4+ "bytes"
5+ "runtime"
6+ "testing"
7+ )
8+
9+ func TestZeroBytes (t * testing.T ) {
10+ tests := []struct {
11+ name string
12+ data []byte
13+ }{
14+ {
15+ name : "non-empty slice" ,
16+ data : []byte ("sensitive data" ),
17+ },
18+ {
19+ name : "empty slice" ,
20+ data : []byte {},
21+ },
22+ {
23+ name : "nil slice" ,
24+ data : nil ,
25+ },
26+ {
27+ name : "single byte" ,
28+ data : []byte {0x42 },
29+ },
30+ {
31+ name : "binary data" ,
32+ data : []byte {0x01 , 0x02 , 0x03 , 0xff , 0xfe , 0xfd },
33+ },
34+ }
35+
36+ for _ , tt := range tests {
37+ t .Run (tt .name , func (t * testing.T ) {
38+ original := make ([]byte , len (tt .data ))
39+ copy (original , tt .data )
40+
41+ ZeroBytes (tt .data )
42+
43+ // Verify all bytes are zeroed
44+ for i , b := range tt .data {
45+ if b != 0 {
46+ t .Errorf ("byte at index %d not zeroed: got %d, want 0" , i , b )
47+ }
48+ }
49+
50+ // Verify we didn't panic on edge cases
51+ if len (tt .data ) == 0 {
52+ // Should handle empty/nil slices gracefully
53+ return
54+ }
55+
56+ // Verify the slice was actually modified
57+ if bytes .Equal (tt .data , original ) && len (original ) > 0 {
58+ t .Error ("slice was not modified" )
59+ }
60+ })
61+ }
62+ }
63+
64+ func TestZeroString (t * testing.T ) {
65+ tests := []struct {
66+ name string
67+ input string
68+ expected string
69+ }{
70+ {
71+ name : "non-empty string" ,
72+ input : "sensitive password" ,
73+ expected : "" ,
74+ },
75+ {
76+ name : "empty string" ,
77+ input : "" ,
78+ expected : "" ,
79+ },
80+ {
81+ name : "single character" ,
82+ input : "x" ,
83+ expected : "" ,
84+ },
85+ {
86+ name : "unicode string" ,
87+ input : "🔐password🔑" ,
88+ expected : "" ,
89+ },
90+ }
91+
92+ for _ , tt := range tests {
93+ t .Run (tt .name , func (t * testing.T ) {
94+ s := tt .input
95+
96+ ZeroString (& s )
97+
98+ // Verify string is now empty
99+ if s != tt .expected {
100+ t .Errorf ("string not cleared: got %q, want %q" , s , tt .expected )
101+ }
102+
103+ // Note: We can't reliably test if the underlying memory was zeroed
104+ // because Go strings are immutable and memory clearing depends on GC timing
105+ })
106+ }
107+ }
108+
109+ func TestZeroStringNilPointer (t * testing.T ) {
110+ // Test that ZeroString handles nil pointer gracefully
111+ defer func () {
112+ if r := recover (); r != nil {
113+ t .Errorf ("ZeroString panicked with nil pointer: %v" , r )
114+ }
115+ }()
116+
117+ ZeroString (nil )
118+ }
119+
120+ func TestSecureBytes (t * testing.T ) {
121+ t .Run ("basic functionality" , func (t * testing.T ) {
122+ original := []byte ("secret data" )
123+ sb := NewSecureBytes (original )
124+
125+ // Verify data is accessible
126+ data := sb .Bytes ()
127+ if ! bytes .Equal (data , original ) {
128+ t .Errorf ("SecureBytes data mismatch: got %v, want %v" , data , original )
129+ }
130+
131+ // Verify copy works
132+ copied := sb .Copy ()
133+ if ! bytes .Equal (copied , original ) {
134+ t .Errorf ("SecureBytes copy mismatch: got %v, want %v" , copied , original )
135+ }
136+
137+ // Verify modifying copy doesn't affect original
138+ copied [0 ] = 'X'
139+ if bytes .Equal (sb .Bytes (), copied ) {
140+ t .Error ("SecureBytes copy shares memory with original" )
141+ }
142+ })
143+
144+ t .Run ("manual clear" , func (t * testing.T ) {
145+ sb := NewSecureBytes ([]byte ("secret" ))
146+ sb .Clear ()
147+
148+ // After Clear(), data should be nil
149+ if sb .data != nil {
150+ t .Error ("SecureBytes data not nil after Clear()" )
151+ }
152+
153+ // Calling Clear() again should not panic
154+ sb .Clear ()
155+ })
156+
157+ t .Run ("finalizer behavior" , func (t * testing.T ) {
158+ // This test verifies that the finalizer doesn't panic
159+ // We can't easily test that it actually zeros memory due to GC timing
160+ func () {
161+ sb := NewSecureBytes ([]byte ("secret" ))
162+ _ = sb // Use the variable to prevent optimization
163+ }()
164+
165+ // Force garbage collection to potentially trigger finalizer
166+ runtime .GC ()
167+ runtime .GC ()
168+
169+ // If we reach here without panic, the finalizer worked correctly
170+ })
171+
172+ t .Run ("empty data" , func (t * testing.T ) {
173+ sb := NewSecureBytes ([]byte {})
174+
175+ if len (sb .Bytes ()) != 0 {
176+ t .Error ("SecureBytes should handle empty data" )
177+ }
178+
179+ sb .Clear ()
180+ // Should not panic
181+ })
182+
183+ t .Run ("nil data" , func (t * testing.T ) {
184+ sb := NewSecureBytes (nil )
185+
186+ if sb .Bytes () == nil {
187+ t .Error ("SecureBytes should create empty slice for nil input" )
188+ }
189+
190+ if len (sb .Bytes ()) != 0 {
191+ t .Error ("SecureBytes should create empty slice for nil input" )
192+ }
193+ })
194+ }
195+
196+ func TestSecureBytesIsolation (t * testing.T ) {
197+ // Verify that SecureBytes creates its own copy of data
198+ original := []byte ("secret data" )
199+ sb := NewSecureBytes (original )
200+
201+ // Modify the original
202+ original [0 ] = 'X'
203+
204+ // SecureBytes should be unaffected
205+ if sb .Bytes ()[0 ] == 'X' {
206+ t .Error ("SecureBytes shares memory with input data" )
207+ }
208+ }
209+
210+ // Benchmark tests to ensure performance is reasonable
211+ func BenchmarkZeroBytes (b * testing.B ) {
212+ data := make ([]byte , 1024 )
213+ for i := range data {
214+ data [i ] = byte (i % 256 )
215+ }
216+
217+ b .ResetTimer ()
218+ for i := 0 ; i < b .N ; i ++ {
219+ // Reset data for each iteration
220+ for j := range data {
221+ data [j ] = byte (j % 256 )
222+ }
223+ ZeroBytes (data )
224+ }
225+ }
226+
227+ func BenchmarkZeroString (b * testing.B ) {
228+ original := "this is a test string that represents a password or other sensitive data"
229+
230+ b .ResetTimer ()
231+ for i := 0 ; i < b .N ; i ++ {
232+ s := original
233+ ZeroString (& s )
234+ }
235+ }
236+
237+ func BenchmarkSecureBytes (b * testing.B ) {
238+ data := make ([]byte , 256 )
239+ for i := range data {
240+ data [i ] = byte (i )
241+ }
242+
243+ b .ResetTimer ()
244+ for i := 0 ; i < b .N ; i ++ {
245+ sb := NewSecureBytes (data )
246+ _ = sb .Copy ()
247+ sb .Clear ()
248+ }
249+ }
0 commit comments