Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/libmongoc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1307,10 +1307,10 @@ if (ENABLE_EXAMPLES AND ENABLE_SHARED)
mongoc_add_example (fam ${PROJECT_SOURCE_DIR}/examples/find_and_modify_with_opts/fam.c)

if (MONGOC_ENABLE_CLIENT_SIDE_ENCRYPTION)
mongoc_add_example (client-side-encryption-schema-map ${PROJECT_SOURCE_DIR}/examples/client-side-encryption-schema-map.c ${PROJECT_SOURCE_DIR}/examples/client-side-encryption-helpers.c)
mongoc_add_example (client-side-encryption-server-schema ${PROJECT_SOURCE_DIR}/examples/client-side-encryption-server-schema.c ${PROJECT_SOURCE_DIR}/examples/client-side-encryption-helpers.c)
mongoc_add_example (client-side-encryption-explicit ${PROJECT_SOURCE_DIR}/examples/client-side-encryption-explicit.c ${PROJECT_SOURCE_DIR}/examples/client-side-encryption-helpers.c)
mongoc_add_example (client-side-encryption-auto-decryption ${PROJECT_SOURCE_DIR}/examples/client-side-encryption-auto-decryption.c ${PROJECT_SOURCE_DIR}/examples/client-side-encryption-helpers.c)
mongoc_add_example (client-side-encryption-schema-map ${PROJECT_SOURCE_DIR}/examples/client-side-encryption-schema-map.c)
mongoc_add_example (client-side-encryption-server-schema ${PROJECT_SOURCE_DIR}/examples/client-side-encryption-server-schema.c)
mongoc_add_example (client-side-encryption-explicit ${PROJECT_SOURCE_DIR}/examples/client-side-encryption-explicit.c)
mongoc_add_example (client-side-encryption-auto-decryption ${PROJECT_SOURCE_DIR}/examples/client-side-encryption-auto-decryption.c)
mongoc_add_example (client-side-encryption-doc-snippets ${PROJECT_SOURCE_DIR}/examples/client-side-encryption-doc-snippets.c)
endif ()

Expand Down
310 changes: 134 additions & 176 deletions src/libmongoc/examples/client-side-encryption-auto-decryption.c
Original file line number Diff line number Diff line change
@@ -1,210 +1,168 @@
#include "./client-side-encryption-helpers.h"
// Demonstrates how to use explicit encryption with automatic decryption using the community version of MongoDB

#include <mongoc/mongoc.h>

#include <stdio.h>
#include <stdlib.h>

/* This example demonstrates how to set up automatic decryption without
* automatic encryption using the community version of MongoDB */
#define FAIL(...) \
fprintf(stderr, "Error [%s:%d]:\n", __FILE__, __LINE__); \
fprintf(stderr, __VA_ARGS__); \
fprintf(stderr, "\n"); \
abort();

// `init_bson` creates BSON from JSON. Aborts on error. Use the `BSON_STR()` macro to avoid quotes.
#define init_bson(bson, json) \
if (!bson_init_from_json(&bson, json, -1, &error)) { \
FAIL("Failed to create BSON: %s", error.message); \
}

