Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

crypto: implement basic secure heap support #36779

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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`
Expand Down Expand Up @@ -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
Expand Down
15 changes: 15 additions & 0 deletions doc/api/crypto.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions doc/node.1
Original file line number Diff line number Diff line change
Expand Up @@ -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.
.
Expand Down
2 changes: 2 additions & 0 deletions lib/crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ const {
setDefaultEncoding,
setEngine,
lazyRequire,
secureHeapUsed,
} = require('internal/crypto/util');
const Certificate = require('internal/crypto/certificate');

Expand Down Expand Up @@ -230,6 +231,7 @@ module.exports = {
Sign,
Verify,
X509Certificate,
secureHeapUsed,
};

function setFipsDisabled() {
Expand Down
6 changes: 4 additions & 2 deletions lib/internal/crypto/keygen.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,15 @@ function generateKeyPairSync(type, options) {
}

function handleError(ret) {
if (ret === undefined)
if (ret == null)
return; // async

const [err, [publicKey, privateKey]] = ret;
const [err, keys] = ret;
if (err !== undefined)
throw err;

const [publicKey, privateKey] = keys;

// If no encoding was chosen, return key objects instead.
return {
publicKey: wrapKey(publicKey, PublicKeyObject),
Expand Down
16 changes: 16 additions & 0 deletions lib/internal/crypto/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const {
ArrayPrototypeIncludes,
ArrayPrototypePush,
FunctionPrototypeBind,
Number,
Promise,
StringPrototypeToLowerCase,
StringPrototypeToUpperCase,
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -402,4 +417,5 @@ module.exports = {
getStringOption,
getUsagesUnion,
getHashLength,
secureHeapUsed,
};
28 changes: 28 additions & 0 deletions src/crypto/crypto_util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ namespace node {

using v8::ArrayBuffer;
using v8::BackingStore;
using v8::BigInt;
using v8::Context;
using v8::Exception;
using v8::FunctionCallbackInfo;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand All @@ -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

Expand Down
24 changes: 24 additions & 0 deletions src/node_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

#include <errno.h>
#include <sstream>
#include <limits>
#include <algorithm>
#include <cstdlib> // strtoul, errno

using v8::Boolean;
Expand Down Expand Up @@ -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" &&
Expand Down Expand Up @@ -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 "
Expand Down
2 changes: 2 additions & 0 deletions src/node_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
72 changes: 72 additions & 0 deletions test/parallel/test-crypto-secure-heap.js
Original file line number Diff line number Diff line change
@@ -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);
}
10 changes: 8 additions & 2 deletions test/parallel/test-process-env-allowed-flags-are-documented.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down