github.com/gagliardetto/solana-go@v1.11.0/vault/kmsgcp.go (about) 1 // Copyright 2020 dfuse Platform Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package vault 16 17 import ( 18 "context" 19 "crypto/rand" 20 "encoding/base64" 21 "fmt" 22 "io" 23 "sync" 24 25 "golang.org/x/crypto/argon2" 26 "golang.org/x/crypto/nacl/secretbox" 27 "golang.org/x/oauth2/google" 28 "google.golang.org/api/cloudkms/v1" 29 ) 30 31 /// 32 /// Boxer implementation. 33 /// 34 35 type KMSGCPBoxer struct { 36 keyPath string 37 } 38 39 func NewKMSGCPBoxer(keyPath string) *KMSGCPBoxer { 40 return &KMSGCPBoxer{ 41 keyPath: keyPath, 42 } 43 } 44 45 func (b *KMSGCPBoxer) Seal(in []byte) (string, error) { 46 mgr, err := NewKMSGCPManager(b.keyPath) 47 if err != nil { 48 return "", fmt.Errorf("new kms gcp manager, %s", err) 49 } 50 51 encrypted, err := mgr.Encrypt(in) 52 if err != nil { 53 return "", fmt.Errorf("kms encryption, %s", err) 54 } 55 56 return base64.RawStdEncoding.EncodeToString(encrypted), nil 57 58 } 59 60 func (b *KMSGCPBoxer) Open(in string) ([]byte, error) { 61 mgr, err := NewKMSGCPManager(b.keyPath) 62 if err != nil { 63 return []byte{}, fmt.Errorf("new kms gcp manager, %s", err) 64 } 65 data, err := base64.RawStdEncoding.DecodeString(in) 66 if err != nil { 67 return []byte{}, fmt.Errorf("base 64 decode, %s", err) 68 } 69 out, err := mgr.Decrypt(data) 70 if err != nil { 71 return []byte{}, fmt.Errorf("base 64 decode, %s", err) 72 } 73 return out, nil 74 } 75 76 func (b *KMSGCPBoxer) WrapType() string { 77 return "kms-gcp" 78 } 79 80 const ( 81 saltLength = 16 82 nonceLength = 24 83 keyLength = 32 84 shamirSecretLength = 32 85 ) 86 87 func deriveKey(passphrase string, salt []byte) [keyLength]byte { 88 secretKeyBytes := argon2.IDKey([]byte(passphrase), salt, 4, 64*1024, 4, 32) 89 var secretKey [keyLength]byte 90 copy(secretKey[:], secretKeyBytes) 91 return secretKey 92 } 93 94 /// 95 /// Manager implementation 96 /// 97 98 func NewKMSGCPManager(keyPath string) (*KMSGCPManager, error) { 99 ctx := context.Background() 100 client, err := google.DefaultClient(ctx, cloudkms.CloudPlatformScope) 101 if err != nil { 102 return nil, err 103 } 104 105 kmsService, err := cloudkms.New(client) 106 if err != nil { 107 return nil, err 108 } 109 110 manager := &KMSGCPManager{ 111 service: kmsService, 112 keyPath: keyPath, 113 } 114 115 return manager, nil 116 } 117 118 type KMSGCPManager struct { 119 dekCache map[string][32]byte 120 dekCacheLock sync.Mutex 121 localDEK [32]byte 122 localWrappedDEK string 123 service *cloudkms.Service 124 keyPath string 125 } 126 127 func (k *KMSGCPManager) setupEncryption() error { 128 if k.dekCache != nil { 129 return nil 130 } 131 132 _, err := io.ReadFull(rand.Reader, k.localDEK[:]) 133 if err != nil { 134 return err 135 } 136 137 req := &cloudkms.EncryptRequest{ 138 Plaintext: base64.StdEncoding.EncodeToString(k.localDEK[:]), 139 } 140 141 resp, err := k.service.Projects.Locations.KeyRings.CryptoKeys.Encrypt(k.keyPath, req).Do() 142 if err != nil { 143 return err 144 } 145 146 k.localWrappedDEK = resp.Ciphertext 147 k.dekCache = map[string][32]byte{resp.Ciphertext: k.localDEK} 148 149 return nil 150 } 151 152 func (k *KMSGCPManager) fetchPlainDEK(wrappedDEK string) (out [32]byte, err error) { 153 k.dekCacheLock.Lock() 154 defer k.dekCacheLock.Unlock() 155 156 if cachedKey, found := k.dekCache[wrappedDEK]; found { 157 return cachedKey, nil 158 } 159 160 req := &cloudkms.DecryptRequest{ 161 Ciphertext: wrappedDEK, 162 } 163 resp, err := k.service.Projects.Locations.KeyRings.CryptoKeys.Decrypt(k.keyPath, req).Do() 164 if err != nil { 165 return 166 } 167 168 plainKey, err := base64.StdEncoding.DecodeString(resp.Plaintext) 169 if err != nil { 170 return 171 } 172 173 copy(out[:], plainKey) 174 175 if k.dekCache == nil { 176 k.dekCache = map[string][32]byte{} 177 } 178 if k.localWrappedDEK == "" { 179 k.localWrappedDEK = wrappedDEK 180 } 181 k.dekCache[wrappedDEK] = out 182 183 return 184 } 185 186 type BlobV1 struct { 187 Version int `bson:"version"` 188 WrappedDEK string `bson:"wrapped_dek"` 189 Nonce [24]byte `bson:"nonce"` 190 EncryptedData []byte `bson:"data"` 191 } 192 193 func (k *KMSGCPManager) Encrypt(in []byte) ([]byte, error) { 194 if err := k.setupEncryption(); err != nil { 195 return nil, err 196 } 197 198 var nonce [24]byte 199 if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil { 200 return nil, err 201 } 202 203 var sealedMsg []byte 204 sealedMsg = secretbox.Seal(sealedMsg, in, &nonce, &k.localDEK) 205 206 blob := &BlobV1{ 207 Version: 1, 208 WrappedDEK: k.localWrappedDEK, 209 Nonce: nonce, 210 EncryptedData: sealedMsg, 211 } 212 213 cereal, err := json.Marshal(blob) 214 if err != nil { 215 return nil, err 216 } 217 218 return cereal, nil 219 } 220 221 func (k *KMSGCPManager) Decrypt(in []byte) ([]byte, error) { 222 var blob BlobV1 223 err := json.Unmarshal(in, &blob) 224 if err != nil { 225 return nil, err 226 } 227 228 // No need to check `blob.Version` == 1, we did it already with 229 // the `magicFound` comparison. 230 231 plainDEK, err := k.fetchPlainDEK(blob.WrappedDEK) 232 if err != nil { 233 return nil, err 234 } 235 236 plainData, ok := secretbox.Open(nil, blob.EncryptedData, &blob.Nonce, &plainDEK) 237 if !ok { 238 return nil, fmt.Errorf("failed decrypting data, that's all we know") 239 } 240 241 return plainData, nil 242 }