iridium-standard-file.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672
  1. /* iridium-standard-file.c
  2. *
  3. * Copyright 2018 Matthias Vogelgesang
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. #include <string.h>
  19. #include <stdio.h>
  20. #include <json-glib/json-glib.h>
  21. #include <libsoup/soup.h>
  22. #include <nettle/aes.h>
  23. #include <nettle/cbc.h>
  24. #include <nettle/sha2.h>
  25. #include <nettle/hmac.h>
  26. #include <nettle/pbkdf2.h>
  27. #include "iridium-standard-file.h"
  28. #include "iridium-note.h"
  29. typedef enum {
  30. SF_VERSION_001,
  31. SF_VERSION_002,
  32. } StandardFileVersion;
  33. typedef enum {
  34. SF_FUNC_PBKDF2,
  35. } StandardFileFunc;
  36. typedef enum {
  37. SF_HASH_SHA512,
  38. } StandardFileHash;
  39. typedef struct {
  40. guint cost;
  41. guint key_size;
  42. gchar *salt;
  43. StandardFileFunc func;
  44. StandardFileFunc hash;
  45. StandardFileVersion version;
  46. struct {
  47. guint8 password[32];
  48. guint8 master[32];
  49. guint8 auth[32];
  50. } keys;
  51. } StandardFileAuthParams;
  52. typedef struct {
  53. IridiumStandardFile *client;
  54. gchar *password;
  55. } ReadAuthParams;
  56. struct _IridiumStandardFile
  57. {
  58. GObject parent_instance;
  59. gchar *email;
  60. gchar *token;
  61. SoupSession *session;
  62. SoupURI *base_uri;
  63. StandardFileAuthParams auth_params;
  64. };
  65. G_DEFINE_TYPE (IridiumStandardFile, iridium_standard_file, G_TYPE_OBJECT)
  66. const SecretSchema *
  67. standard_file_get_schema (void)
  68. {
  69. static const SecretSchema schema = {
  70. "net.bloerg.Iridium", SECRET_SCHEMA_NONE,
  71. {
  72. { "email", SECRET_SCHEMA_ATTRIBUTE_STRING },
  73. { "server", SECRET_SCHEMA_ATTRIBUTE_STRING },
  74. { NULL, 0 },
  75. }
  76. };
  77. return &schema;
  78. }
  79. IridiumStandardFile *
  80. iridium_standard_file_new (void)
  81. {
  82. return g_object_new (IRIDIUM_TYPE_STANDARD_FILE, NULL);
  83. }
  84. static guint8 *
  85. unhexlify (const gchar *s, gsize length)
  86. {
  87. guint8 *result;
  88. result = g_malloc0 (length / 2);
  89. for (gsize i = 0; i < length / 2; i++)
  90. sscanf (&s[i * 2], "%2hhx", &result[i]);
  91. return result;
  92. }
  93. static gchar *
  94. hexlify (const guint8 *s, gsize length)
  95. {
  96. gchar *result;
  97. result = g_malloc0 (2 * length + 1);
  98. for (gsize i = 0; i < length; i++)
  99. g_snprintf (&result[2 * i], 3, "%02x", s[i]);
  100. return result;
  101. }
  102. static void
  103. derive_keys (const guint8 *password,
  104. StandardFileAuthParams *params)
  105. {
  106. struct hmac_sha512_ctx context;
  107. guint8 dst[96];
  108. g_assert_nonnull (params->salt);
  109. hmac_sha512_set_key (&context, strlen ((gchar *) password), password);
  110. PBKDF2 (&context, hmac_sha512_update, hmac_sha512_digest, SHA512_DIGEST_SIZE,
  111. params->cost, strlen (params->salt), (guint8 *) params->salt, 96, dst);
  112. memcpy (&params->keys, dst, sizeof (dst));
  113. }
  114. static gchar *
  115. decrypt (const gchar *s,
  116. const gchar *check_uuid,
  117. const guint8 *enc_key,
  118. const guint8 *auth_key,
  119. gsize key_length)
  120. {
  121. gchar **v;
  122. gchar *to_auth;
  123. gchar *hash;
  124. gsize cipher_length;
  125. gsize raw_length;
  126. const gchar *version;
  127. const gchar *auth_hash;
  128. const gchar *uuid;
  129. const gchar *iv;
  130. const gchar *cipher_text;
  131. guint8 *iv_bytes;
  132. guint8 *dst;
  133. guchar *cipher_raw_text;
  134. struct hmac_sha256_ctx hmac_contextt;
  135. guint8 digest[SHA256_DIGEST_SIZE];
  136. struct aes_ctx aes_context;
  137. v = g_strsplit (s, ":", 0);
  138. g_assert_nonnull (v[0]); version = v[0];
  139. g_assert_nonnull (v[1]); auth_hash = v[1];
  140. g_assert_nonnull (v[2]); uuid = v[2];
  141. g_assert_nonnull (v[3]); iv = v[3];
  142. g_assert_nonnull (v[4]); cipher_text = v[4];
  143. g_assert_cmpstr (uuid, ==, check_uuid);
  144. to_auth = g_strjoin (":", version, uuid, iv, cipher_text, NULL);
  145. hmac_sha256_set_key (&hmac_contextt, key_length, auth_key);
  146. hmac_sha256_update (&hmac_contextt, strlen (to_auth), (guint8 *) to_auth);
  147. hmac_sha256_digest (&hmac_contextt, SHA256_DIGEST_SIZE, digest);
  148. hash = hexlify (digest, sizeof (digest));
  149. g_assert_cmpstr (hash, ==, (gchar *) auth_hash);
  150. g_free (hash);
  151. cipher_length = strlen (cipher_text);
  152. cipher_length = AES_BLOCK_SIZE * (cipher_length / AES_BLOCK_SIZE + (cipher_length % AES_BLOCK_SIZE ? 1 : 0));
  153. dst = g_malloc0 (cipher_length);
  154. aes_set_decrypt_key (&aes_context, key_length, enc_key);
  155. iv_bytes = unhexlify (iv, strlen (iv));
  156. cipher_raw_text = g_base64_decode (cipher_text, &raw_length);
  157. cbc_decrypt (&aes_context, (nettle_cipher_func *) &aes_decrypt, AES_BLOCK_SIZE, iv_bytes,
  158. raw_length, dst, (guint8 *) cipher_raw_text);
  159. dst[raw_length] = '\0';
  160. g_free (cipher_raw_text);
  161. g_free (iv_bytes);
  162. g_free (to_auth);
  163. g_strfreev (v);
  164. return (gchar *) dst;
  165. }
  166. static gchar *
  167. decrypt_item (JsonObject *item, StandardFileAuthParams *params)
  168. {
  169. const gchar *enc_item_key;
  170. const gchar *uuid;
  171. gchar *enc_auth_key;
  172. const gchar *enc_key;
  173. const gchar *auth_key;
  174. guint8 *enc_key_bytes;
  175. guint8 *auth_key_bytes;
  176. gsize enc_key_size;
  177. const gchar *enc_content;
  178. gchar *content;
  179. uuid = json_object_get_string_member (item, "uuid");
  180. enc_item_key = json_object_get_string_member (item, "enc_item_key");
  181. if (!g_strcmp0 (enc_item_key, ""))
  182. return NULL;
  183. enc_auth_key = decrypt (enc_item_key, uuid, params->keys.master, params->keys.auth, sizeof (params->keys.master));
  184. enc_key = (gchar *) enc_auth_key;
  185. enc_key_size = strlen (enc_auth_key) / 2 - 8;
  186. auth_key = &enc_auth_key[enc_key_size];
  187. enc_key_bytes = unhexlify (enc_key, enc_key_size);
  188. auth_key_bytes = unhexlify (auth_key, enc_key_size);
  189. enc_content = json_object_get_string_member (item, "content");
  190. content = decrypt (enc_content, uuid, enc_key_bytes, auth_key_bytes, enc_key_size / 2);
  191. g_free (enc_key_bytes);
  192. g_free (auth_key_bytes);
  193. g_free (enc_auth_key);
  194. return content;
  195. }
  196. static IridiumNote *
  197. deserialize_note (JsonObject *meta, JsonObject *data)
  198. {
  199. IridiumNote *note;
  200. GTimeVal time;
  201. GDateTime *last_modified;
  202. if (!g_time_val_from_iso8601 (json_object_get_string_member (meta, "created_at"), &time)) {
  203. g_print ("Problem parsing\n");
  204. }
  205. last_modified = g_date_time_new_from_timeval_local (&time);
  206. note = iridium_note_new (json_object_get_string_member (data, "title"),
  207. json_object_get_string_member (data, "text"),
  208. last_modified);
  209. return note;
  210. }
  211. static GObject *
  212. deserialize_item (JsonObject *meta, const gchar *data)
  213. {
  214. JsonObject *root;
  215. g_autoptr(JsonParser) parser;
  216. const gchar *type;
  217. GError *error = NULL;
  218. parser = json_parser_new_immutable ();
  219. json_parser_load_from_data (parser, data, -1, &error);
  220. type = json_object_get_string_member (meta, "content_type");
  221. root = json_node_get_object (json_parser_get_root (parser));
  222. if (!g_strcmp0 (type, "Note"))
  223. return G_OBJECT (deserialize_note (meta, root));
  224. return NULL;
  225. }
  226. static gboolean
  227. get_auth_params (JsonParser *parser,
  228. ReadAuthParams *params,
  229. GError **error)
  230. {
  231. JsonObject *object;
  232. const gchar *s;
  233. object = json_node_get_object (json_parser_get_root (parser));
  234. params->client->auth_params.func = SF_FUNC_PBKDF2;
  235. params->client->auth_params.hash = SF_HASH_SHA512;
  236. params->client->auth_params.cost = json_object_get_int_member (object, "pw_cost");
  237. params->client->auth_params.key_size = json_object_get_int_member (object, "pw_key_size");
  238. params->client->auth_params.salt = g_strdup (json_object_get_string_member (object, "pw_salt"));
  239. if (g_strcmp0 (json_object_get_string_member (object, "pw_alg"), "sha512")) {
  240. g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
  241. "Hash algorithm other than sha512 is not supported");
  242. return FALSE;
  243. }
  244. if (g_strcmp0 (json_object_get_string_member (object, "pw_func"), "pbkdf2")) {
  245. g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
  246. "Password derivative function other than PBKDF2 is not supported");
  247. return FALSE;
  248. }
  249. s = json_object_get_string_member (object, "version");
  250. if (!g_strcmp0 (s, "001"))
  251. params->client->auth_params.version = SF_VERSION_001;
  252. else if (!g_strcmp0 (s, "002"))
  253. params->client->auth_params.version = SF_VERSION_002;
  254. else {
  255. g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
  256. "StandardFile protocols other than 001 and 002 are not supported");
  257. return FALSE;
  258. }
  259. return TRUE;
  260. }
  261. static void
  262. read_auth_params_data_free (ReadAuthParams *data)
  263. {
  264. secret_password_free (data->password);
  265. }
  266. static void
  267. on_signin_response_parsed (GObject *object,
  268. GAsyncResult *result,
  269. gpointer user_data)
  270. {
  271. GTask *task;
  272. ReadAuthParams *data;
  273. JsonParser *parser;
  274. JsonObject *root_object;
  275. GError *error = NULL;
  276. task = user_data;
  277. data = g_task_get_task_data (task);
  278. parser = JSON_PARSER (object);
  279. if (!json_parser_load_from_stream_finish (parser, result, &error)) {
  280. g_task_return_error (task, error);
  281. g_object_unref (task);
  282. return;
  283. }
  284. root_object = json_node_get_object (json_parser_get_root (parser));
  285. g_free (data->client->token);
  286. data->client->token = g_strdup (json_object_get_string_member (root_object, "token"));
  287. g_task_return_boolean (task, TRUE);
  288. g_object_unref (task);
  289. g_object_unref (parser);
  290. }
  291. static void
  292. on_send_signin_message (GObject *object,
  293. GAsyncResult *result,
  294. gpointer user_data)
  295. {
  296. GInputStream *stream;
  297. GTask *task;
  298. JsonParser *parser;
  299. GError *error = NULL;
  300. task = user_data;
  301. stream = soup_request_send_finish (SOUP_REQUEST (object), result, &error);
  302. if (stream == NULL) {
  303. g_task_return_error (task, error);
  304. return;
  305. }
  306. parser = json_parser_new ();
  307. json_parser_load_from_stream_async (parser, stream, g_task_get_cancellable (task),
  308. on_signin_response_parsed, task);
  309. g_object_unref (stream);
  310. }
  311. static void
  312. on_auth_params_response_parsed (GObject *object,
  313. GAsyncResult *result,
  314. gpointer user_data)
  315. {
  316. GTask *task;
  317. JsonParser *parser;
  318. ReadAuthParams *data;
  319. SoupURI *uri;
  320. SoupRequestHTTP *request;
  321. SoupMessage *msg;
  322. gchar *password;
  323. gchar *body;
  324. GError *error = NULL;
  325. task = user_data;
  326. data = g_task_get_task_data (task);
  327. parser = JSON_PARSER (object);
  328. if (!json_parser_load_from_stream_finish (parser, result, &error)) {
  329. g_task_return_error (task, error);
  330. return;
  331. }
  332. if (!get_auth_params (parser, data, &error)) {
  333. g_task_return_error (task, error);
  334. return;
  335. }
  336. derive_keys ((guint8 *) data->password, &data->client->auth_params);
  337. g_debug ("StandardFile parameters: version=%i func=%i hash=%i key_size=%u iterations=%u",
  338. data->client->auth_params.version,
  339. data->client->auth_params.func,
  340. data->client->auth_params.hash,
  341. data->client->auth_params.key_size,
  342. data->client->auth_params.cost);
  343. uri = soup_uri_new_with_base (data->client->base_uri, "api/auth/sign_in");
  344. request = soup_session_request_http_uri (data->client->session, "POST", uri, &error);
  345. if (request == NULL) {
  346. g_task_return_error (task, error);
  347. return;
  348. }
  349. password = hexlify (data->client->auth_params.keys.password,
  350. sizeof (data->client->auth_params.keys.password));
  351. body = g_strdup_printf ("{\"email\": \"%s\", \"password\": \"%s\"}", data->client->email, password);
  352. msg = soup_request_http_get_message (request);
  353. soup_message_set_request (msg, "application/json", SOUP_MEMORY_TAKE, body, strlen (body));
  354. soup_request_send_async (SOUP_REQUEST (request), g_task_get_cancellable (task), on_send_signin_message, task);
  355. g_free (password);
  356. soup_uri_free (uri);
  357. g_object_unref (msg);
  358. g_object_unref (parser);
  359. }
  360. static void
  361. on_send_auth_params_message (GObject *object,
  362. GAsyncResult *result,
  363. gpointer user_data)
  364. {
  365. GInputStream *stream;
  366. GTask *task;
  367. JsonParser *parser;
  368. GError *error = NULL;
  369. task = user_data;
  370. stream = soup_request_send_finish (SOUP_REQUEST (object), result, &error);
  371. if (stream == NULL) {
  372. g_task_return_error (task, error);
  373. return;
  374. }
  375. parser = json_parser_new ();
  376. json_parser_load_from_stream_async (parser, stream, g_task_get_cancellable (task),
  377. on_auth_params_response_parsed, task);
  378. g_object_unref (object);
  379. g_object_unref (stream);
  380. }
  381. static void
  382. on_sync_response_parsed (GObject *object,
  383. GAsyncResult *result,
  384. gpointer user_data)
  385. {
  386. GTask *task;
  387. StandardFileAuthParams *auth_params;
  388. JsonParser *parser;
  389. JsonObject *root;
  390. JsonArray *array;
  391. GList *items = NULL;
  392. GError *error = NULL;
  393. task = G_TASK (user_data);
  394. auth_params = g_task_get_task_data (task);
  395. parser = JSON_PARSER (object);
  396. if (!json_parser_load_from_stream_finish (parser, result, &error)) {
  397. g_task_return_error (task, error);
  398. return;
  399. }
  400. root = json_node_get_object (json_parser_get_root (parser));
  401. array = json_object_get_array_member (root, "retrieved_items");
  402. for (guint i = 0; i < json_array_get_length (array); i++) {
  403. JsonObject *data;
  404. GObject *item;
  405. gchar *content;
  406. data = json_array_get_object_element (array, i);
  407. content = decrypt_item (data, auth_params);
  408. if (content) {
  409. item = deserialize_item (data, content);
  410. if (item)
  411. items = g_list_append (items, item);
  412. }
  413. g_free (content);
  414. }
  415. g_task_return_pointer (task, items, NULL);
  416. g_object_unref (parser);
  417. }
  418. static void
  419. on_send_sync_request (GObject *object,
  420. GAsyncResult *result,
  421. gpointer user_data)
  422. {
  423. GInputStream *stream;
  424. GTask *task;
  425. JsonParser *parser;
  426. GError *error = NULL;
  427. task = user_data;
  428. stream = soup_request_send_finish (SOUP_REQUEST (object), result, &error);
  429. if (stream == NULL) {
  430. g_task_return_error (task, error);
  431. return;
  432. }
  433. parser = json_parser_new ();
  434. json_parser_load_from_stream_async (parser, stream, g_task_get_cancellable (task),
  435. on_sync_response_parsed, task);
  436. g_object_unref (stream);
  437. }
  438. GList *
  439. iridium_standard_file_load_finish (IridiumStandardFile *client,
  440. GAsyncResult *result,
  441. GError **error)
  442. {
  443. g_return_val_if_fail (g_task_is_valid (result, client), FALSE);
  444. return g_task_propagate_pointer (G_TASK (result), error);
  445. }
  446. void
  447. iridium_standard_file_load_async (IridiumStandardFile *client,
  448. GCancellable *cancellable,
  449. GAsyncReadyCallback callback,
  450. gpointer user_data)
  451. {
  452. SoupURI *uri;
  453. SoupRequestHTTP *request;
  454. SoupMessage *msg;
  455. SoupMessageHeaders *headers;
  456. gchar *value;
  457. const gchar *body;
  458. GTask *task;
  459. GError *error = NULL;
  460. task = g_task_new (client, cancellable, callback, user_data);
  461. g_task_set_task_data (task, &client->auth_params, NULL);
  462. uri = soup_uri_new_with_base (client->base_uri, "api/items/sync");
  463. request = soup_session_request_http_uri (client->session, "POST", uri, &error);
  464. if (request == NULL) {
  465. g_task_return_error (task, error);
  466. return;
  467. }
  468. msg = soup_request_http_get_message (request);
  469. g_object_get (msg, "request-headers", &headers, NULL);
  470. value = g_strdup_printf ("Bearer %s", client->token);
  471. soup_message_headers_append (headers, "Authorization", value);
  472. body = "{\"items\": []}";
  473. soup_message_set_request (msg, "application/json", SOUP_MEMORY_STATIC, body, strlen (body));
  474. soup_request_send_async (SOUP_REQUEST (request), cancellable, on_send_sync_request, task);
  475. g_object_unref (msg);
  476. soup_uri_free (uri);
  477. }
  478. gboolean
  479. iridium_standard_file_connect_finish (IridiumStandardFile *client,
  480. GAsyncResult *result,
  481. GError **error)
  482. {
  483. g_return_val_if_fail (g_task_is_valid (result, client), FALSE);
  484. return g_task_propagate_boolean (G_TASK (result), error);
  485. }
  486. void
  487. iridium_standard_file_connect_async (IridiumStandardFile *client,
  488. const gchar *server,
  489. const gchar *email,
  490. const gchar *password,
  491. GCancellable *cancellable,
  492. GAsyncReadyCallback callback,
  493. gpointer user_data)
  494. {
  495. SoupURI *uri;
  496. SoupRequestHTTP *request;
  497. ReadAuthParams *data;
  498. GTask *task;
  499. GError *error = NULL;
  500. if (client->base_uri)
  501. soup_uri_free (client->base_uri);
  502. client->base_uri = soup_uri_new (server);
  503. client->email = g_strdup (email);
  504. uri = soup_uri_new_with_base (client->base_uri, "api/auth/params");
  505. soup_uri_set_query_from_fields (uri, "email", email, NULL);
  506. task = g_task_new (client, cancellable, callback, user_data);
  507. request = soup_session_request_http_uri (client->session, "GET", uri, &error);
  508. if (request == NULL) {
  509. g_task_return_error (task, error);
  510. return;
  511. }
  512. data = g_new0 (ReadAuthParams, 1);
  513. data->client = client;
  514. data->password = g_strdup (password);
  515. g_task_set_task_data (task, data, (GDestroyNotify) read_auth_params_data_free);
  516. soup_request_send_async (SOUP_REQUEST (request), cancellable, on_send_auth_params_message, task);
  517. soup_uri_free (uri);
  518. }
  519. static void
  520. iridium_standard_file_dispose (GObject *object)
  521. {
  522. IridiumStandardFile *self;
  523. self = IRIDIUM_STANDARD_FILE (object);
  524. g_object_unref (self->session);
  525. soup_uri_free (self->base_uri);
  526. G_OBJECT_CLASS (iridium_standard_file_parent_class)->dispose (object);
  527. }
  528. static void
  529. iridium_standard_file_finalize (GObject *object)
  530. {
  531. IridiumStandardFile *self;
  532. self = IRIDIUM_STANDARD_FILE (object);
  533. g_free (self->email);
  534. g_free (self->token);
  535. G_OBJECT_CLASS (iridium_standard_file_parent_class)->finalize (object);
  536. }
  537. static void
  538. iridium_standard_file_class_init (IridiumStandardFileClass *klass)
  539. {
  540. GObjectClass *oclass;
  541. oclass = G_OBJECT_CLASS (klass);
  542. oclass->dispose = iridium_standard_file_dispose;
  543. oclass->finalize = iridium_standard_file_finalize;
  544. }
  545. static void
  546. iridium_standard_file_init (IridiumStandardFile *self)
  547. {
  548. self->base_uri = NULL;
  549. self->email = NULL;
  550. self->token = NULL;
  551. self->session = soup_session_new ();
  552. }