X-Git-Url: https://gitweb.ps.run/matrix_esp_thesis/blobdiff_plain/21c6e8484b0bd05c27e5a91f2884d431926adc61..da776f86b42946715c27edd64f7558b9d5080df1:/ext/olm/src/inbound_group_session.c diff --git a/ext/olm/src/inbound_group_session.c b/ext/olm/src/inbound_group_session.c new file mode 100644 index 0000000..d6f73b7 --- /dev/null +++ b/ext/olm/src/inbound_group_session.c @@ -0,0 +1,540 @@ +/* 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/crypto.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" + + +#define OLM_PROTOCOL_VERSION 3 +#define GROUP_SESSION_ID_LENGTH ED25519_PUBLIC_KEY_LENGTH +#define PICKLE_VERSION 2 +#define SESSION_KEY_VERSION 2 +#define SESSION_EXPORT_VERSION 1 + +struct OlmInboundGroupSession { + /** our earliest known ratchet value */ + Megolm initial_ratchet; + + /** The most recent ratchet value */ + Megolm latest_ratchet; + + /** The ed25519 signing key */ + struct _olm_ed25519_public_key signing_key; + + /** + * Have we ever seen any evidence that this is a valid session? + * (either because the original session share was signed, or because we + * have subsequently successfully decrypted a message) + * + * (We don't do anything with this currently, but we may want to bear it in + * mind when we consider handling key-shares for sessions we already know + * about.) + */ + int signing_key_verified; + + enum OlmErrorCode last_error; +}; + +size_t olm_inbound_group_session_size(void) { + 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); +} + +enum OlmErrorCode olm_inbound_group_session_last_error_code( + const OlmInboundGroupSession *session +) { + return session->last_error; +} + +size_t olm_clear_inbound_group_session( + OlmInboundGroupSession *session +) { + _olm_unset(session, sizeof(OlmInboundGroupSession)); + return sizeof(OlmInboundGroupSession); +} + +#define SESSION_EXPORT_RAW_LENGTH \ + (1 + 4 + MEGOLM_RATCHET_LENGTH + ED25519_PUBLIC_KEY_LENGTH) + +#define SESSION_KEY_RAW_LENGTH \ + (1 + 4 + MEGOLM_RATCHET_LENGTH + ED25519_PUBLIC_KEY_LENGTH\ + + ED25519_SIGNATURE_LENGTH) + +static size_t _init_group_session_keys( + OlmInboundGroupSession *session, + const uint8_t *key_buf, + int export_format +) { + const uint8_t expected_version = + (export_format ? SESSION_EXPORT_VERSION : SESSION_KEY_VERSION); + const uint8_t *ptr = key_buf; + size_t version = *ptr++; + + if (version != expected_version) { + session->last_error = OLM_BAD_SESSION_KEY; + return (size_t)-1; + } + + uint32_t counter = 0; + // Decode counter as a big endian 32-bit number. + for (unsigned i = 0; i < 4; i++) { + counter <<= 8; counter |= *ptr++; + } + + megolm_init(&session->initial_ratchet, ptr, counter); + megolm_init(&session->latest_ratchet, ptr, counter); + + ptr += MEGOLM_RATCHET_LENGTH; + memcpy( + session->signing_key.public_key, ptr, ED25519_PUBLIC_KEY_LENGTH + ); + ptr += ED25519_PUBLIC_KEY_LENGTH; + + if (!export_format) { + if (!_olm_crypto_ed25519_verify(&session->signing_key, key_buf, + ptr - key_buf, ptr)) { + session->last_error = OLM_BAD_SIGNATURE; + return (size_t)-1; + } + + /* signed keyshare */ + session->signing_key_verified = 1; + } + return 0; +} + +size_t olm_init_inbound_group_session( + OlmInboundGroupSession *session, + const uint8_t * session_key, size_t session_key_length +) { + uint8_t key_buf[SESSION_KEY_RAW_LENGTH]; + size_t raw_length = _olm_decode_base64_length(session_key_length); + size_t result; + + if (raw_length == (size_t)-1) { + session->last_error = OLM_INVALID_BASE64; + return (size_t)-1; + } + + if (raw_length != SESSION_KEY_RAW_LENGTH) { + session->last_error = OLM_BAD_SESSION_KEY; + return (size_t)-1; + } + + _olm_decode_base64(session_key, session_key_length, key_buf); + result = _init_group_session_keys(session, key_buf, 0); + _olm_unset(key_buf, SESSION_KEY_RAW_LENGTH); + return result; +} + +size_t olm_import_inbound_group_session( + OlmInboundGroupSession *session, + const uint8_t * session_key, size_t session_key_length +) { + uint8_t key_buf[SESSION_EXPORT_RAW_LENGTH]; + size_t raw_length = _olm_decode_base64_length(session_key_length); + size_t result; + + if (raw_length == (size_t)-1) { + session->last_error = OLM_INVALID_BASE64; + return (size_t)-1; + } + + if (raw_length != SESSION_EXPORT_RAW_LENGTH) { + session->last_error = OLM_BAD_SESSION_KEY; + return (size_t)-1; + } + + _olm_decode_base64(session_key, session_key_length, key_buf); + result = _init_group_session_keys(session, key_buf, 1); + _olm_unset(key_buf, SESSION_EXPORT_RAW_LENGTH); + return result; +} + +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); + length += _olm_pickle_ed25519_public_key_length(&session->signing_key); + length += _olm_pickle_bool_length(session->signing_key_verified); + 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); + pos = _olm_pickle_ed25519_public_key(pos, &session->signing_key); + pos = _olm_pickle_bool(pos, session->signing_key_verified); + + 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); + FAIL_ON_CORRUPTED_PICKLE(pos, session); + + if (pickle_version < 1 || pickle_version > PICKLE_VERSION) { + session->last_error = OLM_UNKNOWN_PICKLE_VERSION; + return (size_t)-1; + } + + pos = megolm_unpickle(&session->initial_ratchet, pos, end); + FAIL_ON_CORRUPTED_PICKLE(pos, session); + + pos = megolm_unpickle(&session->latest_ratchet, pos, end); + FAIL_ON_CORRUPTED_PICKLE(pos, session); + + pos = _olm_unpickle_ed25519_public_key(pos, end, &session->signing_key); + FAIL_ON_CORRUPTED_PICKLE(pos, session); + + if (pickle_version == 1) { + /* pickle v1 had no signing_key_verified field (all keyshares were + * verified at import time) */ + session->signing_key_verified = 1; + } else { + pos = _olm_unpickle_bool(pos, end, &(session->signing_key_verified)); + } + FAIL_ON_CORRUPTED_PICKLE(pos, session); + + if (pos != end) { + /* Input was longer than expected. */ + session->last_error = OLM_PICKLE_EXTRA_DATA; + return (size_t)-1; + } + + return pickled_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 +) { + struct _OlmDecodeGroupMessageResults decoded_results; + + _olm_decode_group_message( + message, message_length, + megolm_cipher->ops->mac_length(megolm_cipher), + ED25519_SIGNATURE_LENGTH, + &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 megolm_cipher->ops->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; + + 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 + ); +} + +/** + * get a copy of the megolm ratchet, advanced + * to the relevant index. Returns 0 on success, -1 on error + */ +static size_t _get_megolm( + OlmInboundGroupSession *session, uint32_t message_index, Megolm *result +) { + /* pick a megolm instance to use. If we're at or beyond the latest ratchet + * value, use that */ + if ((message_index - session->latest_ratchet.counter) < (1U << 31)) { + megolm_advance_to(&session->latest_ratchet, message_index); + *result = session->latest_ratchet; + return 0; + } else if ((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; + } else { + /* otherwise, start from the initial megolm. Take a copy so that we + * don't overwrite the initial megolm */ + *result = session->initial_ratchet; + megolm_advance_to(result, message_index); + return 0; + } +} + +/** + * 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, + uint32_t * message_index +) { + struct _OlmDecodeGroupMessageResults decoded_results; + size_t max_length, r; + Megolm megolm; + + _olm_decode_group_message( + message, message_length, + megolm_cipher->ops->mac_length(megolm_cipher), + ED25519_SIGNATURE_LENGTH, + &decoded_results); + + if (decoded_results.version != OLM_PROTOCOL_VERSION) { + session->last_error = OLM_BAD_MESSAGE_VERSION; + return (size_t)-1; + } + + if (!decoded_results.has_message_index || !decoded_results.ciphertext) { + session->last_error = OLM_BAD_MESSAGE_FORMAT; + return (size_t)-1; + } + + if (message_index != NULL) { + *message_index = decoded_results.message_index; + } + + /* verify the signature. We could do this before decoding the message, but + * we allow for the possibility of future protocol versions which use a + * different signing mechanism; we would rather throw "BAD_MESSAGE_VERSION" + * than "BAD_SIGNATURE" in this case. + */ + message_length -= ED25519_SIGNATURE_LENGTH; + r = _olm_crypto_ed25519_verify( + &session->signing_key, + message, message_length, + message + message_length + ); + if (!r) { + session->last_error = OLM_BAD_SIGNATURE; + return (size_t)-1; + } + + max_length = megolm_cipher->ops->decrypt_max_plaintext_length( + megolm_cipher, + decoded_results.ciphertext_length + ); + if (max_plaintext_length < max_length) { + session->last_error = OLM_OUTPUT_BUFFER_TOO_SMALL; + return (size_t)-1; + } + + r = _get_megolm(session, decoded_results.message_index, &megolm); + if (r == (size_t)-1) { + return r; + } + + /* now try checking the mac, and decrypting */ + r = megolm_cipher->ops->decrypt( + megolm_cipher, + megolm_get_data(&megolm), MEGOLM_RATCHET_LENGTH, + message, message_length, + decoded_results.ciphertext, decoded_results.ciphertext_length, + plaintext, max_plaintext_length + ); + + _olm_unset(&megolm, sizeof(megolm)); + if (r == (size_t)-1) { + session->last_error = OLM_BAD_MESSAGE_MAC; + return r; + } + + /* once we have successfully decrypted a message, set a flag to say the + * session appears valid. */ + session->signing_key_verified = 1; + + return r; +} + +size_t olm_group_decrypt( + OlmInboundGroupSession *session, + uint8_t * message, size_t message_length, + uint8_t * plaintext, size_t max_plaintext_length, + uint32_t * message_index +) { + 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, + message_index + ); +} + +size_t olm_inbound_group_session_id_length( + const OlmInboundGroupSession *session +) { + return _olm_encode_base64_length(GROUP_SESSION_ID_LENGTH); +} + +size_t olm_inbound_group_session_id( + OlmInboundGroupSession *session, + uint8_t * id, size_t id_length +) { + if (id_length < olm_inbound_group_session_id_length(session)) { + session->last_error = OLM_OUTPUT_BUFFER_TOO_SMALL; + return (size_t)-1; + } + + return _olm_encode_base64( + session->signing_key.public_key, GROUP_SESSION_ID_LENGTH, id + ); +} + +uint32_t olm_inbound_group_session_first_known_index( + const OlmInboundGroupSession *session +) { + return session->initial_ratchet.counter; +} + +int olm_inbound_group_session_is_verified( + const OlmInboundGroupSession *session +) { + return session->signing_key_verified; +} + +size_t olm_export_inbound_group_session_length( + const OlmInboundGroupSession *session +) { + return _olm_encode_base64_length(SESSION_EXPORT_RAW_LENGTH); +} + +size_t olm_export_inbound_group_session( + OlmInboundGroupSession *session, + uint8_t * key, size_t key_length, uint32_t message_index +) { + uint8_t *raw; + uint8_t *ptr; + Megolm megolm; + size_t r; + size_t encoded_length = olm_export_inbound_group_session_length(session); + + if (key_length < encoded_length) { + session->last_error = OLM_OUTPUT_BUFFER_TOO_SMALL; + return (size_t)-1; + } + + r = _get_megolm(session, message_index, &megolm); + if (r == (size_t)-1) { + return r; + } + + /* put the raw data at the end of the output buffer. */ + raw = ptr = key + encoded_length - SESSION_EXPORT_RAW_LENGTH; + *ptr++ = SESSION_EXPORT_VERSION; + + // Encode message index as a big endian 32-bit number. + for (unsigned i = 0; i < 4; i++) { + *ptr++ = 0xFF & (message_index >> 24); message_index <<= 8; + } + + memcpy(ptr, megolm_get_data(&megolm), MEGOLM_RATCHET_LENGTH); + ptr += MEGOLM_RATCHET_LENGTH; + + memcpy( + ptr, session->signing_key.public_key, + ED25519_PUBLIC_KEY_LENGTH + ); + ptr += ED25519_PUBLIC_KEY_LENGTH; + + return _olm_encode_base64(raw, SESSION_EXPORT_RAW_LENGTH, key); +}