cipher_crypto.go

v1.0.0
Doc Versions Source
1
package bitwarden
2
3
import (
4
	cryptorand "crypto/rand"
5
	"encoding/json"
6
	"fmt"
7
	"io"
8
9
	"sourcecraft.dev/bigbes/go-bitwarden/crypto"
10
)
11
12
var cryptoRandRead = func(b []byte) (int, error) {
13
	return io.ReadFull(cryptorand.Reader, b)
14
}
15
16
// encryptedCipher is the server-side representation of a cipher (all fields encrypted).
17
type encryptedCipher struct {
18
	ID              string          `json:"id,omitempty"`
19
	OrganizationID  string          `json:"organizationId,omitempty"`
20
	FolderID        *string         `json:"folderId,omitempty"`
21
	Type            ItemType        `json:"type"`
22
	Reprompt        int             `json:"reprompt"`
23
	Name            string          `json:"name"`
24
	Notes           string          `json:"notes,omitempty"`
25
	Favorite        bool            `json:"favorite"`
26
	Key             string          `json:"key,omitempty"`
27
	Login           *encryptedLogin `json:"login,omitempty"`
28
	Card            json.RawMessage `json:"card,omitempty"`
29
	Identity        json.RawMessage `json:"identity,omitempty"`
30
	SecureNote      *SecureNote     `json:"secureNote,omitempty"`
31
	Fields          json.RawMessage `json:"fields,omitempty"`
32
	PasswordHistory json.RawMessage `json:"passwordHistory,omitempty"`
33
	CollectionIDs   []string        `json:"collectionIds,omitempty"`
34
	RevisionDate    string          `json:"revisionDate,omitempty"`
35
	CreationDate    string          `json:"creationDate,omitempty"`
36
	DeletedDate     *string         `json:"deletedDate,omitempty"`
37
	Attachments     json.RawMessage `json:"attachments,omitempty"`
38
}
39
40
type encryptedLogin struct {
41
	Username string              `json:"username,omitempty"`
42
	Password string              `json:"password,omitempty"`
43
	TOTP     string              `json:"totp,omitempty"`
44
	URIs     []encryptedLoginURI `json:"uris,omitempty"`
45
}
46
47
type encryptedLoginURI struct {
48
	URI   string        `json:"uri,omitempty"`
49
	Match *URIMatchType `json:"match,omitempty"`
50
}
51
52
type encryptedField struct {
53
	Name     string    `json:"name,omitempty"`
54
	Value    string    `json:"value,omitempty"`
55
	Type     FieldType `json:"type"`
56
	LinkedID *int      `json:"linkedId,omitempty"`
57
}
58
59
type encryptedFolder struct {
60
	ID           string `json:"id,omitempty"`
61
	Name         string `json:"name"`
62
	RevisionDate string `json:"revisionDate,omitempty"`
63
}
64
65
// encryptItem encrypts an Item into the server wire format.
66
func encryptItem(item Item, kc *crypto.KeyChain) (*encryptedCipher, error) {
67
	key, err := kc.KeyForCipher(item.OrganizationID, "")
68
	if err != nil {
69
		return nil, fmt.Errorf("get encryption key: %w", err)
70
	}
71
72
	ec := &encryptedCipher{
73
		ID:             item.ID,
74
		OrganizationID: item.OrganizationID,
75
		FolderID:       item.FolderID,
76
		Type:           item.Type,
77
		Reprompt:       item.Reprompt,
78
		Favorite:       item.Favorite,
79
		SecureNote:     item.SecureNote,
80
		CollectionIDs:  item.CollectionIDs,
81
		RevisionDate:   item.RevisionDate,
82
		CreationDate:   item.CreationDate,
83
		DeletedDate:    item.DeletedDate,
84
	}
85
86
	if ec.Name, err = encryptStr(item.Name, key); err != nil {
87
		return nil, fmt.Errorf("encrypt name: %w", err)
88
	}
89
	if item.Notes != "" {
90
		if ec.Notes, err = encryptStr(item.Notes, key); err != nil {
91
			return nil, fmt.Errorf("encrypt notes: %w", err)
92
		}
93
	}
94
95
	if item.Login != nil {
96
		el := &encryptedLogin{}
97
		if el.Username, err = encryptStrOpt(item.Login.Username, key); err != nil {
98
			return nil, err
99
		}
100
		if el.Password, err = encryptStrOpt(item.Login.Password, key); err != nil {
101
			return nil, err
102
		}
103
		if el.TOTP, err = encryptStrOpt(item.Login.TOTP, key); err != nil {
104
			return nil, err
105
		}
106
		for _, u := range item.Login.URIs {
107
			eu := encryptedLoginURI{Match: u.Match}
108
			if eu.URI, err = encryptStrOpt(u.URI, key); err != nil {
109
				return nil, err
110
			}
111
			el.URIs = append(el.URIs, eu)
112
		}
113
		ec.Login = el
114
	}
115
116
	if item.Card != nil {
117
		ec.Card, err = encryptCard(item.Card, key)
118
		if err != nil {
119
			return nil, err
120
		}
121
	}
122
123
	if item.Identity != nil {
124
		ec.Identity, err = encryptIdentity(item.Identity, key)
125
		if err != nil {
126
			return nil, err
127
		}
128
	}
129
130
	if len(item.Fields) > 0 {
131
		ec.Fields, err = encryptFields(item.Fields, key)
132
		if err != nil {
133
			return nil, err
134
		}
135
	}
136
137
	if len(item.PasswordHistory) > 0 {
138
		ec.PasswordHistory, err = encryptPasswordHistory(item.PasswordHistory, key)
139
		if err != nil {
140
			return nil, err
141
		}
142
	}
143
144
	return ec, nil
145
}
146
147
// decryptCipher decrypts a server cipher into an Item.
148
func decryptCipher(data []byte, kc *crypto.KeyChain) (*Item, error) {
149
	var ec encryptedCipher
150
	if err := json.Unmarshal(data, &ec); err != nil {
151
		return nil, fmt.Errorf("unmarshal cipher: %w", err)
152
	}
153
154
	key, err := kc.KeyForCipher(ec.OrganizationID, ec.Key)
155
	if err != nil {
156
		return nil, fmt.Errorf("get decryption key: %w", err)
157
	}
158
159
	item := &Item{
160
		ID:             ec.ID,
161
		OrganizationID: ec.OrganizationID,
162
		FolderID:       ec.FolderID,
163
		Type:           ec.Type,
164
		Reprompt:       ec.Reprompt,
165
		Favorite:       ec.Favorite,
166
		SecureNote:     ec.SecureNote,
167
		CollectionIDs:  ec.CollectionIDs,
168
		RevisionDate:   ec.RevisionDate,
169
		CreationDate:   ec.CreationDate,
170
		DeletedDate:    ec.DeletedDate,
171
	}
172
173
	if item.Name, err = decryptStr(ec.Name, key); err != nil {
174
		return nil, fmt.Errorf("decrypt name: %w", err)
175
	}
176
	if item.Notes, err = decryptStr(ec.Notes, key); err != nil {
177
		return nil, fmt.Errorf("decrypt notes: %w", err)
178
	}
179
180
	if ec.Login != nil {
181
		login := &Login{}
182
		if login.Username, err = decryptStr(ec.Login.Username, key); err != nil {
183
			return nil, err
184
		}
185
		if login.Password, err = decryptStr(ec.Login.Password, key); err != nil {
186
			return nil, err
187
		}
188
		if login.TOTP, err = decryptStr(ec.Login.TOTP, key); err != nil {
189
			return nil, err
190
		}
191
		for _, eu := range ec.Login.URIs {
192
			u := LoginURI{Match: eu.Match}
193
			if u.URI, err = decryptStr(eu.URI, key); err != nil {
194
				return nil, err
195
			}
196
			login.URIs = append(login.URIs, u)
197
		}
198
		item.Login = login
199
	}
200
201
	if len(ec.Card) > 0 {
202
		item.Card, err = decryptCard(ec.Card, key)
203
		if err != nil {
204
			return nil, err
205
		}
206
	}
207
208
	if len(ec.Identity) > 0 {
209
		item.Identity, err = decryptIdentity(ec.Identity, key)
210
		if err != nil {
211
			return nil, err
212
		}
213
	}
214
215
	if len(ec.Fields) > 0 {
216
		item.Fields, err = decryptFields(ec.Fields, key)
217
		if err != nil {
218
			return nil, err
219
		}
220
	}
221
222
	if len(ec.PasswordHistory) > 0 {
223
		item.PasswordHistory, err = decryptPasswordHistory(ec.PasswordHistory, key)
224
		if err != nil {
225
			return nil, err
226
		}
227
	}
228
229
	// Decrypt attachment metadata (filenames)
230
	if len(ec.Attachments) > 0 {
231
		var rawAtts []struct {
232
			ID       string `json:"id"`
233
			FileName string `json:"fileName"`
234
			Size     string `json:"size"`
235
			SizeName string `json:"sizeName"`
236
			URL      string `json:"url"`
237
			Key      string `json:"key"`
238
		}
239
		if err := json.Unmarshal(ec.Attachments, &rawAtts); err == nil {
240
			for _, ra := range rawAtts {
241
				att := Attachment{
242
					ID:       ra.ID,
243
					Size:     ra.Size,
244
					SizeName: ra.SizeName,
245
					URL:      ra.URL,
246
				}
247
				if att.FileName, err = decryptStr(ra.FileName, key); err != nil {
248
					att.FileName = ra.FileName // fallback to encrypted name
249
				}
250
				item.Attachments = append(item.Attachments, att)
251
			}
252
		}
253
	}
254
255
	return item, nil
256
}
257
258
// encryptFolder encrypts a Folder for the server.
259
func encryptFolder(f Folder, kc *crypto.KeyChain) (*encryptedFolder, error) {
260
	key := kc.UserKey()
261
	name, err := encryptStr(f.Name, key)
262
	if err != nil {
263
		return nil, fmt.Errorf("encrypt folder name: %w", err)
264
	}
265
	return &encryptedFolder{
266
		ID:   f.ID,
267
		Name: name,
268
	}, nil
269
}
270
271
// decryptFolder decrypts a server folder.
272
func decryptFolder(data []byte, kc *crypto.KeyChain) (*Folder, error) {
273
	var ef encryptedFolder
274
	if err := json.Unmarshal(data, &ef); err != nil {
275
		return nil, fmt.Errorf("unmarshal folder: %w", err)
276
	}
277
278
	key := kc.UserKey()
279
	name, err := decryptStr(ef.Name, key)
280
	if err != nil {
281
		return nil, fmt.Errorf("decrypt folder name: %w", err)
282
	}
283
284
	return &Folder{
285
		ID:   ef.ID,
286
		Name: name,
287
	}, nil
288
}
289
290
// encryptedSend is the server-side representation of a Send (encrypted fields).
291
type encryptedSend struct {
292
	ID             string             `json:"id,omitempty"`
293
	AccessID       string             `json:"accessId,omitempty"`
294
	Type           SendType           `json:"type"`
295
	Name           string             `json:"name"`
296
	Notes          string             `json:"notes,omitempty"`
297
	File           *SendFile          `json:"file,omitempty"`
298
	Text           *sendTextEncrypted `json:"text,omitempty"`
299
	Key            string             `json:"key,omitempty"`
300
	MaxAccessCount *int               `json:"maxAccessCount,omitempty"`
301
	AccessCount    int                `json:"accessCount,omitempty"`
302
	Password       string             `json:"password,omitempty"`
303
	Disabled       bool               `json:"disabled"`
304
	RevisionDate   string             `json:"revisionDate,omitempty"`
305
	DeletionDate   string             `json:"deletionDate"`
306
	ExpirationDate *string            `json:"expirationDate,omitempty"`
307
	HideEmail      bool               `json:"hideEmail"`
308
}
309
310
type sendTextEncrypted struct {
311
	Text   string `json:"text"`
312
	Hidden bool   `json:"hidden"`
313
}
314
315
// encryptSend encrypts a Send's fields for the server.
316
// It generates a random send key, encrypts it with the user key, then uses
317
// the derived send key to encrypt Name, Notes, and Text.Text.
318
func encryptSend(s Send, kc *crypto.KeyChain) (*encryptedSend, error) {
319
	userKey := kc.UserKey()
320
321
	// Generate random 16-byte send key
322
	sendKeyRaw := make([]byte, 16)
323
	if _, err := cryptoRandRead(sendKeyRaw); err != nil {
324
		return nil, fmt.Errorf("generate send key: %w", err)
325
	}
326
327
	// Encrypt the send key with user key
328
	encSendKeyCS, err := userKey.Encrypt(sendKeyRaw)
329
	if err != nil {
330
		return nil, fmt.Errorf("encrypt send key: %w", err)
331
	}
332
333
	// Derive the send encryption key via HKDF
334
	sendKey, err := crypto.DeriveSendKey(sendKeyRaw)
335
	if err != nil {
336
		return nil, fmt.Errorf("derive send key: %w", err)
337
	}
338
339
	es := &encryptedSend{
340
		ID:             s.ID,
341
		AccessID:       s.AccessID,
342
		Type:           s.Type,
343
		Key:            encSendKeyCS.String(),
344
		MaxAccessCount: s.MaxAccessCount,
345
		AccessCount:    s.AccessCount,
346
		Password:       s.Password,
347
		Disabled:       s.Disabled,
348
		DeletionDate:   s.DeletionDate,
349
		ExpirationDate: s.ExpirationDate,
350
		HideEmail:      s.HideEmail,
351
	}
352
353
	// Encrypt name (required)
354
	if es.Name, err = encryptStr(s.Name, sendKey); err != nil {
355
		return nil, fmt.Errorf("encrypt send name: %w", err)
356
	}
357
358
	// Encrypt notes
359
	if s.Notes != "" {
360
		if es.Notes, err = encryptStr(s.Notes, sendKey); err != nil {
361
			return nil, fmt.Errorf("encrypt send notes: %w", err)
362
		}
363
	}
364
365
	// Encrypt text content
366
	if s.Text != nil {
367
		et := &sendTextEncrypted{Hidden: s.Text.Hidden}
368
		if s.Text.Text != "" {
369
			if et.Text, err = encryptStr(s.Text.Text, sendKey); err != nil {
370
				return nil, fmt.Errorf("encrypt send text: %w", err)
371
			}
372
		}
373
		es.Text = et
374
	}
375
376
	// File sends: encrypt filename
377
	if s.File != nil {
378
		ef := &SendFile{
379
			ID:       s.File.ID,
380
			Size:     s.File.Size,
381
			SizeName: s.File.SizeName,
382
		}
383
		if s.File.FileName != "" {
384
			if ef.FileName, err = encryptStr(s.File.FileName, sendKey); err != nil {
385
				return nil, fmt.Errorf("encrypt send filename: %w", err)
386
			}
387
		}
388
		es.File = ef
389
	}
390
391
	return es, nil
392
}
393
394
// decryptSend decrypts a Send's encrypted fields.
395
func decryptSend(data []byte, kc *crypto.KeyChain) (*Send, error) {
396
	var es encryptedSend
397
	if err := json.Unmarshal(data, &es); err != nil {
398
		return nil, fmt.Errorf("unmarshal send: %w", err)
399
	}
400
401
	userKey := kc.UserKey()
402
403
	s := &Send{
404
		ID:             es.ID,
405
		AccessID:       es.AccessID,
406
		Type:           es.Type,
407
		Key:            es.Key,
408
		MaxAccessCount: es.MaxAccessCount,
409
		AccessCount:    es.AccessCount,
410
		Password:       es.Password,
411
		Disabled:       es.Disabled,
412
		RevisionDate:   es.RevisionDate,
413
		DeletionDate:   es.DeletionDate,
414
		ExpirationDate: es.ExpirationDate,
415
		HideEmail:      es.HideEmail,
416
	}
417
418
	// If no Key, we can't decrypt — return with encrypted values
419
	if es.Key == "" {
420
		s.Name = es.Name
421
		s.Notes = es.Notes
422
		if es.Text != nil {
423
			s.Text = &SendText{Text: es.Text.Text, Hidden: es.Text.Hidden}
424
		}
425
		if es.File != nil {
426
			s.File = es.File
427
		}
428
		return s, nil
429
	}
430
431
	// Decrypt the send key
432
	sendKeyCS, err := crypto.ParseCipherString(es.Key)
433
	if err != nil {
434
		return nil, fmt.Errorf("parse send key: %w", err)
435
	}
436
	sendKeyRaw, err := userKey.Decrypt(sendKeyCS)
437
	if err != nil {
438
		return nil, fmt.Errorf("decrypt send key: %w", err)
439
	}
440
441
	sendKey, err := crypto.DeriveSendKey(sendKeyRaw)
442
	if err != nil {
443
		return nil, fmt.Errorf("derive send key: %w", err)
444
	}
445
446
	// Decrypt fields
447
	if s.Name, err = decryptStr(es.Name, sendKey); err != nil {
448
		return nil, fmt.Errorf("decrypt send name: %w", err)
449
	}
450
	if s.Notes, err = decryptStr(es.Notes, sendKey); err != nil {
451
		return nil, fmt.Errorf("decrypt send notes: %w", err)
452
	}
453
454
	if es.Text != nil {
455
		st := &SendText{Hidden: es.Text.Hidden}
456
		if st.Text, err = decryptStr(es.Text.Text, sendKey); err != nil {
457
			return nil, fmt.Errorf("decrypt send text: %w", err)
458
		}
459
		s.Text = st
460
	}
461
462
	if es.File != nil {
463
		sf := &SendFile{
464
			ID:       es.File.ID,
465
			Size:     es.File.Size,
466
			SizeName: es.File.SizeName,
467
		}
468
		if sf.FileName, err = decryptStr(es.File.FileName, sendKey); err != nil {
469
			return nil, fmt.Errorf("decrypt send filename: %w", err)
470
		}
471
		s.File = sf
472
	}
473
474
	return s, nil
475
}
476
477
// Helper functions
478
479
func encryptStr(s string, key *crypto.SymmetricKey) (string, error) {
480
	if s == "" {
481
		return "", nil
482
	}
483
	return key.EncryptString(s)
484
}
485
486
func encryptStrOpt(s string, key *crypto.SymmetricKey) (string, error) {
487
	if s == "" {
488
		return "", nil
489
	}
490
	return key.EncryptString(s)
491
}
492
493
func decryptStr(s string, key *crypto.SymmetricKey) (string, error) {
494
	if s == "" {
495
		return "", nil
496
	}
497
	return crypto.DecryptString(s, key)
498
}
499
500
func encryptCard(c *Card, key *crypto.SymmetricKey) (json.RawMessage, error) {
501
	enc := map[string]string{}
502
	fields := map[string]string{
503
		"cardholderName": c.CardholderName,
504
		"brand":          c.Brand,
505
		"number":         c.Number,
506
		"expMonth":       c.ExpMonth,
507
		"expYear":        c.ExpYear,
508
		"code":           c.Code,
509
	}
510
	for k, v := range fields {
511
		if v != "" {
512
			encrypted, err := key.EncryptString(v)
513
			if err != nil {
514
				return nil, fmt.Errorf("encrypt card.%s: %w", k, err)
515
			}
516
			enc[k] = encrypted
517
		}
518
	}
519
	return json.Marshal(enc)
520
}
521
522
func decryptCard(data json.RawMessage, key *crypto.SymmetricKey) (*Card, error) {
523
	var enc map[string]string
524
	if err := json.Unmarshal(data, &enc); err != nil {
525
		return nil, fmt.Errorf("unmarshal card: %w", err)
526
	}
527
528
	c := &Card{}
529
	var err error
530
	if c.CardholderName, err = decryptStr(enc["cardholderName"], key); err != nil {
531
		return nil, err
532
	}
533
	if c.Brand, err = decryptStr(enc["brand"], key); err != nil {
534
		return nil, err
535
	}
536
	if c.Number, err = decryptStr(enc["number"], key); err != nil {
537
		return nil, err
538
	}
539
	if c.ExpMonth, err = decryptStr(enc["expMonth"], key); err != nil {
540
		return nil, err
541
	}
542
	if c.ExpYear, err = decryptStr(enc["expYear"], key); err != nil {
543
		return nil, err
544
	}
545
	if c.Code, err = decryptStr(enc["code"], key); err != nil {
546
		return nil, err
547
	}
548
	return c, nil
549
}
550
551
func encryptIdentity(id *Identity, key *crypto.SymmetricKey) (json.RawMessage, error) {
552
	enc := map[string]string{}
553
	fields := map[string]string{
554
		"title": id.Title, "firstName": id.FirstName, "middleName": id.MiddleName,
555
		"lastName": id.LastName, "address1": id.Address1, "address2": id.Address2,
556
		"address3": id.Address3, "city": id.City, "state": id.State,
557
		"postalCode": id.PostalCode, "country": id.Country, "company": id.Company,
558
		"email": id.Email, "phone": id.Phone, "ssn": id.SSN,
559
		"username": id.Username, "passportNumber": id.PassportNumber, "licenseNumber": id.LicenseNumber,
560
	}
561
	for k, v := range fields {
562
		if v != "" {
563
			encrypted, err := key.EncryptString(v)
564
			if err != nil {
565
				return nil, fmt.Errorf("encrypt identity.%s: %w", k, err)
566
			}
567
			enc[k] = encrypted
568
		}
569
	}
570
	return json.Marshal(enc)
571
}
572
573
func decryptIdentity(data json.RawMessage, key *crypto.SymmetricKey) (*Identity, error) {
574
	var enc map[string]string
575
	if err := json.Unmarshal(data, &enc); err != nil {
576
		return nil, fmt.Errorf("unmarshal identity: %w", err)
577
	}
578
579
	id := &Identity{}
580
	var err error
581
	if id.Title, err = decryptStr(enc["title"], key); err != nil {
582
		return nil, err
583
	}
584
	if id.FirstName, err = decryptStr(enc["firstName"], key); err != nil {
585
		return nil, err
586
	}
587
	if id.MiddleName, err = decryptStr(enc["middleName"], key); err != nil {
588
		return nil, err
589
	}
590
	if id.LastName, err = decryptStr(enc["lastName"], key); err != nil {
591
		return nil, err
592
	}
593
	if id.Address1, err = decryptStr(enc["address1"], key); err != nil {
594
		return nil, err
595
	}
596
	if id.Address2, err = decryptStr(enc["address2"], key); err != nil {
597
		return nil, err
598
	}
599
	if id.Address3, err = decryptStr(enc["address3"], key); err != nil {
600
		return nil, err
601
	}
602
	if id.City, err = decryptStr(enc["city"], key); err != nil {
603
		return nil, err
604
	}
605
	if id.State, err = decryptStr(enc["state"], key); err != nil {
606
		return nil, err
607
	}
608
	if id.PostalCode, err = decryptStr(enc["postalCode"], key); err != nil {
609
		return nil, err
610
	}
611
	if id.Country, err = decryptStr(enc["country"], key); err != nil {
612
		return nil, err
613
	}
614
	if id.Company, err = decryptStr(enc["company"], key); err != nil {
615
		return nil, err
616
	}
617
	if id.Email, err = decryptStr(enc["email"], key); err != nil {
618
		return nil, err
619
	}
620
	if id.Phone, err = decryptStr(enc["phone"], key); err != nil {
621
		return nil, err
622
	}
623
	if id.SSN, err = decryptStr(enc["ssn"], key); err != nil {
624
		return nil, err
625
	}
626
	if id.Username, err = decryptStr(enc["username"], key); err != nil {
627
		return nil, err
628
	}
629
	if id.PassportNumber, err = decryptStr(enc["passportNumber"], key); err != nil {
630
		return nil, err
631
	}
632
	if id.LicenseNumber, err = decryptStr(enc["licenseNumber"], key); err != nil {
633
		return nil, err
634
	}
635
	return id, nil
636
}
637
638
func encryptFields(fields []Field, key *crypto.SymmetricKey) (json.RawMessage, error) {
639
	var enc []encryptedField
640
	for _, f := range fields {
641
		ef := encryptedField{
642
			Type:     f.Type,
643
			LinkedID: f.LinkedID,
644
		}
645
		var err error
646
		if ef.Name, err = encryptStrOpt(f.Name, key); err != nil {
647
			return nil, err
648
		}
649
		if ef.Value, err = encryptStrOpt(f.Value, key); err != nil {
650
			return nil, err
651
		}
652
		enc = append(enc, ef)
653
	}
654
	return json.Marshal(enc)
655
}
656
657
func decryptFields(data json.RawMessage, key *crypto.SymmetricKey) ([]Field, error) {
658
	var enc []encryptedField
659
	if err := json.Unmarshal(data, &enc); err != nil {
660
		return nil, fmt.Errorf("unmarshal fields: %w", err)
661
	}
662
663
	var fields []Field
664
	for _, ef := range enc {
665
		f := Field{
666
			Type:     ef.Type,
667
			LinkedID: ef.LinkedID,
668
		}
669
		var err error
670
		if f.Name, err = decryptStr(ef.Name, key); err != nil {
671
			return nil, err
672
		}
673
		if f.Value, err = decryptStr(ef.Value, key); err != nil {
674
			return nil, err
675
		}
676
		fields = append(fields, f)
677
	}
678
	return fields, nil
679
}
680
681
type encryptedPasswordHistory struct {
682
	LastUsedDate string `json:"lastUsedDate"`
683
	Password     string `json:"password"`
684
}
685
686
func encryptPasswordHistory(history []PasswordHistory, key *crypto.SymmetricKey) (json.RawMessage, error) {
687
	var enc []encryptedPasswordHistory
688
	for _, h := range history {
689
		eh := encryptedPasswordHistory{LastUsedDate: h.LastUsedDate}
690
		var err error
691
		if eh.Password, err = encryptStr(h.Password, key); err != nil {
692
			return nil, fmt.Errorf("encrypt password history: %w", err)
693
		}
694
		enc = append(enc, eh)
695
	}
696
	return json.Marshal(enc)
697
}
698
699
func decryptPasswordHistory(data json.RawMessage, key *crypto.SymmetricKey) ([]PasswordHistory, error) {
700
	var enc []encryptedPasswordHistory
701
	if err := json.Unmarshal(data, &enc); err != nil {
702
		return nil, fmt.Errorf("unmarshal password history: %w", err)
703
	}
704
705
	var history []PasswordHistory
706
	for _, eh := range enc {
707
		h := PasswordHistory{LastUsedDate: eh.LastUsedDate}
708
		var err error
709
		if h.Password, err = decryptStr(eh.Password, key); err != nil {
710
			return nil, err
711
		}
712
		history = append(history, h)
713
	}
714
	return history, nil
715
}
716

Source Files