From 818d00b4cceab93949aec208c8555aa8c409a0f2 Mon Sep 17 00:00:00 2001 From: Michael Vrable Date: Sun, 17 Oct 2010 13:40:20 -0700 Subject: [PATCH] Add per-item encryption/authentication to the cloud log storage. We should generate encrypted data and decrypt it again on read, but we don't yet enforce only reading data which passes the integrity check. --- bluesky/bluesky-private.h | 9 +++ bluesky/cloudlog.c | 49 +++++++++++++++ bluesky/crypto.c | 125 +++++++++++++++++++++++++++++++++----- bluesky/imap.c | 3 + bluesky/log.c | 3 + 5 files changed, 175 insertions(+), 14 deletions(-) diff --git a/bluesky/bluesky-private.h b/bluesky/bluesky-private.h index 74b4e06..dfcb7c5 100644 --- a/bluesky/bluesky-private.h +++ b/bluesky/bluesky-private.h @@ -75,6 +75,14 @@ void bluesky_crypt_derive_keys(BlueSkyCryptKeys *keys, const gchar *master); BlueSkyRCStr *bluesky_crypt_encrypt(BlueSkyRCStr *in, const uint8_t *key); BlueSkyRCStr *bluesky_crypt_decrypt(BlueSkyRCStr *in, const uint8_t *key); +void bluesky_crypt_block_encrypt(gchar *cloud_block, size_t len, + BlueSkyCryptKeys *keys); +gboolean bluesky_crypt_block_decrypt(gchar *cloud_block, size_t len, + BlueSkyCryptKeys *keys); +void bluesky_cloudlog_encrypt(GString *segment, BlueSkyCryptKeys *keys); +void bluesky_cloudlog_decrypt(char *segment, size_t len, + BlueSkyCryptKeys *keys); + /* Storage layer. Requests can be performed asynchronously, so these objects * help keep track of operations in progress. */ typedef enum { @@ -240,6 +248,7 @@ struct cloudlog_header { #define JOURNAL_MAGIC "\nLog" #define CLOUDLOG_MAGIC "AgI-" +#define CLOUDLOG_MAGIC_ENCRYPTED "AgI=" // CLOUDLOG_MAGIC[3] ^= 0x10 /* A record which tracks an object which has been written to a local log, * cached, locally, and/or written to the cloud. */ diff --git a/bluesky/cloudlog.c b/bluesky/cloudlog.c index ec168e8..142104f 100644 --- a/bluesky/cloudlog.c +++ b/bluesky/cloudlog.c @@ -463,6 +463,7 @@ void bluesky_cloudlog_flush(BlueSkyFS *fs) g_print("Serializing %zd bytes of data to cloud\n", state->data->len); SerializedRecord *record = g_new0(SerializedRecord, 1); + bluesky_cloudlog_encrypt(state->data, fs->keys); record->data = bluesky_string_new_from_gstring(state->data); record->items = state->writeback_list; record->lock = g_mutex_new(); @@ -488,3 +489,51 @@ void bluesky_cloudlog_flush(BlueSkyFS *fs) state->location.offset = 0; state->data = g_string_new(""); } + +/* Make an encryption pass over a cloud log segment to encrypt private data in + * it. */ +void bluesky_cloudlog_encrypt(GString *segment, BlueSkyCryptKeys *keys) +{ + char *data = segment->str; + size_t remaining_size = segment->len; + + while (remaining_size >= sizeof(struct cloudlog_header)) { + struct cloudlog_header *header = (struct cloudlog_header *)data; + size_t item_size = sizeof(struct cloudlog_header) + + GUINT32_FROM_LE(header->size1) + + GUINT32_FROM_LE(header->size2) + + GUINT32_FROM_LE(header->size3); + if (item_size > remaining_size) + break; + bluesky_crypt_block_encrypt(data, item_size, keys); + + data += item_size; + remaining_size -= item_size; + } +} + +/* Make an decryption pass over a cloud log segment to decrypt items which were + * encrypted. TODO: Also computes a list of all offsets which at which valid + * cloud log items are found. */ +void bluesky_cloudlog_decrypt(char *segment, size_t len, BlueSkyCryptKeys *keys) +{ + char *data = segment; + size_t remaining_size = len; + + while (remaining_size >= sizeof(struct cloudlog_header)) { + struct cloudlog_header *header = (struct cloudlog_header *)data; + size_t item_size = sizeof(struct cloudlog_header) + + GUINT32_FROM_LE(header->size1) + + GUINT32_FROM_LE(header->size2) + + GUINT32_FROM_LE(header->size3); + if (item_size > remaining_size) + break; + if (bluesky_crypt_block_decrypt(data, item_size, keys)) { + g_print("Decrypted valid cloud log item at offset %zd\n", + data - segment); + } + + data += item_size; + remaining_size -= item_size; + } +} diff --git a/bluesky/crypto.c b/bluesky/crypto.c index 13e19c1..1dc5e0d 100644 --- a/bluesky/crypto.c +++ b/bluesky/crypto.c @@ -109,6 +109,26 @@ void bluesky_crypt_derive_keys(BlueSkyCryptKeys *keys, const gchar *master) memcpy(keys->authentication_key, outbuf, sizeof(keys->authentication_key)); } +/* A boolean: are blocks of the specified type encrypted in the BlueSky file + * system? */ +gboolean bluesky_crypt_block_needs_encryption(uint8_t type) +{ + type -= '0'; + + switch (type) { + case LOGTYPE_DATA: + case LOGTYPE_INODE: + return TRUE; + case LOGTYPE_INODE_MAP: + case LOGTYPE_CHECKPOINT: + return FALSE; + default: + g_warning("Unknown log item type in crypto layer: %d!\n", + type); + return TRUE; + } +} + void bluesky_crypt_block_encrypt(gchar *cloud_block, size_t len, BlueSkyCryptKeys *keys) { @@ -131,27 +151,104 @@ void bluesky_crypt_block_encrypt(gchar *cloud_block, size_t len, gcry_strerror(status)); } - bluesky_crypt_random_bytes(header->crypt_iv, sizeof(header->crypt_iv)); - status = gcry_cipher_setctr(handle, header->crypt_iv, - sizeof(header->crypt_iv)); - if (status) { - g_error("gcrypt error setting IV: %s\n", - gcry_strerror(status)); + gboolean encrypted = bluesky_crypt_block_needs_encryption(header->type); + + if (encrypted) { + header->magic[3] ^= 0x10; + + bluesky_crypt_random_bytes(header->crypt_iv, sizeof(header->crypt_iv)); + status = gcry_cipher_setctr(handle, header->crypt_iv, + sizeof(header->crypt_iv)); + if (status) { + g_error("gcrypt error setting IV: %s\n", + gcry_strerror(status)); + } + + status = gcry_cipher_encrypt(handle, + cloud_block + sizeof(struct cloudlog_header), + GUINT32_FROM_LE(header->size1), + NULL, 0); + if (status) { + g_error("gcrypt error encrypting: %s\n", + gcry_strerror(status)); + } + } else { + memset(header->crypt_iv, 0, sizeof(header->crypt_iv)); } - status = gcry_cipher_encrypt(handle, - cloud_block + sizeof(struct cloudlog_header), - GUINT32_FROM_LE(header->size1), - NULL, 0); - if (status) { - g_error("gcrypt error encrypting: %s\n", - gcry_strerror(status)); + bluesky_crypt_hmac((char *)&header->crypt_iv, + cloud_block + len - (char *)&header->crypt_iv, + keys->authentication_key, + header->crypt_auth); + + gcry_cipher_close(handle); +} + +gboolean bluesky_crypt_block_decrypt(gchar *cloud_block, size_t len, + BlueSkyCryptKeys *keys) +{ + gcry_error_t status; + uint8_t hmac_check[CRYPTO_HASH_SIZE]; + + gboolean encrypted = TRUE; + + struct cloudlog_header *header = (struct cloudlog_header *)cloud_block; + if (memcmp(header->magic, CLOUDLOG_MAGIC, + sizeof(header->magic)) == 0) + encrypted = FALSE; + else + g_assert(memcmp(header->magic, CLOUDLOG_MAGIC_ENCRYPTED, + sizeof(header->magic)) == 0); + + if (encrypted != bluesky_crypt_block_needs_encryption(header->type)) { + g_warning("Encrypted status of item does not match expected!\n"); } bluesky_crypt_hmac((char *)&header->crypt_iv, cloud_block + len - (char *)&header->crypt_iv, keys->authentication_key, - header->crypt_auth); + hmac_check); + if (memcmp(hmac_check, header->crypt_auth, CRYPTO_HASH_SIZE) != 0) { + g_warning("Cloud block HMAC does not match!\n"); + return FALSE; + } + + if (encrypted) { + gcry_cipher_hd_t handle; + status = gcry_cipher_open(&handle, GCRY_CIPHER_AES, + GCRY_CIPHER_MODE_CTR, 0); + if (status) { + g_error("gcrypt error setting up encryption: %s\n", + gcry_strerror(status)); + } + + gcry_cipher_setkey(handle, keys->encryption_key, CRYPTO_KEY_SIZE); + if (status) { + g_error("gcrypt error setting key: %s\n", + gcry_strerror(status)); + } + + status = gcry_cipher_setctr(handle, header->crypt_iv, + sizeof(header->crypt_iv)); + if (status) { + g_error("gcrypt error setting IV: %s\n", + gcry_strerror(status)); + } + + status = gcry_cipher_decrypt(handle, + cloud_block + sizeof(struct cloudlog_header), + GUINT32_FROM_LE(header->size1), + NULL, 0); + if (status) { + g_error("gcrypt error decrypting: %s\n", + gcry_strerror(status)); + } + header->magic[3] ^= 0x10; + + gcry_cipher_close(handle); + } + + return TRUE; } #if 0 diff --git a/bluesky/imap.c b/bluesky/imap.c index 4d22350..9036263 100644 --- a/bluesky/imap.c +++ b/bluesky/imap.c @@ -279,6 +279,9 @@ gboolean bluesky_checkpoint_load(BlueSkyFS *fs) return FALSE; } + last = bluesky_string_dup(last); + bluesky_cloudlog_decrypt(last->data, last->len, fs->keys); + /* Scan through the contents of the last log segment to find a checkpoint * record. We need to do a linear scan since at this point we don't have a * direct pointer; once we have the last commit record then all other data diff --git a/bluesky/log.c b/bluesky/log.c index ed13ac1..e5f0321 100644 --- a/bluesky/log.c +++ b/bluesky/log.c @@ -381,6 +381,9 @@ static void cloudlog_fetch_complete(BlueSkyStoreAsync *async, char *pathname = g_strdup_printf("%s/%s", cachefile->log->log_directory, cachefile->filename); + async->data = bluesky_string_dup(async->data); + bluesky_cloudlog_decrypt(async->data->data, async->data->len, + cachefile->fs->keys); if (!g_file_set_contents(pathname, async->data->data, async->data->len, NULL)) g_print("Error writing out fetched file to cache!\n"); -- 2.20.1