| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672 |
- /* iridium-standard-file.c
- *
- * Copyright 2018 Matthias Vogelgesang
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
- #include <string.h>
- #include <stdio.h>
- #include <json-glib/json-glib.h>
- #include <libsoup/soup.h>
- #include <nettle/aes.h>
- #include <nettle/cbc.h>
- #include <nettle/sha2.h>
- #include <nettle/hmac.h>
- #include <nettle/pbkdf2.h>
- #include "iridium-standard-file.h"
- #include "iridium-note.h"
- typedef enum {
- SF_VERSION_001,
- SF_VERSION_002,
- } StandardFileVersion;
- typedef enum {
- SF_FUNC_PBKDF2,
- } StandardFileFunc;
- typedef enum {
- SF_HASH_SHA512,
- } StandardFileHash;
- typedef struct {
- guint cost;
- guint key_size;
- gchar *salt;
- StandardFileFunc func;
- StandardFileFunc hash;
- StandardFileVersion version;
- struct {
- guint8 password[32];
- guint8 master[32];
- guint8 auth[32];
- } keys;
- } StandardFileAuthParams;
- typedef struct {
- IridiumStandardFile *client;
- gchar *password;
- } ReadAuthParams;
- struct _IridiumStandardFile
- {
- GObject parent_instance;
- gchar *email;
- gchar *token;
- SoupSession *session;
- SoupURI *base_uri;
- StandardFileAuthParams auth_params;
- };
- G_DEFINE_TYPE (IridiumStandardFile, iridium_standard_file, G_TYPE_OBJECT)
- const SecretSchema *
- standard_file_get_schema (void)
- {
- static const SecretSchema schema = {
- "net.bloerg.Iridium", SECRET_SCHEMA_NONE,
- {
- { "email", SECRET_SCHEMA_ATTRIBUTE_STRING },
- { "server", SECRET_SCHEMA_ATTRIBUTE_STRING },
- { NULL, 0 },
- }
- };
- return &schema;
- }
- IridiumStandardFile *
- iridium_standard_file_new (void)
- {
- return g_object_new (IRIDIUM_TYPE_STANDARD_FILE, NULL);
- }
- static guint8 *
- unhexlify (const gchar *s, gsize length)
- {
- guint8 *result;
- result = g_malloc0 (length / 2);
- for (gsize i = 0; i < length / 2; i++)
- sscanf (&s[i * 2], "%2hhx", &result[i]);
- return result;
- }
- static gchar *
- hexlify (const guint8 *s, gsize length)
- {
- gchar *result;
- result = g_malloc0 (2 * length + 1);
- for (gsize i = 0; i < length; i++)
- g_snprintf (&result[2 * i], 3, "%02x", s[i]);
- return result;
- }
- static void
- derive_keys (const guint8 *password,
- StandardFileAuthParams *params)
- {
- struct hmac_sha512_ctx context;
- guint8 dst[96];
- g_assert_nonnull (params->salt);
- hmac_sha512_set_key (&context, strlen ((gchar *) password), password);
- PBKDF2 (&context, hmac_sha512_update, hmac_sha512_digest, SHA512_DIGEST_SIZE,
- params->cost, strlen (params->salt), (guint8 *) params->salt, 96, dst);
- memcpy (¶ms->keys, dst, sizeof (dst));
- }
- static gchar *
- decrypt (const gchar *s,
- const gchar *check_uuid,
- const guint8 *enc_key,
- const guint8 *auth_key,
- gsize key_length)
- {
- gchar **v;
- gchar *to_auth;
- gchar *hash;
- gsize cipher_length;
- gsize raw_length;
- const gchar *version;
- const gchar *auth_hash;
- const gchar *uuid;
- const gchar *iv;
- const gchar *cipher_text;
- guint8 *iv_bytes;
- guint8 *dst;
- guchar *cipher_raw_text;
- struct hmac_sha256_ctx hmac_contextt;
- guint8 digest[SHA256_DIGEST_SIZE];
- struct aes_ctx aes_context;
- v = g_strsplit (s, ":", 0);
- g_assert_nonnull (v[0]); version = v[0];
- g_assert_nonnull (v[1]); auth_hash = v[1];
- g_assert_nonnull (v[2]); uuid = v[2];
- g_assert_nonnull (v[3]); iv = v[3];
- g_assert_nonnull (v[4]); cipher_text = v[4];
- g_assert_cmpstr (uuid, ==, check_uuid);
- to_auth = g_strjoin (":", version, uuid, iv, cipher_text, NULL);
- hmac_sha256_set_key (&hmac_contextt, key_length, auth_key);
- hmac_sha256_update (&hmac_contextt, strlen (to_auth), (guint8 *) to_auth);
- hmac_sha256_digest (&hmac_contextt, SHA256_DIGEST_SIZE, digest);
- hash = hexlify (digest, sizeof (digest));
- g_assert_cmpstr (hash, ==, (gchar *) auth_hash);
- g_free (hash);
- cipher_length = strlen (cipher_text);
- cipher_length = AES_BLOCK_SIZE * (cipher_length / AES_BLOCK_SIZE + (cipher_length % AES_BLOCK_SIZE ? 1 : 0));
- dst = g_malloc0 (cipher_length);
- aes_set_decrypt_key (&aes_context, key_length, enc_key);
- iv_bytes = unhexlify (iv, strlen (iv));
- cipher_raw_text = g_base64_decode (cipher_text, &raw_length);
- cbc_decrypt (&aes_context, (nettle_cipher_func *) &aes_decrypt, AES_BLOCK_SIZE, iv_bytes,
- raw_length, dst, (guint8 *) cipher_raw_text);
- dst[raw_length] = '\0';
- g_free (cipher_raw_text);
- g_free (iv_bytes);
- g_free (to_auth);
- g_strfreev (v);
- return (gchar *) dst;
- }
- static gchar *
- decrypt_item (JsonObject *item, StandardFileAuthParams *params)
- {
- const gchar *enc_item_key;
- const gchar *uuid;
- gchar *enc_auth_key;
- const gchar *enc_key;
- const gchar *auth_key;
- guint8 *enc_key_bytes;
- guint8 *auth_key_bytes;
- gsize enc_key_size;
- const gchar *enc_content;
- gchar *content;
- uuid = json_object_get_string_member (item, "uuid");
- enc_item_key = json_object_get_string_member (item, "enc_item_key");
- if (!g_strcmp0 (enc_item_key, ""))
- return NULL;
- enc_auth_key = decrypt (enc_item_key, uuid, params->keys.master, params->keys.auth, sizeof (params->keys.master));
- enc_key = (gchar *) enc_auth_key;
- enc_key_size = strlen (enc_auth_key) / 2 - 8;
- auth_key = &enc_auth_key[enc_key_size];
- enc_key_bytes = unhexlify (enc_key, enc_key_size);
- auth_key_bytes = unhexlify (auth_key, enc_key_size);
- enc_content = json_object_get_string_member (item, "content");
- content = decrypt (enc_content, uuid, enc_key_bytes, auth_key_bytes, enc_key_size / 2);
- g_free (enc_key_bytes);
- g_free (auth_key_bytes);
- g_free (enc_auth_key);
- return content;
- }
- static IridiumNote *
- deserialize_note (JsonObject *meta, JsonObject *data)
- {
- IridiumNote *note;
- GTimeVal time;
- GDateTime *last_modified;
- if (!g_time_val_from_iso8601 (json_object_get_string_member (meta, "created_at"), &time)) {
- g_print ("Problem parsing\n");
- }
- last_modified = g_date_time_new_from_timeval_local (&time);
- note = iridium_note_new (json_object_get_string_member (data, "title"),
- json_object_get_string_member (data, "text"),
- last_modified);
- return note;
- }
- static GObject *
- deserialize_item (JsonObject *meta, const gchar *data)
- {
- JsonObject *root;
- g_autoptr(JsonParser) parser;
- const gchar *type;
- GError *error = NULL;
- parser = json_parser_new_immutable ();
- json_parser_load_from_data (parser, data, -1, &error);
- type = json_object_get_string_member (meta, "content_type");
- root = json_node_get_object (json_parser_get_root (parser));
- if (!g_strcmp0 (type, "Note"))
- return G_OBJECT (deserialize_note (meta, root));
- return NULL;
- }
- static gboolean
- get_auth_params (JsonParser *parser,
- ReadAuthParams *params,
- GError **error)
- {
- JsonObject *object;
- const gchar *s;
- object = json_node_get_object (json_parser_get_root (parser));
- params->client->auth_params.func = SF_FUNC_PBKDF2;
- params->client->auth_params.hash = SF_HASH_SHA512;
- params->client->auth_params.cost = json_object_get_int_member (object, "pw_cost");
- params->client->auth_params.key_size = json_object_get_int_member (object, "pw_key_size");
- params->client->auth_params.salt = g_strdup (json_object_get_string_member (object, "pw_salt"));
- if (g_strcmp0 (json_object_get_string_member (object, "pw_alg"), "sha512")) {
- g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
- "Hash algorithm other than sha512 is not supported");
- return FALSE;
- }
- if (g_strcmp0 (json_object_get_string_member (object, "pw_func"), "pbkdf2")) {
- g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
- "Password derivative function other than PBKDF2 is not supported");
- return FALSE;
- }
- s = json_object_get_string_member (object, "version");
- if (!g_strcmp0 (s, "001"))
- params->client->auth_params.version = SF_VERSION_001;
- else if (!g_strcmp0 (s, "002"))
- params->client->auth_params.version = SF_VERSION_002;
- else {
- g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
- "StandardFile protocols other than 001 and 002 are not supported");
- return FALSE;
- }
- return TRUE;
- }
- static void
- read_auth_params_data_free (ReadAuthParams *data)
- {
- secret_password_free (data->password);
- }
- static void
- on_signin_response_parsed (GObject *object,
- GAsyncResult *result,
- gpointer user_data)
- {
- GTask *task;
- ReadAuthParams *data;
- JsonParser *parser;
- JsonObject *root_object;
- GError *error = NULL;
- task = user_data;
- data = g_task_get_task_data (task);
- parser = JSON_PARSER (object);
- if (!json_parser_load_from_stream_finish (parser, result, &error)) {
- g_task_return_error (task, error);
- g_object_unref (task);
- return;
- }
- root_object = json_node_get_object (json_parser_get_root (parser));
- g_free (data->client->token);
- data->client->token = g_strdup (json_object_get_string_member (root_object, "token"));
- g_task_return_boolean (task, TRUE);
- g_object_unref (task);
- g_object_unref (parser);
- }
- static void
- on_send_signin_message (GObject *object,
- GAsyncResult *result,
- gpointer user_data)
- {
- GInputStream *stream;
- GTask *task;
- JsonParser *parser;
- GError *error = NULL;
- task = user_data;
- stream = soup_request_send_finish (SOUP_REQUEST (object), result, &error);
- if (stream == NULL) {
- g_task_return_error (task, error);
- return;
- }
- parser = json_parser_new ();
- json_parser_load_from_stream_async (parser, stream, g_task_get_cancellable (task),
- on_signin_response_parsed, task);
- g_object_unref (stream);
- }
- static void
- on_auth_params_response_parsed (GObject *object,
- GAsyncResult *result,
- gpointer user_data)
- {
- GTask *task;
- JsonParser *parser;
- ReadAuthParams *data;
- SoupURI *uri;
- SoupRequestHTTP *request;
- SoupMessage *msg;
- gchar *password;
- gchar *body;
- GError *error = NULL;
- task = user_data;
- data = g_task_get_task_data (task);
- parser = JSON_PARSER (object);
- if (!json_parser_load_from_stream_finish (parser, result, &error)) {
- g_task_return_error (task, error);
- return;
- }
- if (!get_auth_params (parser, data, &error)) {
- g_task_return_error (task, error);
- return;
- }
- derive_keys ((guint8 *) data->password, &data->client->auth_params);
- g_debug ("StandardFile parameters: version=%i func=%i hash=%i key_size=%u iterations=%u",
- data->client->auth_params.version,
- data->client->auth_params.func,
- data->client->auth_params.hash,
- data->client->auth_params.key_size,
- data->client->auth_params.cost);
- uri = soup_uri_new_with_base (data->client->base_uri, "api/auth/sign_in");
- request = soup_session_request_http_uri (data->client->session, "POST", uri, &error);
- if (request == NULL) {
- g_task_return_error (task, error);
- return;
- }
- password = hexlify (data->client->auth_params.keys.password,
- sizeof (data->client->auth_params.keys.password));
- body = g_strdup_printf ("{\"email\": \"%s\", \"password\": \"%s\"}", data->client->email, password);
- msg = soup_request_http_get_message (request);
- soup_message_set_request (msg, "application/json", SOUP_MEMORY_TAKE, body, strlen (body));
- soup_request_send_async (SOUP_REQUEST (request), g_task_get_cancellable (task), on_send_signin_message, task);
- g_free (password);
- soup_uri_free (uri);
- g_object_unref (msg);
- g_object_unref (parser);
- }
- static void
- on_send_auth_params_message (GObject *object,
- GAsyncResult *result,
- gpointer user_data)
- {
- GInputStream *stream;
- GTask *task;
- JsonParser *parser;
- GError *error = NULL;
- task = user_data;
- stream = soup_request_send_finish (SOUP_REQUEST (object), result, &error);
- if (stream == NULL) {
- g_task_return_error (task, error);
- return;
- }
- parser = json_parser_new ();
- json_parser_load_from_stream_async (parser, stream, g_task_get_cancellable (task),
- on_auth_params_response_parsed, task);
- g_object_unref (object);
- g_object_unref (stream);
- }
- static void
- on_sync_response_parsed (GObject *object,
- GAsyncResult *result,
- gpointer user_data)
- {
- GTask *task;
- StandardFileAuthParams *auth_params;
- JsonParser *parser;
- JsonObject *root;
- JsonArray *array;
- GList *items = NULL;
- GError *error = NULL;
- task = G_TASK (user_data);
- auth_params = g_task_get_task_data (task);
- parser = JSON_PARSER (object);
- if (!json_parser_load_from_stream_finish (parser, result, &error)) {
- g_task_return_error (task, error);
- return;
- }
- root = json_node_get_object (json_parser_get_root (parser));
- array = json_object_get_array_member (root, "retrieved_items");
- for (guint i = 0; i < json_array_get_length (array); i++) {
- JsonObject *data;
- GObject *item;
- gchar *content;
- data = json_array_get_object_element (array, i);
- content = decrypt_item (data, auth_params);
- if (content) {
- item = deserialize_item (data, content);
- if (item)
- items = g_list_append (items, item);
- }
- g_free (content);
- }
- g_task_return_pointer (task, items, NULL);
- g_object_unref (parser);
- }
- static void
- on_send_sync_request (GObject *object,
- GAsyncResult *result,
- gpointer user_data)
- {
- GInputStream *stream;
- GTask *task;
- JsonParser *parser;
- GError *error = NULL;
- task = user_data;
- stream = soup_request_send_finish (SOUP_REQUEST (object), result, &error);
- if (stream == NULL) {
- g_task_return_error (task, error);
- return;
- }
- parser = json_parser_new ();
- json_parser_load_from_stream_async (parser, stream, g_task_get_cancellable (task),
- on_sync_response_parsed, task);
- g_object_unref (stream);
- }
- GList *
- iridium_standard_file_load_finish (IridiumStandardFile *client,
- GAsyncResult *result,
- GError **error)
- {
- g_return_val_if_fail (g_task_is_valid (result, client), FALSE);
- return g_task_propagate_pointer (G_TASK (result), error);
- }
- void
- iridium_standard_file_load_async (IridiumStandardFile *client,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data)
- {
- SoupURI *uri;
- SoupRequestHTTP *request;
- SoupMessage *msg;
- SoupMessageHeaders *headers;
- gchar *value;
- const gchar *body;
- GTask *task;
- GError *error = NULL;
- task = g_task_new (client, cancellable, callback, user_data);
- g_task_set_task_data (task, &client->auth_params, NULL);
- uri = soup_uri_new_with_base (client->base_uri, "api/items/sync");
- request = soup_session_request_http_uri (client->session, "POST", uri, &error);
- if (request == NULL) {
- g_task_return_error (task, error);
- return;
- }
- msg = soup_request_http_get_message (request);
- g_object_get (msg, "request-headers", &headers, NULL);
- value = g_strdup_printf ("Bearer %s", client->token);
- soup_message_headers_append (headers, "Authorization", value);
- body = "{\"items\": []}";
- soup_message_set_request (msg, "application/json", SOUP_MEMORY_STATIC, body, strlen (body));
- soup_request_send_async (SOUP_REQUEST (request), cancellable, on_send_sync_request, task);
- g_object_unref (msg);
- soup_uri_free (uri);
- }
- gboolean
- iridium_standard_file_connect_finish (IridiumStandardFile *client,
- GAsyncResult *result,
- GError **error)
- {
- g_return_val_if_fail (g_task_is_valid (result, client), FALSE);
- return g_task_propagate_boolean (G_TASK (result), error);
- }
- void
- iridium_standard_file_connect_async (IridiumStandardFile *client,
- const gchar *server,
- const gchar *email,
- const gchar *password,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data)
- {
- SoupURI *uri;
- SoupRequestHTTP *request;
- ReadAuthParams *data;
- GTask *task;
- GError *error = NULL;
- if (client->base_uri)
- soup_uri_free (client->base_uri);
- client->base_uri = soup_uri_new (server);
- client->email = g_strdup (email);
- uri = soup_uri_new_with_base (client->base_uri, "api/auth/params");
- soup_uri_set_query_from_fields (uri, "email", email, NULL);
- task = g_task_new (client, cancellable, callback, user_data);
- request = soup_session_request_http_uri (client->session, "GET", uri, &error);
- if (request == NULL) {
- g_task_return_error (task, error);
- return;
- }
- data = g_new0 (ReadAuthParams, 1);
- data->client = client;
- data->password = g_strdup (password);
- g_task_set_task_data (task, data, (GDestroyNotify) read_auth_params_data_free);
- soup_request_send_async (SOUP_REQUEST (request), cancellable, on_send_auth_params_message, task);
- soup_uri_free (uri);
- }
- static void
- iridium_standard_file_dispose (GObject *object)
- {
- IridiumStandardFile *self;
- self = IRIDIUM_STANDARD_FILE (object);
- g_object_unref (self->session);
- soup_uri_free (self->base_uri);
- G_OBJECT_CLASS (iridium_standard_file_parent_class)->dispose (object);
- }
- static void
- iridium_standard_file_finalize (GObject *object)
- {
- IridiumStandardFile *self;
- self = IRIDIUM_STANDARD_FILE (object);
- g_free (self->email);
- g_free (self->token);
- G_OBJECT_CLASS (iridium_standard_file_parent_class)->finalize (object);
- }
- static void
- iridium_standard_file_class_init (IridiumStandardFileClass *klass)
- {
- GObjectClass *oclass;
- oclass = G_OBJECT_CLASS (klass);
- oclass->dispose = iridium_standard_file_dispose;
- oclass->finalize = iridium_standard_file_finalize;
- }
- static void
- iridium_standard_file_init (IridiumStandardFile *self)
- {
- self->base_uri = NULL;
- self->email = NULL;
- self->token = NULL;
- self->session = soup_session_new ();
- }
|