-
Notifications
You must be signed in to change notification settings - Fork 0
/
mattress.go
127 lines (108 loc) · 4.15 KB
/
mattress.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
// mattress provides a secure way to handle sensitive data within Go applications.
// It leverages the memguard library to create encrypted enclaves for sensitive information,
// ensuring that data is protected both in memory and during runtime. This package is designed
// to mitigate accidental leaks of sensitive data through improper memory handling or
// exposure via runtime panics.
//
// Note: While this package offers enhanced security for sensitive data, it is important to
// acknowledge that no method is entirely foolproof. Users are encouraged to employ this
// package in conjunction with other security best practices for more comprehensive protection.
//
// Warning: This package utilizes runtime finalizers to ensure cleanup of sensitive data. Due
// to the nature of Go's runtime, which does not guarantee immediate execution of finalizers,
// sensitive data may reside in memory longer than anticipated. Users should proceed with
// caution and ensure they fully comprehend the potential implications.
//
// Example Usage:
//
// import m "github.com/garrettladley/mattress"
//
// type User struct {
// Username string
// Password *m.Secret[string]
// }
//
// func main() {
// password, err := m.NewSecret("password")
// if err != nil {
// // handle error
// }
//
// user := User{
// Username: "username",
// Password: password,
// }
//
// fmt.Println(user.Password) // Output: memory address
// fmt.Println(user.Password.String()) // Output: "[SECRET]"
// fmt.Println(user.Password.Expose()) // Output: "password"
// }
package mattress
import (
"bytes"
"encoding/gob"
"runtime"
"sync"
"github.com/awnumar/memguard"
)
// init is called on package load and sets up a signal handler to catch interrupts.
// This ensures that sensitive data is securely wiped from memory if the application
// is interrupted.
func init() {
// CatchInterrupt ensures that if the application is interrupted, any sensitive data
// handled by memguard will be securely wiped from memory before exit.
memguard.CatchInterrupt()
}
// Secret holds a reference to a securely stored piece of data of any type.
// The data is stored within a memguard.LockedBuffer, providing encryption at rest
// and secure memory handling.
type Secret[T any] struct {
buffer *memguard.LockedBuffer // buffer holds the encrypted data
lock sync.RWMutex // synchronize access to the buffer
}
// NewSecret initializes a new Secret with the provided data. It serializes the data using
// encoding/gob and stores it securely using memguard. This function returns an error if
// encoding the data fails or if there is an issue securing the data in memory.
func NewSecret[T any](data T) (*Secret[T], error) {
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
err := enc.Encode(data)
if err != nil {
return nil, err
}
bytes := buf.Bytes()
enclave := memguard.NewEnclave(bytes)
buffer, err := enclave.Open()
if err != nil {
return nil, err
}
// WipeBytes securely erases the original byte slice to minimize the risk of data leakage.
memguard.WipeBytes(bytes)
// Assign a runtime finalizer to ensure the secure buffer is wiped when the Secret is
// garbage collected.
secret := &Secret[T]{buffer: buffer}
runtime.SetFinalizer(secret, func(s *Secret[T]) {
s.zero()
})
return secret, nil
}
// zero securely wipes the memory area holding the sensitive data, ensuring it cannot
// be accessed once the Secret is no longer needed.
func (s *Secret[T]) zero() {
s.buffer.Destroy()
}
// Expose decrypts and returns the stored data. Note that this operation potentially
// exposes sensitive data in memory. Ensure that the returned data is handled securely
// and is wiped from memory when no longer needed.
func (s *Secret[T]) Expose() T {
s.lock.RLock() // RLock before reading the buffer
defer s.lock.RUnlock() // Ensure the lock is RUnlocked when the method returns
var data T
gob.NewDecoder(bytes.NewReader(s.buffer.Bytes())).Decode(&data)
return data
}
// String provides a safe string representation of the Secret, ensuring that sensitive
// data is not accidentally exposed via logging or other string handling mechanisms.
func (s *Secret[T]) String() string {
return "[SECRET]"
}