mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00:00
crypto: implement basic secure heap support
Adds two new command line arguments: * `--secure-heap=n`, which causes node.js to initialize an openssl secure heap of `n` bytes on openssl initialization. * `--secure-heap-min=n`, which specifies the minimum allocation from the secure heap. * A new method `crypto.secureHeapUsed()` that returns details about the total and used secure heap allocation. The secure heap is an openssl feature that allows certain kinds of potentially sensitive information (such as private key BigNums) to be allocated from a dedicated memory area that is protected against pointer over- and underruns. The secure heap is a fixed size, so it's important that users pick a large enough size to cover the crypto operations they intend to utilize. The secure heap is disabled by default. Signed-off-by: James M Snell <jasnell@gmail.com> PR-URL: https://github.com/nodejs/node/pull/36779 Refs: https://github.com/nodejs/node/pull/36729 Reviewed-By: Tobias Nießen <tniessen@tnie.de>
This commit is contained in:
parent
b4378aa38a
commit
03c056401f
@ -848,6 +848,40 @@ Enables report to be generated on uncaught exceptions. Useful when inspecting
|
||||
the JavaScript stack in conjunction with native stack and other runtime
|
||||
environment data.
|
||||
|
||||
### `--secure-heap=n`
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
Initializes an OpenSSL secure heap of `n` bytes. When initialized, the
|
||||
secure heap is used for selected types of allocations within OpenSSL
|
||||
during key generation and other operations. This is useful, for instance,
|
||||
to prevent sensitive information from leaking due to pointer overruns
|
||||
or underruns.
|
||||
|
||||
The secure heap is a fixed size and cannot be resized at runtime so,
|
||||
if used, it is important to select a large enough heap to cover all
|
||||
application uses.
|
||||
|
||||
The heap size given must be a power of two. Any value less than 2
|
||||
will disable the secure heap.
|
||||
|
||||
The secure heap is disabled by default.
|
||||
|
||||
The secure heap is not available on Windows.
|
||||
|
||||
See [`CRYPTO_secure_malloc_init`][] for more details.
|
||||
|
||||
### `--secure-heap-min=n`
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
When using `--secure-heap`, the `--secure-heap-min` flag specifies the
|
||||
minimum allocation from the secure heap. The minimum value is `2`.
|
||||
The maximum value is the lesser of `--secure-heap` or `2147483647`.
|
||||
The value given must be a power of two.
|
||||
|
||||
### `--throw-deprecation`
|
||||
<!-- YAML
|
||||
added: v0.11.14
|
||||
@ -1361,6 +1395,8 @@ Node.js options that are allowed are:
|
||||
* `--report-signal`
|
||||
* `--report-uncaught-exception`
|
||||
* `--require`, `-r`
|
||||
* `--secure-heap-min`
|
||||
* `--secure-heap`
|
||||
* `--throw-deprecation`
|
||||
* `--title`
|
||||
* `--tls-cipher-list`
|
||||
@ -1659,6 +1695,7 @@ $ node --max-old-space-size=1536 index.js
|
||||
[`--openssl-config`]: #cli_openssl_config_file
|
||||
[`Atomics.wait()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/wait
|
||||
[`Buffer`]: buffer.md#buffer_class_buffer
|
||||
[`CRYPTO_secure_malloc_init`]: https://www.openssl.org/docs/man1.1.0/man3/CRYPTO_secure_malloc_init.html
|
||||
[`NODE_OPTIONS`]: #cli_node_options_options
|
||||
[`SlowBuffer`]: buffer.md#buffer_class_slowbuffer
|
||||
[`process.setUncaughtExceptionCaptureCallback()`]: process.md#process_process_setuncaughtexceptioncapturecallback_fn
|
||||
|
@ -3545,6 +3545,21 @@ const key2 = crypto.scryptSync('password', 'salt', 64, { N: 1024 });
|
||||
console.log(key2.toString('hex')); // '3745e48...aa39b34'
|
||||
```
|
||||
|
||||
### `crypto.secureHeapUsed()`
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* Returns: {Object}
|
||||
* `total` {number} The total allocated secure heap size as specified
|
||||
using the `--secure-heap=n` command-line flag.
|
||||
* `min` {number} The minimum allocation from the secure heap as
|
||||
specified using the `--secure-heap-min` command-line flag.
|
||||
* `used` {number} The total number of bytes currently allocated from
|
||||
the secure heap.
|
||||
* `utilization` {number} The calculated ratio of `used` to `total`
|
||||
allocated bytes.
|
||||
|
||||
### `crypto.setEngine(engine[, flags])`
|
||||
<!-- YAML
|
||||
added: v0.11.11
|
||||
|
@ -359,6 +359,13 @@ Enables
|
||||
to be generated on un-caught exceptions. Useful when inspecting JavaScript
|
||||
stack in conjunction with native stack and other runtime environment data.
|
||||
.
|
||||
.It Fl -secure-heap Ns = Ns Ar n
|
||||
Specify the size of the OpenSSL secure heap. Any value less than 2 disables
|
||||
the secure heap. The default is 0. The value must be a power of two.
|
||||
.
|
||||
.It Fl -secure-heap-min Ns = Ns Ar n
|
||||
Specify the minimum allocation from the OpenSSL secure heap. The default is 2. The value must be a power of two.
|
||||
.
|
||||
.It Fl -throw-deprecation
|
||||
Throw errors for deprecations.
|
||||
.
|
||||
|
@ -118,6 +118,7 @@ const {
|
||||
setDefaultEncoding,
|
||||
setEngine,
|
||||
lazyRequire,
|
||||
secureHeapUsed,
|
||||
} = require('internal/crypto/util');
|
||||
const Certificate = require('internal/crypto/certificate');
|
||||
|
||||
@ -230,6 +231,7 @@ module.exports = {
|
||||
Sign,
|
||||
Verify,
|
||||
X509Certificate,
|
||||
secureHeapUsed,
|
||||
};
|
||||
|
||||
function setFipsDisabled() {
|
||||
|
@ -4,6 +4,7 @@ const {
|
||||
ArrayPrototypeIncludes,
|
||||
ArrayPrototypePush,
|
||||
FunctionPrototypeBind,
|
||||
Number,
|
||||
Promise,
|
||||
StringPrototypeToLowerCase,
|
||||
StringPrototypeToUpperCase,
|
||||
@ -15,8 +16,11 @@ const {
|
||||
getCurves: _getCurves,
|
||||
getHashes: _getHashes,
|
||||
setEngine: _setEngine,
|
||||
secureHeapUsed: _secureHeapUsed,
|
||||
} = internalBinding('crypto');
|
||||
|
||||
const { getOptionValue } = require('internal/options');
|
||||
|
||||
const {
|
||||
crypto: {
|
||||
ENGINE_METHOD_ALL
|
||||
@ -371,6 +375,17 @@ function validateKeyOps(keyOps, usagesSet) {
|
||||
}
|
||||
}
|
||||
|
||||
function secureHeapUsed() {
|
||||
const val = _secureHeapUsed();
|
||||
if (val === undefined)
|
||||
return { total: 0, used: 0, utilization: 0, min: 0 };
|
||||
const used = Number(_secureHeapUsed());
|
||||
const total = Number(getOptionValue('--secure-heap'));
|
||||
const min = Number(getOptionValue('--secure-heap-min'));
|
||||
const utilization = used / total;
|
||||
return { total, used, utilization, min };
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getArrayBufferOrView,
|
||||
getCiphers,
|
||||
@ -402,4 +417,5 @@ module.exports = {
|
||||
getStringOption,
|
||||
getUsagesUnion,
|
||||
getHashLength,
|
||||
secureHeapUsed,
|
||||
};
|
||||
|
@ -18,6 +18,7 @@ namespace node {
|
||||
|
||||
using v8::ArrayBuffer;
|
||||
using v8::BackingStore;
|
||||
using v8::BigInt;
|
||||
using v8::Context;
|
||||
using v8::Exception;
|
||||
using v8::FunctionCallbackInfo;
|
||||
@ -113,6 +114,25 @@ void InitCryptoOnce() {
|
||||
settings = nullptr;
|
||||
#endif
|
||||
|
||||
#ifndef _WIN32
|
||||
if (per_process::cli_options->secure_heap != 0) {
|
||||
switch (CRYPTO_secure_malloc_init(
|
||||
per_process::cli_options->secure_heap,
|
||||
static_cast<int>(per_process::cli_options->secure_heap_min))) {
|
||||
case 0:
|
||||
fprintf(stderr, "Unable to initialize openssl secure heap.\n");
|
||||
break;
|
||||
case 2:
|
||||
// Not a fatal error but worthy of a warning.
|
||||
fprintf(stderr, "Unable to memory map openssl secure heap.\n");
|
||||
break;
|
||||
case 1:
|
||||
// OK!
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef NODE_FIPS_MODE
|
||||
/* Override FIPS settings in cnf file, if needed. */
|
||||
unsigned long err = 0; // NOLINT(runtime/int)
|
||||
@ -617,6 +637,13 @@ void SecureBuffer(const FunctionCallbackInfo<Value>& args) {
|
||||
Local<ArrayBuffer> buffer = ArrayBuffer::New(env->isolate(), store);
|
||||
args.GetReturnValue().Set(Uint8Array::New(buffer, 0, len));
|
||||
}
|
||||
|
||||
void SecureHeapUsed(const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
if (CRYPTO_secure_malloc_initialized())
|
||||
args.GetReturnValue().Set(
|
||||
BigInt::New(env->isolate(), CRYPTO_secure_used()));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace Util {
|
||||
@ -634,6 +661,7 @@ void Initialize(Environment* env, Local<Object> target) {
|
||||
NODE_DEFINE_CONSTANT(target, kCryptoJobSync);
|
||||
|
||||
env->SetMethod(target, "secureBuffer", SecureBuffer);
|
||||
env->SetMethod(target, "secureHeapUsed", SecureHeapUsed);
|
||||
}
|
||||
} // namespace Util
|
||||
|
||||
|
@ -7,6 +7,8 @@
|
||||
|
||||
#include <errno.h>
|
||||
#include <sstream>
|
||||
#include <limits>
|
||||
#include <algorithm>
|
||||
#include <cstdlib> // strtoul, errno
|
||||
|
||||
using v8::Boolean;
|
||||
@ -64,6 +66,20 @@ void PerProcessOptions::CheckOptions(std::vector<std::string>* errors) {
|
||||
errors->push_back("either --use-openssl-ca or --use-bundled-ca can be "
|
||||
"used, not both");
|
||||
}
|
||||
|
||||
// Any value less than 2 disables use of the secure heap.
|
||||
if (secure_heap >= 2) {
|
||||
if ((secure_heap & (secure_heap - 1)) != 0)
|
||||
errors->push_back("--secure-heap must be a power of 2");
|
||||
secure_heap_min =
|
||||
std::min({
|
||||
secure_heap,
|
||||
secure_heap_min,
|
||||
static_cast<int64_t>(std::numeric_limits<int>::max())});
|
||||
secure_heap_min = std::max(static_cast<int64_t>(2), secure_heap_min);
|
||||
if ((secure_heap_min & (secure_heap_min - 1)) != 0)
|
||||
errors->push_back("--secure-heap-min must be a power of 2");
|
||||
}
|
||||
#endif
|
||||
if (use_largepages != "off" &&
|
||||
use_largepages != "on" &&
|
||||
@ -760,6 +776,14 @@ PerProcessOptionsParser::PerProcessOptionsParser(
|
||||
&PerProcessOptions::force_fips_crypto,
|
||||
kAllowedInEnvironment);
|
||||
#endif
|
||||
AddOption("--secure-heap",
|
||||
"total size of the OpenSSL secure heap",
|
||||
&PerProcessOptions::secure_heap,
|
||||
kAllowedInEnvironment);
|
||||
AddOption("--secure-heap-min",
|
||||
"minimum allocation size from the OpenSSL secure heap",
|
||||
&PerProcessOptions::secure_heap_min,
|
||||
kAllowedInEnvironment);
|
||||
#endif
|
||||
AddOption("--use-largepages",
|
||||
"Map the Node.js static code to large pages. Options are "
|
||||
|
@ -236,6 +236,8 @@ class PerProcessOptions : public Options {
|
||||
#if HAVE_OPENSSL
|
||||
std::string openssl_config;
|
||||
std::string tls_cipher_list = DEFAULT_CIPHER_LIST_CORE;
|
||||
int64_t secure_heap = 0;
|
||||
int64_t secure_heap_min = 2;
|
||||
#ifdef NODE_OPENSSL_CERT_STORE
|
||||
bool ssl_openssl_cert_store = true;
|
||||
#else
|
||||
|
72
test/parallel/test-crypto-secure-heap.js
Normal file
72
test/parallel/test-crypto-secure-heap.js
Normal file
@ -0,0 +1,72 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasCrypto)
|
||||
common.skip('missing crypto');
|
||||
|
||||
if (common.isWindows)
|
||||
common.skip('Not supported on Windows');
|
||||
|
||||
const assert = require('assert');
|
||||
const { fork } = require('child_process');
|
||||
const fixtures = require('../common/fixtures');
|
||||
const {
|
||||
secureHeapUsed,
|
||||
createDiffieHellman,
|
||||
} = require('crypto');
|
||||
|
||||
if (process.argv[2] === 'child') {
|
||||
|
||||
const a = secureHeapUsed();
|
||||
|
||||
assert(a);
|
||||
assert.strictEqual(typeof a, 'object');
|
||||
assert.strictEqual(a.total, 65536);
|
||||
assert.strictEqual(a.min, 4);
|
||||
assert.strictEqual(a.used, 0);
|
||||
|
||||
{
|
||||
const dh1 = createDiffieHellman(common.hasFipsCrypto ? 1024 : 256);
|
||||
const p1 = dh1.getPrime('buffer');
|
||||
const dh2 = createDiffieHellman(p1, 'buffer');
|
||||
const key1 = dh1.generateKeys();
|
||||
const key2 = dh2.generateKeys('hex');
|
||||
dh1.computeSecret(key2, 'hex', 'base64');
|
||||
dh2.computeSecret(key1, 'latin1', 'buffer');
|
||||
|
||||
const b = secureHeapUsed();
|
||||
assert(b);
|
||||
assert.strictEqual(typeof b, 'object');
|
||||
assert.strictEqual(b.total, 65536);
|
||||
assert.strictEqual(b.min, 4);
|
||||
// The amount used can vary on a number of factors
|
||||
assert(b.used > 0);
|
||||
assert(b.utilization > 0.0);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const child = fork(
|
||||
process.argv[1],
|
||||
['child'],
|
||||
{ execArgv: ['--secure-heap=65536', '--secure-heap-min=4'] });
|
||||
|
||||
child.on('exit', common.mustCall((code) => {
|
||||
assert.strictEqual(code, 0);
|
||||
}));
|
||||
|
||||
{
|
||||
const child = fork(fixtures.path('a.js'), {
|
||||
execArgv: ['--secure-heap=3', '--secure-heap-min=3'],
|
||||
stdio: 'pipe'
|
||||
});
|
||||
let res = '';
|
||||
child.on('exit', common.mustCall((code) => {
|
||||
assert.notStrictEqual(code, 0);
|
||||
assert.match(res, /--secure-heap must be a power of 2/);
|
||||
assert.match(res, /--secure-heap-min must be a power of 2/);
|
||||
}));
|
||||
child.stderr.setEncoding('utf8');
|
||||
child.stderr.on('data', (chunk) => res += chunk);
|
||||
}
|
@ -43,8 +43,14 @@ for (const line of [...nodeOptionsLines, ...v8OptionsLines]) {
|
||||
const conditionalOpts = [
|
||||
{ include: common.hasCrypto,
|
||||
filter: (opt) => {
|
||||
return ['--openssl-config', '--tls-cipher-list', '--use-bundled-ca',
|
||||
'--use-openssl-ca' ].includes(opt);
|
||||
return [
|
||||
'--openssl-config',
|
||||
'--tls-cipher-list',
|
||||
'--use-bundled-ca',
|
||||
'--use-openssl-ca',
|
||||
'--secure-heap',
|
||||
'--secure-heap-min',
|
||||
].includes(opt);
|
||||
} },
|
||||
{
|
||||
// We are using openssl_is_fips from the configuration because it could be
|
||||
|
Loading…
Reference in New Issue
Block a user