aboutsummaryrefslogtreecommitdiff
path: root/libdino/src/crypto
diff options
context:
space:
mode:
authorfrosty <frostinator33@airmail.cc>2026-10-26 10:21:57 -0400
committerfrosty <frostinator33@airmail.cc>2026-10-26 10:21:57 -0400
commit74aecd8b0986d07b50b7ce310d64e645b8b54e38 (patch)
tree560e6434f183eb54cd779936f3a4c9575146a066 /libdino/src/crypto
parent3d7e90d401da02d430a87db0ec7e1db3a6f1a68f (diff)
parent4436001bb361ad4f9e4e05819214ecf0ea77c063 (diff)
Merge 'upstream/master'
Diffstat (limited to 'libdino/src/crypto')
-rw-r--r--libdino/src/crypto/cipher.vala19
-rw-r--r--libdino/src/crypto/cipher_converter.vala110
-rw-r--r--libdino/src/crypto/dtls_srtp.vala146
-rw-r--r--libdino/src/crypto/error.vala11
-rw-r--r--libdino/src/crypto/gnu/cipher.vala137
-rw-r--r--libdino/src/crypto/gnu/dtls_srtp.vala245
-rw-r--r--libdino/src/crypto/gnu/omemo.c257
-rw-r--r--libdino/src/crypto/gnu/utils.vala21
-rw-r--r--libdino/src/crypto/omemo/context.h0
-rw-r--r--libdino/src/crypto/omemo/context.vala113
-rw-r--r--libdino/src/crypto/omemo/helper.c64
-rw-r--r--libdino/src/crypto/omemo/helper.h40
-rw-r--r--libdino/src/crypto/omemo/simple_iks.vala60
-rw-r--r--libdino/src/crypto/omemo/simple_pks.vala47
-rw-r--r--libdino/src/crypto/omemo/simple_spks.vala47
-rw-r--r--libdino/src/crypto/omemo/simple_ss.vala84
-rw-r--r--libdino/src/crypto/omemo/store.vala361
-rw-r--r--libdino/src/crypto/omemo/util.vala47
-rw-r--r--libdino/src/crypto/openssl/cipher.vala156
-rw-r--r--libdino/src/crypto/openssl/dtls_srtp.vala202
-rw-r--r--libdino/src/crypto/openssl/omemo.c313
-rw-r--r--libdino/src/crypto/openssl/utils.c49
-rw-r--r--libdino/src/crypto/openssl/utils.h8
-rw-r--r--libdino/src/crypto/openssl/utils.vala19
-rw-r--r--libdino/src/crypto/srtp.vala122
25 files changed, 2678 insertions, 0 deletions
diff --git a/libdino/src/crypto/cipher.vala b/libdino/src/crypto/cipher.vala
new file mode 100644
index 00000000..89282144
--- /dev/null
+++ b/libdino/src/crypto/cipher.vala
@@ -0,0 +1,19 @@
+namespace Dino.Crypto {
+
+public interface SymmetricCipher {
+ public abstract void set_key(uint8[] key) throws Error;
+ public abstract void set_iv(uint8[] iv) throws Error;
+ public abstract void reset() throws Error;
+}
+
+public interface SymmetricCipherEncryption : SymmetricCipher, Object {
+ public abstract int encrypt(uint8[] output, uint8[] input) throws Error;
+ public abstract uint8[] get_tag(size_t taglen) throws Error;
+}
+
+public interface SymmetricCipherDecryption : SymmetricCipher, Object {
+ public abstract int decrypt(uint8[] output, uint8[] input) throws Error;
+ public abstract void check_tag(uint8[] tag) throws Error;
+}
+
+}
diff --git a/libdino/src/crypto/cipher_converter.vala b/libdino/src/crypto/cipher_converter.vala
new file mode 100644
index 00000000..c2404c6d
--- /dev/null
+++ b/libdino/src/crypto/cipher_converter.vala
@@ -0,0 +1,110 @@
+using GLib;
+
+namespace Dino.Crypto {
+
+public class SymmetricCipherEncrypter : Converter, Object {
+ private SymmetricCipherEncryption cipher;
+ private size_t attached_taglen;
+
+ public SymmetricCipherEncrypter(owned SymmetricCipherEncryption cipher, size_t attached_taglen = 0) {
+ this.cipher = (owned) cipher;
+ this.attached_taglen = attached_taglen;
+ }
+
+ public void reset() {
+ try {
+ cipher.reset();
+ } catch (Crypto.Error e) {
+ warning(@"$(e.domain) error while resetting cipher: $(e.message)");
+ }
+ }
+
+ public uint8[] get_tag(size_t taglen) throws Error {
+ return ((SymmetricCipherEncryption)cipher).get_tag(taglen);
+ }
+
+ public ConverterResult convert(uint8[] inbuf, uint8[] outbuf, ConverterFlags flags, out size_t bytes_read, out size_t bytes_written) throws IOError {
+ if (inbuf.length > outbuf.length) {
+ throw new IOError.NO_SPACE("CipherConverter needs at least the size of input as output space");
+ }
+ if ((flags & ConverterFlags.INPUT_AT_END) != 0 && inbuf.length + attached_taglen > outbuf.length) {
+ throw new IOError.NO_SPACE("CipherConverter needs additional output space to attach tag");
+ }
+ try {
+ if (inbuf.length > 0) {
+ ((SymmetricCipherEncryption)cipher).encrypt(outbuf, inbuf);
+ }
+ bytes_read = inbuf.length;
+ bytes_written = inbuf.length;
+ if ((flags & ConverterFlags.INPUT_AT_END) != 0) {
+ if (attached_taglen > 0) {
+ Memory.copy((uint8*)outbuf + inbuf.length, get_tag(attached_taglen), attached_taglen);
+ bytes_written = inbuf.length + attached_taglen;
+ }
+ return ConverterResult.FINISHED;
+ }
+ if ((flags & ConverterFlags.FLUSH) != 0) {
+ return ConverterResult.FLUSHED;
+ }
+ return ConverterResult.CONVERTED;
+ } catch (Crypto.Error e) {
+ throw new IOError.FAILED(@"$(e.domain) error while encrypting: $(e.message)");
+ }
+ }
+}
+
+public class SymmetricCipherDecrypter : Converter, Object {
+ private SymmetricCipherDecryption cipher;
+ private size_t attached_taglen;
+
+ public SymmetricCipherDecrypter(owned SymmetricCipherDecryption cipher, size_t attached_taglen = 0) {
+ this.cipher = (owned) cipher;
+ this.attached_taglen = attached_taglen;
+ }
+
+ public void reset() {
+ try {
+ cipher.reset();
+ } catch (Crypto.Error e) {
+ warning(@"$(e.domain) error while resetting cipher: $(e.message)");
+ }
+ }
+
+ public void check_tag(uint8[] tag) throws Error {
+ ((SymmetricCipherDecryption)cipher).check_tag(tag);
+ }
+
+ public ConverterResult convert(uint8[] inbuf, uint8[] outbuf, ConverterFlags flags, out size_t bytes_read, out size_t bytes_written) throws IOError {
+ if (inbuf.length > outbuf.length + attached_taglen) {
+ throw new IOError.NO_SPACE("CipherConverter needs at least the size of input as output space");
+ }
+ if ((flags & ConverterFlags.INPUT_AT_END) != 0 && inbuf.length < attached_taglen) {
+ throw new IOError.PARTIAL_INPUT("CipherConverter needs additional input to read tag");
+ } else if ((flags & ConverterFlags.INPUT_AT_END) == 0 && inbuf.length < attached_taglen + 1) {
+ throw new IOError.PARTIAL_INPUT("CipherConverter needs additional input to make sure to not accidentally read tag");
+ }
+ try {
+ inbuf.length -= (int) attached_taglen;
+ if (inbuf.length > 0) {
+ ((SymmetricCipherDecryption)cipher).decrypt(outbuf, inbuf);
+ }
+ bytes_read = inbuf.length;
+ bytes_written = inbuf.length;
+ inbuf.length += (int) attached_taglen;
+ if ((flags & ConverterFlags.INPUT_AT_END) != 0) {
+ if (attached_taglen > 0) {
+ check_tag(inbuf[(inbuf.length - attached_taglen):inbuf.length]);
+ bytes_read = inbuf.length;
+ }
+ return ConverterResult.FINISHED;
+ }
+ if ((flags & ConverterFlags.FLUSH) != 0) {
+ return ConverterResult.FLUSHED;
+ }
+ return ConverterResult.CONVERTED;
+ } catch (Crypto.Error e) {
+ throw new IOError.FAILED(@"$(e.domain) error while decrypting: $(e.message)");
+ }
+ }
+}
+}
diff --git a/libdino/src/crypto/dtls_srtp.vala b/libdino/src/crypto/dtls_srtp.vala
new file mode 100644
index 00000000..884f1a43
--- /dev/null
+++ b/libdino/src/crypto/dtls_srtp.vala
@@ -0,0 +1,146 @@
+namespace Dino.Crypto.DtlsSrtp {
+
+public abstract class CredentialsCapsule : Object {
+ public uint8[] own_fingerprint;
+}
+
+public abstract class AbstractHandler {
+
+ public signal void send_data(uint8[] data);
+
+ public bool ready { get {
+ return srtp_session.has_encrypt && srtp_session.has_decrypt;
+ }}
+
+ public Mode mode { get; set; default = Mode.CLIENT; }
+ public uint8[] own_fingerprint { get; private set; }
+ public uint8[] peer_fingerprint { get; set; }
+ public string peer_fp_algo { get; set; }
+
+ protected CredentialsCapsule credentials;
+ protected Cond buffer_cond = Cond();
+ protected Mutex buffer_mutex = Mutex();
+ protected Gee.LinkedList<Bytes> buffer_queue = new Gee.LinkedList<Bytes>();
+
+ protected bool running = false;
+ protected bool stop = false;
+ protected bool restart = false;
+
+ protected Crypto.Srtp.Session srtp_session = new Crypto.Srtp.Session();
+
+ protected AbstractHandler.with_cert(CredentialsCapsule creds) {
+ this.credentials = creds;
+ this.own_fingerprint = creds.own_fingerprint;
+ }
+
+ public uint8[]? process_incoming_data(uint component_id, uint8[] data) throws Crypto.Error {
+ if (data[0] >= 128) {
+ if (!srtp_session.has_decrypt) {
+ debug("Received data before SRTP session is ready, dropping.");
+ return null;
+ }
+ if (component_id == 1) {
+ if (data.length >= 2 && data[1] >= 192 && data[1] < 224) {
+ return srtp_session.decrypt_rtcp(data);
+ }
+ return srtp_session.decrypt_rtp(data);
+ }
+ if (component_id == 2) return srtp_session.decrypt_rtcp(data);
+ }
+ if (component_id == 1 && data.length >= 1 && (data[0] >= 20 && data[0] < 64)) {
+ on_data_rec(data);
+ return null;
+ }
+ debug("Dropping unknown data from component %u", component_id);
+ return null;
+ }
+
+ public uint8[]? process_outgoing_data(uint component_id, uint8[] data) throws Crypto.Error {
+ if (srtp_session.has_encrypt) {
+ if (component_id == 1) {
+ if (data.length >= 2 && data[1] >= 192 && data[1] < 224) {
+ return srtp_session.encrypt_rtcp(data);
+ }
+ return srtp_session.encrypt_rtp(data);
+ }
+ if (component_id == 2) return srtp_session.encrypt_rtcp(data);
+ }
+ return null;
+ }
+
+ public void on_data_rec(owned uint8[] data) {
+ buffer_mutex.lock();
+ buffer_queue.add(new Bytes.take(data));
+ buffer_cond.signal();
+ buffer_mutex.unlock();
+ }
+
+ public void stop_dtls_connection() {
+ buffer_mutex.lock();
+ stop = true;
+ buffer_cond.signal();
+ buffer_mutex.unlock();
+ }
+
+ protected Bytes get_buffer_if_present () {
+ buffer_mutex.lock();
+ if (buffer_queue.size == 0) {
+ buffer_mutex.unlock();
+ return null;
+ }
+ Bytes res = buffer_queue.remove_at(0);
+ buffer_mutex.unlock();
+ return res;
+ }
+
+ public async Xmpp.Xep.Jingle.ContentEncryption? setup_dtls_connection() {
+ MainContext context = MainContext.current_source().get_context();
+ var thread = new Thread<Xmpp.Xep.Jingle.ContentEncryption>("dtls-connection", () => {
+ // Check we can start working on the thread
+ buffer_mutex.lock();
+ if (stop) {
+ restart = true;
+ buffer_mutex.unlock();
+ return null;
+ }
+ if (running || ready) {
+ buffer_mutex.unlock();
+ return null;
+ }
+ running = true;
+ restart = false;
+ buffer_mutex.unlock();
+
+ // Actual work
+ var res = setup_dtls_connection_thread();
+
+ // Callback
+ Source source = new IdleSource();
+ source.set_callback(setup_dtls_connection.callback);
+ source.attach(context);
+ return res;
+ });
+ yield;
+ return thread.join();
+ }
+
+ protected abstract Xmpp.Xep.Jingle.ContentEncryption? setup_dtls_connection_thread();
+}
+
+private string format_fingerprint(uint8[] fingerprint) {
+ var sb = new StringBuilder();
+ for (int i = 0; i < fingerprint.length; i++) {
+ sb.append("%02X".printf(fingerprint[i]));
+ if (i < fingerprint.length - 1) {
+ sb.append(":");
+ }
+ }
+ return sb.str;
+}
+
+
+public enum Mode {
+ CLIENT, SERVER
+}
+
+}
diff --git a/libdino/src/crypto/error.vala b/libdino/src/crypto/error.vala
new file mode 100644
index 00000000..2458ae4a
--- /dev/null
+++ b/libdino/src/crypto/error.vala
@@ -0,0 +1,11 @@
+namespace Dino.Crypto {
+
+public errordomain Error {
+ ILLEGAL_ARGUMENTS,
+ GCRYPT,
+ OPENSSL,
+ AUTHENTICATION_FAILED,
+ UNKNOWN
+}
+
+}
diff --git a/libdino/src/crypto/gnu/cipher.vala b/libdino/src/crypto/gnu/cipher.vala
new file mode 100644
index 00000000..41f15837
--- /dev/null
+++ b/libdino/src/crypto/gnu/cipher.vala
@@ -0,0 +1,137 @@
+namespace Dino.Crypto {
+
+internal class SymmetricCipherGcrypt : SymmetricCipher, SymmetricCipherEncryption, SymmetricCipherDecryption, Object {
+ private GCrypt.Cipher.Cipher cipher;
+
+ private static unowned string mode_to_string(GCrypt.Cipher.Mode mode) {
+ switch (mode) {
+ case GCrypt.Cipher.Mode.ECB: return "ECB";
+ case GCrypt.Cipher.Mode.CFB: return "CFB";
+ case GCrypt.Cipher.Mode.CBC: return "CBC";
+ case GCrypt.Cipher.Mode.STREAM: return "STREAM";
+ case GCrypt.Cipher.Mode.OFB: return "OFB";
+ case GCrypt.Cipher.Mode.CTR: return "CTR";
+ case GCrypt.Cipher.Mode.AESWRAP: return "AESWRAP";
+ case GCrypt.Cipher.Mode.GCM: return "GCM";
+ case GCrypt.Cipher.Mode.POLY1305: return "POLY1305";
+ case GCrypt.Cipher.Mode.OCB: return "OCB";
+ case GCrypt.Cipher.Mode.CFB8: return "CFB8";
+ // case GCrypt.Cipher.Mode.XTS: return "XTS"; // Not supported in gcrypt < 1.8
+ }
+ return "NONE";
+ }
+
+ private static GCrypt.Cipher.Mode mode_from_string(string name) {
+ switch (name) {
+ case "ECB": return GCrypt.Cipher.Mode.ECB;
+ case "CFB": return GCrypt.Cipher.Mode.CFB;
+ case "CBC": return GCrypt.Cipher.Mode.CBC;
+ case "STREAM": return GCrypt.Cipher.Mode.STREAM;
+ case "OFB": return GCrypt.Cipher.Mode.OFB;
+ case "CTR": return GCrypt.Cipher.Mode.CTR;
+ case "AESWRAP": return GCrypt.Cipher.Mode.AESWRAP;
+ case "GCM": return GCrypt.Cipher.Mode.GCM;
+ case "POLY1305": return GCrypt.Cipher.Mode.POLY1305;
+ case "OCB": return GCrypt.Cipher.Mode.OCB;
+ case "CFB8": return GCrypt.Cipher.Mode.CFB8;
+ // case "XTS": return GCrypt.Cipher.Mode.XTS; // Not supported in gcrypt < 1.8
+ }
+ return GCrypt.Cipher.Mode.NONE;
+ }
+
+ private static string flags_to_string(GCrypt.Cipher.Flag flags) {
+ string? s = null;
+ if ((GCrypt.Cipher.Flag.CBC_MAC & flags) != 0) s = (s == null ? "" : @"$s-") + "MAC";
+ if ((GCrypt.Cipher.Flag.CBC_CTS & flags) != 0) s = (s == null ? "" : @"$s-") + "CTS";
+ if ((GCrypt.Cipher.Flag.ENABLE_SYNC & flags) != 0) s = (s == null ? "" : @"$s-") + "SYNC";
+ if ((GCrypt.Cipher.Flag.SECURE & flags) != 0) s = (s == null ? "" : @"$s-") + "SECURE";
+ return s ?? "NONE";
+ }
+
+ private static GCrypt.Cipher.Flag flag_from_string(string flag_name) {
+ if (flag_name == "SECURE") return GCrypt.Cipher.Flag.SECURE;
+ if (flag_name == "SYNC") return GCrypt.Cipher.Flag.ENABLE_SYNC;
+ if (flag_name == "CTS") return GCrypt.Cipher.Flag.CBC_CTS;
+ if (flag_name == "MAC") return GCrypt.Cipher.Flag.CBC_MAC;
+ return 0;
+ }
+
+ private static GCrypt.Cipher.Flag flags_from_string(string flag_names) {
+ GCrypt.Cipher.Flag flags = 0;
+ foreach(string flag in flag_names.split("-")) {
+ flags |= flag_from_string(flag);
+ }
+ return flags;
+ }
+
+ private static bool parse(string algo_name, out GCrypt.Cipher.Algorithm algo, out GCrypt.Cipher.Mode mode, out GCrypt.Cipher.Flag flags) {
+ algo = GCrypt.Cipher.Algorithm.NONE;
+ mode = GCrypt.Cipher.Mode.NONE;
+ flags = 0;
+ string[] algo_parts = algo_name.split("-", 3);
+
+ algo = GCrypt.Cipher.Algorithm.from_string(algo_parts[0]);
+ if (algo_parts.length >= 2) {
+ mode = mode_from_string(algo_parts[1]);
+ }
+ if (algo_parts.length == 3) {
+ flags |= flags_from_string(algo_parts[2]);
+ }
+ return to_algo_name(algo, mode, flags) == algo_name;
+ }
+
+ private static string to_algo_name(GCrypt.Cipher.Algorithm algo = GCrypt.Cipher.Algorithm.NONE, GCrypt.Cipher.Mode mode = GCrypt.Cipher.Mode.NONE, GCrypt.Cipher.Flag flags = 0) {
+ if (flags != 0) {
+ return @"$algo-$(mode_to_string(mode))-$(flags_to_string(flags))";
+ } else if (mode != GCrypt.Cipher.Mode.NONE) {
+ return @"$algo-$(mode_to_string(mode))";
+ } else {
+ return algo.to_string();
+ }
+ }
+
+ public SymmetricCipherGcrypt(string algo_name) throws Error {
+ GCrypt.Cipher.Algorithm algo;
+ GCrypt.Cipher.Mode mode;
+ GCrypt.Cipher.Flag flags;
+ if (parse(algo_name, out algo, out mode, out flags)) {
+ may_throw_gcrypt_error(GCrypt.Cipher.Cipher.open(out this.cipher, algo, mode, flags));
+ } else {
+ throw new Error.ILLEGAL_ARGUMENTS(@"The algorithm $algo_name is not supported");
+ }
+ }
+
+ public void set_key(uint8[] key) throws Error {
+ may_throw_gcrypt_error(cipher.set_key(key));
+ }
+
+ public void set_iv(uint8[] iv) throws Error {
+ may_throw_gcrypt_error(cipher.set_iv(iv));
+ }
+
+ public void reset() throws Error {
+ may_throw_gcrypt_error(cipher.reset());
+ }
+
+ public uint8[] get_tag(size_t taglen) throws Error {
+ uint8[] tag = new uint8[taglen];
+ may_throw_gcrypt_error(cipher.get_tag(tag));
+ return tag;
+ }
+
+ public void check_tag(uint8[] tag) throws Error {
+ may_throw_gcrypt_error(cipher.check_tag(tag));
+ }
+
+ public int encrypt(uint8[] output, uint8[] input) throws Error {
+ may_throw_gcrypt_error(cipher.encrypt(output, input));
+ return input.length;
+ }
+
+ public int decrypt(uint8[] output, uint8[] input) throws Error {
+ may_throw_gcrypt_error(cipher.decrypt(output, input));
+ return input.length;
+ }
+}
+
+}
diff --git a/libdino/src/crypto/gnu/dtls_srtp.vala b/libdino/src/crypto/gnu/dtls_srtp.vala
new file mode 100644
index 00000000..f6f0a24e
--- /dev/null
+++ b/libdino/src/crypto/gnu/dtls_srtp.vala
@@ -0,0 +1,245 @@
+using GnuTLS;
+
+namespace Dino.Crypto.DtlsSrtp {
+
+public class CredentialsCapsuleGnuTLS : CredentialsCapsule {
+ internal X509.Certificate[] own_cert;
+ internal X509.PrivateKey private_key;
+}
+
+private uint8[] get_fingerprint(X509.Certificate certificate, DigestAlgorithm digest_algo) {
+ uint8[] buf = new uint8[512];
+ size_t buf_out_size = 512;
+ certificate.get_fingerprint(digest_algo, buf, ref buf_out_size);
+
+ uint8[] ret = new uint8[buf_out_size];
+ for (int i = 0; i < buf_out_size; i++) {
+ ret[i] = buf[i];
+ }
+ return ret;
+}
+
+public CredentialsCapsule generate_credentials () throws GLib.Error {
+ int err = 0;
+
+ X509.PrivateKey private_key = X509.PrivateKey.create ();
+ err = private_key.generate (PKAlgorithm.ECDSA, 256);
+ throw_if_error (err);
+
+ var start_time = new DateTime.now_local().add_days(-1);
+ var end_time = start_time.add_days (2);
+
+ X509.Certificate cert = X509.Certificate.create ();
+ cert.set_key (private_key);
+ cert.set_version (1);
+ cert.set_activation_time ((time_t) start_time.to_unix ());
+ cert.set_expiration_time ((time_t) end_time.to_unix ());
+
+ uint32 serial = 1;
+ cert.set_serial (&serial, sizeof(uint32));
+
+ cert.sign (cert, private_key);
+
+ uint8[] own_fingerprint = get_fingerprint (cert, DigestAlgorithm.SHA256);
+ X509.Certificate[] own_cert = new X509.Certificate[] { (owned)cert };
+
+ var creds = new CredentialsCapsuleGnuTLS();
+ creds.own_fingerprint = own_fingerprint;
+ creds.own_cert = (owned) own_cert;
+ creds.private_key = (owned) private_key;
+
+ return creds;
+}
+
+public class Handler : AbstractHandler {
+
+ public Handler.with_cert(CredentialsCapsule creds) {
+ base.with_cert(creds);
+ }
+
+ protected override Xmpp.Xep.Jingle.ContentEncryption? setup_dtls_connection_thread() {
+ InitFlags server_or_client = mode == Mode.SERVER ? InitFlags.SERVER : InitFlags.CLIENT;
+ debug("Setting up DTLS connection using GnuTLS. We're %s", mode.to_string());
+
+ CertificateCredentials cert_cred = CertificateCredentials.create();
+ int err = cert_cred.set_x509_key(((CredentialsCapsuleGnuTLS)credentials).own_cert, ((CredentialsCapsuleGnuTLS)credentials).private_key);
+ throw_if_error(err);
+
+ Session? session = Session.create(server_or_client | InitFlags.DATAGRAM);
+ session.enable_heartbeat(1);
+ session.set_srtp_profile_direct("SRTP_AES128_CM_HMAC_SHA1_80");
+ session.set_credentials(GnuTLS.CredentialsType.CERTIFICATE, cert_cred);
+ session.server_set_request(CertificateRequest.REQUEST);
+ session.set_priority_from_string("NORMAL:!VERS-TLS-ALL:+VERS-DTLS-ALL:+CTYPE-CLI-X509");
+
+ session.set_transport_pointer(this);
+ session.set_pull_function(pull_function);
+ session.set_pull_timeout_function(pull_timeout_function);
+ session.set_push_function(push_function);
+ session.set_verify_function(verify_function);
+
+ DateTime maximum_time = new DateTime.now_utc().add_seconds(20);
+ do {
+ err = session.handshake();
+
+ DateTime current_time = new DateTime.now_utc();
+ if (maximum_time.compare(current_time) < 0) {
+ warning("DTLS handshake timeouted");
+ err = ErrorCode.APPLICATION_ERROR_MIN + 1;
+ break;
+ }
+ if (stop) {
+ debug("DTLS handshake stopped");
+ err = ErrorCode.APPLICATION_ERROR_MIN + 2;
+ break;
+ }
+ } while (err < 0 && !((ErrorCode)err).is_fatal());
+
+ buffer_mutex.lock();
+ if (stop) {
+ stop = false;
+ running = false;
+ bool restart = restart;
+ buffer_mutex.unlock();
+ if (restart) {
+ debug("Restarting DTLS handshake");
+ return setup_dtls_connection_thread();
+ }
+ return null;
+ }
+ buffer_mutex.unlock();
+ if (err != ErrorCode.SUCCESS) {
+ warning("DTLS handshake failed: %s", ((ErrorCode)err).to_string());
+ return null;
+ }
+
+ uint8[] km = new uint8[150];
+ Datum? client_key, client_salt, server_key, server_salt;
+ session.get_srtp_keys(km, km.length, out client_key, out client_salt, out server_key, out server_salt);
+ if (client_key == null || client_salt == null || server_key == null || server_salt == null) {
+ warning("SRTP client/server key/salt null");
+ }
+
+ debug("Finished DTLS connection. We're %s", mode.to_string());
+ if (mode == Mode.SERVER) {
+ srtp_session.set_encryption_key(Crypto.Srtp.AES_CM_128_HMAC_SHA1_80, server_key.extract(), server_salt.extract());
+ srtp_session.set_decryption_key(Crypto.Srtp.AES_CM_128_HMAC_SHA1_80, client_key.extract(), client_salt.extract());
+ } else {
+ srtp_session.set_encryption_key(Crypto.Srtp.AES_CM_128_HMAC_SHA1_80, client_key.extract(), client_salt.extract());
+ srtp_session.set_decryption_key(Crypto.Srtp.AES_CM_128_HMAC_SHA1_80, server_key.extract(), server_salt.extract());
+ }
+ return new Xmpp.Xep.Jingle.ContentEncryption(Xmpp.Xep.JingleIceUdp.DTLS_NS_URI, "DTLS-SRTP", credentials.own_fingerprint, peer_fingerprint);
+ }
+
+ private static ssize_t pull_function(void* transport_ptr, uint8[] buffer) {
+ Handler self = transport_ptr as Handler;
+
+ self.buffer_mutex.lock();
+ while (self.buffer_queue.size == 0) {
+ self.buffer_cond.wait(self.buffer_mutex);
+ if (self.stop) {
+ self.buffer_mutex.unlock();
+ debug("DTLS handshake pull_function stopped");
+ return -1;
+ }
+ }
+ Bytes data = self.buffer_queue.remove_at(0);
+ self.buffer_mutex.unlock();
+
+ uint8[] data_uint8 = Bytes.unref_to_data((owned) data);
+ Memory.copy(buffer, data_uint8, data_uint8.length);
+
+ // The callback should return 0 on connection termination, a positive number indicating the number of bytes received, and -1 on error.
+ return (ssize_t)data_uint8.length;
+ }
+
+ private static int pull_timeout_function(void* transport_ptr, uint ms) {
+ Handler self = transport_ptr as Handler;
+
+ int64 end_time = get_monotonic_time() + ms * 1000;
+
+ self.buffer_mutex.lock();
+ while (self.buffer_queue.size == 0) {
+ self.buffer_cond.wait_until(self.buffer_mutex, end_time);
+ if (self.stop) {
+ self.buffer_mutex.unlock();
+ debug("DTLS handshake pull_timeout_function stopped");
+ return -1;
+ }
+
+ if (get_monotonic_time() > end_time) {
+ self.buffer_mutex.unlock();
+ return 0;
+ }
+ }
+ self.buffer_mutex.unlock();
+
+ // The callback should return 0 on timeout, a positive number if data can be received, and -1 on error.
+ return 1;
+ }
+
+ private static ssize_t push_function(void* transport_ptr, uint8[] buffer) {
+ Handler self = transport_ptr as Handler;
+ self.send_data(buffer);
+
+ // The callback should return a positive number indicating the bytes sent, and -1 on error.
+ return (ssize_t)buffer.length;
+ }
+
+ private static int verify_function(Session session) {
+ Handler self = session.get_transport_pointer() as Handler;
+ try {
+ bool valid = self.verify_peer_cert(session);
+ if (!valid) {
+ warning("DTLS certificate invalid. Aborting handshake.");
+ return 1;
+ }
+ } catch (Error e) {
+ warning("Error during DTLS certificate validation: %s. Aborting handshake.", e.message);
+ return 1;
+ }
+
+ // The callback function should return 0 for the handshake to continue or non-zero to terminate.
+ return 0;
+ }
+
+ private bool verify_peer_cert(Session session) throws GLib.Error {
+ unowned Datum[] cert_datums = session.get_peer_certificates();
+ if (cert_datums.length == 0) {
+ warning("No peer certs");
+ return false;
+ }
+ if (cert_datums.length > 1) warning("More than one peer cert");
+
+ X509.Certificate peer_cert = X509.Certificate.create();
+ peer_cert.import(ref cert_datums[0], CertificateFormat.DER);
+
+ DigestAlgorithm algo;
+ switch (peer_fp_algo) {
+ case "sha-256":
+ algo = DigestAlgorithm.SHA256;
+ break;
+ default:
+ warning("Unkown peer fingerprint algorithm: %s", peer_fp_algo);
+ return false;
+ }
+
+ uint8[] real_peer_fp = get_fingerprint(peer_cert, algo);
+
+ if (real_peer_fp.length != this.peer_fingerprint.length) {
+ warning("Fingerprint lengths not equal %i vs %i", real_peer_fp.length, peer_fingerprint.length);
+ return false;
+ }
+
+ for (int i = 0; i < real_peer_fp.length; i++) {
+ if (real_peer_fp[i] != this.peer_fingerprint[i]) {
+ warning("First cert in peer cert list doesn't equal advertised one: %s vs %s", format_fingerprint(real_peer_fp), format_fingerprint(peer_fingerprint));
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
+
+} \ No newline at end of file
diff --git a/libdino/src/crypto/gnu/omemo.c b/libdino/src/crypto/gnu/omemo.c
new file mode 100644
index 00000000..765e3d79
--- /dev/null
+++ b/libdino/src/crypto/gnu/omemo.c
@@ -0,0 +1,257 @@
+#include "../omemo/helper.h"
+#include <gcrypt.h>
+
+int signal_vala_random_generator(uint8_t *data, size_t len, void *user_data) {
+ gcry_randomize(data, len, GCRY_STRONG_RANDOM);
+ return SG_SUCCESS;
+}
+
+int signal_vala_hmac_sha256_init(void **hmac_context, const uint8_t *key, size_t key_len, void *user_data) {
+ gcry_mac_hd_t* ctx = malloc(sizeof(gcry_mac_hd_t));
+ if (!ctx) return SG_ERR_NOMEM;
+
+ if (gcry_mac_open(ctx, GCRY_MAC_HMAC_SHA256, 0, 0)) {
+ free(ctx);
+ return SG_ERR_UNKNOWN;
+ }
+
+ if (gcry_mac_setkey(*ctx, key, key_len)) {
+ free(ctx);
+ return SG_ERR_UNKNOWN;
+ }
+
+ *hmac_context = ctx;
+
+ return SG_SUCCESS;
+}
+
+int signal_vala_hmac_sha256_update(void *hmac_context, const uint8_t *data, size_t data_len, void *user_data) {
+ gcry_mac_hd_t* ctx = hmac_context;
+
+ if (gcry_mac_write(*ctx, data, data_len)) return SG_ERR_UNKNOWN;
+
+ return SG_SUCCESS;
+}
+
+int signal_vala_hmac_sha256_final(void *hmac_context, signal_buffer **output, void *user_data) {
+ size_t len = gcry_mac_get_algo_maclen(GCRY_MAC_HMAC_SHA256);
+ uint8_t md[len];
+ gcry_mac_hd_t* ctx = hmac_context;
+
+ if (gcry_mac_read(*ctx, md, &len)) return SG_ERR_UNKNOWN;
+
+ signal_buffer *output_buffer = signal_buffer_create(md, len);
+ if (!output_buffer) return SG_ERR_NOMEM;
+
+ *output = output_buffer;
+
+ return SG_SUCCESS;
+}
+
+void signal_vala_hmac_sha256_cleanup(void *hmac_context, void *user_data) {
+ gcry_mac_hd_t* ctx = hmac_context;
+ if (ctx) {
+ gcry_mac_close(*ctx);
+ free(ctx);
+ }
+}
+
+static int aes_cipher(int cipher, size_t key_len, int* algo, int* mode) {
+ switch (key_len) {
+ case 16:
+ *algo = GCRY_CIPHER_AES128;
+ break;
+ case 24:
+ *algo = GCRY_CIPHER_AES192;
+ break;
+ case 32:
+ *algo = GCRY_CIPHER_AES256;
+ break;
+ default:
+ return SG_ERR_UNKNOWN;
+ }
+ switch (cipher) {
+ case SG_CIPHER_AES_CBC_PKCS5:
+ *mode = GCRY_CIPHER_MODE_CBC;
+ break;
+ case SG_CIPHER_AES_CTR_NOPADDING:
+ *mode = GCRY_CIPHER_MODE_CTR;
+ break;
+ case SG_CIPHER_AES_GCM_NOPADDING:
+ *mode = GCRY_CIPHER_MODE_GCM;
+ break;
+ default:
+ return SG_ERR_UNKNOWN;
+ }
+ return SG_SUCCESS;
+}
+
+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 algo, mode, error_code = SG_ERR_UNKNOWN;
+ if (aes_cipher(cipher, key_len, &algo, &mode)) return SG_ERR_INVAL;
+
+ gcry_cipher_hd_t ctx = {0};
+
+ if (gcry_cipher_open(&ctx, algo, mode, 0)) return SG_ERR_NOMEM;
+
+ signal_buffer* padded = 0;
+ signal_buffer* out_buf = 0;
+ goto no_error;
+error:
+ gcry_cipher_close(ctx);
+ if (padded != 0) {
+ signal_buffer_bzero_free(padded);
+ }
+ if (out_buf != 0) {
+ signal_buffer_free(out_buf);
+ }
+ return error_code;
+no_error:
+
+ if (gcry_cipher_setkey(ctx, key, key_len)) goto error;
+
+ uint8_t tag_len = 0, pad_len = 0;
+ switch (cipher) {
+ case SG_CIPHER_AES_CBC_PKCS5:
+ if (gcry_cipher_setiv(ctx, iv, iv_len)) goto error;
+ pad_len = 16 - (plaintext_len % 16);
+ if (pad_len == 0) pad_len = 16;
+ break;
+ case SG_CIPHER_AES_CTR_NOPADDING:
+ if (gcry_cipher_setctr(ctx, iv, iv_len)) goto error;
+ break;
+ case SG_CIPHER_AES_GCM_NOPADDING:
+ if (gcry_cipher_setiv(ctx, iv, iv_len)) goto error;
+ tag_len = 16;
+ break;
+ default:
+ return SG_ERR_UNKNOWN;
+ }
+
+ size_t padded_len = plaintext_len + pad_len;
+ padded = signal_buffer_alloc(padded_len);
+ if (padded == 0) {
+ error_code = SG_ERR_NOMEM;
+ goto error;
+ }
+
+ memset(signal_buffer_data(padded) + plaintext_len, pad_len, pad_len);
+ memcpy(signal_buffer_data(padded), plaintext, plaintext_len);
+
+ out_buf = signal_buffer_alloc(padded_len + tag_len);
+ if (out_buf == 0) {
+ error_code = SG_ERR_NOMEM;
+ goto error;
+ }
+
+ if (gcry_cipher_encrypt(ctx, signal_buffer_data(out_buf), padded_len, signal_buffer_data(padded), padded_len)) goto error;
+
+ if (tag_len > 0) {
+ if (gcry_cipher_gettag(ctx, signal_buffer_data(out_buf) + padded_len, tag_len)) goto error;
+ }
+
+ *output = out_buf;
+ out_buf = 0;
+
+ signal_buffer_bzero_free(padded);
+ padded = 0;
+
+ gcry_cipher_close(ctx);
+ return SG_SUCCESS;
+}
+
+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 algo, mode, error_code = SG_ERR_UNKNOWN;
+ *output = 0;
+ if (aes_cipher(cipher, key_len, &algo, &mode)) return SG_ERR_INVAL;
+ if (ciphertext_len == 0) return SG_ERR_INVAL;
+
+ gcry_cipher_hd_t ctx = {0};
+
+ if (gcry_cipher_open(&ctx, algo, mode, 0)) return SG_ERR_NOMEM;
+
+ signal_buffer* out_buf = 0;
+ goto no_error;
+error:
+ gcry_cipher_close(ctx);
+ if (out_buf != 0) {
+ signal_buffer_bzero_free(out_buf);
+ }
+ return error_code;
+no_error:
+
+ if (gcry_cipher_setkey(ctx, key, key_len)) goto error;
+
+ uint8_t tag_len = 0, pkcs_pad = FALSE;
+ switch (cipher) {
+ case SG_CIPHER_AES_CBC_PKCS5:
+ if (gcry_cipher_setiv(ctx, iv, iv_len)) goto error;
+ pkcs_pad = TRUE;
+ break;
+ case SG_CIPHER_AES_CTR_NOPADDING:
+ if (gcry_cipher_setctr(ctx, iv, iv_len)) goto error;
+ break;
+ case SG_CIPHER_AES_GCM_NOPADDING:
+ if (gcry_cipher_setiv(ctx, iv, iv_len)) goto error;
+ if (ciphertext_len < 16) goto error;
+ tag_len = 16;
+ break;
+ default:
+ goto error;
+ }
+
+ size_t padded_len = ciphertext_len - tag_len;
+ out_buf = signal_buffer_alloc(padded_len);
+ if (out_buf == 0) {
+ error_code = SG_ERR_NOMEM;
+ goto error;
+ }
+
+ if (gcry_cipher_decrypt(ctx, signal_buffer_data(out_buf), signal_buffer_len(out_buf), ciphertext, padded_len)) goto error;
+
+ if (tag_len > 0) {
+ if (gcry_cipher_checktag(ctx, ciphertext + padded_len, tag_len)) goto error;
+ }
+
+ if (pkcs_pad) {
+ uint8_t pad_len = signal_buffer_data(out_buf)[padded_len - 1];
+ if (pad_len > 16 || pad_len > padded_len) goto error;
+ *output = signal_buffer_create(signal_buffer_data(out_buf), padded_len - pad_len);
+ signal_buffer_bzero_free(out_buf);
+ out_buf = 0;
+ } else {
+ *output = out_buf;
+ out_buf = 0;
+ }
+
+ gcry_cipher_close(ctx);
+ return SG_SUCCESS;
+}
+
+void setup_signal_vala_crypto_provider(signal_context *context) {
+ gcry_check_version(NULL);
+
+ 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,
+ .encrypt_func = signal_vala_encrypt,
+ .decrypt_func = signal_vala_decrypt,
+ .user_data = 0
+ };
+
+ signal_context_set_crypto_provider(context, &provider);
+}
+
diff --git a/libdino/src/crypto/gnu/utils.vala b/libdino/src/crypto/gnu/utils.vala
new file mode 100644
index 00000000..8a25ab27
--- /dev/null
+++ b/libdino/src/crypto/gnu/utils.vala
@@ -0,0 +1,21 @@
+namespace Dino.Crypto {
+
+public static void randomize(uint8[] buffer) {
+ GCrypt.Random.randomize(buffer);
+}
+
+internal void may_throw_gcrypt_error(GCrypt.Error e) throws Error {
+ if (((int)e) != 0) {
+ throw new Crypto.Error.GCRYPT(e.to_string());
+ }
+}
+
+public SymmetricCipherEncryption create_symmetric_cipher_encryption(string algo_name) throws Error {
+ return new SymmetricCipherGcrypt(algo_name);
+}
+
+public SymmetricCipherDecryption create_symmetric_cipher_decryption(string algo_name) throws Error {
+ return new SymmetricCipherGcrypt(algo_name);
+}
+
+}
diff --git a/libdino/src/crypto/omemo/context.h b/libdino/src/crypto/omemo/context.h
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/libdino/src/crypto/omemo/context.h
diff --git a/libdino/src/crypto/omemo/context.vala b/libdino/src/crypto/omemo/context.vala
new file mode 100644
index 00000000..3238dc3d
--- /dev/null
+++ b/libdino/src/crypto/omemo/context.vala
@@ -0,0 +1,113 @@
+using Omemo;
+
+namespace Dino.Crypto.Omemo {
+
+public class Context {
+ public 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, size_t len, 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 SignedPreKeyRecord generate_signed_pre_key(IdentityKeyPair identity_key_pair, int32 id, uint64 timestamp = 0) throws Error {
+ if (timestamp == 0) timestamp = new DateTime.now_utc().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();
+ PreKeyRecord record;
+ throw_by_code(PreKeyRecord.create(out record, i, pair));
+ res.add(record);
+ }
+ 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 OmemoMessage deserialize_signal_message(uint8[] data) throws Error {
+ OmemoMessage res;
+ throw_by_code(message_deserialize(out res, data, native_context));
+ return res;
+ }
+
+ public OmemoMessage deserialize_omemo_message(uint8[] data) throws Error {
+ OmemoMessage res;
+ throw_by_code(message_deserialize_omemo(out res, data, native_context));
+ return res;
+ }
+
+ public OmemoMessage copy_message(CiphertextMessage original) throws Error {
+ OmemoMessage res;
+ throw_by_code(message_copy(out res, (OmemoMessage) original, native_context));
+ return res;
+ }
+
+ public PreKeyOmemoMessage deserialize_signal_pre_key_message(uint8[] data) throws Error {
+ PreKeyOmemoMessage res;
+ throw_by_code(pre_key_message_deserialize(out res, data, native_context));
+ return res;
+ }
+
+ public PreKeyOmemoMessage deserialize_omemo_pre_key_message(uint8[] data, uint32 remote_registration_id) throws Error {
+ PreKeyOmemoMessage res;
+ throw_by_code(pre_key_message_deserialize_omemo(out res, data, remote_registration_id, native_context));
+ return res;
+ }
+
+ public PreKeyOmemoMessage copy_pre_key_message(CiphertextMessage original) throws Error {
+ PreKeyOmemoMessage res;
+ throw_by_code(pre_key_message_copy(out res, (PreKeyOmemoMessage) original, native_context));
+ return res;
+ }
+}
+
+}
diff --git a/libdino/src/crypto/omemo/helper.c b/libdino/src/crypto/omemo/helper.c
new file mode 100644
index 00000000..1fafd2b0
--- /dev/null
+++ b/libdino/src/crypto/omemo/helper.c
@@ -0,0 +1,64 @@
+#include "src/crypto/omemo/helper.h"
+
+signal_type_base* signal_type_ref_vapi(void* instance) {
+ g_return_val_if_fail(instance != NULL, NULL);
+ signal_type_ref(instance);
+ return instance;
+}
+
+signal_type_base* signal_type_unref_vapi(void* instance) {
+ g_return_val_if_fail(instance != NULL, NULL);
+ signal_type_unref(instance);
+ return NULL;
+}
+
+signal_protocol_address* signal_protocol_address_new(const gchar* name, int32_t device_id) {
+ g_return_val_if_fail(name != NULL, NULL);
+ signal_protocol_address* address = malloc(sizeof(signal_protocol_address));
+ address->device_id = -1;
+ address->name = NULL;
+ signal_protocol_address_set_name(address, name);
+ signal_protocol_address_set_device_id(address, device_id);
+ return address;
+}
+
+void signal_protocol_address_free(signal_protocol_address* ptr) {
+ g_return_if_fail(ptr != NULL);
+ if (ptr->name) {
+ g_free((void*)ptr->name);
+ }
+ return free(ptr);
+}
+
+void signal_protocol_address_set_name(signal_protocol_address* self, const gchar* name) {
+ g_return_if_fail(self != NULL);
+ g_return_if_fail(name != NULL);
+ 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) {
+ g_return_val_if_fail(self != NULL, NULL);
+ g_return_val_if_fail(self->name != NULL, 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;
+}
+
+int32_t signal_protocol_address_get_device_id(signal_protocol_address* self) {
+ g_return_val_if_fail(self != NULL, -1);
+ return self->device_id;
+}
+
+void signal_protocol_address_set_device_id(signal_protocol_address* self, int32_t device_id) {
+ g_return_if_fail(self != NULL);
+ self->device_id = device_id;
+}
+
diff --git a/libdino/src/crypto/omemo/helper.h b/libdino/src/crypto/omemo/helper.h
new file mode 100644
index 00000000..cbbe220f
--- /dev/null
+++ b/libdino/src/crypto/omemo/helper.h
@@ -0,0 +1,40 @@
+#ifndef SIGNAL_PROTOCOL_VALA_HELPER
+#define SIGNAL_PROTOCOL_VALA_HELPER 1
+
+#include <omemo/signal_protocol.h>
+#include <string.h>
+#include <glib.h>
+
+#define SG_CIPHER_AES_GCM_NOPADDING 1000
+
+signal_type_base* signal_type_ref_vapi(void* what);
+signal_type_base* signal_type_unref_vapi(void* what);
+
+signal_protocol_address* signal_protocol_address_new(const gchar* name, int32_t device_id);
+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);
+void signal_protocol_address_set_device_id(signal_protocol_address* self, int32_t device_id);
+int32_t signal_protocol_address_get_device_id(signal_protocol_address* self);
+
+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_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
diff --git a/libdino/src/crypto/omemo/simple_iks.vala b/libdino/src/crypto/omemo/simple_iks.vala
new file mode 100644
index 00000000..1c9c8a1d
--- /dev/null
+++ b/libdino/src/crypto/omemo/simple_iks.vala
@@ -0,0 +1,60 @@
+using Gee;
+using Omemo;
+
+namespace Dino.Crypto.Omemo {
+
+public class SimpleIdentityKeyStore : IdentityKeyStore {
+ public signal void trusted_identity_added(TrustedIdentity id);
+ public signal void trusted_identity_updated(TrustedIdentity id);
+
+ public override Bytes identity_key_private { get; set; }
+ public override Bytes identity_key_public { get; set; }
+ public override uint32 local_registration_id { get; set; }
+ private Map<string, Map<int, TrustedIdentity>> trusted_identities = new HashMap<string, Map<int, 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, 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;
+ }
+
+ 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);
+ }
+ }
+}
+
+}
diff --git a/libdino/src/crypto/omemo/simple_pks.vala b/libdino/src/crypto/omemo/simple_pks.vala
new file mode 100644
index 00000000..f22def89
--- /dev/null
+++ b/libdino/src/crypto/omemo/simple_pks.vala
@@ -0,0 +1,47 @@
+using Gee;
+using Omemo;
+
+namespace Dino.Crypto.Omemo {
+
+public class SimplePreKeyStore : PreKeyStore {
+ public signal void pre_key_stored(Key key);
+ public signal void pre_key_deleted(Key key);
+
+ private Map<uint32, Key> pre_key_map = new HashMap<uint32, 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 {
+ 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 {
+ Key key;
+ if (pre_key_map.unset(pre_key_id, out key)) {
+ pre_key_deleted(key);
+ }
+ }
+
+ 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;
+ }
+ }
+}
+
+} \ No newline at end of file
diff --git a/libdino/src/crypto/omemo/simple_spks.vala b/libdino/src/crypto/omemo/simple_spks.vala
new file mode 100644
index 00000000..b4a1b610
--- /dev/null
+++ b/libdino/src/crypto/omemo/simple_spks.vala
@@ -0,0 +1,47 @@
+using Gee;
+using Omemo;
+
+namespace Dino.Crypto.Omemo {
+
+public class SimpleSignedPreKeyStore : SignedPreKeyStore {
+ public signal void signed_pre_key_stored(Key key);
+ public signal void signed_pre_key_deleted(Key key);
+
+ private Map<uint32, Key> pre_key_map = new HashMap<uint32, 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 {
+ 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 {
+ Key key;
+ if (pre_key_map.unset(pre_key_id, out key)) {
+ signed_pre_key_deleted(key);
+ }
+ }
+
+ 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;
+ }
+ }
+}
+
+} \ No newline at end of file
diff --git a/libdino/src/crypto/omemo/simple_ss.vala b/libdino/src/crypto/omemo/simple_ss.vala
new file mode 100644
index 00000000..8c095c6d
--- /dev/null
+++ b/libdino/src/crypto/omemo/simple_ss.vala
@@ -0,0 +1,84 @@
+using Gee;
+using Omemo;
+
+namespace Dino.Crypto.Omemo {
+
+public class SimpleSessionStore : SessionStore {
+ public signal void session_stored(Session session);
+ public signal void session_removed(Session session);
+
+ private Map<string, ArrayList<Session>> session_map = new HashMap<string, ArrayList<Session>>();
+
+ public override uint8[]? load_session(Address address) throws Error {
+ if (session_map.has_key(address.name)) {
+ foreach (Session session in session_map[address.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 (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<Session>();
+ }
+ 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 (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 (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 (Session session in session_map[name]) {
+ session_map[name].remove(session);
+ if (session_map[name].size == 0) {
+ session_map.unset(name);
+ }
+ session_removed(session);
+ }
+ }
+ }
+
+ public class Session {
+ public string name;
+ public int device_id;
+ public uint8[] record;
+ }
+}
+
+} \ No newline at end of file
diff --git a/libdino/src/crypto/omemo/store.vala b/libdino/src/crypto/omemo/store.vala
new file mode 100644
index 00000000..746c07d7
--- /dev/null
+++ b/libdino/src/crypto/omemo/store.vala
@@ -0,0 +1,361 @@
+using Omemo;
+
+namespace Dino.Crypto.Omemo {
+
+public abstract class IdentityKeyStore : Object {
+ public abstract Bytes identity_key_private { get; set; }
+ public abstract Bytes identity_key_public { get; set; }
+ public abstract uint32 local_registration_id { get; set; }
+
+ public abstract void save_identity(Address address, uint8[] key) throws Error ;
+
+ public abstract bool is_trusted_identity(Address address, uint8[] key) throws Error ;
+}
+
+public abstract class SessionStore : Object {
+ 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 abstract class PreKeyStore : Object {
+ 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 abstract class SignedPreKeyStore : Object {
+ 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 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; } }
+ public 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.get_data());
+ private_data = new Buffer.from(store.identity_key_store.identity_key_private.get_data());
+ 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 void iks_destroy_func(void* user_data) {
+ }
+
+ static int ss_load_session_func(out Buffer? record, out Buffer? user_record, Address address, void* user_data) {
+ Store store = (Store) user_data;
+ user_record = null; // No support for user_record
+ uint8[]? res = null;
+ try {
+ res = store.session_store.load_session(address);
+ } catch (Error e) {
+ record = null;
+ return e.code;
+ }
+ if (res == null) {
+ record = null;
+ return 0;
+ }
+ record = new Buffer.from((!)res);
+ if (record == 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, uint8[] user_record, void* user_data) {
+ // Ignoring user_record
+ 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 void ss_destroy_func(void* user_data) {
+ }
+
+ 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 void pks_destroy_func(void* user_data) {
+ }
+
+ 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 void spks_destroy_func(void* user_data) {
+ }
+
+ 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 void delete_session(Address address) throws Error {
+ throw_by_code(Protocol.Session.delete_session(native_context, address));
+ }
+
+ 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;
+ }
+}
+
+}
diff --git a/libdino/src/crypto/omemo/util.vala b/libdino/src/crypto/omemo/util.vala
new file mode 100644
index 00000000..eca12fbb
--- /dev/null
+++ b/libdino/src/crypto/omemo/util.vala
@@ -0,0 +1,47 @@
+using Omemo;
+
+namespace Dino.Crypto.Omemo {
+
+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
diff --git a/libdino/src/crypto/openssl/cipher.vala b/libdino/src/crypto/openssl/cipher.vala
new file mode 100644
index 00000000..02064770
--- /dev/null
+++ b/libdino/src/crypto/openssl/cipher.vala
@@ -0,0 +1,156 @@
+namespace Dino.Crypto {
+
+internal class SymmetricCipherEncryptionOpenssl : SymmetricCipher, SymmetricCipherEncryption, Object {
+ private OpenSSL.EVP.CipherContext? cipher;
+ private bool is_finalized = false;
+
+ public SymmetricCipherEncryptionOpenssl(string algo_name) throws Error {
+ if (algo_name != "AES-GCM") throw new Error.ILLEGAL_ARGUMENTS(@"The algorithm $algo_name is not supported");
+ cipher = new OpenSSL.EVP.CipherContext();
+ }
+
+ public void set_key(uint8[] key) throws Error {
+ unowned OpenSSL.EVP.Cipher algo;
+ if (key.length == 16) {
+ algo = OpenSSL.EVP.aes_128_gcm();
+ } else if (key.length == 32) {
+ algo = OpenSSL.EVP.aes_256_gcm();
+ } else {
+ throw new Crypto.Error.ILLEGAL_ARGUMENTS("key length must be 16 or 32 for AES-GCM");
+ }
+ try {
+ cipher.encrypt_init(algo, null, null);
+ cipher.encrypt_init(null, key, null);
+ } catch (GLib.Error e) {
+ throw new Crypto.Error.OPENSSL(e.message);
+ }
+ }
+
+ public void set_iv(uint8[] iv) throws Error {
+ if (iv.length != 12) {
+ cipher.ctrl(OpenSSL.EVP.Control.GCM_SET_IVLEN, iv.length, null);
+ }
+ try {
+ cipher.encrypt_init(null, null, iv);
+ } catch (GLib.Error e) {
+ throw new Crypto.Error.OPENSSL(e.message);
+ }
+ }
+
+ public void reset() throws Error {
+ throw new Crypto.Error.ILLEGAL_ARGUMENTS("can't reset OpenSSL cipher context");
+ }
+
+ public uint8[] get_tag(size_t taglen) throws Error {
+ uint8[] tag = new uint8[taglen];
+ if (!is_finalized) {
+ uint8[] empty = new uint8[0];
+ int empty_len = 0;
+ try {
+ cipher.encrypt_final(empty, out empty_len);
+ } catch (GLib.Error e) {
+ throw new Crypto.Error.OPENSSL(e.message);
+ }
+ if (empty_len != 0) {
+ throw new Crypto.Error.ILLEGAL_ARGUMENTS("get_tag called on a stream with remaining data");
+ }
+ }
+ if (cipher.ctrl(OpenSSL.EVP.Control.GCM_GET_TAG, (int)taglen, tag) != 1) {
+ openssl_error();
+ }
+ return tag;
+ }
+
+ public int encrypt(uint8[] output, uint8[] input) throws Error {
+ int output_length = output.length;
+ if (is_finalized) {
+ throw new Crypto.Error.ILLEGAL_ARGUMENTS("encrypt called after get_tag");
+ }
+ try {
+ cipher.encrypt_update(output, out output_length, input);
+ } catch (GLib.Error e) {
+ throw new Crypto.Error.OPENSSL(e.message);
+ }
+ return output_length;
+ }
+}
+
+internal class SymmetricCipherDecryptionOpenssl : SymmetricCipher, SymmetricCipherDecryption, Object {
+ private OpenSSL.EVP.CipherContext? cipher;
+ private bool is_finalized = false;
+
+ public SymmetricCipherDecryptionOpenssl(string algo_name) throws Error {
+ if (algo_name != "AES-GCM") throw new Error.ILLEGAL_ARGUMENTS(@"The algorithm $algo_name is not supported");
+ cipher = new OpenSSL.EVP.CipherContext();
+ try {
+ cipher.decrypt_init(OpenSSL.EVP.aes_128_gcm(), null, null);
+ } catch (GLib.Error e) {
+ throw new Crypto.Error.OPENSSL(e.message);
+ }
+ }
+
+ public void set_key(uint8[] key) throws Error {
+ unowned OpenSSL.EVP.Cipher algo;
+ if (key.length == 16) {
+ algo = OpenSSL.EVP.aes_128_gcm();
+ } else if (key.length == 32) {
+ algo = OpenSSL.EVP.aes_256_gcm();
+ } else {
+ throw new Crypto.Error.ILLEGAL_ARGUMENTS("key length must be 16 or 32 for AES-GCM");
+ }
+ try {
+ cipher.decrypt_init(algo, null, null);
+ cipher.decrypt_init(null, key, null);
+ } catch (GLib.Error e) {
+ throw new Crypto.Error.OPENSSL(e.message);
+ }
+ }
+
+ public void set_iv(uint8[] iv) throws Error {
+ if (iv.length != 12) {
+ cipher.ctrl(OpenSSL.EVP.Control.GCM_SET_IVLEN, iv.length, null);
+ }
+ try {
+ cipher.decrypt_init(null, null, iv);
+ } catch (GLib.Error e) {
+ throw new Crypto.Error.OPENSSL(e.message);
+ }
+ }
+
+ public void reset() throws Error {
+ throw new Crypto.Error.ILLEGAL_ARGUMENTS("can't reset OpenSSL cipher context");
+ }
+
+ public void check_tag(uint8[] tag) throws Error {
+ if (cipher.ctrl(OpenSSL.EVP.Control.GCM_SET_TAG, tag.length, tag) != 1) {
+ openssl_error();
+ }
+ if (!is_finalized) {
+ uint8[] empty = new uint8[0];
+ int empty_len = 0;
+ try {
+ cipher.decrypt_final(empty, out empty_len);
+ } catch (GLib.Error e) {
+ throw new Crypto.Error.OPENSSL(e.message);
+ }
+ if (empty_len != 0) {
+ throw new Crypto.Error.ILLEGAL_ARGUMENTS("check_tag called on a stream with remaining data");
+ }
+ }
+ }
+
+ public int decrypt(uint8[] output, uint8[] input) throws Error {
+ if (is_finalized) {
+ throw new Crypto.Error.ILLEGAL_ARGUMENTS("decrypt called after check_tag");
+ }
+ int output_length = output.length;
+ try {
+ cipher.decrypt_update(output, out output_length, input);
+ } catch (GLib.Error e) {
+ throw new Crypto.Error.OPENSSL(e.message);
+ }
+ return output_length;
+ }
+}
+
+}
diff --git a/libdino/src/crypto/openssl/dtls_srtp.vala b/libdino/src/crypto/openssl/dtls_srtp.vala
new file mode 100644
index 00000000..192f0da2
--- /dev/null
+++ b/libdino/src/crypto/openssl/dtls_srtp.vala
@@ -0,0 +1,202 @@
+using OpenSSL;
+
+namespace Dino.Crypto.DtlsSrtp {
+
+public class CredentialsCapsuleOpenSSL : CredentialsCapsule {
+ internal X509.Certificate own_cert;
+ internal EVP.Key private_key;
+}
+
+public CredentialsCapsule generate_credentials () throws GLib.Error {
+ EVP.Key key = new EVP.Key.generate_ecdsa ("prime256v1");
+
+ var start_time = new DateTime.now_local().add_days(-1);
+ var end_time = start_time.add_days (2);
+
+ X509.Certificate cert = new X509.Certificate ();
+ cert.set_public_key (key);
+ cert.set_version (1);
+ cert.not_before.set ((time_t) start_time.to_unix ());
+ cert.not_after.set ((time_t) end_time.to_unix ());
+ cert.serial_number.set (1);
+
+ cert.sign (key, EVP.sha256 ());
+
+ CredentialsCapsuleOpenSSL credentials = new CredentialsCapsuleOpenSSL ();
+ credentials.own_fingerprint = cert.digest (EVP.sha256 ());
+ credentials.own_cert = (owned) cert;
+ credentials.private_key = (owned) key;
+ return credentials;
+}
+
+public class Handler : AbstractHandler {
+
+ public Handler.with_cert (CredentialsCapsule creds) {
+ base.with_cert (creds);
+ }
+
+ protected override Xmpp.Xep.Jingle.ContentEncryption? setup_dtls_connection_thread () {
+ debug("Setting up DTLS connection using OpenSSL. We're %s", mode.to_string());
+ SSL.Context context = new SSL.Context (mode == Mode.SERVER ? SSL.Method.DTLS_SERVER : SSL.Method.DTLS_CLIENT);
+ context.set_tlsext_use_srtp ("SRTP_AES128_CM_SHA1_80");
+ context.use_certificate (((CredentialsCapsuleOpenSSL) credentials).own_cert);
+ context.use_private_key (((CredentialsCapsuleOpenSSL) credentials).private_key);
+ SSL session = new SSL (context);
+ BIO<Handler> bio = new BIO<Handler>(get_handler_based_bio_method());
+ bio.data = this;
+ session.rbio = bio;
+ session.wbio = bio;
+
+ DateTime maximum_time = new DateTime.now_utc().add_seconds(20);
+ int err = 0;
+ if (mode == Mode.SERVER) {
+ session.set_accept_state ();
+ } else {
+ session.set_connect_state ();
+ }
+ do {
+ try {
+ SSL.Error ret = session.do_handshake();
+ switch (ret) {
+ case SSL.Error.NONE:
+ err = 1;
+ break;
+ case SSL.Error.WANT_READ:
+ debug ("DTLS wants to read, but nothing in queue, wait for buffer");
+ buffer_mutex.lock();
+ if (buffer_queue.size == 0) {
+ buffer_cond.wait_until(buffer_mutex, get_monotonic_time() + maximum_time.difference(new DateTime.now_utc()));
+ }
+ buffer_mutex.unlock();
+ err = -1;
+ break;
+ default:
+ warning ("DTLS Error: %s", @"$ret");
+ err = 4;
+ break;
+ }
+ } catch (Error e) {
+ warning ("DTLS Error: %s", e.message);
+ err = e.code;
+ }
+
+ DateTime current_time = new DateTime.now_utc();
+ if (maximum_time.compare(current_time) < 0) {
+ debug ("DTLS handshake timeouted");
+ err = 2;
+ break;
+ }
+ if (stop) {
+ debug ("DTLS handshake stopped");
+ err = 3;
+ break;
+ }
+ } while (err < 0);
+
+ buffer_mutex.lock();
+ if (stop) {
+ stop = false;
+ running = false;
+ bool restart = restart;
+ buffer_mutex.unlock();
+ if (restart) {
+ debug ("Restarting DTLS handshake");
+ return setup_dtls_connection_thread();
+ }
+ return null;
+ }
+ buffer_mutex.unlock();
+ if (err != 1) {
+ debug ("DTLS handshake failed");
+ return null;
+ }
+
+ // TODO: Check peer certificate
+
+ uint8[] km = new uint8[60];
+ session.export_keying_material (km, "EXTRACTOR-dtls_srtp");
+ uint8[] client_key = km[0:16];
+ uint8[] server_key = km[16:32];
+ uint8[] client_salt = km[32:46];
+ uint8[] server_salt = km[46:60];
+
+ debug ("Finished DTLS connection. We're %s", mode.to_string());
+ if (mode == Mode.SERVER) {
+ srtp_session.set_encryption_key (Crypto.Srtp.AES_CM_128_HMAC_SHA1_80, server_key, server_salt);
+ srtp_session.set_decryption_key (Crypto.Srtp.AES_CM_128_HMAC_SHA1_80, client_key, client_salt);
+ } else {
+ srtp_session.set_encryption_key (Crypto.Srtp.AES_CM_128_HMAC_SHA1_80, client_key, client_salt);
+ srtp_session.set_decryption_key (Crypto.Srtp.AES_CM_128_HMAC_SHA1_80, server_key, server_salt);
+ }
+ return new Xmpp.Xep.Jingle.ContentEncryption (Xmpp.Xep.JingleIceUdp.DTLS_NS_URI, "DTLS-SRTP", credentials.own_fingerprint, peer_fingerprint);
+ }
+
+ private int read_impl (BIO<Handler> bio, uint8[] out_buf) {
+ Bytes data = get_buffer_if_present ();
+ if (data == null) {
+ if (!stop) {
+ debug ("No pending buffer in queue, but end not reached yet, set_retry_read");
+ bio.set_retry_read ();
+ }
+ return -1;
+ }
+ if (data.length > out_buf.length) {
+ warning ("Buffer in queue does not fit in out_buf");
+ return -1;
+ }
+ debug ("Read %d bytes from queue", data.length);
+
+ uint8[] data_uint8 = Bytes.unref_to_data((owned) data);
+ Memory.copy (out_buf, data_uint8, data_uint8.length);
+ bio.clear_retry_flags ();
+ return data_uint8.length;
+ }
+
+ private int write_impl (BIO<Handler> bio, uint8[] buf) {
+ debug ("Write %d bytes to network", buf.length);
+ send_data (buf);
+ return buf.length;
+ }
+
+ private long ctrl_impl (BIO<Handler> bio, BIO.ControlCommand cmd, long num, void* ptr) {
+ switch (cmd) {
+ case BIO.ControlCommand.FLUSH:
+ // Datagrams are sent out immediately
+ return 1;
+ case BIO.ControlCommand.DGRAM_SET_NEXT_TIMEOUT:
+ // Datagrams are sent out immediately
+ return 1;
+ case BIO.ControlCommand.DGRAM_GET_MTU_OVERHEAD:
+ // Just some safe estimate
+ return 70;
+ case BIO.ControlCommand.DGRAM_QUERY_MTU:
+ // Just some safe estimate
+ return 1200;
+ case BIO.ControlCommand.WPENDING:
+ // Datagrams are sent out immediately
+ return 0;
+ case BIO.ControlCommand.POP:
+ case BIO.ControlCommand.PUSH:
+ // Ignored
+ return 0;
+ default:
+ warning (@"Unimplemented ctrl cmd:$cmd, num:$num");
+ return 0;
+ }
+ }
+
+ private static BIO.Method<Handler> _handler_based_bio_method_instance = null;
+
+ private static unowned BIO.Method<Handler> get_handler_based_bio_method () {
+ if (_handler_based_bio_method_instance != null) return _handler_based_bio_method_instance;
+ _handler_based_bio_method_instance = new BIO.Method<Handler> (BIO.Type.SOURCE_SINK | BIO.Type.DGRAM, "Dino DTLS Handler BIO");
+ _handler_based_bio_method_instance.read = (bio, out_buf) => bio.data.read_impl (bio, out_buf);
+ _handler_based_bio_method_instance.write = (bio, buf) => bio.data.write_impl (bio, buf);
+ _handler_based_bio_method_instance.ctrl = (bio, cmd, num, ptr) => bio.data.ctrl_impl (bio, cmd, num, ptr);
+ return _handler_based_bio_method_instance;
+ }
+}
+
+
+
+} \ No newline at end of file
diff --git a/libdino/src/crypto/openssl/omemo.c b/libdino/src/crypto/openssl/omemo.c
new file mode 100644
index 00000000..0912c172
--- /dev/null
+++ b/libdino/src/crypto/openssl/omemo.c
@@ -0,0 +1,313 @@
+#include "../omemo/helper.h"
+#include <openssl/evp.h>
+#include <openssl/rand.h>
+
+int signal_vala_random_generator(uint8_t *data, size_t len, void *user_data) {
+ return RAND_bytes(data, len) == 1 ? SG_SUCCESS : SG_ERR_UNKNOWN;
+}
+
+struct SIGNAL_VALA_HMAC_CTX {
+ EVP_PKEY *pkey;
+ EVP_MD_CTX *ctx;
+};
+
+int signal_vala_hmac_sha256_init(void **hmac_context, const uint8_t *key, size_t key_len, void *user_data) {
+ EVP_PKEY *pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_HMAC, NULL, key, key_len);
+ if (!pkey) {
+ return SG_ERR_NOMEM;
+ }
+ EVP_MD_CTX *ctx = EVP_MD_CTX_new();
+ if (!ctx) {
+ EVP_PKEY_free(pkey);
+ return SG_ERR_NOMEM;
+ }
+ if (EVP_DigestSignInit(ctx, NULL, EVP_sha256(), NULL, pkey) != 1) {
+ EVP_MD_CTX_free(ctx);
+ EVP_PKEY_free(pkey);
+ return SG_ERR_UNKNOWN;
+ }
+
+ struct SIGNAL_VALA_HMAC_CTX *hmac_ctx = malloc(sizeof(*hmac_ctx));
+ hmac_ctx->pkey = pkey;
+ hmac_ctx->ctx = ctx;
+ *hmac_context = hmac_ctx;
+
+ return SG_SUCCESS;
+}
+
+int signal_vala_hmac_sha256_update(void *hmac_context, const uint8_t *data, size_t data_len, void *user_data) {
+ struct SIGNAL_VALA_HMAC_CTX *hmac_ctx = hmac_context;
+ if (EVP_DigestSignUpdate(hmac_ctx->ctx, data, data_len) != 1) {
+ return SG_ERR_UNKNOWN;
+ }
+ return SG_SUCCESS;
+}
+
+int signal_vala_hmac_sha256_final(void *hmac_context, signal_buffer **output, void *user_data) {
+ size_t len;
+ struct SIGNAL_VALA_HMAC_CTX *hmac_ctx = hmac_context;
+ if (EVP_DigestSignFinal(hmac_ctx->ctx, NULL, &len) != 1) {
+ return SG_ERR_UNKNOWN;
+ }
+ signal_buffer *output_buffer = signal_buffer_alloc(len);
+ if (!output_buffer) {
+ return SG_ERR_NOMEM;
+ }
+ size_t another_len = len;
+ if (EVP_DigestSignFinal(hmac_ctx->ctx, signal_buffer_data(output_buffer), &another_len) != 1) {
+ signal_buffer_free(output_buffer);
+ return SG_ERR_UNKNOWN;
+ }
+ if (another_len != len) {
+ signal_buffer_free(output_buffer);
+ return SG_ERR_UNKNOWN;
+ }
+ *output = output_buffer;
+ return SG_SUCCESS;
+}
+
+void signal_vala_hmac_sha256_cleanup(void *hmac_context, void *user_data) {
+ struct SIGNAL_VALA_HMAC_CTX *hmac_ctx = hmac_context;
+ if (hmac_ctx) {
+ EVP_MD_CTX_free(hmac_ctx->ctx);
+ EVP_PKEY_free(hmac_ctx->pkey);
+ free(hmac_ctx);
+ }
+}
+
+static const EVP_CIPHER *aes_cipher(int cipher, size_t key_len) {
+ switch (cipher) {
+ case SG_CIPHER_AES_CBC_PKCS5:
+ switch (key_len) {
+ case 16: return EVP_aes_128_cbc();
+ case 24: return EVP_aes_192_cbc();
+ case 32: return EVP_aes_256_cbc();
+ }
+ break;
+ case SG_CIPHER_AES_CTR_NOPADDING:
+ switch (key_len) {
+ case 16: return EVP_aes_128_ctr();
+ case 24: return EVP_aes_192_ctr();
+ case 32: return EVP_aes_256_ctr();
+ }
+ break;
+ case SG_CIPHER_AES_GCM_NOPADDING:
+ switch (key_len) {
+ case 16: return EVP_aes_128_gcm();
+ case 24: return EVP_aes_192_gcm();
+ case 32: return EVP_aes_256_gcm();
+ }
+ break;
+
+ }
+ return NULL;
+}
+
+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 = NULL;
+ 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 (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_new();
+ 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;
+
+ if (EVP_EncryptInit_ex(ctx, evp_cipher, NULL, NULL, NULL) != 1) {
+ // fprintf(stderr, "cannot initialize cipher\n");
+ result = SG_ERR_UNKNOWN;
+ goto complete;
+ }
+
+ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, iv_len, NULL) != 1) {
+ // fprintf(stderr, "cannot set iv size\n");
+ result = SG_ERR_UNKNOWN;
+ goto complete;
+ }
+
+ if (EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv) != 1) {
+ // fprintf(stderr, "cannot set key/iv\n");
+ result = SG_ERR_UNKNOWN;
+ goto complete;
+ }
+ } else {
+ // TODO: set ivlen?
+ if (EVP_EncryptInit_ex(ctx, evp_cipher, 0, key, iv) != 1) {
+ // 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) {
+ if (EVP_CIPHER_CTX_set_padding(ctx, 0) != 1) {
+ // fprintf(stderr, "cannot set padding\n");
+ result = SG_ERR_UNKNOWN;
+ goto complete;
+ }
+ }
+
+ out_buf = malloc(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;
+ if (EVP_EncryptUpdate(ctx, out_buf, &out_len, plaintext, plaintext_len) != 1) {
+ // fprintf(stderr, "cannot encrypt plaintext\n");
+ result = SG_ERR_UNKNOWN;
+ goto complete;
+ }
+
+ int final_len = 0;
+ if (EVP_EncryptFinal_ex(ctx, out_buf + out_len, &final_len) != 1) {
+ // fprintf(stderr, "cannot finish encrypting plaintext\n");
+ result = SG_ERR_UNKNOWN;
+ goto complete;
+ }
+
+ if (cipher == SG_CIPHER_AES_GCM_NOPADDING) {
+ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, out_buf + out_len + final_len) != 1) {
+ // 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_free(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 = NULL;
+ 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 (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_new();
+
+ if (cipher == SG_CIPHER_AES_GCM_NOPADDING) {
+ // In GCM mode we use the last 16 bytes as auth tag
+ ciphertext_len -= 16;
+
+ if (EVP_DecryptInit_ex(ctx, evp_cipher, NULL, NULL, NULL) != 1) {
+ // fprintf(stderr, "cannot initialize cipher\n");
+ result = SG_ERR_UNKNOWN;
+ goto complete;
+ }
+
+ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, iv_len, NULL) != 1) {
+ // fprintf(stderr, "cannot set iv size\n");
+ result = SG_ERR_UNKNOWN;
+ goto complete;
+ }
+
+ if (EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv) != 1) {
+ // fprintf(stderr, "cannot set key/iv\n");
+ result = SG_ERR_UNKNOWN;
+ goto complete;
+ }
+ } else {
+ // TODO: set ivlen?
+ if (EVP_DecryptInit_ex(ctx, evp_cipher, 0, key, iv) != 1) {
+ // 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) {
+ if (EVP_CIPHER_CTX_set_padding(ctx, 0) != 1) {
+ // fprintf(stderr, "cannot set padding\n");
+ result = SG_ERR_UNKNOWN;
+ goto complete;
+ }
+ }
+
+ out_buf = malloc(ciphertext_len + EVP_CIPHER_block_size(evp_cipher));
+ if (!out_buf) {
+ // fprintf(stderr, "cannot allocate output buffer\n");
+ result = SG_ERR_NOMEM;
+ goto complete;
+ }
+
+ int out_len = 0;
+ if (EVP_DecryptUpdate(ctx, out_buf, &out_len, ciphertext, ciphertext_len) != 1) {
+ // fprintf(stderr, "cannot decrypt ciphertext\n");
+ result = SG_ERR_UNKNOWN;
+ goto complete;
+ }
+
+ if (cipher == SG_CIPHER_AES_GCM_NOPADDING) {
+ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, (void *)(ciphertext + ciphertext_len)) != 1) {
+ // fprintf(stderr, "cannot set tag\n");
+ result = SG_ERR_UNKNOWN;
+ goto complete;
+ }
+ }
+
+ int final_len = 0;
+ if (EVP_DecryptFinal_ex(ctx, out_buf + out_len, &final_len) != 1) {
+ // 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_free(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,
+ .encrypt_func = signal_vala_encrypt,
+ .decrypt_func = signal_vala_decrypt,
+ .user_data = 0
+ };
+
+ signal_context_set_crypto_provider(context, &provider);
+}
diff --git a/libdino/src/crypto/openssl/utils.c b/libdino/src/crypto/openssl/utils.c
new file mode 100644
index 00000000..1a84f642
--- /dev/null
+++ b/libdino/src/crypto/openssl/utils.c
@@ -0,0 +1,49 @@
+#include "utils.h"
+#include <openssl/opensslv.h>
+#include <openssl/ec.h>
+
+#if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x30000000L
+// This is for OpenSSL 3.0.0 and newer
+
+EVP_PKEY *EVP_PKEY_new_generate_ecdsa (const char* curve) {
+ // Generate EVP_PKEY directly
+ EVP_PKEY *pkey = EVP_EC_gen (curve);
+ return pkey;
+}
+
+#else
+// This is for OpenSSL before 3.0.0 and forks of earlier OpenSSL versions like LibreSSL
+
+EVP_PKEY *EVP_PKEY_new_generate_ecdsa (const char* curve) {
+ // Get NID from curve name
+ int nid = OBJ_sn2nid (curve);
+ if (nid == NID_undef) return NULL;
+
+ // Create EC_KEY
+ EC_KEY *ec_key = EC_KEY_new_by_curve_name (nid);
+ if (!ec_key) return NULL;
+
+ // Generate key
+ if (!EC_KEY_generate_key (ec_key)) {
+ EC_KEY_free (ec_key);
+ return NULL;
+ }
+
+ // Create EVP_PKEY
+ EVP_PKEY *pkey = EVP_PKEY_new ();
+ if (!pkey) {
+ EC_KEY_free (ec_key);
+ return NULL;
+ }
+
+ // Assign EC_KEY to EVP_PKEY
+ if (!EVP_PKEY_assign_EC_KEY (pkey, ec_key)) {
+ EC_KEY_free (ec_key);
+ return NULL;
+ }
+
+ // EVP_PKEY_assign_EC_KEY takes ownership of ec_key, no need to free it explicitly
+ return pkey;
+}
+
+#endif
diff --git a/libdino/src/crypto/openssl/utils.h b/libdino/src/crypto/openssl/utils.h
new file mode 100644
index 00000000..bcc6f53a
--- /dev/null
+++ b/libdino/src/crypto/openssl/utils.h
@@ -0,0 +1,8 @@
+#ifndef DINO_OPENSSL_UTILS_H
+#define DINO_OPENSSL_UTILS_H
+
+#include <openssl/evp.h>
+
+EVP_PKEY *EVP_PKEY_new_generate_ecdsa (const char* curve);
+
+#endif //DINO_OPENSSL_UTILS_H \ No newline at end of file
diff --git a/libdino/src/crypto/openssl/utils.vala b/libdino/src/crypto/openssl/utils.vala
new file mode 100644
index 00000000..7a9ed9b6
--- /dev/null
+++ b/libdino/src/crypto/openssl/utils.vala
@@ -0,0 +1,19 @@
+namespace Dino.Crypto {
+
+public static void randomize(uint8[] buffer) {
+ OpenSSL.RAND.bytes(buffer);
+}
+
+internal void openssl_error() throws Error {
+ throw new Crypto.Error.OPENSSL(OpenSSL.ERR.reason_error_string(OpenSSL.ERR.get_error()));
+}
+
+public SymmetricCipherEncryption create_symmetric_cipher_encryption(string algo_name) throws Error {
+ return new SymmetricCipherEncryptionOpenssl(algo_name);
+}
+
+public SymmetricCipherDecryption create_symmetric_cipher_decryption(string algo_name) throws Error {
+ return new SymmetricCipherDecryptionOpenssl(algo_name);
+}
+
+}
diff --git a/libdino/src/crypto/srtp.vala b/libdino/src/crypto/srtp.vala
new file mode 100644
index 00000000..852dccdc
--- /dev/null
+++ b/libdino/src/crypto/srtp.vala
@@ -0,0 +1,122 @@
+using Srtp;
+
+namespace Dino.Crypto.Srtp {
+public const string AES_CM_128_HMAC_SHA1_80 = "AES_CM_128_HMAC_SHA1_80";
+public const string AES_CM_128_HMAC_SHA1_32 = "AES_CM_128_HMAC_SHA1_32";
+public const string F8_128_HMAC_SHA1_80 = "F8_128_HMAC_SHA1_80";
+
+public class Session {
+ public bool has_encrypt { get; private set; default = false; }
+ public bool has_decrypt { get; private set; default = false; }
+
+ private Context encrypt_context;
+ private Context decrypt_context;
+
+ static construct {
+ init();
+ install_log_handler(log);
+ }
+
+ private static void log(LogLevel level, string msg) {
+ print(@"SRTP[$level]: $msg\n");
+ }
+
+ public Session() {
+ Context.create(out encrypt_context, null);
+ Context.create(out decrypt_context, null);
+ }
+
+ public uint8[] encrypt_rtp(uint8[] data) throws Error {
+ uint8[] buf = new uint8[data.length + MAX_TRAILER_LEN];
+ Memory.copy(buf, data, data.length);
+ int buf_use = data.length;
+ ErrorStatus res = encrypt_context.protect(buf, ref buf_use);
+ if (res != ErrorStatus.ok) {
+ throw new Error.UNKNOWN(@"SRTP encrypt failed: $res");
+ }
+ buf.length = buf_use;
+ return buf;
+ }
+
+ public uint8[] decrypt_rtp(uint8[] data) throws Error {
+ uint8[] buf = new uint8[data.length];
+ Memory.copy(buf, data, data.length);
+ int buf_use = data.length;
+ ErrorStatus res = decrypt_context.unprotect(buf, ref buf_use);
+ switch (res) {
+ case ErrorStatus.auth_fail:
+ throw new Error.AUTHENTICATION_FAILED("SRTP packet failed the message authentication check");
+ case ErrorStatus.ok:
+ break;
+ default:
+ throw new Error.UNKNOWN(@"SRTP decrypt failed: $res");
+ }
+ uint8[] ret = new uint8[buf_use];
+ GLib.Memory.copy(ret, buf, buf_use);
+ return ret;
+ }
+
+ public uint8[] encrypt_rtcp(uint8[] data) throws Error {
+ uint8[] buf = new uint8[data.length + MAX_TRAILER_LEN + 4];
+ Memory.copy(buf, data, data.length);
+ int buf_use = data.length;
+ ErrorStatus res = encrypt_context.protect_rtcp(buf, ref buf_use);
+ if (res != ErrorStatus.ok) {
+ throw new Error.UNKNOWN(@"SRTCP encrypt failed: $res");
+ }
+ buf.length = buf_use;
+ return buf;
+ }
+
+ public uint8[] decrypt_rtcp(uint8[] data) throws Error {
+ uint8[] buf = new uint8[data.length];
+ Memory.copy(buf, data, data.length);
+ int buf_use = data.length;
+ ErrorStatus res = decrypt_context.unprotect_rtcp(buf, ref buf_use);
+ switch (res) {
+ case ErrorStatus.auth_fail:
+ throw new Error.AUTHENTICATION_FAILED("SRTCP packet failed the message authentication check");
+ case ErrorStatus.ok:
+ break;
+ default:
+ throw new Error.UNKNOWN(@"SRTCP decrypt failed: $res");
+ }
+ uint8[] ret = new uint8[buf_use];
+ GLib.Memory.copy(ret, buf, buf_use);
+ return ret;
+ }
+
+ private Policy create_policy(string profile) {
+ Policy policy = Policy();
+ switch (profile) {
+ case AES_CM_128_HMAC_SHA1_80:
+ policy.rtp.set_aes_cm_128_hmac_sha1_80();
+ policy.rtcp.set_aes_cm_128_hmac_sha1_80();
+ break;
+ }
+ return policy;
+ }
+
+ public void set_encryption_key(string profile, uint8[] key, uint8[] salt) {
+ Policy policy = create_policy(profile);
+ policy.ssrc.type = SsrcType.any_outbound;
+ policy.key = new uint8[key.length + salt.length];
+ Memory.copy(policy.key, key, key.length);
+ Memory.copy(((uint8*)policy.key) + key.length, salt, salt.length);
+ policy.next = null;
+ encrypt_context.add_stream(ref policy);
+ has_encrypt = true;
+ }
+
+ public void set_decryption_key(string profile, uint8[] key, uint8[] salt) {
+ Policy policy = create_policy(profile);
+ policy.ssrc.type = SsrcType.any_inbound;
+ policy.key = new uint8[key.length + salt.length];
+ Memory.copy(policy.key, key, key.length);
+ Memory.copy(((uint8*)policy.key) + key.length, salt, salt.length);
+ policy.next = null;
+ decrypt_context.add_stream(ref policy);
+ has_decrypt = true;
+ }
+}
+} \ No newline at end of file