aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/axolotl.cpp432
-rw-r--r--src/crypto.cpp234
-rw-r--r--src/libs.cpp5
-rw-r--r--src/message.cpp170
4 files changed, 841 insertions, 0 deletions
diff --git a/src/axolotl.cpp b/src/axolotl.cpp
new file mode 100644
index 0000000..7384d79
--- /dev/null
+++ b/src/axolotl.cpp
@@ -0,0 +1,432 @@
+#include "axolotl/axolotl.hh"
+#include "axolotl/message.hh"
+
+#include <cstring>
+
+namespace {
+
+std::uint8_t PROTOCOL_VERSION = 3;
+std::size_t MAC_LENGTH = 8;
+std::size_t KEY_LENGTH = axolotl::Curve25519PublicKey::LENGTH;
+std::uint8_t MESSAGE_KEY_SEED[1] = {0x01};
+std::uint8_t CHAIN_KEY_SEED[1] = {0x02};
+std::size_t MAX_MESSAGE_GAP = 2000;
+
+template<typename T>
+void unset(
+ T & value
+) {
+ std::memset(&value, 0, sizeof(T));
+}
+
+
+void create_chain_key(
+ axolotl::SharedKey const & root_key,
+ axolotl::Curve25519KeyPair const & our_key,
+ axolotl::Curve25519PublicKey const & their_key,
+ axolotl::KdfInfo const & info,
+ axolotl::SharedKey & new_root_key,
+ axolotl::ChainKey & new_chain_key
+) {
+ axolotl::SharedKey secret;
+ axolotl::curve25519_shared_secret(our_key, their_key, secret);
+ std::uint8_t derived_secrets[64];
+ axolotl::hkdf_sha256(
+ secret, sizeof(secret),
+ root_key, sizeof(root_key),
+ info.ratchet_info, info.ratchet_info_length,
+ derived_secrets, sizeof(derived_secrets)
+ );
+ std::memcpy(new_root_key, derived_secrets, 32);
+ std::memcpy(new_chain_key.key, derived_secrets + 32, 32);
+ new_chain_key.index = 0;
+ unset(derived_secrets);
+ unset(secret);
+}
+
+
+void advance_chain_key(
+ axolotl::ChainKey const & chain_key,
+ axolotl::ChainKey & new_chain_key
+) {
+ axolotl::hmac_sha256(
+ chain_key.key, sizeof(chain_key.key),
+ CHAIN_KEY_SEED, sizeof(CHAIN_KEY_SEED),
+ new_chain_key.key
+ );
+ new_chain_key.index = chain_key.index + 1;
+}
+
+
+void create_message_keys(
+ axolotl::ChainKey const & chain_key,
+ axolotl::KdfInfo const & info,
+ axolotl::MessageKey & message_key
+) {
+ axolotl::SharedKey secret;
+ axolotl::hmac_sha256(
+ chain_key.key, sizeof(chain_key.key),
+ MESSAGE_KEY_SEED, sizeof(MESSAGE_KEY_SEED),
+ secret
+ );
+ std::uint8_t derived_secrets[80];
+ axolotl::hkdf_sha256(
+ secret, sizeof(secret),
+ NULL, 0,
+ info.message_info, info.message_info_length,
+ derived_secrets, sizeof(derived_secrets)
+ );
+ std::memcpy(message_key.cipher_key.key, derived_secrets, 32);
+ std::memcpy(message_key.mac_key, derived_secrets + 32, 32);
+ std::memcpy(message_key.iv.iv, derived_secrets + 64, 16);
+ message_key.index = chain_key.index;
+ unset(derived_secrets);
+ unset(secret);
+}
+
+
+bool verify_mac(
+ axolotl::MessageKey const & message_key,
+ std::uint8_t const * input,
+ axolotl::MessageReader const & reader
+) {
+ std::uint8_t mac[axolotl::HMAC_SHA256_OUTPUT_LENGTH];
+ axolotl::hmac_sha256(
+ message_key.mac_key, sizeof(message_key.mac_key),
+ input, reader.body_length,
+ mac
+ );
+
+ bool result = std::memcmp(mac, reader.mac, MAC_LENGTH) == 0;
+ unset(mac);
+ return result;
+}
+
+
+bool verify_mac_for_existing_chain(
+ axolotl::Session const & session,
+ axolotl::ChainKey const & chain,
+ std::uint8_t const * input,
+ axolotl::MessageReader const & reader
+) {
+ if (reader.counter < chain.index) {
+ return false;
+ }
+
+ /* Limit the number of hashes we're prepared to compute */
+ if (reader.counter - chain.index > MAX_MESSAGE_GAP) {
+ return false;
+ }
+
+ axolotl::ChainKey new_chain = chain;
+
+ while (new_chain.index < reader.counter) {
+ advance_chain_key(new_chain, new_chain);
+ }
+
+ axolotl::MessageKey message_key;
+ create_message_keys(new_chain, session.kdf_info, message_key);
+
+ bool result = verify_mac(message_key, input, reader);
+ unset(new_chain);
+ return result;
+}
+
+
+bool verify_mac_for_new_chain(
+ axolotl::Session const & session,
+ std::uint8_t const * input,
+ axolotl::MessageReader const & reader
+) {
+ axolotl::SharedKey new_root_key;
+ axolotl::ReceiverChain new_chain;
+
+ /* They shouldn't move to a new chain until we've sent them a message
+ * acknowledging the last one */
+ if (session.sender_chain.empty()) {
+ return false;
+ }
+
+ /* Limit the number of hashes we're prepared to compute */
+ if (reader.counter > MAX_MESSAGE_GAP) {
+ return false;
+ }
+ std::memcpy(
+ new_chain.ratchet_key.public_key, reader.ratchet_key, KEY_LENGTH
+ );
+
+ create_chain_key(
+ session.root_key, session.sender_chain[0].ratchet_key,
+ new_chain.ratchet_key, session.kdf_info,
+ new_root_key, new_chain.chain_key
+ );
+
+ bool result = verify_mac_for_existing_chain(
+ session, new_chain.chain_key, input, reader
+ );
+ unset(new_root_key);
+ unset(new_chain);
+ return result;
+}
+
+} // namespace
+
+
+axolotl::Session::Session(
+ axolotl::KdfInfo const & kdf_info
+) : kdf_info(kdf_info), last_error(axolotl::ErrorCode::SUCCESS) {
+}
+
+
+void axolotl::Session::initialise_as_bob(
+ std::uint8_t const * shared_secret, std::size_t shared_secret_length,
+ axolotl::Curve25519PublicKey const & their_ratchet_key
+) {
+ std::uint8_t derived_secrets[64];
+ axolotl::hkdf_sha256(
+ shared_secret, shared_secret_length,
+ NULL, 0,
+ kdf_info.root_info, kdf_info.root_info_length,
+ derived_secrets, sizeof(derived_secrets)
+ );
+ receiver_chains.insert();
+ std::memcpy(root_key, derived_secrets, 32);
+ std::memcpy(receiver_chains[0].chain_key.key, derived_secrets + 32, 32);
+ receiver_chains[0].ratchet_key = their_ratchet_key;
+ unset(derived_secrets);
+}
+
+
+void axolotl::Session::initialise_as_alice(
+ std::uint8_t const * shared_secret, std::size_t shared_secret_length,
+ axolotl::Curve25519KeyPair const & our_ratchet_key
+) {
+ std::uint8_t derived_secrets[64];
+ axolotl::hkdf_sha256(
+ shared_secret, shared_secret_length,
+ NULL, 0,
+ kdf_info.root_info, kdf_info.root_info_length,
+ derived_secrets, sizeof(derived_secrets)
+ );
+ sender_chain.insert();
+ std::memcpy(root_key, derived_secrets, 32);
+ std::memcpy(sender_chain[0].chain_key.key, derived_secrets + 32, 32);
+ sender_chain[0].ratchet_key = our_ratchet_key;
+ unset(derived_secrets);
+}
+
+
+std::size_t axolotl::Session::encrypt_max_output_length(
+ std::size_t plaintext_length
+) {
+ std::size_t counter = 0;
+ if (!sender_chain.empty()) {
+ counter = sender_chain[0].chain_key.index;
+ }
+ std::size_t padded = axolotl::aes_encrypt_cbc_length(plaintext_length);
+ return axolotl::encode_message_length(
+ counter, KEY_LENGTH, padded, MAC_LENGTH
+ );
+}
+
+
+std::size_t axolotl::Session::encrypt_random_length() {
+ return sender_chain.empty() ? KEY_LENGTH : 0;
+}
+
+
+std::size_t axolotl::Session::encrypt(
+ std::uint8_t const * plaintext, std::size_t plaintext_length,
+ std::uint8_t const * random, std::size_t random_length,
+ std::uint8_t * output, std::size_t max_output_length
+) {
+ if (random_length < encrypt_random_length()) {
+ last_error = axolotl::ErrorCode::NOT_ENOUGH_RANDOM;
+ return std::size_t(-1);
+ }
+ if (max_output_length < encrypt_max_output_length(plaintext_length)) {
+ last_error = axolotl::ErrorCode::OUTPUT_BUFFER_TOO_SMALL;
+ return std::size_t(-1);
+ }
+
+ if (sender_chain.empty()) {
+ sender_chain.insert();
+ axolotl::generate_key(random, sender_chain[0].ratchet_key);
+ create_chain_key(
+ root_key,
+ sender_chain[0].ratchet_key,
+ receiver_chains[0].ratchet_key,
+ kdf_info,
+ root_key, sender_chain[0].chain_key
+ );
+ }
+
+ MessageKey keys;
+ create_message_keys(sender_chain[0].chain_key, kdf_info, keys);
+ advance_chain_key(sender_chain[0].chain_key, sender_chain[0].chain_key);
+
+ std::size_t padded = axolotl::aes_encrypt_cbc_length(plaintext_length);
+ std::uint32_t counter = keys.index;
+ const Curve25519PublicKey &ratchet_key = sender_chain[0].ratchet_key;
+
+ axolotl::MessageWriter writer(axolotl::encode_message(
+ PROTOCOL_VERSION, counter, KEY_LENGTH, padded, output
+ ));
+
+ std::memcpy(writer.ratchet_key, ratchet_key.public_key, KEY_LENGTH);
+
+ axolotl::aes_encrypt_cbc(
+ keys.cipher_key, keys.iv,
+ plaintext, plaintext_length,
+ writer.ciphertext
+ );
+
+ std::uint8_t mac[axolotl::HMAC_SHA256_OUTPUT_LENGTH];
+ axolotl::hmac_sha256(
+ keys.mac_key, sizeof(keys.mac_key),
+ output, writer.body_length,
+ mac
+ );
+ std::memcpy(writer.mac, mac, MAC_LENGTH);
+
+ unset(keys);
+ return writer.body_length + MAC_LENGTH;
+}
+
+
+std::size_t axolotl::Session::decrypt_max_plaintext_length(
+ std::size_t input_length
+) {
+ return input_length;
+}
+
+
+std::size_t axolotl::Session::decrypt(
+ std::uint8_t const * input, std::size_t input_length,
+ std::uint8_t * plaintext, std::size_t max_plaintext_length
+) {
+ if (max_plaintext_length < decrypt_max_plaintext_length(input_length)) {
+ last_error = axolotl::ErrorCode::OUTPUT_BUFFER_TOO_SMALL;
+ return std::size_t(-1);
+ }
+
+ axolotl::MessageReader reader(axolotl::decode_message(
+ input, input_length, MAC_LENGTH
+ ));
+
+ if (reader.version != PROTOCOL_VERSION) {
+ last_error = axolotl::ErrorCode::BAD_MESSAGE_VERSION;
+ return std::size_t(-1);
+ }
+
+ if (reader.body_length == 0 || reader.ratchet_key_length != KEY_LENGTH) {
+ last_error = axolotl::ErrorCode::BAD_MESSAGE_FORMAT;
+ return std::size_t(-1);
+ }
+
+ ReceiverChain * chain = NULL;
+ for (axolotl::ReceiverChain & receiver_chain : receiver_chains) {
+ if (0 == std::memcmp(
+ receiver_chain.ratchet_key.public_key, reader.ratchet_key,
+ KEY_LENGTH
+ )) {
+ chain = &receiver_chain;
+ break;
+ }
+ }
+
+ if (!chain) {
+ if (!verify_mac_for_new_chain(*this, input, reader)) {
+ last_error = axolotl::ErrorCode::BAD_MESSAGE_MAC;
+ return std::size_t(-1);
+ }
+ } else {
+ if (chain->chain_key.index > reader.counter) {
+ /* Chain already advanced beyond the key for this message
+ * Check if the message keys are in the skipped key list. */
+ for (axolotl::SkippedMessageKey & skipped : skipped_message_keys) {
+ if (reader.counter == skipped.message_key.index
+ && 0 == std::memcmp(
+ skipped.ratchet_key.public_key, reader.ratchet_key,
+ KEY_LENGTH
+ )
+ ) {
+ /* Found the key for this message. Check the MAC. */
+ if (!verify_mac(skipped.message_key, input, reader)) {
+ last_error = axolotl::ErrorCode::BAD_MESSAGE_MAC;
+ return std::size_t(-1);
+ }
+
+ std::size_t result = axolotl::aes_decrypt_cbc(
+ skipped.message_key.cipher_key,
+ skipped.message_key.iv,
+ reader.ciphertext, reader.ciphertext_length,
+ plaintext
+ );
+
+ if (result == std::size_t(-1)) {
+ last_error = axolotl::ErrorCode::BAD_MESSAGE_MAC;
+ return result;
+ }
+
+ /* Remove the key from the skipped keys now that we've
+ * decoded the message it corresponds to. */
+ unset(skipped);
+ skipped_message_keys.erase(&skipped);
+ return result;
+ }
+ }
+ /* No matching keys for the message, fail with bad mac */
+ last_error = axolotl::ErrorCode::BAD_MESSAGE_MAC;
+ return std::size_t(-1);
+ } else if (!verify_mac_for_existing_chain(
+ *this, chain->chain_key, input, reader
+ )) {
+ last_error = axolotl::ErrorCode::BAD_MESSAGE_MAC;
+ return std::size_t(-1);
+ }
+ }
+
+ if (!chain) {
+ /* They have started using a new empheral ratchet key.
+ * We need to derive a new set of chain keys.
+ * We can discard our previous empheral ratchet key.
+ * We will generate a new key when we send the next message. */
+ chain = receiver_chains.insert();
+ std::memcpy(
+ chain->ratchet_key.public_key, reader.ratchet_key, KEY_LENGTH
+ );
+ create_chain_key(
+ root_key, sender_chain[0].ratchet_key, chain->ratchet_key,
+ kdf_info, root_key, chain->chain_key
+ );
+ unset(sender_chain[0]);
+ sender_chain.erase(sender_chain.begin());
+ }
+
+ while (chain->chain_key.index < reader.counter) {
+ axolotl::SkippedMessageKey & key = *skipped_message_keys.insert();
+ create_message_keys(chain->chain_key, kdf_info, key.message_key);
+ key.ratchet_key = chain->ratchet_key;
+ advance_chain_key(chain->chain_key, chain->chain_key);
+ }
+
+ axolotl::MessageKey message_key;
+ create_message_keys(chain->chain_key, kdf_info, message_key);
+ std::size_t result = axolotl::aes_decrypt_cbc(
+ message_key.cipher_key,
+ message_key.iv,
+ reader.ciphertext, reader.ciphertext_length,
+ plaintext
+ );
+ unset(message_key);
+
+ advance_chain_key(chain->chain_key, chain->chain_key);
+
+ if (result == std::size_t(-1)) {
+ last_error = axolotl::ErrorCode::BAD_MESSAGE_MAC;
+ return std::size_t(-1);
+ } else {
+ return result;
+ }
+}
diff --git a/src/crypto.cpp b/src/crypto.cpp
new file mode 100644
index 0000000..2397d7c
--- /dev/null
+++ b/src/crypto.cpp
@@ -0,0 +1,234 @@
+#include "axolotl/crypto.hh"
+#include <cstring>
+
+extern "C" {
+
+int curve25519_donna(
+ uint8_t * output,
+ const uint8_t * secret,
+ const uint8_t * basepoint
+);
+
+#include "crypto-algorithms/aes.h"
+#include "crypto-algorithms/sha256.h"
+
+}
+
+
+namespace {
+
+static const std::uint8_t CURVE25519_BASEPOINT[32] = {9};
+static const std::size_t AES_BLOCK_LENGTH = 16;
+static const std::size_t SHA256_HASH_LENGTH = 32;
+static const std::size_t SHA256_BLOCK_LENGTH = 64;
+static const std::uint8_t HKDF_DEFAULT_SALT[32] = {};
+
+
+template<std::size_t block_size>
+inline static void xor_block(
+ std::uint8_t * block,
+ std::uint8_t const * input
+) {
+ for (std::size_t i = 0; i < block_size; ++i) {
+ block[i] ^= input[i];
+ }
+}
+
+
+inline static void hmac_sha256_key(
+ std::uint8_t const * input_key, std::size_t input_key_length,
+ std::uint8_t * hmac_key
+) {
+ std::memset(hmac_key, 0, SHA256_BLOCK_LENGTH);
+ if (input_key_length > SHA256_BLOCK_LENGTH) {
+ ::SHA256_CTX context;
+ ::sha256_init(&context);
+ ::sha256_update(&context, input_key, input_key_length);
+ ::sha256_final(&context, hmac_key);
+ } else {
+ std::memcpy(hmac_key, input_key, input_key_length);
+ }
+}
+
+
+inline void hmac_sha256_init(
+ ::SHA256_CTX * context,
+ std::uint8_t const * hmac_key
+) {
+ std::uint8_t i_pad[SHA256_BLOCK_LENGTH];
+ std::memcpy(i_pad, hmac_key, SHA256_BLOCK_LENGTH);
+ for (std::size_t i = 0; i < SHA256_BLOCK_LENGTH; ++i) {
+ i_pad[i] ^= 0x36;
+ }
+ ::sha256_init(context);
+ ::sha256_update(context, i_pad, SHA256_BLOCK_LENGTH);
+ std::memset(i_pad, 0, sizeof(i_pad));
+}
+
+
+inline void hmac_sha256_final(
+ ::SHA256_CTX * context,
+ std::uint8_t const * hmac_key,
+ std::uint8_t * output
+) {
+ std::uint8_t o_pad[SHA256_BLOCK_LENGTH + SHA256_HASH_LENGTH];
+ std::memcpy(o_pad, hmac_key, SHA256_BLOCK_LENGTH);
+ for (std::size_t i = 0; i < SHA256_BLOCK_LENGTH; ++i) {
+ o_pad[i] ^= 0x5C;
+ }
+ ::sha256_final(context, o_pad + SHA256_BLOCK_LENGTH);
+ ::SHA256_CTX final_context;
+ ::sha256_init(&final_context);
+ ::sha256_update(&final_context, o_pad, sizeof(o_pad));
+ ::sha256_final(&final_context, output);
+ std::memset(o_pad, 0, sizeof(o_pad));
+}
+
+} // namespace
+
+
+void axolotl::generate_key(
+ std::uint8_t const * random_32_bytes,
+ axolotl::Curve25519KeyPair & key_pair
+) {
+ std::memcpy(key_pair.private_key, random_32_bytes, 32);
+ ::curve25519_donna(
+ key_pair.public_key, key_pair.private_key, CURVE25519_BASEPOINT
+ );
+}
+
+
+void axolotl::curve25519_shared_secret(
+ axolotl::Curve25519KeyPair const & our_key,
+ axolotl::Curve25519PublicKey const & their_key,
+ std::uint8_t * output
+) {
+ ::curve25519_donna(output, our_key.private_key, their_key.public_key);
+}
+
+
+std::size_t axolotl::aes_encrypt_cbc_length(
+ std::size_t input_length
+) {
+ return input_length + AES_BLOCK_LENGTH - input_length % AES_BLOCK_LENGTH;
+}
+
+
+void axolotl::aes_encrypt_cbc(
+ axolotl::Aes256Key const & key,
+ axolotl::Aes256Iv const & iv,
+ std::uint8_t const * input, std::size_t input_length,
+ std::uint8_t * output
+) {
+ std::uint32_t key_schedule[60];
+ ::aes_key_setup(key.key, key_schedule, 256);
+ std::uint8_t input_block[AES_BLOCK_LENGTH];
+ std::memcpy(input_block, iv.iv, AES_BLOCK_LENGTH);
+ while (input_length >= AES_BLOCK_LENGTH) {
+ xor_block<AES_BLOCK_LENGTH>(input_block, input);
+ ::aes_encrypt(input_block, output, key_schedule, 256);
+ std::memcpy(input_block, output, AES_BLOCK_LENGTH);
+ input += AES_BLOCK_LENGTH;
+ output += AES_BLOCK_LENGTH;
+ input_length -= AES_BLOCK_LENGTH;
+ }
+ std::size_t i = 0;
+ for (; i < input_length; ++i) {
+ input_block[i] ^= input[i];
+ }
+ for (; i < AES_BLOCK_LENGTH; ++i) {
+ input_block[i] ^= AES_BLOCK_LENGTH - input_length;
+ }
+ ::aes_encrypt(input_block, output, key_schedule, 256);
+ std::memset(key_schedule, 0, sizeof(key_schedule));
+ std::memset(input_block, 0, sizeof(AES_BLOCK_LENGTH));
+}
+
+
+std::size_t axolotl::aes_decrypt_cbc(
+ axolotl::Aes256Key const & key,
+ axolotl::Aes256Iv const & iv,
+ std::uint8_t const * input, std::size_t input_length,
+ std::uint8_t * output
+) {
+ std::uint32_t key_schedule[60];
+ ::aes_key_setup(key.key, key_schedule, 256);
+ for (std::size_t i = 0; i < input_length; i += AES_BLOCK_LENGTH) {
+ ::aes_decrypt(&input[i], &output[i], key_schedule, 256);
+ if (i == 0) {
+ xor_block<AES_BLOCK_LENGTH>(&output[i], iv.iv);
+ } else {
+ xor_block<AES_BLOCK_LENGTH>(&output[i], &input[i - AES_BLOCK_LENGTH]);
+ }
+ }
+ std::memset(key_schedule, 0, sizeof(key_schedule));
+ std::size_t padding = output[input_length - 1];
+ return (padding > input_length) ? std::size_t(-1) : (input_length - padding);
+}
+
+
+void axolotl::sha256(
+ std::uint8_t const * input, std::size_t input_length,
+ std::uint8_t * output
+) {
+ ::SHA256_CTX context;
+ ::sha256_init(&context);
+ ::sha256_update(&context, input, input_length);
+ ::sha256_final(&context, output);
+}
+
+void axolotl::hmac_sha256(
+ std::uint8_t const * key, std::size_t key_length,
+ std::uint8_t const * input, std::size_t input_length,
+ std::uint8_t * output
+) {
+ std::uint8_t hmac_key[SHA256_BLOCK_LENGTH];
+ ::SHA256_CTX context;
+ hmac_sha256_key(key, key_length, hmac_key);
+ hmac_sha256_init(&context, hmac_key);
+ ::sha256_update(&context, input, input_length);
+ hmac_sha256_final(&context, hmac_key, output);
+ std::memset(hmac_key, 0, sizeof(hmac_key));
+}
+
+
+void axolotl::hkdf_sha256(
+ std::uint8_t const * input, std::size_t input_length,
+ std::uint8_t const * salt, std::size_t salt_length,
+ std::uint8_t const * info, std::size_t info_length,
+ std::uint8_t * output, std::size_t output_length
+) {
+ ::SHA256_CTX context;
+ std::uint8_t hmac_key[SHA256_BLOCK_LENGTH];
+ std::uint8_t step_result[SHA256_HASH_LENGTH];
+ std::size_t bytes_remaining = output_length;
+ std::uint8_t iteration = 1;
+ if (!salt) {
+ salt = HKDF_DEFAULT_SALT;
+ salt_length = sizeof(HKDF_DEFAULT_SALT);
+ }
+ /* Expand */
+ hmac_sha256_key(salt, salt_length, hmac_key);
+ hmac_sha256_init(&context, hmac_key);
+ ::sha256_update(&context, input, input_length);
+ hmac_sha256_final(&context, hmac_key, step_result);
+ hmac_sha256_key(step_result, SHA256_HASH_LENGTH, hmac_key);
+
+ /* Extract */
+ hmac_sha256_init(&context, hmac_key);
+ ::sha256_update(&context, info, info_length);
+ ::sha256_update(&context, &iteration, 1);
+ hmac_sha256_final(&context, hmac_key, step_result);
+ while (bytes_remaining > SHA256_HASH_LENGTH) {
+ std::memcpy(output, step_result, SHA256_HASH_LENGTH);
+ output += SHA256_HASH_LENGTH;
+ bytes_remaining -= SHA256_HASH_LENGTH;
+ iteration ++;
+ hmac_sha256_init(&context, hmac_key);
+ ::sha256_update(&context, step_result, SHA256_HASH_LENGTH);
+ ::sha256_update(&context, info, info_length);
+ ::sha256_update(&context, &iteration, 1);
+ hmac_sha256_final(&context, hmac_key, step_result);
+ }
+ std::memcpy(output, step_result, bytes_remaining);
+}
diff --git a/src/libs.cpp b/src/libs.cpp
new file mode 100644
index 0000000..5b9c2aa
--- /dev/null
+++ b/src/libs.cpp
@@ -0,0 +1,5 @@
+extern "C" {
+#include "crypto-algorithms/sha256.c"
+#include "crypto-algorithms/aes.c"
+#include "curve25519-donna/curve25519-donna.c"
+}
diff --git a/src/message.cpp b/src/message.cpp
new file mode 100644
index 0000000..18dbe0e
--- /dev/null
+++ b/src/message.cpp
@@ -0,0 +1,170 @@
+#include "axolotl/message.hh"
+
+namespace {
+
+template<typename T>
+std::size_t varint_length(
+ T value
+) {
+ std::size_t result = 1;
+ while (value > 128U) {
+ ++result;
+ value >>= 7;
+ }
+ return result;
+}
+
+
+template<typename T>
+std::uint8_t * varint_encode(
+ std::uint8_t * output,
+ T value
+) {
+ while (value > 128U) {
+ *(output++) = (0x7F & value) | 0x80;
+ }
+ (*output++) = value;
+ return output;
+}
+
+
+template<typename T>
+T varint_decode(
+ std::uint8_t const * varint_start,
+ std::uint8_t const * varint_end
+) {
+ T value = 0;
+ do {
+ value <<= 7;
+ value |= 0x7F & *(--varint_end);
+ } while (varint_end != varint_start);
+ return value;
+}
+
+
+std::uint8_t const * varint_skip(
+ std::uint8_t const * input,
+ std::uint8_t const * input_end
+) {
+ while (input != input_end) {
+ std::uint8_t tmp = *(input++);
+ if ((tmp & 0x80) == 0) {
+ return input;
+ }
+ }
+ return input;
+}
+
+
+std::size_t varstring_length(
+ std::size_t string_length
+) {
+ return varint_length(string_length) + string_length;
+}
+
+static std::size_t const VERSION_LENGTH = 1;
+static std::uint8_t const RATCHET_KEY_TAG = 012;
+static std::uint8_t const COUNTER_TAG = 020;
+static std::uint8_t const CIPHERTEXT_TAG = 042;
+
+} // namespace
+
+
+std::size_t axolotl::encode_message_length(
+ std::uint32_t counter,
+ std::size_t ratchet_key_length,
+ std::size_t ciphertext_length,
+ std::size_t mac_length
+) {
+ std::size_t length = VERSION_LENGTH;
+ length += 1 + varstring_length(ratchet_key_length);
+ length += 1 + varint_length(counter);
+ length += 1 + varstring_length(ciphertext_length);
+ return length + mac_length;
+}
+
+
+axolotl::MessageWriter axolotl::encode_message(
+ std::uint8_t version,
+ std::uint32_t counter,
+ std::size_t ratchet_key_length,
+ std::size_t ciphertext_length,
+ std::uint8_t * output
+) {
+ axolotl::MessageWriter result;
+ std::uint8_t * pos = output;
+ *(pos++) = version;
+ *(pos++) = COUNTER_TAG;
+ pos = varint_encode(pos, counter);
+ *(pos++) = RATCHET_KEY_TAG;
+ pos = varint_encode(pos, ratchet_key_length);
+ result.ratchet_key = pos;
+ pos += ratchet_key_length;
+ *(pos++) = CIPHERTEXT_TAG;
+ pos = varint_encode(pos, ciphertext_length);
+ result.ciphertext = pos;
+ pos += ciphertext_length;
+ result.body_length = pos - output;
+ result.mac = pos;
+ return result;
+}
+
+
+axolotl::MessageReader axolotl::decode_message(
+ std::uint8_t const * input, std::size_t input_length,
+ std::size_t mac_length
+) {
+ axolotl::MessageReader result;
+ result.body_length = 0;
+ std::uint8_t const * pos = input;
+ std::uint8_t const * end = input + input_length - mac_length;
+ std::uint8_t flags = 0;
+ result.mac = end;
+ if (pos == end) return result;
+ result.version = *(pos++);
+ while (pos != end) {
+ uint8_t tag = *(pos);
+ if (tag == COUNTER_TAG) {
+ ++pos;
+ std::uint8_t const * counter_start = pos;
+ pos = varint_skip(pos, end);
+ result.counter = varint_decode<std::uint32_t>(counter_start, pos);
+ flags |= 1;
+ } else if (tag == RATCHET_KEY_TAG) {
+ ++pos;
+ std::uint8_t const * len_start = pos;
+ pos = varint_skip(pos, end);
+ std::size_t len = varint_decode<std::size_t>(len_start, pos);
+ if (len > end - pos) return result;
+ result.ratchet_key_length = len;
+ result.ratchet_key = pos;
+ pos += len;
+ flags |= 2;
+ } else if (tag == CIPHERTEXT_TAG) {
+ ++pos;
+ std::uint8_t const * len_start = pos;
+ pos = varint_skip(pos, end);
+ std::size_t len = varint_decode<std::size_t>(len_start, pos);
+ if (len > end - pos) return result;
+ result.ciphertext_length = len;
+ result.ciphertext = pos;
+ pos += len;
+ flags |= 4;
+ } else if (tag & 0x7 == 0) {
+ pos = varint_skip(pos, end);
+ pos = varint_skip(pos, end);
+ } else if (tag & 0x7 == 2) {
+ std::uint8_t const * len_start = pos;
+ pos = varint_skip(pos, end);
+ std::size_t len = varint_decode<std::size_t>(len_start, pos);
+ if (len > end - pos) return result;
+ pos += len;
+ } else {
+ return result;
+ }
+ }
+ if (flags == 0x7) {
+ result.body_length = end - input;
+ }
+ return result;
+}