diff options
Diffstat (limited to 'src/outbound_group_session.c')
-rw-r--r-- | src/outbound_group_session.c | 361 |
1 files changed, 361 insertions, 0 deletions
diff --git a/src/outbound_group_session.c b/src/outbound_group_session.c new file mode 100644 index 0000000..4e4561a --- /dev/null +++ b/src/outbound_group_session.c @@ -0,0 +1,361 @@ +/* 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/outbound_group_session.h" + +#include <string.h> +#include <sys/time.h> + +#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 1 +#define SESSION_KEY_VERSION 2 + +struct OlmOutboundGroupSession { + /** the Megolm ratchet providing the encryption keys */ + Megolm ratchet; + + /** The ed25519 keypair used for signing the messages */ + struct _olm_ed25519_key_pair signing_key; + + enum OlmErrorCode last_error; +}; + + +size_t olm_outbound_group_session_size() { + return sizeof(OlmOutboundGroupSession); +} + +OlmOutboundGroupSession * olm_outbound_group_session( + void *memory +) { + OlmOutboundGroupSession *session = memory; + olm_clear_outbound_group_session(session); + return session; +} + +const char *olm_outbound_group_session_last_error( + const OlmOutboundGroupSession *session +) { + return _olm_error_to_string(session->last_error); +} + +size_t olm_clear_outbound_group_session( + OlmOutboundGroupSession *session +) { + _olm_unset(session, sizeof(OlmOutboundGroupSession)); + return sizeof(OlmOutboundGroupSession); +} + +static size_t raw_pickle_length( + const OlmOutboundGroupSession *session +) { + size_t length = 0; + length += _olm_pickle_uint32_length(PICKLE_VERSION); + length += megolm_pickle_length(&(session->ratchet)); + length += _olm_pickle_ed25519_key_pair_length(&(session->signing_key)); + return length; +} + +size_t olm_pickle_outbound_group_session_length( + const OlmOutboundGroupSession *session +) { + return _olm_enc_output_length(raw_pickle_length(session)); +} + +size_t olm_pickle_outbound_group_session( + OlmOutboundGroupSession *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->ratchet), pos); + pos = _olm_pickle_ed25519_key_pair(pos, &(session->signing_key)); + + return _olm_enc_output(key, key_length, pickled, raw_length); +} + +size_t olm_unpickle_outbound_group_session( + OlmOutboundGroupSession *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->ratchet), pos, end); + pos = _olm_unpickle_ed25519_key_pair(pos, end, &(session->signing_key)); + + 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_init_outbound_group_session_random_length( + const OlmOutboundGroupSession *session +) { + /* we need data to initialize the megolm ratchet, plus some more for the + * session id. + */ + return MEGOLM_RATCHET_LENGTH + + ED25519_RANDOM_LENGTH; +} + +size_t olm_init_outbound_group_session( + OlmOutboundGroupSession *session, + uint8_t const * random, size_t random_length +) { + if (random_length < olm_init_outbound_group_session_random_length(session)) { + /* Insufficient random data for new session */ + session->last_error = OLM_NOT_ENOUGH_RANDOM; + return (size_t)-1; + } + + megolm_init(&(session->ratchet), random, 0); + random += MEGOLM_RATCHET_LENGTH; + + _olm_crypto_ed25519_generate_key(random, &(session->signing_key)); + random += ED25519_RANDOM_LENGTH; + + return 0; +} + +static size_t raw_message_length( + OlmOutboundGroupSession *session, + size_t plaintext_length) +{ + size_t ciphertext_length, mac_length; + + ciphertext_length = megolm_cipher->ops->encrypt_ciphertext_length( + megolm_cipher, plaintext_length + ); + + mac_length = megolm_cipher->ops->mac_length(megolm_cipher); + + return _olm_encode_group_message_length( + session->ratchet.counter, + ciphertext_length, mac_length, ED25519_SIGNATURE_LENGTH + ); +} + +size_t olm_group_encrypt_message_length( + OlmOutboundGroupSession *session, + size_t plaintext_length +) { + size_t message_length = raw_message_length(session, plaintext_length); + return _olm_encode_base64_length(message_length); +} + +/** write an un-base64-ed message to the buffer */ +static size_t _encrypt( + OlmOutboundGroupSession *session, uint8_t const * plaintext, size_t plaintext_length, + uint8_t * buffer +) { + size_t ciphertext_length, mac_length, message_length; + size_t result; + uint8_t *ciphertext_ptr; + + ciphertext_length = megolm_cipher->ops->encrypt_ciphertext_length( + megolm_cipher, + plaintext_length + ); + + mac_length = megolm_cipher->ops->mac_length(megolm_cipher); + + /* first we build the message structure, then we encrypt + * the plaintext into it. + */ + message_length = _olm_encode_group_message( + OLM_PROTOCOL_VERSION, + session->ratchet.counter, + ciphertext_length, + buffer, + &ciphertext_ptr); + + message_length += mac_length; + + result = megolm_cipher->ops->encrypt( + megolm_cipher, + megolm_get_data(&(session->ratchet)), MEGOLM_RATCHET_LENGTH, + plaintext, plaintext_length, + ciphertext_ptr, ciphertext_length, + buffer, message_length + ); + + if (result == (size_t)-1) { + return result; + } + + megolm_advance(&(session->ratchet)); + + /* sign the whole thing with the ed25519 key. */ + _olm_crypto_ed25519_sign( + &(session->signing_key), + buffer, message_length, + buffer + message_length + ); + + return result; +} + +size_t olm_group_encrypt( + OlmOutboundGroupSession *session, + uint8_t const * plaintext, size_t plaintext_length, + uint8_t * message, size_t max_message_length +) { + size_t rawmsglen; + size_t result; + uint8_t *message_pos; + + rawmsglen = raw_message_length(session, plaintext_length); + + if (max_message_length < _olm_encode_base64_length(rawmsglen)) { + session->last_error = OLM_OUTPUT_BUFFER_TOO_SMALL; + return (size_t)-1; + } + + /* we construct the message at the end of the buffer, so that + * we have room to base64-encode it once we're done. + */ + message_pos = message + _olm_encode_base64_length(rawmsglen) - rawmsglen; + + /* write the message, and encrypt it, at message_pos */ + result = _encrypt(session, plaintext, plaintext_length, message_pos); + if (result == (size_t)-1) { + return result; + } + + /* bas64-encode it */ + return _olm_encode_base64( + message_pos, rawmsglen, message + ); +} + + +size_t olm_outbound_group_session_id_length( + const OlmOutboundGroupSession *session +) { + return _olm_encode_base64_length(GROUP_SESSION_ID_LENGTH); +} + +size_t olm_outbound_group_session_id( + OlmOutboundGroupSession *session, + uint8_t * id, size_t id_length +) { + if (id_length < olm_outbound_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.public_key, GROUP_SESSION_ID_LENGTH, id + ); +} + +uint32_t olm_outbound_group_session_message_index( + OlmOutboundGroupSession *session +) { + return session->ratchet.counter; +} + +#define SESSION_KEY_RAW_LENGTH \ + (1 + 4 + MEGOLM_RATCHET_LENGTH + ED25519_PUBLIC_KEY_LENGTH\ + + ED25519_SIGNATURE_LENGTH) + +size_t olm_outbound_group_session_key_length( + const OlmOutboundGroupSession *session +) { + return _olm_encode_base64_length(SESSION_KEY_RAW_LENGTH); +} + +size_t olm_outbound_group_session_key( + OlmOutboundGroupSession *session, + uint8_t * key, size_t key_length +) { + uint8_t *raw; + uint8_t *ptr; + size_t encoded_length = olm_outbound_group_session_key_length(session); + + if (key_length < encoded_length) { + session->last_error = OLM_OUTPUT_BUFFER_TOO_SMALL; + return (size_t)-1; + } + + /* put the raw data at the end of the output buffer. */ + raw = ptr = key + encoded_length - SESSION_KEY_RAW_LENGTH; + *ptr++ = SESSION_KEY_VERSION; + + uint32_t counter = session->ratchet.counter; + // Encode counter as a big endian 32-bit number. + for (unsigned i = 0; i < 4; i++) { + *ptr++ = 0xFF & (counter >> 24); counter <<= 8; + } + + memcpy(ptr, megolm_get_data(&session->ratchet), MEGOLM_RATCHET_LENGTH); + ptr += MEGOLM_RATCHET_LENGTH; + + memcpy( + ptr, session->signing_key.public_key.public_key, + ED25519_PUBLIC_KEY_LENGTH + ); + ptr += ED25519_PUBLIC_KEY_LENGTH; + + /* sign the whole thing with the ed25519 key. */ + _olm_crypto_ed25519_sign( + &(session->signing_key), + raw, ptr - raw, ptr + ); + + return _olm_encode_base64(raw, SESSION_KEY_RAW_LENGTH, key); +} |