summaryrefslogtreecommitdiff
path: root/libs/libaxolotl/src/group_cipher.c
diff options
context:
space:
mode:
Diffstat (limited to 'libs/libaxolotl/src/group_cipher.c')
-rw-r--r--libs/libaxolotl/src/group_cipher.c326
1 files changed, 326 insertions, 0 deletions
diff --git a/libs/libaxolotl/src/group_cipher.c b/libs/libaxolotl/src/group_cipher.c
new file mode 100644
index 0000000000..752c2e16d4
--- /dev/null
+++ b/libs/libaxolotl/src/group_cipher.c
@@ -0,0 +1,326 @@
+#include "group_cipher.h"
+
+#include <assert.h>
+#include <string.h>
+#include "axolotl_internal.h"
+#include "protocol.h"
+#include "sender_key.h"
+#include "sender_key_record.h"
+#include "sender_key_state.h"
+
+struct group_cipher
+{
+ axolotl_store_context *store;
+ const axolotl_sender_key_name *sender_key_id;
+ axolotl_context *global_context;
+ int (*decrypt_callback)(group_cipher *cipher, axolotl_buffer *plaintext, void *decrypt_context);
+ int inside_callback;
+ void *user_data;
+};
+
+static int group_cipher_get_sender_key(group_cipher *cipher, sender_message_key **sender_key, sender_key_state *state, uint32_t iteration);
+static int group_cipher_decrypt_callback(group_cipher *cipher, axolotl_buffer *plaintext, void *decrypt_context);
+
+int group_cipher_create(group_cipher **cipher,
+ axolotl_store_context *store, const axolotl_sender_key_name *sender_key_id,
+ axolotl_context *global_context)
+{
+ group_cipher *result_cipher;
+
+ assert(store);
+ assert(global_context);
+
+ result_cipher = malloc(sizeof(group_cipher));
+ if(!result_cipher) {
+ return AX_ERR_NOMEM;
+ }
+ memset(result_cipher, 0, sizeof(group_cipher));
+
+ result_cipher->store = store;
+ result_cipher->sender_key_id = sender_key_id;
+ result_cipher->global_context = global_context;
+
+ *cipher = result_cipher;
+ return 0;
+}
+
+void group_cipher_set_user_data(group_cipher *cipher, void *user_data)
+{
+ assert(cipher);
+ cipher->user_data = user_data;
+}
+
+void *group_cipher_get_user_data(group_cipher *cipher)
+{
+ assert(cipher);
+ return cipher->user_data;
+}
+
+void group_cipher_set_decryption_callback(group_cipher *cipher,
+ int (*callback)(group_cipher *cipher, axolotl_buffer *plaintext, void *decrypt_context))
+{
+ assert(cipher);
+ cipher->decrypt_callback = callback;
+}
+
+int group_cipher_encrypt(group_cipher *cipher,
+ const uint8_t *padded_plaintext, size_t padded_plaintext_len,
+ ciphertext_message **encrypted_message)
+{
+ int result = 0;
+ sender_key_message *result_message = 0;
+ sender_key_record *record = 0;
+ sender_key_state *state = 0;
+ sender_message_key *sender_key = 0;
+ sender_chain_key *next_chain_key = 0;
+ axolotl_buffer *sender_cipher_key = 0;
+ axolotl_buffer *sender_cipher_iv = 0;
+ axolotl_buffer *ciphertext = 0;
+
+ assert(cipher);
+ axolotl_lock(cipher->global_context);
+
+ if(cipher->inside_callback == 1) {
+ result = AX_ERR_INVAL;
+ goto complete;
+ }
+
+ result = axolotl_sender_key_load_key(cipher->store, &record, cipher->sender_key_id);
+ if(result < 0) {
+ goto complete;
+ }
+
+ result = sender_key_record_get_sender_key_state(record, &state);
+ if(result < 0) {
+ goto complete;
+ }
+
+ result = sender_chain_key_create_message_key(sender_key_state_get_chain_key(state), &sender_key);
+ if(result < 0) {
+ goto complete;
+ }
+
+ sender_cipher_key = sender_message_key_get_cipher_key(sender_key);
+ sender_cipher_iv = sender_message_key_get_iv(sender_key);
+
+ result = axolotl_encrypt(cipher->global_context, &ciphertext, AX_CIPHER_AES_CBC_PKCS5,
+ axolotl_buffer_data(sender_cipher_key), axolotl_buffer_len(sender_cipher_key),
+ axolotl_buffer_data(sender_cipher_iv), axolotl_buffer_len(sender_cipher_iv),
+ padded_plaintext, padded_plaintext_len);
+ if(result < 0) {
+ goto complete;
+ }
+
+ result = sender_key_message_create(&result_message,
+ sender_key_state_get_key_id(state),
+ sender_message_key_get_iteration(sender_key),
+ axolotl_buffer_data(ciphertext), axolotl_buffer_len(ciphertext),
+ sender_key_state_get_signing_key_private(state),
+ cipher->global_context);
+ if(result < 0) {
+ goto complete;
+ }
+
+ result = sender_chain_key_create_next(sender_key_state_get_chain_key(state), &next_chain_key);
+ if(result < 0) {
+ goto complete;
+ }
+
+ sender_key_state_set_chain_key(state, next_chain_key);
+
+ result = axolotl_sender_key_store_key(cipher->store, cipher->sender_key_id, record);
+
+complete:
+ if(result >= 0) {
+ *encrypted_message = (ciphertext_message *)result_message;
+ }
+ else {
+ if(result == AX_ERR_INVALID_KEY_ID) {
+ result = AX_ERR_NO_SESSION;
+ }
+ AXOLOTL_UNREF(result_message);
+ }
+ axolotl_buffer_free(ciphertext);
+ AXOLOTL_UNREF(next_chain_key);
+ AXOLOTL_UNREF(sender_key);
+ AXOLOTL_UNREF(record);
+ axolotl_unlock(cipher->global_context);
+ return result;
+}
+
+int group_cipher_decrypt(group_cipher *cipher,
+ sender_key_message *ciphertext, void *decrypt_context,
+ axolotl_buffer **plaintext)
+{
+ int result = 0;
+ axolotl_buffer *result_buf = 0;
+ sender_key_record *record = 0;
+ sender_key_state *state = 0;
+ sender_message_key *sender_key = 0;
+ axolotl_buffer *sender_cipher_key = 0;
+ axolotl_buffer *sender_cipher_iv = 0;
+ axolotl_buffer *ciphertext_body = 0;
+
+ assert(cipher);
+ axolotl_lock(cipher->global_context);
+
+ if(cipher->inside_callback == 1) {
+ result = AX_ERR_INVAL;
+ goto complete;
+ }
+
+ result = axolotl_sender_key_load_key(cipher->store, &record, cipher->sender_key_id);
+ if(result < 0) {
+ goto complete;
+ }
+
+ if(sender_key_record_is_empty(record)) {
+ result = AX_ERR_NO_SESSION;
+ axolotl_log(cipher->global_context, AX_LOG_WARNING, "No sender key for: %s::%s::%d",
+ cipher->sender_key_id->group_id,
+ cipher->sender_key_id->sender.name,
+ cipher->sender_key_id->sender.device_id);
+ goto complete;
+ }
+
+ result = sender_key_record_get_sender_key_state_by_id(record, &state, sender_key_message_get_key_id(ciphertext));
+ if(result < 0) {
+ goto complete;
+ }
+
+ result = sender_key_message_verify_signature(ciphertext, sender_key_state_get_signing_key_public(state));
+ if(result < 0) {
+ goto complete;
+ }
+
+ result = group_cipher_get_sender_key(cipher, &sender_key, state, sender_key_message_get_iteration(ciphertext));
+ if(result < 0) {
+ goto complete;
+ }
+
+ sender_cipher_key = sender_message_key_get_cipher_key(sender_key);
+ sender_cipher_iv = sender_message_key_get_iv(sender_key);
+ ciphertext_body = sender_key_message_get_ciphertext(ciphertext);
+
+ result = axolotl_decrypt(cipher->global_context, &result_buf, AX_CIPHER_AES_CBC_PKCS5,
+ axolotl_buffer_data(sender_cipher_key), axolotl_buffer_len(sender_cipher_key),
+ axolotl_buffer_data(sender_cipher_iv), axolotl_buffer_len(sender_cipher_iv),
+ axolotl_buffer_data(ciphertext_body), axolotl_buffer_len(ciphertext_body));
+ if(result < 0) {
+ goto complete;
+ }
+
+ result = group_cipher_decrypt_callback(cipher, result_buf, decrypt_context);
+ if(result < 0) {
+ goto complete;
+ }
+
+ result = axolotl_sender_key_store_key(cipher->store, cipher->sender_key_id, record);
+
+complete:
+ AXOLOTL_UNREF(sender_key);
+ AXOLOTL_UNREF(record);
+ if(result >= 0) {
+ *plaintext = result_buf;
+ }
+ else {
+ if(result == AX_ERR_INVALID_KEY || result == AX_ERR_INVALID_KEY_ID) {
+ result = AX_ERR_INVALID_MESSAGE;
+ }
+ axolotl_buffer_free(result_buf);
+ }
+ axolotl_unlock(cipher->global_context);
+ return result;
+}
+
+int group_cipher_get_sender_key(group_cipher *cipher, sender_message_key **sender_key, sender_key_state *state, uint32_t iteration)
+{
+ int result = 0;
+ sender_message_key *result_key = 0;
+ sender_chain_key *chain_key = 0;
+ sender_chain_key *next_chain_key = 0;
+ sender_message_key *message_key = 0;
+
+ chain_key = sender_key_state_get_chain_key(state);
+ AXOLOTL_REF(chain_key);
+
+ if(sender_chain_key_get_iteration(chain_key) > iteration) {
+ if(sender_key_state_has_sender_message_key(state, iteration)) {
+ result_key = sender_key_state_remove_sender_message_key(state, iteration);
+ if(!result_key) {
+ result = AX_ERR_UNKNOWN;
+ }
+ goto complete;
+ }
+ else {
+ result = AX_ERR_DUPLICATE_MESSAGE;
+ axolotl_log(cipher->global_context, AX_LOG_WARNING,
+ "Received message with old counter: %d, %d",
+ sender_chain_key_get_iteration(chain_key), iteration);
+ goto complete;
+ }
+ }
+
+ if(iteration - sender_chain_key_get_iteration(chain_key) > 2000) {
+ result = AX_ERR_INVALID_MESSAGE;
+ axolotl_log(cipher->global_context, AX_LOG_WARNING, "Over 2000 messages into the future!");
+ goto complete;
+ }
+
+ while(sender_chain_key_get_iteration(chain_key) < iteration) {
+ result = sender_chain_key_create_message_key(chain_key, &message_key);
+ if(result < 0) {
+ goto complete;
+ }
+
+ result = sender_key_state_add_sender_message_key(state, message_key);
+ if(result < 0) {
+ goto complete;
+ }
+ AXOLOTL_UNREF(message_key);
+
+ result = sender_chain_key_create_next(chain_key, &next_chain_key);
+ if(result < 0) {
+ goto complete;
+ }
+
+ AXOLOTL_UNREF(chain_key);
+ chain_key = next_chain_key;
+ next_chain_key = 0;
+ }
+
+ result = sender_chain_key_create_next(chain_key, &next_chain_key);
+ if(result < 0) {
+ goto complete;
+ }
+
+ sender_key_state_set_chain_key(state, next_chain_key);
+ result = sender_chain_key_create_message_key(chain_key, &result_key);
+
+complete:
+ AXOLOTL_UNREF(message_key);
+ AXOLOTL_UNREF(chain_key);
+ AXOLOTL_UNREF(next_chain_key);
+ if(result >= 0) {
+ *sender_key = result_key;
+ }
+ return result;
+}
+
+static int group_cipher_decrypt_callback(group_cipher *cipher, axolotl_buffer *plaintext, void *decrypt_context)
+{
+ int result = 0;
+ if(cipher->decrypt_callback) {
+ cipher->inside_callback = 1;
+ result = cipher->decrypt_callback(cipher, plaintext, decrypt_context);
+ cipher->inside_callback = 0;
+ }
+ return result;
+}
+
+void group_cipher_free(group_cipher *cipher)
+{
+ if(cipher) {
+ free(cipher);
+ }
+}