@@ -2,23 +2,22 @@ package cryptokeys
2
2
3
3
import (
4
4
"context"
5
+ "strconv"
5
6
"sync"
6
7
"time"
7
8
8
9
"golang.org/x/xerrors"
9
10
10
11
"cdr.dev/slog"
11
12
"github.com/coder/coder/v2/coderd/database"
12
- "github.com/coder/coder/v2/coderd/database/db2sdk"
13
- "github.com/coder/coder/v2/codersdk"
14
13
"github.com/coder/quartz"
15
14
)
16
15
17
16
// never represents the maximum value for a time.Duration.
18
17
const never = 1 << 63 - 1
19
18
20
- //DBCache implements Keycache for callers with access to the database.
21
- type DBCache struct {
19
+ //dbCache implements Keycache for callers with access to the database.
20
+ type dbCache struct {
22
21
db database.Store
23
22
feature database.CryptoKeyFeature
24
23
logger slog.Logger
@@ -34,18 +33,34 @@ type DBCache struct {
34
33
closed bool
35
34
}
36
35
37
- type DBCacheOption func (* DBCache )
36
+ type DBCacheOption func (* dbCache )
38
37
39
38
func WithDBCacheClock (clock quartz.Clock )DBCacheOption {
40
- return func (d * DBCache ) {
39
+ return func (d * dbCache ) {
41
40
d .clock = clock
42
41
}
43
42
}
44
43
45
- //NewDBCache creates a new DBCache. Close should be called to
44
+ //NewSigningCache creates a new DBCache. Close should be called to
46
45
// release resources associated with its internal timer.
47
- func NewDBCache (logger slog.Logger ,db database.Store ,feature database.CryptoKeyFeature ,opts ... func (* DBCache ))* DBCache {
48
- d := & DBCache {
46
+ func NewSigningCache (logger slog.Logger ,db database.Store ,feature database.CryptoKeyFeature ,opts ... func (* dbCache )) (SigningKeycache ,error ) {
47
+ if ! isSigningKeyFeature (feature ) {
48
+ return nil ,ErrInvalidFeature
49
+ }
50
+
51
+ return newDBCache (logger ,db ,feature ,opts ... ),nil
52
+ }
53
+
54
+ func NewEncryptionCache (logger slog.Logger ,db database.Store ,feature database.CryptoKeyFeature ,opts ... func (* dbCache )) (EncryptionKeycache ,error ) {
55
+ if ! isEncryptionKeyFeature (feature ) {
56
+ return nil ,ErrInvalidFeature
57
+ }
58
+
59
+ return newDBCache (logger ,db ,feature ,opts ... ),nil
60
+ }
61
+
62
+ func newDBCache (logger slog.Logger ,db database.Store ,feature database.CryptoKeyFeature ,opts ... func (* dbCache ))* dbCache {
63
+ d := & dbCache {
49
64
db :db ,
50
65
feature :feature ,
51
66
clock :quartz .NewReal (),
@@ -56,23 +71,61 @@ func NewDBCache(logger slog.Logger, db database.Store, feature database.CryptoKe
56
71
opt (d )
57
72
}
58
73
74
+ // Initialize the timer. This will get properly initialized the first time we fetch.
59
75
d .timer = d .clock .AfterFunc (never ,d .clear )
60
76
61
77
return d
62
78
}
63
79
64
- // Verifying returns the CryptoKey with the given sequence number, provided that
80
+ func (d * dbCache )EncryptingKey (ctx context.Context ) (id string ,key interface {},err error ) {
81
+ if ! isEncryptionKeyFeature (d .feature ) {
82
+ return "" ,nil ,ErrInvalidFeature
83
+ }
84
+
85
+ return d .latest (ctx )
86
+ }
87
+
88
+ func (d * dbCache )DecryptingKey (ctx context.Context ,id string ) (key interface {},err error ) {
89
+ if ! isEncryptionKeyFeature (d .feature ) {
90
+ return nil ,ErrInvalidFeature
91
+ }
92
+
93
+ return d .sequence (ctx ,id )
94
+ }
95
+
96
+ func (d * dbCache )SigningKey (ctx context.Context ) (id string ,key interface {},err error ) {
97
+ if ! isSigningKeyFeature (d .feature ) {
98
+ return "" ,nil ,ErrInvalidFeature
99
+ }
100
+
101
+ return d .latest (ctx )
102
+ }
103
+
104
+ func (d * dbCache )VerifyingKey (ctx context.Context ,id string ) (key interface {},err error ) {
105
+ if ! isSigningKeyFeature (d .feature ) {
106
+ return nil ,ErrInvalidFeature
107
+ }
108
+
109
+ return d .sequence (ctx ,id )
110
+ }
111
+
112
+ // sequence returns the CryptoKey with the given sequence number, provided that
65
113
// it is neither deleted nor has breached its deletion date. It should only be
66
114
// used for verifying or decrypting payloads. To sign/encrypt call Signing.
67
- func (d * DBCache )Verifying (ctx context.Context ,sequence int32 ) (codersdk.CryptoKey ,error ) {
115
+ func (d * dbCache )sequence (ctx context.Context ,id string ) (interface {},error ) {
116
+ sequence ,err := strconv .ParseInt (id ,10 ,32 )
117
+ if err != nil {
118
+ return nil ,xerrors .Errorf ("expecting sequence number got %q: %w" ,id ,err )
119
+ }
120
+
68
121
d .keysMu .RLock ()
69
122
if d .closed {
70
123
d .keysMu .RUnlock ()
71
- return codersdk. CryptoKey {} ,ErrClosed
124
+ return nil ,ErrClosed
72
125
}
73
126
74
127
now := d .clock .Now ()
75
- key ,ok := d .keys [sequence ]
128
+ key ,ok := d .keys [int32 ( sequence ) ]
76
129
d .keysMu .RUnlock ()
77
130
if ok {
78
131
return checkKey (key ,now )
@@ -82,67 +135,67 @@ func (d *DBCache) Verifying(ctx context.Context, sequence int32) (codersdk.Crypt
82
135
defer d .keysMu .Unlock ()
83
136
84
137
if d .closed {
85
- return codersdk. CryptoKey {} ,ErrClosed
138
+ return nil ,ErrClosed
86
139
}
87
140
88
- key ,ok = d .keys [sequence ]
141
+ key ,ok = d .keys [int32 ( sequence ) ]
89
142
if ok {
90
143
return checkKey (key ,now )
91
144
}
92
145
93
- err : =d .fetch (ctx )
146
+ err = d .fetch (ctx )
94
147
if err != nil {
95
- return codersdk. CryptoKey {} ,xerrors .Errorf ("fetch: %w" ,err )
148
+ return nil ,xerrors .Errorf ("fetch: %w" ,err )
96
149
}
97
150
98
- key ,ok = d .keys [sequence ]
151
+ key ,ok = d .keys [int32 ( sequence ) ]
99
152
if ! ok {
100
- return codersdk. CryptoKey {} ,ErrKeyNotFound
153
+ return nil ,ErrKeyNotFound
101
154
}
102
155
103
156
return checkKey (key ,now )
104
157
}
105
158
106
- //Signing returns the latest valid key for signing. A valid key is one that is
159
+ //latest returns the latest valid key for signing. A valid key is one that is
107
160
// both past its start time and before its deletion time.
108
- func (d * DBCache ) Signing (ctx context.Context ) (codersdk. CryptoKey ,error ) {
161
+ func (d * dbCache ) latest (ctx context.Context ) (string , interface {} ,error ) {
109
162
d .keysMu .RLock ()
110
163
111
164
if d .closed {
112
165
d .keysMu .RUnlock ()
113
- return codersdk. CryptoKey {} ,ErrClosed
166
+ return "" , nil ,ErrClosed
114
167
}
115
168
116
169
latest := d .latestKey
117
170
d .keysMu .RUnlock ()
118
171
119
172
now := d .clock .Now ()
120
173
if latest .CanSign (now ) {
121
- return db2sdk . CryptoKey (latest ), nil
174
+ return idSecret (latest )
122
175
}
123
176
124
177
d .keysMu .Lock ()
125
178
defer d .keysMu .Unlock ()
126
179
127
180
if d .closed {
128
- return codersdk. CryptoKey {} ,ErrClosed
181
+ return "" , nil ,ErrClosed
129
182
}
130
183
131
184
if d .latestKey .CanSign (now ) {
132
- return db2sdk . CryptoKey (d .latestKey ), nil
185
+ return idSecret (d .latestKey )
133
186
}
134
187
135
188
// Refetch all keys for this feature so we can find the latest valid key.
136
189
err := d .fetch (ctx )
137
190
if err != nil {
138
- return codersdk. CryptoKey {} ,xerrors .Errorf ("fetch: %w" ,err )
191
+ return "" , nil ,xerrors .Errorf ("fetch: %w" ,err )
139
192
}
140
193
141
- return db2sdk . CryptoKey (d .latestKey ), nil
194
+ return idSecret (d .latestKey )
142
195
}
143
196
144
197
// clear invalidates the cache. This forces the subsequent call to fetch fresh keys.
145
- func (d * DBCache )clear () {
198
+ func (d * dbCache )clear () {
146
199
now := d .clock .Now ("DBCache" ,"clear" )
147
200
d .keysMu .Lock ()
148
201
defer d .keysMu .Unlock ()
@@ -158,7 +211,7 @@ func (d *DBCache) clear() {
158
211
159
212
// fetch fetches all keys for the given feature and determines the latest key.
160
213
// It must be called while holding the keysMu lock.
161
- func (d * DBCache )fetch (ctx context.Context )error {
214
+ func (d * dbCache )fetch (ctx context.Context )error {
162
215
keys ,err := d .db .GetCryptoKeysByFeature (ctx ,d .feature )
163
216
if err != nil {
164
217
return xerrors .Errorf ("get crypto keys by feature: %w" ,err )
@@ -189,22 +242,45 @@ func (d *DBCache) fetch(ctx context.Context) error {
189
242
return nil
190
243
}
191
244
192
- func checkKey (key database.CryptoKey ,now time.Time ) (codersdk. CryptoKey ,error ) {
245
+ func checkKey (key database.CryptoKey ,now time.Time ) (interface {} ,error ) {
193
246
if ! key .CanVerify (now ) {
194
- return codersdk. CryptoKey {} ,ErrKeyInvalid
247
+ return nil ,ErrKeyInvalid
195
248
}
196
249
197
- return db2sdk . CryptoKey ( key ), nil
250
+ return key . DecodeString ()
198
251
}
199
252
200
- func (d * DBCache )Close () {
253
+ func (d * dbCache )Close ()error {
201
254
d .keysMu .Lock ()
202
255
defer d .keysMu .Unlock ()
203
256
204
257
if d .closed {
205
- return
258
+ return nil
206
259
}
207
260
208
261
d .timer .Stop ()
209
262
d .closed = true
263
+ return nil
264
+ }
265
+
266
+ func isEncryptionKeyFeature (feature database.CryptoKeyFeature )bool {
267
+ return feature == database .CryptoKeyFeatureWorkspaceApps
268
+ }
269
+
270
+ func isSigningKeyFeature (feature database.CryptoKeyFeature )bool {
271
+ switch feature {
272
+ case database .CryptoKeyFeatureTailnetResume ,database .CryptoKeyFeatureOidcConvert :
273
+ return true
274
+ default :
275
+ return false
276
+ }
277
+ }
278
+
279
+ func idSecret (k database.CryptoKey ) (string ,interface {},error ) {
280
+ key ,err := k .DecodeString ()
281
+ if err != nil {
282
+ return "" ,nil ,xerrors .Errorf ("decode key: %w" ,err )
283
+ }
284
+
285
+ return strconv .FormatInt (int64 (k .Sequence ),10 ),key ,nil
210
286
}