Skip to content
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
13 changes: 13 additions & 0 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -1538,6 +1538,18 @@ Instructs Node.js to error prior to running any code if the policy does not have
the specified integrity. It expects a [Subresource Integrity][] string as a
parameter.

### `--policy-signature`

<!-- YAML
added: v22.0.0
-->

> Stability: 1 - Experimental

A path to a PKCS7 detached signature of the policy file. Node.js consults
the OS to determine if the signature is trusted. If the signature is not
trusted, Node.js will error

### `--preserve-symlinks`

<!-- YAML
Expand Down Expand Up @@ -2668,6 +2680,7 @@ one is included in the list below.
* `--openssl-shared-config`
* `--pending-deprecation`
* `--policy-integrity`
* `--policy-signature`
* `--preserve-symlinks-main`
* `--preserve-symlinks`
* `--prof-process`
Expand Down
9 changes: 9 additions & 0 deletions doc/api/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -2214,6 +2214,15 @@ documentation for [policy][] manifests for more information.
An attempt was made to load a policy manifest, but the manifest was unable to
be parsed. See the documentation for [policy][] manifests for more information.

<a id="ERR_MANIFEST_SYSTEM_CI_VIOLATION"></a>

### `ERR_MANIFEST_SYSTEM_CI_VIOLATION`

The manifest does not match the signature provided or the signature
does not conform to system code integrity policy.

See the documentation for [policy][] manifests for more information.

<a id="ERR_MANIFEST_TDZ"></a>

### `ERR_MANIFEST_TDZ`
Expand Down
10 changes: 9 additions & 1 deletion doc/api/permissions.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,22 @@ The policy manifest will be used to enforce constraints on code loaded by
Node.js.

To mitigate tampering with policy files on disk, an integrity for
the policy file itself may be provided via `--policy-integrity`.
the policy file itself may be provided in two ways.
One, via `--policy-integrity` using a subresource integrity string.
Or, via `--policy-signature` using a PKCS7 detached signature created with
a signing key trusted by system code integrity policy.

This allows running `node` and asserting the policy file contents
even if the file is changed on disk.

```bash
node --experimental-policy=policy.json --policy-integrity="sha384-SggXRQHwCG8g+DktYYzxkXRIkTiEYWBHqev0xnpCxYlqMBufKZHAHQM3/boDaI/0" app.js
```

```bash
node --experimental-policy=policy.json --policy-signature=policy.json.p7s app.js
```

#### Features

##### Error behavior
Expand Down
11 changes: 11 additions & 0 deletions lib/codeintegrity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use strict';

const {
isCodeIntegrityForcedByOS,
isFileTrustedBySystemCodeIntegrityPolicy,
} = internalBinding('code_integrity');

module.exports = {
isCodeIntegrityForcedByOS,
isFileTrustedBySystemCodeIntegrityPolicy,
};
3 changes: 3 additions & 0 deletions lib/internal/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -1588,6 +1588,9 @@ E('ERR_MANIFEST_INVALID_RESOURCE_FIELD',
E('ERR_MANIFEST_INVALID_SPECIFIER',
'Manifest resource %s has invalid dependency mapping %s',
TypeError);
E('ERR_MANIFEST_SYSTEM_CI_VIOLATION',
'The provided manifest does not pass system code integrity validation. "%s"',
Error);
E('ERR_MANIFEST_TDZ', 'Manifest initialization has not yet run', Error);
E('ERR_MANIFEST_UNKNOWN_ONERROR',
'Manifest specified unknown error behavior "%s".',
Expand Down
51 changes: 44 additions & 7 deletions lib/internal/process/pre_execution.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const {
const {
ERR_INVALID_THIS,
ERR_MANIFEST_ASSERT_INTEGRITY,
ERR_MANIFEST_SYSTEM_CI_VIOLATION,
ERR_NO_CRYPTO,
ERR_MISSING_OPTION,
ERR_ACCESS_DENIED,
Expand Down Expand Up @@ -642,21 +643,55 @@ function initializePermission() {
}

function readPolicyFromDisk() {
const ci = require('codeintegrity');
const experimentalPolicy = getOptionValue('--experimental-policy');
if (experimentalPolicy) {

const forceCI = ci.isCodeIntegrityForcedByOS();
if (forceCI && !experimentalPolicy) {
throw new ERR_MANIFEST_SYSTEM_CI_VIOLATION(
'Code integrity is forced by system policy. ' +
'A Policy manifest (--experimental-policy) and signature (--policy-signature) are required');
}

if (experimentalPolicy || forceCI) {
process.emitWarning('Policies are experimental.',
'ExperimentalWarning');
const { pathToFileURL, URL } = require('internal/url');
const { pathToFileURL, URL, fileURLToPath } = require('internal/url');
// URL here as it is slightly different parsing
// no bare specifiers for now
let manifestURL;
if (require('path').isAbsolute(experimentalPolicy)) {
manifestURL = pathToFileURL(experimentalPolicy);
} else {

function makeAbsoluteUrl(f) {
if (require('path').isAbsolute(f)) {
return pathToFileURL(f);
}

const cwdURL = pathToFileURL(process.cwd());
cwdURL.pathname += '/';
manifestURL = new URL(experimentalPolicy, cwdURL);
return new URL(f, cwdURL);
}

const manifestURL = makeAbsoluteUrl(experimentalPolicy);
const policySignature = getOptionValue('--policy-signature');
if (forceCI && !policySignature) {
throw new ERR_MANIFEST_SYSTEM_CI_VIOLATION(
'Code integrity is forced by system policy. ' +
'A Policy manifest (--experimental-policy) and signature (--policy-signature) are required');
}

if (policySignature) {
const policySignatureUrl = makeAbsoluteUrl(policySignature);

const isPolicyTrusted = ci.isFileTrustedBySystemCodeIntegrityPolicy(
fileURLToPath(manifestURL),
fileURLToPath(policySignatureUrl),
);

if (!isPolicyTrusted) {
throw new ERR_MANIFEST_SYSTEM_CI_VIOLATION(
'The --policy-signature provided is not valid or does not meet system code integrity requirements');
}
}

const fs = require('fs');
const src = fs.readFileSync(manifestURL, 'utf8');
const experimentalPolicyIntegrity = getOptionValue('--policy-integrity');
Expand Down Expand Up @@ -691,6 +726,7 @@ function readPolicyFromDisk() {
}
}


function initializeCJSLoader() {
const { initializeCJS } = require('internal/modules/cjs/loader');
initializeCJS();
Expand Down Expand Up @@ -782,4 +818,5 @@ module.exports = {
markBootstrapComplete,
loadPreloadModules,
initializeFrozenIntrinsics,
readPolicyFromDisk,
};
2 changes: 2 additions & 0 deletions node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
'src/node_blob.cc',
'src/node_buffer.cc',
'src/node_builtins.cc',
'src/node_code_integrity.cc',
'src/node_config.cc',
'src/node_constants.cc',
'src/node_contextify.cc',
Expand Down Expand Up @@ -217,6 +218,7 @@
'src/node_blob.h',
'src/node_buffer.h',
'src/node_builtins.h',
'src/node_code_integrity.h',
'src/node_constants.h',
'src/node_context_data.h',
'src/node_contextify.h',
Expand Down
1 change: 1 addition & 0 deletions src/node_binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
V(buffer) \
V(builtins) \
V(cares_wrap) \
V(code_integrity) \
V(config) \
V(constants) \
V(contextify) \
Expand Down
Loading