diff --git a/build.mcpp b/build.mcpp index 8a8fa9e..3f89376 100644 --- a/build.mcpp +++ b/build.mcpp @@ -116,6 +116,7 @@ int main() { mcpp::rerun_if_changed("port/ntdll.def"); mcpp::rerun_if_changed("port/shell32.def"); mcpp::rerun_if_changed("port/synchronization.def"); + mcpp::rerun_if_changed("port/bcrypt.def"); if (host_is_windows()) return 0; @@ -137,7 +138,8 @@ int main() { if (out.empty() || root.empty()) return 0; const std::string tool = dlltool(); - for (auto name : { "kernel32", "ntdll", "shell32", "synchronization" }) { + for (auto name : { "kernel32", "ntdll", "shell32", "synchronization", + "bcrypt" }) { const auto def = std::format("{}/port/{}.def", root, name); const auto lib = std::format("{}/lib{}.a", out, name); // ⚠️ `-m i386:x86-64` is stated. See port/README.md: the 32-bit ABI diff --git a/mcpp.toml b/mcpp.toml index d555db1..2a6812b 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,7 +1,7 @@ [package] namespace = "mcpplibs" name = "openkal-windows" -version = "0.1.4" +version = "0.1.5" description = "An implementation of openkal for Windows, written on the Win32 interfaces and the object manager beneath them, using no C runtime symbol." license = "Apache-2.0" @@ -18,7 +18,7 @@ authors = ["mcpplibs"] repo = "https://github.com/mcpplibs/openkal-windows" [dependencies] -openkal = "0.6.0" +openkal = "0.7.0" # The package contributes definitions and no modules. The interface it # implements is declared by the specification package, which this package @@ -49,7 +49,7 @@ openkal = "0.6.0" # predicate the second one linked with none of these libraries and failed on # `GetStdHandle`. [target.'cfg(all(windows, not(env = "msvc")))'.build] -ldflags = ["-lntdll", "-lsynchronization", "-lshell32", "-lkernel32"] +ldflags = ["-lntdll", "-lsynchronization", "-lshell32", "-lkernel32", "-lbcrypt"] # Exceptions and run-time type information, on the one ABI where their absence # is asserted. @@ -67,7 +67,8 @@ ldflags = ["-lntdll", "-lsynchronization", "-lshell32", "-lkernel32"] # nothing to say about how a program that has a runtime unwinds. cxxflags = ["-fno-exceptions", "-fno-rtti"] -# The other ABI names them in src/win.cpp instead, where its compilers record +# The other ABI names them in the sources instead --- src/win.cpp for the four +# above and src/random.cpp for bcrypt --- where its compilers record # the requirement in the object they produce. A library named on the link line # there would have to be named again by every program that links this package; # a library named in the object travels with it. diff --git a/port/bcrypt.def b/port/bcrypt.def new file mode 100644 index 0000000..3f52272 --- /dev/null +++ b/port/bcrypt.def @@ -0,0 +1,18 @@ +; The system's random generator, and nothing else from this library. +; +; ⭐ BCryptGenRandom WITH BCRYPT_USE_SYSTEM_PREFERRED_RNG, WHICH TAKES A NULL +; HANDLE. The ordinary use of this call opens an algorithm handle first, and a +; handle would give openkal.random an initialisation order it does not have. +; +; ⚠️ `ProcessPrng` (bcryptprimitives.dll) was tried first: fewer moving parts, +; and what Chromium and Rust use. It fails on a WINDOWS HOST, where these +; generated libraries are not built at all --- `build.mcpp` returns immediately +; there, the vendor's own being present and complete --- and the Windows SDK +; ships no import library for `bcryptprimitives.dll`. The name below is listed +; by `bcrypt.lib`, so it resolves through the vendor's libraries and through +; this file alike. +; +; Generated into an import library by build.mcpp; see port/README.md. +LIBRARY bcrypt.dll +EXPORTS +BCryptGenRandom diff --git a/src/random.cpp b/src/random.cpp new file mode 100644 index 0000000..3882060 --- /dev/null +++ b/src/random.cpp @@ -0,0 +1,88 @@ +// openkal.random on Windows --- BCryptGenRandom with the system-preferred RNG. +// +// ⭐ THE FLAG IS WHAT REMOVES THE HANDLE, AND THE HANDLE WAS THE WHOLE OBJECTION. +// +// `BCryptGenRandom` ordinarily takes an algorithm handle, which this backend +// would have to open on every call or cache — and caching one gives this +// interface an initialisation order it does not otherwise have. +// `BCRYPT_USE_SYSTEM_PREFERRED_RNG` says "use the system's own generator" and +// takes a null handle, which is exactly what is wanted here. +// +// ⚠️ AND NOT `ProcessPrng`, WHICH WAS TRIED FIRST AND FAILS ON A WINDOWS HOST. +// +// lld-link: error: undefined symbol: __declspec(dllimport) ProcessPrng +// +// The cross build linked and the native one did not, and the asymmetry is in +// where the import library comes from. Cross-compiling, this package generates +// its own from `port/*.def` — a list of names, so a name it lists is a name it +// has. On a Windows host `build.mcpp` returns immediately and the VENDOR's +// import libraries are used, because they are present and complete; and the +// Windows SDK ships `bcrypt.lib` but no import library for +// `bcryptprimitives.dll`. `ProcessPrng` is documented and has no `.lib`. +// +// ⭐ A backend that links on one host and not another is not a backend. The +// name below is exported by `bcrypt.dll` and listed by `bcrypt.lib`, so it +// resolves through the vendor's libraries and through this package's generated +// one alike. +// +// ⚠️ THE FIRST DIAGNOSIS OF THIS WAS WRONG AND IS RECORDED SO IT IS NOT REPEATED: +// it read `/usr/x86_64-w64-mingw32/lib` and concluded from mingw's contents. +// mingw is not part of this ecosystem — it is the very thing `build.mcpp` +// exists to stop depending on, as the note at the top of that file says. What +// this backend links against is either the vendor's SDK or this package's own +// generated libraries, and never a third party's. +// The library this one interface lives in, named the way src/win.cpp names the +// other four and for the reason given there. It is here rather than beside +// them because a directive travels in the object that carries it, and the +// object a linker pulls in for `kal_random_fill` is this one. +// +// ⚠️ TWO SPELLINGS IN THE MANIFEST WERE TRIED BEFORE THIS AND BOTH WERE PUSHED, +// while the mechanism was already established in this package and named four +// lines below the block being edited. `ldflags` reaches the command line +// verbatim, and the two toolchains that `env = "msvc"` selects reject each +// other's word for a library: +// +// LINK.EXE -lbcrypt → LNK4044: unrecognized option '/lbcrypt' +// clang++ bcrypt.lib → error: no such file or directory +// +// `cfg()` cannot separate them — its four keys name the target, and which +// compiler drives the link is not a property of the target. +#if defined(_MSC_VER) +#pragma comment(lib, "bcrypt.lib") +#endif + +#include "win.h" +#include + +extern "C" { + +// From bcrypt.dll. Declared here rather than by including for the +// reason the whole of win.h exists: this package names what it uses. +__declspec(dllimport) long __stdcall BCryptGenRandom(void* hAlgorithm, + unsigned char* pbBuffer, + unsigned long cbBuffer, + unsigned long dwFlags); + +int kal_random_fill(void* out, kal_uintptr len) { + if (len == 0) return kal_ok; + if (out == nullptr) return kal_err_invalid; + + // BCRYPT_USE_SYSTEM_PREFERRED_RNG. Spelled as its value for the reason the + // rest of this file spells things: the header it lives in is the system's. + constexpr unsigned long use_system_preferred_rng = 0x00000002ul; + + // ⚠️ NO LOOP. This call fills the whole buffer or fails; unlike a read it + // has no short return, so a loop would always run once and would suggest a + // partial state this interface does not have. + const long st = BCryptGenRandom(nullptr, static_cast(out), + static_cast(len), + use_system_preferred_rng); + return st == 0 /* STATUS_SUCCESS */ ? kal_ok : kal_err_io; +} + +// Neither blocking nor hardware. The system's generator is seeded before a +// process runs, so there is no wait to report; and whether the seed came from a +// hardware source is not something this backend can observe. +const kal_uintptr kal_random_props = 0; + +} // extern "C"