...

Source file src/go.mongodb.org/mongo-driver/mongo/integration/client_side_encryption_test.go

Documentation: go.mongodb.org/mongo-driver/mongo/integration

     1  // Copyright (C) MongoDB, Inc. 2021-present.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License"); you may
     4  // not use this file except in compliance with the License. You may obtain
     5  // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
     6  
     7  //go:build cse
     8  // +build cse
     9  
    10  package integration
    11  
    12  import (
    13  	"context"
    14  	"testing"
    15  
    16  	"go.mongodb.org/mongo-driver/bson"
    17  	"go.mongodb.org/mongo-driver/bson/bsontype"
    18  	"go.mongodb.org/mongo-driver/bson/primitive"
    19  	"go.mongodb.org/mongo-driver/event"
    20  	"go.mongodb.org/mongo-driver/internal/assert"
    21  	"go.mongodb.org/mongo-driver/internal/integtest"
    22  	"go.mongodb.org/mongo-driver/mongo"
    23  	"go.mongodb.org/mongo-driver/mongo/integration/mtest"
    24  	"go.mongodb.org/mongo-driver/mongo/options"
    25  	"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
    26  	"go.mongodb.org/mongo-driver/x/mongo/driver"
    27  	mcopts "go.mongodb.org/mongo-driver/x/mongo/driver/mongocrypt/options"
    28  )
    29  
    30  // createDataKeyAndEncrypt creates a data key with the alternate name @keyName.
    31  // Returns a ciphertext encrypted with the data key as test data.
    32  func createDataKeyAndEncrypt(mt *mtest.T, keyName string) primitive.Binary {
    33  	mt.Helper()
    34  
    35  	kvClientOpts := options.Client().
    36  		ApplyURI(mtest.ClusterURI()).
    37  		SetReadConcern(mtest.MajorityRc).
    38  		SetWriteConcern(mtest.MajorityWc)
    39  
    40  	integtest.AddTestServerAPIVersion(kvClientOpts)
    41  
    42  	kmsProvidersMap := map[string]map[string]interface{}{
    43  		"local": {"key": localMasterKey},
    44  	}
    45  
    46  	kvClient, err := mongo.Connect(context.Background(), kvClientOpts)
    47  	defer kvClient.Disconnect(context.Background())
    48  	assert.Nil(mt, err, "Connect error: %v", err)
    49  
    50  	err = kvClient.Database("keyvault").Collection("datakeys").Drop(context.Background())
    51  	assert.Nil(mt, err, "Drop error: %v", err)
    52  
    53  	ceOpts := options.ClientEncryption().
    54  		SetKmsProviders(kmsProvidersMap).
    55  		SetKeyVaultNamespace("keyvault.datakeys")
    56  
    57  	ce, err := mongo.NewClientEncryption(kvClient, ceOpts)
    58  	assert.Nil(mt, err, "NewClientEncryption error: %v", err)
    59  
    60  	dkOpts := options.DataKey().SetKeyAltNames([]string{keyName})
    61  	_, err = ce.CreateDataKey(context.Background(), "local", dkOpts)
    62  	assert.Nil(mt, err, "CreateDataKey error: %v", err)
    63  
    64  	in := bson.RawValue{Type: bsontype.String, Value: bsoncore.AppendString(nil, "test")}
    65  	eOpts := options.Encrypt().
    66  		SetAlgorithm("AEAD_AES_256_CBC_HMAC_SHA_512-Random").
    67  		SetKeyAltName(keyName)
    68  
    69  	ciphertext, err := ce.Encrypt(context.Background(), in, eOpts)
    70  	assert.Nil(mt, err, "Encrypt error: %v", err)
    71  	return ciphertext
    72  }
    73  
    74  func getLsid(mt *mtest.T, doc bson.Raw) bson.Raw {
    75  	mt.Helper()
    76  
    77  	lsid, err := doc.LookupErr("lsid")
    78  	assert.Nil(mt, err, "expected lsid in document: %v", doc)
    79  	lsidDoc, ok := lsid.DocumentOK()
    80  	assert.True(mt, ok, "expected lsid to be document, but got: %v", lsid)
    81  	return lsidDoc
    82  }
    83  
    84  func makeMonitor(mt *mtest.T, captured *[]event.CommandStartedEvent) *event.CommandMonitor {
    85  	mt.Helper()
    86  	assert.NotNil(mt, captured, "captured is nil")
    87  
    88  	return &event.CommandMonitor{
    89  		Started: func(_ context.Context, cse *event.CommandStartedEvent) {
    90  			assert.NotNil(mt, cse, "expected non-Nil CommandStartedEvent")
    91  			*captured = append(*captured, *cse)
    92  		},
    93  	}
    94  }
    95  
    96  func TestClientSideEncryptionWithExplicitSessions(t *testing.T) {
    97  	verifyClientSideEncryptionVarsSet(t)
    98  	mt := mtest.New(t, mtest.NewOptions().MinServerVersion("4.2").Enterprise(true).CreateClient(false))
    99  
   100  	kmsProvidersMap := map[string]map[string]interface{}{
   101  		"local": {"key": localMasterKey},
   102  	}
   103  
   104  	schema := bson.D{
   105  		{"bsonType", "object"},
   106  		{"properties", bson.D{
   107  			{"encryptMe", bson.D{
   108  				{"encrypt", bson.D{
   109  					{"keyId", "/keyName"},
   110  					{"bsonType", "string"},
   111  					{"algorithm", "AEAD_AES_256_CBC_HMAC_SHA_512-Random"},
   112  				}},
   113  			}},
   114  		}},
   115  	}
   116  	schemaMap := map[string]interface{}{"db.coll": schema}
   117  
   118  	mt.Run("automatic encryption", func(mt *mtest.T) {
   119  		createDataKeyAndEncrypt(mt, "myKey")
   120  
   121  		aeOpts := options.AutoEncryption().
   122  			SetKmsProviders(kmsProvidersMap).
   123  			SetKeyVaultNamespace("keyvault.datakeys").
   124  			SetSchemaMap(schemaMap).
   125  			SetExtraOptions(getCryptSharedLibExtraOptions())
   126  
   127  		var capturedEvents []event.CommandStartedEvent
   128  
   129  		clientOpts := options.Client().
   130  			ApplyURI(mtest.ClusterURI()).
   131  			SetReadConcern(mtest.MajorityRc).
   132  			SetWriteConcern(mtest.MajorityWc).
   133  			SetAutoEncryptionOptions(aeOpts).
   134  			SetMonitor(makeMonitor(mt, &capturedEvents))
   135  
   136  		integtest.AddTestServerAPIVersion(clientOpts)
   137  
   138  		client, err := mongo.Connect(context.Background(), clientOpts)
   139  		assert.Nil(mt, err, "Connect error: %v", err)
   140  		defer client.Disconnect(context.Background())
   141  
   142  		coll := client.Database("db").Collection("coll")
   143  		err = coll.Drop(context.Background())
   144  		assert.Nil(mt, err, "Drop error: %v", err)
   145  
   146  		session, err := client.StartSession()
   147  		assert.Nil(mt, err, "StartSession error: %v", err)
   148  		sessionCtx := mongo.NewSessionContext(context.Background(), session)
   149  
   150  		capturedEvents = make([]event.CommandStartedEvent, 0)
   151  		_, err = coll.InsertOne(sessionCtx, bson.D{{"encryptMe", "test"}, {"keyName", "myKey"}})
   152  		assert.Nil(mt, err, "InsertOne error: %v", err)
   153  
   154  		assert.Equal(mt, len(capturedEvents), 2, "expected 2 events, got %v", len(capturedEvents))
   155  
   156  		// Assert the first event is a find on the keyvault.datakeys collection.
   157  		event := capturedEvents[0]
   158  		assert.Equal(mt, event.CommandName, "find", "expected command find, got %q", event.CommandName)
   159  		assert.Equal(mt, event.DatabaseName, "keyvault", "expected find on keyvault, got %q", event.DatabaseName)
   160  
   161  		// Assert the find used an implicit session with an lsid != session.ID()
   162  		lsid := getLsid(mt, event.Command)
   163  		assert.Nil(mt, err, "lsid not found in %v", event.Command)
   164  		assert.NotEqual(mt, lsid, session.ID(), "expected different lsid, but got %v", lsid)
   165  
   166  		// Assert the second event is the original insert.
   167  		event = capturedEvents[1]
   168  		assert.Equal(mt, event.CommandName, "insert", "expected command insert, got %q", event.CommandName)
   169  
   170  		// Assert the insert used the explicit session.
   171  		lsid = getLsid(mt, event.Command)
   172  		assert.Nil(mt, err, "lsid not found on %v", event.Command)
   173  		assert.Equal(mt, lsid, session.ID(), "expected lsid %v, but got %v", session.ID(), lsid)
   174  
   175  		// Check that encryptMe is encrypted.
   176  		encryptMe, err := event.Command.LookupErr("documents", "0", "encryptMe")
   177  		assert.Nil(mt, err, "could not find encryptMe in %v", event.Command)
   178  		assert.Equal(mt, encryptMe.Type, bson.TypeBinary, "expected Binary, got %v", encryptMe.Type)
   179  	})
   180  
   181  	mt.Run("automatic decryption", func(mt *mtest.T) {
   182  		ciphertext := createDataKeyAndEncrypt(mt, "myKey")
   183  
   184  		aeOpts := options.AutoEncryption().
   185  			SetKmsProviders(kmsProvidersMap).
   186  			SetKeyVaultNamespace("keyvault.datakeys").
   187  			SetBypassAutoEncryption(true)
   188  
   189  		var capturedEvents []event.CommandStartedEvent
   190  
   191  		clientOpts := options.Client().
   192  			ApplyURI(mtest.ClusterURI()).
   193  			SetReadConcern(mtest.MajorityRc).
   194  			SetWriteConcern(mtest.MajorityWc).
   195  			SetAutoEncryptionOptions(aeOpts).
   196  			SetMonitor(makeMonitor(mt, &capturedEvents))
   197  
   198  		integtest.AddTestServerAPIVersion(clientOpts)
   199  
   200  		client, err := mongo.Connect(context.Background(), clientOpts)
   201  		assert.Nil(mt, err, "Connect error: %v", err)
   202  		defer client.Disconnect(context.Background())
   203  
   204  		coll := client.Database("db").Collection("coll")
   205  		err = coll.Drop(context.Background())
   206  		assert.Nil(mt, err, "Drop error: %v", err)
   207  		_, err = coll.InsertOne(context.Background(), bson.D{{"encryptMe", ciphertext}})
   208  		assert.Nil(mt, err, "InsertOne error: %v", err)
   209  
   210  		session, err := client.StartSession()
   211  		assert.Nil(mt, err, "StartSession error: %v", err)
   212  		sessionCtx := mongo.NewSessionContext(context.Background(), session)
   213  
   214  		capturedEvents = make([]event.CommandStartedEvent, 0)
   215  		res := coll.FindOne(sessionCtx, bson.D{{}})
   216  		assert.Nil(mt, res.Err(), "FindOne error: %v", res.Err())
   217  
   218  		assert.Equal(mt, len(capturedEvents), 2, "expected 2 events, got %v", len(capturedEvents))
   219  
   220  		// Assert the first event is the original find.
   221  		event := capturedEvents[0]
   222  		assert.Equal(mt, event.CommandName, "find", "expected command find, got %q", event.CommandName)
   223  		assert.Equal(mt, event.DatabaseName, "db", "expected find on db, got %q", event.DatabaseName)
   224  
   225  		// Assert the find used the explicit session
   226  		lsid := getLsid(mt, event.Command)
   227  		assert.Nil(mt, err, "lsid not found on %v", event.Command)
   228  		assert.Equal(mt, lsid, session.ID(), "expected lsid %v, but got %v", session.ID(), lsid)
   229  
   230  		// Assert the second event is the find on the keyvault.datakeys collection.
   231  		event = capturedEvents[1]
   232  		assert.Equal(mt, event.CommandName, "find", "expected command find, got %q", event.CommandName)
   233  		assert.Equal(mt, event.DatabaseName, "keyvault", "expected find on keyvault, got %q", event.DatabaseName)
   234  
   235  		// Assert the find used an implicit session with an lsid != session.ID()
   236  		lsid = getLsid(mt, event.Command)
   237  		assert.Nil(mt, err, "lsid not found on %v", event.Command)
   238  		assert.NotEqual(mt, lsid, session.ID(), "expected different lsid, but got %v", lsid)
   239  	})
   240  }
   241  
   242  // customCrypt is a test implementation of the driver.Crypt interface. It keeps track of the number of times its
   243  // methods have been called.
   244  type customCrypt struct {
   245  	numEncryptCalls                   int
   246  	numDecryptCalls                   int
   247  	numCreateDataKeyCalls             int
   248  	numEncryptExplicitCalls           int
   249  	numEncryptExplicitExpressionCalls int
   250  	numDecryptExplicitCalls           int
   251  	numCloseCalls                     int
   252  	numBypassAutoEncryptionCalls      int
   253  	numRewrapDataKeyCalls             int
   254  }
   255  
   256  var (
   257  	_     driver.Crypt = (*customCrypt)(nil)
   258  	mySSN              = "123456789"
   259  )
   260  
   261  // Encrypt encrypts the given command.
   262  func (c *customCrypt) Encrypt(_ context.Context, _ string, cmd bsoncore.Document) (bsoncore.Document, error) {
   263  	c.numEncryptCalls++
   264  	elems, err := cmd.Elements()
   265  	if err != nil {
   266  		return nil, err
   267  	}
   268  
   269  	encryptedCmd := bsoncore.NewDocumentBuilder()
   270  	for _, elem := range elems {
   271  		// "encrypt" ssn element as "hidden"
   272  		if elem.Key() == "ssn" {
   273  			encryptedCmd = encryptedCmd.AppendString("ssn", "hidden")
   274  		} else {
   275  			encryptedCmd = encryptedCmd.AppendValue(elem.Key(), elem.Value())
   276  		}
   277  	}
   278  	return encryptedCmd.Build(), nil
   279  }
   280  
   281  // Decrypt decrypts the given command response.
   282  func (c *customCrypt) Decrypt(_ context.Context, cmdResponse bsoncore.Document) (bsoncore.Document, error) {
   283  	c.numDecryptCalls++
   284  	elems, err := cmdResponse.Elements()
   285  	if err != nil {
   286  		return nil, err
   287  	}
   288  
   289  	decryptedCmdResponse := bsoncore.NewDocumentBuilder()
   290  	for _, elem := range elems {
   291  		// "decrypt" ssn element as mySSN
   292  		if elem.Key() == "ssn" {
   293  			decryptedCmdResponse = decryptedCmdResponse.AppendString("ssn", mySSN)
   294  		} else {
   295  			decryptedCmdResponse = decryptedCmdResponse.AppendValue(elem.Key(), elem.Value())
   296  		}
   297  	}
   298  	return decryptedCmdResponse.Build(), nil
   299  }
   300  
   301  // CreateDataKey implements the driver.Crypt interface.
   302  func (c *customCrypt) CreateDataKey(_ context.Context, _ string, _ *mcopts.DataKeyOptions) (bsoncore.Document, error) {
   303  	c.numCreateDataKeyCalls++
   304  	return nil, nil
   305  }
   306  
   307  // EncryptExplicit implements the driver.Crypt interface.
   308  func (c *customCrypt) EncryptExplicit(_ context.Context, _ bsoncore.Value, _ *mcopts.ExplicitEncryptionOptions) (byte, []byte, error) {
   309  	c.numEncryptExplicitCalls++
   310  	return 0, nil, nil
   311  }
   312  
   313  // EncryptExplicit implements the driver.Crypt interface.
   314  func (c *customCrypt) EncryptExplicitExpression(_ context.Context, _ bsoncore.Document, _ *mcopts.ExplicitEncryptionOptions) (bsoncore.Document, error) {
   315  	c.numEncryptExplicitExpressionCalls++
   316  	return nil, nil
   317  }
   318  
   319  // DecryptExplicit implements the driver.Crypt interface.
   320  func (c *customCrypt) DecryptExplicit(_ context.Context, _ byte, _ []byte) (bsoncore.Value, error) {
   321  	c.numDecryptExplicitCalls++
   322  	return bsoncore.Value{}, nil
   323  }
   324  
   325  // Close implements the driver.Crypt interface.
   326  func (c *customCrypt) Close() {
   327  	c.numCloseCalls++
   328  }
   329  
   330  // BypassAutoEncryption implements the driver.Crypt interface.
   331  func (c *customCrypt) BypassAutoEncryption() bool {
   332  	c.numBypassAutoEncryptionCalls++
   333  	return false
   334  }
   335  
   336  // RewrapDataKey attempts to rewrap the document data keys matching the filter, preparing the re-wrapped documents to
   337  // be returned as a slice of bsoncore.Document.
   338  func (c *customCrypt) RewrapDataKey(_ context.Context, _ []byte,
   339  	_ *mcopts.RewrapManyDataKeyOptions) ([]bsoncore.Document, error) {
   340  
   341  	c.numRewrapDataKeyCalls++
   342  	return nil, nil
   343  }
   344  
   345  func TestClientSideEncryptionCustomCrypt(t *testing.T) {
   346  	mt := mtest.New(t, mtest.NewOptions().MinServerVersion("4.2").Enterprise(true).CreateClient(false))
   347  
   348  	kmsProvidersMap := map[string]map[string]interface{}{
   349  		"local": {"key": localMasterKey},
   350  	}
   351  
   352  	mt.Run("auto encryption and decryption", func(mt *mtest.T) {
   353  		aeOpts := options.AutoEncryption().
   354  			SetKmsProviders(kmsProvidersMap).
   355  			SetKeyVaultNamespace("keyvault.datakeys").
   356  			SetExtraOptions(getCryptSharedLibExtraOptions())
   357  		clientOpts := options.Client().
   358  			ApplyURI(mtest.ClusterURI()).
   359  			SetAutoEncryptionOptions(aeOpts)
   360  		cc := &customCrypt{}
   361  		clientOpts.Crypt = cc
   362  		integtest.AddTestServerAPIVersion(clientOpts)
   363  
   364  		client, err := mongo.Connect(context.Background(), clientOpts)
   365  		defer client.Disconnect(context.Background())
   366  		assert.Nil(mt, err, "Connect error: %v", err)
   367  
   368  		coll := client.Database("db").Collection("coll")
   369  		defer func() { _ = coll.Drop(context.Background()) }()
   370  
   371  		doc := bson.D{{"foo", "bar"}, {"ssn", mySSN}}
   372  		_, err = coll.InsertOne(context.Background(), doc)
   373  		assert.Nil(mt, err, "InsertOne error: %v", err)
   374  
   375  		res := coll.FindOne(context.Background(), bson.D{{"foo", "bar"}})
   376  		assert.Nil(mt, res.Err(), "FindOne error: %v", err)
   377  
   378  		rawRes, err := res.Raw()
   379  		assert.Nil(mt, err, "Raw error: %v", err)
   380  		ssn, ok := rawRes.Lookup("ssn").StringValueOK()
   381  		assert.True(mt, ok, "expected 'ssn' value to be type string, got %T", ssn)
   382  		assert.Equal(mt, ssn, mySSN, "expected 'ssn' value %q, got %q", mySSN, ssn)
   383  
   384  		// Assert customCrypt methods are called the correct number of times.
   385  		assert.Equal(mt, cc.numEncryptCalls, 1,
   386  			"expected 1 call to Encrypt, got %v", cc.numEncryptCalls)
   387  		assert.Equal(mt, cc.numDecryptCalls, 1,
   388  			"expected 1 call to Decrypt, got %v", cc.numDecryptCalls)
   389  		assert.Equal(mt, cc.numCreateDataKeyCalls, 0,
   390  			"expected 0 calls to CreateDataKey, got %v", cc.numCreateDataKeyCalls)
   391  		assert.Equal(mt, cc.numEncryptExplicitCalls, 0,
   392  			"expected 0 calls to EncryptExplicit, got %v", cc.numEncryptExplicitCalls)
   393  		assert.Equal(mt, cc.numEncryptExplicitExpressionCalls, 0,
   394  			"expected 0 calls to EncryptExplicitExpression, got %v", cc.numEncryptExplicitExpressionCalls)
   395  		assert.Equal(mt, cc.numDecryptExplicitCalls, 0,
   396  			"expected 0 calls to DecryptExplicit, got %v", cc.numDecryptExplicitCalls)
   397  		assert.Equal(mt, cc.numCloseCalls, 0,
   398  			"expected 0 calls to Close, got %v", cc.numCloseCalls)
   399  		assert.Equal(mt, cc.numBypassAutoEncryptionCalls, 2,
   400  			"expected 2 calls to BypassAutoEncryption, got %v", cc.numBypassAutoEncryptionCalls)
   401  	})
   402  }
   403  
   404  func TestFLE2CreateCollection(t *testing.T) {
   405  	// FLE 2 (aka Queryable Encryption) is not supported on Standalone topology.
   406  	mtOpts := mtest.NewOptions().
   407  		MinServerVersion("7.0").
   408  		Enterprise(true).
   409  		CreateClient(false).
   410  		Topologies(mtest.ReplicaSet,
   411  			mtest.Sharded,
   412  			mtest.LoadBalanced,
   413  			mtest.ShardedReplicaSet)
   414  	mt := mtest.New(t, mtOpts)
   415  
   416  	efJSON := `
   417  	{
   418  		"escCollection": "enxcol_.encryptedCollection.esc",
   419  		"ecocCollection": "enxcol_.encryptedCollection.ecoc",
   420  		"fields": [
   421  		  {
   422  			"path": "firstName",
   423  			"bsonType": "string",
   424  			"keyId": {
   425  			  "$binary": {
   426  				"subType": "04",
   427  				"base64": "AAAAAAAAAAAAAAAAAAAAAA=="
   428  			  }
   429  			}
   430  		  }
   431  		]
   432  	  }
   433  	`
   434  	var efBSON bson.Raw
   435  	err := bson.UnmarshalExtJSON([]byte(efJSON), true /* canonical */, &efBSON)
   436  	assert.Nil(mt, err, "UnmarshalExtJSON error: %v", err)
   437  
   438  	// Test the behavior in the specification test fle2-CreateCollection.json: "CreateCollection from encryptedFields.".
   439  	// The Go driver does not support encryptedFields as an option to Drop. See: GODRIVER-2413.
   440  	mt.Run("CreateCollection from encryptedFields", func(mt *mtest.T) {
   441  		// Drop data and state collections to clean up from a prior test run.
   442  		{
   443  			err = mt.DB.Collection("enxcol_.encryptedCollection.esc").Drop(context.Background())
   444  			assert.Nil(mt, err, "error in Drop: %v", err)
   445  			err = mt.DB.Collection("enxcol_.encryptedCollection.ecoc").Drop(context.Background())
   446  			assert.Nil(mt, err, "error in Drop: %v", err)
   447  			err := mt.DB.Collection("coll").Drop(context.Background())
   448  			assert.Nil(mt, err, "error in Drop: %v", err)
   449  		}
   450  
   451  		mt.DB.CreateCollection(context.Background(), "coll", options.CreateCollection().SetEncryptedFields(efBSON))
   452  
   453  		// Check expected collections and index exist.
   454  		{
   455  			got, err := mt.DB.ListCollectionNames(context.Background(), bson.D{{"name", "coll"}})
   456  			assert.Nil(mt, err, "error in ListCollectionNames")
   457  			assert.Equal(mt, got, []string{"coll"}, "expected ['coll'], got: %v", got)
   458  
   459  			got, err = mt.DB.ListCollectionNames(context.Background(), bson.D{{"name", "enxcol_.encryptedCollection.esc"}})
   460  			assert.Nil(mt, err, "error in ListCollectionNames")
   461  			assert.Equal(mt, got, []string{"enxcol_.encryptedCollection.esc"}, "expected ['encryptedCollection.esc'], got: %v", got)
   462  
   463  			got, err = mt.DB.ListCollectionNames(context.Background(), bson.D{{"name", "enxcol_.encryptedCollection.ecoc"}})
   464  			assert.Nil(mt, err, "error in ListCollectionNames")
   465  			assert.Equal(mt, got, []string{"enxcol_.encryptedCollection.ecoc"}, "expected ['encryptedCollection.ecoc'], got: %v", got)
   466  
   467  			indexSpecs, err := mt.DB.Collection("coll").Indexes().ListSpecifications(context.Background())
   468  			assert.Nil(mt, err, "error in Indexes().ListSpecifications: %v", err)
   469  			assert.Equal(mt, len(indexSpecs), 2, "expected two indexes on 'coll', got: %v", indexSpecs)
   470  			assert.Equal(mt, indexSpecs[1].Name, "__safeContent___1", "expected second index to be '__safeContent___1', got %v", indexSpecs[1].Name)
   471  		}
   472  	})
   473  }
   474  
   475  func TestFLE2DocsExample(t *testing.T) {
   476  	// FLE 2 is not supported on Standalone topology.
   477  	// Only test MongoDB Server 7.0+. MongoDB Server 7.0 introduced a backwards breaking change to the Queryable Encryption (QE) protocol: QEv2.
   478  	// libmongocrypt is configured to use the QEv2 protocol.
   479  	mtOpts := mtest.NewOptions().
   480  		MinServerVersion("7.0").
   481  		Enterprise(true).
   482  		CreateClient(false).
   483  		Topologies(mtest.ReplicaSet,
   484  			mtest.Sharded,
   485  			mtest.LoadBalanced,
   486  			mtest.ShardedReplicaSet)
   487  	mt := mtest.New(t, mtOpts)
   488  
   489  	mt.Run("Auto Encryption", func(mt *mtest.T) {
   490  		// Drop data from prior test runs.
   491  		{
   492  			err := mt.Client.Database("keyvault").Collection("datakeys").Drop(context.Background())
   493  			assert.Nil(mt, err, "error in Drop: %v", err)
   494  			err = mt.Client.Database("docsExamples").Drop(context.Background())
   495  			assert.Nil(mt, err, "error in Drop: %v", err)
   496  		}
   497  
   498  		kmsProvidersMap := map[string]map[string]interface{}{
   499  			"local": {"key": localMasterKey},
   500  		}
   501  
   502  		var key1ID primitive.Binary
   503  		var key2ID primitive.Binary
   504  
   505  		// Create two data keys.
   506  		{
   507  			cOpts := options.Client().ApplyURI(mtest.ClusterURI())
   508  			integtest.AddTestServerAPIVersion(cOpts)
   509  			keyVaultClient, err := mongo.Connect(context.Background(), cOpts)
   510  			assert.Nil(mt, err, "error in Connect: %v", err)
   511  			defer keyVaultClient.Disconnect(context.Background())
   512  			ceOpts := options.ClientEncryption().SetKmsProviders(kmsProvidersMap).SetKeyVaultNamespace("keyvault.datakeys")
   513  			ce, err := mongo.NewClientEncryption(keyVaultClient, ceOpts)
   514  			assert.Nil(mt, err, "error in NewClientEncryption: %v", err)
   515  			defer ce.Close(context.Background())
   516  			key1ID, err = ce.CreateDataKey(context.Background(), "local")
   517  			assert.Nil(mt, err, "error in CreateDataKey: %v", err)
   518  			key2ID, err = ce.CreateDataKey(context.Background(), "local")
   519  			assert.Nil(mt, err, "error in CreateDataKey: %v", err)
   520  		}
   521  
   522  		// Create an encryptedFieldsMap.
   523  		encryptedFieldsMap := bson.M{
   524  			"docsExamples.encrypted": bson.M{
   525  				"fields": []bson.M{
   526  					{
   527  						"path":     "encryptedIndexed",
   528  						"bsonType": "string",
   529  						"keyId":    key1ID,
   530  						"queries": []bson.M{
   531  							{
   532  								"queryType": "equality",
   533  							},
   534  						},
   535  					},
   536  					{
   537  						"path":     "encryptedUnindexed",
   538  						"bsonType": "string",
   539  						"keyId":    key2ID,
   540  					},
   541  				},
   542  			},
   543  		}
   544  
   545  		// Create an FLE 2 collection.
   546  		var encryptedColl *mongo.Collection
   547  		{
   548  			cOpts := options.Client().ApplyURI(mtest.ClusterURI())
   549  			integtest.AddTestServerAPIVersion(cOpts)
   550  			aeOpts := options.AutoEncryption().SetKmsProviders(kmsProvidersMap).SetKeyVaultNamespace("keyvault.datakeys").SetEncryptedFieldsMap(encryptedFieldsMap).SetExtraOptions(getCryptSharedLibExtraOptions())
   551  			cOpts.SetAutoEncryptionOptions(aeOpts)
   552  			encryptedClient, err := mongo.Connect(context.Background(), cOpts)
   553  			defer encryptedClient.Disconnect(context.Background())
   554  			assert.Nil(mt, err, "error in Connect: %v", err)
   555  			// Create the FLE 2 collection docsExample.encrypted.
   556  			db := encryptedClient.Database("docsExamples")
   557  			// Because docsExample.encrypted is in encryptedFieldsMap, it is created with FLE 2 support.
   558  			err = db.CreateCollection(context.Background(), "encrypted")
   559  			assert.Nil(mt, err, "error in CreateCollection")
   560  			encryptedColl = db.Collection("encrypted")
   561  		}
   562  
   563  		// Auto encrypt an insert and find.
   564  		{
   565  			// Encrypt an insert.
   566  			_, err := encryptedColl.InsertOne(context.Background(), bson.M{
   567  				"_id":                1,
   568  				"encryptedIndexed":   "indexedValue",
   569  				"encryptedUnindexed": "unindexedValue",
   570  			})
   571  			assert.Nil(mt, err, "error in InsertOne")
   572  
   573  			// Encrypt a find.
   574  			res := encryptedColl.FindOne(context.Background(), bson.M{
   575  				"encryptedIndexed": "indexedValue",
   576  			})
   577  			assert.Nil(mt, res.Err(), "error in FindOne: %v", res.Err())
   578  			var resBSON bson.M
   579  			err = res.Decode(&resBSON)
   580  			assert.Nil(mt, err, "error in Decode: %v", err)
   581  			assert.Equal(mt, resBSON["encryptedIndexed"], "indexedValue", "expected 'indexedValue', got %q", resBSON["encryptedIndexed"])
   582  			assert.Equal(mt, resBSON["encryptedUnindexed"], "unindexedValue", "expected 'unindexedValue', got %q", resBSON["encryptedUnindexed"])
   583  		}
   584  
   585  		// Find documents without decryption.
   586  		{
   587  			unencryptedColl := mt.Client.Database("docsExamples").Collection("encrypted")
   588  			res := unencryptedColl.FindOne(context.Background(), bson.M{"_id": 1})
   589  			assert.Nil(mt, res.Err(), "error in FindOne: %v", res.Err())
   590  			resBSON, err := res.Raw()
   591  			assert.Nil(mt, err, "error in Raw: %v", err)
   592  
   593  			val := resBSON.Lookup("encryptedIndexed")
   594  			assert.Equal(mt, val.Type, bsontype.Binary, "expected encryptedIndexed to be Binary, got %v", val.Type)
   595  			val = resBSON.Lookup("encryptedUnindexed")
   596  			assert.Equal(mt, val.Type, bsontype.Binary, "expected encryptedUnindexed to be Binary, got %v", val.Type)
   597  		}
   598  	})
   599  }
   600  

View as plain text