@@ -2,23 +2,22 @@ package cryptokeys
22
33import (
44 "context"
5+ "strconv"
56 "sync"
67 "time"
78
89 "golang.org/x/xerrors"
910
1011 "cdr.dev/slog"
1112 "github.com/coder/coder/v2/coderd/database"
12- "github.com/coder/coder/v2/coderd/database/db2sdk"
13- "github.com/coder/coder/v2/codersdk"
1413 "github.com/coder/quartz"
1514)
1615
1716// never represents the maximum value for a time.Duration.
1817const never = 1 << 63 - 1
1918
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 {
2221 db database.Store
2322 feature database.CryptoKeyFeature
2423 logger slog.Logger
@@ -34,18 +33,34 @@ type DBCache struct {
3433 closed bool
3534}
3635
37- type DBCacheOption func (* DBCache )
36+ type DBCacheOption func (* dbCache )
3837
3938func WithDBCacheClock (clock quartz.Clock ) DBCacheOption {
40- return func (d * DBCache ) {
39+ return func (d * dbCache ) {
4140 d .clock = clock
4241 }
4342}
4443
45- // NewDBCache creates a new DBCache. Close should be called to
44+ // NewSigningCache creates a new DBCache. Close should be called to
4645// 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 {
4964 db : db ,
5065 feature : feature ,
5166 clock : quartz .NewReal (),
@@ -56,23 +71,61 @@ func NewDBCache(logger slog.Logger, db database.Store, feature database.CryptoKe
5671 opt (d )
5772 }
5873
74+ // Initialize the timer. This will get properly initialized the first time we fetch.
5975 d .timer = d .clock .AfterFunc (never , d .clear )
6076
6177 return d
6278}
6379
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
65113// it is neither deleted nor has breached its deletion date. It should only be
66114// 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+
68121 d .keysMu .RLock ()
69122 if d .closed {
70123 d .keysMu .RUnlock ()
71- return codersdk. CryptoKey {} , ErrClosed
124+ return nil , ErrClosed
72125 }
73126
74127 now := d .clock .Now ()
75- key , ok := d .keys [sequence ]
128+ key , ok := d .keys [int32 ( sequence ) ]
76129 d .keysMu .RUnlock ()
77130 if ok {
78131 return checkKey (key , now )
@@ -82,67 +135,67 @@ func (d *DBCache) Verifying(ctx context.Context, sequence int32) (codersdk.Crypt
82135 defer d .keysMu .Unlock ()
83136
84137 if d .closed {
85- return codersdk. CryptoKey {} , ErrClosed
138+ return nil , ErrClosed
86139 }
87140
88- key , ok = d .keys [sequence ]
141+ key , ok = d .keys [int32 ( sequence ) ]
89142 if ok {
90143 return checkKey (key , now )
91144 }
92145
93- err : = d .fetch (ctx )
146+ err = d .fetch (ctx )
94147 if err != nil {
95- return codersdk. CryptoKey {} , xerrors .Errorf ("fetch: %w" , err )
148+ return nil , xerrors .Errorf ("fetch: %w" , err )
96149 }
97150
98- key , ok = d .keys [sequence ]
151+ key , ok = d .keys [int32 ( sequence ) ]
99152 if ! ok {
100- return codersdk. CryptoKey {} , ErrKeyNotFound
153+ return nil , ErrKeyNotFound
101154 }
102155
103156 return checkKey (key , now )
104157}
105158
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
107160// 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 ) {
109162 d .keysMu .RLock ()
110163
111164 if d .closed {
112165 d .keysMu .RUnlock ()
113- return codersdk. CryptoKey {} , ErrClosed
166+ return "" , nil , ErrClosed
114167 }
115168
116169 latest := d .latestKey
117170 d .keysMu .RUnlock ()
118171
119172 now := d .clock .Now ()
120173 if latest .CanSign (now ) {
121- return db2sdk . CryptoKey (latest ), nil
174+ return idSecret (latest )
122175 }
123176
124177 d .keysMu .Lock ()
125178 defer d .keysMu .Unlock ()
126179
127180 if d .closed {
128- return codersdk. CryptoKey {} , ErrClosed
181+ return "" , nil , ErrClosed
129182 }
130183
131184 if d .latestKey .CanSign (now ) {
132- return db2sdk . CryptoKey (d .latestKey ), nil
185+ return idSecret (d .latestKey )
133186 }
134187
135188 // Refetch all keys for this feature so we can find the latest valid key.
136189 err := d .fetch (ctx )
137190 if err != nil {
138- return codersdk. CryptoKey {} , xerrors .Errorf ("fetch: %w" , err )
191+ return "" , nil , xerrors .Errorf ("fetch: %w" , err )
139192 }
140193
141- return db2sdk . CryptoKey (d .latestKey ), nil
194+ return idSecret (d .latestKey )
142195}
143196
144197// clear invalidates the cache. This forces the subsequent call to fetch fresh keys.
145- func (d * DBCache ) clear () {
198+ func (d * dbCache ) clear () {
146199 now := d .clock .Now ("DBCache" , "clear" )
147200 d .keysMu .Lock ()
148201 defer d .keysMu .Unlock ()
@@ -158,7 +211,7 @@ func (d *DBCache) clear() {
158211
159212// fetch fetches all keys for the given feature and determines the latest key.
160213// 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 {
162215 keys , err := d .db .GetCryptoKeysByFeature (ctx , d .feature )
163216 if err != nil {
164217 return xerrors .Errorf ("get crypto keys by feature: %w" , err )
@@ -189,22 +242,45 @@ func (d *DBCache) fetch(ctx context.Context) error {
189242 return nil
190243}
191244
192- func checkKey (key database.CryptoKey , now time.Time ) (codersdk. CryptoKey , error ) {
245+ func checkKey (key database.CryptoKey , now time.Time ) (interface {} , error ) {
193246 if ! key .CanVerify (now ) {
194- return codersdk. CryptoKey {} , ErrKeyInvalid
247+ return nil , ErrKeyInvalid
195248 }
196249
197- return db2sdk . CryptoKey ( key ), nil
250+ return key . DecodeString ()
198251}
199252
200- func (d * DBCache ) Close () {
253+ func (d * dbCache ) Close () error {
201254 d .keysMu .Lock ()
202255 defer d .keysMu .Unlock ()
203256
204257 if d .closed {
205- return
258+ return nil
206259 }
207260
208261 d .timer .Stop ()
209262 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
210286}
0 commit comments