Skip to content
Snippets Groups Projects
Commit caaed796 authored by Richard van der Hoff's avatar Richard van der Hoff
Browse files

Implementation of an outbound group session

parent 68d3c7bf
No related branches found
No related tags found
No related merge requests found
......@@ -34,7 +34,6 @@ enum OlmErrorCode {
/* remember to update the list of string constants in error.c when updating
* this list. */
};
/** get a string representation of the given error code. */
......
......@@ -47,6 +47,14 @@ typedef struct Megolm {
uint32_t counter;
} Megolm;
/**
* Get the cipher used in megolm-backed conversations
*
* (AES256 + SHA256, with keys based on an HKDF with info of MEGOLM_KEYS)
*/
const struct _olm_cipher *megolm_cipher();
/**
* initialize the megolm ratchet. random_data should be at least
* MEGOLM_RATCHET_LENGTH bytes of randomness.
......
/* 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.
*/
/**
* functions for encoding and decoding messages in the Olm protocol.
*
* Some of these functions have only C++ bindings, and are declared in
* message.hh; in time, they should probably be converted to plain C and
* declared here.
*/
#ifndef OLM_MESSAGE_H_
#define OLM_MESSAGE_H_
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* The length of the buffer needed to hold a group message.
*/
size_t _olm_encode_group_message_length(
size_t group_session_id_length,
uint32_t chain_index,
size_t ciphertext_length,
size_t mac_length
);
/**
* Writes the message headers into the output buffer.
*
* version: version number of the olm protocol
* session_id: group session identifier
* session_id_length: length of session_id
* chain_index: message index
* ciphertext_length: length of the ciphertext
* output: where to write the output. Should be at least
* olm_encode_group_message_length() bytes long.
* ciphertext_ptr: returns the address that the ciphertext
* should be written to, followed by the MAC.
*/
void _olm_encode_group_message(
uint8_t version,
const uint8_t *session_id,
size_t session_id_length,
uint32_t chain_index,
size_t ciphertext_length,
uint8_t *output,
uint8_t **ciphertext_ptr
);
#ifdef __cplusplus
} // extern "C"
#endif
#endif /* OLM_MESSAGE_H_ */
......@@ -12,6 +12,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* functions for encoding and decoding messages in the Olm protocol.
*
* Some of these functions have plain-C bindings, and are declared in
* message.h; in time, all of the functions declared here should probably be
* converted to plain C and moved to message.h.
*/
#include "message.h"
#include <cstddef>
#include <cstdint>
......
......@@ -19,6 +19,8 @@
#include <stddef.h>
#include <stdint.h>
#include "olm/outbound_group_session.h"
#ifdef __cplusplus
extern "C" {
#endif
......
/* 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.
*/
#ifndef OLM_OUTBOUND_GROUP_SESSION_H_
#define OLM_OUTBOUND_GROUP_SESSION_H_
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct OlmOutboundGroupSession OlmOutboundGroupSession;
/** get the size of an outbound group session, in bytes. */
size_t olm_outbound_group_session_size();
/**
* Initialise an outbound group session object using the supplied memory
* The supplied memory should be at least olm_outbound_group_session_size()
* bytes.
*/
OlmOutboundGroupSession * olm_outbound_group_session(
void *memory
);
/**
* A null terminated string describing the most recent error to happen to a
* group session */
const char *olm_outbound_group_session_last_error(
const OlmOutboundGroupSession *session
);
/** Clears the memory used to back this group session */
size_t olm_clear_outbound_group_session(
OlmOutboundGroupSession *session
);
/** The number of random bytes needed to create an outbound group session */
size_t olm_init_outbound_group_session_random_length(
const OlmOutboundGroupSession *session
);
/**
* Start a new outbound group session. Returns std::size_t(-1) on failure. On
* failure last_error will be set with an error code. The last_error will be
* NOT_ENOUGH_RANDOM if the number of random bytes was too small.
*/
size_t olm_init_outbound_group_session(
OlmOutboundGroupSession *session,
uint8_t const * random, size_t random_length
);
/**
* The number of bytes that will be created by encrypting a message
*/
size_t olm_group_encrypt_message_length(
OlmOutboundGroupSession *session,
size_t plaintext_length
);
/**
* Encrypt some plain-text. Returns the length of the encrypted message or
* std::size_t(-1) on failure. On failure last_error will be set with an
* error code. The last_error will be OUTPUT_BUFFER_TOO_SMALL if the output
* buffer is too small.
*/
size_t olm_group_encrypt(
OlmOutboundGroupSession *session,
uint8_t const * plaintext, size_t plaintext_length,
uint8_t * message, size_t message_length
);
#ifdef __cplusplus
} // extern "C"
#endif
#endif /* OLM_OUTBOUND_GROUP_SESSION_H_ */
......@@ -18,8 +18,22 @@
#include <string.h>
#include "olm/cipher.h"
#include "olm/crypto.h"
const struct _olm_cipher *megolm_cipher() {
static const uint8_t CIPHER_KDF_INFO[] = "MEGOLM_KEYS";
static struct _olm_cipher *cipher;
static struct _olm_cipher_aes_sha_256 OLM_CIPHER;
if (!cipher) {
cipher = _olm_cipher_aes_sha_256_init(
&OLM_CIPHER,
CIPHER_KDF_INFO, sizeof(CIPHER_KDF_INFO) - 1
);
}
return cipher;
}
/* the seeds used in the HMAC-SHA-256 functions for each part of the ratchet.
*/
#define HASH_KEY_SEED_LENGTH 1
......
/* Copyright 2015 OpenMarket Ltd
/* Copyright 2015-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.
......@@ -325,3 +325,41 @@ void olm::decode_one_time_key_message(
unknown = pos;
}
}
static std::uint8_t const GROUP_SESSION_ID_TAG = 052;
size_t _olm_encode_group_message_length(
size_t group_session_id_length,
uint32_t chain_index,
size_t ciphertext_length,
size_t mac_length
) {
size_t length = VERSION_LENGTH;
length += 1 + varstring_length(group_session_id_length);
length += 1 + varint_length(chain_index);
length += 1 + varstring_length(ciphertext_length);
length += mac_length;
return length;
}
void _olm_encode_group_message(
uint8_t version,
const uint8_t *session_id,
size_t session_id_length,
uint32_t chain_index,
size_t ciphertext_length,
uint8_t *output,
uint8_t **ciphertext_ptr
) {
std::uint8_t * pos = output;
std::uint8_t * session_id_pos;
*(pos++) = version;
pos = encode(pos, GROUP_SESSION_ID_TAG, session_id_pos, session_id_length);
std::memcpy(session_id_pos, session_id, session_id_length);
pos = encode(pos, COUNTER_TAG, chain_index);
pos = encode(pos, CIPHERTEXT_TAG, *ciphertext_ptr, ciphertext_length);
}
/* 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/error.h"
#include "olm/megolm.h"
#include "olm/message.h"
#define OLM_PROTOCOL_VERSION 3
#define SESSION_ID_RANDOM_BYTES 4
#define GROUP_SESSION_ID_LENGTH (sizeof(struct timeval) + SESSION_ID_RANDOM_BYTES)
struct OlmOutboundGroupSession {
/** the Megolm ratchet providing the encryption keys */
Megolm ratchet;
/** 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
) {
memset(session, 0, sizeof(OlmOutboundGroupSession));
return sizeof(OlmOutboundGroupSession);
}
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 + SESSION_ID_RANDOM_BYTES;
}
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;
/* 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;
const struct _olm_cipher *cipher = megolm_cipher();
ciphertext_length = cipher->ops->encrypt_ciphertext_length(
cipher, plaintext_length
);
mac_length = cipher->ops->mac_length(cipher);
return _olm_encode_group_message_length(
GROUP_SESSION_ID_LENGTH, session->ratchet.counter,
ciphertext_length, mac_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);
}
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 ciphertext_length;
size_t rawmsglen;
size_t result;
uint8_t *ciphertext_ptr, *message_pos;
const struct _olm_cipher *cipher = megolm_cipher();
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;
}
ciphertext_length = cipher->ops->encrypt_ciphertext_length(
cipher,
plaintext_length
);
/* 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;
/* first we build the message structure, then we encrypt
* the plaintext into it.
*/
_olm_encode_group_message(
OLM_PROTOCOL_VERSION,
session->session_id, GROUP_SESSION_ID_LENGTH,
session->ratchet.counter,
ciphertext_length,
message_pos,
&ciphertext_ptr);
result = cipher->ops->encrypt(
cipher,
megolm_get_data(&(session->ratchet)), MEGOLM_RATCHET_LENGTH,
plaintext, plaintext_length,
ciphertext_ptr, ciphertext_length,
message_pos, rawmsglen
);
if (result == (size_t)-1) {
return result;
}
megolm_advance(&(session->ratchet));
return _olm_encode_base64(
message_pos, rawmsglen,
message
);
}
/* 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 "unittest.hh"
int main() {
uint8_t random_bytes[] =
"0123456789ABDEF0123456789ABCDEF"
"0123456789ABDEF0123456789ABCDEF"
"0123456789ABDEF0123456789ABCDEF"
"0123456789ABDEF0123456789ABCDEF"
"0123456789ABDEF0123456789ABCDEF";
{
TestCase test_case("Group message send/receive");
size_t size = olm_outbound_group_session_size();
void *memory = alloca(size);
OlmOutboundGroupSession *session = olm_outbound_group_session(memory);
assert_equals((size_t)132,
olm_init_outbound_group_session_random_length(session));
size_t res = olm_init_outbound_group_session(
session, random_bytes, sizeof(random_bytes));
assert_equals((size_t)0, res);
uint8_t plaintext[] = "Message";
size_t plaintext_length = sizeof(plaintext) - 1;
size_t msglen = olm_group_encrypt_message_length(
session, plaintext_length);
uint8_t *msg = (uint8_t *)alloca(msglen);
res = olm_group_encrypt(session, plaintext, plaintext_length,
msg, msglen);
assert_equals(msglen, res);
// TODO: decode the message
}
}
/* Copyright 2015 OpenMarket Ltd
/* Copyright 2015-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.
......@@ -62,4 +62,39 @@ assert_equals(message2, output, 35);
} /* Message encode test */
{ /* group message encode test */
TestCase test_case("Group message encode test");
const uint8_t session_id[] = "sessionid";
size_t session_id_len = 9;
size_t length = _olm_encode_group_message_length(
session_id_len, 200, 10, 8);
size_t expected_length = 1 + (2+session_id_len) + (1+2) + (2+10) + 8;
assert_equals(expected_length, length);
uint8_t output[50];
uint8_t *ciphertext_ptr;
_olm_encode_group_message(
3,
session_id, session_id_len,
200, // counter
10, // ciphertext length
output,
&ciphertext_ptr
);
uint8_t expected[] =
"\x03"
"\x2A\x09sessionid"
"\x10\xc8\x01"
"\x22\x0a";
assert_equals(expected, output, sizeof(expected)-1);
assert_equals(output+sizeof(expected)-1, ciphertext_ptr);
} /* group message encode test */
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment