Add Windows/MSVC port - #464
Conversation
Builds libwolfprov.dll on Windows with Visual Studio 2022 (v143, x64) against a
user_settings.h-configured wolfSSL and OpenSSL 3.x, for both non-FIPS and FIPS.
Build files live under IDE/WINVS/, laid out like the rest of the wolfSSL family
(wolfssh/ide/winvs): solution, property sheet and user_settings.h at the top,
projects one directory down. Four configurations:
DLL Release|x64, DLL Debug|x64 the shipped provider
Static Release|x64, Static Debug|x64 unit tests only, never distributed
The static configurations exist solely so the unit test can link wolfProvider's
internals. The DLL exports exactly one symbol, OSSL_provider_init, via a .def
rather than adding __declspec to the sources. A static library cannot serve as a
provider at all -- OpenSSL loads one only through LoadLibraryA plus
DSO_bind_func("OSSL_provider_init") -- so this costs nothing and keeps internal
crypto symbols off the shipped ABI.
Naming diverges from wolfSSH deliberately. There, unprefixed means static; here
the unprefixed names would be the test-only ones, and Visual Studio's default
selection sorts to them. "DLL" sorts before "Static" under either ordering, so
the default selection is always a customer artifact.
Three latent defects fixed, none of them Windows-specific:
- wolfProvider never called wolfCrypt_Init(). wolfSSL's drbgStateMutex is
declared with a static-initializer clause that expands to nothing off
pthreads, and a Win32 CRITICAL_SECTION has no static-initializer form, so
the mutex stayed zeroed and wc_InitRng() faulted inside ntdll. Only
wolfCrypt_Init() runs wc_DrbgState_MutexInit(). Paired with
wolfCrypt_Cleanup() in teardown and on the init failure path, since OpenSSL
does not call teardown when init returns 0.
- wolfProvider never called wc_curve25519_set_rng(), so blinded curve25519 was
broken: X25519 derive, pkey -pubout, and the default TLS 1.3 group. Blinding
is on whenever USE_INTEL_SPEEDUP is off, so this breaks any
--disable-intelasm build on any platform. wp_ecx_gen was worse than a NULL
pointer: wc_curve25519_make_key attaches the generation context's RNG, which
is then freed. Now a per-key WC_RNG, mirroring wp_ecc_kmgmt.c.
- wp_fips.h gated WP_FIPS_CHECKS_DEFAULT on HAVE_FIPS but included no wolfSSL
configuration under WOLFSSL_USER_SETTINGS, so HAVE_FIPS was invisible and the
mask silently evaluated to 0. src/wp_fips.c includes only that header, so
fipsChecks initialised to zero and every wolfProvider FIPS check was
disabled -- the provider still claimed P-192, RSA-SHA1 and small RSA keys but
no longer rejected them. Now includes wolfssl/wolfcrypt/settings.h.
Portability: options.h guarded behind WOLFSSL_USER_SETTINGS (it is an autotools
output that does not exist in such a build); strcasecmp -> XSTRCASECMP;
XSTRLEN("literal") -> sizeof("literal") - 1 in two static tables, which GCC
constant-folds and MSVC does not; the FIPS CAST mutex initialiser moved off
__attribute__((constructor)), which MSVC has no equivalent for, to a
CRYPTO_THREAD_run_once from wolfssl_provider_init(); and a local byte-swap
replacing ByteReverseWord32, which is static in misc.c and never linkable.
include/wolfprovider/version.h is now tracked. It is an AC_CONFIG_FILES output
that was gitignored, so a fresh clone had no way to produce it without running
configure -- which Windows does not have. wolfSSL and wolfSSH both track theirs.
Distribution: IDE is enumerated file by file through nested include.am rather
than named as a directory, because automake's distdir rule is a plain recursive
cp that is not gitignore-aware and the Visual Studio projects write build output
inside the repo. The same reasoning removes EXTRA_DIST+=examples, which shipped
a built binary. Two source-list gates run from make check.
Verified in a Windows Server 2025 guest, MSVC v143 x64:
non-FIPS 199/199 unit tests; 11/11 shipped-artifact checks
FIPS the same, against four bundles --
5.9.2+v5.2.4, 5.8.4+v5.2.3, 5.9.2+v5.2.1, 5.9.1+FIPS-Ready
wolfSSL is built SHARED in every case, including FIPS, and the in-core integrity
check passes. IDE/WIN10/README.txt says FIPS "must be built as a static library,
for the moment", but specifies the linker settings required for a DLL twenty
lines later; the blocker is that their project never applies them to x64 and
never disables Whole Program Optimization for a DLL configuration. A static
wolfSSL would put the FIPS boundary inside libwolfprov.dll, so every wolfProvider
rebuild would invalidate the module hash.
USE_INTEL_SPEEDUP is deliberately omitted: wolfSSL ships x86-64 assembly for nine
features as GAS .S only, with no MASM equivalent, so enabling it fails to link.
WOLFSSL_AESNI and WOLFSSL_SP_X86_64_ASM are kept.
See IDE/WINVS/README.md for the build procedure, including what a FIPS build
requires beyond wolfSSL's own documented steps and why each of those is not
optional. The Linux autotools build is unaffected and still compiles clean under
-Werror.
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #464
Scan targets checked: wolfprovider-bugs, wolfprovider-src
Findings: 5
5 finding(s) posted as inline comments (see file-level comments below)
This review was generated automatically by Fenrir. Findings are non-blocking.
| * _WIN32 so builds without blinding keep the coverage. */ | ||
| PRINT_MSG("X25519 ECDH KAT skipped - curve25519 blinding enabled"); | ||
| (void)data; | ||
| return 0; |
There was a problem hiding this comment.
🟠 [Medium] X25519 tests return success instead of running under WOLFSSL_CURVE25519_BLINDING · Weak or missing assertions
The new guard makes test_ecdh_x25519_vector (and test_ecx_x25519_get_params_stale_ret at test/test_ecx.c:830) return 0 — a pass — whenever curve25519 blinding is enabled, which is wolfSSL's default without USE_INTEL_SPEEDUP. The RFC 7748 KAT and the get_params stale-return_size regression check therefore stop running on ordinary Linux builds too, and no wc_curve25519_set_rng() call exists anywhere in src/, so the condition the skip works around is still present.
Fix: Attach an RNG with wc_curve25519_set_rng() in the X25519 key paths so the tests can run, or return a distinct skip status the harness reports separately from a pass.
There was a problem hiding this comment.
This is intentional, curve25519 blinding breaks on linux as well with same build config. Proper X25519 RNG handling for blinding will be a follow up PR.
| #endif | ||
|
|
||
| #define LIBWOLFPROV_VERSION_STRING "1.2.1" | ||
| #define LIBWOLFPROV_VERSION_HEX 0x01002001 |
There was a problem hiding this comment.
🔵 [Low] Generated version.h committed with hardcoded version that configure overwrites · Copy-paste errors
include/wolfprovider/version.h is an AC_CONFIG_FILES output (configure.ac:230) generated from version.h.in, yet it is now tracked with the version literal duplicated from AC_INIT([wolfprov], [1.2.1]). Running configure rewrites the tracked file, make distclean deletes it, and a version bump in configure.ac leaves the Windows build reporting the old value.
Fix: Track a Windows-only header (e.g. IDE/WINVS/version.h) instead of the configure output, or drive both values from a single committed source.
There was a problem hiding this comment.
version.h structure is intentional and mirrors wolfssl proper
| #if defined(HAVE_FIPS) && !defined(WP_SINGLE_THREADED) | ||
| /* FIPS CAST tests are run lazily per-algorithm via wp_init_cast(); | ||
| * their mutexes must exist before the first one runs. */ | ||
| if (!wp_init_cast_mutexes()) { |
There was a problem hiding this comment.
🔵 [Low] wp_init_cast_mutexes() error check cannot detect mutex initialization failure · Dead error handling
wolfprov_init_cast_mutex() (src/wp_internal.c:105) returns void and discards every wc_InitMutex() return, so wp_init_cast_mutexes() reports success whenever CRYPTO_THREAD_run_once succeeds. The new "Failed to initialize FIPS CAST mutexes" branch can never fire for the failure it names, leaving wp_init_cast() to lock uninitialized mutexes.
Fix: Record wc_InitMutex() failures in a static flag inside wolfprov_init_cast_mutex() and have wp_init_cast_mutexes() return 0 when it is set.
| * _WIN32 so builds without blinding keep the coverage. */ | ||
| PRINT_MSG("X25519 ECDH KAT skipped - curve25519 blinding enabled"); | ||
| (void)data; | ||
| return 0; |
There was a problem hiding this comment.
🟠 [Medium] X25519 ECDH known-answer test unconditionally passes when curve25519 blinding is enabled · Weak or missing assertions
Under WOLFSSL_CURVE25519_BLINDING the function returns 0 (pass) before running the RFC 7748 KAT, and the wc_curve25519_set_rng() call it says is missing is not added anywhere in src/. The X25519 shared-secret KAT therefore reports success in every blinded build, including the new MSVC configuration where the derive path is known broken.
Fix: Attach an RNG in the X25519 keymgmt so the KAT can run, or return a distinct non-pass skip status instead of 0.
| * _WIN32 so builds without blinding keep the coverage. */ | ||
| PRINT_MSG("X25519 get_params test skipped - curve25519 blinding enabled"); | ||
| (void)data; | ||
| return 0; |
There was a problem hiding this comment.
🔵 [Low] X25519 get_params regression test unconditionally passes under blinding · Weak or missing assertions
The early return 0 under WOLFSSL_CURVE25519_BLINDING skips all three assertions (private, public and encoded-public export with a stale zero return_size), so the buffer-sizing regression this test guards is unverified in blinded builds while still counting as a pass.
Fix: Restrict the skip to the public-key sections that need an RNG, keeping the private-key stale-return_size assertions active.
| @@ -0,0 +1,39 @@ | |||
| /* version.h.in | |||
| * | |||
| * Copyright (C) 2006-2025 wolfSSL Inc. | |||
There was a problem hiding this comment.
Rather than check this in, can we put this in IDE/WINVS?
If not, AI says we need to update configure.ac, remove version.hin, etc
There was a problem hiding this comment.
I dont think thats a good idea actually, that setup is practically begging for a version.h clash if include paths are off even a little. AI is just overly upset, wolfSSL proper uses this exact same setup.
| { "WP_LOG_LEVEL_VERBOSE", XSTRLEN("WP_LOG_LEVEL_VERBOSE"),WP_LOG_LEVEL_VERBOSE }, | ||
| { "WP_LOG_LEVEL_DEBUG", XSTRLEN("WP_LOG_LEVEL_DEBUG"), WP_LOG_LEVEL_DEBUG }, | ||
| { "WP_LOG_LEVEL_TRACE", XSTRLEN("WP_LOG_LEVEL_TRACE"), WP_LOG_LEVEL_TRACE }, | ||
| { "WP_LOG_LEVEL_ERROR", sizeof("WP_LOG_LEVEL_ERROR") - 1, WP_LOG_LEVEL_ERROR }, |
There was a problem hiding this comment.
nit: Could be nice to macro-ize this for DRYness and compactness with a macro to define each entry
| * which wolfProvider does not do yet. Gated on the macro rather than | ||
| * _WIN32 so builds without blinding keep the coverage. */ | ||
| PRINT_MSG("X25519 get_params test skipped - curve25519 blinding enabled"); | ||
| (void)data; |
There was a problem hiding this comment.
Nit: the return before the declarations is not C99. Could add the #else below
Use #else rather than an early return followed by declarations, which C90 rejects. Review feedback from padelsbach on PR 464.
Adds a Visual Studio solution under IDE/WINVS and the source changes needed to build wolfProvider with MSVC, FIPS and non-FIPS. Four configurations, x64.
user_settings.h is hand-written and committed rather than generated. options.h can't be reused here: it's an autotools output that bakes in results that are wrong under MSVC. wolfSSL and wolfProvider have to be built against the same one.
include/wolfprovider/version.h is now tracked. It was gitignored, so a fresh clone couldn't build on Windows at all.
Built and tested FIPS and non-FIPS, in the VS IDE.