[epiphany/wip/sync: 56/86] sync: Obtain the default sync keys at sign in



commit 92517c54e8ec6826f09b99483be87eee8ebc3a3d
Author: Gabriel Ivascu <ivascu gabriel59 gmail com>
Date:   Thu Mar 9 18:27:28 2017 +0200

    sync: Obtain the default sync keys at sign in

 src/sync/ephy-sync-crypto.c  |   45 +++++++++++++++
 src/sync/ephy-sync-crypto.h  |    6 ++
 src/sync/ephy-sync-secret.c  |    2 +
 src/sync/ephy-sync-service.c |  126 +++++++++++++++++++++++++++++++++++++++++-
 src/sync/ephy-sync-utils.c   |    8 +++
 src/sync/ephy-sync-utils.h   |    4 +-
 6 files changed, 189 insertions(+), 2 deletions(-)
---
diff --git a/src/sync/ephy-sync-crypto.c b/src/sync/ephy-sync-crypto.c
index fc16156..3984070 100644
--- a/src/sync/ephy-sync-crypto.c
+++ b/src/sync/ephy-sync-crypto.c
@@ -653,6 +653,51 @@ ephy_sync_crypto_derive_master_keys (const guint8  *kB,
   g_free (hmac_key_hex);
 }
 
+gboolean
+ephy_sync_crypto_sha256_hmac_is_valid (const char   *text,
+                                       const guint8 *key,
+                                       const char   *expected)
+{
+  char *hmac;
+  gboolean retval;
+
+  g_return_val_if_fail (text, FALSE);
+  g_return_val_if_fail (key, FALSE);
+  g_return_val_if_fail (expected, FALSE);
+
+  /* SHA256 expects a 32 bytes key. */
+  hmac = g_compute_hmac_for_string (G_CHECKSUM_SHA256, key, 32, text, -1);
+  retval = g_strcmp0 (hmac, expected) == 0;
+  g_free (hmac);
+
+  return retval;
+}
+
+char *
+ephy_sync_crypto_decrypt_record (const char   *ciphertext_b64,
+                                 const char   *iv_b64,
+                                 const guint8 *aes_key)
+{
+  char *decrypted;
+  guint8 *ciphertext;
+  guint8 *iv;
+  gsize ciphertext_len;
+  gsize iv_len;
+
+  g_return_val_if_fail (ciphertext_b64, NULL);
+  g_return_val_if_fail (iv_b64, NULL);
+  g_return_val_if_fail (aes_key, NULL);
+
+  ciphertext = g_base64_decode (ciphertext_b64, &ciphertext_len);
+  iv = g_base64_decode (iv_b64, &iv_len);
+  decrypted = ephy_sync_crypto_aes_256_decrypt (ciphertext, ciphertext_len, aes_key, iv);
+
+  g_free (ciphertext);
+  g_free (iv);
+
+  return decrypted;
+}
+
 SyncCryptoHawkHeader *
 ephy_sync_crypto_compute_hawk_header (const char            *url,
                                       const char            *method,
diff --git a/src/sync/ephy-sync-crypto.h b/src/sync/ephy-sync-crypto.h
index 4a02178..6f957a2 100644
--- a/src/sync/ephy-sync-crypto.h
+++ b/src/sync/ephy-sync-crypto.h
@@ -97,6 +97,12 @@ void                    ephy_sync_crypto_compute_sync_keys        (const char
 void                    ephy_sync_crypto_derive_master_keys       (const guint8           *kB,
                                                                    guint8                **aes_key,
                                                                    guint8                **hmac_key);
+gboolean                ephy_sync_crypto_sha256_hmac_is_valid     (const char             *text,
+                                                                   const guint8           *key,
+                                                                   const char             *expected);
+char                   *ephy_sync_crypto_decrypt_record           (const char             *ciphertext_b64,
+                                                                   const char             *iv_b64,
+                                                                   const guint8           *aes_key);
 SyncCryptoHawkHeader   *ephy_sync_crypto_compute_hawk_header      (const char             *url,
                                                                    const char             *method,
                                                                    const char             *id,
diff --git a/src/sync/ephy-sync-secret.c b/src/sync/ephy-sync-secret.c
index 2db6fde..a09af8b 100644
--- a/src/sync/ephy-sync-secret.c
+++ b/src/sync/ephy-sync-secret.c
@@ -224,6 +224,8 @@ ephy_sync_secret_store_tokens (EphySyncService *service)
                                               "unwrapBKey", ephy_sync_service_get_token (service, 
TOKEN_UNWRAPBKEY),
                                               "kA", ephy_sync_service_get_token (service, TOKEN_KA),
                                               "kB", ephy_sync_service_get_token (service, TOKEN_KB),
+                                              "defaultAESKey", ephy_sync_service_get_token (service, 
TOKEN_DEFAULT_AES_KEY),
+                                              "defaultHMACKey", ephy_sync_service_get_token (service, 
TOKEN_DEFAULT_HMAC_KEY),
                                               NULL);
   value = secret_value_new (tokens, -1, "text/plain");
   attributes = secret_attributes_build (EPHY_SYNC_TOKEN_SCHEMA, EMAIL_KEY,
diff --git a/src/sync/ephy-sync-service.c b/src/sync/ephy-sync-service.c
index c2c905f..fae70d7 100644
--- a/src/sync/ephy-sync-service.c
+++ b/src/sync/ephy-sync-service.c
@@ -55,6 +55,8 @@ struct _EphySyncService {
   char        *unwrapBKey;
   char        *kA;
   char        *kB;
+  char        *default_aes_key;
+  char        *default_hmac_key;
 
   char        *user_email;
   double       sync_time;
@@ -768,6 +770,10 @@ ephy_sync_service_get_token (EphySyncService   *self,
       return self->kA;
     case TOKEN_KB:
       return self->kB;
+    case TOKEN_DEFAULT_AES_KEY:
+      return self->default_aes_key;
+    case TOKEN_DEFAULT_HMAC_KEY:
+      return self->default_hmac_key;
     default:
       g_assert_not_reached ();
   }
@@ -806,6 +812,14 @@ ephy_sync_service_set_token (EphySyncService   *self,
       g_free (self->kB);
       self->kB = g_strdup (value);
       break;
+    case TOKEN_DEFAULT_AES_KEY:
+      g_free (self->default_aes_key);
+      self->default_aes_key = g_strdup (value);
+      break;
+    case TOKEN_DEFAULT_HMAC_KEY:
+      g_free (self->default_hmac_key);
+      self->default_hmac_key = g_strdup (value);
+      break;
     default:
       g_assert_not_reached ();
   }
@@ -834,6 +848,8 @@ ephy_sync_service_clear_tokens (EphySyncService *self)
   g_clear_pointer (&self->unwrapBKey, g_free);
   g_clear_pointer (&self->kA, g_free);
   g_clear_pointer (&self->kB, g_free);
+  g_clear_pointer (&self->default_aes_key, g_free);
+  g_clear_pointer (&self->default_hmac_key, g_free);
 }
 
 static void
@@ -908,6 +924,112 @@ ephy_sync_service_destroy_session (EphySyncService *self,
 }
 
 static void
+obtain_default_sync_keys_cb (SoupSession *session,
+                             SoupMessage *msg,
+                             gpointer     user_data)
+{
+  EphySyncService *service;
+  JsonParser *parser;
+  JsonObject *json;
+  JsonArray *array;
+  const char *ciphertext_b64;
+  const char *iv_b64;
+  const char *hmac;
+  char *payload;
+  char *record;
+  char *default_aes_key_hex;
+  char *default_hmac_key_hex;
+  guint8 *kB;
+  guint8 *aes_key;
+  guint8 *hmac_key;
+  guint8 *default_aes_key;
+  guint8 *default_hmac_key;
+  gsize len;
+
+  parser = json_parser_new ();
+  json_parser_load_from_data (parser, msg->response_body->data, -1, NULL);
+  json = json_node_get_object (json_parser_get_root (parser));
+
+  if (msg->status_code != 200) {
+    g_warning ("Failed to get crypto/keys record: errno: %ld, errmsg: %s",
+               json_object_get_int_member (json, "errno"),
+               json_object_get_string_member (json, "message"));
+    goto out;
+  }
+
+  service = ephy_shell_get_sync_service (ephy_shell_get_default ());
+  payload = g_strdup (json_object_get_string_member (json, "payload"));
+  json_parser_load_from_data (parser, payload, -1, NULL);
+  json = json_node_get_object (json_parser_get_root (parser));
+  ciphertext_b64 = json_object_get_string_member (json, "ciphertext");
+  iv_b64 = json_object_get_string_member (json, "IV");
+  hmac = json_object_get_string_member (json, "hmac");
+
+  /* Derive the Sync Key bundle from kB. The bundle consists of two 32 bytes keys:
+   * the first one used as a symmetric encryption key (AES) and the second one
+   * used as a HMAC key. */
+  kB = ephy_sync_crypto_decode_hex (ephy_sync_service_get_token (service, TOKEN_KB));
+  ephy_sync_crypto_derive_master_keys (kB, &aes_key, &hmac_key);
+
+  /* Under no circumstances should a client try to decrypt a record if the HMAC
+   * verification fails. If the verification is successful, proceed to decrypt
+   * the record and retrieve the default sync keys. Otherwise, signal the error
+   * to the user.*/
+  if (!ephy_sync_crypto_sha256_hmac_is_valid (ciphertext_b64, hmac_key, hmac)) {
+    g_signal_emit (service, signals[SIGN_IN_ERROR], 0, _("Failed to obtain the default sync keys"));
+    ephy_sync_service_destroy_session (service, NULL);
+    ephy_sync_service_set_user_email (service, NULL);
+    ephy_sync_service_clear_tokens (service);
+    g_settings_set_string (EPHY_SETTINGS_MAIN, EPHY_PREFS_SYNC_USER, "");
+    LOG ("Failed to verify the HMAC value of the crypto/keys record");
+  } else {
+    record = ephy_sync_crypto_decrypt_record (ciphertext_b64, iv_b64, aes_key);
+    json_parser_load_from_data (parser, record, -1, NULL);
+    json = json_node_get_object (json_parser_get_root (parser));
+    array = json_object_get_array_member (json, "default");
+
+    /* TODO: Handle the case when the decrypted record has a non-empty "collections" member.
+     * https://github.com/michielbdejong/fxsync-webcrypto/issues/19 */
+
+    default_aes_key = g_base64_decode (json_array_get_string_element (array, 0), &len);
+    default_hmac_key = g_base64_decode (json_array_get_string_element (array, 1), &len);
+    default_aes_key_hex = ephy_sync_crypto_encode_hex (default_aes_key, 0);
+    default_hmac_key_hex = ephy_sync_crypto_encode_hex (default_hmac_key, 0);
+    ephy_sync_service_set_token (service, default_aes_key_hex, TOKEN_DEFAULT_AES_KEY);
+    ephy_sync_service_set_token (service, default_hmac_key_hex, TOKEN_DEFAULT_HMAC_KEY);
+
+    /* Everything is OK, store the tokens in the secret schema. */
+    ephy_sync_secret_store_tokens (service);
+
+    g_free (record);
+    g_free (default_aes_key);
+    g_free (default_hmac_key);
+    g_free (default_aes_key_hex);
+    g_free (default_hmac_key_hex);
+  }
+
+  g_free (payload);
+  g_free (kB);
+  g_free (aes_key);
+  g_free (hmac_key);
+
+out:
+  g_object_unref (parser);
+
+  ephy_sync_service_send_next_storage_request (service);
+}
+
+static void
+ephy_sync_service_obtain_default_sync_keys (EphySyncService *self)
+{
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
+
+  ephy_sync_service_queue_storage_request (self, "storage/crypto/keys",
+                                           SOUP_METHOD_GET, NULL, -1, -1,
+                                           obtain_default_sync_keys_cb, NULL);
+}
+
+static void
 check_storage_version_cb (SoupSession *session,
                           SoupMessage *msg,
                           gpointer     user_data)
@@ -935,8 +1057,10 @@ check_storage_version_cb (SoupSession *session,
   json = json_node_get_object (json_parser_get_root (parser));
   storage_version = json_object_get_int_member (json, "storageVersion");
 
+  /* If the storage version is correct, proceed to obtain the default sync keys.
+   * Otherwise, signal the error to the user. */
   if (storage_version == STORAGE_VERSION) {
-    ephy_sync_secret_store_tokens (service);
+    ephy_sync_service_obtain_default_sync_keys (service);
   } else {
     /* Translators: the %d is the storage version, the \n is a newline character. */
     char *message = g_strdup_printf (_("Your Firefox Account uses a storage version "
diff --git a/src/sync/ephy-sync-utils.c b/src/sync/ephy-sync-utils.c
index 03e345a..496903d 100644
--- a/src/sync/ephy-sync-utils.c
+++ b/src/sync/ephy-sync-utils.c
@@ -107,6 +107,10 @@ ephy_sync_utils_token_name_from_type (EphySyncTokenType type)
       return "kA";
     case TOKEN_KB:
       return "kB";
+    case TOKEN_DEFAULT_AES_KEY:
+      return "defaultAESKey";
+    case TOKEN_DEFAULT_HMAC_KEY:
+      return "defaultHMACKey";
     default:
       g_assert_not_reached ();
   }
@@ -127,6 +131,10 @@ ephy_sync_utils_token_type_from_name (const char *name)
     return TOKEN_KA;
   } else if (!g_strcmp0 (name, "kB")) {
     return TOKEN_KB;
+  } else if (!g_strcmp0 (name, "defaultAESKey")) {
+    return TOKEN_DEFAULT_AES_KEY;
+  } else if (!g_strcmp0 (name, "defaultHMACKey")) {
+    return TOKEN_DEFAULT_HMAC_KEY;
   } else {
     g_assert_not_reached ();
   }
diff --git a/src/sync/ephy-sync-utils.h b/src/sync/ephy-sync-utils.h
index 4dd8d5d..0ae3f2f 100644
--- a/src/sync/ephy-sync-utils.h
+++ b/src/sync/ephy-sync-utils.h
@@ -28,7 +28,9 @@ typedef enum {
   TOKEN_KEYFETCHTOKEN,
   TOKEN_UNWRAPBKEY,
   TOKEN_KA,
-  TOKEN_KB
+  TOKEN_KB,
+  TOKEN_DEFAULT_AES_KEY,
+  TOKEN_DEFAULT_HMAC_KEY
 } EphySyncTokenType;
 
 G_BEGIN_DECLS


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]