[epiphany/wip/ephy-sync] API to retrieve the storage endpoint from the Token Server



commit 3f1beea9e16f02872376c7d7fea2edeb92d9fec1
Author: Gabriel Ivascu <ivascu gabriel59 gmail com>
Date:   Wed Jul 20 16:13:27 2016 +0300

    API to retrieve the storage endpoint from the Token Server

 src/ephy-sync-crypto.c  |  117 +++++++++++++++++++--
 src/ephy-sync-crypto.h  |   11 ++-
 src/ephy-sync-service.c |  255 +++++++++++++++++++++++++++++++++++++++++++----
 src/ephy-sync-service.h |    2 +-
 4 files changed, 349 insertions(+), 36 deletions(-)
---
diff --git a/src/ephy-sync-crypto.c b/src/ephy-sync-crypto.c
index 50c1584..83bb6bb 100644
--- a/src/ephy-sync-crypto.c
+++ b/src/ephy-sync-crypto.c
@@ -143,16 +143,16 @@ ephy_sync_crypto_sync_keys_new (guint8 *kA,
 }
 
 static EphySyncCryptoRSAKeyPair *
-ephy_sync_crypto_rsa_key_pair_new (struct rsa_public_key  public_key,
-                                   struct rsa_private_key private_key)
+ephy_sync_crypto_rsa_key_pair_new (struct rsa_public_key  public,
+                                   struct rsa_private_key private)
 {
-  EphySyncCryptoRSAKeyPair *key_pair;
+  EphySyncCryptoRSAKeyPair *keypair;
 
-  key_pair = g_slice_new (EphySyncCryptoRSAKeyPair);
-  key_pair->public_key = public_key;
-  key_pair->private_key = private_key;
+  keypair = g_slice_new (EphySyncCryptoRSAKeyPair);
+  keypair->public = public;
+  keypair->private = private;
 
-  return key_pair;
+  return keypair;
 }
 
 void
@@ -241,14 +241,45 @@ ephy_sync_crypto_sync_keys_free (EphySyncCryptoSyncKeys *sync_keys)
 }
 
 void
