Skip to content

Commit

Permalink
bcrypt: Add support for calculating secret ecc keys.
Browse files Browse the repository at this point in the history
(updated by Paul Gofman)

For Rainbow 6: Siege.
  • Loading branch information
Guy1524 authored and ivyl committed Feb 15, 2024
1 parent ca71621 commit 8ae2eb0
Show file tree
Hide file tree
Showing 3 changed files with 250 additions and 4 deletions.
14 changes: 14 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ AC_ARG_WITH(dbus, AS_HELP_STRING([--without-dbus],[do not use DBus (dynamic
AC_ARG_WITH(float-abi, AS_HELP_STRING([--with-float-abi=abi],[specify the ABI (soft|softfp|hard) for ARM platforms]))
AC_ARG_WITH(fontconfig,AS_HELP_STRING([--without-fontconfig],[do not use fontconfig]))
AC_ARG_WITH(freetype, AS_HELP_STRING([--without-freetype],[do not use the FreeType library]))
AC_ARG_WITH(gcrypt, AS_HELP_STRING([--without-gcrypt],[do not use libgcrypt]))
AC_ARG_WITH(gettext, AS_HELP_STRING([--without-gettext],[do not use gettext]))
AC_ARG_WITH(gettextpo, AS_HELP_STRING([--with-gettextpo],[use the GetTextPO library to rebuild po files]),
[if test "x$withval" = "xno"; then ac_cv_header_gettext_po_h=no; fi])
Expand Down Expand Up @@ -1884,6 +1885,19 @@ fi
WINE_NOTICE_WITH(vulkan,[test "x$ac_cv_lib_soname_vulkan" = "x" -a "x$ac_cv_lib_soname_MoltenVK" = "x"],
[libvulkan and libMoltenVK ${notice_platform}development files not found, Vulkan won't be supported.])

dnl **** Check for gcrypt ****
if test "x$with_gcrypt" != "xno"
then
WINE_PACKAGE_FLAGS(GCRYPT,[libgcrypt],,,,
[AC_CHECK_HEADERS([gcrypt.h])
if test "$ac_cv_header_gcrypt_h" = "yes"
then
WINE_CHECK_SONAME(gcrypt,gcry_sexp_build,,,[$GCRYPT_LIBS])
fi])
fi
WINE_NOTICE_WITH(gcrypt,[test "x$ac_cv_lib_soname_gcrypt" = "x"],
[libgcrypt ${notice_platform}development files not found, GCRYPT won't be supported.])

dnl **** Check for gcc specific options ****

if test "x${GCC}" = "xyes"
Expand Down
230 changes: 230 additions & 0 deletions dlls/bcrypt/gnutls.c
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@
#include <fcntl.h>
#include <unistd.h>

#ifdef HAVE_GCRYPT_H
#include <gcrypt.h>
#endif

WINE_DEFAULT_DEBUG_CHANNEL(bcrypt);
WINE_DECLARE_DEBUG_CHANNEL(winediag);

Expand Down Expand Up @@ -203,6 +207,12 @@ static int (*pgnutls_dh_params_import_raw2)(gnutls_dh_params_t dh_params, const
static int (*pgnutls_dh_params_export_raw)(gnutls_dh_params_t params, gnutls_datum_t * prime,
gnutls_datum_t * generator, unsigned int *bits);

static int (*pgnutls_ecdh_compute_key)(gnutls_ecc_curve_t curve,
const gnutls_datum_t *x, const gnutls_datum_t *y,
const gnutls_datum_t *k,
const gnutls_datum_t *peer_x, const gnutls_datum_t *peer_y,
gnutls_datum_t *Z);

#define MAKE_FUNCPTR(f) static typeof(f) * p##f
MAKE_FUNCPTR(gnutls_cipher_decrypt2);
MAKE_FUNCPTR(gnutls_cipher_deinit);
Expand Down Expand Up @@ -242,6 +252,24 @@ MAKE_FUNCPTR(mpz_mod);
MAKE_FUNCPTR(mpz_powm);
MAKE_FUNCPTR(mpz_sub_ui);
#endif

#if defined(HAVE_GCRYPT_H) && defined(SONAME_LIBGCRYPT)
static BOOL gcrypt_available;
static void *libgcrypt_handle;

MAKE_FUNCPTR(gcry_check_version);
MAKE_FUNCPTR(gcry_sexp_build);
MAKE_FUNCPTR(gcry_pk_encrypt);
MAKE_FUNCPTR(gcry_mpi_new);
MAKE_FUNCPTR(gcry_mpi_print);
MAKE_FUNCPTR(gcry_sexp_release);
MAKE_FUNCPTR(gcry_mpi_release);
MAKE_FUNCPTR(gcry_strsource);
MAKE_FUNCPTR(gcry_strerror);
MAKE_FUNCPTR(gcry_sexp_find_token);
MAKE_FUNCPTR(gcry_sexp_nth_mpi);
#endif

#undef MAKE_FUNCPTR

static int compat_gnutls_cipher_tag(gnutls_cipher_hd_t handle, void *tag, size_t tag_size)
Expand Down Expand Up @@ -496,6 +524,36 @@ static NTSTATUS gnutls_process_attach( void *args )
#undef LOAD_FUNCPTR_STR
#endif

#if defined(HAVE_GCRYPT_H) && defined(SONAME_LIBGCRYPT)
#define LOAD_FUNCPTR(f) \
if (!(p##f = dlsym( libgcrypt_handle, #f ))) \
{ \
WARN( "failed to load %s\n", #f ); \
gcrypt_available = FALSE; \
}

if ((libgcrypt_handle = dlopen( SONAME_LIBGCRYPT, RTLD_NOW )))
{
gcrypt_available = TRUE;

LOAD_FUNCPTR(gcry_check_version);
LOAD_FUNCPTR(gcry_sexp_build);
LOAD_FUNCPTR(gcry_pk_encrypt);
LOAD_FUNCPTR(gcry_mpi_new);
LOAD_FUNCPTR(gcry_mpi_print);
LOAD_FUNCPTR(gcry_sexp_release);
LOAD_FUNCPTR(gcry_mpi_release);
LOAD_FUNCPTR(gcry_strsource);
LOAD_FUNCPTR(gcry_strerror);
LOAD_FUNCPTR(gcry_sexp_find_token);
LOAD_FUNCPTR(gcry_sexp_nth_mpi);
}
else
WARN("failed to load gcrypt, no support for ECC secret agreement\n");

#undef LOAD_FUNCPTR
#endif

#define LOAD_FUNCPTR_OPT(f) \
if (!(p##f = dlsym( libgnutls_handle, #f ))) \
{ \
Expand Down Expand Up @@ -568,6 +626,12 @@ static NTSTATUS gnutls_process_attach( void *args )
ERR_(winediag)("Compiled without DH support.\n");
#endif

if (!(pgnutls_ecdh_compute_key = dlsym( libgnutls_handle, "_gnutls_ecdh_compute_key" ))
&& !(pgnutls_ecdh_compute_key = dlsym( libgnutls_handle, "gnutls_ecdh_compute_key" )))
{
WARN("gnutls_ecdh_compute_key not found\n");
}

if (TRACE_ON( bcrypt ))
{
pgnutls_global_set_log_level( 4 );
Expand Down Expand Up @@ -604,6 +668,11 @@ static NTSTATUS gnutls_process_detach( void *args )
dlclose( libgmp_handle );
libgmp_handle = NULL;
#endif

#if defined(HAVE_GCRYPT_H) && defined(SONAME_LIBGCRYPT)
dlclose( libgcrypt_handle );
libgcrypt_handle = NULL;
#endif
}

struct buffer
Expand Down Expand Up @@ -2711,6 +2780,60 @@ static NTSTATUS key_asymmetric_encrypt( void *args )
return status;
}

#if defined(HAVE_GCRYPT_H) && defined(SONAME_LIBGCRYPT)
static NTSTATUS gcrypt_extract_result_into_secret(gcry_sexp_t result, gnutls_datum_t *s)
{
NTSTATUS status = STATUS_SUCCESS;
gcry_mpi_t fullcoords = NULL;
gcry_sexp_t fragment = NULL;
UCHAR *tmp_buffer = NULL;
gcry_error_t err;
size_t size;

fragment = pgcry_sexp_find_token( result, "s", 0 );
if (!fragment)
{
status = STATUS_NO_MEMORY;
goto done;
}

fullcoords = pgcry_sexp_nth_mpi( fragment, 1, GCRYMPI_FMT_USG );
if (!fullcoords)
{
status = STATUS_NO_MEMORY;
goto done;
}

if ((err = pgcry_mpi_print( GCRYMPI_FMT_USG, NULL, 0, &size, fullcoords)) )
{
ERR("Error = %s/%s.\n", pgcry_strsource( err ), pgcry_strerror( err ));
status = STATUS_INTERNAL_ERROR;
goto done;
}

tmp_buffer = malloc(size);
if ((err = pgcry_mpi_print( GCRYMPI_FMT_STD, tmp_buffer, size, NULL, fullcoords)) )
{
ERR( "Error = %s/%s.\n", pgcry_strsource(err), pgcry_strerror(err) );
status = STATUS_INTERNAL_ERROR;
goto done;
}

s->size = size / 2;
s->data = malloc( s->size );
memcpy( s->data, tmp_buffer + size % 2, size / 2 );

done:
free( tmp_buffer );

pgcry_mpi_release( fullcoords );
pgcry_sexp_release( fragment );

return status;
}
#endif


static NTSTATUS key_asymmetric_derive_key( void *args )
{
const struct key_asymmetric_derive_key_params *params = args;
Expand Down Expand Up @@ -2785,6 +2908,113 @@ static NTSTATUS key_asymmetric_derive_key( void *args )
return STATUS_NOT_IMPLEMENTED;
#endif

case ALG_ID_ECDH_P256:
case ALG_ID_ECDH_P384:
/* this is necessary since GNUTLS doesn't support ECDH public key encryption, maybe we can replace this when it does:
https://github.com/gnutls/gnutls/blob/cdc4fc288d87f91f974aa23b6e8595a53970ce00/lib/nettle/pk.c#L495 */
#if defined(HAVE_GCRYPT_H) && defined(SONAME_LIBGCRYPT)
{
gcry_sexp_t xchg_result = NULL;
gcry_sexp_t privkey = NULL;
gcry_sexp_t pubkey = NULL;
const char *pubkey_format;
BCRYPT_ECCKEY_BLOB *h;
UCHAR *privkey_blob;
UCHAR *pubkey_raw;
gcry_error_t err;
ULONG key_length;
NTSTATUS status;
ULONG key_len;

if (!gcrypt_available)
{
ERR("ECDH secret agreement is not available.\n");
return STATUS_NOT_IMPLEMENTED;
}

if (priv_key->alg_id == ALG_ID_ECDH_P256)
{
pubkey_format = "NIST P-256";
key_length = 32;
}
else if (priv_key->alg_id == ALG_ID_ECDH_P384)
{
pubkey_format = "NIST P-384";
key_length = 48;
}
else return STATUS_NOT_IMPLEMENTED;

if (key_length != priv_key->u.a.bitlen / 8)
{
ERR( "Key length mismatch, key->u.a.bitlen %u, key_length %u.\n", (int)priv_key->u.a.bitlen,
(int)key_length );
return STATUS_INVALID_PARAMETER;
}

if ((status = key_export_ecc( priv_key, NULL, 0, &key_len )))
return status;
privkey_blob = malloc( key_len );
if ((status = key_export_ecc( priv_key, privkey_blob, key_len, &key_len )))
{
free( privkey_blob );
return status;
}

if ((status = key_export_ecc_public( peer_key, NULL, 0, &key_len )))
return status;
h = malloc( key_len );
if ((status = key_export_ecc_public( peer_key, (UCHAR *)h, key_len, &key_len )))
{
free( privkey_blob );
return status;
}

/* copy public key into temporary buffer so we can prepend 0x04 (to indicate it is uncompressed) */
pubkey_raw = malloc( (key_length * 2) + 1 );
pubkey_raw[0] = 0x04;
memcpy( pubkey_raw + 1, h + 1, key_length * 2 );
free( h );

err = pgcry_sexp_build( &pubkey, NULL, "(key-data(public-key(ecdh(curve %s)(q %b))))", pubkey_format,
(key_length * 2) + 1, pubkey_raw );
free( pubkey_raw );
if (err)
{
free( privkey_blob );
ERR( "Failed to build gcrypt public key. err %s/%s\n", pgcry_strsource( err ), pgcry_strerror( err ));
return STATUS_INTERNAL_ERROR;
}

err = pgcry_sexp_build( &privkey, NULL, "(data(flags raw)(value %b))", key_length,
privkey_blob + sizeof(BCRYPT_ECCKEY_BLOB) + key_length * 2 );
free( privkey_blob );
if (err)
{
pgcry_sexp_release( pubkey );
return STATUS_INTERNAL_ERROR;
}
err = pgcry_pk_encrypt( &xchg_result, privkey, pubkey );
pgcry_sexp_release( privkey );
pgcry_sexp_release( pubkey );
if (err)
{
ERR( "Failed to perform key exchange. err %s/%s\n", pgcry_strsource( err ), pgcry_strerror( err ));
return STATUS_INTERNAL_ERROR;
}
status = gcrypt_extract_result_into_secret( xchg_result, &s );
pgcry_sexp_release(xchg_result);
if (status)
{
ERR("Failed to extract secret key.\n");
return status;
}
break;
}
#else
WARN("Compiled without ECC secret support.\n");
return STATUS_NOT_IMPLEMENTED;
#endif

default:
ERR( "unhandled algorithm %u\n", priv_key->alg_id );
return STATUS_INVALID_HANDLE;
Expand Down
10 changes: 6 additions & 4 deletions dlls/bcrypt/tests/bcrypt.c
Original file line number Diff line number Diff line change
Expand Up @@ -3057,10 +3057,13 @@ static void test_ECDH(void)
win_skip("BCRYPT_KDF_RAW_SECRET not supported\n");
goto raw_secret_end;
}
todo_wine ok(status == STATUS_SUCCESS, "got %#lx\n", status);
if (status != STATUS_SUCCESS) goto raw_secret_end;
ok(status == STATUS_SUCCESS, "got %#lx\n", status);

ok(size == 32, "size of secret key incorrect, got %lu, expected 32\n", size);
if (!size)
goto raw_secret_end;


buf = malloc(size);
status = BCryptDeriveKey(secret, BCRYPT_KDF_RAW_SECRET, NULL, buf, size, &size, 0);
ok(status == STATUS_SUCCESS, "got %#lx\n", status);
Expand All @@ -3069,7 +3072,7 @@ static void test_ECDH(void)

raw_secret_end:
status = BCryptDeriveKey(secret, BCRYPT_KDF_HASH, &hash_params, NULL, 0, &size, 0);
todo_wine ok (status == STATUS_SUCCESS, "got %#lx\n", status);
ok (status == STATUS_SUCCESS, "got %#lx\n", status);
if (status != STATUS_SUCCESS) goto derive_end;

ok (size == 20, "got %lu\n", size);
Expand Down Expand Up @@ -3869,7 +3872,6 @@ static void test_SecretAgreement(void)
ok(status == STATUS_INVALID_PARAMETER, "got %#lx\n", status);

status = BCryptDeriveKey(secret, L"HASH", NULL, NULL, 0, &size, 0);
todo_wine
ok(status == STATUS_SUCCESS, "got %#lx\n", status);

status = BCryptDestroyHash(secret);
Expand Down

0 comments on commit 8ae2eb0

Please sign in to comment.