- AES GCM protocol mismatch
- better, granular error handling
- UUID now uses crypto.randomUUID()
- added native mlock addon to improve security
- ZeroBuffer uses explicit_bzero now
- fixed imports

feat:
-  added unit tests
This commit is contained in:
Alpha Nerd 2026-03-04 11:30:44 +01:00
parent 0b09b9a9c3
commit c7601b2270
17 changed files with 12600 additions and 164 deletions

142
native/src/mlock.cc Normal file
View file

@ -0,0 +1,142 @@
/**
* Native mlock/munlock/secure-zero addon for nomyo-js
*
* Provides OS-level memory locking (mlock) and secure zeroing to prevent
* sensitive key material from being swapped to disk.
*
* Platforms:
* Linux/macOS: mlock + explicit_bzero (or memset with compiler barrier)
* Windows: VirtualLock + SecureZeroMemory
*
* Note: mlock may fail without CAP_IPC_LOCK capability (Linux) or elevated
* privileges (Windows). The JS layer treats locking as best-effort.
*/
#include <napi.h>
#include <cstring>
#include <cstdint>
#ifdef _WIN32
#include <windows.h>
#else
#include <sys/mman.h>
#include <unistd.h>
#endif
// ---------------------------------------------------------------------------
// Secure zeroing — must not be optimized away by the compiler
// ---------------------------------------------------------------------------
static void secure_zero(void* ptr, size_t len) {
#if defined(_WIN32)
SecureZeroMemory(ptr, len);
#elif defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__linux__) || defined(__APPLE__)
// explicit_bzero is available on Linux (glibc 2.25+), macOS 10.12+, BSDs.
// If unavailable, the volatile trick below still works.
explicit_bzero(ptr, len);
#else
volatile uint8_t* p = reinterpret_cast<volatile uint8_t*>(ptr);
for (size_t i = 0; i < len; ++i) {
p[i] = 0;
}
#endif
}
// ---------------------------------------------------------------------------
// mlockBuffer(buffer: Buffer) → boolean
// ---------------------------------------------------------------------------
Napi::Value MlockBuffer(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
if (info.Length() < 1 || !info[0].IsBuffer()) {
Napi::TypeError::New(env, "Expected a Buffer argument").ThrowAsJavaScriptException();
return env.Null();
}
auto buf = info[0].As<Napi::Buffer<uint8_t>>();
void* ptr = buf.Data();
size_t len = buf.Length();
if (len == 0) {
return Napi::Boolean::New(env, true);
}
#ifdef _WIN32
BOOL ok = VirtualLock(ptr, len);
return Napi::Boolean::New(env, ok != 0);
#else
int rc = mlock(ptr, len);
return Napi::Boolean::New(env, rc == 0);
#endif
}
// ---------------------------------------------------------------------------
// munlockBuffer(buffer: Buffer) → boolean
// ---------------------------------------------------------------------------
Napi::Value MunlockBuffer(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
if (info.Length() < 1 || !info[0].IsBuffer()) {
Napi::TypeError::New(env, "Expected a Buffer argument").ThrowAsJavaScriptException();
return env.Null();
}
auto buf = info[0].As<Napi::Buffer<uint8_t>>();
void* ptr = buf.Data();
size_t len = buf.Length();
if (len == 0) {
return Napi::Boolean::New(env, true);
}
#ifdef _WIN32
BOOL ok = VirtualUnlock(ptr, len);
return Napi::Boolean::New(env, ok != 0);
#else
int rc = munlock(ptr, len);
return Napi::Boolean::New(env, rc == 0);
#endif
}
// ---------------------------------------------------------------------------
// secureZeroBuffer(buffer: Buffer) → void
// ---------------------------------------------------------------------------
Napi::Value SecureZeroBuffer(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
if (info.Length() < 1 || !info[0].IsBuffer()) {
Napi::TypeError::New(env, "Expected a Buffer argument").ThrowAsJavaScriptException();
return env.Null();
}
auto buf = info[0].As<Napi::Buffer<uint8_t>>();
secure_zero(buf.Data(), buf.Length());
return env.Undefined();
}
// ---------------------------------------------------------------------------
// getPageSize() → number
// ---------------------------------------------------------------------------
Napi::Value GetPageSize(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
#ifdef _WIN32
SYSTEM_INFO si;
GetSystemInfo(&si);
return Napi::Number::New(env, static_cast<double>(si.dwPageSize));
#else
return Napi::Number::New(env, static_cast<double>(sysconf(_SC_PAGESIZE)));
#endif
}
// ---------------------------------------------------------------------------
// Module init
// ---------------------------------------------------------------------------
Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports.Set("mlockBuffer", Napi::Function::New(env, MlockBuffer));
exports.Set("munlockBuffer", Napi::Function::New(env, MunlockBuffer));
exports.Set("secureZeroBuffer", Napi::Function::New(env, SecureZeroBuffer));
exports.Set("getPageSize", Napi::Function::New(env, GetPageSize));
return exports;
}
NODE_API_MODULE(nomyo_native, Init)