From e4bd6c1ce4f6e00dca68f68b7cf84ce7ec806add Mon Sep 17 00:00:00 2001 From: hrxi Date: Fri, 24 Nov 2023 20:53:50 +0100 Subject: [PATCH] Allow using OpenSSL instead of GnuTLS In preparation of Windows support. --- crypto-vala/CMakeLists.txt | 2 + crypto-vala/meson.build | 7 +- crypto-vala/src/cipher.vala | 147 +++++++- crypto-vala/src/error.vala | 9 +- crypto-vala/src/random.vala | 6 +- crypto-vala/vapi/openssl.vapi | 21 +- meson.build | 50 ++- meson_options.txt | 2 + plugins/ice/meson.build | 9 +- plugins/omemo/CMakeLists.txt | 4 +- plugins/omemo/meson.build | 18 +- .../src/file_transfer/file_decryptor.vala | 2 +- .../src/file_transfer/file_encryptor.vala | 2 +- plugins/omemo/src/jingle/jet_omemo.vala | 4 +- plugins/omemo/src/protocol/bundle.vala | 4 +- plugins/omemo/src/signal/context.vala | 2 +- plugins/omemo/src/signal/signal_helper.c | 348 +++++++++++++++++- plugins/omemo/src/signal/store.vala | 2 +- plugins/omemo/vapi/openssl.vapi | 0 19 files changed, 604 insertions(+), 35 deletions(-) create mode 100644 plugins/omemo/vapi/openssl.vapi diff --git a/crypto-vala/CMakeLists.txt b/crypto-vala/CMakeLists.txt index 6dec5292..bb28aa87 100644 --- a/crypto-vala/CMakeLists.txt +++ b/crypto-vala/CMakeLists.txt @@ -22,6 +22,8 @@ GENERATE_VAPI crypto-vala GENERATE_HEADER crypto-vala +DEFINITIONS + GCRYPT ) add_custom_target(crypto-vala-vapi diff --git a/crypto-vala/meson.build b/crypto-vala/meson.build index c3feb4d1..64bd3872 100644 --- a/crypto-vala/meson.build +++ b/crypto-vala/meson.build @@ -1,7 +1,7 @@ dependencies = [ dep_gio, dep_glib, - dep_libgcrypt, + dep_libgcrypt_or_openssl, dep_libsrtp2, ] sources = files( @@ -17,6 +17,11 @@ c_args = [ vala_args = [ '--vapidir', meson.current_source_dir() / 'vapi', ] +if crypto_backend == 'openssl' + vala_args += ['--pkg', 'openssl'] # Work around https://github.com/mesonbuild/meson/issues/2103. +elif crypto_backend == 'gnutls' + vala_args += ['-D', 'GCRYPT'] +endif lib_crypto_vala = library('crypto-vala', sources, c_args: c_args, vala_args: vala_args, dependencies: dependencies, version: '0.0', install: true, install_dir: [true, true, true]) dep_crypto_vala = declare_dependency(link_with: lib_crypto_vala, include_directories: include_directories('.')) diff --git a/crypto-vala/src/cipher.vala b/crypto-vala/src/cipher.vala index 306dafa8..1b2a02a5 100644 --- a/crypto-vala/src/cipher.vala +++ b/crypto-vala/src/cipher.vala @@ -1,14 +1,24 @@ namespace Crypto { public class SymmetricCipher { +#if GCRYPT private GCrypt.Cipher.Cipher cipher; +#else + bool is_encryption; + private OpenSSL.EVP.CipherContext? cipher; +#endif public static bool supports(string algo_name) { +#if GCRYPT GCrypt.Cipher.Algorithm algo; GCrypt.Cipher.Mode mode; GCrypt.Cipher.Flag flags; return parse(algo_name, out algo, out mode, out flags); +#else + return algo_name == "AES-GCM"; +#endif } +#if GCRYPT private static unowned string mode_to_string(GCrypt.Cipher.Mode mode) { switch (mode) { case GCrypt.Cipher.Mode.ECB: return "ECB"; @@ -95,8 +105,18 @@ public class SymmetricCipher { return algo.to_string(); } } +#endif - public SymmetricCipher(string algo_name) throws Error { + public SymmetricCipher.encryption(string algo_name) throws Error { + this.initialize(algo_name, true); + } + + public SymmetricCipher.decryption(string algo_name) throws Error { + this.initialize(algo_name, false); + } + + private SymmetricCipher.initialize(string algo_name, bool is_encryption) throws Error { +#if GCRYPT GCrypt.Cipher.Algorithm algo; GCrypt.Cipher.Mode mode; GCrypt.Cipher.Flag flags; @@ -105,48 +125,157 @@ public class SymmetricCipher { } else { throw new Error.ILLEGAL_ARGUMENTS(@"The algorithm $algo_name is not supported"); } +#else + if (algo_name == "AES-GCM") { + this.openssl(is_encryption); + } else { + throw new Error.ILLEGAL_ARGUMENTS(@"The algorithm $algo_name is not supported"); + } +#endif } +#if GCRYPT private SymmetricCipher.gcrypt(GCrypt.Cipher.Algorithm algo, GCrypt.Cipher.Mode mode, GCrypt.Cipher.Flag flags) throws Error { may_throw_gcrypt_error(GCrypt.Cipher.Cipher.open(out this.cipher, algo, mode, flags)); } +#else + private SymmetricCipher.openssl(bool is_encryption) throws Error { + this.is_encryption = is_encryption; + cipher = new OpenSSL.EVP.CipherContext(); + if (is_encryption) { + if (cipher.encrypt_init(OpenSSL.EVP.aes_128_gcm(), null, null, null) != 1) { + openssl_error(); + } + } else { + if (cipher.decrypt_init(OpenSSL.EVP.aes_128_gcm(), null, null, null) != 1) { + openssl_error(); + } + } + } +#endif public void set_key(uint8[] key) throws Error { +#if GCRYPT may_throw_gcrypt_error(cipher.set_key(key)); +#else + if (key.length != 16) { + throw new Crypto.Error.ILLEGAL_ARGUMENTS("key length must be 16 for AES-GCM"); + } + if (is_encryption) { + if (cipher.encrypt_init(null, null, key, null) != 1) { + openssl_error(); + } + } else { + if (cipher.decrypt_init(null, null, key, null) != 1) { + openssl_error(); + } + } +#endif } public void set_iv(uint8[] iv) throws Error { +#if GCRYPT may_throw_gcrypt_error(cipher.set_iv(iv)); - } - - public void set_counter_vector(uint8[] ctr) throws Error { - may_throw_gcrypt_error(cipher.set_counter_vector(ctr)); +#else + if (iv.length != 12) { + throw new Crypto.Error.ILLEGAL_ARGUMENTS("intialization vector must be of length 16 for AES-GCM"); + } + if (is_encryption) { + if (cipher.encrypt_init(null, null, null, iv) != 1) { + openssl_error(); + } + } else { + if (cipher.decrypt_init(null, null, null, iv) != 1) { + openssl_error(); + } + } +#endif } public void reset() throws Error { +#if GCRYPT may_throw_gcrypt_error(cipher.reset()); +#else + throw new Crypto.Error.ILLEGAL_ARGUMENTS("can't reset OpenSSL cipher context"); +#endif } public uint8[] get_tag(size_t taglen) throws Error { uint8[] tag = new uint8[taglen]; +#if GCRYPT may_throw_gcrypt_error(cipher.get_tag(tag)); +#else + if (!is_encryption) { + throw new Crypto.Error.ILLEGAL_ARGUMENTS("can't call get_tag on decryption context"); + } + uint8[] empty = new uint8[0]; + int empty_len = 0; + if (cipher.encrypt_final(empty, out empty_len) != 1) { + openssl_error(); + } + if (empty_len != 0) { + throw new Crypto.Error.ILLEGAL_ARGUMENTS("get_tag called on a stream with remaining data"); + } + if (cipher.ctrl(OpenSSL.EVP.CTRL_GCM_GET_TAG, (int)taglen, tag) != 1) { + openssl_error(); + } +#endif return tag; } public void check_tag(uint8[] tag) throws Error { +#if GCRYPT may_throw_gcrypt_error(cipher.check_tag(tag)); +#else + if (is_encryption) { + throw new Crypto.Error.ILLEGAL_ARGUMENTS("can't call check_tag on encryption context"); + } + if (cipher.ctrl(OpenSSL.EVP.CTRL_GCM_SET_TAG, tag.length, tag) != 1) { + openssl_error(); + } + uint8[] empty = new uint8[0]; + int empty_len = 0; + if (cipher.decrypt_final(empty, out empty_len) != 1) { + openssl_error(); + } + if (empty_len != 0) { + throw new Crypto.Error.ILLEGAL_ARGUMENTS("check_tag called on a stream with remaining data"); + } +#endif } public void encrypt(uint8[] output, uint8[] input) throws Error { +#if GCRYPT may_throw_gcrypt_error(cipher.encrypt(output, input)); +#else + if (!is_encryption) { + throw new Crypto.Error.ILLEGAL_ARGUMENTS("can't call encrypt on decryption context"); + } + int output_length = output.length; + if (cipher.encrypt_update(output, out output_length, input) != 1) { + openssl_error(); + } + if (output_length != output.length) { + throw new Crypto.Error.ILLEGAL_ARGUMENTS("invalid output array length"); + } +#endif } public void decrypt(uint8[] output, uint8[] input) throws Error { +#if GCRYPT may_throw_gcrypt_error(cipher.decrypt(output, input)); - } - - public void sync() throws Error { - may_throw_gcrypt_error(cipher.sync()); +#else + if (is_encryption) { + throw new Crypto.Error.ILLEGAL_ARGUMENTS("can't call decrypt on encryption context"); + } + int output_length = output.length; + if (cipher.decrypt_update(output, out output_length, input) != 1) { + openssl_error(); + } + if (output_length != output.length) { + throw new Crypto.Error.ILLEGAL_ARGUMENTS("invalid output array length"); + } +#endif } } } diff --git a/crypto-vala/src/error.vala b/crypto-vala/src/error.vala index 5007d725..4ea02610 100644 --- a/crypto-vala/src/error.vala +++ b/crypto-vala/src/error.vala @@ -3,13 +3,20 @@ namespace Crypto { public errordomain Error { ILLEGAL_ARGUMENTS, GCRYPT, + OPENSSL, AUTHENTICATION_FAILED, UNKNOWN } +#if GCRYPT internal void may_throw_gcrypt_error(GCrypt.Error e) throws Error { if (((int)e) != 0) { throw new Crypto.Error.GCRYPT(e.to_string()); } } -} \ No newline at end of file +#else +internal void openssl_error() throws Error { + throw new Crypto.Error.OPENSSL(OpenSSL.ERR.reason_error_string(OpenSSL.ERR.get_error())); +} +#endif +} diff --git a/crypto-vala/src/random.vala b/crypto-vala/src/random.vala index 3f5d3ba9..5687d505 100644 --- a/crypto-vala/src/random.vala +++ b/crypto-vala/src/random.vala @@ -1,5 +1,9 @@ namespace Crypto { public static void randomize(uint8[] buffer) { +#if GCRYPT GCrypt.Random.randomize(buffer); +#else + OpenSSL.RAND.bytes(buffer); +#endif +} } -} \ No newline at end of file diff --git a/crypto-vala/vapi/openssl.vapi b/crypto-vala/vapi/openssl.vapi index 47884a44..8dd8c092 100644 --- a/crypto-vala/vapi/openssl.vapi +++ b/crypto-vala/vapi/openssl.vapi @@ -669,7 +669,7 @@ namespace OpenSSL public int set_padding (int pad); [CCode (cname = "EVP_EncryptInit_ex")] - public int encrypt_init (Cipher cipher, Engine? engine, [CCode (array_length = false)] uchar[] key, [CCode (array_length = false)] uchar[] iv); + public int encrypt_init (Cipher? cipher, Engine? engine, [CCode (array_length = false)] uchar[]? key, [CCode (array_length = false)] uchar[]? iv); [CCode (cname = "EVP_EncryptUpdate")] public int encrypt_update ([CCode (array_length = false)] uchar[] ciphertext, out int ciphertext_len, uchar[] plaintext); @@ -678,13 +678,16 @@ namespace OpenSSL public int encrypt_final ([CCode (array_length = false)] uchar[] ciphertext, out int ciphertext_len); [CCode (cname = "EVP_DecryptInit_ex")] - public int decrypt_init (Cipher cipher, Engine? engine, [CCode (array_length = false)] uchar[] key, [CCode (array_length = false)] uchar[] iv); + public int decrypt_init (Cipher? cipher, Engine? engine, [CCode (array_length = false)] uchar[]? key, [CCode (array_length = false)] uchar[]? iv); [CCode (cname = "EVP_DecryptUpdate")] public int decrypt_update ([CCode (array_length = false)] uchar[] plaintext, out int plaintext_len, uchar[] ciphertext); [CCode (cname = "EVP_DecryptFinal_ex")] public int decrypt_final ([CCode (array_length = false)] uchar[] plaintext, out int plaintext_len); + + [CCode (simple_generics = true)] + public int ctrl(int type, int arg, T? ptr); } } @@ -802,4 +805,18 @@ namespace OpenSSL public int i2d_RSA_PUBKEY (RSA rsa, [CCode (array_length = false)] out uint8[] ppout); public int i2d_RSA_PUBKEY_fp (GLib.FileStream fp, RSA a); public int i2d_RSA_PUBKEY_bio (BIO bp, RSA a); + + [CCode (cprefix = "ERR_", lower_case_cprefix = "ERR_", cheader_filename = "openssl/err.h")] + namespace ERR + { + public ulong get_error(); + public unowned string? reason_error_string(ulong e); + } + + [CCode (cprefix = "RAND_", lower_case_cprefix = "RAND_", cheader_filename = "openssl/rand.h")] + namespace RAND + { + public int bytes(uint8[] buf); + } + } diff --git a/meson.build b/meson.build index 23a7b3d1..f3372953 100644 --- a/meson.build +++ b/meson.build @@ -8,20 +8,53 @@ python = import('python') # plugin_crypto is enabled if any of the crypto plugins is enabled, auto if # none of them are explicitly enabled but at least one is set to auto, or # disabled if all of them are disabled. -plugin_crypto = get_option('plugin-ice') -foreach plugin : ['plugin-ice', 'plugin-omemo', 'plugin-rtp'] - if get_option(plugin).enabled() and not plugin_crypto.enabled() - plugin_crypto = get_option(plugin) - elif get_option(plugin).allowed() and not plugin_crypto.allowed() - plugin_crypto = get_option(plugin) +# +# On Windows, it's always required because we need it for glib-networking. +if host_machine.system() == 'windows' + plugin_crypto = true +else + plugin_crypto = get_option('plugin-ice') + foreach plugin : ['plugin-ice', 'plugin-omemo', 'plugin-rtp'] + if get_option(plugin).enabled() and not plugin_crypto.enabled() + plugin_crypto = get_option(plugin) + elif get_option(plugin).allowed() and not plugin_crypto.allowed() + plugin_crypto = get_option(plugin) + endif + endforeach +endif + +if get_option('crypto-backend') == 'auto' + # Prefer libgcrypt/gnutls over openssl because glib-networking is usually + # built with gnutls anyway. + dep_libgcrypt = dependency('libgcrypt', required: false) + dep_gnutls = dependency('gnutls', required: false) + if dep_libgcrypt.found() and dep_libgcrypt.found() + dep_libgcrypt_or_openssl = dep_libgcrypt + dep_gnutls_or_openssl = dep_gnutls + crypto_backend = 'gnutls' + else + dep_openssl = dependency('openssl', disabler: true, required: plugin_crypto) + dep_libgcrypt_or_openssl = dep_openssl + dep_gnutls_or_openssl = dep_openssl + crypto_backend = 'openssl' endif -endforeach +elif get_option('crypto-backend') == 'openssl' + dep_openssl = dependency('openssl', disabler: true, required: plugin_crypto) + dep_libgcrypt_or_openssl = dep_openssl + dep_gnutls_or_openssl = dep_openssl + crypto_backend = 'openssl' +elif get_option('crypto-backend') == 'gnutls' + dep_libgcrypt = dependency('libgcrypt', disabler: true, required: plugin_crypto) + dep_gnutls = dependency('gnutls', disabler: true, required: get_option('plugin-ice')) + dep_libgcrypt_or_openssl = dep_libgcrypt + dep_gnutls_or_openssl = dep_gnutls + crypto_backend = 'gnutls' +endif dep_gdk_pixbuf = dependency('gdk-pixbuf-2.0') dep_gee = dependency('gee-0.8') dep_gio = dependency('gio-2.0') dep_glib = dependency('glib-2.0') -dep_gnutls = dependency('gnutls', disabler: true, required: get_option('plugin-ice')) dep_gmodule = dependency('gmodule-2.0') dep_gpgme = dependency('gpgme', disabler: true, required: get_option('plugin-openpgp')) dep_gstreamer = dependency('gstreamer-1.0', disabler: true, required: get_option('plugin-rtp')) @@ -33,7 +66,6 @@ dep_gtk4 = dependency('gtk4') dep_icu_uc = dependency('icu-uc') dep_libadwaita = dependency('libadwaita-1') dep_libcanberra = dependency('libcanberra', disabler: true, required: get_option('plugin-notification-sound')) -dep_libgcrypt = dependency('libgcrypt', disabler: true, required: plugin_crypto) dep_libqrencode = dependency('libqrencode', disabler: true, required: get_option('plugin-omemo')) dep_libsrtp2 = dependency('libsrtp2', disabler: true, required: plugin_crypto) # libsignal-protocol-c has a history of breaking compatibility on the patch level diff --git a/meson_options.txt b/meson_options.txt index caee3093..aa0c5bc2 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,5 +1,7 @@ option('plugindir', type: 'string', value: 'lib/dino/plugins', description: 'Dino plugin directory') +option('crypto-backend', type: 'combo', choices: ['auto', 'openssl', 'gnutls'], value: 'auto', description: 'Preferred crypto backend') + option('plugin-http-files', type: 'feature', description: 'HTTP file upload') option('plugin-ice', type: 'feature', description: '') option('plugin-notification-sound', type: 'feature', description: 'Sound for chat notifications') diff --git a/plugins/ice/meson.build b/plugins/ice/meson.build index 40e54ce3..de66c2ed 100644 --- a/plugins/ice/meson.build +++ b/plugins/ice/meson.build @@ -1,3 +1,10 @@ +if crypto_backend == 'openssl' + if get_option('plugin-ice').enabled() + error('plugin-ice does not work with openssl backend yet') + else + subdir_done() + endif +endif dependencies = [ dep_crypto_vala, dep_dino, @@ -5,7 +12,7 @@ dependencies = [ dep_gee, dep_glib, dep_gmodule, - dep_gnutls, + dep_gnutls_or_openssl, dep_nice, dep_qlite, dep_xmpp_vala, diff --git a/plugins/omemo/CMakeLists.txt b/plugins/omemo/CMakeLists.txt index 7ecaa0b8..83e6426f 100644 --- a/plugins/omemo/CMakeLists.txt +++ b/plugins/omemo/CMakeLists.txt @@ -90,9 +90,11 @@ GENERATE_VAPI omemo GENERATE_HEADER omemo +DEFINITIONS + GCRYPT ) -add_definitions(${VALA_CFLAGS} -DGETTEXT_PACKAGE=\"${GETTEXT_PACKAGE}\" -DLOCALE_INSTALL_DIR=\"${LOCALE_INSTALL_DIR}\" -DG_LOG_DOMAIN="OMEMO") +add_definitions(${VALA_CFLAGS} -DGETTEXT_PACKAGE=\"${GETTEXT_PACKAGE}\" -DLOCALE_INSTALL_DIR=\"${LOCALE_INSTALL_DIR}\" -DG_LOG_DOMAIN="OMEMO" -DGCRYPT) add_library(omemo SHARED ${OMEMO_VALA_C} ${OMEMO_GRESOURCES_TARGET} ${CMAKE_CURRENT_SOURCE_DIR}/src/signal/signal_helper.c) add_dependencies(omemo ${GETTEXT_PACKAGE}-translations) target_include_directories(omemo PUBLIC src) diff --git a/plugins/omemo/meson.build b/plugins/omemo/meson.build index 57eec2ce..5835f00b 100644 --- a/plugins/omemo/meson.build +++ b/plugins/omemo/meson.build @@ -6,7 +6,7 @@ dependencies = [ dep_glib, dep_gmodule, dep_gtk4, - dep_libgcrypt, + dep_libgcrypt_or_openssl, dep_libqrencode, dep_libsignal_protocol_c, dep_qlite, @@ -61,8 +61,24 @@ c_args = [ '-DGETTEXT_PACKAGE="dino-omemo"', '-DLOCALE_INSTALL_DIR="@0@"'.format(get_option('prefix') / get_option('localedir')), ] +if crypto_backend == 'gnutls' + c_args += ['-DGCRYPT'] +endif vala_args = [ '--vapidir', meson.current_source_dir() / 'vapi', ] +if crypto_backend == 'gnutls' + vala_args += ['-D', 'GCRYPT'] +endif lib_omemo = shared_library('omemo', sources, name_prefix: '', c_args: c_args, vala_args: vala_args, include_directories: include_directories('src'), dependencies: dependencies, install: true, install_dir: get_option('libdir') / 'dino/plugins') dep_omemo = declare_dependency(link_with: lib_omemo, include_directories: include_directories('.')) + +sources = files( + 'tests/signal/common.vala', + 'tests/signal/curve25519.vala', + 'tests/signal/hkdf.vala', + 'tests/signal/session_builder.vala', + 'tests/signal/testcase.vala', +) +test_omemo = executable('test_omemo', sources, vala_args: vala_args, include_directories: include_directories('src'), dependencies: dependencies + [dep_omemo]) +test('omemo', test_omemo) diff --git a/plugins/omemo/src/file_transfer/file_decryptor.vala b/plugins/omemo/src/file_transfer/file_decryptor.vala index 97d61899..647fdae2 100644 --- a/plugins/omemo/src/file_transfer/file_decryptor.vala +++ b/plugins/omemo/src/file_transfer/file_decryptor.vala @@ -59,7 +59,7 @@ public class OmemoFileDecryptor : FileDecryptor, Object { file_transfer.encryption = Encryption.OMEMO; debug("Decrypting file %s from %s", file_transfer.file_name, file_transfer.server_file_name); - SymmetricCipher cipher = new SymmetricCipher("AES-GCM"); + SymmetricCipher cipher = new SymmetricCipher.decryption("AES-GCM"); cipher.set_key(key); cipher.set_iv(iv); return new ConverterInputStream(encrypted_stream, new SymmetricCipherDecrypter((owned) cipher, 16)); diff --git a/plugins/omemo/src/file_transfer/file_encryptor.vala b/plugins/omemo/src/file_transfer/file_encryptor.vala index 39af8292..b8aad5db 100644 --- a/plugins/omemo/src/file_transfer/file_encryptor.vala +++ b/plugins/omemo/src/file_transfer/file_encryptor.vala @@ -31,7 +31,7 @@ public class OmemoFileEncryptor : Dino.FileEncryptor, Object { uint8[] key = new uint8[KEY_SIZE]; Plugin.get_context().randomize(key); - SymmetricCipher cipher = new SymmetricCipher("AES-GCM"); + SymmetricCipher cipher = new SymmetricCipher.encryption("AES-GCM"); cipher.set_key(key); cipher.set_iv(iv); diff --git a/plugins/omemo/src/jingle/jet_omemo.vala b/plugins/omemo/src/jingle/jet_omemo.vala index afcdfcd6..323ca37b 100644 --- a/plugins/omemo/src/jingle/jet_omemo.vala +++ b/plugins/omemo/src/jingle/jet_omemo.vala @@ -99,13 +99,13 @@ public class AesGcmCipher : Jet.Cipher, Object { return new Jet.TransportSecret(key, iv); } public InputStream wrap_input_stream(InputStream input, Jet.TransportSecret secret) requires (secret.transport_key.length == key_size) { - SymmetricCipher cipher = new SymmetricCipher("AES-GCM"); + SymmetricCipher cipher = new SymmetricCipher.decryption("AES-GCM"); cipher.set_key(secret.transport_key); cipher.set_iv(secret.initialization_vector); return new ConverterInputStream(input, new SymmetricCipherDecrypter((owned) cipher, 16)); } public OutputStream wrap_output_stream(OutputStream output, Jet.TransportSecret secret) requires (secret.transport_key.length == key_size) { - Crypto.SymmetricCipher cipher = new SymmetricCipher("AES-GCM"); + Crypto.SymmetricCipher cipher = new SymmetricCipher.encryption("AES-GCM"); cipher.set_key(secret.transport_key); cipher.set_iv(secret.initialization_vector); return new ConverterOutputStream(output, new SymmetricCipherEncrypter((owned) cipher, 16)); diff --git a/plugins/omemo/src/protocol/bundle.vala b/plugins/omemo/src/protocol/bundle.vala index f3bebfcb..a179a312 100644 --- a/plugins/omemo/src/protocol/bundle.vala +++ b/plugins/omemo/src/protocol/bundle.vala @@ -12,7 +12,7 @@ public class Bundle { assert(Plugin.ensure_context()); } - public int32 signed_pre_key_id { owned get { + public int32 signed_pre_key_id { get { if (node == null) return -1; string? id = ((!)node).get_deep_attribute("signedPreKeyPublic", "signedPreKeyId"); if (id == null) return -1; @@ -69,7 +69,7 @@ public class Bundle { this.node = node; } - public int32 key_id { owned get { + public int32 key_id { get { return int.parse(node.get_attribute("preKeyId") ?? "-1"); }} diff --git a/plugins/omemo/src/signal/context.vala b/plugins/omemo/src/signal/context.vala index 40a07b0f..15ff2c34 100644 --- a/plugins/omemo/src/signal/context.vala +++ b/plugins/omemo/src/signal/context.vala @@ -1,7 +1,7 @@ namespace Signal { public class Context { - internal NativeContext native_context; + public NativeContext native_context; private RecMutex mutex = RecMutex(); static void locking_function_lock(void* user_data) { diff --git a/plugins/omemo/src/signal/signal_helper.c b/plugins/omemo/src/signal/signal_helper.c index 17682929..35d9d69f 100644 --- a/plugins/omemo/src/signal/signal_helper.c +++ b/plugins/omemo/src/signal/signal_helper.c @@ -1,6 +1,11 @@ #include "signal_helper.h" +#ifdef GCRYPT #include +#else +#include +#include +#endif signal_type_base* signal_type_ref_vapi(void* instance) { g_return_val_if_fail(instance != NULL, NULL); @@ -65,16 +70,31 @@ void signal_protocol_address_set_device_id(signal_protocol_address* self, int32_ } int signal_vala_randomize(uint8_t *data, size_t len) { +#ifdef GCRYPT gcry_randomize(data, len, GCRY_STRONG_RANDOM); return SG_SUCCESS; +#else + return RAND_bytes(data, len) == 1 ? SG_SUCCESS : SG_ERR_UNKNOWN; +#endif } int signal_vala_random_generator(uint8_t *data, size_t len, void *user_data) { +#ifdef GCRYPT gcry_randomize(data, len, GCRY_STRONG_RANDOM); return SG_SUCCESS; +#else + return RAND_bytes(data, len) == 1 ? SG_SUCCESS : SG_ERR_UNKNOWN; +#endif } +#ifndef GCRYPT +struct SIGNAL_VALA_HMAC_CTX { + EVP_PKEY *pkey; + EVP_MD_CTX *ctx; +}; +#endif int signal_vala_hmac_sha256_init(void **hmac_context, const uint8_t *key, size_t key_len, void *user_data) { +#ifdef GCRYPT gcry_mac_hd_t* ctx = malloc(sizeof(gcry_mac_hd_t)); if (!ctx) return SG_ERR_NOMEM; @@ -91,17 +111,49 @@ int signal_vala_hmac_sha256_init(void **hmac_context, const uint8_t *key, size_t *hmac_context = ctx; return SG_SUCCESS; +#else + 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; +#endif } int signal_vala_hmac_sha256_update(void *hmac_context, const uint8_t *data, size_t data_len, void *user_data) { +#ifdef GCRYPT gcry_mac_hd_t* ctx = hmac_context; if (gcry_mac_write(*ctx, data, data_len)) return SG_ERR_UNKNOWN; return SG_SUCCESS; +#else + 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; +#endif } int signal_vala_hmac_sha256_final(void *hmac_context, signal_buffer **output, void *user_data) { +#ifdef GCRYPT size_t len = gcry_mac_get_algo_maclen(GCRY_MAC_HMAC_SHA256); uint8_t md[len]; gcry_mac_hd_t* ctx = hmac_context; @@ -114,17 +166,49 @@ int signal_vala_hmac_sha256_final(void *hmac_context, signal_buffer **output, vo *output = output_buffer; return SG_SUCCESS; +#else + 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; +#endif } void signal_vala_hmac_sha256_cleanup(void *hmac_context, void *user_data) { +#ifdef GCRYPT gcry_mac_hd_t* ctx = hmac_context; if (ctx) { gcry_mac_close(*ctx); free(ctx); } +#else + 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); + } +#endif } int signal_vala_sha512_digest_init(void **digest_context, void *user_data) { +#ifdef GCRYPT gcry_md_hd_t* ctx = malloc(sizeof(gcry_mac_hd_t)); if (!ctx) return SG_ERR_NOMEM; @@ -136,17 +220,38 @@ int signal_vala_sha512_digest_init(void **digest_context, void *user_data) { *digest_context = ctx; return SG_SUCCESS; +#else + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + if (!ctx) { + return SG_ERR_NOMEM; + } + if (EVP_DigestInit_ex(ctx, EVP_sha512(), NULL) != 1) { + EVP_MD_CTX_free(ctx); + return SG_ERR_UNKNOWN; + } + *digest_context = ctx; + return SG_SUCCESS; +#endif } int signal_vala_sha512_digest_update(void *digest_context, const uint8_t *data, size_t data_len, void *user_data) { +#ifdef GCRYPT gcry_md_hd_t* ctx = digest_context; gcry_md_write(*ctx, data, data_len); return SG_SUCCESS; +#else + EVP_MD_CTX *ctx = digest_context; + if (EVP_DigestUpdate(ctx, data, data_len) != 1) { + return SG_ERR_UNKNOWN; + } + return SG_SUCCESS; +#endif } int signal_vala_sha512_digest_final(void *digest_context, signal_buffer **output, void *user_data) { +#ifdef GCRYPT size_t len = gcry_md_get_algo_dlen(GCRY_MD_SHA512); gcry_md_hd_t* ctx = digest_context; @@ -162,17 +267,43 @@ int signal_vala_sha512_digest_final(void *digest_context, signal_buffer **output *output = output_buffer; return SG_SUCCESS; +#else + EVP_MD_CTX *ctx = digest_context; + size_t len = EVP_MD_size(EVP_sha512()); + signal_buffer *output_buffer = signal_buffer_alloc(len); + if (!output_buffer) { + return SG_ERR_NOMEM; + } + if (EVP_DigestSignFinal(ctx, signal_buffer_data(output_buffer), &len) != 1) { + signal_buffer_free(output_buffer); + return SG_ERR_UNKNOWN; + } + if (len != EVP_MD_size(EVP_sha512())) { + signal_buffer_free(output_buffer); + return SG_ERR_UNKNOWN; + } + *output = output_buffer; + return SG_SUCCESS; +#endif } void signal_vala_sha512_digest_cleanup(void *digest_context, void *user_data) { +#ifdef GCRYPT gcry_md_hd_t* ctx = digest_context; if (ctx) { gcry_md_close(*ctx); free(ctx); } +#else + EVP_MD_CTX *ctx = digest_context; + if (ctx) { + EVP_MD_CTX_free(ctx); + } +#endif } -const int aes_cipher(int cipher, size_t key_len, int* algo, int* mode) { +#ifdef GCRYPT +static int aes_cipher(int cipher, size_t key_len, int* algo, int* mode) { switch (key_len) { case 16: *algo = GCRY_CIPHER_AES128; @@ -201,6 +332,35 @@ const int aes_cipher(int cipher, size_t key_len, int* algo, int* mode) { } return SG_SUCCESS; } +#else +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; +} +#endif int signal_vala_encrypt(signal_buffer **output, int cipher, @@ -208,6 +368,7 @@ int signal_vala_encrypt(signal_buffer **output, const uint8_t *iv, size_t iv_len, const uint8_t *plaintext, size_t plaintext_len, void *user_data) { +#ifdef GCRYPT int algo, mode, error_code = SG_ERR_UNKNOWN; if (aes_cipher(cipher, key_len, &algo, &mode)) return SG_ERR_INVAL; @@ -279,6 +440,97 @@ no_error: gcry_cipher_close(ctx); return SG_SUCCESS; +#else + 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; +#endif } int signal_vala_decrypt(signal_buffer **output, @@ -287,6 +539,7 @@ int signal_vala_decrypt(signal_buffer **output, const uint8_t *iv, size_t iv_len, const uint8_t *ciphertext, size_t ciphertext_len, void *user_data) { +#ifdef GCRYPT int algo, mode, error_code = SG_ERR_UNKNOWN; *output = 0; if (aes_cipher(cipher, key_len, &algo, &mode)) return SG_ERR_INVAL; @@ -352,11 +605,104 @@ no_error: gcry_cipher_close(ctx); return SG_SUCCESS; +#else + 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; +#endif } void setup_signal_vala_crypto_provider(signal_context *context) { +#ifdef GCRYPT gcry_check_version(NULL); +#endif signal_crypto_provider provider = { .random_func = signal_vala_random_generator, diff --git a/plugins/omemo/src/signal/store.vala b/plugins/omemo/src/signal/store.vala index b440d838..9fee3373 100644 --- a/plugins/omemo/src/signal/store.vala +++ b/plugins/omemo/src/signal/store.vala @@ -107,7 +107,7 @@ public class Store : Object { 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_; }} + 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) { diff --git a/plugins/omemo/vapi/openssl.vapi b/plugins/omemo/vapi/openssl.vapi new file mode 100644 index 00000000..e69de29b