2016-05-17 12:52:06 +02:00
|
|
|
/* 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"
|
2016-09-05 20:49:36 +02:00
|
|
|
#include "olm/crypto.h"
|
2016-05-17 12:52:06 +02:00
|
|
|
#include "olm/error.h"
|
|
|
|
#include "olm/megolm.h"
|
2016-05-20 13:35:59 +02:00
|
|
|
#include "olm/memory.h"
|
2016-05-17 12:52:06 +02:00
|
|
|
#include "olm/message.h"
|
2016-05-17 19:53:00 +02:00
|
|
|
#include "olm/pickle.h"
|
|
|
|
#include "olm/pickle_encoding.h"
|
2016-05-17 12:52:06 +02:00
|
|
|
|
|
|
|
#define OLM_PROTOCOL_VERSION 3
|
|
|
|
#define SESSION_ID_RANDOM_BYTES 4
|
|
|
|
#define GROUP_SESSION_ID_LENGTH (sizeof(struct timeval) + SESSION_ID_RANDOM_BYTES)
|
2016-05-17 19:53:00 +02:00
|
|
|
#define PICKLE_VERSION 1
|
2016-09-05 20:49:36 +02:00
|
|
|
#define SESSION_KEY_VERSION 1
|
2016-05-17 12:52:06 +02:00
|
|
|
|
|
|
|
struct OlmOutboundGroupSession {
|
|
|
|
/** the Megolm ratchet providing the encryption keys */
|
|
|
|
Megolm ratchet;
|
|
|
|
|
2016-09-05 20:49:36 +02:00
|
|
|
/** The ed25519 keypair used for signing the messages */
|
|
|
|
struct _olm_ed25519_key_pair signing_key;
|
|
|
|
|
2016-05-17 12:52:06 +02:00
|
|
|
/** unique identifier for this session */
|
|
|
|
uint8_t session_id[GROUP_SESSION_ID_LENGTH];
|
|
|
|
|
|
|
|
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
|
|
|
|
) {
|
2016-05-20 13:35:59 +02:00
|
|
|
_olm_unset(session, sizeof(OlmOutboundGroupSession));
|
2016-05-17 12:52:06 +02:00
|
|
|
return sizeof(OlmOutboundGroupSession);
|
|
|
|
}
|
|
|
|
|
2016-05-17 19:53:00 +02:00
|
|
|
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));
|
2016-09-05 20:49:36 +02:00
|
|
|
length += _olm_pickle_ed25519_key_pair_length(&(session->signing_key));
|
2016-05-17 19:53:00 +02:00
|
|
|
length += _olm_pickle_bytes_length(session->session_id,
|
|
|
|
GROUP_SESSION_ID_LENGTH);
|
|
|
|
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);
|
2016-09-05 20:49:36 +02:00
|
|
|
pos = _olm_pickle_ed25519_key_pair(pos, &(session->signing_key));
|
2016-05-17 19:53:00 +02:00
|
|
|
pos = _olm_pickle_bytes(pos, session->session_id, GROUP_SESSION_ID_LENGTH);
|
|
|
|
|
|
|
|
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);
|
2016-09-05 20:49:36 +02:00
|
|
|
pos = _olm_unpickle_ed25519_key_pair(pos, end, &(session->signing_key));
|
2016-05-17 19:53:00 +02:00
|
|
|
pos = _olm_unpickle_bytes(pos, end, session->session_id, GROUP_SESSION_ID_LENGTH);
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-05-17 12:52:06 +02:00
|
|
|
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.
|
|
|
|
*/
|
2016-09-05 20:49:36 +02:00
|
|
|
return MEGOLM_RATCHET_LENGTH +
|
|
|
|
ED25519_RANDOM_LENGTH +
|
|
|
|
SESSION_ID_RANDOM_BYTES;
|
2016-05-17 12:52:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
2016-09-05 20:49:36 +02:00
|
|
|
_olm_crypto_ed25519_generate_key(random, &(session->signing_key));
|
|
|
|
random += ED25519_RANDOM_LENGTH;
|
|
|
|
|
2016-05-17 12:52:06 +02:00
|
|
|
/* initialise the session id. This just has to be unique. We use the
|
|
|
|
* current time plus some random data.
|
|
|
|
*/
|
|
|
|
gettimeofday((struct timeval *)(session->session_id), NULL);
|
|
|
|
memcpy((session->session_id) + sizeof(struct timeval),
|
|
|
|
random, SESSION_ID_RANDOM_BYTES);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static size_t raw_message_length(
|
|
|
|
OlmOutboundGroupSession *session,
|
|
|
|
size_t plaintext_length)
|
|
|
|
{
|
|
|
|
size_t ciphertext_length, mac_length;
|
|
|
|
|
2016-05-24 15:54:01 +02:00
|
|
|
ciphertext_length = megolm_cipher->ops->encrypt_ciphertext_length(
|
|
|
|
megolm_cipher, plaintext_length
|
2016-05-17 12:52:06 +02:00
|
|
|
);
|
|
|
|
|
2016-05-24 15:54:01 +02:00
|
|
|
mac_length = megolm_cipher->ops->mac_length(megolm_cipher);
|
2016-05-17 12:52:06 +02:00
|
|
|
|
|
|
|
return _olm_encode_group_message_length(
|
2016-05-25 16:16:14 +02:00
|
|
|
session->ratchet.counter,
|
2016-09-05 20:49:36 +02:00
|
|
|
ciphertext_length, mac_length, ED25519_SIGNATURE_LENGTH
|
|
|
|
);
|
2016-05-17 12:52:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2016-05-24 17:23:19 +02:00
|
|
|
/** 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
|
2016-05-17 12:52:06 +02:00
|
|
|
) {
|
2016-05-24 17:23:19 +02:00
|
|
|
size_t ciphertext_length, mac_length, message_length;
|
2016-05-17 12:52:06 +02:00
|
|
|
size_t result;
|
2016-05-24 17:23:19 +02:00
|
|
|
uint8_t *ciphertext_ptr;
|
2016-05-17 12:52:06 +02:00
|
|
|
|
2016-05-24 15:54:01 +02:00
|
|
|
ciphertext_length = megolm_cipher->ops->encrypt_ciphertext_length(
|
|
|
|
megolm_cipher,
|
2016-05-17 12:52:06 +02:00
|
|
|
plaintext_length
|
|
|
|
);
|
|
|
|
|
2016-05-24 17:23:19 +02:00
|
|
|
mac_length = megolm_cipher->ops->mac_length(megolm_cipher);
|
2016-05-17 12:52:06 +02:00
|
|
|
|
|
|
|
/* first we build the message structure, then we encrypt
|
|
|
|
* the plaintext into it.
|
|
|
|
*/
|
2016-05-24 17:23:19 +02:00
|
|
|
message_length = _olm_encode_group_message(
|
2016-05-17 12:52:06 +02:00
|
|
|
OLM_PROTOCOL_VERSION,
|
|
|
|
session->ratchet.counter,
|
|
|
|
ciphertext_length,
|
2016-05-24 17:23:19 +02:00
|
|
|
buffer,
|
2016-05-17 12:52:06 +02:00
|
|
|
&ciphertext_ptr);
|
|
|
|
|
2016-05-24 17:23:19 +02:00
|
|
|
message_length += mac_length;
|
|
|
|
|
2016-05-24 15:54:01 +02:00
|
|
|
result = megolm_cipher->ops->encrypt(
|
|
|
|
megolm_cipher,
|
2016-05-17 12:52:06 +02:00
|
|
|
megolm_get_data(&(session->ratchet)), MEGOLM_RATCHET_LENGTH,
|
|
|
|
plaintext, plaintext_length,
|
|
|
|
ciphertext_ptr, ciphertext_length,
|
2016-05-24 17:23:19 +02:00
|
|
|
buffer, message_length
|
2016-05-17 12:52:06 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
if (result == (size_t)-1) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
megolm_advance(&(session->ratchet));
|
|
|
|
|
2016-09-05 20:49:36 +02:00
|
|
|
/* sign the whole thing with the ed25519 key. */
|
|
|
|
_olm_crypto_ed25519_sign(
|
|
|
|
&(session->signing_key),
|
|
|
|
buffer, message_length,
|
|
|
|
buffer + message_length
|
|
|
|
);
|
|
|
|
|
2016-05-24 17:23:19 +02:00
|
|
|
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 */
|
2016-05-17 12:52:06 +02:00
|
|
|
return _olm_encode_base64(
|
2016-05-18 18:20:06 +02:00
|
|
|
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->session_id, GROUP_SESSION_ID_LENGTH, id);
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t olm_outbound_group_session_message_index(
|
|
|
|
OlmOutboundGroupSession *session
|
|
|
|
) {
|
|
|
|
return session->ratchet.counter;
|
|
|
|
}
|
|
|
|
|
2016-09-05 20:49:36 +02:00
|
|
|
#define SESSION_KEY_RAW_LENGTH \
|
|
|
|
(1 + MEGOLM_RATCHET_LENGTH + ED25519_PUBLIC_KEY_LENGTH)
|
|
|
|
|
2016-05-18 18:20:06 +02:00
|
|
|
size_t olm_outbound_group_session_key_length(
|
|
|
|
const OlmOutboundGroupSession *session
|
|
|
|
) {
|
2016-09-05 20:49:36 +02:00
|
|
|
return _olm_encode_base64_length(SESSION_KEY_RAW_LENGTH);
|
2016-05-18 18:20:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
size_t olm_outbound_group_session_key(
|
|
|
|
OlmOutboundGroupSession *session,
|
|
|
|
uint8_t * key, size_t key_length
|
|
|
|
) {
|
2016-09-05 20:49:36 +02:00
|
|
|
uint8_t *raw;
|
|
|
|
uint8_t *ptr;
|
|
|
|
size_t encoded_length = olm_outbound_group_session_key_length(session);
|
|
|
|
|
|
|
|
if (key_length < encoded_length) {
|
2016-05-18 18:20:06 +02:00
|
|
|
session->last_error = OLM_OUTPUT_BUFFER_TOO_SMALL;
|
|
|
|
return (size_t)-1;
|
|
|
|
}
|
|
|
|
|
2016-09-05 20:49:36 +02:00
|
|
|
/* put the raw data at the end of the output buffer. */
|
|
|
|
raw = ptr = key + encoded_length - SESSION_KEY_RAW_LENGTH;
|
|
|
|
*ptr++ = SESSION_KEY_VERSION;
|
|
|
|
|
|
|
|
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
|
2016-05-17 12:52:06 +02:00
|
|
|
);
|
2016-09-05 20:49:36 +02:00
|
|
|
ptr += ED25519_PUBLIC_KEY_LENGTH;
|
|
|
|
|
|
|
|
return _olm_encode_base64(raw, SESSION_KEY_RAW_LENGTH, key);
|
2016-05-17 12:52:06 +02:00
|
|
|
}
|