-
Notifications
You must be signed in to change notification settings - Fork 30
/
Copy pathdatabase.go
239 lines (201 loc) · 6.32 KB
/
database.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
package gokeepasslib
import (
"errors"
)
// ErrInvalidDatabaseOrCredentials is returned when the file cannot be read properly.
var ErrInvalidDatabaseOrCredentials = errors.New(
"Cannot read database: Either credentials are invalid or the database file is corrupted",
)
// Database stores all contents necessary for a keepass database file
type Database struct {
Options *DBOptions
Credentials *DBCredentials
Header *DBHeader
Hashes *DBHashes
Content *DBContent
}
// DBOptions stores options for database decoding/encoding
type DBOptions struct {
ValidateHashes bool // True to validate header hash
}
type DatabaseOption func(*Database)
func WithDatabaseFormattedTime(formatted bool) DatabaseOption {
return func(db *Database) {
WithDBContentFormattedTime(formatted)(db.Content)
}
}
func WithDatabaseKDBXVersion3() DatabaseOption {
return func(db *Database) {
db.Header = NewKDBX3Header()
}
}
func WithDatabaseKDBXVersion4() DatabaseOption {
return func(db *Database) {
db.Header = NewKDBX4Header()
withDBContentKDBX4InnerHeader(db.Content)
}
}
// NewDatabase creates a new database with some sensable default settings in KDBX version 3.1.
// To create a database with no settings pre-set, use gokeepasslib.Database{}
func NewDatabase(options ...DatabaseOption) *Database {
db := &Database{
Options: NewOptions(),
Credentials: new(DBCredentials),
Content: NewContent(),
}
for _, option := range options {
option(db)
}
if db.Header == nil {
db.Header = NewHeader()
}
if db.Hashes == nil {
db.Hashes = NewHashes(db.Header)
}
return db
}
// NewOptions creates new options with default values
func NewOptions() *DBOptions {
return &DBOptions{
ValidateHashes: true,
}
}
func (db *Database) ensureKdbxFormatVersion() {
db.Content.setKdbxFormatVersion(
db.Header.formatVersion(),
)
}
// getTransformedKey returns the transformed key Credentials
func (db *Database) getTransformedKey() ([]byte, error) {
if db.Credentials == nil {
return nil, ErrRequiredAttributeMissing("Credentials")
}
return db.Credentials.buildTransformedKey(db)
}
// GetEncrypterManager returns an EncryptManager based on the master key and EncryptionIV,
// or nil if the type is unsupported
func (db *Database) GetEncrypterManager(transformedKey []byte) (*EncrypterManager, error) {
return NewEncrypterManager(
db.Header.FileHeaders.CipherID,
buildMasterKey(db, transformedKey),
db.Header.FileHeaders.EncryptionIV,
)
}
// GetStreamManager returns a StreamManager based on the db headers,
// or nil if the type is unsupported
// Can be used to lock only certain entries instead of calling
func (db *Database) GetStreamManager() (*StreamManager, error) {
if db.Header != nil && db.Header.Signature != nil {
if db.Header.IsKdbx4() {
if db.Content == nil ||
db.Content.InnerHeader == nil ||
db.Content.InnerHeader.InnerRandomStreamKey == nil {
return nil, ErrInvalidDatabaseOrCredentials
}
return NewStreamManager(
db.Content.InnerHeader.InnerRandomStreamID,
db.Content.InnerHeader.InnerRandomStreamKey,
)
}
if db.Header.FileHeaders != nil &&
db.Header.FileHeaders.ProtectedStreamKey == nil {
return nil, ErrInvalidDatabaseOrCredentials
}
return NewStreamManager(
db.Header.FileHeaders.InnerRandomStreamID,
db.Header.FileHeaders.ProtectedStreamKey,
)
}
return nil, nil
}
// UnlockProtectedEntries goes through the entire database and encrypts
// any Values in entries with protected=true set.
// This should be called after decoding if you want to view plaintext password in an entry
// Warning: If you call this when entry values are already unlocked,
// it will cause them to be unreadable
func (db *Database) UnlockProtectedEntries() error {
manager, err := db.GetStreamManager()
if err != nil {
return err
}
if manager == nil {
return ErrUnsupportedStreamType
}
manager.UnlockProtectedGroups(db.Content.Root.Groups)
return nil
}
// LockProtectedEntries goes through the entire database and decrypts
// any Values in entries with protected=true set.
// Warning: Do not call this if entries are already locked
// Warning: Encoding a database calls LockProtectedEntries automatically
func (db *Database) LockProtectedEntries() error {
manager, err := db.GetStreamManager()
if err != nil {
return err
}
manager.LockProtectedGroups(db.Content.Root.Groups)
return nil
}
// AddBinary adds a binary to the database.
// It takes care of adding it to the correct place based on the format version
func (db *Database) AddBinary(binaryContent []byte) *Binary {
if db.Header.IsKdbx4() {
return db.getBinaries().Add(binaryContent, WithKDBXv4Binary)
}
return db.getBinaries().Add(binaryContent, WithKDBXv31Binary)
}
// FindBinary returns the binary with the given id if one could be found. It returns nil otherwise
func (db *Database) FindBinary(id int) *Binary {
return db.getBinaries().Find(id)
}
// ErrRequiredAttributeMissing is returned if a required value is not given
type ErrRequiredAttributeMissing string
func (e ErrRequiredAttributeMissing) Error() string {
return "gokeepasslib: operation can not be performed if database does not have " + string(e)
}
type binariesUsages map[int][]*BinaryReference
func (db *Database) getBinaries() *Binaries {
if db.Header.IsKdbx4() {
return &db.Content.InnerHeader.Binaries
}
return &db.Content.Meta.Binaries
}
func (db *Database) cleanupBinaries() {
usages := db.getBinariesUsages()
updated := Binaries{}
counter := 0
for _, binary := range *db.getBinaries() {
if refs, ok := usages[binary.ID]; ok {
for _, ref := range refs {
ref.Value.ID = counter
}
binary.ID = counter
updated = append(updated, binary)
counter++
}
}
*db.getBinaries() = updated
}
func addEntriesBinaries(result binariesUsages, entries []Entry) {
for _, entry := range entries {
for i, binary := range entry.Binaries {
id := binary.Value.ID
result[id] = append(result[id], &entry.Binaries[i])
}
for _, history := range entry.Histories {
addEntriesBinaries(result, history.Entries)
}
}
}
func addGroupBinaries(result binariesUsages, parent *Group) {
addEntriesBinaries(result, parent.Entries)
for _, group := range parent.Groups {
g := group
addGroupBinaries(result, &g)
}
}
func (db *Database) getBinariesUsages() binariesUsages {
result := binariesUsages{}
addGroupBinaries(result, &db.Content.Root.Groups[0])
return result
}