forked from notaryproject/notary
-
Notifications
You must be signed in to change notification settings - Fork 0
/
keydbstore.go
234 lines (194 loc) · 6.6 KB
/
keydbstore.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
package keydbstore
import (
"errors"
"fmt"
"sync"
"github.com/docker/notary/passphrase"
"github.com/docker/notary/trustmanager"
"github.com/docker/notary/tuf/data"
jose "github.com/dvsekhvalnov/jose2go"
"github.com/jinzhu/gorm"
)
// Constants
const (
EncryptionAlg = jose.A256GCM
KeywrapAlg = jose.PBES2_HS256_A128KW
)
// KeyDBStore persists and manages private keys on a SQL database
type KeyDBStore struct {
sync.Mutex
db gorm.DB
defaultPassAlias string
retriever passphrase.Retriever
cachedKeys map[string]data.PrivateKey
}
// GormPrivateKey represents a PrivateKey in the database
type GormPrivateKey struct {
gorm.Model
KeyID string `sql:"not null;unique;index:key_id_idx"`
EncryptionAlg string `sql:"not null"`
KeywrapAlg string `sql:"not null"`
Algorithm string `sql:"not null"`
PassphraseAlias string `sql:"not null"`
Public string `sql:"not null"`
Private string `sql:"not null"`
}
// TableName sets a specific table name for our GormPrivateKey
func (g GormPrivateKey) TableName() string {
return "private_keys"
}
// NewKeyDBStore returns a new KeyDBStore backed by a SQL database
func NewKeyDBStore(passphraseRetriever passphrase.Retriever, defaultPassAlias string,
dbDialect string, dbArgs ...interface{}) (*KeyDBStore, error) {
cachedKeys := make(map[string]data.PrivateKey)
db, err := gorm.Open(dbDialect, dbArgs...)
if err != nil {
return nil, err
}
return &KeyDBStore{
db: db,
defaultPassAlias: defaultPassAlias,
retriever: passphraseRetriever,
cachedKeys: cachedKeys}, nil
}
// Name returns a user friendly name for the storage location
func (s *KeyDBStore) Name() string {
return "database"
}
// AddKey stores the contents of a private key. Both role and gun are ignored,
// we always use Key IDs as name, and don't support aliases
func (s *KeyDBStore) AddKey(keyInfo trustmanager.KeyInfo, privKey data.PrivateKey) error {
passphrase, _, err := s.retriever(privKey.ID(), s.defaultPassAlias, false, 1)
if err != nil {
return err
}
encryptedKey, err := jose.Encrypt(string(privKey.Private()), KeywrapAlg, EncryptionAlg, passphrase)
if err != nil {
return err
}
gormPrivKey := GormPrivateKey{
KeyID: privKey.ID(),
EncryptionAlg: EncryptionAlg,
KeywrapAlg: KeywrapAlg,
PassphraseAlias: s.defaultPassAlias,
Algorithm: privKey.Algorithm(),
Public: string(privKey.Public()),
Private: encryptedKey}
// Add encrypted private key to the database
s.db.Create(&gormPrivKey)
// Value will be false if Create succeeds
failure := s.db.NewRecord(gormPrivKey)
if failure {
return fmt.Errorf("failed to add private key to database: %s", privKey.ID())
}
// Add the private key to our cache
s.Lock()
defer s.Unlock()
s.cachedKeys[privKey.ID()] = privKey
return nil
}
// GetKey returns the PrivateKey given a KeyID
func (s *KeyDBStore) GetKey(keyID string) (data.PrivateKey, string, error) {
s.Lock()
defer s.Unlock()
cachedKeyEntry, ok := s.cachedKeys[keyID]
if ok {
return cachedKeyEntry, "", nil
}
// Retrieve the GORM private key from the database
dbPrivateKey := GormPrivateKey{}
if s.db.Where(&GormPrivateKey{KeyID: keyID}).First(&dbPrivateKey).RecordNotFound() {
return nil, "", trustmanager.ErrKeyNotFound{KeyID: keyID}
}
// Get the passphrase to use for this key
passphrase, _, err := s.retriever(dbPrivateKey.KeyID, dbPrivateKey.PassphraseAlias, false, 1)
if err != nil {
return nil, "", err
}
// Decrypt private bytes from the gorm key
decryptedPrivKey, _, err := jose.Decode(dbPrivateKey.Private, passphrase)
if err != nil {
return nil, "", err
}
pubKey := data.NewPublicKey(dbPrivateKey.Algorithm, []byte(dbPrivateKey.Public))
// Create a new PrivateKey with unencrypted bytes
privKey, err := data.NewPrivateKey(pubKey, []byte(decryptedPrivKey))
if err != nil {
return nil, "", err
}
// Add the key to cache
s.cachedKeys[privKey.ID()] = privKey
return privKey, "", nil
}
// GetKeyInfo returns the PrivateKey's role and gun in a KeyInfo given a KeyID
func (s *KeyDBStore) GetKeyInfo(keyID string) (trustmanager.KeyInfo, error) {
return trustmanager.KeyInfo{}, fmt.Errorf("GetKeyInfo currently not supported for KeyDBStore, as it does not track roles or GUNs")
}
// ListKeys always returns nil. This method is here to satisfy the KeyStore interface
func (s *KeyDBStore) ListKeys() map[string]trustmanager.KeyInfo {
return nil
}
// RemoveKey removes the key from the keyfilestore
func (s *KeyDBStore) RemoveKey(keyID string) error {
s.Lock()
defer s.Unlock()
delete(s.cachedKeys, keyID)
// Retrieve the GORM private key from the database
dbPrivateKey := GormPrivateKey{}
if s.db.Where(&GormPrivateKey{KeyID: keyID}).First(&dbPrivateKey).RecordNotFound() {
return trustmanager.ErrKeyNotFound{KeyID: keyID}
}
// Delete the key from the database
s.db.Delete(&dbPrivateKey)
return nil
}
// RotateKeyPassphrase rotates the key-encryption-key
func (s *KeyDBStore) RotateKeyPassphrase(keyID, newPassphraseAlias string) error {
// Retrieve the GORM private key from the database
dbPrivateKey := GormPrivateKey{}
if s.db.Where(&GormPrivateKey{KeyID: keyID}).First(&dbPrivateKey).RecordNotFound() {
return trustmanager.ErrKeyNotFound{KeyID: keyID}
}
// Get the current passphrase to use for this key
passphrase, _, err := s.retriever(dbPrivateKey.KeyID, dbPrivateKey.PassphraseAlias, false, 1)
if err != nil {
return err
}
// Decrypt private bytes from the gorm key
decryptedPrivKey, _, err := jose.Decode(dbPrivateKey.Private, passphrase)
if err != nil {
return err
}
// Get the new passphrase to use for this key
newPassphrase, _, err := s.retriever(dbPrivateKey.KeyID, newPassphraseAlias, false, 1)
if err != nil {
return err
}
// Re-encrypt the private bytes with the new passphrase
newEncryptedKey, err := jose.Encrypt(decryptedPrivKey, KeywrapAlg, EncryptionAlg, newPassphrase)
if err != nil {
return err
}
// Update the database object
dbPrivateKey.Private = newEncryptedKey
dbPrivateKey.PassphraseAlias = newPassphraseAlias
s.db.Save(dbPrivateKey)
return nil
}
// ExportKey is currently unimplemented and will always return an error
func (s *KeyDBStore) ExportKey(keyID string) ([]byte, error) {
return nil, errors.New("Exporting from a KeyDBStore is not supported.")
}
// HealthCheck verifies that DB exists and is query-able
func (s *KeyDBStore) HealthCheck() error {
dbPrivateKey := GormPrivateKey{}
tableOk := s.db.HasTable(&dbPrivateKey)
switch {
case s.db.Error != nil:
return s.db.Error
case !tableOk:
return fmt.Errorf(
"Cannot access table: %s", dbPrivateKey.TableName())
}
return nil
}