1package vault
2
3import (
4	"context"
5	"crypto/subtle"
6	"encoding/json"
7	"fmt"
8	"sync/atomic"
9
10	proto "github.com/golang/protobuf/proto"
11	"github.com/hashicorp/errwrap"
12	"github.com/hashicorp/vault/physical"
13	"github.com/hashicorp/vault/vault/seal"
14)
15
16// barrierTypeUpgradeCheck checks for backwards compat on barrier type, not
17// applicable in the OSS side
18var barrierTypeUpgradeCheck = func(_ string, _ *SealConfig) {}
19
20// autoSeal is a Seal implementation that contains logic for encrypting and
21// decrypting stored keys via an underlying AutoSealAccess implementation, as
22// well as logic related to recovery keys and barrier config.
23type autoSeal struct {
24	seal.Access
25
26	barrierConfig  atomic.Value
27	recoveryConfig atomic.Value
28	core           *Core
29}
30
31// Ensure we are implementing the Seal interface
32var _ Seal = (*autoSeal)(nil)
33
34func NewAutoSeal(lowLevel seal.Access) Seal {
35	ret := &autoSeal{
36		Access: lowLevel,
37	}
38	ret.barrierConfig.Store((*SealConfig)(nil))
39	ret.recoveryConfig.Store((*SealConfig)(nil))
40	return ret
41}
42
43func (d *autoSeal) checkCore() error {
44	if d.core == nil {
45		return fmt.Errorf("seal does not have a core set")
46	}
47	return nil
48}
49
50func (d *autoSeal) SetCore(core *Core) {
51	d.core = core
52}
53
54func (d *autoSeal) Init(ctx context.Context) error {
55	return d.Access.Init(ctx)
56}
57
58func (d *autoSeal) Finalize(ctx context.Context) error {
59	return d.Access.Finalize(ctx)
60}
61
62func (d *autoSeal) BarrierType() string {
63	return d.SealType()
64}
65
66func (d *autoSeal) StoredKeysSupported() bool {
67	return true
68}
69
70func (d *autoSeal) RecoveryKeySupported() bool {
71	return true
72}
73
74// SetStoredKeys uses the autoSeal.Access.Encrypts method to wrap the keys. The stored entry
75// does not need to be seal wrapped in this case.
76func (d *autoSeal) SetStoredKeys(ctx context.Context, keys [][]byte) error {
77	if keys == nil {
78		return fmt.Errorf("keys were nil")
79	}
80	if len(keys) == 0 {
81		return fmt.Errorf("no keys provided")
82	}
83
84	buf, err := json.Marshal(keys)
85	if err != nil {
86		return errwrap.Wrapf("failed to encode keys for storage: {{err}}", err)
87	}
88
89	// Encrypt and marshal the keys
90	blobInfo, err := d.Encrypt(ctx, buf)
91	if err != nil {
92		return errwrap.Wrapf("failed to encrypt keys for storage: {{err}}", err)
93	}
94
95	value, err := proto.Marshal(blobInfo)
96	if err != nil {
97		return errwrap.Wrapf("failed to marshal value for storage: {{err}}", err)
98	}
99
100	// Store the seal configuration.
101	pe := &physical.Entry{
102		Key:   StoredBarrierKeysPath,
103		Value: value,
104	}
105
106	if err := d.core.physical.Put(ctx, pe); err != nil {
107		return errwrap.Wrapf("failed to write keys to storage: {{err}}", err)
108	}
109
110	return nil
111}
112
113// GetStoredKeys retrieves the key shares by unwrapping the encrypted key using the
114// autoseal.
115func (d *autoSeal) GetStoredKeys(ctx context.Context) ([][]byte, error) {
116	pe, err := d.core.physical.Get(ctx, StoredBarrierKeysPath)
117	if err != nil {
118		return nil, errwrap.Wrapf("failed to fetch stored keys: {{err}}", err)
119	}
120
121	// This is not strictly an error; we may not have any stored keys, for
122	// instance, if we're not initialized
123	if pe == nil {
124		return nil, nil
125	}
126
127	blobInfo := &physical.EncryptedBlobInfo{}
128	if err := proto.Unmarshal(pe.Value, blobInfo); err != nil {
129		return nil, errwrap.Wrapf("failed to proto decode stored keys: {{err}}", err)
130	}
131
132	pt, err := d.Decrypt(ctx, blobInfo)
133	if err != nil {
134		return nil, errwrap.Wrapf("failed to decrypt encrypted stored keys: {{err}}", err)
135	}
136
137	// Decode the barrier entry
138	var keys [][]byte
139	if err := json.Unmarshal(pt, &keys); err != nil {
140		return nil, fmt.Errorf("failed to decode stored keys: %v", err)
141	}
142
143	return keys, nil
144}
145
146func (d *autoSeal) BarrierConfig(ctx context.Context) (*SealConfig, error) {
147	if d.barrierConfig.Load().(*SealConfig) != nil {
148		return d.barrierConfig.Load().(*SealConfig).Clone(), nil
149	}
150
151	if err := d.checkCore(); err != nil {
152		return nil, err
153	}
154
155	sealType := "barrier"
156
157	entry, err := d.core.physical.Get(ctx, barrierSealConfigPath)
158	if err != nil {
159		d.core.logger.Error("autoseal: failed to read seal configuration", "seal_type", sealType, "error", err)
160		return nil, errwrap.Wrapf(fmt.Sprintf("failed to read %q seal configuration: {{err}}", sealType), err)
161	}
162
163	// If the seal configuration is missing, we are not initialized
164	if entry == nil {
165		if d.core.logger.IsInfo() {
166			d.core.logger.Info("autoseal: seal configuration missing, not initialized", "seal_type", sealType)
167		}
168		return nil, nil
169	}
170
171	conf := &SealConfig{}
172	err = json.Unmarshal(entry.Value, conf)
173	if err != nil {
174		d.core.logger.Error("autoseal: failed to decode seal configuration", "seal_type", sealType, "error", err)
175		return nil, errwrap.Wrapf(fmt.Sprintf("failed to decode %q seal configuration: {{err}}", sealType), err)
176	}
177
178	// Check for a valid seal configuration
179	if err := conf.Validate(); err != nil {
180		d.core.logger.Error("autoseal: invalid seal configuration", "seal_type", sealType, "error", err)
181		return nil, errwrap.Wrapf(fmt.Sprintf("%q seal validation failed: {{err}}", sealType), err)
182	}
183
184	barrierTypeUpgradeCheck(d.BarrierType(), conf)
185
186	if conf.Type != d.BarrierType() {
187		d.core.logger.Error("autoseal: barrier seal type does not match loaded type", "seal_type", conf.Type, "loaded_type", d.BarrierType())
188		return nil, fmt.Errorf("barrier seal type of %q does not match loaded type of %q", conf.Type, d.BarrierType())
189	}
190
191	d.barrierConfig.Store(conf)
192	return conf.Clone(), nil
193}
194
195func (d *autoSeal) SetBarrierConfig(ctx context.Context, conf *SealConfig) error {
196	if err := d.checkCore(); err != nil {
197		return err
198	}
199
200	if conf == nil {
201		d.barrierConfig.Store((*SealConfig)(nil))
202		return nil
203	}
204
205	conf.Type = d.BarrierType()
206
207	// Encode the seal configuration
208	buf, err := json.Marshal(conf)
209	if err != nil {
210		return errwrap.Wrapf("failed to encode barrier seal configuration: {{err}}", err)
211	}
212
213	// Store the seal configuration
214	pe := &physical.Entry{
215		Key:   barrierSealConfigPath,
216		Value: buf,
217	}
218
219	if err := d.core.physical.Put(ctx, pe); err != nil {
220		d.core.logger.Error("autoseal: failed to write barrier seal configuration", "error", err)
221		return errwrap.Wrapf("failed to write barrier seal configuration: {{err}}", err)
222	}
223
224	d.barrierConfig.Store(conf.Clone())
225
226	return nil
227}
228
229func (d *autoSeal) SetCachedBarrierConfig(config *SealConfig) {
230	d.barrierConfig.Store(config)
231}
232
233func (d *autoSeal) RecoveryType() string {
234	return RecoveryTypeShamir
235}
236
237// RecoveryConfig returns the recovery config on recoverySealConfigPlaintextPath.
238func (d *autoSeal) RecoveryConfig(ctx context.Context) (*SealConfig, error) {
239	if d.recoveryConfig.Load().(*SealConfig) != nil {
240		return d.recoveryConfig.Load().(*SealConfig).Clone(), nil
241	}
242
243	if err := d.checkCore(); err != nil {
244		return nil, err
245	}
246
247	sealType := "recovery"
248
249	var entry *physical.Entry
250	var err error
251	entry, err = d.core.physical.Get(ctx, recoverySealConfigPlaintextPath)
252	if err != nil {
253		d.core.logger.Error("autoseal: failed to read seal configuration", "seal_type", sealType, "error", err)
254		return nil, errwrap.Wrapf(fmt.Sprintf("failed to read %q seal configuration: {{err}}", sealType), err)
255	}
256
257	if entry == nil {
258		if d.core.Sealed() {
259			d.core.logger.Info("autoseal: seal configuration missing, but cannot check old path as core is sealed", "seal_type", sealType)
260			return nil, nil
261		}
262
263		// Check the old recovery seal config path so an upgraded standby will
264		// return the correct seal config
265		be, err := d.core.barrier.Get(ctx, recoverySealConfigPath)
266		if err != nil {
267			return nil, errwrap.Wrapf("failed to read old recovery seal configuration: {{err}}", err)
268		}
269
270		// If the seal configuration is missing, then we are not initialized.
271		if be == nil {
272			if d.core.logger.IsInfo() {
273				d.core.logger.Info("autoseal: seal configuration missing, not initialized", "seal_type", sealType)
274			}
275			return nil, nil
276		}
277
278		// Reconstruct the physical entry
279		entry = &physical.Entry{
280			Key:   be.Key,
281			Value: be.Value,
282		}
283	}
284
285	conf := &SealConfig{}
286	if err := json.Unmarshal(entry.Value, conf); err != nil {
287		d.core.logger.Error("autoseal: failed to decode seal configuration", "seal_type", sealType, "error", err)
288		return nil, errwrap.Wrapf(fmt.Sprintf("failed to decode %q seal configuration: {{err}}", sealType), err)
289	}
290
291	// Check for a valid seal configuration
292	if err := conf.Validate(); err != nil {
293		d.core.logger.Error("autoseal: invalid seal configuration", "seal_type", sealType, "error", err)
294		return nil, errwrap.Wrapf(fmt.Sprintf("%q seal validation failed: {{err}}", sealType), err)
295	}
296
297	if conf.Type != d.RecoveryType() {
298		d.core.logger.Error("autoseal: recovery seal type does not match loaded type", "seal_type", conf.Type, "loaded_type", d.RecoveryType())
299		return nil, fmt.Errorf("recovery seal type of %q does not match loaded type of %q", conf.Type, d.RecoveryType())
300	}
301
302	d.recoveryConfig.Store(conf)
303	return conf.Clone(), nil
304}
305
306func (d *autoSeal) RecoveryKey(ctx context.Context) ([]byte, error) {
307	pe, err := d.core.physical.Get(ctx, recoveryKeyPath)
308	if err != nil {
309		d.core.logger.Error("autoseal: failed to read recovery key", "error", err)
310		return nil, errwrap.Wrapf("failed to read recovery key: {{err}}", err)
311	}
312	if pe == nil {
313		d.core.logger.Warn("autoseal: no recovery key found")
314		return nil, fmt.Errorf("no recovery key found")
315	}
316
317	blobInfo := &physical.EncryptedBlobInfo{}
318	if err := proto.Unmarshal(pe.Value, blobInfo); err != nil {
319		return nil, errwrap.Wrapf("failed to proto decode recovery keys: {{err}}", err)
320	}
321
322	pt, err := d.Decrypt(ctx, blobInfo)
323	if err != nil {
324		return nil, errwrap.Wrapf("failed to decrypt encrypted recovery keys: {{err}}", err)
325	}
326
327	return pt, nil
328}
329
330// SetRecoveryConfig writes the recovery configuration to the physical storage
331// and sets it as the seal's recoveryConfig.
332func (d *autoSeal) SetRecoveryConfig(ctx context.Context, conf *SealConfig) error {
333	if err := d.checkCore(); err != nil {
334		return err
335	}
336
337	// Perform migration if applicable
338	if err := d.migrateRecoveryConfig(ctx); err != nil {
339		return err
340	}
341
342	if conf == nil {
343		d.recoveryConfig.Store((*SealConfig)(nil))
344		return nil
345	}
346
347	conf.Type = d.RecoveryType()
348
349	// Encode the seal configuration
350	buf, err := json.Marshal(conf)
351	if err != nil {
352		return errwrap.Wrapf("failed to encode recovery seal configuration: {{err}}", err)
353	}
354
355	// Store the seal configuration directly in the physical storage
356	pe := &physical.Entry{
357		Key:   recoverySealConfigPlaintextPath,
358		Value: buf,
359	}
360
361	if err := d.core.physical.Put(ctx, pe); err != nil {
362		d.core.logger.Error("autoseal: failed to write recovery seal configuration", "error", err)
363		return errwrap.Wrapf("failed to write recovery seal configuration: {{err}}", err)
364	}
365
366	d.recoveryConfig.Store(conf.Clone())
367
368	return nil
369}
370
371func (d *autoSeal) SetCachedRecoveryConfig(config *SealConfig) {
372	d.recoveryConfig.Store(config)
373}
374
375func (d *autoSeal) VerifyRecoveryKey(ctx context.Context, key []byte) error {
376	if key == nil {
377		return fmt.Errorf("recovery key to verify is nil")
378	}
379
380	pt, err := d.RecoveryKey(ctx)
381	if err != nil {
382		return err
383	}
384
385	if subtle.ConstantTimeCompare(key, pt) != 1 {
386		return fmt.Errorf("recovery key does not match submitted values")
387	}
388
389	return nil
390}
391
392func (d *autoSeal) SetRecoveryKey(ctx context.Context, key []byte) error {
393	if err := d.checkCore(); err != nil {
394		return err
395	}
396
397	if key == nil {
398		return fmt.Errorf("recovery key to store is nil")
399	}
400
401	// Encrypt and marshal the keys
402	blobInfo, err := d.Encrypt(ctx, key)
403	if err != nil {
404		return errwrap.Wrapf("failed to encrypt keys for storage: {{err}}", err)
405	}
406
407	value, err := proto.Marshal(blobInfo)
408	if err != nil {
409		return errwrap.Wrapf("failed to marshal value for storage: {{err}}", err)
410	}
411
412	be := &physical.Entry{
413		Key:   recoveryKeyPath,
414		Value: value,
415	}
416
417	if err := d.core.physical.Put(ctx, be); err != nil {
418		d.core.logger.Error("autoseal: failed to write recovery key", "error", err)
419		return errwrap.Wrapf("failed to write recovery key: {{err}}", err)
420	}
421
422	return nil
423}
424
425// migrateRecoveryConfig is a helper func to migrate the recovery config to
426// live outside the barrier. This is called from SetRecoveryConfig which is
427// always called with the stateLock.
428func (d *autoSeal) migrateRecoveryConfig(ctx context.Context) error {
429	// Get config from the old recoverySealConfigPath path
430	be, err := d.core.barrier.Get(ctx, recoverySealConfigPath)
431	if err != nil {
432		return errwrap.Wrapf("failed to read old recovery seal configuration during migration: {{err}}", err)
433	}
434
435	// If this entry is nil, then skip migration
436	if be == nil {
437		return nil
438	}
439
440	// Only log if we are performing the migration
441	d.core.logger.Debug("migrating recovery seal configuration")
442	defer d.core.logger.Debug("done migrating recovery seal configuration")
443
444	// Perform migration
445	pe := &physical.Entry{
446		Key:   recoverySealConfigPlaintextPath,
447		Value: be.Value,
448	}
449
450	if err := d.core.physical.Put(ctx, pe); err != nil {
451		return errwrap.Wrapf("failed to write recovery seal configuration during migration: {{err}}", err)
452	}
453
454	// Perform deletion of the old entry
455	if err := d.core.barrier.Delete(ctx, recoverySealConfigPath); err != nil {
456		return errwrap.Wrapf("failed to delete old recovery seal configuration during migration: {{err}}", err)
457	}
458
459	return nil
460}
461