[epiphany/wip/ephy-sync: 93/116] API to retrieve the storage endpoint from the Token Server
- From: Gabriel - Cristian Ivascu <gabrielivascu src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [epiphany/wip/ephy-sync: 93/116] API to retrieve the storage endpoint from the Token Server
- Date: Thu, 28 Jul 2016 09:33:39 +0000 (UTC)
commit ce76757130f37ae2275a5be1de34bd862f94b610
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]