forked from hashicorp/vault
-
Notifications
You must be signed in to change notification settings - Fork 0
/
barrier_aes_gcm.go
351 lines (297 loc) · 8.52 KB
/
barrier_aes_gcm.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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
package vault
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/json"
"fmt"
"strings"
"sync"
"time"
"github.com/armon/go-metrics"
"github.com/hashicorp/vault/physical"
)
const (
// keyEpoch is the hard coded key epoch. Eventually, when
// a key ring is supported for online key rotation, the epoch
// will be incremented and represents the encryption key to use.
keyEpoch = 1
// epochSize is the number of bytes used for the key epoch.
epochSize = 4
// aesgcmVersionByte is prefixed to a message to allow for
// future versioning of barrier implementations.
aesgcmVersionByte = 0x1
)
// barrierInit is the JSON encoded value stored
type barrierInit struct {
Version int // Version is the current format version
Key []byte // Key is the primary encryption key
}
// AESGCMBarrier is a SecurityBarrier implementation that uses the AES
// cipher core and the Galois Counter Mode block mode. It defaults to
// the golang NONCE default value of 12 and a key size of 256
// bit. AES-GCM is high performance, and provides both confidentiality
// and integrity.
type AESGCMBarrier struct {
backend physical.Backend
l sync.RWMutex
sealed bool
// primary is the AEAD keyed from the encryption key.
// This is the cipher that should be used to encrypt/decrypt
// all the underlying values. It will be available if the
// barrier is unsealed.
primary cipher.AEAD
}
// NewAESGCMBarrier is used to construct a new barrier that uses
// the provided physical backend for storage.
func NewAESGCMBarrier(physical physical.Backend) (*AESGCMBarrier, error) {
b := &AESGCMBarrier{
backend: physical,
sealed: true,
}
return b, nil
}
// Initialized checks if the barrier has been initialized
// and has a master key set.
func (b *AESGCMBarrier) Initialized() (bool, error) {
// Read the init sentinel file
out, err := b.backend.Get(barrierInitPath)
if err != nil {
return false, fmt.Errorf("failed to check for initialization: %v", err)
}
return out != nil, nil
}
// Initialize works only if the barrier has not been initialized
// and makes use of the given master key.
func (b *AESGCMBarrier) Initialize(key []byte) error {
// Verify the key size
min, max := b.KeyLength()
if len(key) < min || len(key) > max {
return fmt.Errorf("Key size must be %d or %d", min, max)
}
// Check if already initialized
if alreadyInit, err := b.Initialized(); err != nil {
return err
} else if alreadyInit {
return ErrBarrierAlreadyInit
}
// Create the AES-GCM
gcm, err := b.aeadFromKey(key)
if err != nil {
return err
}
// Generate encryption key
encrypt, err := b.GenerateKey()
if err != nil {
return fmt.Errorf("failed to generate encryption key: %v", err)
}
defer memzero(encrypt)
// Create the barrier init entry
init := barrierInit{
Version: 1,
Key: encrypt,
}
buf, err := json.Marshal(init)
if err != nil {
return fmt.Errorf("failed to create barrier entry: %v", err)
}
defer memzero(buf)
// Encrypt the barrier init value
value := b.encrypt(gcm, buf)
// Create the barrierInitPath
pe := &physical.Entry{
Key: barrierInitPath,
Value: value,
}
if err := b.backend.Put(pe); err != nil {
return fmt.Errorf("failed to create initialization key: %v", err)
}
return nil
}
// GenerateKey is used to generate a new key
func (b *AESGCMBarrier) GenerateKey() ([]byte, error) {
// Generate a 256bit key
buf := make([]byte, 2*aes.BlockSize)
_, err := rand.Read(buf)
return buf, err
}
// KeyLength is used to sanity check a key
func (b *AESGCMBarrier) KeyLength() (int, int) {
return aes.BlockSize, 2 * aes.BlockSize
}
// Sealed checks if the barrier has been unlocked yet. The Barrier
// is not expected to be able to perform any CRUD until it is unsealed.
func (b *AESGCMBarrier) Sealed() (bool, error) {
b.l.RLock()
defer b.l.RUnlock()
return b.sealed, nil
}
// Unseal is used to provide the master key which permits the barrier
// to be unsealed. If the key is not correct, the barrier remains sealed.
func (b *AESGCMBarrier) Unseal(key []byte) error {
b.l.Lock()
defer b.l.Unlock()
// Do nothing if already unsealed
if !b.sealed {
return nil
}
// Read the barrier initialization key
out, err := b.backend.Get(barrierInitPath)
if err != nil {
return fmt.Errorf("failed to check for initialization: %v", err)
}
if out == nil {
return ErrBarrierNotInit
}
// Create the AES-GCM
gcm, err := b.aeadFromKey(key)
if err != nil {
return err
}
// Decrypt the barrier init key
plain, err := b.decrypt(gcm, out.Value)
if err != nil {
if strings.Contains(err.Error(), "message authentication failed") {
return ErrBarrierInvalidKey
}
return err
}
defer memzero(plain)
// Unmarshal the barrier init
var init barrierInit
if err := json.Unmarshal(plain, &init); err != nil {
return fmt.Errorf("failed to unmarshal barrier init file")
}
defer memzero(init.Key)
// Initialize the master encryption key
b.primary, err = b.aeadFromKey(init.Key)
if err != nil {
return err
}
// Set the vault as unsealed
b.sealed = false
return nil
}
// Seal is used to re-seal the barrier. This requires the barrier to
// be unsealed again to perform any further operations.
func (b *AESGCMBarrier) Seal() error {
b.l.Lock()
defer b.l.Unlock()
// Remove the primary key, and seal the vault
b.primary = nil
b.sealed = true
return nil
}
// Put is used to insert or update an entry
func (b *AESGCMBarrier) Put(entry *Entry) error {
defer metrics.MeasureSince([]string{"barrier", "put"}, time.Now())
b.l.RLock()
defer b.l.RUnlock()
primary := b.primary
if primary == nil {
return ErrBarrierSealed
}
pe := &physical.Entry{
Key: entry.Key,
Value: b.encrypt(primary, entry.Value),
}
return b.backend.Put(pe)
}
// Get is used to fetch an entry
func (b *AESGCMBarrier) Get(key string) (*Entry, error) {
defer metrics.MeasureSince([]string{"barrier", "get"}, time.Now())
b.l.RLock()
defer b.l.RUnlock()
primary := b.primary
if primary == nil {
return nil, ErrBarrierSealed
}
// Read the key from the backend
pe, err := b.backend.Get(key)
if err != nil {
return nil, err
} else if pe == nil {
return nil, nil
}
// Decrypt the ciphertext
plain, err := b.decrypt(primary, pe.Value)
if err != nil {
return nil, fmt.Errorf("decryption failed: %v", err)
}
// Wrap in a logical entry
entry := &Entry{
Key: key,
Value: plain,
}
return entry, nil
}
// Delete is used to permanently delete an entry
func (b *AESGCMBarrier) Delete(key string) error {
defer metrics.MeasureSince([]string{"barrier", "delete"}, time.Now())
b.l.RLock()
defer b.l.RUnlock()
if b.sealed {
return ErrBarrierSealed
}
return b.backend.Delete(key)
}
// List is used ot list all the keys under a given
// prefix, up to the next prefix.
func (b *AESGCMBarrier) List(prefix string) ([]string, error) {
defer metrics.MeasureSince([]string{"barrier", "list"}, time.Now())
b.l.RLock()
defer b.l.RUnlock()
if b.sealed {
return nil, ErrBarrierSealed
}
return b.backend.List(prefix)
}
// aeadFromKey returns an AES-GCM AEAD using the given key.
func (b *AESGCMBarrier) aeadFromKey(key []byte) (cipher.AEAD, error) {
// Create the AES cipher
aesCipher, err := aes.NewCipher(key)
if err != nil {
return nil, fmt.Errorf("failed to create cipher: %v", err)
}
// Create the GCM mode AEAD
gcm, err := cipher.NewGCM(aesCipher)
if err != nil {
return nil, fmt.Errorf("failed to initialize GCM mode")
}
return gcm, nil
}
// encrypt is used to encrypt a value
func (b *AESGCMBarrier) encrypt(gcm cipher.AEAD, plain []byte) []byte {
// Allocate the output buffer with room for epoch, version byte,
// nonce, GCM tag and the plaintext
capacity := epochSize + 1 + gcm.NonceSize() + gcm.Overhead() + len(plain)
size := epochSize + 1 + gcm.NonceSize()
out := make([]byte, size, capacity)
// Set the epoch to 1
out[3] = keyEpoch
// Set the version byte
out[4] = aesgcmVersionByte
// Generate a random nonce
nonce := out[5 : 5+gcm.NonceSize()]
rand.Read(nonce)
// Seal the output
out = gcm.Seal(out, nonce, plain, nil)
return out
}
// decrypt is used to decrypt a value
func (b *AESGCMBarrier) decrypt(gcm cipher.AEAD, cipher []byte) ([]byte, error) {
// Verify the epoch
if cipher[0] != 0 || cipher[1] != 0 || cipher[2] != 0 || cipher[3] != keyEpoch {
return nil, fmt.Errorf("epoch mis-match")
}
// Verify the version byte
if cipher[4] != aesgcmVersionByte {
return nil, fmt.Errorf("version bytes mis-match")
}
// Capture the parts
nonce := cipher[5 : 5+gcm.NonceSize()]
raw := cipher[5+gcm.NonceSize():]
out := make([]byte, 0, len(raw)-gcm.NonceSize())
// Attempt to open
return gcm.Open(out, nonce, raw, nil)
}