@@ -5,9 +5,12 @@ import (
55 "crypto/aes"
66 "crypto/cipher"
77 "crypto/rand"
8+ "crypto/sha256"
89 "encoding/json"
910 "fmt"
1011 "strings"
12+ "sync"
13+ "time"
1114
1215 kms "cloud.google.com/go/kms/apiv1"
1316 "cloud.google.com/go/kms/apiv1/kmspb"
@@ -42,8 +45,9 @@ func (c realKMSEnvelopeClient) GetCryptoKey(ctx context.Context, req *kmspb.GetC
4245// GCPKMSEnvelopeCipher encrypts credentials with a local DEK and wraps the DEK
4346// with Google Cloud KMS.
4447type GCPKMSEnvelopeCipher struct {
45- client kmsEnvelopeClient
46- keyName string
48+ client kmsEnvelopeClient
49+ keyName string
50+ dekCache * kmsDEKCache
4751}
4852
4953type kmsEnvelopePayload struct {
@@ -80,8 +84,9 @@ func newGCPKMSEnvelopeCipherWithClient(client kmsEnvelopeClient, keyName string)
8084 return nil , fmt .Errorf ("kms client is required" )
8185 }
8286 return & GCPKMSEnvelopeCipher {
83- client : client ,
84- keyName : keyName ,
87+ client : client ,
88+ keyName : keyName ,
89+ dekCache : newKMSDEKCache (5 * time .Minute , 1024 ),
8590 }, nil
8691}
8792
@@ -160,14 +165,19 @@ func (c *GCPKMSEnvelopeCipher) Decrypt(ctx context.Context, ciphertext []byte, k
160165 keyVersion = c .keyName
161166 }
162167
163- unwrapped , err := c .client .Decrypt (ctx , & kmspb.DecryptRequest {
164- Name : kmsEnvelopeCryptoKeyName (keyVersion , c .keyName ),
165- Ciphertext : payload .WrappedDEK ,
166- })
167- if err != nil {
168- return nil , fmt .Errorf ("unwrap dek: %w" , err )
168+ cacheKey := kmsEnvelopeDEKCacheKey (keyVersion , payload .WrappedDEK )
169+ dek , ok := c .dekCache .Get (cacheKey )
170+ if ! ok {
171+ unwrapped , err := c .client .Decrypt (ctx , & kmspb.DecryptRequest {
172+ Name : kmsEnvelopeCryptoKeyName (keyVersion , c .keyName ),
173+ Ciphertext : payload .WrappedDEK ,
174+ })
175+ if err != nil {
176+ return nil , fmt .Errorf ("unwrap dek: %w" , err )
177+ }
178+ dek = append ([]byte (nil ), unwrapped .GetPlaintext ()... )
179+ c .dekCache .Set (cacheKey , dek )
169180 }
170- dek := append ([]byte (nil ), unwrapped .GetPlaintext ()... )
171181 defer clearBytes (dek )
172182
173183 block , err := aes .NewCipher (dek )
@@ -202,6 +212,96 @@ func kmsEnvelopeAAD(keyVersion string) []byte {
202212 return []byte ("wasteland:dolthub-auth:gcp-kms-envelope:" + strings .TrimSpace (keyVersion ))
203213}
204214
215+ func kmsEnvelopeDEKCacheKey (keyVersion string , wrappedDEK []byte ) string {
216+ sum := sha256 .Sum256 (wrappedDEK )
217+ return strings .TrimSpace (keyVersion ) + ":" + fmt .Sprintf ("%x" , sum [:])
218+ }
219+
220+ type kmsDEKCache struct {
221+ mu sync.Mutex
222+ ttl time.Duration
223+ maxEntries int
224+ entries map [string ]kmsDEKCacheEntry
225+ }
226+
227+ type kmsDEKCacheEntry struct {
228+ dek []byte
229+ expiresAt time.Time
230+ usedAt time.Time
231+ }
232+
233+ func newKMSDEKCache (ttl time.Duration , maxEntries int ) * kmsDEKCache {
234+ if ttl <= 0 || maxEntries <= 0 {
235+ return nil
236+ }
237+ return & kmsDEKCache {
238+ ttl : ttl ,
239+ maxEntries : maxEntries ,
240+ entries : make (map [string ]kmsDEKCacheEntry ),
241+ }
242+ }
243+
244+ func (c * kmsDEKCache ) Get (key string ) ([]byte , bool ) {
245+ if c == nil {
246+ return nil , false
247+ }
248+ now := time .Now ()
249+ c .mu .Lock ()
250+ defer c .mu .Unlock ()
251+ entry , ok := c .entries [key ]
252+ if ! ok {
253+ return nil , false
254+ }
255+ if now .After (entry .expiresAt ) {
256+ clearBytes (entry .dek )
257+ delete (c .entries , key )
258+ return nil , false
259+ }
260+ entry .usedAt = now
261+ c .entries [key ] = entry
262+ return append ([]byte (nil ), entry .dek ... ), true
263+ }
264+
265+ func (c * kmsDEKCache ) Set (key string , dek []byte ) {
266+ if c == nil || key == "" || len (dek ) == 0 {
267+ return
268+ }
269+ now := time .Now ()
270+ c .mu .Lock ()
271+ defer c .mu .Unlock ()
272+ if old , ok := c .entries [key ]; ok {
273+ clearBytes (old .dek )
274+ }
275+ c .entries [key ] = kmsDEKCacheEntry {
276+ dek : append ([]byte (nil ), dek ... ),
277+ expiresAt : now .Add (c .ttl ),
278+ usedAt : now ,
279+ }
280+ c .evictLocked (now )
281+ }
282+
283+ func (c * kmsDEKCache ) evictLocked (now time.Time ) {
284+ for key , entry := range c .entries {
285+ if now .After (entry .expiresAt ) {
286+ clearBytes (entry .dek )
287+ delete (c .entries , key )
288+ }
289+ }
290+ for len (c .entries ) > c .maxEntries {
291+ var oldestKey string
292+ var oldestAt time.Time
293+ for key , entry := range c .entries {
294+ if oldestKey == "" || entry .usedAt .Before (oldestAt ) {
295+ oldestKey = key
296+ oldestAt = entry .usedAt
297+ }
298+ }
299+ entry := c .entries [oldestKey ]
300+ clearBytes (entry .dek )
301+ delete (c .entries , oldestKey )
302+ }
303+ }
304+
205305func clearBytes (buf []byte ) {
206306 for i := range buf {
207307 buf [i ] = 0
0 commit comments