Implementation of an outbound group session
This commit is contained in:
parent
68d3c7bfa9
commit
caaed796ad
11 changed files with 512 additions and 3 deletions
|
@ -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.
|
||||
|
|
72
include/olm/message.h
Normal file
72
include/olm/message.h
Normal file
|
@ -0,0 +1,72 @@
|
|||
/* 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
|
||||
|
|
90
include/olm/outbound_group_session.h
Normal file
90
include/olm/outbound_group_session.h
Normal file
|
@ -0,0 +1,90 @@
|
|||
/* 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_ */
|
14
src/megolm.c
14
src/megolm.c
|
@ -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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* 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);
|
||||
}
|
||||
|
|
183
src/outbound_group_session.c
Normal file
183
src/outbound_group_session.c
Normal file
|
@ -0,0 +1,183 @@
|
|||
/* 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
|
||||
);
|
||||
}
|
56
tests/test_group_session.cpp
Normal file
56
tests/test_group_session.cpp
Normal file
|
@ -0,0 +1,56 @@
|
|||
/* 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
|
||||
}
|
||||
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
/* 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 */
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue