-
Notifications
You must be signed in to change notification settings - Fork 18
/
db.go
267 lines (222 loc) · 8.19 KB
/
db.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
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
package authdb
import (
"context"
"crypto/rand"
"crypto/sha256"
"encoding/base32"
"strings"
"sync"
"time"
"github.com/spacemonkeygo/monkit/v3"
"github.com/zeebo/errs"
"storj.io/common/encryption"
"storj.io/common/grant"
"storj.io/common/macaroon"
"storj.io/common/storj"
"storj.io/edge/pkg/nodelist"
)
var (
mon = monkit.Package()
// NotFound is returned when a record is not found.
NotFound = errs.Class("not found")
// ErrAccessGrant occurs when an invalid access grant is given.
ErrAccessGrant = errs.Class("access grant")
base32Encoding = base32.StdEncoding.WithPadding(base32.NoPadding)
)
// EncKeySizeEncoded is size in base32 bytes + magic byte.
const EncKeySizeEncoded = 28
const encKeyVersionByte = byte(77) // magic number for v1 EncryptionKey encoding
const secKeyVersionByte = byte(78) // magic number for v1 SecretKey encoding
// EncryptionKey is an encryption key that an access/secret are encrypted with.
type EncryptionKey [16]byte
// SecretKey is the secret key used to sign requests.
type SecretKey [32]byte
// NewEncryptionKey returns a new random EncryptionKey with initial version byte.
func NewEncryptionKey() (EncryptionKey, error) {
key := EncryptionKey{encKeyVersionByte}
if _, err := rand.Read(key[:]); err != nil {
return key, err
}
return key, nil
}
// Hash returns the KeyHash for the EncryptionKey.
func (k EncryptionKey) Hash() KeyHash {
return KeyHash(sha256.Sum256(k[:]))
}
// FromBase32 loads the EncryptionKey from a lowercase RFC 4648 base32 string.
func (k *EncryptionKey) FromBase32(encoded string) error {
if len(encoded) != EncKeySizeEncoded {
return errs.New("alphanumeric encryption key length expected to be %d, was %d", EncKeySizeEncoded, len(encoded))
}
data, err := base32Encoding.DecodeString(strings.ToUpper(encoded))
if err != nil {
return errs.Wrap(err)
}
return k.FromBinary(data)
}
// FromBinary reads the key from binary which must include the version byte.
func (k *EncryptionKey) FromBinary(data []byte) error {
if data[0] != encKeyVersionByte {
return errs.New("encryption key did not start with expected byte")
}
copy(k[:], data[1:]) // overwrite k
return nil
}
// ToBase32 returns the EncryptionKey as a lowercase RFC 4648 base32 string.
func (k EncryptionKey) ToBase32() string {
return toBase32(k.ToBinary())
}
// ToBinary returns the EncryptionKey including the version byte.
func (k EncryptionKey) ToBinary() []byte {
return append([]byte{encKeyVersionByte}, k[:]...)
}
// ToStorjKey returns the storj.Key equivalent for the EncryptionKey.
func (k EncryptionKey) ToStorjKey() storj.Key {
var storjKey storj.Key
copy(storjKey[:], k[:])
return storjKey
}
// ToBase32 returns the SecretKey as a lowercase RFC 4648 base32 string.
func (s SecretKey) ToBase32() string {
return toBase32(s.ToBinary())
}
// ToBinary returns the SecretKey including the version byte.
func (s SecretKey) ToBinary() []byte {
return append([]byte{secKeyVersionByte}, s[:]...)
}
// toBase32 returns the buffer as a lowercase RFC 4648 base32 string.
func toBase32(k []byte) string {
return strings.ToLower(base32Encoding.EncodeToString(k))
}
// Database wraps Storage implementation and uses it to store encrypted accesses
// and secrets.
type Database struct {
storage Storage
mu sync.Mutex
allowedSatelliteURLs map[storj.NodeURL]struct{}
}
// NewDatabase constructs a Database. allowedSatelliteAddresses should contain
// the full URL (with a node ID), including port, for each satellite we
// allow for incoming access grants.
func NewDatabase(storage Storage, allowedSatelliteURLs map[storj.NodeURL]struct{}) *Database {
return &Database{
storage: storage,
allowedSatelliteURLs: allowedSatelliteURLs,
}
}
// SetAllowedSatellites updates the allowed satellites list from configuration values.
func (db *Database) SetAllowedSatellites(allowedSatelliteURLs map[storj.NodeURL]struct{}) {
db.mu.Lock()
db.allowedSatelliteURLs = allowedSatelliteURLs
db.mu.Unlock()
}
// Put encrypts the access grant with the key and stores it under the hash of
// the encryption key. It rejects access grants with expiration times that are
// before a minute from now.
func (db *Database) Put(ctx context.Context, key EncryptionKey, accessGrant string, public bool) (secretKey SecretKey, err error) {
defer mon.Task()(&ctx)(&err)
access, err := grant.ParseAccess(accessGrant)
if err != nil {
return secretKey, ErrAccessGrant.Wrap(err)
}
// Check that the satellite address embedded in the access grant is on the
// allowed list.
satelliteAddr := access.SatelliteAddress
nodeURL, err := nodelist.ParseNodeURL(satelliteAddr)
if err != nil {
return secretKey, ErrAccessGrant.Wrap(err)
}
db.mu.Lock()
_, ok := db.allowedSatelliteURLs[nodeURL]
db.mu.Unlock()
if !ok {
return secretKey, ErrAccessGrant.New("disallowed satellite: %q", satelliteAddr)
}
if _, err := rand.Read(secretKey[:]); err != nil {
return secretKey, errs.Wrap(err)
}
storjKey := key.ToStorjKey()
// note that we currently always use the same nonce here - all zero's for secret keys
encryptedSecretKey, err := encryption.Encrypt(secretKey[:], storj.EncAESGCM, &storjKey, &storj.Nonce{})
if err != nil {
return secretKey, errs.Wrap(err)
}
// note that we currently always use the same nonce here - one then all zero's for access grants
encryptedAccessGrant, err := encryption.Encrypt([]byte(accessGrant), storj.EncAESGCM, &storjKey, &storj.Nonce{1})
if err != nil {
return secretKey, errs.Wrap(err)
}
expiration, err := apiKeyExpiration(access.APIKey)
if err != nil {
return secretKey, ErrAccessGrant.Wrap(err)
}
record := &Record{
SatelliteAddress: satelliteAddr,
MacaroonHead: access.APIKey.Head(),
EncryptedSecretKey: encryptedSecretKey,
EncryptedAccessGrant: encryptedAccessGrant,
Public: public,
ExpiresAt: expiration,
}
return secretKey, errs.Wrap(db.storage.Put(ctx, key.Hash(), record))
}
// Get retrieves an access grant and secret key, looked up by the hash of the
// access key, and then decrypted.
func (db *Database) Get(ctx context.Context, accessKeyID EncryptionKey) (accessGrant string, public bool, secretKey SecretKey, err error) {
defer mon.Task()(&ctx)(&err)
record, err := db.storage.Get(ctx, accessKeyID.Hash())
if err != nil {
return "", false, secretKey, errs.Wrap(err)
} else if record == nil {
return "", false, secretKey, NotFound.New("key hash: %x", accessKeyID.Hash())
}
storjKey := accessKeyID.ToStorjKey()
// note that we currently always use the same nonce here - all zero's for secret keys
sk, err := encryption.Decrypt(record.EncryptedSecretKey, storj.EncAESGCM, &storjKey, &storj.Nonce{})
if err != nil {
return "", false, secretKey, errs.Wrap(err)
}
copy(secretKey[:], sk)
// note that we currently always use the same nonce here - one then all zero's for access grants
ag, err := encryption.Decrypt(record.EncryptedAccessGrant, storj.EncAESGCM, &storjKey, &storj.Nonce{1})
if err != nil {
return "", false, secretKey, errs.Wrap(err)
}
return string(ag), record.Public, secretKey, nil
}
// HealthCheck ensures the underlying storage backend works and returns an error
// otherwise.
func (db *Database) HealthCheck(ctx context.Context) (err error) {
defer mon.Task()(&ctx)(&err)
return errs.Wrap(db.storage.HealthCheck(ctx))
}
// apiKeyExpiration returns the expiration time of apiKey, and any error
// encountered. It rejects expiration times that are before a minute from now.
//
// TODO: we should expose this functionality in the API Key type natively.
func apiKeyExpiration(apiKey *macaroon.APIKey) (*time.Time, error) {
mac, err := macaroon.ParseMacaroon(apiKey.SerializeRaw())
if err != nil {
return nil, err
}
var expiration *time.Time
for _, cavbuf := range mac.Caveats() {
var cav macaroon.Caveat
err := cav.UnmarshalBinary(cavbuf)
if err != nil {
return nil, err
}
if cav.NotAfter != nil {
cavExpiration := *cav.NotAfter
if expiration == nil || expiration.After(cavExpiration) {
expiration = &cavExpiration
}
}
}
if expiration != nil && expiration.Before(time.Now().Add(time.Minute)) {
return nil, errs.New("expiration cannot be shorter than a minute")
}
return expiration, nil
}