Skip to content

policy: add WDAC integration on Windows

Last year, I spoke with @bmeck @mhdawson and @RafaelGSS about extending the chain of trust of node policy files into the OS. Work has been done on the OS side to better support language interpreters with code integrity, and now we're ready to integrate this work with NodeJS.

This is intended to be a draft PR to demonstrate what the proposed changes are and to facilitate discussion around OS code integrity integration on Windows. I will submit an issue so we can discuss this in further detail during the next security wg meeting.

Changes Added an alternate option to --policy-integrity so the integrity of the policy can be rooted in the OS.

The alternative option is --policy-signature which is a path to a PKCS7 detached signature of the policy file.

The policy signature is verified using the OS CI policy system. On Windows, this uses WDAC. On *nix this is stubbed out.

Added a query to WDAC to see if a policy file and signature are mandatory for Node to run.

Windows Code Integrity (WDAC) Official WDAC docs

WDAC is Window's code integrity enforcement system. Using WDAC, system administrators can define policy rules that dictate which executables can be run on a system where WDAC is enabled. One of the criteria they can define is a set of valid signing keys. EXEs and DLLs have signatures embedded into them (in headers and other "unused" blocks within the file) which are validated when loaded. Since interpreted languages don't use the standard EXE and DLL loading entry points, and the code which they interpret can be various file types, enforcement by the OS is difficult. There needs to be some cooperation between the OS and the language runtime.

Node policies seem like a natural point we can integrate with WDAC. In the current form, however, a Node policy manifest can be altered on disk. This can be mitigated with the --policy-integrity option. However, this can be bypassed if an attacker can modify the command parameters. We're proposing linking the chain of trust into something the OS trusts, rather (or in addition to) a command line parameter. To do this, the manifest is signed with a PKCS7 detached signature. This adds another layer of security, since now a trusted signing key would need to be compromised to use NodeJS on a locked-down system.

This can be done with signtool with signtool sign /p7 .\ /p7co 1.2.840.113549.1.7.2 /p7ce Pkcs7DetachedSignedData /fd sha256 /f key.pfx node_policy.json

Or OpenSSL with openssl smime -sign -binary -in policy.json -signer signer.pem -inkey signer.key -outform DER -out policy.json.p7s

This signature can then be passed in along with a Node policy.

node.exe --experimental-policy policy.json --policy-signature policy.json.p7s app.js

If the OS is configured to enforce code integrity for Node, Node will not execute unless both a policy and its signature are provided and pass WDAC's verification. From that point, integrity enforcement is delegated to Node's manifest/policy subsystem.

Some questions that I have:

  • I couldn't find where in the Node project calls to OS functions are located, so I put them alongside the other win32 methods in UV. Please let me know if there's a better place
  • I have tested this on Windows with a couple VMs on different OS versions. However, I don't know what the best course of action is for adding unit tests, since this is an OS integration feature. I'm open to any suggestions you have.

Merge request reports

Loading