Add per-item encryption/authentication to the cloud log storage.
authorMichael Vrable <mvrable@cs.ucsd.edu>
Sun, 17 Oct 2010 20:40:20 +0000 (13:40 -0700)
committerMichael Vrable <mvrable@cs.ucsd.edu>
Sun, 17 Oct 2010 20:40:20 +0000 (13:40 -0700)
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
bluesky/cloudlog.c
bluesky/crypto.c
bluesky/imap.c
bluesky/log.c

index 74b4e06..dfcb7c5 100644 (file)
@@ -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. */
index ec168e8..142104f 100644 (file)
@@ -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;
+    }
+}
index 13e19c1..1dc5e0d 100644 (file)
@@ -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
index 4d22350..9036263 100644 (file)
@@ -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
index ed13ac1..e5f0321 100644 (file)
@@ -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");