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