From 39ad75314b9e28053f568ed6a4109f5d3a9468fe Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 18 May 2016 17:23:09 +0100 Subject: Implement decrypting inbound group messages Includes creation of inbound sessions, etc --- src/inbound_group_session.c | 199 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 src/inbound_group_session.c (limited to 'src/inbound_group_session.c') diff --git a/src/inbound_group_session.c b/src/inbound_group_session.c new file mode 100644 index 0000000..4796414 --- /dev/null +++ b/src/inbound_group_session.c @@ -0,0 +1,199 @@ +/* Copyright 2016 OpenMarket Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "olm/inbound_group_session.h" + +#include + +#include "olm/base64.h" +#include "olm/cipher.h" +#include "olm/error.h" +#include "olm/megolm.h" +#include "olm/message.h" + +#define OLM_PROTOCOL_VERSION 3 + +struct OlmInboundGroupSession { + /** our earliest known ratchet value */ + Megolm initial_ratchet; + + /** The most recent ratchet value */ + Megolm latest_ratchet; + + enum OlmErrorCode last_error; +}; + +size_t olm_inbound_group_session_size() { + return sizeof(OlmInboundGroupSession); +} + +OlmInboundGroupSession * olm_inbound_group_session( + void *memory +) { + OlmInboundGroupSession *session = memory; + olm_clear_inbound_group_session(session); + return session; +} + +const char *olm_inbound_group_session_last_error( + const OlmInboundGroupSession *session +) { + return _olm_error_to_string(session->last_error); +} + +size_t olm_clear_inbound_group_session( + OlmInboundGroupSession *session +) { + memset(session, 0, sizeof(OlmInboundGroupSession)); + return sizeof(OlmInboundGroupSession); +} + +size_t olm_init_inbound_group_session( + OlmInboundGroupSession *session, + uint32_t message_index, + const uint8_t * session_key, size_t session_key_length +) { + uint8_t key_buf[MEGOLM_RATCHET_LENGTH]; + size_t raw_length = _olm_decode_base64_length(session_key_length); + + if (raw_length == (size_t)-1) { + session->last_error = OLM_INVALID_BASE64; + return (size_t)-1; + } + + if (raw_length != MEGOLM_RATCHET_LENGTH) { + session->last_error = OLM_BAD_RATCHET_KEY; + return (size_t)-1; + } + + _olm_decode_base64(session_key, session_key_length, key_buf); + megolm_init(&session->initial_ratchet, key_buf, message_index); + megolm_init(&session->latest_ratchet, key_buf, message_index); + memset(key_buf, 0, MEGOLM_RATCHET_LENGTH); + + return 0; +} + +size_t olm_group_decrypt_max_plaintext_length( + OlmInboundGroupSession *session, + uint8_t * message, size_t message_length +) { + size_t r; + const struct _olm_cipher *cipher = megolm_cipher(); + struct _OlmDecodeGroupMessageResults decoded_results; + + r = _olm_decode_base64(message, message_length, message); + if (r == (size_t)-1) { + session->last_error = OLM_INVALID_BASE64; + return r; + } + + _olm_decode_group_message( + message, message_length, + cipher->ops->mac_length(cipher), + &decoded_results); + + if (decoded_results.version != OLM_PROTOCOL_VERSION) { + session->last_error = OLM_BAD_MESSAGE_VERSION; + return (size_t)-1; + } + + if (!decoded_results.ciphertext) { + session->last_error = OLM_BAD_MESSAGE_FORMAT; + return (size_t)-1; + } + + return cipher->ops->decrypt_max_plaintext_length( + cipher, decoded_results.ciphertext_length); +} + + +size_t olm_group_decrypt( + OlmInboundGroupSession *session, + uint8_t * message, size_t message_length, + uint8_t * plaintext, size_t max_plaintext_length +) { + struct _OlmDecodeGroupMessageResults decoded_results; + const struct _olm_cipher *cipher = megolm_cipher(); + size_t max_length, raw_message_length, r; + Megolm *megolm; + Megolm tmp_megolm; + + raw_message_length = _olm_decode_base64(message, message_length, message); + if (raw_message_length == (size_t)-1) { + session->last_error = OLM_INVALID_BASE64; + return (size_t)-1; + } + + _olm_decode_group_message( + message, raw_message_length, + cipher->ops->mac_length(cipher), + &decoded_results); + + if (decoded_results.version != OLM_PROTOCOL_VERSION) { + session->last_error = OLM_BAD_MESSAGE_VERSION; + return (size_t)-1; + } + + if (!decoded_results.has_chain_index || !decoded_results.session_id + || !decoded_results.ciphertext + ) { + session->last_error = OLM_BAD_MESSAGE_FORMAT; + return (size_t)-1; + } + + max_length = cipher->ops->decrypt_max_plaintext_length( + cipher, + decoded_results.ciphertext_length + ); + if (max_plaintext_length < max_length) { + session->last_error = OLM_OUTPUT_BUFFER_TOO_SMALL; + return (size_t)-1; + } + + /* pick a megolm instance to use. If we're at or beyond the latest ratchet + * value, use that */ + if ((int32_t)(decoded_results.chain_index - session->latest_ratchet.counter) >= 0) { + megolm = &session->latest_ratchet; + } else if ((int32_t)(decoded_results.chain_index - session->initial_ratchet.counter) < 0) { + /* the counter is before our intial ratchet - we can't decode this. */ + session->last_error = OLM_BAD_CHAIN_INDEX; + return (size_t)-1; + } else { + /* otherwise, start from the initial megolm. Take a copy so that we + * don't overwrite the initial megolm */ + tmp_megolm = session->initial_ratchet; + megolm = &tmp_megolm; + } + + megolm_advance_to(megolm, decoded_results.chain_index); + + /* now try checking the mac, and decrypting */ + r = cipher->ops->decrypt( + cipher, + megolm_get_data(megolm), MEGOLM_RATCHET_LENGTH, + message, raw_message_length, + decoded_results.ciphertext, decoded_results.ciphertext_length, + plaintext, max_plaintext_length + ); + + memset(&tmp_megolm, 0, sizeof(tmp_megolm)); + if (r == (size_t)-1) { + session->last_error = OLM_BAD_MESSAGE_MAC; + return r; + } + + return r; +} -- cgit v1.2.3 From a073d12d8367d27db97751d46b766e8480fd39e4 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 18 May 2016 18:03:59 +0100 Subject: Support for pickling inbound group sessions --- src/inbound_group_session.c | 76 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) (limited to 'src/inbound_group_session.c') diff --git a/src/inbound_group_session.c b/src/inbound_group_session.c index 4796414..34908a9 100644 --- a/src/inbound_group_session.c +++ b/src/inbound_group_session.c @@ -22,8 +22,12 @@ #include "olm/error.h" #include "olm/megolm.h" #include "olm/message.h" +#include "olm/pickle.h" +#include "olm/pickle_encoding.h" + #define OLM_PROTOCOL_VERSION 3 +#define PICKLE_VERSION 1 struct OlmInboundGroupSession { /** our earliest known ratchet value */ @@ -86,6 +90,78 @@ size_t olm_init_inbound_group_session( return 0; } +static size_t raw_pickle_length( + const OlmInboundGroupSession *session +) { + size_t length = 0; + length += _olm_pickle_uint32_length(PICKLE_VERSION); + length += megolm_pickle_length(&session->initial_ratchet); + length += megolm_pickle_length(&session->latest_ratchet); + return length; +} + +size_t olm_pickle_inbound_group_session_length( + const OlmInboundGroupSession *session +) { + return _olm_enc_output_length(raw_pickle_length(session)); +} + +size_t olm_pickle_inbound_group_session( + OlmInboundGroupSession *session, + void const * key, size_t key_length, + void * pickled, size_t pickled_length +) { + size_t raw_length = raw_pickle_length(session); + uint8_t *pos; + + if (pickled_length < _olm_enc_output_length(raw_length)) { + session->last_error = OLM_OUTPUT_BUFFER_TOO_SMALL; + return (size_t)-1; + } + + pos = _olm_enc_output_pos(pickled, raw_length); + pos = _olm_pickle_uint32(pos, PICKLE_VERSION); + pos = megolm_pickle(&session->initial_ratchet, pos); + pos = megolm_pickle(&session->latest_ratchet, pos); + + return _olm_enc_output(key, key_length, pickled, raw_length); +} + +size_t olm_unpickle_inbound_group_session( + OlmInboundGroupSession *session, + void const * key, size_t key_length, + void * pickled, size_t pickled_length +) { + const uint8_t *pos; + const uint8_t *end; + uint32_t pickle_version; + + size_t raw_length = _olm_enc_input( + key, key_length, pickled, pickled_length, &(session->last_error) + ); + if (raw_length == (size_t)-1) { + return raw_length; + } + + pos = pickled; + end = pos + raw_length; + pos = _olm_unpickle_uint32(pos, end, &pickle_version); + if (pickle_version != PICKLE_VERSION) { + session->last_error = OLM_UNKNOWN_PICKLE_VERSION; + return (size_t)-1; + } + pos = megolm_unpickle(&session->initial_ratchet, pos, end); + pos = megolm_unpickle(&session->latest_ratchet, pos, end); + + if (end != pos) { + /* We had the wrong number of bytes in the input. */ + session->last_error = OLM_CORRUPTED_PICKLE; + return (size_t)-1; + } + + return pickled_length; +} + size_t olm_group_decrypt_max_plaintext_length( OlmInboundGroupSession *session, uint8_t * message, size_t message_length -- cgit v1.2.3 From fc4756ddf17f536912a89a4ffcf90a309c236ced Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Thu, 19 May 2016 07:53:07 +0100 Subject: Fix up some names, and protobuf tags Make names (of session_key and message_index) more consistent. Use our own protobuf tags rather than trying to piggyback on the one-to-one structure. --- src/inbound_group_session.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'src/inbound_group_session.c') diff --git a/src/inbound_group_session.c b/src/inbound_group_session.c index 34908a9..cc6ba5e 100644 --- a/src/inbound_group_session.c +++ b/src/inbound_group_session.c @@ -78,7 +78,7 @@ size_t olm_init_inbound_group_session( } if (raw_length != MEGOLM_RATCHET_LENGTH) { - session->last_error = OLM_BAD_RATCHET_KEY; + session->last_error = OLM_BAD_SESSION_KEY; return (size_t)-1; } @@ -223,7 +223,7 @@ size_t olm_group_decrypt( return (size_t)-1; } - if (!decoded_results.has_chain_index || !decoded_results.session_id + if (!decoded_results.has_message_index || !decoded_results.session_id || !decoded_results.ciphertext ) { session->last_error = OLM_BAD_MESSAGE_FORMAT; @@ -241,11 +241,11 @@ size_t olm_group_decrypt( /* pick a megolm instance to use. If we're at or beyond the latest ratchet * value, use that */ - if ((int32_t)(decoded_results.chain_index - session->latest_ratchet.counter) >= 0) { + if ((int32_t)(decoded_results.message_index - session->latest_ratchet.counter) >= 0) { megolm = &session->latest_ratchet; - } else if ((int32_t)(decoded_results.chain_index - session->initial_ratchet.counter) < 0) { + } else if ((int32_t)(decoded_results.message_index - session->initial_ratchet.counter) < 0) { /* the counter is before our intial ratchet - we can't decode this. */ - session->last_error = OLM_BAD_CHAIN_INDEX; + session->last_error = OLM_UNKNOWN_MESSAGE_INDEX; return (size_t)-1; } else { /* otherwise, start from the initial megolm. Take a copy so that we @@ -254,7 +254,7 @@ size_t olm_group_decrypt( megolm = &tmp_megolm; } - megolm_advance_to(megolm, decoded_results.chain_index); + megolm_advance_to(megolm, decoded_results.message_index); /* now try checking the mac, and decrypting */ r = cipher->ops->decrypt( -- cgit v1.2.3 From 173cbe112c139de0bd1a69dce5a03db360dc5abc Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Fri, 20 May 2016 12:40:59 +0100 Subject: Avoid relying on uint -> int casting behaviour The behaviour when casting from a uint32_t which has overflowed (so has the top bit set) to int32_t is implementation-defined, so let's avoid relying on it. --- src/inbound_group_session.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/inbound_group_session.c') diff --git a/src/inbound_group_session.c b/src/inbound_group_session.c index cc6ba5e..b8f762d 100644 --- a/src/inbound_group_session.c +++ b/src/inbound_group_session.c @@ -241,9 +241,9 @@ size_t olm_group_decrypt( /* pick a megolm instance to use. If we're at or beyond the latest ratchet * value, use that */ - if ((int32_t)(decoded_results.message_index - session->latest_ratchet.counter) >= 0) { + if ((decoded_results.message_index - session->latest_ratchet.counter) < (1U << 31)) { megolm = &session->latest_ratchet; - } else if ((int32_t)(decoded_results.message_index - session->initial_ratchet.counter) < 0) { + } else if ((decoded_results.message_index - session->initial_ratchet.counter) >= (1U << 31)) { /* the counter is before our intial ratchet - we can't decode this. */ session->last_error = OLM_UNKNOWN_MESSAGE_INDEX; return (size_t)-1; -- cgit v1.2.3 From fa1e9446ac2b4d26dd592813ce0a372565df4c93 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Fri, 20 May 2016 12:35:59 +0100 Subject: Use _olm_unset instead of memset memset is at risk of being optimised away, so use _olm_unset instead. --- src/inbound_group_session.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'src/inbound_group_session.c') diff --git a/src/inbound_group_session.c b/src/inbound_group_session.c index b8f762d..6cded75 100644 --- a/src/inbound_group_session.c +++ b/src/inbound_group_session.c @@ -21,6 +21,7 @@ #include "olm/cipher.h" #include "olm/error.h" #include "olm/megolm.h" +#include "olm/memory.h" #include "olm/message.h" #include "olm/pickle.h" #include "olm/pickle_encoding.h" @@ -60,7 +61,7 @@ const char *olm_inbound_group_session_last_error( size_t olm_clear_inbound_group_session( OlmInboundGroupSession *session ) { - memset(session, 0, sizeof(OlmInboundGroupSession)); + _olm_unset(session, sizeof(OlmInboundGroupSession)); return sizeof(OlmInboundGroupSession); } @@ -85,7 +86,7 @@ size_t olm_init_inbound_group_session( _olm_decode_base64(session_key, session_key_length, key_buf); megolm_init(&session->initial_ratchet, key_buf, message_index); megolm_init(&session->latest_ratchet, key_buf, message_index); - memset(key_buf, 0, MEGOLM_RATCHET_LENGTH); + _olm_unset(key_buf, MEGOLM_RATCHET_LENGTH); return 0; } @@ -265,7 +266,7 @@ size_t olm_group_decrypt( plaintext, max_plaintext_length ); - memset(&tmp_megolm, 0, sizeof(tmp_megolm)); + _olm_unset(&tmp_megolm, sizeof(tmp_megolm)); if (r == (size_t)-1) { session->last_error = OLM_BAD_MESSAGE_MAC; return r; -- cgit v1.2.3 From a919a149fbb192e3fae7aba921ca28e02d9c0d10 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Tue, 24 May 2016 14:54:01 +0100 Subject: Update megolm_cipher as a global struct Initialise megolm_cipher via the preprocessor macro, instead of with a function. --- src/inbound_group_session.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) (limited to 'src/inbound_group_session.c') diff --git a/src/inbound_group_session.c b/src/inbound_group_session.c index 6cded75..b6894c1 100644 --- a/src/inbound_group_session.c +++ b/src/inbound_group_session.c @@ -168,7 +168,6 @@ size_t olm_group_decrypt_max_plaintext_length( uint8_t * message, size_t message_length ) { size_t r; - const struct _olm_cipher *cipher = megolm_cipher(); struct _OlmDecodeGroupMessageResults decoded_results; r = _olm_decode_base64(message, message_length, message); @@ -179,7 +178,7 @@ size_t olm_group_decrypt_max_plaintext_length( _olm_decode_group_message( message, message_length, - cipher->ops->mac_length(cipher), + megolm_cipher->ops->mac_length(megolm_cipher), &decoded_results); if (decoded_results.version != OLM_PROTOCOL_VERSION) { @@ -192,8 +191,8 @@ size_t olm_group_decrypt_max_plaintext_length( return (size_t)-1; } - return cipher->ops->decrypt_max_plaintext_length( - cipher, decoded_results.ciphertext_length); + return megolm_cipher->ops->decrypt_max_plaintext_length( + megolm_cipher, decoded_results.ciphertext_length); } @@ -203,7 +202,6 @@ size_t olm_group_decrypt( uint8_t * plaintext, size_t max_plaintext_length ) { struct _OlmDecodeGroupMessageResults decoded_results; - const struct _olm_cipher *cipher = megolm_cipher(); size_t max_length, raw_message_length, r; Megolm *megolm; Megolm tmp_megolm; @@ -216,7 +214,7 @@ size_t olm_group_decrypt( _olm_decode_group_message( message, raw_message_length, - cipher->ops->mac_length(cipher), + megolm_cipher->ops->mac_length(megolm_cipher), &decoded_results); if (decoded_results.version != OLM_PROTOCOL_VERSION) { @@ -231,8 +229,8 @@ size_t olm_group_decrypt( return (size_t)-1; } - max_length = cipher->ops->decrypt_max_plaintext_length( - cipher, + max_length = megolm_cipher->ops->decrypt_max_plaintext_length( + megolm_cipher, decoded_results.ciphertext_length ); if (max_plaintext_length < max_length) { @@ -258,8 +256,8 @@ size_t olm_group_decrypt( megolm_advance_to(megolm, decoded_results.message_index); /* now try checking the mac, and decrypting */ - r = cipher->ops->decrypt( - cipher, + r = megolm_cipher->ops->decrypt( + megolm_cipher, megolm_get_data(megolm), MEGOLM_RATCHET_LENGTH, message, raw_message_length, decoded_results.ciphertext, decoded_results.ciphertext_length, -- cgit v1.2.3 From 1b15465c42a88f750a960a0e73f186245f9bba33 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Tue, 24 May 2016 16:23:19 +0100 Subject: Separate base64ing from the rest of msg encoding Factor the actual message encoding/decoding and encrypting/decrypting out to separate functions from the top-level functions which do the base64-wrangling. This is particularly helpful in the 'outbound' code-path where the offsets required to allow room to base64-encode make the flow hard to see when it's all inline. --- src/inbound_group_session.c | 64 ++++++++++++++++++++++++++++++++------------- 1 file changed, 46 insertions(+), 18 deletions(-) (limited to 'src/inbound_group_session.c') diff --git a/src/inbound_group_session.c b/src/inbound_group_session.c index b6894c1..e171205 100644 --- a/src/inbound_group_session.c +++ b/src/inbound_group_session.c @@ -163,19 +163,15 @@ size_t olm_unpickle_inbound_group_session( return pickled_length; } -size_t olm_group_decrypt_max_plaintext_length( +/** + * get the max plaintext length in an un-base64-ed message + */ +static size_t _decrypt_max_plaintext_length( OlmInboundGroupSession *session, uint8_t * message, size_t message_length ) { - size_t r; struct _OlmDecodeGroupMessageResults decoded_results; - r = _olm_decode_base64(message, message_length, message); - if (r == (size_t)-1) { - session->last_error = OLM_INVALID_BASE64; - return r; - } - _olm_decode_group_message( message, message_length, megolm_cipher->ops->mac_length(megolm_cipher), @@ -195,25 +191,38 @@ size_t olm_group_decrypt_max_plaintext_length( megolm_cipher, decoded_results.ciphertext_length); } +size_t olm_group_decrypt_max_plaintext_length( + OlmInboundGroupSession *session, + uint8_t * message, size_t message_length +) { + size_t raw_length; -size_t olm_group_decrypt( + raw_length = _olm_decode_base64(message, message_length, message); + if (raw_length == (size_t)-1) { + session->last_error = OLM_INVALID_BASE64; + return (size_t)-1; + } + + return _decrypt_max_plaintext_length( + session, message, raw_length + ); +} + +/** + * decrypt an un-base64-ed message + */ +static size_t _decrypt( OlmInboundGroupSession *session, uint8_t * message, size_t message_length, uint8_t * plaintext, size_t max_plaintext_length ) { struct _OlmDecodeGroupMessageResults decoded_results; - size_t max_length, raw_message_length, r; + size_t max_length, r; Megolm *megolm; Megolm tmp_megolm; - raw_message_length = _olm_decode_base64(message, message_length, message); - if (raw_message_length == (size_t)-1) { - session->last_error = OLM_INVALID_BASE64; - return (size_t)-1; - } - _olm_decode_group_message( - message, raw_message_length, + message, message_length, megolm_cipher->ops->mac_length(megolm_cipher), &decoded_results); @@ -259,7 +268,7 @@ size_t olm_group_decrypt( r = megolm_cipher->ops->decrypt( megolm_cipher, megolm_get_data(megolm), MEGOLM_RATCHET_LENGTH, - message, raw_message_length, + message, message_length, decoded_results.ciphertext, decoded_results.ciphertext_length, plaintext, max_plaintext_length ); @@ -272,3 +281,22 @@ size_t olm_group_decrypt( return r; } + +size_t olm_group_decrypt( + OlmInboundGroupSession *session, + uint8_t * message, size_t message_length, + uint8_t * plaintext, size_t max_plaintext_length +) { + size_t raw_message_length; + + raw_message_length = _olm_decode_base64(message, message_length, message); + if (raw_message_length == (size_t)-1) { + session->last_error = OLM_INVALID_BASE64; + return (size_t)-1; + } + + return _decrypt( + session, message, raw_message_length, + plaintext, max_plaintext_length + ); +} -- cgit v1.2.3