Skip to content
Snippets Groups Projects
Commit 186df912 authored by Mark Haines's avatar Mark Haines
Browse files

Start implementing the ratchet

parent a4e5bf97
No related branches found
No related tags found
No related merge requests found
#include "axololt/crypto.hh"
#include "axololt/list.hh"
namespace axolotl {
typedef std::uint8_t SharedKey[32];
struct ChainKey {
std::uint32_t index;
SharedKey key;
};
struct MessageKey {
std::uint32_t index;
Aes256Key cipher_key;
SharedKey mac_key;
Aes256Iv iv;
};
struct SenderChain {
Curve25519KeyPair ratchet_key;
ChainKey chain_key;
};
struct ReceiverChain {
Curve25519PublicKey ratchet_key;
ChainKey chain_key;
};
struct SkippedMessageKey {
Curve25519PublicKey ratchet_key;
MessageKey message_key;
};
enum struct ErrorCode {
SUCCESS = 0, /*!< There wasn't an error */
NOT_ENOUGH_RANDOM = 1, /*!< Not enough entropy was supplied */
OUTPUT_BUFFER_TOO_SMALL = 2, /*!< Supplied output buffer is too small */
BAD_MESSAGE_VERSION = 3, /*!< The message version is unsupported */
BAD_MESSAGE_FORMAT = 4, /*!< The message couldn't be decoded */
BAD_MESSAGE_MAC = 5, /*!< The message couldn't be decrypted */
};
static std::size_t const MAX_RECEIVER_CHAINS = 5;
static std::size_t const MAX_SKIPPED_MESSAGE_KEYS = 40;
struct KdfInfo {
std::uint8_t const * ratchet_info;
std::size_t ratchet_info_length;
std::uint8_t const * message_info;
std::size_t message_info_length;
};
struct Session {
/** A pair of string to feed into the KDF identifing the application */
KdfInfo kdf_info;
/** The last error that happened encypting or decrypting a message */
ErrorCode last_error;
SharedKey root_key;
List<SenderChain, 1> sender_chain;
List<ReceiverChain, MAX_RECEIVER_CHAINS> reciever_chains;
List<SkippedMessageKey, MAX_SKIPPED_MESSAGE_KEYS> skipped_message_keys;
std::size_t encrypt_max_output_length(
std::size_t plaintext_length
);
std::size_t encrypt_random_length();
std::size_t 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
);
std::size_t decrypt_max_plaintext_length(
std::size_t input_length
);
std::size_t decrypt(
std::uint8_t const * input, std::size_t input_length,
std::uint8_t * plaintext, std::size_t max_plaintext_length
);
};
} // namespace axololt
#include "axolotl/axolotl.hh"
namespace {
std::uint8_t PROTOCOL_VERSION = 3;
std::size_t MAC_LENGTH = 8;
std::size_t KEY_LENGTH = 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;
void create_chain_key(
axolotl::SharedKey const & root_key,
Curve25519KeyPair const & our_key,
Curve25519PublicKey const & their_key,
std::uint8_t const * info, std::size_t info_length,
SharedSecret & new_root_key,
ChainKey & new_chain_key
) {
axolotl::SharedSecret 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, 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;
std::memset(derived_secrets, 0, sizeof(derived_secrets);
std::memset(secret, 0, sizeof(secret));
}
void advance_chain_key(
ChainKey const & chain_key,
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(
ChainKey const & chain_key,
std::uint8_t const * info, std::size_t info_length,
MessageKey & message_key
) {
axolotl::SharedSecret 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),
root_key, sizeof(root_key),
info, info_length,
derived_secrets, sizeof(derived_secrets)
);
std::memcpy(message_key.cipher_key, derived_secrets, 32);
std::memcpy(message_key.mac_key, derived_secrets + 32, 32);
std::memcpy(message_key.iv, derived_secrets + 64, 16);
message_key.index = chain_key.index;
std::memset(derived_secrets, 0, sizeof(derived_secrets);
std::memset(secret, 0, sizeof(secret));
}
bool verify_mac(
MessageKey const & message_key,
std::uint8_t const * input,
axolotl::MessageReader const & reader
) {
std::uint8_t mac[HMAC_SHA256_OUTPUT_LENGTH];
axolotl::hmac_sha256(
keys.mac_key, sizeof(keys.mac_key),
ciphertext, reader.body_length,
mac
);
bool result = std::memcmp(mac, reader.mac, MAC_LENGTH) == 0;
std::memset(&mac, 0, HMAC_SHA256_OUTPUT_LENGTH);
return result;
}
bool verify_mac_for_existing_chain(
axolotl::Session const & session,
axolotl::ReceiverChain const & chain,
std::uint8_t const * input,
axolotl::MessageReader const & reader
) {
ReceiverChain new_chain = chain;
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;
}
while (new_chain.index < reader.counter) {
advance_chain_key(new_chain, new_chain);
}
MessageKey message_key;
create_message_keys(
new_chain_key, sender.message_info, sender.message_info_length,
message_key
);
bool result = verify_mac(message_key, input, reader);
std::memset(&new_chain, 0, sizeof(new_chain.ratchet_key);
return result;
}
bool verify_mac_for_new_chain(
axolotl::Session const & session,
std::uint8_t const * input,
axolotl::MessageReader const & reader
) {
SharedSecret new_root_key;
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, reader.ratchet_key, KEY_LENGTH);
create_chain_key(
root_key, sender_chain[0].ratchet_key, new_chain.ratchet_key,
session.kdf_info.ratchet_info, session.kdf_info.ratchet_info_length,
new_root_key, new_chain
);
bool result = verify_mac_for_existing_chain(
session, new_chain, input, reader
);
std::memset(&new_root_key, 0, sizeof(new_root_key));
std::memset(&new_chain, 0, sizeof(new_chain.ratchet_key);
return result;
}
} // namespace
std::size_t axolotl::Session::encrypt_max_output_length(
std::size_t plaintext_length
) {
std::size_t key_length = 1 + varstring_length(Curve25519PublicKey::Length);
std::size_t counter = sender_chain.empty() ? 0 : sender_chain[0].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.size() ? Curve25519PublicKey::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()) {
last_error = axolotl::ErrorCode::OUTPUT_BUFFER_TOO_SMALL;
return std::size_t(-1);
}
if (sender_chain.empty()) {
/** create sender chain */
}
MessageKey keys;
/** create message keys and advance chain */
std::size_t padded = axolotl::aes_encrypt_cbc_length(plaintext_length);
std::size_t key_length = Curve25519PublicKey::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, cipher_text
));
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[HMAC_SHA256_OUTPUT_LENGTH];
axolotl::hmac_sha256(
keys.mac_key, sizeof(keys.mac_key),
ciphertext, writer.body_length,
mac
);
std::memcpy(writer.mac, mac, MAC_LENGTH);
return writer.body_length + MAC_LENGTH;
}
std::size_t 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 != Curve25519PublicKey::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, 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->index > reader.counter) {
/* Chain already advanced beyond the key for this message
* Check if the message keys are in the skipped key list. */
for (const axolotl::SkippedMessageKey & skipped
: skipped_message_keys) {
if (reader.counter == skipped.message_key.index
&& 0 == std::memcmp(
skipped.ratchet_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. */
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(*chain, input, reader)) {
last_error = axolotl::ErrorCode::BAD_MESSAGE_MAC;
return std::size_t(-1);
}
}
if (!chain) {
}
}
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