Allow using OpenSSL instead of GnuTLS

In preparation of Windows support.
This commit is contained in:
hrxi 2023-11-24 20:53:50 +01:00 committed by Maxim Logaev
parent c5704ea56b
commit e4bd6c1ce4
19 changed files with 604 additions and 35 deletions

View file

@ -22,6 +22,8 @@ GENERATE_VAPI
crypto-vala
GENERATE_HEADER
crypto-vala
DEFINITIONS
GCRYPT
)
add_custom_target(crypto-vala-vapi

View file

@ -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('.'))

View file

@ -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
}
}
}

View file

@ -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());
}
}
#else
internal void openssl_error() throws Error {
throw new Crypto.Error.OPENSSL(OpenSSL.ERR.reason_error_string(OpenSSL.ERR.get_error()));
}
#endif
}

View file

@ -1,5 +1,9 @@
namespace Crypto {
public static void randomize(uint8[] buffer) {
#if GCRYPT
GCrypt.Random.randomize(buffer);
#else
OpenSSL.RAND.bytes(buffer);
#endif
}
}

View file

@ -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<T>(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);
}
}

View file

@ -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

View file

@ -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')

View file

@ -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,

View file

@ -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)

View file

@ -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)

View file

@ -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));

View file

@ -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);

View file

@ -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));

View file

@ -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");
}}

View file

@ -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) {

View file

@ -1,6 +1,11 @@
#include "signal_helper.h"
#ifdef GCRYPT
#include <gcrypt.h>
#else
#include <openssl/evp.h>
#include <openssl/rand.h>
#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,

View file

@ -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) {

View file