diff options
author | George Hazan <george.hazan@gmail.com> | 2016-01-26 08:28:32 +0000 |
---|---|---|
committer | George Hazan <george.hazan@gmail.com> | 2016-01-26 08:28:32 +0000 |
commit | 2a82a9154f9d6e0b5658c82cd346051017339a1e (patch) | |
tree | 8321c2d1897d4026e68064241014eef4a57ddc9d /libs/libaxolotl/src/ratchet.c | |
parent | 80148955f82c205cc94f0112e0fbfe8f91bc4330 (diff) |
libaxolotl - initial commit
git-svn-id: http://svn.miranda-ng.org/main/trunk@16169 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c
Diffstat (limited to 'libs/libaxolotl/src/ratchet.c')
-rw-r--r-- | libs/libaxolotl/src/ratchet.c | 1237 |
1 files changed, 1237 insertions, 0 deletions
diff --git a/libs/libaxolotl/src/ratchet.c b/libs/libaxolotl/src/ratchet.c new file mode 100644 index 0000000000..8b0dc87bec --- /dev/null +++ b/libs/libaxolotl/src/ratchet.c @@ -0,0 +1,1237 @@ +#include "ratchet.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include "protobuf-c/protobuf-c.h" + +#include "axolotl_internal.h" +#include "hkdf.h" +#include "curve.h" +#include "session_state.h" +#include "vpool.h" +#include "LocalStorageProtocol.pb-c.h" + +#define HASH_OUTPUT_SIZE 32 +#define DERIVED_MESSAGE_SECRETS_SIZE 80 +#define DERIVED_ROOT_SECRETS_SIZE 64 + +struct ratchet_chain_key { + axolotl_type_base base; + axolotl_context *global_context; + hkdf_context *kdf; + uint8_t *key; + size_t key_len; + uint32_t index; +}; + +struct ratchet_root_key { + axolotl_type_base base; + axolotl_context *global_context; + hkdf_context *kdf; + uint8_t *key; + size_t key_len; +}; + +struct ratchet_identity_key_pair { + axolotl_type_base base; + ec_public_key *public_key; + ec_private_key *private_key; +}; + +int ratchet_chain_key_create(ratchet_chain_key **chain_key, hkdf_context *kdf, uint8_t *key, size_t key_len, uint32_t index, axolotl_context *global_context) +{ + ratchet_chain_key *result = 0; + + if(!kdf || !key) { + return AX_ERR_INVAL; + } + + result = malloc(sizeof(ratchet_chain_key)); + if(!result) { + return AX_ERR_NOMEM; + } + + AXOLOTL_INIT(result, ratchet_chain_key_destroy); + result->global_context = global_context; + result->kdf = kdf; + + result->key = malloc(key_len); + if(!result->key) { + free(result); + return AX_ERR_NOMEM; + } + memcpy(result->key, key, key_len); + result->key_len = key_len; + + result->index = index; + AXOLOTL_REF(result->kdf); + *chain_key = result; + + return 0; +} + +int ratchet_chain_key_get_key(const ratchet_chain_key *chain_key, axolotl_buffer **buffer) +{ + axolotl_buffer *buf = 0; + uint8_t *data = 0; + + buf = axolotl_buffer_alloc(chain_key->key_len); + if(!buf) { + return AX_ERR_NOMEM; + } + + data = axolotl_buffer_data(buf); + memcpy(data, chain_key->key, chain_key->key_len); + + *buffer = buf; + + return 0; +} + +int ratchet_chain_key_get_key_protobuf(const ratchet_chain_key *chain_key, ProtobufCBinaryData *buffer) +{ + uint8_t *data = 0; + + assert(chain_key); + assert(buffer); + + data = malloc(chain_key->key_len); + if(!data) { + return AX_ERR_NOMEM; + } + + memcpy(data, chain_key->key, chain_key->key_len); + + buffer->data = data; + buffer->len = chain_key->key_len; + return 0; +} + +uint32_t ratchet_chain_key_get_index(const ratchet_chain_key *chain_key) +{ + return chain_key->index; +} + +ssize_t ratchet_chain_key_get_base_material(const ratchet_chain_key *chain_key, uint8_t **material, const uint8_t *seed, size_t seed_len) +{ + int result = 0; + axolotl_buffer *output_buffer = 0; + uint8_t *output = 0; + size_t output_len = 0; + + void *hmac_context = 0; + result = axolotl_hmac_sha256_init(chain_key->global_context, &hmac_context, chain_key->key, chain_key->key_len); + if(result < 0) { + goto complete; + } + + result = axolotl_hmac_sha256_update(chain_key->global_context, hmac_context, seed, seed_len); + if(result < 0) { + goto complete; + } + + result = axolotl_hmac_sha256_final(chain_key->global_context, hmac_context, &output_buffer); + if(result < 0) { + goto complete; + } + + output_len = axolotl_buffer_len(output_buffer); + output = malloc(output_len); + if(!output) { + result = AX_ERR_NOMEM; + goto complete; + } + + memcpy(output, axolotl_buffer_data(output_buffer), output_len); + +complete: + axolotl_hmac_sha256_cleanup(chain_key->global_context, hmac_context); + axolotl_buffer_free(output_buffer); + + if(result >= 0) { + *material = output; + return (ssize_t)output_len; + } + else { + return result; + } +} + +int ratchet_chain_key_get_message_keys(ratchet_chain_key *chain_key, ratchet_message_keys *message_keys) +{ + static const uint8_t message_key_seed = 0x01; + static const char key_material_seed[] = "WhisperMessageKeys"; + uint8_t salt[HASH_OUTPUT_SIZE]; + int result = 0; + ssize_t result_size = 0; + uint8_t *input_key_material = 0; + size_t input_key_material_len = 0; + uint8_t *key_material_data = 0; + size_t key_material_data_len = 0; + + memset(message_keys, 0, sizeof(ratchet_message_keys)); + + result_size = ratchet_chain_key_get_base_material(chain_key, &input_key_material, &message_key_seed, sizeof(message_key_seed)); + if(result_size < 0) { + result = (int)result_size; + axolotl_log(chain_key->global_context, AX_LOG_WARNING, "ratchet_chain_key_get_base_material failed"); + goto complete; + } + input_key_material_len = (size_t)result_size; + + memset(salt, 0, sizeof(salt)); + + result_size = hkdf_derive_secrets(chain_key->kdf, + &key_material_data, + input_key_material, input_key_material_len, + salt, sizeof(salt), + (uint8_t *)key_material_seed, sizeof(key_material_seed) - 1, + DERIVED_MESSAGE_SECRETS_SIZE); + if(result_size < 0) { + result = (int)result_size; + axolotl_log(chain_key->global_context, AX_LOG_WARNING, "hkdf_derive_secrets failed"); + goto complete; + } + key_material_data_len = (size_t)result_size; + + if(key_material_data_len != RATCHET_CIPHER_KEY_LENGTH + RATCHET_MAC_KEY_LENGTH + RATCHET_IV_LENGTH) { + axolotl_log(chain_key->global_context, AX_LOG_WARNING, + "key_material_data length mismatch: %d != %d", + key_material_data_len, (RATCHET_CIPHER_KEY_LENGTH + RATCHET_MAC_KEY_LENGTH + RATCHET_IV_LENGTH)); + result = AX_ERR_UNKNOWN; + goto complete; + } + + memcpy(message_keys->cipher_key, key_material_data, RATCHET_CIPHER_KEY_LENGTH); + memcpy(message_keys->mac_key, key_material_data + RATCHET_CIPHER_KEY_LENGTH, RATCHET_MAC_KEY_LENGTH); + memcpy(message_keys->iv, key_material_data + RATCHET_CIPHER_KEY_LENGTH + RATCHET_MAC_KEY_LENGTH, RATCHET_IV_LENGTH); + message_keys->counter = chain_key->index; + +complete: + if(input_key_material) { + free(input_key_material); + } + if(key_material_data) { + free(key_material_data); + } + if(result < 0) { + return result; + } + else { + return 0; + } +} + +int ratchet_chain_key_create_next(const ratchet_chain_key *chain_key, ratchet_chain_key **next_chain_key) +{ + static const uint8_t chain_key_seed = 0x02; + int result = 0; + ssize_t result_size = 0; + uint8_t *next_key = 0; + size_t next_key_len = 0; + + result_size = ratchet_chain_key_get_base_material(chain_key, &next_key, &chain_key_seed, sizeof(chain_key_seed)); + if(result_size < 0) { + result = (int)result_size; + axolotl_log(chain_key->global_context, AX_LOG_WARNING, "ratchet_chain_key_get_base_material failed"); + goto complete; + } + next_key_len = (size_t)result_size; + + result = ratchet_chain_key_create( + next_chain_key, + chain_key->kdf, + next_key, next_key_len, + chain_key->index + 1, + chain_key->global_context); + +complete: + if(next_key) { + free(next_key); + } + + return result; +} + +void ratchet_chain_key_destroy(axolotl_type_base *type) +{ + ratchet_chain_key *chain_key = (ratchet_chain_key *)type; + AXOLOTL_UNREF(chain_key->kdf); + if(chain_key->key) { + axolotl_explicit_bzero(chain_key->key, chain_key->key_len); + free(chain_key->key); + } + free(chain_key); +} + +int ratchet_root_key_create(ratchet_root_key **root_key, hkdf_context *kdf, const uint8_t *key, size_t key_len, axolotl_context *global_context) +{ + ratchet_root_key *result = 0; + + if(!kdf || !key) { + return AX_ERR_INVAL; + } + + result = malloc(sizeof(ratchet_root_key)); + if(!result) { + return AX_ERR_NOMEM; + } + + AXOLOTL_INIT(result, ratchet_root_key_destroy); + result->global_context = global_context; + result->kdf = kdf; + + result->key = malloc(key_len); + if(!result->key) { + free(result); + return AX_ERR_NOMEM; + } + memcpy(result->key, key, key_len); + result->key_len = key_len; + AXOLOTL_REF(result->kdf); + *root_key = result; + + return 0; +} + +int ratchet_root_key_create_chain(ratchet_root_key *root_key, + ratchet_root_key **new_root_key, ratchet_chain_key **new_chain_key, + ec_public_key *their_ratchet_key, + ec_private_key *our_ratchet_key_private) +{ + static const char key_info[] = "WhisperRatchet"; + int result = 0; + ssize_t result_size = 0; + uint8_t *shared_secret = 0; + size_t shared_secret_len = 0; + uint8_t *derived_secret = 0; + ratchet_root_key *new_root_key_result = 0; + ratchet_chain_key *new_chain_key_result = 0; + + if(!their_ratchet_key || !our_ratchet_key_private) { + return AX_ERR_INVAL; + } + + result = curve_calculate_agreement(&shared_secret, their_ratchet_key, our_ratchet_key_private); + if(result < 0) { + axolotl_log(root_key->global_context, AX_LOG_WARNING, "curve_calculate_agreement failed"); + goto complete; + } + shared_secret_len = (size_t)result; + + result_size = hkdf_derive_secrets(root_key->kdf, &derived_secret, + shared_secret, shared_secret_len, + root_key->key, root_key->key_len, + (uint8_t *)key_info, sizeof(key_info) - 1, + DERIVED_ROOT_SECRETS_SIZE); + if(result_size < 0) { + result = (int)result_size; + axolotl_log(root_key->global_context, AX_LOG_WARNING, "hkdf_derive_secrets failed"); + goto complete; + } + else if(result_size != DERIVED_ROOT_SECRETS_SIZE) { + result = AX_ERR_UNKNOWN; + axolotl_log(root_key->global_context, AX_LOG_WARNING, "hkdf_derive_secrets size mismatch"); + goto complete; + } + + result = ratchet_root_key_create(&new_root_key_result, root_key->kdf, + derived_secret, 32, + root_key->global_context); + if(result < 0) { + axolotl_log(root_key->global_context, AX_LOG_WARNING, "ratchet_root_key_create failed"); + goto complete; + } + + result = ratchet_chain_key_create(&new_chain_key_result, root_key->kdf, + derived_secret + 32, 32, 0, + root_key->global_context); + if(result < 0) { + axolotl_log(root_key->global_context, AX_LOG_WARNING, "ratchet_chain_key_create failed"); + goto complete; + } + +complete: + if(shared_secret) { + free(shared_secret); + } + if(derived_secret) { + free(derived_secret); + } + if(result < 0) { + if(new_root_key_result) { + AXOLOTL_UNREF(new_root_key_result); + } + if(new_chain_key_result) { + AXOLOTL_UNREF(new_chain_key_result); + } + return result; + } + else { + *new_root_key = new_root_key_result; + *new_chain_key = new_chain_key_result; + return 0; + } +} + +int ratchet_root_key_get_key(ratchet_root_key *root_key, axolotl_buffer **buffer) +{ + axolotl_buffer *buf = 0; + uint8_t *data = 0; + + assert(root_key); + + buf = axolotl_buffer_alloc(root_key->key_len); + if(!buf) { + return AX_ERR_NOMEM; + } + + data = axolotl_buffer_data(buf); + memcpy(data, root_key->key, root_key->key_len); + + *buffer = buf; + + return 0; +} + +int ratchet_root_key_get_key_protobuf(const ratchet_root_key *root_key, ProtobufCBinaryData *buffer) +{ + uint8_t *data = 0; + + assert(root_key); + assert(buffer); + + data = malloc(root_key->key_len); + if(!data) { + return AX_ERR_NOMEM; + } + + memcpy(data, root_key->key, root_key->key_len); + + buffer->data = data; + buffer->len = root_key->key_len; + return 0; +} + +int ratchet_root_key_compare(const ratchet_root_key *key1, const ratchet_root_key *key2) +{ + if(key1 == key2) { + return 0; + } + else if(key1 == 0 && key2 != 0) { + return -1; + } + else if(key1 != 0 && key2 == 0) { + return 1; + } + else { + int kdf_compare = hkdf_compare(key1->kdf, key2->kdf); + if(kdf_compare != 0) { + return kdf_compare; + } + else if(key1->key_len < key2->key_len) { + return -1; + } + else if(key1->key_len > key2->key_len) { + return 1; + } + else { + return axolotl_constant_memcmp(key1->key, key2->key, key1->key_len); + } + } +} + +void ratchet_root_key_destroy(axolotl_type_base *type) +{ + ratchet_root_key *root_key = (ratchet_root_key *)type; + AXOLOTL_UNREF(root_key->kdf); + if(root_key->key) { + axolotl_explicit_bzero(root_key->key, root_key->key_len); + free(root_key->key); + } + free(root_key); +} + +int ratchet_identity_key_pair_create( + ratchet_identity_key_pair **key_pair, + ec_public_key *public_key, + ec_private_key *private_key) +{ + ratchet_identity_key_pair *result = malloc(sizeof(ratchet_identity_key_pair)); + if(!result) { + return AX_ERR_NOMEM; + } + + AXOLOTL_INIT(result, ratchet_identity_key_pair_destroy); + AXOLOTL_REF(public_key); + AXOLOTL_REF(private_key); + result->public_key = public_key; + result->private_key = private_key; + + *key_pair = result; + + return 0; +} + +int ratchet_identity_key_pair_serialize(axolotl_buffer **buffer, const ratchet_identity_key_pair *key_pair) +{ + int result = 0; + size_t result_size = 0; + axolotl_buffer *result_buf = 0; + Textsecure__IdentityKeyPairStructure key_structure = TEXTSECURE__IDENTITY_KEY_PAIR_STRUCTURE__INIT; + size_t len = 0; + uint8_t *data = 0; + + result = ec_public_key_serialize_protobuf(&key_structure.publickey, key_pair->public_key); + if(result < 0) { + goto complete; + } + key_structure.has_publickey = 1; + + result = ec_private_key_serialize_protobuf(&key_structure.privatekey, key_pair->private_key); + if(result < 0) { + goto complete; + } + key_structure.has_privatekey = 1; + + len = textsecure__identity_key_pair_structure__get_packed_size(&key_structure); + result_buf = axolotl_buffer_alloc(len); + if(!result_buf) { + result = AX_ERR_NOMEM; + goto complete; + } + + data = axolotl_buffer_data(result_buf); + result_size = textsecure__identity_key_pair_structure__pack(&key_structure, data); + if(result_size != len) { + axolotl_buffer_free(result_buf); + result = AX_ERR_INVALID_PROTO_BUF; + result_buf = 0; + goto complete; + } + +complete: + if(key_structure.has_publickey) { + free(key_structure.publickey.data); + } + if(key_structure.has_privatekey) { + free(key_structure.privatekey.data); + } + if(result >= 0) { + result = 0; + *buffer = result_buf; + } + return result; +} + +int ratchet_identity_key_pair_deserialize(ratchet_identity_key_pair **key_pair, const uint8_t *data, size_t len, axolotl_context *global_context) +{ + int result = 0; + ec_public_key *public_key = 0; + ec_private_key *private_key = 0; + ratchet_identity_key_pair *result_pair = 0; + Textsecure__IdentityKeyPairStructure *key_structure = 0; + + key_structure = textsecure__identity_key_pair_structure__unpack(0, len, data); + if(!key_structure) { + result = AX_ERR_INVALID_PROTO_BUF; + goto complete; + } + + if(!key_structure->has_publickey || !key_structure->has_privatekey) { + result = AX_ERR_INVALID_KEY; + goto complete; + } + + result = curve_decode_point( + &public_key, + key_structure->publickey.data, + key_structure->publickey.len, + global_context); + if(result < 0) { + goto complete; + } + + result = curve_decode_private_point( + &private_key, + key_structure->privatekey.data, + key_structure->privatekey.len, + global_context); + if(result < 0) { + goto complete; + } + + result = ratchet_identity_key_pair_create(&result_pair, + public_key, private_key); + +complete: + AXOLOTL_UNREF(public_key); + AXOLOTL_UNREF(private_key); + if(key_structure) { + textsecure__identity_key_pair_structure__free_unpacked(key_structure, 0); + } + if(result >= 0) { + *key_pair = result_pair; + } + return result; +} + +ec_public_key *ratchet_identity_key_pair_get_public(const ratchet_identity_key_pair *key_pair) +{ + assert(key_pair); + assert(key_pair->public_key); + return key_pair->public_key; +} + +ec_private_key *ratchet_identity_key_pair_get_private(const ratchet_identity_key_pair *key_pair) +{ + assert(key_pair); + assert(key_pair->private_key); + return key_pair->private_key; +} + +void ratchet_identity_key_pair_destroy(axolotl_type_base *type) +{ + ratchet_identity_key_pair *key_pair = (ratchet_identity_key_pair *)type; + AXOLOTL_UNREF(key_pair->public_key); + AXOLOTL_UNREF(key_pair->private_key); + free(key_pair); +} + +struct symmetric_axolotl_parameters +{ + axolotl_type_base base; + ratchet_identity_key_pair *our_identity_key; + ec_key_pair *our_base_key; + ec_key_pair *our_ratchet_key; + ec_public_key *their_base_key; + ec_public_key *their_ratchet_key; + ec_public_key *their_identity_key; +}; + +struct alice_axolotl_parameters +{ + axolotl_type_base base; + ratchet_identity_key_pair *our_identity_key; + ec_key_pair *our_base_key; + ec_public_key *their_identity_key; + ec_public_key *their_signed_pre_key; + ec_public_key *their_one_time_pre_key; /* optional */ + ec_public_key *their_ratchet_key; +}; + +struct bob_axolotl_parameters +{ + axolotl_type_base base; + ratchet_identity_key_pair *our_identity_key; + ec_key_pair *our_signed_pre_key; + ec_key_pair *our_one_time_pre_key; /* optional */ + ec_key_pair *our_ratchet_key; + ec_public_key *their_identity_key; + ec_public_key *their_base_key; +}; + +int symmetric_axolotl_parameters_create( + symmetric_axolotl_parameters **parameters, + ratchet_identity_key_pair *our_identity_key, + ec_key_pair *our_base_key, + ec_key_pair *our_ratchet_key, + ec_public_key *their_base_key, + ec_public_key *their_ratchet_key, + ec_public_key *their_identity_key) +{ + symmetric_axolotl_parameters *result = 0; + + if(!our_identity_key || !our_base_key || !our_ratchet_key + || !their_base_key || !their_ratchet_key || !their_identity_key) { + return AX_ERR_INVAL; + } + + result = malloc(sizeof(symmetric_axolotl_parameters)); + if(!result) { + return AX_ERR_NOMEM; + } + + memset(result, 0, sizeof(symmetric_axolotl_parameters)); + + AXOLOTL_INIT(result, symmetric_axolotl_parameters_destroy); + AXOLOTL_REF(our_identity_key); + AXOLOTL_REF(our_base_key); + AXOLOTL_REF(our_ratchet_key); + AXOLOTL_REF(their_base_key); + AXOLOTL_REF(their_ratchet_key); + AXOLOTL_REF(their_identity_key); + result->our_identity_key = our_identity_key; + result->our_base_key = our_base_key; + result->our_ratchet_key = our_ratchet_key; + result->their_base_key = their_base_key; + result->their_ratchet_key = their_ratchet_key; + result->their_identity_key = their_identity_key; + + *parameters = result; + return 0; +} + +ratchet_identity_key_pair *symmetric_axolotl_parameters_get_our_identity_key(const symmetric_axolotl_parameters *parameters) +{ + assert(parameters); + return parameters->our_identity_key; +} + +ec_key_pair *symmetric_axolotl_parameters_get_our_base_key(const symmetric_axolotl_parameters *parameters) +{ + assert(parameters); + return parameters->our_base_key; +} + +ec_key_pair *symmetric_axolotl_parameters_get_our_ratchet_key(const symmetric_axolotl_parameters *parameters) +{ + assert(parameters); + return parameters->our_ratchet_key; +} + +ec_public_key *symmetric_axolotl_parameters_get_their_base_key(const symmetric_axolotl_parameters *parameters) +{ + assert(parameters); + return parameters->their_base_key; +} + +ec_public_key *symmetric_axolotl_parameters_get_their_ratchet_key(const symmetric_axolotl_parameters *parameters) +{ + assert(parameters); + return parameters->their_ratchet_key; +} + +ec_public_key *symmetric_axolotl_parameters_get_their_identity_key(const symmetric_axolotl_parameters *parameters) +{ + assert(parameters); + return parameters->their_identity_key; +} + +void symmetric_axolotl_parameters_destroy(axolotl_type_base *type) +{ + symmetric_axolotl_parameters *parameters = (symmetric_axolotl_parameters *)type; + + AXOLOTL_UNREF(parameters->our_identity_key); + AXOLOTL_UNREF(parameters->our_base_key); + AXOLOTL_UNREF(parameters->our_ratchet_key); + AXOLOTL_UNREF(parameters->their_base_key); + AXOLOTL_UNREF(parameters->their_ratchet_key); + AXOLOTL_UNREF(parameters->their_identity_key); + + free(parameters); +} + +int alice_axolotl_parameters_create( + alice_axolotl_parameters **parameters, + ratchet_identity_key_pair *our_identity_key, + ec_key_pair *our_base_key, + ec_public_key *their_identity_key, + ec_public_key *their_signed_pre_key, + ec_public_key *their_one_time_pre_key, + ec_public_key *their_ratchet_key) +{ + alice_axolotl_parameters *result = 0; + + /* Only "their_one_time_pre_key" is allowed to be null */ + if(!our_identity_key || !our_base_key || !their_identity_key + || !their_signed_pre_key || !their_ratchet_key) { + return AX_ERR_INVAL; + } + + result = malloc(sizeof(alice_axolotl_parameters)); + if(!result) { + return AX_ERR_NOMEM; + } + + memset(result, 0, sizeof(alice_axolotl_parameters)); + + AXOLOTL_INIT(result, alice_axolotl_parameters_destroy); + AXOLOTL_REF(our_identity_key); + AXOLOTL_REF(our_base_key); + AXOLOTL_REF(their_identity_key); + AXOLOTL_REF(their_signed_pre_key); + AXOLOTL_REF(their_ratchet_key); + result->our_identity_key = our_identity_key; + result->our_base_key = our_base_key; + result->their_identity_key = their_identity_key; + result->their_signed_pre_key = their_signed_pre_key; + result->their_ratchet_key = their_ratchet_key; + + if(their_one_time_pre_key) { + AXOLOTL_REF(their_one_time_pre_key); + result->their_one_time_pre_key = their_one_time_pre_key; + } + + *parameters = result; + return 0; +} + +void alice_axolotl_parameters_destroy(axolotl_type_base *type) +{ + alice_axolotl_parameters *parameters = (alice_axolotl_parameters *)type; + + AXOLOTL_UNREF(parameters->our_identity_key); + AXOLOTL_UNREF(parameters->our_base_key); + AXOLOTL_UNREF(parameters->their_identity_key); + AXOLOTL_UNREF(parameters->their_signed_pre_key); + AXOLOTL_UNREF(parameters->their_ratchet_key); + + if(parameters->their_one_time_pre_key) { + AXOLOTL_UNREF(parameters->their_one_time_pre_key); + } + + free(parameters); +} + +int bob_axolotl_parameters_create( + bob_axolotl_parameters **parameters, + ratchet_identity_key_pair *our_identity_key, + ec_key_pair *our_signed_pre_key, + ec_key_pair *our_one_time_pre_key, + ec_key_pair *our_ratchet_key, + ec_public_key *their_identity_key, + ec_public_key *their_base_key) +{ + bob_axolotl_parameters *result = 0; + + /* Only "our_one_time_pre_key" is allowed to be null */ + if(!our_identity_key || !our_signed_pre_key || !our_ratchet_key + || !their_identity_key || !their_base_key) { + return AX_ERR_INVAL; + } + + result = malloc(sizeof(bob_axolotl_parameters)); + if(!result) { + return AX_ERR_NOMEM; + } + + memset(result, 0, sizeof(bob_axolotl_parameters)); + + AXOLOTL_INIT(result, bob_axolotl_parameters_destroy); + AXOLOTL_REF(our_identity_key); + AXOLOTL_REF(our_signed_pre_key); + AXOLOTL_REF(our_ratchet_key); + AXOLOTL_REF(their_identity_key); + AXOLOTL_REF(their_base_key); + result->our_identity_key = our_identity_key; + result->our_signed_pre_key = our_signed_pre_key; + result->our_ratchet_key = our_ratchet_key; + result->their_identity_key = their_identity_key; + result->their_base_key = their_base_key; + + if(our_one_time_pre_key) { + AXOLOTL_REF(our_one_time_pre_key); + result->our_one_time_pre_key = our_one_time_pre_key; + } + + *parameters = result; + return 0; +} + +void bob_axolotl_parameters_destroy(axolotl_type_base *type) +{ + bob_axolotl_parameters *parameters = (bob_axolotl_parameters *)type; + + AXOLOTL_UNREF(parameters->our_identity_key); + AXOLOTL_UNREF(parameters->our_signed_pre_key); + AXOLOTL_UNREF(parameters->our_ratchet_key); + AXOLOTL_UNREF(parameters->their_identity_key); + AXOLOTL_UNREF(parameters->their_base_key); + + if(parameters->our_one_time_pre_key) { + AXOLOTL_UNREF(parameters->our_one_time_pre_key); + } + + free(parameters); +} + +int ratcheting_session_calculate_derived_keys(ratchet_root_key **root_key, ratchet_chain_key **chain_key, + uint32_t version, uint8_t *secret, size_t secret_len, axolotl_context *global_context) +{ + int result = 0; + ssize_t result_size = 0; + hkdf_context *kdf = 0; + ratchet_root_key *root_key_result = 0; + ratchet_chain_key *chain_key_result = 0; + uint8_t *output = 0; + uint8_t salt[HASH_OUTPUT_SIZE]; + static const char key_info[] = "WhisperText"; + + result = hkdf_create(&kdf, (int)version, global_context); + if(result < 0) { + goto complete; + } + + memset(salt, 0, sizeof(salt)); + + result_size = hkdf_derive_secrets(kdf, &output, + secret, secret_len, + salt, sizeof(salt), + (uint8_t *)key_info, sizeof(key_info) - 1, 64); + if(result_size != 64) { + result = AX_ERR_UNKNOWN; + goto complete; + } + + result = ratchet_root_key_create(&root_key_result, kdf, output, 32, global_context); + if(result < 0) { + goto complete; + } + + result = ratchet_chain_key_create(&chain_key_result, kdf, output + 32, 32, 0, global_context); + if(result < 0) { + goto complete; + } + +complete: + if(kdf) { + AXOLOTL_UNREF(kdf); + } + if(output) { + free(output); + } + + if(result < 0) { + if(root_key_result) { + AXOLOTL_UNREF(root_key_result); + } + if(chain_key_result) { + AXOLOTL_UNREF(chain_key_result); + } + } + else { + *root_key = root_key_result; + *chain_key = chain_key_result; + } + + return result; +} + +int ratcheting_session_symmetric_is_alice(symmetric_axolotl_parameters *parameters) +{ + //FIXME Java code checks if our_base_key < their_base_key + // This comparison may not return the same result. However, we should find + // out whether the Java code was doing the right thing and why. + return ec_public_key_memcmp( + ec_key_pair_get_public(parameters->our_base_key), + parameters->their_base_key) < 0; +} + +int ratcheting_session_symmetric_initialize( + session_state *state, uint32_t version, + symmetric_axolotl_parameters *parameters, + axolotl_context *global_context) +{ + int result = 0; + + assert(state); + assert(parameters); + assert(global_context); + + if(ratcheting_session_symmetric_is_alice(parameters)) { + alice_axolotl_parameters *alice_parameters = 0; + result = alice_axolotl_parameters_create(&alice_parameters, + parameters->our_identity_key, + parameters->our_base_key, + parameters->their_identity_key, + parameters->their_base_key, + 0, + parameters->their_ratchet_key); + if(result >= 0) { + result = ratcheting_session_alice_initialize(state, version, alice_parameters, global_context); + } + if(alice_parameters) { + AXOLOTL_UNREF(alice_parameters); + } + } + else { + bob_axolotl_parameters *bob_parameters = 0; + result = bob_axolotl_parameters_create(&bob_parameters, + parameters->our_identity_key, + parameters->our_base_key, + 0, + parameters->our_ratchet_key, + parameters->their_identity_key, + parameters->their_base_key); + if(result >= 0) { + result = ratcheting_session_bob_initialize(state, version, bob_parameters, global_context); + } + if(bob_parameters) { + AXOLOTL_UNREF(bob_parameters); + } + } + return result; +} + +int ratcheting_session_alice_initialize( + session_state *state, uint32_t version, + alice_axolotl_parameters *parameters, + axolotl_context *global_context) +{ + int result = 0; + uint8_t *agreement = 0; + int agreement_len = 0; + ec_key_pair *sending_ratchet_key = 0; + ratchet_root_key *derived_root = 0; + ratchet_chain_key *derived_chain = 0; + ratchet_root_key *sending_chain_root = 0; + ratchet_chain_key *sending_chain_key = 0; + struct vpool vp; + uint8_t *secret = 0; + size_t secret_len = 0; + + assert(state); + assert(parameters); + assert(global_context); + + vpool_init(&vp, 1024, 0); + + result = curve_generate_key_pair(global_context, &sending_ratchet_key); + if(result < 0) { + goto complete; + } + + if(version >= 3) { + uint8_t discontinuity_data[32]; + memset(discontinuity_data, 0xFF, sizeof(discontinuity_data)); + if(!vpool_insert(&vp, vpool_get_length(&vp), discontinuity_data, sizeof(discontinuity_data))) { + result = AX_ERR_NOMEM; + goto complete; + } + } + + agreement_len = curve_calculate_agreement(&agreement, + parameters->their_signed_pre_key, parameters->our_identity_key->private_key); + if(agreement_len < 0) { + result = agreement_len; + goto complete; + } + if(vpool_insert(&vp, vpool_get_length(&vp), agreement, (size_t)agreement_len)) { + free(agreement); agreement = 0; agreement_len = 0; + } + else { + result = AX_ERR_NOMEM; + goto complete; + } + + agreement_len = curve_calculate_agreement(&agreement, + parameters->their_identity_key, ec_key_pair_get_private(parameters->our_base_key)); + if(agreement_len < 0) { + result = agreement_len; + goto complete; + } + if(vpool_insert(&vp, vpool_get_length(&vp), agreement, (size_t)agreement_len)) { + free(agreement); agreement = 0; agreement_len = 0; + } + else { + result = AX_ERR_NOMEM; + goto complete; + } + + agreement_len = curve_calculate_agreement(&agreement, + parameters->their_signed_pre_key, ec_key_pair_get_private(parameters->our_base_key)); + if(agreement_len < 0) { + result = agreement_len; + goto complete; + } + if(vpool_insert(&vp, vpool_get_length(&vp), agreement, (size_t)agreement_len)) { + free(agreement); agreement = 0; agreement_len = 0; + } + else { + result = AX_ERR_NOMEM; + goto complete; + } + + if(version >= 3 && parameters->their_one_time_pre_key) { + agreement_len = curve_calculate_agreement(&agreement, + parameters->their_one_time_pre_key, ec_key_pair_get_private(parameters->our_base_key)); + if(agreement_len < 0) { + result = agreement_len; + goto complete; + } + if(vpool_insert(&vp, vpool_get_length(&vp), agreement, (size_t)agreement_len)) { + free(agreement); agreement = 0; agreement_len = 0; + } + else { + result = AX_ERR_NOMEM; + goto complete; + } + } + + if(vpool_is_empty(&vp)) { + result = AX_ERR_UNKNOWN; + goto complete; + } + + secret = vpool_get_buf(&vp); + secret_len = vpool_get_length(&vp); + + result = ratcheting_session_calculate_derived_keys(&derived_root, &derived_chain, version, secret, secret_len, global_context); + if(result < 0) { + goto complete; + } + + result = ratchet_root_key_create_chain(derived_root, + &sending_chain_root, &sending_chain_key, + parameters->their_ratchet_key, + ec_key_pair_get_private(sending_ratchet_key)); + if(result < 0) { + goto complete; + } + +complete: + if(result >= 0) { + session_state_set_session_version(state, version); + session_state_set_remote_identity_key(state, parameters->their_identity_key); + session_state_set_local_identity_key(state, parameters->our_identity_key->public_key); + session_state_add_receiver_chain(state, parameters->their_ratchet_key, derived_chain); + session_state_set_sender_chain(state, sending_ratchet_key, sending_chain_key); + session_state_set_root_key(state, sending_chain_root); + } + + vpool_final(&vp); + if(agreement) { + free(agreement); + } + if(sending_ratchet_key) { + AXOLOTL_UNREF(sending_ratchet_key); + } + if(derived_root) { + AXOLOTL_UNREF(derived_root); + } + if(derived_chain) { + AXOLOTL_UNREF(derived_chain); + } + if(sending_chain_root) { + AXOLOTL_UNREF(sending_chain_root); + } + if(sending_chain_key) { + AXOLOTL_UNREF(sending_chain_key); + } + + return result; +} + +int ratcheting_session_bob_initialize( + session_state *state, uint32_t version, + bob_axolotl_parameters *parameters, + axolotl_context *global_context) +{ + int result = 0; + uint8_t *agreement = 0; + int agreement_len = 0; + ratchet_root_key *derived_root = 0; + ratchet_chain_key *derived_chain = 0; + struct vpool vp; + uint8_t *secret = 0; + size_t secret_len = 0; + + + assert(state); + assert(parameters); + assert(global_context); + + vpool_init(&vp, 1024, 0); + + if(version >= 3) { + uint8_t discontinuity_data[32]; + memset(discontinuity_data, 0xFF, sizeof(discontinuity_data)); + if(!vpool_insert(&vp, vpool_get_length(&vp), discontinuity_data, sizeof(discontinuity_data))) { + result = AX_ERR_NOMEM; + goto complete; + } + } + + agreement_len = curve_calculate_agreement(&agreement, + parameters->their_identity_key, ec_key_pair_get_private(parameters->our_signed_pre_key)); + if(agreement_len < 0) { + result = agreement_len; + goto complete; + } + if(vpool_insert(&vp, vpool_get_length(&vp), agreement, (size_t)agreement_len)) { + free(agreement); agreement = 0; agreement_len = 0; + } + else { + result = AX_ERR_NOMEM; + goto complete; + } + + agreement_len = curve_calculate_agreement(&agreement, + parameters->their_base_key, parameters->our_identity_key->private_key); + if(agreement_len < 0) { + result = agreement_len; + goto complete; + } + if(vpool_insert(&vp, vpool_get_length(&vp), agreement, (size_t)agreement_len)) { + free(agreement); agreement = 0; agreement_len = 0; + } + else { + result = AX_ERR_NOMEM; + goto complete; + } + + agreement_len = curve_calculate_agreement(&agreement, + parameters->their_base_key, ec_key_pair_get_private(parameters->our_signed_pre_key)); + if(agreement_len < 0) { + result = agreement_len; + goto complete; + } + if(vpool_insert(&vp, vpool_get_length(&vp), agreement, (size_t)agreement_len)) { + free(agreement); agreement = 0; agreement_len = 0; + } + else { + result = AX_ERR_NOMEM; + goto complete; + } + + if(version >= 3 && parameters->our_one_time_pre_key) { + agreement_len = curve_calculate_agreement(&agreement, + parameters->their_base_key, ec_key_pair_get_private(parameters->our_one_time_pre_key)); + if(agreement_len < 0) { + result = agreement_len; + goto complete; + } + if(vpool_insert(&vp, vpool_get_length(&vp), agreement, (size_t)agreement_len)) { + free(agreement); agreement = 0; agreement_len = 0; + } + else { + result = AX_ERR_NOMEM; + goto complete; + } + } + + if(vpool_is_empty(&vp)) { + result = AX_ERR_UNKNOWN; + goto complete; + } + + secret = vpool_get_buf(&vp); + secret_len = vpool_get_length(&vp); + + result = ratcheting_session_calculate_derived_keys(&derived_root, &derived_chain, version, secret, secret_len, global_context); + +complete: + if(result >= 0) { + session_state_set_session_version(state, version); + session_state_set_remote_identity_key(state, parameters->their_identity_key); + session_state_set_local_identity_key(state, parameters->our_identity_key->public_key); + session_state_set_sender_chain(state, parameters->our_ratchet_key, derived_chain); + session_state_set_root_key(state, derived_root); + } + + vpool_final(&vp); + if(agreement) { + free(agreement); + } + if(derived_root) { + AXOLOTL_UNREF(derived_root); + } + if(derived_chain) { + AXOLOTL_UNREF(derived_chain); + } + + return result; +} |