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  }