aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDr. David Alan Gilbert <dave@treblig.org>2018-02-23 01:36:49 +0000
committerDr. David Alan Gilbert <dave@treblig.org>2018-02-25 02:08:49 +0000
commit1bb115c9c46576138df22cd45197e456bf8ce13b (patch)
tree9720eb2ca285a8be813a5e79c34be1ac17abc7e9
parentab534c13c395bdcce5115945b796821cd1e6d75f (diff)
downloadpurple-matrix-1bb115c9c46576138df22cd45197e456bf8ce13b.tar.gz
e2e: Parse media decryption info
For encrypted images the decrypted message contains key information to decrypt the actual media once received. Add a structure and a parser to extract the information from the JSON. Signed-off-by: Dr. David Alan Gilbert <dave@treblig.org>
-rw-r--r--matrix-e2e.c97
-rw-r--r--matrix-e2e.h3
2 files changed, 100 insertions, 0 deletions
diff --git a/matrix-e2e.c b/matrix-e2e.c
index 9c16a5f..93cee3c 100644
--- a/matrix-e2e.c
+++ b/matrix-e2e.c
@@ -74,6 +74,12 @@ typedef struct _MatrixHashKeyInBoundMegOlm {
gchar *device_id;
} MatrixHashKeyInBoundMegOlm;
+struct _MatrixMediaCryptInfo {
+ guchar sha256[32];
+ guchar aes_k[32];
+ guchar aes_iv[16];
+};
+
static void key_upload_callback(MatrixConnectionData *conn,
gpointer user_data,
struct _JsonNode *json_root,
@@ -1704,6 +1710,84 @@ out:
return plaintext_parser;
}
+/* Parse the 'file' object on media to extract keys, returns
+ * FALSE if there's a problem, returns TRUE whether or not there's
+ * any crypto information, returns TRUE and allocates *crypt
+ * with the keys if the crypto info is there.
+ * The *crypt should be g_free'd by someone later.
+ */
+gboolean matrix_e2e_parse_media_decrypt_info(MatrixMediaCryptInfo **crypt,
+ JsonObject *file_obj)
+{
+ const gchar *v_str = matrix_json_object_get_string_member(file_obj, "v");
+ const gchar *iv_str = matrix_json_object_get_string_member(file_obj, "iv");
+ /* RFC7517 JSON Web Key */
+ JsonObject *key_obj = matrix_json_object_get_object_member(file_obj,
+ "key");
+ JsonObject *hashes_obj = matrix_json_object_get_object_member(file_obj,
+ "hashes");
+ const gchar *alg_str = matrix_json_object_get_string_member(key_obj, "alg");
+ const gchar *kty_str = matrix_json_object_get_string_member(key_obj, "kty");
+ const gchar *k_str = matrix_json_object_get_string_member(key_obj, "k");
+ const gchar *sha256_str = matrix_json_object_get_string_member(hashes_obj,
+ "sha256");
+ if (!v_str || strcmp(v_str, "v2")) {
+ purple_debug_info("matrixprpl", "bad media decrypt version\n");
+ return FALSE;
+ }
+ if (!iv_str || !alg_str || !kty_str || !k_str || !sha256_str) {
+ purple_debug_info("matrixprpl", "missing media decrypt field\n");
+ /* Potentially this is OK, just not crypted */
+ return TRUE;
+ }
+ if (strcmp(alg_str, "A256CTR") || strcmp(kty_str, "oct")) {
+ purple_debug_info("matrixprpl", "media decrypt: bad alg/kty :%s/%s\n",
+ alg_str, kty_str);
+ return FALSE;
+ }
+ if (strlen(iv_str) != 22 || strlen(sha256_str) != 43 ||
+ strlen(k_str) != 43) {
+ purple_debug_info("matrixprpl", "media decrypt: Bad key/sha len:"
+ " iv: %s sha256: %s k: %s\n", iv_str, sha256_str, k_str);
+ return FALSE;
+ }
+
+ /* The k_str isn't a simple base64, it's a JSON web signature that needs
+ * to be decoded first.
+ */
+ gchar tmp_str[48];
+ gsize k_len, iv_len, sha256_len;
+ matrix_json_jws_tobase64(tmp_str, k_str);
+ guchar *decoded_k = g_base64_decode(tmp_str, &k_len);
+ matrix_json_jws_tobase64(tmp_str, iv_str);
+ guchar *decoded_iv = g_base64_decode(tmp_str, &iv_len);
+ matrix_json_jws_tobase64(tmp_str, sha256_str);
+ guchar *decoded_sha256 = g_base64_decode(tmp_str, &sha256_len);
+
+ if (k_len != 32 || iv_len != 16 || sha256_len != 32) {
+ purple_debug_info("matrixprpl", "media decrypt: Bad base64 decode:"
+ " iv: %s=%zd sha256: %s=%zd k: %s=%zd\n",
+ iv_str, iv_len, sha256_str, sha256_len, k_str, k_len);
+ g_free(decoded_k);
+ g_free(decoded_iv);
+ g_free(decoded_sha256);
+ return FALSE;
+ }
+ purple_debug_info("matrixprpl", "media decrypt: got decrypt keys\n");
+
+ *crypt = g_new0(MatrixMediaCryptInfo, 1);
+ memcpy((*crypt)->sha256, decoded_sha256, 32);
+ memcpy((*crypt)->aes_k, decoded_k, 32);
+ memcpy((*crypt)->aes_iv, decoded_iv, 16);
+
+ clear_mem((char *)decoded_k, sizeof(decoded_k));
+ g_free(decoded_k);
+ g_free(decoded_iv);
+ g_free(decoded_sha256);
+
+ return TRUE;
+}
+
static void action_device_info(PurplePluginAction *action)
{
PurpleConnection *pc = (PurpleConnection *) action->context;
@@ -1760,6 +1844,19 @@ void matrix_e2e_cleanup_conversation(PurpleConversation *conv)
{
}
+gboolean matrix_e2e_parse_media_decrypt_info(MatrixMediaCryptInfo **crypt,
+ JsonObject *file_obj)
+{
+ JsonObject *key_obj = matrix_json_object_get_object_member(file_obj,
+ "key");
+ if (key_obj) {
+ /* This has a key but with crypto turned off we'll have to fail */
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
GList *matrix_e2e_actions(GList *list)
{
return list;
diff --git a/matrix-e2e.h b/matrix-e2e.h
index b1282a2..def07e5 100644
--- a/matrix-e2e.h
+++ b/matrix-e2e.h
@@ -24,6 +24,7 @@
typedef struct _MatrixE2EData MatrixE2EData;
typedef struct _PurpleConversation PurpleConversation;
+typedef struct _MatrixMediaCryptInfo MatrixMediaCryptInfo;
GList *matrix_e2e_actions(GList *list);
int matrix_e2e_get_device_keys(MatrixConnectionData *conn, const gchar *device_id);
@@ -31,6 +32,8 @@ void matrix_e2e_cleanup_connection(MatrixConnectionData *conn);
void matrix_e2e_cleanup_conversation(PurpleConversation *conv);
void matrix_e2e_decrypt_d2d(struct _PurpleConnection *pc, struct _JsonObject *event);
JsonParser *matrix_e2e_decrypt_room(struct _PurpleConversation *conv, struct _JsonObject *event);
+gboolean matrix_e2e_parse_media_decrypt_info(MatrixMediaCryptInfo **crypt,
+ JsonObject *file_obj);
void matrix_e2e_handle_sync_key_counts(struct _PurpleConnection *pc, struct _JsonObject *count_object, gboolean force_send);
#endif