136 lines
4.4 KiB
Go
136 lines
4.4 KiB
Go
|
// Copyright (C) MongoDB, Inc. 2017-present.
|
||
|
//
|
||
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||
|
// not use this file except in compliance with the License. You may obtain
|
||
|
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||
|
|
||
|
package mongo
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/pkg/errors"
|
||
|
"go.mongodb.org/mongo-driver/bson"
|
||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||
|
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
|
||
|
"go.mongodb.org/mongo-driver/x/mongo/driver"
|
||
|
cryptOpts "go.mongodb.org/mongo-driver/x/mongo/driver/mongocrypt/options"
|
||
|
)
|
||
|
|
||
|
// ClientEncryption is used to create data keys and explicitly encrypt and decrypt BSON values.
|
||
|
type ClientEncryption struct {
|
||
|
crypt *driver.Crypt
|
||
|
keyVaultClient *Client
|
||
|
keyVaultColl *Collection
|
||
|
}
|
||
|
|
||
|
// NewClientEncryption creates a new ClientEncryption instance configured with the given options.
|
||
|
func NewClientEncryption(keyVaultClient *Client, opts ...*options.ClientEncryptionOptions) (*ClientEncryption, error) {
|
||
|
if keyVaultClient == nil {
|
||
|
return nil, errors.New("keyVaultClient must not be nil")
|
||
|
}
|
||
|
|
||
|
ce := &ClientEncryption{
|
||
|
keyVaultClient: keyVaultClient,
|
||
|
}
|
||
|
ceo := options.MergeClientEncryptionOptions(opts...)
|
||
|
|
||
|
// create keyVaultColl
|
||
|
db, coll := splitNamespace(ceo.KeyVaultNamespace)
|
||
|
ce.keyVaultColl = ce.keyVaultClient.Database(db).Collection(coll, keyVaultCollOpts)
|
||
|
|
||
|
// create Crypt
|
||
|
var err error
|
||
|
kr := keyRetriever{coll: ce.keyVaultColl}
|
||
|
cir := collInfoRetriever{client: ce.keyVaultClient}
|
||
|
ce.crypt, err = driver.NewCrypt(&driver.CryptOptions{
|
||
|
KeyFn: kr.cryptKeys,
|
||
|
CollInfoFn: cir.cryptCollInfo,
|
||
|
KmsProviders: ceo.KmsProviders,
|
||
|
})
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return ce, nil
|
||
|
}
|
||
|
|
||
|
// CreateDataKey creates a new key document and inserts it into the key vault collection. Returns the _id of the
|
||
|
// created document.
|
||
|
func (ce *ClientEncryption) CreateDataKey(ctx context.Context, kmsProvider string, opts ...*options.DataKeyOptions) (primitive.Binary, error) {
|
||
|
// translate opts to cryptOpts.DataKeyOptions
|
||
|
dko := options.MergeDataKeyOptions(opts...)
|
||
|
co := cryptOpts.DataKey().SetKeyAltNames(dko.KeyAltNames)
|
||
|
if dko.MasterKey != nil {
|
||
|
keyDoc, err := transformBsoncoreDocument(ce.keyVaultClient.registry, dko.MasterKey)
|
||
|
if err != nil {
|
||
|
return primitive.Binary{}, err
|
||
|
}
|
||
|
|
||
|
co.SetMasterKey(keyDoc)
|
||
|
}
|
||
|
|
||
|
// create data key document
|
||
|
dataKeyDoc, err := ce.crypt.CreateDataKey(ctx, kmsProvider, co)
|
||
|
if err != nil {
|
||
|
return primitive.Binary{}, err
|
||
|
}
|
||
|
|
||
|
// insert key into key vault
|
||
|
_, err = ce.keyVaultColl.InsertOne(ctx, dataKeyDoc)
|
||
|
if err != nil {
|
||
|
return primitive.Binary{}, err
|
||
|
}
|
||
|
|
||
|
subtype, data := bson.Raw(dataKeyDoc).Lookup("_id").Binary()
|
||
|
return primitive.Binary{Subtype: subtype, Data: data}, nil
|
||
|
}
|
||
|
|
||
|
// Encrypt encrypts a BSON value with the given key and algorithm. Returns an encrypted value (BSON binary of subtype 6).
|
||
|
func (ce *ClientEncryption) Encrypt(ctx context.Context, val bson.RawValue, opts ...*options.EncryptOptions) (primitive.Binary, error) {
|
||
|
eo := options.MergeEncryptOptions(opts...)
|
||
|
transformed := cryptOpts.ExplicitEncryption()
|
||
|
if eo.KeyID != nil {
|
||
|
transformed.SetKeyID(*eo.KeyID)
|
||
|
}
|
||
|
if eo.KeyAltName != nil {
|
||
|
transformed.SetKeyAltName(*eo.KeyAltName)
|
||
|
}
|
||
|
transformed.SetAlgorithm(eo.Algorithm)
|
||
|
|
||
|
subtype, data, err := ce.crypt.EncryptExplicit(ctx, bsoncore.Value{Type: val.Type, Data: val.Value}, transformed)
|
||
|
if err != nil {
|
||
|
return primitive.Binary{}, err
|
||
|
}
|
||
|
return primitive.Binary{Subtype: subtype, Data: data}, nil
|
||
|
}
|
||
|
|
||
|
// Decrypt decrypts an encrypted value (BSON binary of subtype 6) and returns the original BSON value.
|
||
|
func (ce *ClientEncryption) Decrypt(ctx context.Context, val primitive.Binary) (bson.RawValue, error) {
|
||
|
decrypted, err := ce.crypt.DecryptExplicit(ctx, val.Subtype, val.Data)
|
||
|
if err != nil {
|
||
|
return bson.RawValue{}, err
|
||
|
}
|
||
|
|
||
|
return bson.RawValue{Type: decrypted.Type, Value: decrypted.Data}, nil
|
||
|
}
|
||
|
|
||
|
// Close cleans up any resources associated with the ClientEncryption instance. This includes disconnecting the
|
||
|
// key-vault Client instance.
|
||
|
func (ce *ClientEncryption) Close(ctx context.Context) error {
|
||
|
ce.crypt.Close()
|
||
|
return ce.keyVaultClient.Disconnect(ctx)
|
||
|
}
|
||
|
|
||
|
// splitNamespace takes a namespace in the form "database.collection" and returns (database name, collection name)
|
||
|
func splitNamespace(ns string) (string, string) {
|
||
|
firstDot := strings.Index(ns, ".")
|
||
|
if firstDot == -1 {
|
||
|
return "", ns
|
||
|
}
|
||
|
|
||
|
return ns[:firstDot], ns[firstDot+1:]
|
||
|
}
|