diff --git a/src/libmongoc/CMakeLists.txt b/src/libmongoc/CMakeLists.txt index 06a541df82..10798e0c62 100644 --- a/src/libmongoc/CMakeLists.txt +++ b/src/libmongoc/CMakeLists.txt @@ -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 () diff --git a/src/libmongoc/examples/client-side-encryption-auto-decryption.c b/src/libmongoc/examples/client-side-encryption-auto-decryption.c index 5591b53a6b..a5279b2a27 100644 --- a/src/libmongoc/examples/client-side-encryption-auto-decryption.c +++ b/src/libmongoc/examples/client-side-encryption-auto-decryption.c @@ -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 #include #include -/* 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; } diff --git a/src/libmongoc/examples/client-side-encryption-explicit.c b/src/libmongoc/examples/client-side-encryption-explicit.c index f2d140ace9..c33714b28f 100644 --- a/src/libmongoc/examples/client-side-encryption-explicit.c +++ b/src/libmongoc/examples/client-side-encryption-explicit.c @@ -1,179 +1,129 @@ -#include "./client-side-encryption-helpers.h" +// Demonstrates how to use explicit encryption and decryption using the community version of MongoDB #include #include #include -/* This example demonstrates how to use explicit encryption and decryption 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_3"}; - 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}; + 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; - } + // Set `local_key` to a 96 byte base64-encoded string: + const char *local_key = + "qx/3ydlPRXgUrBvSBWLsllUTaYDcS/pyaVo27qBHkS2AFePjInwhzCmDWHdmCYPmzhO4lRBzeZKFjSafduLL5z5DMvR/" + "QFfV4zc7btcVmV3QWbDwqZyn6G+Y18ToLHyK"; - kms_providers = BCON_NEW("local", "{", "key", BCON_BIN(0, local_masterkey, local_masterkey_len), "}"); - - /* The mongoc_client_t used to read/write application data. */ - client = mongoc_client_new("mongodb://localhost/?appname=client-side-encryption"); - 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; - } + const char *uri = "mongodb://localhost/?appname=client-side-encryption"; - 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); + mongoc_init(); - /* Set a mongoc_client_t to use for reading/writing to the key vault. This - * can be the same mongoc_client_t used by the main 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; + // Create client: + mongoc_client_t *client = mongoc_client_new(uri); + if (!client) { + FAIL("Failed to create client"); } - /* 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); - if (!mongoc_client_encryption_create_datakey(client_encryption, "local", datakey_opts, &datakey_id, &error)) { - 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); } - /* 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_keyid(encrypt_opts, &datakey_id); - 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; + // 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); } - 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; + // 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); } - printf("encrypted document: "); - if (!print_one_document(coll, &error)) { - 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); } - printf("\n"); - /* Explicitly decrypt a field */ - ret = mongoc_client_encryption_decrypt(client_encryption, &encrypted_field, &decrypted, &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); } - printf("decrypted value: %s\n", decrypted.value.v_utf8.str); - exit_status = EXIT_SUCCESS; -fail: - if (error.code) { - fprintf(stderr, "error: %s\n", error.message); + // Explicitly decrypt a value: + { + bson_value_t decrypted_value; + if (!mongoc_client_encryption_decrypt(client_encryption, &encrypted_value, &decrypted_value, &error)) { + FAIL("Failed to decrypt field: %s", error.message); + } + printf("Decrypted value: %" PRId32 "\n", decrypted_value.value.v_int32); + bson_value_destroy(&decrypted_value); } - 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); + bson_value_destroy(&encrypted_value); bson_value_destroy(&datakey_id); - + mongoc_client_encryption_destroy(client_encryption); + bson_destroy(&kms_providers); + mongoc_client_destroy(client); mongoc_cleanup(); - return exit_status; + return 0; } diff --git a/src/libmongoc/examples/client-side-encryption-helpers.c b/src/libmongoc/examples/client-side-encryption-helpers.c deleted file mode 100644 index 0f8dc3d898..0000000000 --- a/src/libmongoc/examples/client-side-encryption-helpers.c +++ /dev/null @@ -1,64 +0,0 @@ -#include "./client-side-encryption-helpers.h" - -uint8_t * -hex_to_bin(const char *hex, uint32_t *len) -{ - if (!hex) { - *len = 0; - return NULL; - } - const size_t hex_len = strlen(hex); - if (hex_len % 2 != 0) { - *len = 0; - return NULL; - } - - size_t num_bytes = hex_len / 2u; - if (num_bytes >= UINT32_MAX) { - return NULL; - } - - *len = (uint32_t)(hex_len / 2u); - uint8_t *const out = bson_malloc0(*len); - - for (size_t i = 0u; i < hex_len; i += 2u) { - uint32_t hex_char; - - if (1 != sscanf(hex + i, "%2x", &hex_char)) { - bson_free(out); - *len = 0; - return NULL; - } - out[i / 2u] = (uint8_t)hex_char; - } - return out; -} - -bool -print_one_document(mongoc_collection_t *coll, bson_error_t *error) -{ - bool ret = false; - mongoc_cursor_t *cursor = NULL; - const bson_t *found; - bson_t *filter = NULL; - char *as_string = NULL; - - filter = bson_new(); - cursor = mongoc_collection_find_with_opts(coll, filter, NULL /* opts */, NULL /* read prefs */); - if (!mongoc_cursor_next(cursor, &found)) { - fprintf(stderr, "error: did not find inserted document\n"); - goto fail; - } - if (mongoc_cursor_error(cursor, error)) { - goto fail; - } - as_string = bson_as_canonical_extended_json(found, NULL); - printf("%s", as_string); - - ret = true; -fail: - bson_destroy(filter); - mongoc_cursor_destroy(cursor); - bson_free(as_string); - return ret; -} diff --git a/src/libmongoc/examples/client-side-encryption-helpers.h b/src/libmongoc/examples/client-side-encryption-helpers.h deleted file mode 100644 index a7714962c0..0000000000 --- a/src/libmongoc/examples/client-side-encryption-helpers.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef CLIENT_SIDE_ENCRYPTION_HELPERS -#define CLIENT_SIDE_ENCRYPTION_HELPERS - -#include - -/* Helper method to find a single document in the given collection and print it -to stdout */ -bool -print_one_document(mongoc_collection_t *coll, bson_error_t *error); - -/* hex_to_bin parses a hexadecimal string to an array of bytes. `NULL` is - * returned on error. `len` is set to the number of bytes written. */ -uint8_t * -hex_to_bin(const char *hex, uint32_t *len); - -#endif diff --git a/src/libmongoc/examples/client-side-encryption-schema-map.c b/src/libmongoc/examples/client-side-encryption-schema-map.c index e80a7d1b17..044e0885af 100644 --- a/src/libmongoc/examples/client-side-encryption-schema-map.c +++ b/src/libmongoc/examples/client-side-encryption-schema-map.c @@ -1,257 +1,225 @@ -#include "./client-side-encryption-helpers.h" +// Demonstrates automatic encryption with a client-side schema map. Requires mongocryptd/crypt_shared. #include #include #include -/* Helper method to create a new data key in the key vault, a schema to use that - * key, and writes the schema to a file for later use. */ -static bool -create_schema_file(bson_t *kms_providers, - const char *keyvault_db, - const char *keyvault_coll, - mongoc_client_t *keyvault_client, - bson_error_t *error) -{ - 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; - bson_value_t datakey_id = {0}; - char *keyaltnames[] = {"mongoc_encryption_example_1"}; - bson_t *schema = NULL; - char *schema_string = NULL; - size_t schema_string_len; - FILE *outfile = NULL; - bool ret = false; - - 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); - mongoc_client_encryption_opts_set_keyvault_client(client_encryption_opts, keyvault_client); - - client_encryption = mongoc_client_encryption_new(client_encryption_opts, error); - if (!client_encryption) { - goto fail; - } +#define FAIL(...) \ + fprintf(stderr, "Error [%s:%d]:\n", __FILE__, __LINE__); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n"); \ + abort(); - /* Create a new data key and json schema 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); - if (!mongoc_client_encryption_create_datakey(client_encryption, "local", datakey_opts, &datakey_id, error)) { - goto fail; +// `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); \ } - /* Create a schema describing that "encryptedField" is a string encrypted - * with the newly created data key using deterministic encryption. */ - schema = BCON_NEW( - "properties", - "{", - "encryptedField", - "{", - "encrypt", - "{", - "keyId", - "[", - BCON_BIN(datakey_id.value.v_binary.subtype, datakey_id.value.v_binary.data, datakey_id.value.v_binary.data_len), - "]", - "bsonType", - "string", - "algorithm", - MONGOC_AEAD_AES_256_CBC_HMAC_SHA_512_DETERMINISTIC, - "}", - "}", - "}", - "bsonType", - "object"); - - /* Use canonical JSON so that other drivers and tools will be - * able to parse the MongoDB extended JSON file. */ - schema_string = bson_as_canonical_extended_json(schema, &schema_string_len); - outfile = fopen("jsonSchema.json", "w"); - if (0 == fwrite(schema_string, sizeof(char), schema_string_len, outfile)) { - fprintf(stderr, "failed to write to file\n"); - goto fail; - } - - ret = true; -fail: - mongoc_client_encryption_destroy(client_encryption); - mongoc_client_encryption_datakey_opts_destroy(datakey_opts); - mongoc_client_encryption_opts_destroy(client_encryption_opts); - bson_free(schema_string); - bson_destroy(schema); - bson_value_destroy(&datakey_id); - if (outfile) { - fclose(outfile); - } - return ret; -} - -/* This example demonstrates how to use automatic encryption with a client-side - * schema map using the enterprise version of MongoDB */ 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" + bson_error_t error; - 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_json_reader_t *reader = NULL; - bson_t schema = BSON_INITIALIZER; - bson_t *schema_map = NULL; + // The key vault collection stores encrypted data keys: + const char *keyvault_db_name = "keyvault"; + const char *keyvault_coll_name = "datakeys"; - /* The MongoClient used to access the key vault (keyvault_namespace). */ - mongoc_client_t *keyvault_client = NULL; - mongoc_collection_t *keyvault_coll = NULL; - mongoc_auto_encryption_opts_t *auto_encryption_opts = NULL; - mongoc_client_t *client = NULL; - mongoc_collection_t *coll = NULL; - bson_t *to_insert = NULL; - mongoc_client_t *unencrypted_client = NULL; - mongoc_collection_t *unencrypted_coll = NULL; + // The encrypted collection stores application data: + const char *encrypted_db_name = "db"; + const char *encrypted_coll_name = "coll"; - mongoc_init(); - - /* 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; - } + // Set `local_key` to a 96 byte base64-encoded string: + const char *local_key = + "qx/3ydlPRXgUrBvSBWLsllUTaYDcS/pyaVo27qBHkS2AFePjInwhzCmDWHdmCYPmzhO4lRBzeZKFjSafduLL5z5DMvR/" + "QFfV4zc7btcVmV3QWbDwqZyn6G+Y18ToLHyK"; - kms_providers = BCON_NEW("local", "{", "key", BCON_BIN(0, local_masterkey, local_masterkey_len), "}"); + const char *uri = "mongodb://localhost/?appname=client-side-encryption"; - /* Set up the key vault for this example. */ - keyvault_client = mongoc_client_new("mongodb://localhost/?appname=client-side-encryption-keyvault"); - BSON_ASSERT(keyvault_client); - - keyvault_coll = mongoc_client_get_collection(keyvault_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); + mongoc_init(); - 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); } - /* Create a new data key and a schema using it for encryption. Save the - * schema to the file jsonSchema.json */ - ret = create_schema_file(kms_providers, KEYVAULT_DB, KEYVAULT_COLL, keyvault_client, &error); - - if (!ret) { - goto fail; + // Set up key vault collection: + mongoc_client_t *keyvault_client; + { + keyvault_client = mongoc_client_new(uri); + if (!keyvault_client) { + FAIL("Failed to create keyvault client"); + } + mongoc_collection_t *coll = mongoc_client_get_collection(keyvault_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); } - /* Load the JSON Schema and construct the local schema_map option. */ - reader = bson_json_reader_new_from_file("jsonSchema.json", &error); - if (!reader) { - 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, keyvault_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); } - bson_json_reader_read(reader, &schema, &error); - - /* Construct the schema map, mapping the namespace of the collection to the - * schema describing encryption. */ - schema_map = BCON_NEW(ENCRYPTED_DB "." ENCRYPTED_COLL, BCON_DOCUMENT(&schema)); - - auto_encryption_opts = mongoc_auto_encryption_opts_new(); - mongoc_auto_encryption_opts_set_keyvault_client(auto_encryption_opts, keyvault_client); - 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); - mongoc_auto_encryption_opts_set_schema_map(auto_encryption_opts, schema_map); - - client = mongoc_client_new("mongodb://localhost/?appname=client-side-encryption"); - BSON_ASSERT(client); - - /* Enable automatic encryption. It will determine that encryption is - * necessary from the schema map instead of relying on the server to provide - * a schema. */ - ret = mongoc_client_enable_auto_encryption(client, auto_encryption_opts, &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); } - coll = mongoc_client_get_collection(client, ENCRYPTED_DB, ENCRYPTED_COLL); - - /* Clear old data */ - mongoc_collection_drop(coll, NULL); - - to_insert = BCON_NEW("encryptedField", "123456789"); - ret = mongoc_collection_insert_one(coll, to_insert, NULL /* opts */, NULL /* reply */, &error); - if (!ret) { - goto fail; + // Create a schema map: + bson_t schema_map = BSON_INITIALIZER; + { + /* + { + "db.coll": { + "properties" : { + "encryptedField" : { + "encrypt" : { + "keyId" : [ "" ], + "bsonType" : "string", + "algorithm" : "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" + } + } + }, + "bsonType" : "object" + } + } + */ + bson_t key_ids = BSON_INITIALIZER; + BSON_APPEND_VALUE(&key_ids, "0", &datakey_id); + + bson_t encrypt = BSON_INITIALIZER; + BSON_APPEND_ARRAY(&encrypt, "keyId", &key_ids); + BSON_APPEND_UTF8(&encrypt, "bsonType", "string"); + BSON_APPEND_UTF8(&encrypt, "algorithm", "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic"); + + bson_t encryptedField = BSON_INITIALIZER; + BSON_APPEND_DOCUMENT(&encryptedField, "encrypt", &encrypt); + + bson_t properties = BSON_INITIALIZER; + BSON_APPEND_DOCUMENT(&properties, "encryptedField", &encryptedField); + + bson_t db_coll = BSON_INITIALIZER; + BSON_APPEND_DOCUMENT(&db_coll, "properties", &properties); + BSON_APPEND_UTF8(&db_coll, "bsonType", "object"); + + BSON_APPEND_DOCUMENT(&schema_map, "db.coll", &db_coll); + + bson_destroy(&key_ids); + bson_destroy(&db_coll); + bson_destroy(&encrypt); + bson_destroy(&encryptedField); + bson_destroy(&properties); } - printf("decrypted document: "); - if (!print_one_document(coll, &error)) { - goto fail; + + // Create client configured to automatically encrypt: + mongoc_client_t *encrypted_client; + { + encrypted_client = mongoc_client_new(uri); + if (!encrypted_client) { + FAIL("Failed to create client"); + } + mongoc_auto_encryption_opts_t *ae_opts = mongoc_auto_encryption_opts_new(); + mongoc_auto_encryption_opts_set_schema_map(ae_opts, &schema_map); + 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(encrypted_client, ae_opts, &error)) { + FAIL("Failed to enable auto encryption: %s", error.message); + } + mongoc_auto_encryption_opts_destroy(ae_opts); } - printf("\n"); - unencrypted_client = mongoc_client_new("mongodb://localhost/?appname=client-side-encryption-unencrypted"); - BSON_ASSERT(unencrypted_client); + // Insert a document: + mongoc_collection_t *encrypted_coll = + mongoc_client_get_collection(encrypted_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_UTF8(&to_insert, "encryptedField", "foobar"); + 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 automatic encryption: %s\n", as_str); + + bson_free(as_str); + bson_destroy(&to_insert); + } - 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 with automatic decryption: + { + 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); + // Retrieve document without decryption: + { + mongoc_collection_t *unencrypted_coll = + mongoc_client_get_collection(keyvault_client, encrypted_db_name, encrypted_coll_name); + bson_t filter = BSON_INITIALIZER; + mongoc_cursor_t *cursor = mongoc_collection_find_with_opts(unencrypted_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 without automatic decryption: %s\n", as_str); + bson_free(as_str); + mongoc_cursor_destroy(cursor); + bson_destroy(&filter); + mongoc_collection_destroy(unencrypted_coll); } - 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); - bson_json_reader_destroy(reader); - mongoc_auto_encryption_opts_destroy(auto_encryption_opts); - mongoc_collection_destroy(coll); - mongoc_client_destroy(client); - bson_destroy(to_insert); - mongoc_collection_destroy(unencrypted_coll); - mongoc_client_destroy(unencrypted_client); + mongoc_collection_destroy(encrypted_coll); + mongoc_client_destroy(encrypted_client); + bson_destroy(&schema_map); + bson_value_destroy(&datakey_id); + mongoc_client_encryption_destroy(client_encryption); + bson_destroy(&kms_providers); mongoc_client_destroy(keyvault_client); - bson_destroy(&schema); - bson_destroy(schema_map); mongoc_cleanup(); - return exit_status; + return 0; } diff --git a/src/libmongoc/examples/client-side-encryption-server-schema.c b/src/libmongoc/examples/client-side-encryption-server-schema.c index 1bf8c333f0..f6a1db9c06 100644 --- a/src/libmongoc/examples/client-side-encryption-server-schema.c +++ b/src/libmongoc/examples/client-side-encryption-server-schema.c @@ -1,246 +1,239 @@ -#include "./client-side-encryption-helpers.h" +// Demonstrates automatic encryption with a server-side schema. Requires mongocryptd/crypt_shared. #include #include #include -/* Helper method to create and return a JSON schema to use for encryption. -The caller will use the returned schema for server-side encryption validation. -*/ -static bson_t * -create_schema(bson_t *kms_providers, - const char *keyvault_db, - const char *keyvault_coll, - mongoc_client_t *keyvault_client, - bson_error_t *error) -{ - 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; - bson_value_t datakey_id = {0}; - char *keyaltnames[] = {"mongoc_encryption_example_2"}; - bson_t *schema = NULL; - - 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); - mongoc_client_encryption_opts_set_keyvault_client(client_encryption_opts, keyvault_client); - - client_encryption = mongoc_client_encryption_new(client_encryption_opts, error); - if (!client_encryption) { - goto fail; - } +#define FAIL(...) \ + fprintf(stderr, "Error [%s:%d]:\n", __FILE__, __LINE__); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n"); \ + abort(); - /* Create a new data key and json schema 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); - if (!mongoc_client_encryption_create_datakey(client_encryption, "local", datakey_opts, &datakey_id, error)) { - goto fail; +// `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); \ } - /* Create a schema describing that "encryptedField" is a string encrypted - * with the newly created data key using deterministic encryption. */ - schema = BCON_NEW( - "properties", - "{", - "encryptedField", - "{", - "encrypt", - "{", - "keyId", - "[", - BCON_BIN(datakey_id.value.v_binary.subtype, datakey_id.value.v_binary.data, datakey_id.value.v_binary.data_len), - "]", - "bsonType", - "string", - "algorithm", - MONGOC_AEAD_AES_256_CBC_HMAC_SHA_512_DETERMINISTIC, - "}", - "}", - "}", - "bsonType", - "object"); - -fail: - 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(&datakey_id); - return schema; -} - -/* This example demonstrates how to use automatic encryption with a server-side - * schema using the enterprise version of MongoDB */ 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_json_reader_t *reader = NULL; - bson_t *schema = NULL; - - /* The MongoClient used to access the key vault (keyvault_namespace). */ - mongoc_client_t *keyvault_client = NULL; - mongoc_collection_t *keyvault_coll = NULL; - mongoc_auto_encryption_opts_t *auto_encryption_opts = NULL; - mongoc_client_t *client = NULL; - mongoc_collection_t *coll = NULL; - bson_t *to_insert = NULL; - mongoc_client_t *unencrypted_client = NULL; - mongoc_collection_t *unencrypted_coll = NULL; - bson_t *create_cmd = NULL; - bson_t *create_cmd_opts = NULL; - mongoc_write_concern_t *wc = NULL; + bson_error_t error; + + // The key vault collection stores encrypted data keys: + const char *keyvault_db_name = "keyvault"; + const char *keyvault_coll_name = "datakeys"; + + // The encrypted collection stores application data: + const char *encrypted_db_name = "db"; + const char *encrypted_coll_name = "coll"; + + // Set `local_key` to a 96 byte base64-encoded string: + const char *local_key = + "qx/3ydlPRXgUrBvSBWLsllUTaYDcS/pyaVo27qBHkS2AFePjInwhzCmDWHdmCYPmzhO4lRBzeZKFjSafduLL5z5DMvR/" + "QFfV4zc7btcVmV3QWbDwqZyn6G+Y18ToLHyK"; + + const char *uri = "mongodb://localhost/?appname=client-side-encryption"; mongoc_init(); - /* 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; + // 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); } - kms_providers = BCON_NEW("local", "{", "key", BCON_BIN(0, local_masterkey, local_masterkey_len), "}"); - - /* Set up the key vault for this example. */ - keyvault_client = mongoc_client_new("mongodb://localhost/?appname=client-side-encryption-keyvault"); - BSON_ASSERT(keyvault_client); - - keyvault_coll = mongoc_client_get_collection(keyvault_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; + // Set up key vault collection: + mongoc_client_t *keyvault_client; + { + keyvault_client = mongoc_client_new(uri); + if (!keyvault_client) { + FAIL("Failed to create keyvault client"); + } + mongoc_collection_t *coll = mongoc_client_get_collection(keyvault_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); } - auto_encryption_opts = mongoc_auto_encryption_opts_new(); - mongoc_auto_encryption_opts_set_keyvault_client(auto_encryption_opts, keyvault_client); - 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); - schema = create_schema(kms_providers, KEYVAULT_DB, KEYVAULT_COLL, keyvault_client, &error); - - if (!schema) { - 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, keyvault_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); } - client = mongoc_client_new("mongodb://localhost/?appname=client-side-encryption"); - BSON_ASSERT(client); - - ret = mongoc_client_enable_auto_encryption(client, auto_encryption_opts, &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); } - coll = mongoc_client_get_collection(client, ENCRYPTED_DB, ENCRYPTED_COLL); - - /* Clear old data */ - mongoc_collection_drop(coll, NULL); - - /* Create the collection with the encryption JSON Schema. */ - create_cmd = BCON_NEW("create", ENCRYPTED_COLL, "validator", "{", "$jsonSchema", BCON_DOCUMENT(schema), "}"); - wc = mongoc_write_concern_new(); - mongoc_write_concern_set_w(wc, MONGOC_WRITE_CONCERN_W_MAJORITY); - create_cmd_opts = bson_new(); - mongoc_write_concern_append(wc, create_cmd_opts); - ret = mongoc_client_command_with_opts( - client, ENCRYPTED_DB, create_cmd, NULL /* read prefs */, create_cmd_opts, NULL /* reply */, &error); - if (!ret) { - goto fail; + // Create collection with remote schema: + bson_t schema = BSON_INITIALIZER; + { + /* + { + "properties" : { + "encryptedField" : { + "encrypt" : { + "keyId" : [ "" ], + "bsonType" : "string", + "algorithm" : "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" + } + } + }, + "bsonType" : "object" + } + */ + bson_t key_ids = BSON_INITIALIZER; + BSON_APPEND_VALUE(&key_ids, "0", &datakey_id); + + bson_t encrypt = BSON_INITIALIZER; + BSON_APPEND_ARRAY(&encrypt, "keyId", &key_ids); + BSON_APPEND_UTF8(&encrypt, "bsonType", "string"); + BSON_APPEND_UTF8(&encrypt, "algorithm", "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic"); + + bson_t encryptedField = BSON_INITIALIZER; + BSON_APPEND_DOCUMENT(&encryptedField, "encrypt", &encrypt); + + bson_t properties = BSON_INITIALIZER; + BSON_APPEND_DOCUMENT(&properties, "encryptedField", &encryptedField); + + BSON_APPEND_DOCUMENT(&schema, "properties", &properties); + BSON_APPEND_UTF8(&schema, "bsonType", "object"); + + bson_destroy(&key_ids); + bson_destroy(&encrypt); + bson_destroy(&encryptedField); + bson_destroy(&properties); } - to_insert = BCON_NEW("encryptedField", "123456789"); - ret = mongoc_collection_insert_one(coll, to_insert, NULL /* opts */, NULL /* reply */, &error); - if (!ret) { - goto fail; + // Create client configured to automatically encrypt: + mongoc_client_t *encrypted_client; + { + encrypted_client = mongoc_client_new(uri); + if (!encrypted_client) { + FAIL("Failed to create client"); + } + mongoc_auto_encryption_opts_t *ae_opts = mongoc_auto_encryption_opts_new(); + 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(encrypted_client, ae_opts, &error)) { + FAIL("Failed to enable auto encryption: %s", error.message); + } + mongoc_auto_encryption_opts_destroy(ae_opts); } - printf("decrypted document: "); - if (!print_one_document(coll, &error)) { - goto fail; + + // Clear pre-existing data: + { + mongoc_collection_t *coll = + mongoc_client_get_collection(encrypted_client, encrypted_db_name, encrypted_coll_name); + mongoc_collection_drop(coll, NULL); + mongoc_collection_destroy(coll); } - printf("\n"); - unencrypted_client = mongoc_client_new("mongodb://localhost/?appname=client-side-encryption-unencrypted"); - BSON_ASSERT(unencrypted_client); + // Create collection with server-side schema: + mongoc_collection_t *encrypted_coll; + { + mongoc_database_t *db = mongoc_client_get_database(encrypted_client, encrypted_db_name); + bson_t create_opts = BSON_INITIALIZER; // { validator: { $jsonSchema: } } + bson_t json_schema = BSON_INITIALIZER; + BSON_APPEND_DOCUMENT(&json_schema, "$jsonSchema", &schema); + BSON_APPEND_DOCUMENT(&create_opts, "validator", &json_schema); + encrypted_coll = mongoc_database_create_collection(db, encrypted_coll_name, &create_opts, &error); + if (!encrypted_coll) { + FAIL("Failed to create collection: %s", error.message); + } + bson_destroy(&json_schema); + bson_destroy(&create_opts); + mongoc_database_destroy(db); + } - unencrypted_coll = mongoc_client_get_collection(unencrypted_client, ENCRYPTED_DB, ENCRYPTED_COLL); - printf("encrypted document: "); - if (!print_one_document(unencrypted_coll, &error)) { - goto fail; + // Insert a document: + { + bson_t to_insert = BSON_INITIALIZER; + BSON_APPEND_UTF8(&to_insert, "encryptedField", "foobar"); + 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 automatic encryption: %s\n", as_str); + + bson_free(as_str); + bson_destroy(&to_insert); } - printf("\n"); - - /* Expect a server-side error if inserting with the unencrypted collection. - */ - ret = mongoc_collection_insert_one(unencrypted_coll, to_insert, NULL /* opts */, NULL /* reply */, &error); - if (!ret) { - printf("insert with unencrypted collection failed: %s\n", error.message); - memset(&error, 0, sizeof(error)); + + // Retrieve document with automatic decryption: + { + 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); } - exit_status = EXIT_SUCCESS; -fail: - if (error.code) { - fprintf(stderr, "error: %s\n", error.message); + // Retrieve document without automatic decryption: + { + mongoc_collection_t *unencrypted_coll = + mongoc_client_get_collection(keyvault_client, encrypted_db_name, encrypted_coll_name); + bson_t filter = BSON_INITIALIZER; + mongoc_cursor_t *cursor = mongoc_collection_find_with_opts(unencrypted_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 without automatic decryption: %s\n", as_str); + bson_free(as_str); + mongoc_cursor_destroy(cursor); + bson_destroy(&filter); + mongoc_collection_destroy(unencrypted_coll); } - 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); - bson_json_reader_destroy(reader); - mongoc_auto_encryption_opts_destroy(auto_encryption_opts); - mongoc_collection_destroy(coll); - mongoc_client_destroy(client); - bson_destroy(to_insert); - mongoc_collection_destroy(unencrypted_coll); - mongoc_client_destroy(unencrypted_client); + mongoc_collection_destroy(encrypted_coll); + bson_value_destroy(&datakey_id); + mongoc_client_destroy(encrypted_client); + bson_destroy(&schema); + mongoc_client_encryption_destroy(client_encryption); + bson_destroy(&kms_providers); mongoc_client_destroy(keyvault_client); - bson_destroy(schema); - bson_destroy(create_cmd); - bson_destroy(create_cmd_opts); - mongoc_write_concern_destroy(wc); - mongoc_cleanup(); - return exit_status; + return 0; }