...

Source file src/go.mongodb.org/mongo-driver/mongo/client_side_encryption_examples_test.go

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

     1  // Copyright (C) MongoDB, Inc. 2017-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  package mongo
     8  
     9  import (
    10  	"context"
    11  	"crypto/rand"
    12  	"encoding/base64"
    13  	"fmt"
    14  	"log"
    15  
    16  	"go.mongodb.org/mongo-driver/bson"
    17  	"go.mongodb.org/mongo-driver/bson/primitive"
    18  	"go.mongodb.org/mongo-driver/mongo/options"
    19  )
    20  
    21  func Example_clientSideEncryption() {
    22  	// This would have to be the same master key that was used to create the
    23  	// encryption key.
    24  	localKey := make([]byte, 96)
    25  	if _, err := rand.Read(localKey); err != nil {
    26  		log.Fatal(err)
    27  	}
    28  	kmsProviders := map[string]map[string]interface{}{
    29  		"local": {
    30  			"key": localKey,
    31  		},
    32  	}
    33  	keyVaultNamespace := "encryption.__keyVault"
    34  
    35  	uri := "mongodb://localhost:27017"
    36  	autoEncryptionOpts := options.AutoEncryption().
    37  		SetKeyVaultNamespace(keyVaultNamespace).
    38  		SetKmsProviders(kmsProviders)
    39  	clientOpts := options.Client().
    40  		ApplyURI(uri).
    41  		SetAutoEncryptionOptions(autoEncryptionOpts)
    42  	client, err := Connect(context.TODO(), clientOpts)
    43  	if err != nil {
    44  		log.Fatalf("Connect error: %v", err)
    45  	}
    46  	defer func() {
    47  		if err = client.Disconnect(context.TODO()); err != nil {
    48  			log.Fatalf("Disconnect error: %v", err)
    49  		}
    50  	}()
    51  
    52  	collection := client.Database("test").Collection("coll")
    53  	if err := collection.Drop(context.TODO()); err != nil {
    54  		log.Fatalf("Collection.Drop error: %v", err)
    55  	}
    56  
    57  	_, err = collection.InsertOne(
    58  		context.TODO(),
    59  		bson.D{{"encryptedField", "123456789"}})
    60  	if err != nil {
    61  		log.Fatalf("InsertOne error: %v", err)
    62  	}
    63  	res, err := collection.FindOne(context.TODO(), bson.D{}).Raw()
    64  	if err != nil {
    65  		log.Fatalf("FindOne error: %v", err)
    66  	}
    67  	fmt.Println(res)
    68  }
    69  
    70  func Example_clientSideEncryptionCreateKey() {
    71  	keyVaultNamespace := "encryption.__keyVault"
    72  	uri := "mongodb://localhost:27017"
    73  	// kmsProviders would have to be populated with the correct KMS provider
    74  	// information before it's used.
    75  	var kmsProviders map[string]map[string]interface{}
    76  
    77  	// Create Client and ClientEncryption
    78  	clientEncryptionOpts := options.ClientEncryption().
    79  		SetKeyVaultNamespace(keyVaultNamespace).
    80  		SetKmsProviders(kmsProviders)
    81  	keyVaultClient, err := Connect(
    82  		context.TODO(),
    83  		options.Client().ApplyURI(uri))
    84  	if err != nil {
    85  		log.Fatalf("Connect error for keyVaultClient: %v", err)
    86  	}
    87  	clientEnc, err := NewClientEncryption(keyVaultClient, clientEncryptionOpts)
    88  	if err != nil {
    89  		log.Fatalf("NewClientEncryption error: %v", err)
    90  	}
    91  	defer func() {
    92  		// this will disconnect the keyVaultClient as well
    93  		if err = clientEnc.Close(context.TODO()); err != nil {
    94  			log.Fatalf("Close error: %v", err)
    95  		}
    96  	}()
    97  
    98  	// Create a new data key and encode it as base64
    99  	dataKeyID, err := clientEnc.CreateDataKey(context.TODO(), "local")
   100  	if err != nil {
   101  		log.Fatalf("CreateDataKey error: %v", err)
   102  	}
   103  	dataKeyBase64 := base64.StdEncoding.EncodeToString(dataKeyID.Data)
   104  
   105  	// Create a JSON schema using the new data key. This schema could also be
   106  	// written in a separate file and read in using I/O functions.
   107  	schema := `{
   108  		"properties": {
   109  			"encryptedField": {
   110  				"encrypt": {
   111  					"keyId": [{
   112  						"$binary": {
   113  							"base64": "%s",
   114  							"subType": "04"
   115  						}
   116  					}],
   117  					"bsonType": "string",
   118  					"algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic"
   119  				}
   120  			}
   121  		},
   122  		"bsonType": "object"
   123  	}`
   124  	schema = fmt.Sprintf(schema, dataKeyBase64)
   125  	var schemaDoc bson.Raw
   126  	err = bson.UnmarshalExtJSON([]byte(schema), true, &schemaDoc)
   127  	if err != nil {
   128  		log.Fatalf("UnmarshalExtJSON error: %v", err)
   129  	}
   130  
   131  	// Configure a Client with auto encryption using the new schema
   132  	dbName := "test"
   133  	collName := "coll"
   134  	schemaMap := map[string]interface{}{
   135  		dbName + "." + collName: schemaDoc,
   136  	}
   137  	autoEncryptionOpts := options.AutoEncryption().
   138  		SetKmsProviders(kmsProviders).
   139  		SetKeyVaultNamespace(keyVaultNamespace).
   140  		SetSchemaMap(schemaMap)
   141  
   142  	clientOptions := options.Client().
   143  		ApplyURI(uri).
   144  		SetAutoEncryptionOptions(autoEncryptionOpts)
   145  	client, err := Connect(context.TODO(), clientOptions)
   146  	if err != nil {
   147  		log.Fatalf("Connect error for encrypted client: %v", err)
   148  	}
   149  	defer func() {
   150  		_ = client.Disconnect(context.TODO())
   151  	}()
   152  
   153  	// Use client for operations.
   154  }
   155  
   156  func Example_explictEncryption() {
   157  	// localMasterKey must be the same master key that was used to create the
   158  	// encryption key.
   159  	var localMasterKey []byte
   160  	kmsProviders := map[string]map[string]interface{}{
   161  		"local": {
   162  			"key": localMasterKey,
   163  		},
   164  	}
   165  
   166  	// The MongoDB namespace (db.collection) used to store the encryption data
   167  	// keys.
   168  	keyVaultDBName, keyVaultCollName := "encryption", "testKeyVault"
   169  	keyVaultNamespace := keyVaultDBName + "." + keyVaultCollName
   170  
   171  	// The Client used to read/write application data.
   172  	client, err := Connect(
   173  		context.TODO(),
   174  		options.Client().ApplyURI("mongodb://localhost:27017"))
   175  	if err != nil {
   176  		panic(err)
   177  	}
   178  	defer func() { _ = client.Disconnect(context.TODO()) }()
   179  
   180  	// Get a handle to the application collection and clear existing data.
   181  	coll := client.Database("test").Collection("coll")
   182  	_ = coll.Drop(context.TODO())
   183  
   184  	// Set up the key vault for this example.
   185  	keyVaultColl := client.Database(keyVaultDBName).Collection(keyVaultCollName)
   186  	_ = keyVaultColl.Drop(context.TODO())
   187  	// Ensure that two data keys cannot share the same keyAltName.
   188  	keyVaultIndex := IndexModel{
   189  		Keys: bson.D{{"keyAltNames", 1}},
   190  		Options: options.Index().
   191  			SetUnique(true).
   192  			SetPartialFilterExpression(bson.D{
   193  				{"keyAltNames", bson.D{
   194  					{"$exists", true},
   195  				}},
   196  			}),
   197  	}
   198  	_, err = keyVaultColl.Indexes().CreateOne(context.TODO(), keyVaultIndex)
   199  	if err != nil {
   200  		panic(err)
   201  	}
   202  
   203  	// Create the ClientEncryption object to use for explicit
   204  	// encryption/decryption. The Client passed to NewClientEncryption is used
   205  	// to read/write to the key vault. This can be the same Client used by the
   206  	// main application.
   207  	clientEncryptionOpts := options.ClientEncryption().
   208  		SetKmsProviders(kmsProviders).
   209  		SetKeyVaultNamespace(keyVaultNamespace)
   210  	clientEncryption, err := NewClientEncryption(client, clientEncryptionOpts)
   211  	if err != nil {
   212  		panic(err)
   213  	}
   214  	defer func() { _ = clientEncryption.Close(context.TODO()) }()
   215  
   216  	// Create a new data key for the encrypted field.
   217  	dataKeyOpts := options.DataKey().
   218  		SetKeyAltNames([]string{"go_encryption_example"})
   219  	dataKeyID, err := clientEncryption.CreateDataKey(
   220  		context.TODO(),
   221  		"local",
   222  		dataKeyOpts)
   223  	if err != nil {
   224  		panic(err)
   225  	}
   226  
   227  	// Create a bson.RawValue to encrypt and encrypt it using the key that was
   228  	// just created.
   229  	rawValueType, rawValueData, err := bson.MarshalValue("123456789")
   230  	if err != nil {
   231  		panic(err)
   232  	}
   233  	rawValue := bson.RawValue{Type: rawValueType, Value: rawValueData}
   234  	encryptionOpts := options.Encrypt().
   235  		SetAlgorithm("AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic").
   236  		SetKeyID(dataKeyID)
   237  	encryptedField, err := clientEncryption.Encrypt(
   238  		context.TODO(),
   239  		rawValue,
   240  		encryptionOpts)
   241  	if err != nil {
   242  		panic(err)
   243  	}
   244  
   245  	// Insert a document with the encrypted field and then find it.
   246  	_, err = coll.InsertOne(
   247  		context.TODO(),
   248  		bson.D{{"encryptedField", encryptedField}})
   249  	if err != nil {
   250  		panic(err)
   251  	}
   252  	var foundDoc bson.M
   253  	err = coll.FindOne(context.TODO(), bson.D{}).Decode(&foundDoc)
   254  	if err != nil {
   255  		panic(err)
   256  	}
   257  
   258  	// Decrypt the encrypted field in the found document.
   259  	decrypted, err := clientEncryption.Decrypt(
   260  		context.TODO(),
   261  		foundDoc["encryptedField"].(primitive.Binary))
   262  	if err != nil {
   263  		panic(err)
   264  	}
   265  	fmt.Printf("Decrypted value: %s\n", decrypted)
   266  }
   267  
   268  func Example_explictEncryptionWithAutomaticDecryption() {
   269  	// Automatic encryption requires MongoDB 4.2 enterprise, but automatic
   270  	// decryption is supported for all users.
   271  
   272  	// localMasterKey must be the same master key that was used to create the
   273  	// encryption key.
   274  	var localMasterKey []byte
   275  	kmsProviders := map[string]map[string]interface{}{
   276  		"local": {
   277  			"key": localMasterKey,
   278  		},
   279  	}
   280  
   281  	// The MongoDB namespace (db.collection) used to store the encryption data
   282  	// keys.
   283  	keyVaultDBName, keyVaultCollName := "encryption", "testKeyVault"
   284  	keyVaultNamespace := keyVaultDBName + "." + keyVaultCollName
   285  
   286  	// Create the Client for reading/writing application data. Configure it with
   287  	// BypassAutoEncryption=true to disable automatic encryption but keep
   288  	// automatic decryption. Setting BypassAutoEncryption will also bypass
   289  	// spawning mongocryptd in the driver.
   290  	autoEncryptionOpts := options.AutoEncryption().
   291  		SetKmsProviders(kmsProviders).
   292  		SetKeyVaultNamespace(keyVaultNamespace).
   293  		SetBypassAutoEncryption(true)
   294  	clientOpts := options.Client().
   295  		ApplyURI("mongodb://localhost:27017").
   296  		SetAutoEncryptionOptions(autoEncryptionOpts)
   297  	client, err := Connect(context.TODO(), clientOpts)
   298  	if err != nil {
   299  		panic(err)
   300  	}
   301  	defer func() { _ = client.Disconnect(context.TODO()) }()
   302  
   303  	// Get a handle to the application collection and clear existing data.
   304  	coll := client.Database("test").Collection("coll")
   305  	_ = coll.Drop(context.TODO())
   306  
   307  	// Set up the key vault for this example.
   308  	keyVaultColl := client.Database(keyVaultDBName).Collection(keyVaultCollName)
   309  	_ = keyVaultColl.Drop(context.TODO())
   310  	// Ensure that two data keys cannot share the same keyAltName.
   311  	keyVaultIndex := IndexModel{
   312  		Keys: bson.D{{"keyAltNames", 1}},
   313  		Options: options.Index().
   314  			SetUnique(true).
   315  			SetPartialFilterExpression(bson.D{
   316  				{"keyAltNames", bson.D{
   317  					{"$exists", true},
   318  				}},
   319  			}),
   320  	}
   321  
   322  	_, err = keyVaultColl.Indexes().CreateOne(context.TODO(), keyVaultIndex)
   323  	if err != nil {
   324  		panic(err)
   325  	}
   326  
   327  	// Create the ClientEncryption object to use for explicit
   328  	// encryption/decryption. The Client passed to NewClientEncryption is used
   329  	// to read/write to the key vault. This can be the same Client used by the
   330  	// main application.
   331  	clientEncryptionOpts := options.ClientEncryption().
   332  		SetKmsProviders(kmsProviders).
   333  		SetKeyVaultNamespace(keyVaultNamespace)
   334  	clientEncryption, err := NewClientEncryption(client, clientEncryptionOpts)
   335  	if err != nil {
   336  		panic(err)
   337  	}
   338  	defer func() { _ = clientEncryption.Close(context.TODO()) }()
   339  
   340  	// Create a new data key for the encrypted field.
   341  	dataKeyOpts := options.DataKey().
   342  		SetKeyAltNames([]string{"go_encryption_example"})
   343  	dataKeyID, err := clientEncryption.CreateDataKey(
   344  		context.TODO(),
   345  		"local",
   346  		dataKeyOpts)
   347  	if err != nil {
   348  		panic(err)
   349  	}
   350  
   351  	// Create a bson.RawValue to encrypt and encrypt it using the key that was
   352  	// just created.
   353  	rawValueType, rawValueData, err := bson.MarshalValue("123456789")
   354  	if err != nil {
   355  		panic(err)
   356  	}
   357  	rawValue := bson.RawValue{Type: rawValueType, Value: rawValueData}
   358  	encryptionOpts := options.Encrypt().
   359  		SetAlgorithm("AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic").
   360  		SetKeyID(dataKeyID)
   361  	encryptedField, err := clientEncryption.Encrypt(
   362  		context.TODO(),
   363  		rawValue,
   364  		encryptionOpts)
   365  	if err != nil {
   366  		panic(err)
   367  	}
   368  
   369  	// Insert a document with the encrypted field and then find it. The FindOne
   370  	// call will automatically decrypt the field in the document.
   371  	_, err = coll.InsertOne(
   372  		context.TODO(),
   373  		bson.D{{"encryptedField", encryptedField}})
   374  	if err != nil {
   375  		panic(err)
   376  	}
   377  	var foundDoc bson.M
   378  	err = coll.FindOne(context.TODO(), bson.D{}).Decode(&foundDoc)
   379  	if err != nil {
   380  		panic(err)
   381  	}
   382  	fmt.Printf("Decrypted document: %v\n", foundDoc)
   383  }
   384  

View as plain text