From 71de99f0e405cc39e819421ccc470b1085dbe8a1 Mon Sep 17 00:00:00 2001 From: Derek Lesho Date: Fri, 2 Oct 2020 12:11:49 -0500 Subject: [PATCH] bcrypt: Implement BCryptSecretAgreement with libgcrypt. Signed-off-by: Derek Lesho --- configure.ac | 14 ++ dlls/bcrypt/Makefile.in | 1 + dlls/bcrypt/bcrypt_internal.h | 4 + dlls/bcrypt/bcrypt_main.c | 55 ++++++- dlls/bcrypt/gcrypt.c | 292 ++++++++++++++++++++++++++++++++++ dlls/bcrypt/gnutls.c | 3 +- dlls/bcrypt/macos.c | 3 +- dlls/bcrypt/tests/bcrypt.c | 2 +- dlls/bcrypt/unixlib.c | 15 +- 9 files changed, 379 insertions(+), 10 deletions(-) create mode 100644 dlls/bcrypt/gcrypt.c diff --git a/configure.ac b/configure.ac index b6b8d49342f3..7c1c8a7ddaf4 100644 --- a/configure.ac +++ b/configure.ac @@ -46,6 +46,7 @@ AC_ARG_WITH(faudio, AS_HELP_STRING([--without-faudio],[do not use FAudio (XAu 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]) @@ -1989,6 +1990,19 @@ WINE_NOTICE_WITH(vkd3d,[test "x$ac_cv_lib_soname_vkd3d" = "x"], [vkd3d ${notice_platform}development files not found (or too old), Direct3D 12 won't be supported.]) test "x$ac_cv_lib_soname_vkd3d" != "x" || enable_d3d12=${enable_d3d12:-no} +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 **** AC_SUBST(EXTRACFLAGS,"") diff --git a/dlls/bcrypt/Makefile.in b/dlls/bcrypt/Makefile.in index 46a20d473dd7..4a3016784af3 100644 --- a/dlls/bcrypt/Makefile.in +++ b/dlls/bcrypt/Makefile.in @@ -7,6 +7,7 @@ EXTRADLLFLAGS = -mno-cygwin C_SRCS = \ bcrypt_main.c \ + gcrypt.c \ gnutls.c \ macos.c \ md2.c \ diff --git a/dlls/bcrypt/bcrypt_internal.h b/dlls/bcrypt/bcrypt_internal.h index 3c7110d05f84..e7991eac077a 100644 --- a/dlls/bcrypt/bcrypt_internal.h +++ b/dlls/bcrypt/bcrypt_internal.h @@ -192,6 +192,8 @@ struct key struct secret { struct object hdr; + UCHAR *data; + ULONG len; }; struct key_funcs @@ -216,9 +218,11 @@ struct key_funcs NTSTATUS (CDECL *key_import_dsa_capi)( struct key *, UCHAR *, ULONG ); NTSTATUS (CDECL *key_import_ecc)( struct key *, UCHAR *, ULONG ); NTSTATUS (CDECL *key_import_rsa)( struct key *, UCHAR *, ULONG ); + NTSTATUS (CDECL *key_compute_secret_ecc)( unsigned char *privkey_in, struct key *pubkey_in, struct secret *secret ); }; struct key_funcs *gnutls_lib_init(DWORD reason); struct key_funcs *macos_lib_init(DWORD reason); +struct key_funcs *gcrypt_lib_init(DWORD reason); #endif /* __BCRYPT_INTERNAL_H */ diff --git a/dlls/bcrypt/bcrypt_main.c b/dlls/bcrypt/bcrypt_main.c index a1423dcd8368..0655c5dcfe81 100644 --- a/dlls/bcrypt/bcrypt_main.c +++ b/dlls/bcrypt/bcrypt_main.c @@ -1932,9 +1932,12 @@ NTSTATUS WINAPI BCryptSecretAgreement(BCRYPT_KEY_HANDLE privatekey, BCRYPT_KEY_H { struct key *privkey = privatekey; struct key *pubkey = publickey; + UCHAR *privkey_blob = NULL; + ULONG privkey_len; struct secret *secret; + NTSTATUS status; - FIXME( "%p, %p, %p, %08x\n", privatekey, publickey, handle, flags ); + TRACE( "%p, %p, %p, %08x\n", privatekey, publickey, handle, flags ); if (!privkey || privkey->hdr.magic != MAGIC_KEY) return STATUS_INVALID_HANDLE; if (!pubkey || pubkey->hdr.magic != MAGIC_KEY) return STATUS_INVALID_HANDLE; @@ -1943,18 +1946,39 @@ NTSTATUS WINAPI BCryptSecretAgreement(BCRYPT_KEY_HANDLE privatekey, BCRYPT_KEY_H if (!(secret = heap_alloc_zero( sizeof(*secret) ))) return STATUS_NO_MEMORY; secret->hdr.magic = MAGIC_SECRET; + if ((status = key_funcs->key_export_ecc( privkey, NULL, 0, &privkey_len))) + goto done; + + privkey_blob = heap_alloc(privkey_len); + if ((status = key_funcs->key_export_ecc( privkey, privkey_blob, privkey_len, &privkey_len))) + goto done; + + if ((status = key_funcs->key_compute_secret_ecc( privkey_blob, pubkey, secret ))) + goto done; + +done: + if (privkey_blob) + heap_free(privkey_blob); + + if (status) + { + heap_free(secret); + secret = NULL; + } + *handle = secret; - return STATUS_SUCCESS; + return status; } NTSTATUS WINAPI BCryptDestroySecret(BCRYPT_SECRET_HANDLE handle) { struct secret *secret = handle; - FIXME( "%p\n", handle ); + TRACE( "%p\n", handle ); if (!secret || secret->hdr.magic != MAGIC_SECRET) return STATUS_INVALID_HANDLE; secret->hdr.magic = 0; + heap_free( secret->data ); heap_free( secret ); return STATUS_SUCCESS; } @@ -1964,12 +1988,33 @@ NTSTATUS WINAPI BCryptDeriveKey(BCRYPT_SECRET_HANDLE handle, LPCWSTR kdf, BCrypt { struct secret *secret = handle; - FIXME( "%p, %s, %p, %p, %d, %p, %08x\n", secret, debugstr_w(kdf), parameter, derived, derived_size, result, flags ); + TRACE( "%p, %s, %p, %p, %d, %p, %08x\n", secret, debugstr_w(kdf), parameter, derived, derived_size, result, flags ); if (!secret || secret->hdr.magic != MAGIC_SECRET) return STATUS_INVALID_HANDLE; if (!kdf) return STATUS_INVALID_PARAMETER; - return STATUS_INTERNAL_ERROR; + if (!(lstrcmpW( kdf, BCRYPT_KDF_RAW_SECRET ))) + { + ULONG n; + ULONG secret_length = secret->len; + + if (!derived) + { + *result = secret_length; + return STATUS_SUCCESS; + } + + /* outputs in little endian for some reason */ + for (n = 0; n < secret_length && n < derived_size; n++) + { + derived[n] = secret->data[secret_length - n - 1]; + } + + *result = n; + return STATUS_SUCCESS; + } + FIXME( "Derivation function %s not supported.\n", debugstr_w(kdf) ); + return STATUS_NOT_IMPLEMENTED; } BOOL WINAPI DllMain( HINSTANCE hinst, DWORD reason, LPVOID reserved ) diff --git a/dlls/bcrypt/gcrypt.c b/dlls/bcrypt/gcrypt.c new file mode 100644 index 000000000000..e72c27feb519 --- /dev/null +++ b/dlls/bcrypt/gcrypt.c @@ -0,0 +1,292 @@ +#if 0 +#pragma makedep unix +#endif + +#include "config.h" +#include "wine/port.h" + +#include + +#ifdef HAVE_GCRYPT_H + +#include + +#include "ntstatus.h" +#define WIN32_NO_STATUS +#include "windef.h" +#include "winbase.h" +#include "winternl.h" +#include "bcrypt.h" + +#include "bcrypt_internal.h" + +#include "wine/debug.h" +#include "wine/heap.h" +#include "wine/unicode.h" + +WINE_DEFAULT_DEBUG_CHANNEL(bcrypt); +WINE_DECLARE_DEBUG_CHANNEL(winediag); + +static void *libgcrypt_handle; +#define MAKE_FUNCPTR(f) static typeof(f) * p##f +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); +#undef MAKE_FUNCPTR + +BOOL gcrypt_initialize(void) +{ + if (!(libgcrypt_handle = dlopen( SONAME_LIBGCRYPT, RTLD_NOW))) + { + ERR_(winediag)( "failed to load libgcrypt, no support for diffie hellman key exchange\n" ); + return FALSE; + } + +#define LOAD_FUNCPTR(f) \ + if (!(p##f = dlsym( libgcrypt_handle, #f))) \ + { \ + ERR( "failed to load %s\n", #f ); \ + goto fail; \ + } + + 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); +#undef LOAD_FUNCPTR + + return TRUE; + +fail: + dlclose( libgcrypt_handle); + libgcrypt_handle = NULL; + return FALSE; +} + + +static void gcrypt_uninitialize(void) +{ + if (libgcrypt_handle) + { + dlclose( libgcrypt_handle); + libgcrypt_handle = NULL; + } +} + +static NTSTATUS extract_result_into_secret(gcry_sexp_t result, struct secret *secret) +{ + 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; + } + + secret->data = RtlAllocateHeap(GetProcessHeap(), 0, size / 2); + memcpy(secret->data, tmp_buffer + size % 2, size / 2); + secret->len = size / 2; + +done: + free(tmp_buffer); + + pgcry_mpi_release(fullcoords); + pgcry_sexp_release(fragment); + + return status; +} + +/* 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 */ +static NTSTATUS CDECL key_compute_secret_ecc(unsigned char *privkey_in, struct key *pubkey_in, struct secret *secret) +{ + const char *pubkey_format; + DWORD key_size; + gcry_sexp_t pubkey = NULL; + gcry_sexp_t privkey = NULL; + gcry_sexp_t xchg_result = NULL; + gcry_error_t err; + NTSTATUS status = STATUS_SUCCESS; + + if (!libgcrypt_handle) + { + ERR("Secrets not supported without gcrypt\n"); + return STATUS_INTERNAL_ERROR; + } + + switch (pubkey_in->alg_id) + { + case ALG_ID_ECDH_P256: + pubkey_format = "NIST P-256"; + key_size = 32; + break; + default: + FIXME("Unsupported algorithm id: %u\n", pubkey_in->alg_id); + return STATUS_INTERNAL_ERROR; + } + + /* import public key - + copy public key into temporary buffer so we can prepend 0x04 (to indicate it is uncompressed) */ + { + UCHAR *public_key_raw = malloc((key_size * 2) + 1); + public_key_raw[0] = 0x04; + memcpy(public_key_raw + 1, pubkey_in->u.a.pubkey + sizeof(BCRYPT_ECCKEY_BLOB), key_size * 2); + + err = pgcry_sexp_build(&pubkey, NULL, + "(key-data(public-key(ecdh(curve %s)(q %b))))", + pubkey_format, + (key_size * 2) + 1, + public_key_raw); + + free(public_key_raw); + } + + if (err) + { + ERR("Failed to build gcrypt public key\n"); + goto done; + } + + /* import private key */ + /* extract private key from blob structure */ + err = pgcry_sexp_build(&privkey, NULL, + "(data(flags raw)(value %b))", + key_size, + privkey_in + sizeof(BCRYPT_ECCKEY_BLOB) + key_size * 2); + + if (err) + { + ERR("Failed to build gcrypt private key data\n"); + goto done; + } + + if ((err = pgcry_pk_encrypt(&xchg_result, privkey, pubkey))) + { + ERR("Failed to perform key exchange\n"); + goto done; + } + + status = extract_result_into_secret(xchg_result, secret); + if (status) + { + ERR("Failed to extract secret key\n"); + goto done; + } + + if (secret->len != key_size) + { + ERR("got secret size %u, expected %u\n", secret->len, key_size); + status = STATUS_INTERNAL_ERROR; + goto done; + } + + done: + pgcry_sexp_release(pubkey); + pgcry_sexp_release(privkey); + pgcry_sexp_release(xchg_result); + + if (status) + { + return status; + } + if (err) + { + ERR("Error = %s/%s\n", pgcry_strsource (err), pgcry_strerror (err)); + return STATUS_INTERNAL_ERROR; + } + return STATUS_SUCCESS; +} + +static struct key_funcs key_funcs = +{ + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + key_compute_secret_ecc +}; + +struct key_funcs * gcrypt_lib_init( DWORD reason ) +{ + switch (reason) + { + case DLL_PROCESS_ATTACH: + if (!gcrypt_initialize()) return NULL; + return &key_funcs; + case DLL_PROCESS_DETACH: + gcrypt_uninitialize(); + } + return NULL; +} + +#else +#include "ntstatus.h" +#define WIN32_NO_STATUS +#include "windef.h" +#include "winbase.h" +#include "winternl.h" + +struct key_funcs * gcrypt_lib_init( DWORD reason ) +{ + return NULL; +} +#endif diff --git a/dlls/bcrypt/gnutls.c b/dlls/bcrypt/gnutls.c index 9490ea8612a8..78d0f2d95966 100644 --- a/dlls/bcrypt/gnutls.c +++ b/dlls/bcrypt/gnutls.c @@ -1949,7 +1949,8 @@ static const struct key_funcs key_funcs = key_export_ecc, key_import_dsa_capi, key_import_ecc, - key_import_rsa + key_import_rsa, + NULL }; struct key_funcs * gnutls_lib_init( DWORD reason ) diff --git a/dlls/bcrypt/macos.c b/dlls/bcrypt/macos.c index 2a88aec8362c..3ee3515f9de2 100644 --- a/dlls/bcrypt/macos.c +++ b/dlls/bcrypt/macos.c @@ -299,7 +299,8 @@ static const struct key_funcs key_funcs = key_export_ecc, key_import_dsa_capi, key_import_ecc, - key_import_rsa + key_import_rsa, + NULL }; struct key_funcs * macos_lib_init( DWORD reason ) diff --git a/dlls/bcrypt/tests/bcrypt.c b/dlls/bcrypt/tests/bcrypt.c index 456727d04a96..6be406dee21f 100644 --- a/dlls/bcrypt/tests/bcrypt.c +++ b/dlls/bcrypt/tests/bcrypt.c @@ -2163,7 +2163,7 @@ static void test_ECDH(void) goto raw_secret_end; } - todo_wine ok(status == STATUS_SUCCESS, "got %08x\n", status); + ok(status == STATUS_SUCCESS, "got %08x\n", status); if (status != STATUS_SUCCESS) { diff --git a/dlls/bcrypt/unixlib.c b/dlls/bcrypt/unixlib.c index a158ec1630a6..a01e2ad02ed9 100644 --- a/dlls/bcrypt/unixlib.c +++ b/dlls/bcrypt/unixlib.c @@ -129,6 +129,12 @@ static void CDECL key_asymmetric_destroy( struct key *key ) FIXME( "not implemented\n" ); } +static NTSTATUS CDECL key_compute_secret_ecc (unsigned char *privkey_in, struct key *pubkey_in, struct secret *secret) +{ + FIXME( "not implemented\n" ); + return STATUS_NOT_IMPLEMENTED; +} + static struct key_funcs key_funcs = { key_set_property, @@ -148,13 +154,15 @@ static struct key_funcs key_funcs = key_export_dsa_capi, key_export_ecc, key_import_dsa_capi, - key_import_ecc + key_import_ecc, + key_compute_secret_ecc }; NTSTATUS CDECL __wine_init_unix_lib( HMODULE module, DWORD reason, const void *ptr_in, void *ptr_out ) { struct key_funcs *gnutls_funcs = gnutls_lib_init(reason); struct key_funcs *macos_funcs = macos_lib_init(reason); + struct key_funcs *gcrypt_funcs = gcrypt_lib_init(reason); if (reason == DLL_PROCESS_ATTACH) { @@ -162,7 +170,9 @@ NTSTATUS CDECL __wine_init_unix_lib( HMODULE module, DWORD reason, const void *p if (macos_funcs && macos_funcs->key_##name) \ key_funcs.key_##name = macos_funcs->key_##name; \ if (gnutls_funcs && gnutls_funcs->key_##name) \ - key_funcs.key_##name = gnutls_funcs->key_##name; + key_funcs.key_##name = gnutls_funcs->key_##name; \ + if (gcrypt_funcs && gcrypt_funcs->key_##name) \ + key_funcs.key_##name = gcrypt_funcs->key_##name; RESOLVE_FUNC(set_property) RESOLVE_FUNC(symmetric_init) @@ -182,6 +192,7 @@ NTSTATUS CDECL __wine_init_unix_lib( HMODULE module, DWORD reason, const void *p RESOLVE_FUNC(export_ecc) RESOLVE_FUNC(import_dsa_capi) RESOLVE_FUNC(import_ecc) + RESOLVE_FUNC(compute_secret_ecc) #undef RESOLVE_FUNC -- 2.29.2