-ephy_sync_crypto_rsa_key_pair_free (EphySyncCryptoRSAKeyPair *key_pair)
+ephy_sync_crypto_rsa_key_pair_free (EphySyncCryptoRSAKeyPair *keypair)
 {
-  g_return_if_fail (key_pair != NULL);
+  g_return_if_fail (keypair != NULL);
 
-  rsa_public_key_clear (&key_pair->public_key);
-  rsa_private_key_clear (&key_pair->private_key);
+  rsa_public_key_clear (&keypair->public);
+  rsa_private_key_clear (&keypair->private);
 
-  g_slice_free (EphySyncCryptoRSAKeyPair, key_pair);
+  g_slice_free (EphySyncCryptoRSAKeyPair, keypair);
+}
+
+static gchar *
+base64_urlsafe_strip (guint8 *data,
+                      gsize   data_length)
+{
+  gchar *encoded;
+  gchar *base64;
+  gsize start;
+  gssize end;
+
+  base64 = g_base64_encode (data, data_length);
+
+  start = 0;
+  while (start < strlen (base64) && base64[start] == '=')
+    start++;
+
+  end = strlen (base64) - 1;
+  while (end >= 0 && base64[end] == '=')
+    end--;
+
+  encoded = g_strndup (base64 + start, end - start + 1);
+
+  /* Replace '+' with '-' */
+  g_strcanon (encoded, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/", '-');
+  /* Replace '/' with '_' */
+  g_strcanon (encoded, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-", '_');
+
+  g_free (base64);
+
+  return encoded;
 }
 
 static guint8 *
@@ -825,3 +856,65 @@ ephy_sync_crypto_generate_rsa_key_pair (void)
 
   return ephy_sync_crypto_rsa_key_pair_new (public, private);
 }
+
+gchar *
+ephy_sync_crypto_create_assertion (const gchar              *certificate,
+                                   const gchar              *audience,
+                                   guint64                   duration,
+                                   EphySyncCryptoRSAKeyPair *keypair)
+{
+  struct sha256_ctx sha256;
+  mpz_t signature;
+  const gchar *header = "{\"alg\": \"RS256\"}";
+  gchar *body;
+  gchar *body_b64;
+  gchar *header_b64;
+  gchar *to_sign;
+  gchar *sig_b64 = NULL;
+  gchar *assertion = NULL;
+  guint8 *sig = NULL;
+  guint64 expires_at;
+  gsize expected_size;
+  gsize count;
+
+  expires_at = g_get_real_time () / 1000 + duration * 1000;
+  body = g_strdup_printf ("{\"exp\": %lu, \"aud\": \"%s\"}", expires_at, audience);
+  body_b64 = base64_urlsafe_strip ((guint8 *) body, strlen (body));
+  header_b64 = base64_urlsafe_strip ((guint8 *) header, strlen (header));
+  to_sign = g_strdup_printf ("%s.%s", header_b64, body_b64);
+
+  mpz_init (signature);
+  sha256_init (&sha256);
+  sha256_update (&sha256, strlen (to_sign), (guint8 *) to_sign);
+
+  if (rsa_sha256_sign_tr (&keypair->public, &keypair->private,
+                          NULL, random_func,
+                          &sha256, signature) == 0) {
+    g_warning ("Failed to sign the message. Giving up.");
+    goto out;
+  }
+
+  expected_size = (mpz_sizeinbase (signature, 2) + 7) / 8;
+  sig = g_malloc (expected_size);
+  mpz_export (sig, &count, 1, sizeof (guint8), 0, 0, signature);
+
+  if (count != expected_size) {
+    g_warning ("Expected %lu bytes, got %lu. Giving up.", count, expected_size);
+    goto out;
+  }
+
+  sig_b64 = base64_urlsafe_strip (sig, count);
+  assertion = g_strdup_printf ("%s~%s.%s.%s",
+                               certificate, header_b64, body_b64, sig_b64);
+
+out:
+  g_free (body);
+  g_free (body_b64);
+  g_free (header_b64);
+  g_free (to_sign);
+  g_free (sig_b64);
+  g_free (sig);
+  mpz_clear (signature);
+
+  return assertion;
+}
diff --git a/src/ephy-sync-crypto.h b/src/ephy-sync-crypto.h
index 545eaa2..a498415 100644
--- a/src/ephy-sync-crypto.h
+++ b/src/ephy-sync-crypto.h
@@ -74,8 +74,8 @@ typedef struct {
 } EphySyncCryptoSyncKeys;
 
 typedef struct {
-  struct rsa_public_key public_key;
-  struct rsa_private_key private_key;
+  struct rsa_public_key public;
+  struct rsa_private_key private;
 } EphySyncCryptoRSAKeyPair;
 
 EphySyncCryptoHawkOptions   *ephy_sync_crypto_hawk_options_new       (gchar *app,
@@ -98,7 +98,7 @@ void                        ephy_sync_crypto_processed_st_free       (EphySyncCr
 
 void                        ephy_sync_crypto_sync_keys_free          (EphySyncCryptoSyncKeys *sync_keys);
 
-void                        ephy_sync_crypto_rsa_key_pair_free       (EphySyncCryptoRSAKeyPair *key_pair);
+void                        ephy_sync_crypto_rsa_key_pair_free       (EphySyncCryptoRSAKeyPair *keypair);
 
 EphySyncCryptoProcessedKFT *ephy_sync_crypto_process_key_fetch_token (const gchar *keyFetchToken);
 
@@ -118,6 +118,11 @@ EphySyncCryptoHawkHeader   *ephy_sync_crypto_compute_hawk_header     (const gcha
 
 EphySyncCryptoRSAKeyPair   *ephy_sync_crypto_generate_rsa_key_pair   (void);
 
+gchar                      *ephy_sync_crypto_create_assertion        (const gchar              *certificate,
+                                                                      const gchar              *audience,
+                                                                      guint64                   duration,
+                                                                      EphySyncCryptoRSAKeyPair *keypair);
+
 G_END_DECLS
 
 #endif
diff --git a/src/ephy-sync-service.c b/src/ephy-sync-service.c
index d4b78c2..f3eabcf 100644
--- a/src/ephy-sync-service.c
+++ b/src/ephy-sync-service.c
@@ -29,9 +29,10 @@
 #include <libsoup/soup.h>
 #include <string.h>
 
-#define FXA_BASEURL   "https://api.accounts.firefox.com/";
-#define FXA_VERSION   "v1/"
-#define STATUS_OK     200
+#define TOKEN_SERVER_URL  "https://token.services.mozilla.com/1.0/sync/1.5";
+#define FXA_BASEURL       "https://api.accounts.firefox.com/";
+#define FXA_VERSION       "v1/"
+#define STATUS_OK         200
 
 struct _EphySyncService {
   GObject parent_instance;
@@ -39,20 +40,29 @@ struct _EphySyncService {
   SoupSession *soup_session;
   JsonParser *parser;
 
-  gchar *user_email;
   GHashTable *tokens;
+
+  gchar *user_email;
+  gint64 last_auth_at;
+
+  gchar *certificate;
+  gchar *storage_endpoint;
+  gchar *token_server_id;
+  gchar *token_server_key;
+
+  EphySyncCryptoRSAKeyPair *keypair;
 };
 
 G_DEFINE_TYPE (EphySyncService, ephy_sync_service, G_TYPE_OBJECT);
 
 static guint
 synchronous_hawk_post_request (EphySyncService  *self,
-                              const gchar      *endpoint,
-                              const gchar      *id,
-                              guint8           *key,
-                              gsize             key_length,
-                              gchar            *request_body,
-                              JsonObject      **jobject)
+                              const gchar       *endpoint,
+                              const gchar       *id,
+                              guint8            *key,
+                              gsize              key_length,
+                              gchar             *request_body,
+                              JsonObject       **jobject)
 {
   EphySyncCryptoHawkOptions *hawk_options;
   EphySyncCryptoHawkHeader *hawk_header;
@@ -164,15 +174,211 @@ session_destroyed_cb (SoupSession *session,
   g_object_unref (parser);
 }
 
+static gchar *
+get_audience_for_url (const gchar *url)
+{
+  SoupURI *uri;
+  const gchar *scheme;
+  const gchar *host;
+  gchar *port_str;
+  guint port;
+  gchar *audience;
+
+  uri = soup_uri_new (url);
+
+  if (uri == NULL) {
+    g_warning ("Failed to build SoupURI, invalid url: %s", url);
+    return NULL;
+  }
+
+  scheme = soup_uri_get_scheme (uri);
+  host = soup_uri_get_host (uri);
+  port = soup_uri_get_port (uri);
+  port_str = g_strdup_printf (":%u", port);
+
+  /* Even if the url doesn't contain the port, soup_uri_get_port() will return
+   * the default port for the url's scheme so we need to check if the port was
+   * really present in the url.
+   */
+  if (g_strstr_len (url, -1, port_str) != NULL)
+    audience = g_strdup_printf ("%s://%s:%u", scheme, host, port);
+  else
+    audience = g_strdup_printf ("%s://%s", scheme, host);
+
+  soup_uri_free (uri);
+  g_free (port_str);
+
+  return audience;
+}
+
+static guchar *
+base64_parse (const gchar *string,
+              gsize       *out_len)
+{
+  gchar *suffix;
+  gchar *full;
+  guchar *decoded;
+  gsize len;
+
+  len = ((4 - strlen (string) % 4) % 4);
+  suffix = g_strnfill (len, '=');
+  full = g_strconcat (string, suffix, NULL);
+  decoded = g_base64_decode (full, out_len);
+
+  g_free (suffix);
+  g_free (full);
+
+  return decoded;
+}
+
+static gboolean
+check_certificate (EphySyncService *self,
+                   const gchar     *certificate)
+{
+  JsonParser *parser;
+  JsonNode *root;
+  JsonObject *object;
+  JsonObject *principal;
+  gchar **pieces;
+  gchar *header;
+  gchar *payload;
+  gchar *uid_email;
+  const gchar *algorithm;
+  const gchar *email;
+  gsize header_len;
+  gsize payload_len;
+  gboolean retval = FALSE;
+
+  g_return_val_if_fail (certificate != NULL, FALSE);
+
+  pieces = g_strsplit (certificate, ".", 0);
+  header = (gchar *) base64_parse (pieces[0], &header_len);
+  payload = (gchar *) base64_parse (pieces[1], &payload_len);
+
+  parser = json_parser_new ();
+  json_parser_load_from_data (parser, header, -1, NULL);
+  root = json_parser_get_root (parser);
+  object = json_node_get_object (root);
+  algorithm = json_object_get_string_member (object, "alg");
+
+  if (g_str_equal (algorithm, "RS256") == FALSE) {
+    g_warning ("Expected algorithm RS256, found %s. Giving up.", algorithm);
+    goto out;
+  }
+
+  json_parser_load_from_data (parser, payload, -1, NULL);
+  root = json_parser_get_root (parser);
+  object = json_node_get_object (root);
+  principal = json_object_get_object_member (object, "principal");
+  email = json_object_get_string_member (principal, "email");
+  uid_email = g_strdup_printf ("%s@%s",
+                               ephy_sync_service_get_token (self, EPHY_SYNC_TOKEN_UID),
+                               soup_uri_get_host (soup_uri_new (FXA_BASEURL)));
+
+  if (g_str_equal (uid_email, email) == FALSE) {
+    g_warning ("Expected email %s, found %s. Giving up.", uid_email, email);
+    goto out;
+  }
+
+  self->last_auth_at = json_object_get_int_member (object, "fxa-lastAuthAt");
+  retval = TRUE;
+
+out:
+  g_free (header);
+  g_free (payload);
+  g_free (uid_email);
+  g_strfreev (pieces);
+  g_object_unref (parser);
+
+  return retval;
+}
+
+static gboolean
+query_token_server (EphySyncService *self,
+                    const gchar     *assertion)
+{
+  SoupMessage *message;
+  JsonParser *parser;
+  JsonNode *root;
+  JsonObject *object;
+  gchar *authorization;
+  gboolean retval = FALSE;
+
+  g_return_val_if_fail (assertion != NULL, FALSE);
+
+  message = soup_message_new (SOUP_METHOD_GET, TOKEN_SERVER_URL);
+  authorization = g_strdup_printf ("BrowserID %s", assertion);
+  soup_message_headers_append (message->request_headers,
+                               "authorization", authorization);
+  soup_session_send_message (self->soup_session, message);
+
+  parser = json_parser_new ();
+  json_parser_load_from_data (parser,
+                              message->response_body->data,
+                              -1, NULL);
+  root = json_parser_get_root (parser);
+  object = json_node_get_object (root);
+
+  if (message->status_code == STATUS_OK) {
+    self->storage_endpoint = g_strdup (json_object_get_string_member (object, "api_endpoint"));
+    self->token_server_id = g_strdup (json_object_get_string_member (object, "id"));
+    self->token_server_key = g_strdup (json_object_get_string_member (object, "key"));
+  } else if (message->status_code == 400) {
+    g_warning ("Failed to talk to the Token Server: malformed request");
+    goto out;
+  } else if (message->status_code == 401) {
+    JsonArray *array;
+    JsonNode *node;
+    JsonObject *errors;
+    const gchar *status;
+    const gchar *description;
+
+    status = json_object_get_string_member (object, "status");
+    array = json_object_get_array_member (object, "errors");
+    node = json_array_get_element (array, 0);
+    errors = json_node_get_object (node);
+    description = json_object_get_string_member (errors, "description");
+
+    g_warning ("Failed to talk to the Token Server: %s: %s",
+               status, description);
+    goto out;
+  } else if (message->status_code == 404) {
+    g_warning ("Failed to talk to the Token Server: unknown URL");
+    goto out;
+  } else if (message->status_code == 405) {
+    g_warning ("Failed to talk to the Token Server: unsupported method");
+    goto out;
+  } else if (message->status_code == 406) {
+    g_warning ("Failed to talk to the Token Server: unacceptable");
+    goto out;
+  } else if (message->status_code == 503) {
+    g_warning ("Failed to talk to the Token Server: service unavailable");
+    goto out;
+  }
+
+  retval = TRUE;
+
+out:
+  g_free (authorization);
+  g_object_unref (parser);
+
+  return retval;
+}
+
 static void
 ephy_sync_service_finalize (GObject *object)
 {
   EphySyncService *self = EPHY_SYNC_SERVICE (object);
 
   g_free (self->user_email);
+  g_free (self->certificate);
+  g_free (self->storage_endpoint);
+  g_free (self->token_server_id);
+  g_free (self->token_server_key);
   g_hash_table_destroy (self->tokens);
   g_clear_object (&self->soup_session);
   g_clear_object (&self->parser);
+  ephy_sync_crypto_rsa_key_pair_free (self->keypair);
 
   G_OBJECT_CLASS (ephy_sync_service_parent_class)->finalize (object);
 }
@@ -396,11 +602,11 @@ out:
   return retval;
 }
 
-const gchar *
+gboolean
 ephy_sync_service_sign_certificate (EphySyncService *self)
 {
   EphySyncCryptoProcessedST *processed_st;
-  EphySyncCryptoRSAKeyPair *kpair;
+  EphySyncCryptoRSAKeyPair *keypair;
   JsonObject *jobject;
   gchar *sessionToken;
   gchar *tokenID;
@@ -408,20 +614,21 @@ ephy_sync_service_sign_certificate (EphySyncService *self)
   gchar *request_body;
   gchar *n_str;
   gchar *e_str;
+  const gchar *certificate;
   guint status_code;
-  const gchar *certificate = NULL;
+  gboolean retval = FALSE;
 
   sessionToken = ephy_sync_service_get_token (self, EPHY_SYNC_TOKEN_SESSIONTOKEN);
-  g_return_val_if_fail (sessionToken != NULL, NULL);
+  g_return_val_if_fail (sessionToken != NULL, FALSE);
 
-  kpair = ephy_sync_crypto_generate_rsa_key_pair ();
-  g_return_val_if_fail (kpair != NULL, NULL);
+  keypair = ephy_sync_crypto_generate_rsa_key_pair ();
+  g_return_val_if_fail (keypair != NULL, FALSE);
 
   processed_st = ephy_sync_crypto_process_session_token (sessionToken);
   tokenID = ephy_sync_utils_encode_hex (processed_st->tokenID, 0);
 
-  n_str = mpz_get_str (NULL, 10, kpair->public_key.n);
-  e_str = mpz_get_str (NULL, 10, kpair->public_key.e);
+  n_str = mpz_get_str (NULL, 10, keypair->public.n);
+  e_str = mpz_get_str (NULL, 10, keypair->public.e);
   public_key_json = ephy_sync_utils_build_json_string ("algorithm", "RS",
                                                        "n", n_str,
                                                        "e", e_str,
@@ -444,16 +651,24 @@ ephy_sync_service_sign_certificate (EphySyncService *self)
     goto out;
   }
 
+  /* Check if the certificate is valid */
   certificate = json_object_get_string_member (jobject, "cert");
+  if (check_certificate (self, certificate) == FALSE) {
+    ephy_sync_crypto_rsa_key_pair_free (keypair);
+    goto out;
+  }
+
+  self->certificate = g_strdup (certificate);
+  self->keypair = keypair;
+  retval = TRUE;
 
 out:
   ephy_sync_crypto_processed_st_free (processed_st);
-  ephy_sync_crypto_rsa_key_pair_free (kpair);
   g_free (tokenID);
   g_free (public_key_json);
   g_free (request_body);
   g_free (n_str);
   g_free (e_str);
 
-  return certificate;
+  return retval;
 }
diff --git a/src/ephy-sync-service.h b/src/ephy-sync-service.h
index 9e78a4d..680a3b5 100644
--- a/src/ephy-sync-service.h
+++ b/src/ephy-sync-service.h
@@ -58,7 +58,7 @@ gboolean         ephy_sync_service_fetch_sync_keys      (EphySyncService *self,
                                                          const gchar     *keyFetchToken,
                                                          const gchar     *unwrapBKey);
 
-const gchar     *ephy_sync_service_sign_certificate     (EphySyncService *self);
+gboolean         ephy_sync_service_sign_certificate     (EphySyncService *self);
 
 G_END_DECLS
 


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