From d5ea5172a754848c10d061a4a9dd777f63ba71c1 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Sat, 11 Mar 2017 01:29:38 +0100 Subject: Add OMEMO via Plugin --- plugins/signal-protocol/src/context.vala | 101 ++++++ plugins/signal-protocol/src/signal_helper.c | 506 +++++++++++++++++++++++++++ plugins/signal-protocol/src/signal_helper.h | 43 +++ plugins/signal-protocol/src/simple_iks.vala | 40 +++ plugins/signal-protocol/src/simple_pks.vala | 33 ++ plugins/signal-protocol/src/simple_spks.vala | 33 ++ plugins/signal-protocol/src/simple_ss.vala | 77 ++++ plugins/signal-protocol/src/store.vala | 413 ++++++++++++++++++++++ plugins/signal-protocol/src/util.vala | 45 +++ 9 files changed, 1291 insertions(+) create mode 100644 plugins/signal-protocol/src/context.vala create mode 100644 plugins/signal-protocol/src/signal_helper.c create mode 100644 plugins/signal-protocol/src/signal_helper.h create mode 100644 plugins/signal-protocol/src/simple_iks.vala create mode 100644 plugins/signal-protocol/src/simple_pks.vala create mode 100644 plugins/signal-protocol/src/simple_spks.vala create mode 100644 plugins/signal-protocol/src/simple_ss.vala create mode 100644 plugins/signal-protocol/src/store.vala create mode 100644 plugins/signal-protocol/src/util.vala (limited to 'plugins/signal-protocol/src') diff --git a/plugins/signal-protocol/src/context.vala b/plugins/signal-protocol/src/context.vala new file mode 100644 index 00000000..38ac3662 --- /dev/null +++ b/plugins/signal-protocol/src/context.vala @@ -0,0 +1,101 @@ +namespace Signal { + +public class Context { + internal NativeContext native_context; + private RecMutex mutex = RecMutex(); + + static void locking_function_lock(void* user_data) { + Context ctx = (Context) user_data; + ctx.mutex.lock(); + } + + static void locking_function_unlock(void* user_data) { + Context ctx = (Context) user_data; + ctx.mutex.unlock(); + } + + static void stderr_log(LogLevel level, string message, void* user_data) { + printerr(@"$level: $message\n"); + } + + public Context(bool log = false) throws Error { + throw_by_code(NativeContext.create(out native_context, this), "Error initializing native context"); + throw_by_code(native_context.set_locking_functions(locking_function_lock, locking_function_unlock), "Error initializing native locking functions"); + if (log) native_context.set_log_function(stderr_log); + setup_crypto_provider(native_context); + } + + public Store create_store() { + return new Store(this); + } + + public void randomize(uint8[] data) throws Error { + throw_by_code(Signal.native_random(native_context, data)); + } + + public SignedPreKeyRecord generate_signed_pre_key(IdentityKeyPair identity_key_pair, int32 id, uint64 timestamp = 0) throws Error { + if (timestamp == 0) timestamp = new DateTime.now_local().to_unix(); + SignedPreKeyRecord res; + throw_by_code(Protocol.KeyHelper.generate_signed_pre_key(out res, identity_key_pair, id, timestamp, native_context)); + return res; + } + + public Gee.Set generate_pre_keys(uint start, uint count) throws Error { + Gee.Set res = new Gee.HashSet(); + for(uint i = start; i < start+count; i++) { + ECKeyPair pair = generate_key_pair(); + res.add(new PreKeyRecord(i, pair)); + } + return res; + } + + public ECPublicKey decode_public_key(uint8[] bytes) throws Error { + ECPublicKey public_key; + throw_by_code(curve_decode_point(out public_key, bytes, native_context), "Error decoding public key"); + return public_key; + } + + public ECPrivateKey decode_private_key(uint8[] bytes) throws Error { + ECPrivateKey private_key; + throw_by_code(curve_decode_private_point(out private_key, bytes, native_context), "Error decoding private key"); + return private_key; + } + + public ECKeyPair generate_key_pair() throws Error { + ECKeyPair key_pair; + throw_by_code(curve_generate_key_pair(native_context, out key_pair), "Error generating key pair"); + return key_pair; + } + + public uint8[] calculate_signature(ECPrivateKey signing_key, uint8[] message) throws Error { + Buffer signature; + throw_by_code(Curve.calculate_signature(native_context, out signature, signing_key, message), "Error calculating signature"); + return signature.data; + } + + public SignalMessage deserialize_signal_message(uint8[] data) throws Error { + SignalMessage res; + throw_by_code(signal_message_deserialize(out res, data, native_context)); + return res; + } + + public SignalMessage copy_signal_message(CiphertextMessage original) throws Error { + SignalMessage res; + throw_by_code(signal_message_copy(out res, (SignalMessage) original, native_context)); + return res; + } + + public PreKeySignalMessage deserialize_pre_key_signal_message(uint8[] data) throws Error { + PreKeySignalMessage res; + throw_by_code(pre_key_signal_message_deserialize(out res, data, native_context)); + return res; + } + + public PreKeySignalMessage copy_pre_key_signal_message(CiphertextMessage original) throws Error { + PreKeySignalMessage res; + throw_by_code(pre_key_signal_message_copy(out res, (PreKeySignalMessage) original, native_context)); + return res; + } +} + +} \ No newline at end of file diff --git a/plugins/signal-protocol/src/signal_helper.c b/plugins/signal-protocol/src/signal_helper.c new file mode 100644 index 00000000..2df2a627 --- /dev/null +++ b/plugins/signal-protocol/src/signal_helper.c @@ -0,0 +1,506 @@ +#include +#include + +#include +#include +#include +#include +#include + +signal_type_base* signal_type_ref_vapi(signal_type_base* instance) { + if (instance->ref_count > 100 || instance->ref_count < 1) + printf("REF %x -> %d\n", instance, instance->ref_count+1); + signal_type_ref(instance); + return instance; +} + +signal_type_base* signal_type_unref_vapi(signal_type_base* instance) { + if (instance->ref_count > 100 || instance->ref_count < 0) + printf("UNREF %x -> %d\n", instance, instance->ref_count-1); + signal_type_unref(instance); + return 0; +} + +signal_protocol_address* signal_protocol_address_new() { + signal_protocol_address* address = malloc(sizeof(signal_protocol_address)); + address->name = 0; + address->device_id = 0; + return address; +} + +void signal_protocol_address_free(signal_protocol_address* ptr) { + if (ptr->name) { + g_free((void*)ptr->name); + } + return free(ptr); +} + +void signal_protocol_address_set_name(signal_protocol_address* self, const gchar* name) { + gchar* n = g_malloc(strlen(name)+1); + memcpy(n, name, strlen(name)); + n[strlen(name)] = 0; + if (self->name) { + g_free((void*)self->name); + } + self->name = n; + self->name_len = strlen(n); +} + +gchar* signal_protocol_address_get_name(signal_protocol_address* self) { + if (self->name == 0) return 0; + gchar* res = g_malloc(sizeof(char) * (self->name_len + 1)); + memcpy(res, self->name, self->name_len); + res[self->name_len] = 0; + return res; +} + +session_pre_key* session_pre_key_new(uint32_t pre_key_id, ec_key_pair* pair, int* err) { + session_pre_key* res; + *err = session_pre_key_create(&res, pre_key_id, pair); + return res; +} + +session_signed_pre_key* session_signed_pre_key_new(uint32_t id, uint64_t timestamp, ec_key_pair* pair, uint8_t* key, int key_len, int* err) { + session_signed_pre_key* res; + *err = session_signed_pre_key_create(&res, id, timestamp, pair, key, key_len); + return res; +} + + + +int signal_vala_random_generator(uint8_t *data, size_t len, void *user_data) +{ + if(RAND_bytes(data, len)) { + return 0; + } + else { + return SG_ERR_UNKNOWN; + } +} + +int signal_vala_hmac_sha256_init(void **hmac_context, const uint8_t *key, size_t key_len, void *user_data) +{ + HMAC_CTX *ctx = malloc(sizeof(HMAC_CTX)); + if(!ctx) { + return SG_ERR_NOMEM; + } + HMAC_CTX_init(ctx); + *hmac_context = ctx; + + if(HMAC_Init_ex(ctx, key, key_len, EVP_sha256(), 0) != 1) { + return SG_ERR_UNKNOWN; + } + + return 0; +} + +int signal_vala_hmac_sha256_update(void *hmac_context, const uint8_t *data, size_t data_len, void *user_data) +{ + HMAC_CTX *ctx = hmac_context; + int result = HMAC_Update(ctx, data, data_len); + + return (result == 1) ? 0 : -1; +} + +int signal_vala_hmac_sha256_final(void *hmac_context, signal_buffer **output, void *user_data) +{ + int result = 0; + unsigned char md[EVP_MAX_MD_SIZE]; + unsigned int len = 0; + HMAC_CTX *ctx = hmac_context; + + if(HMAC_Final(ctx, md, &len) != 1) { + return SG_ERR_UNKNOWN; + } + + signal_buffer *output_buffer = signal_buffer_create(md, len); + if(!output_buffer) { + result = SG_ERR_NOMEM; + goto complete; + } + + *output = output_buffer; + +complete: + return result; +} + +void signal_vala_hmac_sha256_cleanup(void *hmac_context, void *user_data) +{ + if(hmac_context) { + HMAC_CTX *ctx = hmac_context; + HMAC_CTX_cleanup(ctx); + free(ctx); + } +} + +const EVP_CIPHER *aes_cipher(int cipher, size_t key_len) +{ + if(cipher == SG_CIPHER_AES_CBC_PKCS5) { + if(key_len == 16) { + return EVP_aes_128_cbc(); + } + else if(key_len == 24) { + return EVP_aes_192_cbc(); + } + else if(key_len == 32) { + return EVP_aes_256_cbc(); + } + } + else if(cipher == SG_CIPHER_AES_CTR_NOPADDING) { + if(key_len == 16) { + return EVP_aes_128_ctr(); + } + else if(key_len == 24) { + return EVP_aes_192_ctr(); + } + else if(key_len == 32) { + return EVP_aes_256_ctr(); + } + } + else if (cipher == SG_CIPHER_AES_GCM_NOPADDING) { + if(key_len == 16) { + return EVP_aes_128_gcm(); + } + else if(key_len == 24) { + return EVP_aes_192_gcm(); + } + else if(key_len == 32) { + return EVP_aes_256_gcm(); + } + } + return 0; +} + +int signal_vala_sha512_digest_init(void **digest_context, void *user_data) +{ + int result = 0; + EVP_MD_CTX *ctx; + + ctx = EVP_MD_CTX_create(); + if(!ctx) { + result = SG_ERR_NOMEM; + goto complete; + } + + result = EVP_DigestInit_ex(ctx, EVP_sha512(), 0); + if(result == 1) { + result = SG_SUCCESS; + } + else { + result = SG_ERR_UNKNOWN; + } + +complete: + if(result < 0) { + if(ctx) { + EVP_MD_CTX_destroy(ctx); + } + } + else { + *digest_context = ctx; + } + return result; +} + +int signal_vala_sha512_digest_update(void *digest_context, const uint8_t *data, size_t data_len, void *user_data) +{ + EVP_MD_CTX *ctx = digest_context; + + int result = EVP_DigestUpdate(ctx, data, data_len); + + return (result == 1) ? SG_SUCCESS : SG_ERR_UNKNOWN; +} + +int signal_vala_sha512_digest_final(void *digest_context, signal_buffer **output, void *user_data) +{ + int result = 0; + unsigned char md[EVP_MAX_MD_SIZE]; + unsigned int len = 0; + EVP_MD_CTX *ctx = digest_context; + + result = EVP_DigestFinal_ex(ctx, md, &len); + if(result == 1) { + result = SG_SUCCESS; + } + else { + result = SG_ERR_UNKNOWN; + goto complete; + } + + result = EVP_DigestInit_ex(ctx, EVP_sha512(), 0); + if(result == 1) { + result = SG_SUCCESS; + } + else { + result = SG_ERR_UNKNOWN; + goto complete; + } + + signal_buffer *output_buffer = signal_buffer_create(md, len); + if(!output_buffer) { + result = SG_ERR_NOMEM; + goto complete; + } + + *output = output_buffer; + +complete: + return result; +} + +void signal_vala_sha512_digest_cleanup(void *digest_context, void *user_data) +{ + EVP_MD_CTX *ctx = digest_context; + EVP_MD_CTX_destroy(ctx); +} + +int signal_vala_encrypt(signal_buffer **output, + int cipher, + const uint8_t *key, size_t key_len, + const uint8_t *iv, size_t iv_len, + const uint8_t *plaintext, size_t plaintext_len, + void *user_data) +{ + int result = 0; + uint8_t *out_buf = 0; + + const EVP_CIPHER *evp_cipher = aes_cipher(cipher, key_len); + if(!evp_cipher) { + fprintf(stderr, "invalid AES mode or key size: %zu\n", key_len); + return SG_ERR_UNKNOWN; + } + + if(iv_len != 16) { + fprintf(stderr, "invalid AES IV size: %zu\n", iv_len); + return SG_ERR_UNKNOWN; + } + + if(plaintext_len > INT_MAX - EVP_CIPHER_block_size(evp_cipher)) { + fprintf(stderr, "invalid plaintext length: %zu\n", plaintext_len); + return SG_ERR_UNKNOWN; + } + + EVP_CIPHER_CTX ctx; + EVP_CIPHER_CTX_init(&ctx); + + int buf_extra = 0; + + if(cipher == SG_CIPHER_AES_GCM_NOPADDING) { + // In GCM mode we use the last 16 bytes as auth tag + buf_extra += 16; + + result = EVP_EncryptInit_ex(&ctx, evp_cipher, NULL, NULL, NULL); + if(!result) { + fprintf(stderr, "cannot initialize cipher\n"); + result = SG_ERR_UNKNOWN; + goto complete; + } + + result = EVP_CIPHER_CTX_ctrl(&ctx, EVP_CTRL_GCM_SET_IVLEN, 16, NULL); + if(!result) { + fprintf(stderr, "cannot set iv size\n"); + result = SG_ERR_UNKNOWN; + goto complete; + } + + result = EVP_EncryptInit_ex(&ctx, NULL, NULL, key, iv); + if(!result) { + fprintf(stderr, "cannot set key/iv\n"); + result = SG_ERR_UNKNOWN; + goto complete; + } + } else { + result = EVP_EncryptInit_ex(&ctx, evp_cipher, 0, key, iv); + if(!result) { + fprintf(stderr, "cannot initialize cipher\n"); + result = SG_ERR_UNKNOWN; + goto complete; + } + } + + if(cipher == SG_CIPHER_AES_CTR_NOPADDING || cipher == SG_CIPHER_AES_GCM_NOPADDING) { + result = EVP_CIPHER_CTX_set_padding(&ctx, 0); + if(!result) { + fprintf(stderr, "cannot set padding\n"); + result = SG_ERR_UNKNOWN; + goto complete; + } + } + + out_buf = malloc(sizeof(uint8_t) * (plaintext_len + EVP_CIPHER_block_size(evp_cipher) + buf_extra)); + if(!out_buf) { + fprintf(stderr, "cannot allocate output buffer\n"); + result = SG_ERR_NOMEM; + goto complete; + } + + int out_len = 0; + result = EVP_EncryptUpdate(&ctx, + out_buf, &out_len, plaintext, plaintext_len); + if(!result) { + fprintf(stderr, "cannot encrypt plaintext\n"); + result = SG_ERR_UNKNOWN; + goto complete; + } + + int final_len = 0; + result = EVP_EncryptFinal_ex(&ctx, out_buf + out_len, &final_len); + if(!result) { + fprintf(stderr, "cannot finish encrypting plaintext\n"); + result = SG_ERR_UNKNOWN; + goto complete; + } + + if(cipher == SG_CIPHER_AES_GCM_NOPADDING) { + result = EVP_CIPHER_CTX_ctrl(&ctx, EVP_CTRL_GCM_GET_TAG, 16, out_buf + (out_len + final_len)); + if(!result) { + fprintf(stderr, "cannot get tag\n"); + result = SG_ERR_UNKNOWN; + goto complete; + } + } + + *output = signal_buffer_create(out_buf, out_len + final_len + buf_extra); + +complete: + EVP_CIPHER_CTX_cleanup(&ctx); + if(out_buf) { + free(out_buf); + } + return result; +} + +int signal_vala_decrypt(signal_buffer **output, + int cipher, + const uint8_t *key, size_t key_len, + const uint8_t *iv, size_t iv_len, + const uint8_t *ciphertext, size_t ciphertext_len, + void *user_data) +{ + int result = 0; + uint8_t *out_buf = 0; + + const EVP_CIPHER *evp_cipher = aes_cipher(cipher, key_len); + if(!evp_cipher) { + fprintf(stderr, "invalid AES mode or key size: %zu\n", key_len); + return SG_ERR_INVAL; + } + + if(iv_len != 16) { + fprintf(stderr, "invalid AES IV size: %zu\n", iv_len); + return SG_ERR_INVAL; + } + + if(ciphertext_len > INT_MAX - EVP_CIPHER_block_size(evp_cipher)) { + fprintf(stderr, "invalid ciphertext length: %zu\n", ciphertext_len); + return SG_ERR_UNKNOWN; + } + + EVP_CIPHER_CTX ctx; + EVP_CIPHER_CTX_init(&ctx); + + if(cipher == SG_CIPHER_AES_GCM_NOPADDING) { + // In GCM mode we use the last 16 bytes as auth tag + ciphertext_len -= 16; + + result = EVP_DecryptInit_ex(&ctx, evp_cipher, NULL, NULL, NULL); + if(!result) { + fprintf(stderr, "cannot initialize cipher\n"); + result = SG_ERR_UNKNOWN; + goto complete; + } + + result = EVP_CIPHER_CTX_ctrl(&ctx, EVP_CTRL_GCM_SET_IVLEN, 16, NULL); + if(!result) { + fprintf(stderr, "cannot set iv size\n"); + result = SG_ERR_UNKNOWN; + goto complete; + } + + result = EVP_DecryptInit_ex(&ctx, NULL, NULL, key, iv); + if(!result) { + fprintf(stderr, "cannot set key/iv\n"); + result = SG_ERR_UNKNOWN; + goto complete; + } + } else { + result = EVP_DecryptInit_ex(&ctx, evp_cipher, 0, key, iv); + if(!result) { + fprintf(stderr, "cannot initialize cipher\n"); + result = SG_ERR_UNKNOWN; + goto complete; + } + } + + if(cipher == SG_CIPHER_AES_CTR_NOPADDING || cipher == SG_CIPHER_AES_GCM_NOPADDING) { + result = EVP_CIPHER_CTX_set_padding(&ctx, 0); + if(!result) { + fprintf(stderr, "cannot set padding\n"); + result = SG_ERR_UNKNOWN; + goto complete; + } + } + + out_buf = malloc(sizeof(uint8_t) * (ciphertext_len + EVP_CIPHER_block_size(evp_cipher))); + if(!out_buf) { + fprintf(stderr, "cannot allocate output buffer\n"); + result = SG_ERR_UNKNOWN; + goto complete; + } + + int out_len = 0; + result = EVP_DecryptUpdate(&ctx, + out_buf, &out_len, ciphertext, ciphertext_len); + if(!result) { + fprintf(stderr, "cannot decrypt ciphertext\n"); + result = SG_ERR_UNKNOWN; + goto complete; + } + + if(cipher == SG_CIPHER_AES_GCM_NOPADDING) { + result = EVP_CIPHER_CTX_ctrl(&ctx, EVP_CTRL_GCM_SET_TAG, 16, (uint8_t*)ciphertext + ciphertext_len); + if(!result) { + fprintf(stderr, "cannot set tag\n"); + result = SG_ERR_UNKNOWN; + goto complete; + } + } + + int final_len = 0; + result = EVP_DecryptFinal_ex(&ctx, out_buf + out_len, &final_len); + if(!result) { + fprintf(stderr, "cannot finish decrypting ciphertexts\n"); + result = SG_ERR_UNKNOWN; + goto complete; + } + + *output = signal_buffer_create(out_buf, out_len + final_len); + +complete: + EVP_CIPHER_CTX_cleanup(&ctx); + if(out_buf) { + free(out_buf); + } + return result; +} + +void setup_signal_vala_crypto_provider(signal_context *context) +{ + signal_crypto_provider provider = { + .random_func = signal_vala_random_generator, + .hmac_sha256_init_func = signal_vala_hmac_sha256_init, + .hmac_sha256_update_func = signal_vala_hmac_sha256_update, + .hmac_sha256_final_func = signal_vala_hmac_sha256_final, + .hmac_sha256_cleanup_func = signal_vala_hmac_sha256_cleanup, + .sha512_digest_init_func = signal_vala_sha512_digest_init, + .sha512_digest_update_func = signal_vala_sha512_digest_update, + .sha512_digest_final_func = signal_vala_sha512_digest_final, + .sha512_digest_cleanup_func = signal_vala_sha512_digest_cleanup, + .encrypt_func = signal_vala_encrypt, + .decrypt_func = signal_vala_decrypt, + .user_data = 0 + }; + + signal_context_set_crypto_provider(context, &provider); +} \ No newline at end of file diff --git a/plugins/signal-protocol/src/signal_helper.h b/plugins/signal-protocol/src/signal_helper.h new file mode 100644 index 00000000..b4b05582 --- /dev/null +++ b/plugins/signal-protocol/src/signal_helper.h @@ -0,0 +1,43 @@ +#ifndef SIGNAL_PROTOCOL_VALA_HELPER +#define SIGNAL_PROTOCOL_VALA_HELPER 1 + +#include +#include +#include + +#define SG_CIPHER_AES_GCM_NOPADDING 1000 + +signal_type_base* signal_type_ref_vapi(signal_type_base* what); +signal_type_base* signal_type_unref_vapi(signal_type_base* what); +signal_protocol_address* signal_protocol_address_new(); +void signal_protocol_address_free(signal_protocol_address* ptr); +void signal_protocol_address_set_name(signal_protocol_address* self, const gchar* name); +gchar* signal_protocol_address_get_name(signal_protocol_address* self); +session_pre_key* session_pre_key_new(uint32_t pre_key_id, ec_key_pair* pair, int* err); +session_signed_pre_key* session_signed_pre_key_new(uint32_t id, uint64_t timestamp, ec_key_pair* pair, uint8_t* key, int key_len, int* err); + +int signal_vala_random_generator(uint8_t *data, size_t len, void *user_data); +int signal_vala_hmac_sha256_init(void **hmac_context, const uint8_t *key, size_t key_len, void *user_data); +int signal_vala_hmac_sha256_update(void *hmac_context, const uint8_t *data, size_t data_len, void *user_data); +int signal_vala_hmac_sha256_final(void *hmac_context, signal_buffer **output, void *user_data); +void signal_vala_hmac_sha256_cleanup(void *hmac_context, void *user_data); +int signal_vala_sha512_digest_init(void **digest_context, void *user_data); +int signal_vala_sha512_digest_update(void *digest_context, const uint8_t *data, size_t data_len, void *user_data); +int signal_vala_sha512_digest_final(void *digest_context, signal_buffer **output, void *user_data); +void signal_vala_sha512_digest_cleanup(void *digest_context, void *user_data); + +int signal_vala_encrypt(signal_buffer **output, + int cipher, + const uint8_t *key, size_t key_len, + const uint8_t *iv, size_t iv_len, + const uint8_t *plaintext, size_t plaintext_len, + void *user_data); +int signal_vala_decrypt(signal_buffer **output, + int cipher, + const uint8_t *key, size_t key_len, + const uint8_t *iv, size_t iv_len, + const uint8_t *ciphertext, size_t ciphertext_len, + void *user_data); +void setup_signal_vala_crypto_provider(signal_context *context); + +#endif \ No newline at end of file diff --git a/plugins/signal-protocol/src/simple_iks.vala b/plugins/signal-protocol/src/simple_iks.vala new file mode 100644 index 00000000..1e575515 --- /dev/null +++ b/plugins/signal-protocol/src/simple_iks.vala @@ -0,0 +1,40 @@ +using Gee; + +namespace Signal { + +public class SimpleIdentityKeyStore : IdentityKeyStore { + public override uint8[] identity_key_private { get; set; } + public override uint8[] identity_key_public { get; set; } + public override uint32 local_registration_id { get; set; } + private Map> trusted_identities = new HashMap>(); + + public override void save_identity(Address address, uint8[] key) throws Error { + string name = address.name; + if (trusted_identities.has_key(name)) { + if (trusted_identities[name].has_key(address.device_id)) { + trusted_identities[name][address.device_id].key = key; + trusted_identity_updated(trusted_identities[name][address.device_id]); + } else { + trusted_identities[name][address.device_id] = new TrustedIdentity.by_address(address, key); + trusted_identity_added(trusted_identities[name][address.device_id]); + } + } else { + trusted_identities[name] = new HashMap(); + trusted_identities[name][address.device_id] = new TrustedIdentity.by_address(address, key); + trusted_identity_added(trusted_identities[name][address.device_id]); + } + } + + public override bool is_trusted_identity(Address address, uint8[] key) throws Error { + if (!trusted_identities.has_key(address.name)) return true; + if (!trusted_identities[address.name].has_key(address.device_id)) return true; + uint8[] other_key = trusted_identities[address.name][address.device_id].key; + if (other_key.length != key.length) return false; + for (int i = 0; i < key.length; i++) { + if (other_key[i] != key[i]) return false; + } + return true; + } +} + +} \ No newline at end of file diff --git a/plugins/signal-protocol/src/simple_pks.vala b/plugins/signal-protocol/src/simple_pks.vala new file mode 100644 index 00000000..1f059fda --- /dev/null +++ b/plugins/signal-protocol/src/simple_pks.vala @@ -0,0 +1,33 @@ +using Gee; + +namespace Signal { + +public class SimplePreKeyStore : PreKeyStore { + private Map pre_key_map = new HashMap(); + + public override uint8[]? load_pre_key(uint32 pre_key_id) throws Error { + if (contains_pre_key(pre_key_id)) { + return pre_key_map[pre_key_id].record; + } + return null; + } + + public override void store_pre_key(uint32 pre_key_id, uint8[] record) throws Error { + PreKeyStore.Key key = new Key(pre_key_id, record); + pre_key_map[pre_key_id] = key; + pre_key_stored(key); + } + + public override bool contains_pre_key(uint32 pre_key_id) throws Error { + return pre_key_map.has_key(pre_key_id); + } + + public override void delete_pre_key(uint32 pre_key_id) throws Error { + PreKeyStore.Key key; + if (pre_key_map.unset(pre_key_id, out key)) { + pre_key_deleted(key); + } + } +} + +} \ No newline at end of file diff --git a/plugins/signal-protocol/src/simple_spks.vala b/plugins/signal-protocol/src/simple_spks.vala new file mode 100644 index 00000000..f0fe09ab --- /dev/null +++ b/plugins/signal-protocol/src/simple_spks.vala @@ -0,0 +1,33 @@ +using Gee; + +namespace Signal { + +public class SimpleSignedPreKeyStore : SignedPreKeyStore { + private Map pre_key_map = new HashMap(); + + public override uint8[]? load_signed_pre_key(uint32 pre_key_id) throws Error { + if (contains_signed_pre_key(pre_key_id)) { + return pre_key_map[pre_key_id].record; + } + return null; + } + + public override void store_signed_pre_key(uint32 pre_key_id, uint8[] record) throws Error { + SignedPreKeyStore.Key key = new Key(pre_key_id, record); + pre_key_map[pre_key_id] = key; + signed_pre_key_stored(key); + } + + public override bool contains_signed_pre_key(uint32 pre_key_id) throws Error { + return pre_key_map.has_key(pre_key_id); + } + + public override void delete_signed_pre_key(uint32 pre_key_id) throws Error { + SignedPreKeyStore.Key key; + if (pre_key_map.unset(pre_key_id, out key)) { + signed_pre_key_deleted(key); + } + } +} + +} \ No newline at end of file diff --git a/plugins/signal-protocol/src/simple_ss.vala b/plugins/signal-protocol/src/simple_ss.vala new file mode 100644 index 00000000..cc8e6b78 --- /dev/null +++ b/plugins/signal-protocol/src/simple_ss.vala @@ -0,0 +1,77 @@ +using Gee; + +namespace Signal { + +public class SimpleSessionStore : SessionStore { + + private Map> session_map = new HashMap>(); + + public override uint8[]? load_session(Address address) throws Error { + string name = address.name; + if (name == null) return null; + if (session_map.has_key(name)) { + foreach (SessionStore.Session session in session_map[name]) { + if (session.device_id == address.device_id) return session.record; + } + } + return null; + } + + public override IntList get_sub_device_sessions(string name) throws Error { + IntList res = new IntList(); + if (session_map.has_key(name)) { + foreach (SessionStore.Session session in session_map[name]) { + res.add(session.device_id); + } + } + return res; + } + + public override void store_session(Address address, uint8[] record) throws Error { + if (contains_session(address)) { + delete_session(address); + } + if (!session_map.has_key(address.name)) { + session_map[address.name] = new ArrayList(); + } + SessionStore.Session session = new Session() { name = address.name, device_id = address.device_id, record = record }; + session_map[address.name].add(session); + session_stored(session); + } + + public override bool contains_session(Address address) throws Error { + if (!session_map.has_key(address.name)) return false; + foreach (SessionStore.Session session in session_map[address.name]) { + if (session.device_id == address.device_id) return true; + } + return false; + } + + public override void delete_session(Address address) throws Error { + if (!session_map.has_key(address.name)) throw_by_code(ErrorCode.UNKNOWN, "No session found"); + foreach (SessionStore.Session session in session_map[address.name]) { + if (session.device_id == address.device_id) { + session_map[address.name].remove(session); + if (session_map[address.name].size == 0) { + session_map.unset(address.name); + } + session_removed(session); + return; + } + } + } + + public override void delete_all_sessions(string name) throws Error { + if (session_map.has_key(name)) { + foreach (SessionStore.Session session in session_map[name]) { + session_map[name].remove(session); + if (session_map[name].size == 0) { + session_map.unset(name); + } + session_removed(session); + } + } + } +} + +} \ No newline at end of file diff --git a/plugins/signal-protocol/src/store.vala b/plugins/signal-protocol/src/store.vala new file mode 100644 index 00000000..e0d74d0d --- /dev/null +++ b/plugins/signal-protocol/src/store.vala @@ -0,0 +1,413 @@ +namespace Signal { + +public abstract class IdentityKeyStore : Object { + public abstract uint8[] identity_key_private { get; set; } + public abstract uint8[] identity_key_public { get; set; } + public abstract uint32 local_registration_id { get; set; } + + public signal void trusted_identity_added(TrustedIdentity id); + public signal void trusted_identity_updated(TrustedIdentity id); + + public abstract void save_identity(Address address, uint8[] key) throws Error ; + + public abstract bool is_trusted_identity(Address address, uint8[] key) throws Error ; + + public class TrustedIdentity { + public uint8[] key { get; set; } + public string name { get; private set; } + public int device_id { get; private set; } + + public TrustedIdentity(string name, int device_id, uint8[] key) { + this.key = key; + this.name = name; + this.device_id = device_id; + } + + public TrustedIdentity.by_address(Address address, uint8[] key) { + this(address.name, address.device_id, key); + } + } +} + +public abstract class SessionStore : Object { + + public signal void session_stored(Session session); + public signal void session_removed(Session session); + public abstract uint8[]? load_session(Address address) throws Error ; + + public abstract IntList get_sub_device_sessions(string name) throws Error ; + + public abstract void store_session(Address address, uint8[] record) throws Error ; + + public abstract bool contains_session(Address address) throws Error ; + + public abstract void delete_session(Address address) throws Error ; + + public abstract void delete_all_sessions(string name) throws Error ; + + public class Session { + public string name; + public int device_id; + public uint8[] record; + } +} + +public abstract class PreKeyStore : Object { + + public signal void pre_key_stored(Key key); + public signal void pre_key_deleted(Key key); + + public abstract uint8[]? load_pre_key(uint32 pre_key_id) throws Error ; + + public abstract void store_pre_key(uint32 pre_key_id, uint8[] record) throws Error ; + + public abstract bool contains_pre_key(uint32 pre_key_id) throws Error ; + + public abstract void delete_pre_key(uint32 pre_key_id) throws Error ; + + public class Key { + public uint32 key_id { get; private set; } + public uint8[] record { get; private set; } + + public Key(uint32 key_id, uint8[] record) { + this.key_id = key_id; + this.record = record; + } + } +} + +public abstract class SignedPreKeyStore : Object { + + public signal void signed_pre_key_stored(Key key); + public signal void signed_pre_key_deleted(Key key); + + public abstract uint8[]? load_signed_pre_key(uint32 pre_key_id) throws Error ; + + public abstract void store_signed_pre_key(uint32 pre_key_id, uint8[] record) throws Error ; + + public abstract bool contains_signed_pre_key(uint32 pre_key_id) throws Error ; + + public abstract void delete_signed_pre_key(uint32 pre_key_id) throws Error ; + + public class Key { + public uint32 key_id { get; private set; } + public uint8[] record { get; private set; } + + public Key(uint32 key_id, uint8[] record) { + this.key_id = key_id; + this.record = record; + } + } +} + +public class Store : Object { + public Context context { get; private set; } + public IdentityKeyStore identity_key_store { get; set; default = new SimpleIdentityKeyStore(); } + public SessionStore session_store { get; set; default = new SimpleSessionStore(); } + public PreKeyStore pre_key_store { get; set; default = new SimplePreKeyStore(); } + public SignedPreKeyStore signed_pre_key_store { get; set; default = new SimpleSignedPreKeyStore(); } + public uint32 local_registration_id { get { return identity_key_store.local_registration_id; } } + internal NativeStoreContext native_context {get { return native_store_context_; }} + private NativeStoreContext native_store_context_; + + static int iks_get_identity_key_pair(out Buffer public_data, out Buffer private_data, void* user_data) { + Store store = (Store) user_data; + public_data = new Buffer.from(store.identity_key_store.identity_key_public); + private_data = new Buffer.from(store.identity_key_store.identity_key_private); + return 0; + } + + static int iks_get_local_registration_id(void* user_data, out uint32 registration_id) { + Store store = (Store) user_data; + registration_id = store.identity_key_store.local_registration_id; + return 0; + } + + static int iks_save_identity(Address address, uint8[] key, void* user_data) { + Store store = (Store) user_data; + return catch_to_code(() => { + store.identity_key_store.save_identity(address, key); + return 0; + }); + } + + static int iks_is_trusted_identity(Address address, uint8[] key, void* user_data) { + Store store = (Store) user_data; + return catch_to_code(() => { + return store.identity_key_store.is_trusted_identity(address, key) ? 1 : 0; + }); + } + + static int iks_destroy_func(void* user_data) { + return 0; + } + + static int ss_load_session_func(out Buffer buffer, Address address, void* user_data) { + Store store = (Store) user_data; + uint8[] res = null; + try { + res = store.session_store.load_session(address); + } catch (Error e) { + buffer = null; + return e.code; + } + if (res == null) { + buffer = null; + return 0; + } + buffer = new Buffer.from(res); + if (buffer == null) return ErrorCode.NOMEM; + return 1; + } + + static int ss_get_sub_device_sessions_func(out IntList sessions, char[] name, void* user_data) { + Store store = (Store) user_data; + try { + sessions = store.session_store.get_sub_device_sessions(carr_to_string(name)); + } catch (Error e) { + sessions = null; + return e.code; + } + return 0; + } + + static int ss_store_session_func(Address address, uint8[] record, void* user_data) { + Store store = (Store) user_data; + return catch_to_code(() => { + store.session_store.store_session(address, record); + return 0; + }); + } + + static int ss_contains_session_func(Address address, void* user_data) { + Store store = (Store) user_data; + return catch_to_code(() => { + return store.session_store.contains_session(address) ? 1 : 0; + }); + } + + static int ss_delete_session_func(Address address, void* user_data) { + Store store = (Store) user_data; + return catch_to_code(() => { + store.session_store.delete_session(address); + return 0; + }); + } + + static int ss_delete_all_sessions_func(char[] name, void* user_data) { + Store store = (Store) user_data; + return catch_to_code(() => { + store.session_store.delete_all_sessions(carr_to_string(name)); + return 0; + }); + } + + static int ss_destroy_func(void* user_data) { + return 0; + } + + static int pks_load_pre_key(out Buffer record, uint32 pre_key_id, void* user_data) { + Store store = (Store) user_data; + uint8[] res = null; + try { + res = store.pre_key_store.load_pre_key(pre_key_id); + } catch (Error e) { + record = null; + return e.code; + } + if (res == null) { + record = new Buffer(0); + return 0; + } + record = new Buffer.from(res); + if (record == null) return ErrorCode.NOMEM; + return 1; + } + + static int pks_store_pre_key(uint32 pre_key_id, uint8[] record, void* user_data) { + Store store = (Store) user_data; + return catch_to_code(() => { + store.pre_key_store.store_pre_key(pre_key_id, record); + return 0; + }); + } + + static int pks_contains_pre_key(uint32 pre_key_id, void* user_data) { + Store store = (Store) user_data; + return catch_to_code(() => { + return store.pre_key_store.contains_pre_key(pre_key_id) ? 1 : 0; + }); + } + + static int pks_remove_pre_key(uint32 pre_key_id, void* user_data) { + Store store = (Store) user_data; + return catch_to_code(() => { + store.pre_key_store.delete_pre_key(pre_key_id); + return 0; + }); + } + + static int pks_destroy_func(void* user_data) { + return 0; + } + + static int spks_load_signed_pre_key(out Buffer record, uint32 pre_key_id, void* user_data) { + Store store = (Store) user_data; + uint8[] res = null; + try { + res = store.signed_pre_key_store.load_signed_pre_key(pre_key_id); + } catch (Error e) { + record = null; + return e.code; + } + if (res == null) { + record = new Buffer(0); + return 0; + } + record = new Buffer.from(res); + if (record == null) return ErrorCode.NOMEM; + return 1; + } + + static int spks_store_signed_pre_key(uint32 pre_key_id, uint8[] record, void* user_data) { + Store store = (Store) user_data; + return catch_to_code(() => { + store.signed_pre_key_store.store_signed_pre_key(pre_key_id, record); + return 0; + }); + } + + static int spks_contains_signed_pre_key(uint32 pre_key_id, void* user_data) { + Store store = (Store) user_data; + return catch_to_code(() => { + return store.signed_pre_key_store.contains_signed_pre_key(pre_key_id) ? 1 : 0; + }); + } + + static int spks_remove_signed_pre_key(uint32 pre_key_id, void* user_data) { + Store store = (Store) user_data; + return catch_to_code(() => { + store.signed_pre_key_store.delete_signed_pre_key(pre_key_id); + return 0; + }); + } + + static int spks_destroy_func(void* user_data) { + return 0; + } + + internal Store(Context context) { + this.context = context; + NativeStoreContext.create(out native_store_context_, context.native_context); + + NativeIdentityKeyStore iks = NativeIdentityKeyStore() { + get_identity_key_pair = iks_get_identity_key_pair, + get_local_registration_id = iks_get_local_registration_id, + save_identity = iks_save_identity, + is_trusted_identity = iks_is_trusted_identity, + destroy_func = iks_destroy_func, + user_data = this + }; + native_context.set_identity_key_store(iks); + + NativeSessionStore ss = NativeSessionStore() { + load_session_func = ss_load_session_func, + get_sub_device_sessions_func = ss_get_sub_device_sessions_func, + store_session_func = ss_store_session_func, + contains_session_func = ss_contains_session_func, + delete_session_func = ss_delete_session_func, + delete_all_sessions_func = ss_delete_all_sessions_func, + destroy_func = ss_destroy_func, + user_data = this + }; + native_context.set_session_store(ss); + + NativePreKeyStore pks = NativePreKeyStore() { + load_pre_key = pks_load_pre_key, + store_pre_key = pks_store_pre_key, + contains_pre_key = pks_contains_pre_key, + remove_pre_key = pks_remove_pre_key, + destroy_func = pks_destroy_func, + user_data = this + }; + native_context.set_pre_key_store(pks); + + NativeSignedPreKeyStore spks = NativeSignedPreKeyStore() { + load_signed_pre_key = spks_load_signed_pre_key, + store_signed_pre_key = spks_store_signed_pre_key, + contains_signed_pre_key = spks_contains_signed_pre_key, + remove_signed_pre_key = spks_remove_signed_pre_key, + destroy_func = spks_destroy_func, + user_data = this + }; + native_context.set_signed_pre_key_store(spks); + } + + public SessionBuilder create_session_builder(Address other) throws Error { + SessionBuilder builder; + throw_by_code(session_builder_create(out builder, native_context, other, context.native_context), "Error creating session builder"); + return builder; + } + + public SessionCipher create_session_cipher(Address other) throws Error { + SessionCipher cipher; + throw_by_code(session_cipher_create(out cipher, native_context, other, context.native_context)); + return cipher; + } + + public IdentityKeyPair identity_key_pair { + owned get { + IdentityKeyPair pair; + Protocol.Identity.get_key_pair(native_context, out pair); + return pair; + } + } + + public bool is_trusted_identity(Address address, ECPublicKey key) throws Error { + return throw_by_code(Protocol.Identity.is_trusted_identity(native_context, address, key)) == 1; + } + + public void save_identity(Address address, ECPublicKey key) throws Error { + throw_by_code(Protocol.Identity.save_identity(native_context, address, key)); + } + + public bool contains_session(Address other) throws Error { + return throw_by_code(Protocol.Session.contains_session(native_context, other)) == 1; + } + + public SessionRecord load_session(Address other) throws Error { + SessionRecord record; + throw_by_code(Protocol.Session.load_session(native_context, out record, other)); + return record; + } + + public bool contains_pre_key(uint32 pre_key_id) throws Error { + return throw_by_code(Protocol.PreKey.contains_key(native_context, pre_key_id)) == 1; + } + + public void store_pre_key(PreKeyRecord record) throws Error { + throw_by_code(Protocol.PreKey.store_key(native_context, record)); + } + + public PreKeyRecord load_pre_key(uint32 pre_key_id) throws Error { + PreKeyRecord res; + throw_by_code(Protocol.PreKey.load_key(native_context, out res, pre_key_id)); + return res; + } + + public bool contains_signed_pre_key(uint32 pre_key_id) throws Error { + return throw_by_code(Protocol.SignedPreKey.contains_key(native_context, pre_key_id)) == 1; + } + + public void store_signed_pre_key(SignedPreKeyRecord record) throws Error { + throw_by_code(Protocol.SignedPreKey.store_key(native_context, record)); + } + + public SignedPreKeyRecord load_signed_pre_key(uint32 pre_key_id) throws Error { + SignedPreKeyRecord res; + throw_by_code(Protocol.SignedPreKey.load_key(native_context, out res, pre_key_id)); + return res; + } +} + +} \ No newline at end of file diff --git a/plugins/signal-protocol/src/util.vala b/plugins/signal-protocol/src/util.vala new file mode 100644 index 00000000..4c0ae72d --- /dev/null +++ b/plugins/signal-protocol/src/util.vala @@ -0,0 +1,45 @@ +namespace Signal { + +public ECPublicKey generate_public_key(ECPrivateKey private_key) throws Error { + ECPublicKey public_key; + throw_by_code(ECPublicKey.generate(out public_key, private_key), "Error generating public key"); + + return public_key; +} + +public uint8[] calculate_agreement(ECPublicKey public_key, ECPrivateKey private_key) throws Error { + uint8[] res; + int len = Curve.calculate_agreement(out res, public_key, private_key); + throw_by_code(len, "Error calculating agreement"); + res.length = len; + return res; +} + +public bool verify_signature(ECPublicKey signing_key, uint8[] message, uint8[] signature) throws Error { + return throw_by_code(Curve.verify_signature(signing_key, message, signature)) == 1; +} + +public PreKeyBundle create_pre_key_bundle(uint32 registration_id, int device_id, uint32 pre_key_id, ECPublicKey? pre_key_public, + uint32 signed_pre_key_id, ECPublicKey? signed_pre_key_public, uint8[]? signed_pre_key_signature, ECPublicKey? identity_key) throws Error { + PreKeyBundle res; + throw_by_code(PreKeyBundle.create(out res, registration_id, device_id, pre_key_id, pre_key_public, signed_pre_key_id, signed_pre_key_public, signed_pre_key_signature, identity_key), "Error creating PreKeyBundle"); + return res; +} + +internal string carr_to_string(char[] carr) { + char[] nu = new char[carr.length + 1]; + Memory.copy(nu, carr, carr.length); + return (string) nu; +} + +internal delegate int CodeErroringFunc() throws Error; + +internal int catch_to_code(CodeErroringFunc func) { + try { + return func(); + } catch (Error e) { + return e.code; + } +} + +} \ No newline at end of file -- cgit v1.2.3-54-g00ecf