├── LICENSE ├── secrets.go └── secrets_test.go /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2014 Stephen Touset 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /secrets.go: -------------------------------------------------------------------------------- 1 | // Package secrets creates low-level heap-allocated buffers safe for 2 | // the storage of cryptographic secrets. 3 | // 4 | // A Secret is protected from being read from, written to, or executed 5 | // once allocated. They are prevented from being paged out to swap 6 | // (although systems that hibernate will be able to bypass this 7 | // restriction), they have guard pages and a canary to protect against 8 | // buffer overflows and underflows, and their contents are 9 | // automatically zeroed when they become garbage collected. 10 | // 11 | // A Secret attempts to protect memory contents pessimistically. If 12 | // the memory cannot be protected during initialization, no memory 13 | // will be allocated and an error will be returned. If memory 14 | // protections cannot be maintained during the lifespan of an existing 15 | // Secret, the library will panic. 16 | // 17 | // The use of this package should be limited to storing cryptographic 18 | // secrets. In order to provide the promised protections, allocations 19 | // are significantly larger than the amount of memory requested and 20 | // the number of operations during allocation is more than with 21 | // typical allocators like malloc. 22 | // 23 | // Examples 24 | // 25 | // // Create a new Secret 32 bytes in size. The Secret is 26 | // // initialized to zero bytes. 27 | // secret, err := NewSecret(32) 28 | // 29 | // // Allow the Secret to be written to. We defer locking the Secret 30 | // // so we can guarantee that its contents are protected once the 31 | // // function returns. 32 | // secret.Write() 33 | // defer secret.Lock() 34 | // 35 | // // Read (up to) 32 bytes from stdin into the secret 36 | // os.Stdin.Read(secret.Slice()) 37 | // 38 | // // This will automatically happen once the Secret is garbage 39 | // // collected, but being explicit allows the memory to be zeroed 40 | // // out earlier. 41 | // secret.Wipe() 42 | // 43 | package secrets 44 | 45 | // #cgo pkg-config: libsodium 46 | // 47 | // #include 48 | // #include 49 | // #include 50 | // 51 | // #include 52 | // #include 53 | // #include 54 | // 55 | // #define _MAP_FAILED (intptr_t)MAP_FAILED 56 | import "C" 57 | 58 | import ( 59 | "reflect" 60 | "runtime" 61 | "unsafe" 62 | ) 63 | 64 | var ( 65 | // the size of a page of memory 66 | pageSize = C.size_t(C.getpagesize()) 67 | 68 | // the canary will be filled during init() 69 | canarySize = C.size_t(128) 70 | canary = C.malloc(canarySize) 71 | ) 72 | 73 | func init() { 74 | if canary == nil { 75 | panic("secrets: couldn't allocate memory for a canary") 76 | } 77 | 78 | if int(C.sodium_init()) == -1 { 79 | panic("secrets: libsodium couldn't be initialized") 80 | } 81 | 82 | // give the canary a cryptographically random default value 83 | C.randombytes_buf(canary, canarySize) 84 | } 85 | 86 | // A Secret contains a protected cryptographic secret. The contents of 87 | // the Secret may only be accessed when explicitly unlocked, and its 88 | // memory is zeroed out before being released back to the operating system. 89 | type Secret struct { 90 | secret *secret 91 | } 92 | 93 | // NewSecret creates a new Secret capable of storing len bytes. The 94 | // Secret cannot be read from or written to until unlocked. 95 | // 96 | // If memory allocation fails or memory regions can't be adequately 97 | // protected, an error will be returned. 98 | func NewSecret( 99 | len int, 100 | ) (*Secret, error) { 101 | var ( 102 | sec Secret 103 | err error 104 | ) 105 | 106 | sec = Secret{&secret{}} 107 | 108 | // empty secrets are valid, but we don't have anything to do 109 | if len == 0 { 110 | return &sec, nil 111 | } 112 | 113 | if err = sec.secret.alloc(C.size_t(len)); err != nil { 114 | return nil, err 115 | } 116 | 117 | return &sec, nil 118 | } 119 | 120 | // NewSecretFromBytes creates a new Secret from a preexisting byte 121 | // slice. The contents of the byte slice are zeroed out after they are 122 | // copied into the Secret. 123 | // 124 | // If memory allocation fails or memory regions can't be adequately 125 | // protected, an error will be returned. 126 | // 127 | // Note that a Secret allocated this way cannot make any security 128 | // guarantees about the original contents of the byte slice. They may 129 | // have been copied by other parts of the program, or silently copied 130 | // by the Go runtime. If you must allocate a Secret from a byte slice, 131 | // it should be done as soon as possible after the byte slice has had 132 | // the secret data written to it. 133 | func NewSecretFromBytes( 134 | data []byte, 135 | ) (*Secret, error) { 136 | var ( 137 | dataPtr, dataSize = _byteSlicePtrSize(data) 138 | secret, err = NewSecret(len(data)) 139 | ) 140 | 141 | if err != nil { 142 | return nil, err 143 | } 144 | 145 | secret.Write() 146 | defer secret.Lock() 147 | 148 | C.memcpy(secret.Pointer(), dataPtr, dataSize) 149 | C.sodium_memzero(dataPtr, dataSize) 150 | 151 | return secret, nil 152 | } 153 | 154 | // Returns the length of the Secret in bytes. 155 | func (s Secret) Len() int { return int(s.Size()) } 156 | 157 | // Returns the C size_t length of the Secret in bytes 158 | func (s Secret) Size() C.size_t { return s.secret.size } 159 | 160 | // Locks the Secret, preventing any access to its contents. 161 | func (s Secret) Lock() { s.secret.lock() } 162 | 163 | // Allows the Secret's contents to be read. Immediately after calling 164 | // this method, always `defer secret.Lock()` to ensure its protection 165 | // is restored. 166 | func (s Secret) Read() { s.secret.unlock(C.PROT_READ) } 167 | 168 | // Allows the Secret's contents to be written to. Immediately after 169 | // calling this method, always `defer secret.Lock()` to ensure its 170 | // protection is restored. 171 | func (s Secret) Write() { s.secret.unlock(C.PROT_WRITE) } 172 | 173 | // Allows the Secret's contents to be read from and written 174 | // to. Immediately after calling this method, always `defer 175 | // secret.Lock()` to ensure its protection is restored. 176 | func (s Secret) ReadWrite() { s.secret.unlock(C.PROT_READ | C.PROT_WRITE) } 177 | 178 | // Returns an unsafe.Pointer pointing to the memory contents of the 179 | // Secret. When accessing memory through this pointer, take care to 180 | // never access more than Len() bytes from this pointer. This pointer 181 | // can only be read from or written to when the Secret itself is 182 | // unlocked. 183 | func (s Secret) Pointer() unsafe.Pointer { 184 | return s.secret.ptr 185 | } 186 | 187 | // Returns a byte slice containing the contents of the Secret. This 188 | // slice may only be read from or written to when the Secret itself is 189 | // unlocked. Take care not to create copies of the contents of the 190 | // returned slice. 191 | func (s Secret) Slice() []byte { 192 | sh := reflect.SliceHeader{ 193 | Data: uintptr(s.Pointer()), 194 | Len: s.Len(), 195 | Cap: s.Len(), 196 | } 197 | 198 | // cast the address of the SliceHeader into a slice pointer, 199 | // then take the value of that pointer to get the data as an 200 | // actual slice 201 | return *(*[]byte)(unsafe.Pointer(&sh)) 202 | } 203 | 204 | // Copies a Secret's contents into a new Secret. If either allocating 205 | // the new Secret or unlocking the existing Secret fails, returns an 206 | // error. 207 | func (s Secret) Copy() (*Secret, error) { 208 | copy, err := NewSecret(s.Len()) 209 | 210 | if err != nil { 211 | return nil, err 212 | } 213 | 214 | copy.Write() 215 | defer copy.Lock() 216 | 217 | s.Read() 218 | defer s.Lock() 219 | 220 | C.memcpy( 221 | copy.Pointer(), 222 | s.Pointer(), 223 | s.Size(), 224 | ) 225 | 226 | return copy, nil 227 | } 228 | 229 | // Reduces the size of the Secret to len bytes. The location of the 230 | // overflow canary is adjusted to reflect the new size of the 231 | // Secret. If len is larger than the current length of the secret, 232 | // no operation is performed. 233 | func (s Secret) Trim(len int) error { 234 | // trim only shrinks; otherwise it's a no-op 235 | if len >= s.Len() { 236 | return nil 237 | } 238 | 239 | return s.secret.realloc(C.size_t(len)) 240 | } 241 | 242 | // Splits the Secret into two halves, with the right half beginning at 243 | // the specified offset. The original secret is trimmed to only 244 | // contain the contents of the left half, and the contents of the 245 | // right half are copied into a new Secret which is returned. 246 | func (s Secret) Split(offset int) (*Secret, error) { 247 | var ( 248 | right *Secret 249 | err error 250 | ) 251 | 252 | s.ReadWrite() 253 | defer s.Lock() 254 | 255 | if right, err = NewSecretFromBytes(s.Slice()[offset:]); err != nil { 256 | return nil, err 257 | } 258 | 259 | s.Trim(offset) 260 | 261 | return right, nil 262 | } 263 | 264 | // Compares two Secrets for equality in constant time. 265 | func (s Secret) Equal(other *Secret) bool { 266 | if s.Len() != other.Len() { 267 | return false 268 | } 269 | 270 | s.Read() 271 | defer s.Lock() 272 | 273 | other.Read() 274 | defer other.Lock() 275 | 276 | ret := C.sodium_memcmp( 277 | other.Pointer(), 278 | s.Pointer(), 279 | s.Size(), 280 | ) 281 | 282 | return ret == 0 283 | } 284 | 285 | // Immediately zeroes out and releases the Secret's memory. Any 286 | // attempt to reuse a Secret after a call to Wipe() will result in 287 | // undefined behavior. 288 | func (s Secret) Wipe() { 289 | // no need to run the finalizer now; this prevents us from 290 | // accidentally trying to re-free the same memory 291 | runtime.SetFinalizer(s.secret, nil) 292 | 293 | // explicitly zero out and free memory 294 | s.secret.free() 295 | s.secret = nil 296 | } 297 | 298 | // The actual struct that holds pointers to the underlying data for a 299 | // Secret. This is structured so that the secret has a finalizer which 300 | // cleans up and frees allocated memory once it is garbage collected, 301 | // but a Secret can be copied around (for instance, by passing them as 302 | // values to functions) and garbage collected without invoking the 303 | // finalizer. 304 | type secret struct { 305 | ptr unsafe.Pointer 306 | size C.size_t 307 | } 308 | 309 | // Allocates enough memory to contain size bytes, plus room for a 310 | // canary and a guard page before and after the allocation. The pages 311 | // are locked into memory. 312 | // 313 | // The allocated memory is zeroed. 314 | func (s *secret) alloc(size C.size_t) error { 315 | var err error 316 | 317 | // calculate the size of the user region, then allocate enough 318 | // guarded pages for that amount 319 | s.size = size 320 | s.ptr, err = guardedAlloc(size) 321 | 322 | if err != nil { 323 | return err 324 | } 325 | 326 | // ensure we clean up after ourselves, now that we've 327 | // allocated memory 328 | runtime.SetFinalizer(s, func(s *secret) { s.free() }) 329 | 330 | s.unlock(C.PROT_WRITE) 331 | defer s.lock() 332 | 333 | C.sodium_memzero(s.ptr, s.size) 334 | 335 | return nil 336 | } 337 | 338 | func (s *secret) realloc(size C.size_t) error { 339 | ptr, err := guardedRealloc(s.ptr, s.size, size) 340 | 341 | if err != nil { 342 | return err 343 | } 344 | 345 | s.ptr = ptr 346 | s.size = size 347 | 348 | return nil 349 | } 350 | 351 | // Zeroes out the contents of the secret and releases its memory back 352 | // to the system. 353 | func (s *secret) free() { 354 | // free the entire allocated region 355 | guardedFree(s.ptr, s.size) 356 | 357 | // don't maintain dangling pointers 358 | s.ptr = nil 359 | s.size = 0 360 | } 361 | 362 | // Locks the secret's contents, preventing them from being read, 363 | // written to, or executed. 364 | func (s *secret) lock() { 365 | if ret, err := C.mprotect(s.ptr, s.size, C.PROT_NONE); ret != 0 { 366 | panic(err) 367 | } 368 | } 369 | 370 | // Unlocks the secret's contents, giving them the protection level 371 | // specified. 372 | func (s *secret) unlock(prot C.int) { 373 | if ret, err := C.mprotect(s.ptr, s.size, prot); ret != 0 { 374 | panic(err) 375 | } 376 | } 377 | 378 | // Calculates the size of an allocation with enough room for two extra 379 | // guard pages. 380 | func guardedAllocSize(size C.size_t) C.size_t { 381 | return 2*pageSize + _pageRound(size) 382 | } 383 | 384 | // Allocates the requested amount of memory, plus two guard pages. The 385 | // entire region is protected against any memory access. The pointer 386 | // returned points to a region inbetween the guard pages with enough 387 | // space to contain size bytes. An error is returned if the memory 388 | // can't be allocated or protected. 389 | func guardedAlloc(size C.size_t) (unsafe.Pointer, error) { 390 | var ( 391 | userSize = size + canarySize 392 | allocSize = guardedAllocSize(userSize) 393 | ) 394 | 395 | allocPtr, err := C.mmap(nil, allocSize, C.PROT_NONE, C.MAP_ANON|C.MAP_PRIVATE, -1, 0) 396 | 397 | if int(uintptr(allocPtr)) == C._MAP_FAILED { 398 | return nil, err 399 | } 400 | 401 | userPtr := _ptrAdd(allocPtr, pageSize) 402 | 403 | // we only need to mlock the user region; the guard pages can 404 | // be swapped to disk if the OS wants to 405 | if ret, err := C.sodium_mlock(userPtr, userSize); ret != 0 { 406 | return nil, err 407 | } 408 | 409 | canaryWrite(userPtr, size) 410 | 411 | // return a pointer to the interior non-guard pages 412 | return userPtr, nil 413 | } 414 | 415 | func guardedRealloc( 416 | ptr unsafe.Pointer, 417 | old C.size_t, 418 | new C.size_t, 419 | ) (unsafe.Pointer, error) { 420 | if old == new { 421 | return ptr, nil 422 | } 423 | 424 | if old > new { 425 | // TODO(stephen): 426 | // - wipe the now-unused part of the secret 427 | 428 | canaryVerify(ptr, old) 429 | canaryWrite(ptr, new) 430 | 431 | return ptr, nil 432 | } 433 | 434 | panic("secrets: guardedRealloc only shrinks allocations") 435 | } 436 | 437 | // Frees an earlier allocation of the given number of bytes. Also 438 | // makes sure to free the surrounding pages. 439 | func guardedFree(ptr unsafe.Pointer, size C.size_t) { 440 | var ( 441 | allocSize = guardedAllocSize(size) 442 | userSize = size + canarySize 443 | 444 | allocPtr = _ptrAdd(ptr, -pageSize) 445 | userPtr = ptr 446 | ) 447 | 448 | canaryVerify(userPtr, size) 449 | 450 | if ret, err := C.mprotect(userPtr, userSize, C.PROT_READ|C.PROT_WRITE); ret != 0 { 451 | panic(err) 452 | } 453 | 454 | // wipe the user region (and canary, to avoid it from being leaked) 455 | C.sodium_munlock(userPtr, userSize) 456 | 457 | C.munmap(allocPtr, allocSize) 458 | } 459 | 460 | func canaryWrite(ptr unsafe.Pointer, size C.size_t) { 461 | var ( 462 | canaryPtr = _ptrAdd(ptr, size) 463 | canaryPagePtr = _ptrPageRound(canaryPtr) 464 | ) 465 | 466 | // allow the user region to be written to, for the canary 467 | if ret, _ := C.mprotect(canaryPagePtr, canarySize, C.PROT_WRITE); ret != 0 { 468 | panic("secrets: couldn't write a canary") 469 | } 470 | 471 | // write the canary immediately after the user region 472 | C.memcpy(canaryPtr, canary, canarySize) 473 | 474 | // re-lock the user region 475 | if ret, _ := C.mprotect(canaryPagePtr, canarySize, C.PROT_NONE); ret != 0 { 476 | panic("secrets: couldn't write a canary") 477 | } 478 | } 479 | 480 | func canaryVerify(ptr unsafe.Pointer, size C.size_t) { 481 | var ( 482 | canaryPtr = _ptrAdd(ptr, size) 483 | canaryPagePtr = _ptrPageRound(canaryPtr) 484 | ) 485 | 486 | // ensure the canary can be read and the user area can be 487 | // wiped clean 488 | if ret, err := C.mprotect(canaryPagePtr, canarySize, C.PROT_READ); ret != 0 { 489 | panic(err) 490 | } 491 | 492 | // verify the canary 493 | if C.memcmp(canaryPtr, canary, canarySize) != C.int(0) { 494 | panic("secrets: buffer overflow canary triggered") 495 | } 496 | } 497 | 498 | // Rounds the provided pointer to the beginning of its page. 499 | func _ptrPageRound(ptr unsafe.Pointer) unsafe.Pointer { 500 | return _ptrAdd(ptr, -(C.size_t(uintptr(ptr)) % pageSize)) 501 | } 502 | 503 | // Rounds size to the next highest page boundary. 504 | func _pageRound(size C.size_t) C.size_t { 505 | return (size/pageSize)*pageSize + pageSize 506 | } 507 | 508 | // Returns a pointer to the underlying buffer and the size of a byte slice. 509 | func _byteSlicePtrSize(slice []byte) (unsafe.Pointer, C.size_t) { 510 | sh := (*reflect.SliceHeader)(unsafe.Pointer(&slice)) 511 | 512 | return unsafe.Pointer(sh.Data), C.size_t(sh.Len) 513 | } 514 | 515 | // Performs pointer arithmetic, adding an offset (positive or 516 | // negative) to the provided pointer. 517 | func _ptrAdd(ptr unsafe.Pointer, offset C.size_t) unsafe.Pointer { 518 | return unsafe.Pointer(uintptr(ptr) + uintptr(offset)) 519 | } 520 | -------------------------------------------------------------------------------- /secrets_test.go: -------------------------------------------------------------------------------- 1 | package secrets 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func ExampleNewSecret() { 9 | // allocate a new 8-byte secret 10 | secret, err := NewSecret(8) 11 | 12 | if err != nil { 13 | return 14 | } 15 | 16 | // allow the secret to be read; good hygeine dictates that the 17 | // Lock() should be deferred, so it is guaranteed to happen 18 | // when the method returns 19 | secret.Read() 20 | defer secret.Lock() 21 | 22 | fmt.Printf("0x%x", secret.Slice()) 23 | 24 | // while not strictly necessary, explicitly wiping the secret 25 | // when done is also good hygeine; this will happen when 26 | // the secret is garbage collected, but doing it explicitly 27 | // causes the memory to be zeroed immediately 28 | secret.Wipe() 29 | 30 | // Output: 0x0000000000000000 31 | } 32 | 33 | func ExampleNewSecretFromBytes() { 34 | var ( 35 | bytes = []byte("secrets!") 36 | secret, err = NewSecretFromBytes(bytes) 37 | ) 38 | 39 | if err != nil { 40 | return 41 | } 42 | 43 | secret.Read() 44 | defer secret.Lock() 45 | 46 | fmt.Printf("0x%x", secret.Slice()) 47 | 48 | secret.Wipe() 49 | 50 | // Output: 0x7365637265747321 51 | } 52 | 53 | func ExampleNewSecretFromBytes_zeroing() { 54 | var ( 55 | bytes = []byte("secrets!") 56 | _, err = NewSecretFromBytes(bytes) 57 | ) 58 | 59 | if err != nil { 60 | return 61 | } 62 | 63 | fmt.Printf("0x%x", bytes) 64 | 65 | // Output: 0x0000000000000000 66 | } 67 | 68 | func ExampleSecret_Equal() { 69 | var ( 70 | secret1, _ = NewSecretFromBytes([]byte("secret")) 71 | secret2, _ = NewSecretFromBytes([]byte("secret")) 72 | secret3, _ = NewSecretFromBytes([]byte("secrex")) 73 | ) 74 | 75 | fmt.Printf("%t %t", secret1.Equal(secret2), secret1.Equal(secret3)) 76 | 77 | // Output: true false 78 | } 79 | 80 | func ExampleSecret_Trim() { 81 | secret, err := NewSecretFromBytes([]byte("secret!")) 82 | 83 | if err != nil { 84 | return 85 | } 86 | 87 | secret.Trim(4) 88 | 89 | secret.Read() 90 | defer secret.Lock() 91 | 92 | fmt.Printf("%s", secret.Slice()) 93 | 94 | secret.Wipe() 95 | 96 | // Output: secr 97 | } 98 | 99 | func ExampleSecret_Split() { 100 | var ( 101 | secret1, _ = NewSecretFromBytes([]byte("secret!")) 102 | secret2, _ = secret1.Split(4) 103 | ) 104 | 105 | secret1.Read() 106 | defer secret1.Lock() 107 | 108 | secret2.Read() 109 | defer secret2.Lock() 110 | 111 | fmt.Printf("%s %s", secret1.Slice(), secret2.Slice()) 112 | 113 | // Output: secr et! 114 | } 115 | 116 | func ExampleSecret_Wipe() { 117 | secret, err := NewSecretFromBytes([]byte("secret!")) 118 | 119 | if err != nil { 120 | return 121 | } 122 | 123 | secret.Wipe() 124 | 125 | // The pointer to the Secret should be NULL 126 | fmt.Printf("%x\n", secret.Pointer()) 127 | 128 | // Output: 0 129 | } 130 | 131 | func TestNewSecret(t *testing.T) { 132 | var ( 133 | secret1 *Secret 134 | secret2 *Secret 135 | err error 136 | ) 137 | 138 | secret1, err = NewSecret(32) 139 | 140 | if err != nil { 141 | t.Error("NewSecret(32) = _, err; want nil") 142 | } 143 | 144 | secret1.Write() 145 | defer secret1.Lock() 146 | 147 | copy(secret1.Slice(), "cryptographic secrets are secret") 148 | 149 | secret2, err = secret1.Copy() 150 | 151 | if err != nil { 152 | t.Error("Copy() = _, err; want nil") 153 | } 154 | 155 | if !secret1.Equal(secret2) { 156 | t.Error("sec1.Equal(sec2) = false; want true") 157 | } 158 | } 159 | 160 | func TestEmptySecret(t *testing.T) { 161 | secret, err := NewSecret(0) 162 | 163 | if err != nil { 164 | t.Error("NewSecret(0) = _, err; want nil") 165 | } 166 | 167 | if secret.Pointer() != nil { 168 | t.Errorf("secret.Pointer() = %x; want 0", secret.Pointer()) 169 | } 170 | } 171 | 172 | func TestSecretCanary(t *testing.T) { 173 | var ( 174 | secret, _ = NewSecret(32) 175 | slice = secret.Slice() 176 | ) 177 | 178 | // trim the secret by one byte 179 | secret.Trim(31) 180 | 181 | secret.Write() 182 | defer secret.Lock() 183 | 184 | defer func() { 185 | if recover() == nil { 186 | t.Error("secret's canary should have triggered") 187 | } 188 | }() 189 | 190 | // attempt to write past the secret's reduced length 191 | slice[31] = 42 192 | 193 | // the canary should trigger when the secret is wiped 194 | secret.Wipe() 195 | } 196 | 197 | func TestSecretCopies(t *testing.T) { 198 | var ( 199 | secret1, _ = NewSecretFromBytes([]byte("secret stuff")) 200 | secret2, _ = NewSecretFromBytes([]byte("secret stuff")) 201 | secret3, _ = secret1.Copy() 202 | ) 203 | 204 | secret1.Wipe() 205 | 206 | if !secret3.Equal(secret2) { 207 | // oops, looks like we didn't actually copy the contents 208 | t.Error("secret3.Equal(secret2) = false; want true") 209 | } 210 | } 211 | --------------------------------------------------------------------------------