aboutsummaryrefslogtreecommitdiff
path: root/plugins/signal-protocol/src
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/signal-protocol/src')
-rw-r--r--plugins/signal-protocol/src/context.vala101
-rw-r--r--plugins/signal-protocol/src/signal_helper.c506
-rw-r--r--plugins/signal-protocol/src/signal_helper.h43
-rw-r--r--plugins/signal-protocol/src/simple_iks.vala40
-rw-r--r--plugins/signal-protocol/src/simple_pks.vala33
-rw-r--r--plugins/signal-protocol/src/simple_spks.vala33
-rw-r--r--plugins/signal-protocol/src/simple_ss.vala77
-rw-r--r--plugins/signal-protocol/src/store.vala413
-rw-r--r--plugins/signal-protocol/src/util.vala45
9 files changed, 1291 insertions, 0 deletions
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<PreKeyRecord> generate_pre_keys(uint start, uint count) throws Error {
+ Gee.Set<PreKeyRecord> res = new Gee.HashSet<PreKeyRecord>();
+ 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 <signal_helper.h>
+#include <signal_protocol_internal.h>
+
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
+#include <openssl/rand.h>
+#include <openssl/sha.h>
+#include <openssl/err.h>
+
+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 <signal_protocol.h>
+#include <string.h>
+#include <glib.h>
+
+#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<string, Map<int, IdentityKeyStore.TrustedIdentity>> trusted_identities = new HashMap<string, Map<int, IdentityKeyStore.TrustedIdentity>>();
+
+ 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<int, IdentityKeyStore.TrustedIdentity>();
+ 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<uint32, PreKeyStore.Key> pre_key_map = new HashMap<uint32, PreKeyStore.Key>();
+
+ 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<uint32, SignedPreKeyStore.Key> pre_key_map = new HashMap<uint32, SignedPreKeyStore.Key>();
+
+ 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<string, ArrayList<SessionStore.Session>> session_map = new HashMap<string, ArrayList<SessionStore.Session>>();
+
+ 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>();
+ }
+ 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