int
main(void)
{
/* The collection used to store the encryption data keys. */
#define KEYVAULT_DB "encryption"
#define KEYVAULT_COLL "__libmongocTestKeyVault"
/* The collection used to store the encrypted documents in this example. */
#define ENCRYPTED_DB "test"
#define ENCRYPTED_COLL "coll"

int exit_status = EXIT_FAILURE;
bool ret;
uint8_t *local_masterkey = NULL;
uint32_t local_masterkey_len;
bson_t *kms_providers = NULL;
bson_error_t error = {0};
bson_t *index_keys = NULL;
bson_t *index_opts = NULL;
mongoc_index_model_t *index_model = NULL;
bson_t *schema = NULL;
mongoc_client_t *client = NULL;
mongoc_collection_t *coll = NULL;
mongoc_collection_t *keyvault_coll = NULL;
bson_t *to_insert = NULL;
bson_t *create_cmd = NULL;
bson_t *create_cmd_opts = NULL;
mongoc_write_concern_t *wc = NULL;
mongoc_client_encryption_t *client_encryption = NULL;
mongoc_client_encryption_opts_t *client_encryption_opts = NULL;
mongoc_client_encryption_datakey_opts_t *datakey_opts = NULL;
char *keyaltnames[] = {"mongoc_encryption_example_4"};
bson_value_t datakey_id = {0};
bson_value_t encrypted_field = {0};
bson_value_t to_encrypt = {0};
mongoc_client_encryption_encrypt_opts_t *encrypt_opts = NULL;
bson_value_t decrypted = {0};
mongoc_auto_encryption_opts_t *auto_encryption_opts = NULL;
mongoc_client_t *unencrypted_client = NULL;
mongoc_collection_t *unencrypted_coll = NULL;
bson_error_t error;

mongoc_init();
// The key vault collection stores encrypted data keys:
const char *keyvault_db_name = "keyvault";
const char *keyvault_coll_name = "datakeys";

/* Configure the master key. This must be the same master key that was used
* to create the encryption key. */
local_masterkey = hex_to_bin(getenv("LOCAL_MASTERKEY"), &local_masterkey_len);
if (!local_masterkey || local_masterkey_len != 96) {
fprintf(stderr,
"Specify LOCAL_MASTERKEY environment variable as a "
"secure random 96 byte hex value.\n");
goto fail;
}
// The encrypted collection stores application data:
const char *encrypted_db_name = "db";
const char *encrypted_coll_name = "coll";

kms_providers = BCON_NEW("local", "{", "key", BCON_BIN(0, local_masterkey, local_masterkey_len), "}");
// Set `local_key` to a 96 byte base64-encoded string:
const char *local_key =
"qx/3ydlPRXgUrBvSBWLsllUTaYDcS/pyaVo27qBHkS2AFePjInwhzCmDWHdmCYPmzhO4lRBzeZKFjSafduLL5z5DMvR/"
"QFfV4zc7btcVmV3QWbDwqZyn6G+Y18ToLHyK";

client = mongoc_client_new("mongodb://localhost/?appname=client-side-encryption");
auto_encryption_opts = mongoc_auto_encryption_opts_new();
mongoc_auto_encryption_opts_set_keyvault_namespace(auto_encryption_opts, KEYVAULT_DB, KEYVAULT_COLL);
mongoc_auto_encryption_opts_set_kms_providers(auto_encryption_opts, kms_providers);
const char *uri = "mongodb://localhost/?appname=client-side-encryption";

/* Setting bypass_auto_encryption to true disables automatic encryption but
* keeps the automatic decryption behavior. bypass_auto_encryption will also
* disable spawning mongocryptd */
mongoc_auto_encryption_opts_set_bypass_auto_encryption(auto_encryption_opts, true);
mongoc_init();

/* Once bypass_auto_encryption is set, community users can enable auto
* encryption on the client. This will, in fact, only perform automatic
* decryption. */
ret = mongoc_client_enable_auto_encryption(client, auto_encryption_opts, &error);
if (!ret) {
goto fail;
// Configure KMS providers used to encrypt data keys:
bson_t kms_providers;
{
char *as_json = bson_strdup_printf(BSON_STR({"local" : {"key" : "%s"}}), local_key);
init_bson(kms_providers, as_json);
bson_free(as_json);
}

/* Now that automatic decryption is on, we can test it by inserting a
* document with an explicitly encrypted value into the collection. When we
* look up the document later, it should be automatically decrypted for us.
*/
coll = mongoc_client_get_collection(client, ENCRYPTED_DB, ENCRYPTED_COLL);

/* Clear old data */
mongoc_collection_drop(coll, NULL);

/* Set up the key vault for this example. */
keyvault_coll = mongoc_client_get_collection(client, KEYVAULT_DB, KEYVAULT_COLL);
mongoc_collection_drop(keyvault_coll, NULL);

/* Create a unique index to ensure that two data keys cannot share the same
* keyAltName. This is recommended practice for the key vault. */
index_keys = BCON_NEW("keyAltNames", BCON_INT32(1));
index_opts = BCON_NEW("unique",
BCON_BOOL(true),
"partialFilterExpression",
"{",
"keyAltNames",
"{",
"$exists",
BCON_BOOL(true),
"}",
"}");
index_model = mongoc_index_model_new(index_keys, index_opts);
ret = mongoc_collection_create_indexes_with_opts(
keyvault_coll, &index_model, 1, NULL /* opts */, NULL /* reply */, &error);

if (!ret) {
goto fail;
// Create client configured to automatically decrypt:
mongoc_client_t *client;
{
client = mongoc_client_new(uri);
if (!client) {
FAIL("Failed to create client");
}
mongoc_auto_encryption_opts_t *ae_opts = mongoc_auto_encryption_opts_new();
// Bypass automatic encryption (requires mongocryptd/crypt_shared) but keep automatic decryption:
mongoc_auto_encryption_opts_set_bypass_auto_encryption(ae_opts, true);
mongoc_auto_encryption_opts_set_keyvault_namespace(ae_opts, keyvault_db_name, keyvault_coll_name);
mongoc_auto_encryption_opts_set_kms_providers(ae_opts, &kms_providers);
if (!mongoc_client_enable_auto_encryption(client, ae_opts, &error)) {
FAIL("Failed to enable auto encryption: %s", error.message);
}
mongoc_auto_encryption_opts_destroy(ae_opts);
}

client_encryption_opts = mongoc_client_encryption_opts_new();
mongoc_client_encryption_opts_set_kms_providers(client_encryption_opts, kms_providers);
mongoc_client_encryption_opts_set_keyvault_namespace(client_encryption_opts, KEYVAULT_DB, KEYVAULT_COLL);

/* The key vault client is used for reading to/from the key vault. This can
* be the same mongoc_client_t used by the application. */
mongoc_client_encryption_opts_set_keyvault_client(client_encryption_opts, client);
client_encryption = mongoc_client_encryption_new(client_encryption_opts, &error);
if (!client_encryption) {
goto fail;
// Set up key vault collection:
{
mongoc_collection_t *coll = mongoc_client_get_collection(client, keyvault_db_name, keyvault_coll_name);
mongoc_collection_drop(coll, NULL); // Clear pre-existing data.

// Create index to ensure keys have unique keyAltNames:
bson_t index_keys, index_opts;
init_bson(index_keys, BSON_STR({"keyAltNames" : 1}));
init_bson(index_opts,
BSON_STR({"unique" : true, "partialFilterExpression" : {"keyAltNames" : {"$exists" : true}}}));
mongoc_index_model_t *index_model = mongoc_index_model_new(&index_keys, &index_opts);
if (!mongoc_collection_create_indexes_with_opts(
coll, &index_model, 1, NULL /* opts */, NULL /* reply */, &error)) {
FAIL("Failed to create index: %s", error.message);
}

mongoc_index_model_destroy(index_model);
bson_destroy(&index_opts);
bson_destroy(&index_keys);
mongoc_collection_destroy(coll);
}

/* Create a new data key for the encryptedField.
* https://dochub.mongodb.org/core/client-side-field-level-encryption-automatic-encryption-rules
*/
datakey_opts = mongoc_client_encryption_datakey_opts_new();
mongoc_client_encryption_datakey_opts_set_keyaltnames(datakey_opts, keyaltnames, 1);
ret = mongoc_client_encryption_create_datakey(client_encryption, "local", datakey_opts, &datakey_id, &error);
if (!ret) {
goto fail;
// Create ClientEncryption object:
mongoc_client_encryption_t *client_encryption;
{
mongoc_client_encryption_opts_t *ce_opts = mongoc_client_encryption_opts_new();
mongoc_client_encryption_opts_set_kms_providers(ce_opts, &kms_providers);
mongoc_client_encryption_opts_set_keyvault_namespace(ce_opts, keyvault_db_name, keyvault_coll_name);
mongoc_client_encryption_opts_set_keyvault_client(ce_opts, client);
client_encryption = mongoc_client_encryption_new(ce_opts, &error);
if (!client_encryption) {
FAIL("Failed to create ClientEncryption: %s", error.message);
}
mongoc_client_encryption_opts_destroy(ce_opts);
}

/* Explicitly encrypt a field. */
encrypt_opts = mongoc_client_encryption_encrypt_opts_new();
mongoc_client_encryption_encrypt_opts_set_algorithm(encrypt_opts,
MONGOC_AEAD_AES_256_CBC_HMAC_SHA_512_DETERMINISTIC);
mongoc_client_encryption_encrypt_opts_set_keyaltname(encrypt_opts, "mongoc_encryption_example_4");
to_encrypt.value_type = BSON_TYPE_UTF8;
to_encrypt.value.v_utf8.str = "123456789";
const size_t len = strlen(to_encrypt.value.v_utf8.str);
BSON_ASSERT(len <= UINT32_MAX);
to_encrypt.value.v_utf8.len = (uint32_t)len;

ret = mongoc_client_encryption_encrypt(client_encryption, &to_encrypt, encrypt_opts, &encrypted_field, &error);
if (!ret) {
goto fail;
// Create data key (see:
// https://dochub.mongodb.org/core/client-side-field-level-encryption-automatic-encryption-rules):
bson_value_t datakey_id;
{
mongoc_client_encryption_datakey_opts_t *dk_opts = mongoc_client_encryption_datakey_opts_new();
if (!mongoc_client_encryption_create_datakey(client_encryption, "local", dk_opts, &datakey_id, &error)) {
FAIL("Failed to create data key: %s", error.message);
}
mongoc_client_encryption_datakey_opts_destroy(dk_opts);
}

to_insert = bson_new();
BSON_APPEND_VALUE(to_insert, "encryptedField", &encrypted_field);
ret = mongoc_collection_insert_one(coll, to_insert, NULL /* opts */, NULL /* reply */, &error);
if (!ret) {
goto fail;
// Explicitly encrypt a value:
bson_value_t encrypted_value;
{
mongoc_client_encryption_encrypt_opts_t *e_opts = mongoc_client_encryption_encrypt_opts_new();
mongoc_client_encryption_encrypt_opts_set_algorithm(e_opts, MONGOC_AEAD_AES_256_CBC_HMAC_SHA_512_DETERMINISTIC);
mongoc_client_encryption_encrypt_opts_set_keyid(e_opts, &datakey_id);
bson_value_t to_encrypt = {.value_type = BSON_TYPE_INT32, .value = {.v_int32 = 123}};
if (!mongoc_client_encryption_encrypt(client_encryption, &to_encrypt, e_opts, &encrypted_value, &error)) {
FAIL("Failed to encrypt field: %s", error.message);
}
mongoc_client_encryption_encrypt_opts_destroy(e_opts);
}

/* When we retrieve the document, any encrypted fields will get automatically
* decrypted by the driver. */
printf("decrypted document: ");
if (!print_one_document(coll, &error)) {
goto fail;
// Insert document with encrypted payload:
mongoc_collection_t *encrypted_coll = mongoc_client_get_collection(client, encrypted_db_name, encrypted_coll_name);
{
mongoc_collection_drop(encrypted_coll, NULL); // Clear pre-existing data.

bson_t to_insert = BSON_INITIALIZER;
BSON_APPEND_VALUE(&to_insert, "encryptedField", &encrypted_value);
if (!mongoc_collection_insert_one(encrypted_coll, &to_insert, NULL /* opts */, NULL /* reply */, &error)) {
FAIL("Failed to insert: %s", error.message);
}
char *as_str = bson_as_relaxed_extended_json(&to_insert, NULL);
printf("Inserted document with encrypted payload: %s\n", as_str);

bson_free(as_str);
bson_destroy(&to_insert);
}
printf("\n");

unencrypted_client = mongoc_client_new("mongodb://localhost/?appname=client-side-encryption");
unencrypted_coll = mongoc_client_get_collection(unencrypted_client, ENCRYPTED_DB, ENCRYPTED_COLL);

printf("encrypted document: ");
if (!print_one_document(unencrypted_coll, &error)) {
goto fail;
// Retrieve document (automatically decrypts):
{
bson_t filter = BSON_INITIALIZER;
mongoc_cursor_t *cursor = mongoc_collection_find_with_opts(encrypted_coll, &filter, NULL, NULL);
const bson_t *result;
if (!mongoc_cursor_next(cursor, &result)) {
FAIL("Failed to find inserted document: %s", error.message);
}
char *as_str = bson_as_relaxed_extended_json(result, NULL);
printf("Retrieved document with automatic decryption: %s\n", as_str);
bson_free(as_str);
mongoc_cursor_destroy(cursor);
bson_destroy(&filter);
}
printf("\n");

exit_status = EXIT_SUCCESS;
fail:
if (error.code) {
fprintf(stderr, "error: %s\n", error.message);
}

bson_free(local_masterkey);
bson_destroy(kms_providers);
mongoc_collection_destroy(keyvault_coll);
mongoc_index_model_destroy(index_model);
bson_destroy(index_opts);
bson_destroy(index_keys);
mongoc_collection_destroy(coll);
mongoc_client_destroy(client);
bson_destroy(to_insert);
bson_destroy(schema);
bson_destroy(create_cmd);
bson_destroy(create_cmd_opts);
mongoc_write_concern_destroy(wc);
mongoc_client_encryption_destroy(client_encryption);
mongoc_client_encryption_datakey_opts_destroy(datakey_opts);
mongoc_client_encryption_opts_destroy(client_encryption_opts);
bson_value_destroy(&encrypted_field);
mongoc_client_encryption_encrypt_opts_destroy(encrypt_opts);
bson_value_destroy(&decrypted);
mongoc_collection_destroy(encrypted_coll);
bson_value_destroy(&encrypted_value);
bson_value_destroy(&datakey_id);
mongoc_collection_destroy(unencrypted_coll);
mongoc_client_destroy(unencrypted_client);
mongoc_auto_encryption_opts_destroy(auto_encryption_opts);

mongoc_client_encryption_destroy(client_encryption);
bson_destroy(&kms_providers);
mongoc_client_destroy(client);
mongoc_cleanup();
return exit_status;
return 0;
}
Loading