fix:
- 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:
parent
0b09b9a9c3
commit
c7601b2270
17 changed files with 12600 additions and 164 deletions
142
native/src/mlock.cc
Normal file
142
native/src/mlock.cc
Normal 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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue