diff options
Diffstat (limited to 'src/third_party/wiredtiger/examples/c/ex_encrypt.c')
-rw-r--r-- | src/third_party/wiredtiger/examples/c/ex_encrypt.c | 593 |
1 files changed, 593 insertions, 0 deletions
diff --git a/src/third_party/wiredtiger/examples/c/ex_encrypt.c b/src/third_party/wiredtiger/examples/c/ex_encrypt.c new file mode 100644 index 00000000000..425ee6b7287 --- /dev/null +++ b/src/third_party/wiredtiger/examples/c/ex_encrypt.c @@ -0,0 +1,593 @@ +/*- + * Public Domain 2014-2016 MongoDB, Inc. + * Public Domain 2008-2014 WiredTiger, Inc. + * + * This is free and unencumbered software released into the public domain. + * + * Anyone is free to copy, modify, publish, use, compile, sell, or + * distribute this software, either in source code form or as a compiled + * binary, for any purpose, commercial or non-commercial, and by any + * means. + * + * In jurisdictions that recognize copyright laws, the author or authors + * of this software dedicate any and all copyright interest in the + * software to the public domain. We make this dedication for the benefit + * of the public at large and to the detriment of our heirs and + * successors. We intend this dedication to be an overt act of + * relinquishment in perpetuity of all present and future rights to this + * software under copyright law. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * ex_encrypt.c + * demonstrates how to use the encryption API. + */ +#include <ctype.h> +#include <errno.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifndef _WIN32 +#include <unistd.h> +#else +#include "windows_shim.h" +#endif + +#include <wiredtiger.h> +#include <wiredtiger_ext.h> + +#ifdef _WIN32 +/* + * Explicitly export this function so it is visible when loading extensions. + */ +__declspec(dllexport) +#endif +int add_my_encryptors(WT_CONNECTION *connection); + +static const char *home = NULL; + +#define SYS_KEYID "system" +#define SYS_PW "system_password" +#define USER1_KEYID "user1" +#define USER2_KEYID "user2" +#define USERBAD_KEYID "userbad" + +#define ITEM_MATCHES(config_item, s) \ + (strlen(s) == (config_item).len && \ + strncmp((config_item).str, s, (config_item).len) == 0) + +/*! [encryption example callback implementation] */ +typedef struct { + WT_ENCRYPTOR encryptor; /* Must come first */ + int rot_N; /* rotN value */ + uint32_t num_calls; /* Count of calls */ + char *keyid; /* Saved keyid */ + char *password; /* Saved password */ +} MY_CRYPTO; + +#define CHKSUM_LEN 4 +#define IV_LEN 16 + +/* + * make_cksum -- + * This is where one would call a checksum function on the encrypted + * buffer. Here we just put a constant value in it. + */ +static void +make_cksum(uint8_t *dst) +{ + int i; + /* + * Assume array is big enough for the checksum. + */ + for (i = 0; i < CHKSUM_LEN; i++) + dst[i] = 'C'; +} + +/* + * make_iv -- + * This is where one would generate the initialization vector. + * Here we just put a constant value in it. + */ +static void +make_iv(uint8_t *dst) +{ + int i; + /* + * Assume array is big enough for the initialization vector. + */ + for (i = 0; i < IV_LEN; i++) + dst[i] = 'I'; +} + +/* + * Rotate encryption functions. + */ +/* + * do_rotate -- + * Perform rot-N on the buffer given. + */ +static void +do_rotate(char *buf, size_t len, int rotn) +{ + uint32_t i; + /* + * Now rotate + */ + for (i = 0; i < len; i++) + if (isalpha(buf[i])) { + if (islower(buf[i])) + buf[i] = ((buf[i] - 'a') + rotn) % 26 + 'a'; + else + buf[i] = ((buf[i] - 'A') + rotn) % 26 + 'A'; + } +} + +/* + * rotate_decrypt -- + * A simple rotate decryption. + */ +static int +rotate_decrypt(WT_ENCRYPTOR *encryptor, WT_SESSION *session, + uint8_t *src, size_t src_len, + uint8_t *dst, size_t dst_len, + size_t *result_lenp) +{ + MY_CRYPTO *my_crypto = (MY_CRYPTO *)encryptor; + size_t mylen; + uint32_t i; + + (void)session; /* Unused */ + ++my_crypto->num_calls; + + if (src == NULL) + return (0); + /* + * Make sure it is big enough. + */ + mylen = src_len - (CHKSUM_LEN + IV_LEN); + if (dst_len < mylen) { + fprintf(stderr, + "Rotate: ENOMEM ERROR: dst_len %zu src_len %zu\n", + dst_len, src_len); + return (ENOMEM); + } + + /* + * !!! Most implementations would verify any needed + * checksum and initialize the IV here. + */ + /* + * Copy the encrypted data to the destination buffer and then + * decrypt the destination buffer in place. + */ + i = CHKSUM_LEN + IV_LEN; + memcpy(&dst[0], &src[i], mylen); + /* + * Call common rotate function on the text portion of the + * buffer. Send in dst_len as the length of the text. + */ + /* + * !!! Most implementations would need the IV too. + */ + do_rotate((char *)dst, mylen, 26 - my_crypto->rot_N); + *result_lenp = mylen; + return (0); +} + +/* + * rotate_encrypt -- + * A simple rotate encryption. + */ +static int +rotate_encrypt(WT_ENCRYPTOR *encryptor, WT_SESSION *session, + uint8_t *src, size_t src_len, + uint8_t *dst, size_t dst_len, + size_t *result_lenp) +{ + MY_CRYPTO *my_crypto = (MY_CRYPTO *)encryptor; + uint32_t i; + + (void)session; /* Unused */ + ++my_crypto->num_calls; + + if (src == NULL) + return (0); + if (dst_len < src_len + CHKSUM_LEN + IV_LEN) + return (ENOMEM); + + i = CHKSUM_LEN + IV_LEN; + /* + * Skip over space reserved for checksum and initialization + * vector. Copy text into destination buffer then encrypt + * in place. + */ + memcpy(&dst[i], &src[0], src_len); + /* + * Call common rotate function on the text portion of the + * destination buffer. Send in src_len as the length of + * the text. + */ + do_rotate((char *)dst + i, src_len, my_crypto->rot_N); + /* + * Checksum the encrypted buffer and add the IV. + */ + i = 0; + make_cksum(&dst[i]); + i += CHKSUM_LEN; + make_iv(&dst[i]); + *result_lenp = dst_len; + return (0); +} + +/* + * rotate_sizing -- + * A sizing example that returns the header size needed. + */ +static int +rotate_sizing(WT_ENCRYPTOR *encryptor, WT_SESSION *session, + size_t *expansion_constantp) +{ + MY_CRYPTO *my_crypto = (MY_CRYPTO *)encryptor; + + (void)session; /* Unused parameters */ + + ++my_crypto->num_calls; /* Call count */ + + *expansion_constantp = CHKSUM_LEN + IV_LEN; + return (0); +} + +/* + * rotate_customize -- + * The customize function creates a customized encryptor + */ +static int +rotate_customize(WT_ENCRYPTOR *encryptor, WT_SESSION *session, + WT_CONFIG_ARG *encrypt_config, WT_ENCRYPTOR **customp) +{ + MY_CRYPTO *my_crypto; + WT_CONFIG_ITEM keyid, secret; + WT_EXTENSION_API *extapi; + int ret; + const MY_CRYPTO *orig_crypto; + + extapi = session->connection->get_extension_api(session->connection); + + orig_crypto = (const MY_CRYPTO *)encryptor; + if ((my_crypto = calloc(1, sizeof(MY_CRYPTO))) == NULL) { + ret = errno; + goto err; + } + *my_crypto = *orig_crypto; + my_crypto->keyid = my_crypto->password = NULL; + + /* + * Stash the keyid and the (optional) secret key + * from the configuration string. + */ + if ((ret = extapi->config_get(extapi, session, encrypt_config, + "keyid", &keyid)) == 0 && keyid.len != 0) { + if ((my_crypto->keyid = malloc(keyid.len + 1)) == NULL) { + ret = errno; + goto err; + } + strncpy(my_crypto->keyid, keyid.str, keyid.len + 1); + my_crypto->keyid[keyid.len] = '\0'; + } + + if ((ret = extapi->config_get(extapi, session, encrypt_config, + "secretkey", &secret)) == 0 && secret.len != 0) { + if ((my_crypto->password = malloc(secret.len + 1)) == NULL) { + ret = errno; + goto err; + } + strncpy(my_crypto->password, secret.str, secret.len + 1); + my_crypto->password[secret.len] = '\0'; + } + /* + * Presumably we'd have some sophisticated key management + * here that maps the id onto a secret key. + */ + if (ITEM_MATCHES(keyid, "system")) { + if (my_crypto->password == NULL || + strcmp(my_crypto->password, SYS_PW) != 0) { + ret = EPERM; + goto err; + } + my_crypto->rot_N = 13; + } else if (ITEM_MATCHES(keyid, USER1_KEYID)) + my_crypto->rot_N = 4; + else if (ITEM_MATCHES(keyid, USER2_KEYID)) + my_crypto->rot_N = 19; + else { + ret = EINVAL; + goto err; + } + + ++my_crypto->num_calls; /* Call count */ + + *customp = (WT_ENCRYPTOR *)my_crypto; + return (0); + +err: free(my_crypto->keyid); + free(my_crypto->password); + free(my_crypto); + return (ret); +} + +/* + * rotate_terminate -- + * WiredTiger rotate encryption termination. + */ +static int +rotate_terminate(WT_ENCRYPTOR *encryptor, WT_SESSION *session) +{ + MY_CRYPTO *my_crypto = (MY_CRYPTO *)encryptor; + + (void)session; /* Unused parameters */ + + ++my_crypto->num_calls; /* Call count */ + + /* Free the allocated memory. */ + free(my_crypto->password); + my_crypto->password = NULL; + + free(my_crypto->keyid); + my_crypto->keyid = NULL; + + free(encryptor); + + return (0); +} + +/* + * add_my_encryptors -- + * A simple example of adding encryption callbacks. + */ +int +add_my_encryptors(WT_CONNECTION *connection) +{ + MY_CRYPTO *m; + WT_ENCRYPTOR *wt; + int ret; + + /* + * Initialize our top level encryptor. + */ + if ((m = calloc(1, sizeof(MY_CRYPTO))) == NULL) + return (errno); + wt = (WT_ENCRYPTOR *)&m->encryptor; + wt->encrypt = rotate_encrypt; + wt->decrypt = rotate_decrypt; + wt->sizing = rotate_sizing; + wt->customize = rotate_customize; + wt->terminate = rotate_terminate; + m->num_calls = 0; + if ((ret = connection->add_encryptor( + connection, "rotn", (WT_ENCRYPTOR *)m, NULL)) != 0) + return (ret); + + return (0); +} + +/* + * simple_walk_log -- + * A simple walk of the write-ahead log. + * We wrote text messages into the log. Print them. + * This verifies we're decrypting properly. + */ +static int +simple_walk_log(WT_SESSION *session) +{ + WT_CURSOR *cursor; + WT_ITEM logrec_key, logrec_value; + WT_LSN lsn; + uint64_t txnid; + uint32_t fileid, opcount, optype, rectype; + int found, ret; + + ret = session->open_cursor(session, "log:", NULL, NULL, &cursor); + + found = 0; + while ((ret = cursor->next(cursor)) == 0) { + ret = cursor->get_key(cursor, &lsn.file, &lsn.offset, &opcount); + ret = cursor->get_value(cursor, &txnid, + &rectype, &optype, &fileid, &logrec_key, &logrec_value); + + if (rectype == WT_LOGREC_MESSAGE) { + found = 1; + printf("Application Log Record: %s\n", + (char *)logrec_value.data); + } + } + if (ret == WT_NOTFOUND) + ret = 0; + ret = cursor->close(cursor); + if (found == 0) { + fprintf(stderr, "Did not find log messages.\n"); + exit(EXIT_FAILURE); + } + return (ret); +} + +#define MAX_KEYS 20 + +#define EXTENSION_NAME "local=(entry=add_my_encryptors)" + +#define WT_OPEN_CONFIG_COMMON \ + "create,cache_size=100MB,extensions=[" EXTENSION_NAME "],"\ + "log=(archive=false,enabled=true)," \ + +#define WT_OPEN_CONFIG_GOOD \ + WT_OPEN_CONFIG_COMMON \ + "encryption=(name=rotn,keyid=" SYS_KEYID ",secretkey=" SYS_PW ")" + +#define COMP_A "AAAAAAAAAAAAAAAAAA" +#define COMP_B "BBBBBBBBBBBBBBBBBB" +#define COMP_C "CCCCCCCCCCCCCCCCCC" + +int +main(void) +{ + WT_CONNECTION *conn; + WT_CURSOR *c1, *c2, *nc; + WT_SESSION *session; + int i, ret; + char keybuf[16], valbuf[16]; + char *key1, *key2, *key3, *val1, *val2, *val3; + + /* + * Create a clean test directory for this run of the test program if the + * environment variable isn't already set (as is done by make check). + */ + if (getenv("WIREDTIGER_HOME") == NULL) { + home = "WT_HOME"; + ret = system("rm -rf WT_HOME && mkdir WT_HOME"); + } else + home = NULL; + + ret = wiredtiger_open(home, NULL, WT_OPEN_CONFIG_GOOD, &conn); + + ret = conn->open_session(conn, NULL, NULL, &session); + + /* + * Write a log record that is larger than the base 128 bytes and + * also should compress well. + */ + ret = session->log_printf(session, + COMP_A COMP_B COMP_C COMP_A COMP_B COMP_C + COMP_A COMP_B COMP_C COMP_A COMP_B COMP_C + "The quick brown fox jumps over the lazy dog "); + ret = simple_walk_log(session); + + /* + * Create and open some encrypted and not encrypted tables. + * Also use column store and compression for some tables. + */ + ret = session->create(session, "table:crypto1", + "encryption=(name=rotn,keyid=" USER1_KEYID")," + "columns=(key0,value0)," + "key_format=S,value_format=S"); + ret = session->create(session, "index:crypto1:byvalue", + "encryption=(name=rotn,keyid=" USER1_KEYID")," + "columns=(value0,key0)"); + ret = session->create(session, "table:crypto2", + "encryption=(name=rotn,keyid=" USER2_KEYID")," + "key_format=S,value_format=S"); + ret = session->create(session, "table:nocrypto", + "key_format=S,value_format=S"); + + /* + * Send in an unknown keyid. WiredTiger will try to add in the + * new keyid, but the customize function above will return an + * error since it is unrecognized. + */ + ret = session->create(session, "table:cryptobad", + "encryption=(name=rotn,keyid=" USERBAD_KEYID")," + "key_format=S,value_format=S"); + if (ret == 0) { + fprintf(stderr, "Did not detect bad/unknown keyid error\n"); + exit(EXIT_FAILURE); + } + + ret = session->open_cursor(session, "table:crypto1", NULL, NULL, &c1); + ret = session->open_cursor(session, "table:crypto2", NULL, NULL, &c2); + ret = session->open_cursor(session, "table:nocrypto", NULL, NULL, &nc); + + /* + * Insert a set of keys and values. Insert the same data into + * all tables so that we can verify they're all the same after + * we decrypt on read. + */ + for (i = 0; i < MAX_KEYS; i++) { + snprintf(keybuf, sizeof(keybuf), "key%d", i); + c1->set_key(c1, keybuf); + c2->set_key(c2, keybuf); + nc->set_key(nc, keybuf); + + snprintf(valbuf, sizeof(valbuf), "value%d", i); + c1->set_value(c1, valbuf); + c2->set_value(c2, valbuf); + nc->set_value(nc, valbuf); + + ret = c1->insert(c1); + ret = c2->insert(c2); + ret = nc->insert(nc); + if (i % 5 == 0) + ret = session->log_printf(session, + "Wrote %d records", i); + } + ret = session->log_printf(session, "Done. Wrote %d total records", i); + + while (c1->next(c1) == 0) { + ret = c1->get_key(c1, &key1); + ret = c1->get_value(c1, &val1); + + printf("Read key %s; value %s\n", key1, val1); + } + ret = simple_walk_log(session); + printf("CLOSE\n"); + ret = conn->close(conn, NULL); + + /* + * We want to close and reopen so that we recreate the cache + * by reading the data from disk, forcing decryption. + */ + printf("REOPEN and VERIFY encrypted data\n"); + + ret = wiredtiger_open(home, NULL, WT_OPEN_CONFIG_GOOD, &conn); + + ret = conn->open_session(conn, NULL, NULL, &session); + /* + * Verify we can read the encrypted log after restart. + */ + ret = simple_walk_log(session); + ret = session->open_cursor(session, "table:crypto1", NULL, NULL, &c1); + ret = session->open_cursor(session, "table:crypto2", NULL, NULL, &c2); + ret = session->open_cursor(session, "table:nocrypto", NULL, NULL, &nc); + + /* + * Read the same data from each cursor. All should be identical. + */ + while (c1->next(c1) == 0) { + ret = c2->next(c2); + ret = nc->next(nc); + ret = c1->get_key(c1, &key1); + ret = c1->get_value(c1, &val1); + ret = c2->get_key(c2, &key2); + ret = c2->get_value(c2, &val2); + ret = nc->get_key(nc, &key3); + ret = nc->get_value(nc, &val3); + + if (strcmp(key1, key2) != 0) + fprintf(stderr, "Key1 %s and Key2 %s do not match\n", + key1, key2); + if (strcmp(key1, key3) != 0) + fprintf(stderr, "Key1 %s and Key3 %s do not match\n", + key1, key3); + if (strcmp(key2, key3) != 0) + fprintf(stderr, "Key2 %s and Key3 %s do not match\n", + key2, key3); + if (strcmp(val1, val2) != 0) + fprintf(stderr, "Val1 %s and Val2 %s do not match\n", + val1, val2); + if (strcmp(val1, val3) != 0) + fprintf(stderr, "Val1 %s and Val3 %s do not match\n", + val1, val3); + if (strcmp(val2, val3) != 0) + fprintf(stderr, "Val2 %s and Val3 %s do not match\n", + val2, val3); + + printf("Verified key %s; value %s\n", key1, val1); + } + ret = conn->close(conn, NULL); + return (ret); +} |