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