-
Notifications
You must be signed in to change notification settings - Fork 30k
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
https: disallow boolean types for key
and cert
options
#14807
Conversation
When using https.createServer, passing boolean values for `key` and `cert` properties in the options object parameter doesn't throw an error an could lead to potential issues if they're accidentally passed. This PR aims to throw a reasonable error if a boolean was passed to either of those properties. Fixes: nodejs#12802
lib/https.js
Outdated
throw new Error('"options.key" must not be a boolean'); | ||
|
||
if (opts && typeof opts.cert === 'boolean') | ||
throw new Error('"options.cert" must not be a boolean'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By the way, we try to migrate to a new error system that allows for more flexibility in the future, it would be cool if you think you could take a look at using that.
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | ||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR | ||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE | ||
// USE OR OTHER DEALINGS IN THE SOFTWARE. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don’t need the copyright header for new files.
Hello @yjimk and welcome. Thank you very much for you contribution 🥇.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @yjimk! Welcome and thanks for the PR! I left a few comments with requested changes. Thanks again!
lib/https.js
Outdated
@@ -40,6 +40,12 @@ function Server(opts, requestListener) { | |||
} | |||
opts = util._extend({}, opts); | |||
|
|||
if (opts && typeof opts.key === 'boolean') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Line 41 guarantee that opts
is an object so there is no need to check opts
here or in line 46.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think checking against all invalid values is the way to go. I think it should confirm it is a valid value and reject everything else.
https.createServer({ | ||
key: true, | ||
cert: fs.readFileSync(`${common.fixturesDir}/keys/agent1-cert.pem`) | ||
}), /"options\.key" must not be a boolean/); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These regular expressions would be better if they matched the entire message (that is, if they started with ^
and ended with $
).
lib/https.js
Outdated
@@ -40,6 +40,12 @@ function Server(opts, requestListener) { | |||
} | |||
opts = util._extend({}, opts); | |||
|
|||
if (opts && typeof opts.key === 'boolean') | |||
throw new Error('"options.key" must not be a boolean'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This and the other Error
should be a TypeError
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with some cleanup.
cert: fs.readFileSync(`${common.fixturesDir}/keys/agent1-cert.pem`) | ||
})); | ||
|
||
assert.throws(() => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can reduce code duplication a lot here. All of these assert.throws()
calls are essentially the same, with only the value of key
, cert
, and the regular expression changing. You can do something like this instead:
const key = fs.readFileSync(`${common.fixturesDir}/keys/agent1-key.pem`);
const cert = fs.readFileSync(`${common.fixturesDir}/keys/agent1-cert.pem`);
const invalidKeyRE = /^"options\.key" must not be a boolean$/;
const invalidCertRE = /^"options\.cert" must not be a boolean$/;
[
[true, cert, invalidKeyRE],
// Fill in other cases here
].forEach((params) => {
assert.throws(() => {
https.createServer({
key: params[0],
cert: params[1]
});
}, params[2]);
});
I agree with @Trott, better verify for validity instead of checking single invalid values like booleans (e.g. whitelist over blacklist approach). Also, I think the checks should also be done on |
Thank you all for the super helpful and detailed feedback! I'll make those changes and see if I can get closer. |
Taking on board the review from the initial PR, multiple changes have been made. - Type checking now a whitelist as opposed to only checking for a boolean - Type checking moved to the underlying `_tls_common` , `tls` and `https` modules now affected - Arrays are now iterated using `map` instead of `for` for affected sections -Testing added for the `tls` module Fixes: nodejs#12802
I believe I might have addressed all of the comments left with my latest commit. The new code is a bit of a departure from the old, so I hope it is OK and would appreciate any tips. Particularly worried about using that function There are a few other things in that file that I noticed are a different style compared to other parts of the code, such as usage of Thanks again for your great feedback and comments, it's been a fantastic learning experience. |
lib/_tls_common.js
Outdated
@@ -52,6 +54,13 @@ function SecureContext(secureProtocol, secureOptions, context) { | |||
if (secureOptions) this.context.setOptions(secureOptions); | |||
} | |||
|
|||
function validateKeyCert(value, type) { | |||
if (typeof value !== 'string' && !(value instanceof Buffer)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is too strict, and instanceof Buffer
is not a foolproof way to check for Buffers. Can you use ArrayBuffer.isView(value)
instead and adjust the message accordingly (Buffers are just special Uint8Arrays, but all Uint8Arrays and other ArrayBufferViews should work, I think.).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, brilliant! Replacing the two check works perfectly. I'll change this over and add some additional tests.
Regarding the new error message, it is currently ['string', 'buffer']
and I'm thinking it should change to ['string', 'ArrayBuffer']
with perhaps string
=> String
. Would you have an opinion on how that should look?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don’t think we can list ArrayBuffer
, as those are not ArrayBufferViews themselves.
Maybe ['string', 'Buffer', 'TypedArray', 'DataView']
? That would match the message here:
Line 231 in cde272a
'Invalid dictionary: it should be a Buffer, TypedArray, or DataView'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perfect! Thank you, I'll create a commit for this change.
const https = require('https'); | ||
const fs = require('fs'); | ||
|
||
const keyBuff = fs.readFileSync(`${common.fixturesDir}/keys/agent1-key.pem`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's been some recent work to simplify fixtures access...
example...
const fixtures = require('../common/fixtures');
const keyBuff = fixtures.readKey('agent1-key.pem');
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah OK, cool. Thanks, I'll change that over too. The original test file was copied over from another and amended, hence the previous inclusion of the license comments and usage of the fs.readFileSync
method of including the pem files. Is there are issue/task that you're aware of to update the older tests to use the newer methods? Something like that I feel like would be a useful task for me to take up in future.
Since this PR relates to #12802 which is marked as "good first contribution", I'm going to ask a question (as I also intended on grabbing this). If this is inappropriate, delete my question and I'll take it to IRC or SO From e92e64a extended commit message:
What's the benefit of changing the loop type? I can't seem to find related review comment that (probably) requested the change. |
RE: @fl0w, mostly due to personal preference toward This tweet (although it mentions https://twitter.com/_ericelliott/status/710571765844615168 I'd love to hear the ...sorry |
Additional changes in line with PR review - Loosen type checking for buffers using the ArrayBuffer method - Require pem files using updated fixture access method - Add tests for ArrayBuffer and DataView types Fixes: nodejs#12802
I've changed the type checking for buffer, but my knowledge of ArrayBuffers and DataViews is pretty limited. |
lib/_tls_common.js
Outdated
options.ca.map((ca) => { | ||
validateKeyCert(ca, 'ca'); | ||
c.context.addCACert(ca); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This map
can be changed to a forEach
because you're not using the return value.
lib/_tls_common.js
Outdated
options.cert.map((cert) => { | ||
validateKeyCert(cert, 'cert'); | ||
c.context.setCert(cert); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here.
lib/_tls_common.js
Outdated
options.key.map((k) => { | ||
validateKeyCert(k.pem || k, 'key'); | ||
c.context.setKey(k.pem || k, k.passphrase || options.passphrase); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Few nits, overall LGTM.
Sorry, just saw your comment after review. I personally see no benefit of using Node.js uses |
No problem, I'll change it over to use the forEach method. :) |
- Change iteration method from map -> forEach Fixes: nodejs#12802
It does not appear to have changed by much. |
Landed in a7dccd0 |
PR-URL: #14807 Fixes: #12802 Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Roman Reiss <[email protected]> Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]>
PR-URL: nodejs/node#14807 Fixes: nodejs/node#12802 Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Roman Reiss <[email protected]> Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]>
PR-URL: nodejs/node#14807 Fixes: nodejs/node#12802 Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Roman Reiss <[email protected]> Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]>
* **async_hooks** * Older experimental `async_hooks` APIs have been removed [[`d731369b1d`](d731369b1d)] **(SEMVER-MAJOR)** [#14414](#14414) * **Errors** * Multiple built in modules have been migrated to use static error codes * **Domains** * The long deprecated `.dispose()` method has been removed [[`602fd36d95`](602fd36d95)] **(SEMVER-MAJOR)** [#15412](#15412) * **File system** * `fs.ReadStream` and `fs.WriteStream` now make use of `.destroy()` [[`e5c290bed9`](e5c290bed9)] **(SEMVER-MAJOR)** [#15407](#15407) * `fs` callbacks are now invoked with an undefined `this` context [[`2249234fee`](2249234fee)] **(SEMVER-MAJOR)** [#14645](#14645) * **HTTP** * Socket timeout is set when the socket connects [[`10be20a0e8`](10be20a0e8)] **(SEMVER-MAJOR)** [#8895](#8895) * A bug causing the request `error` event to fire twice has been fixed [[`620ba41694`](620ba41694)] **(SEMVER-MAJOR)** [#14659](#14659) * The `pipe` method on `OutgoingMessage` has been disabled [[`156549d8ff`](156549d8ff)] **(SEMVER-MAJOR)** [#14358](#14358) * **HTTP/2** * The `--expose-http2` command-line argument is no longer required [[`f55ee6e24a`](f55ee6e24a)] **(SEMVER-MAJOR)** [#15535](#15535) * **Internationalization** * The `Intl.v8BreakIterator` class has been removed [[`668ad44922`](668ad44922)] **(SEMVER-MAJOR)** [#15238](#15238) * **OS** * `os.EOL` is now read-only [[`f6caeb9526`](f6caeb9526)] **(SEMVER-MAJOR)** [#14622](#14622) * **Process** * It is now possible to pass additional flags to `dlopen` [[`5f22375922`](5f22375922)] **(SEMVER-MAJOR)** [#12794](#12794) * **Timers** * Using a timeout duration larger than 32-bits will now emit a warning [[`ce3586da31`](ce3586da31)] **(SEMVER-MAJOR)** [#15627](#15627) * **TLS** * `parseCertString` has been deprecated [[`468110b327`](468110b327)] **(SEMVER-MAJOR)** [#14249](#14249) * Type-checking for `key`, `cert`, and `ca` options has been added [[`a7dccd040d`](a7dccd040d)] **(SEMVER-MAJOR)** [#14807](#14807)
* **async_hooks** * Older experimental `async_hooks` APIs have been removed [[`d731369b1d`](d731369b1d)] **(SEMVER-MAJOR)** [#14414](#14414) * **Errors** * Multiple built in modules have been migrated to use static error codes * **Domains** * The long deprecated `.dispose()` method has been removed [[`602fd36d95`](602fd36d95)] **(SEMVER-MAJOR)** [#15412](#15412) * **File system** * `fs.ReadStream` and `fs.WriteStream` now make use of `.destroy()` [[`e5c290bed9`](e5c290bed9)] **(SEMVER-MAJOR)** [#15407](#15407) * `fs` callbacks are now invoked with an undefined `this` context [[`2249234fee`](2249234fee)] **(SEMVER-MAJOR)** [#14645](#14645) * **HTTP** * Socket timeout is set when the socket connects [[`10be20a0e8`](10be20a0e8)] **(SEMVER-MAJOR)** [#8895](#8895) * A bug causing the request `error` event to fire twice has been fixed [[`620ba41694`](620ba41694)] **(SEMVER-MAJOR)** [#14659](#14659) * The `pipe` method on `OutgoingMessage` has been disabled [[`156549d8ff`](156549d8ff)] **(SEMVER-MAJOR)** [#14358](#14358) * **HTTP/2** * The `--expose-http2` command-line argument is no longer required [[`f55ee6e24a`](f55ee6e24a)] **(SEMVER-MAJOR)** [#15535](#15535) * **Internationalization** * The `Intl.v8BreakIterator` class has been removed [[`668ad44922`](668ad44922)] **(SEMVER-MAJOR)** [#15238](#15238) * **OS** * `os.EOL` is now read-only [[`f6caeb9526`](f6caeb9526)] **(SEMVER-MAJOR)** [#14622](#14622) * **Process** * It is now possible to pass additional flags to `dlopen` [[`5f22375922`](5f22375922)] **(SEMVER-MAJOR)** [#12794](#12794) * **Timers** * Using a timeout duration larger than 32-bits will now emit a warning [[`ce3586da31`](ce3586da31)] **(SEMVER-MAJOR)** [#15627](#15627) * **TLS** * `parseCertString` has been deprecated [[`468110b327`](468110b327)] **(SEMVER-MAJOR)** [#14249](#14249) * Type-checking for `key`, `cert`, and `ca` options has been added [[`a7dccd040d`](a7dccd040d)] **(SEMVER-MAJOR)** [#14807](#14807)
* **async_hooks** * Older experimental `async_hooks` APIs have been removed [[`d731369b1d`](d731369b1d)] **(SEMVER-MAJOR)** [#14414](#14414) * **Errors** * Multiple built in modules have been migrated to use static error codes * **Domains** * The long deprecated `.dispose()` method has been removed [[`602fd36d95`](602fd36d95)] **(SEMVER-MAJOR)** [#15412](#15412) * **File system** * `fs.ReadStream` and `fs.WriteStream` now make use of `.destroy()` [[`e5c290bed9`](e5c290bed9)] **(SEMVER-MAJOR)** [#15407](#15407) * `fs` callbacks are now invoked with an undefined `this` context [[`2249234fee`](2249234fee)] **(SEMVER-MAJOR)** [#14645](#14645) * **HTTP** * Socket timeout is set when the socket connects [[`10be20a0e8`](10be20a0e8)] **(SEMVER-MAJOR)** [#8895](#8895) * A bug causing the request `error` event to fire twice has been fixed [[`620ba41694`](620ba41694)] **(SEMVER-MAJOR)** [#14659](#14659) * The `pipe` method on `OutgoingMessage` has been disabled [[`156549d8ff`](156549d8ff)] **(SEMVER-MAJOR)** [#14358](#14358) * **HTTP/2** * The `--expose-http2` command-line argument is no longer required [[`f55ee6e24a`](f55ee6e24a)] **(SEMVER-MAJOR)** [#15535](#15535) * **Internationalization** * The `Intl.v8BreakIterator` class has been removed [[`668ad44922`](668ad44922)] **(SEMVER-MAJOR)** [#15238](#15238) * **OS** * `os.EOL` is now read-only [[`f6caeb9526`](f6caeb9526)] **(SEMVER-MAJOR)** [#14622](#14622) * **Process** * It is now possible to pass additional flags to `dlopen` [[`5f22375922`](5f22375922)] **(SEMVER-MAJOR)** [#12794](#12794) * **Timers** * Using a timeout duration larger than 32-bits will now emit a warning [[`ce3586da31`](ce3586da31)] **(SEMVER-MAJOR)** [#15627](#15627) * **TLS** * `parseCertString` has been deprecated [[`468110b327`](468110b327)] **(SEMVER-MAJOR)** [#14249](#14249) * Type-checking for `key`, `cert`, and `ca` options has been added [[`a7dccd040d`](a7dccd040d)] **(SEMVER-MAJOR)** [#14807](#14807)
* **async_hooks** * Older experimental `async_hooks` APIs have been removed [[`d731369b1d`](d731369b1d)] **(SEMVER-MAJOR)** [#14414](#14414) * **Errors** * Multiple built in modules have been migrated to use static error codes * **Domains** * The long deprecated `.dispose()` method has been removed [[`602fd36d95`](602fd36d95)] **(SEMVER-MAJOR)** [#15412](#15412) * **File system** * `fs.ReadStream` and `fs.WriteStream` now make use of `.destroy()` [[`e5c290bed9`](e5c290bed9)] **(SEMVER-MAJOR)** [#15407](#15407) * `fs` callbacks are now invoked with an undefined `this` context [[`2249234fee`](2249234fee)] **(SEMVER-MAJOR)** [#14645](#14645) * **HTTP** * Socket timeout is set when the socket connects [[`10be20a0e8`](10be20a0e8)] **(SEMVER-MAJOR)** [#8895](#8895) * A bug causing the request `error` event to fire twice has been fixed [[`620ba41694`](620ba41694)] **(SEMVER-MAJOR)** [#14659](#14659) * The `pipe` method on `OutgoingMessage` has been disabled [[`156549d8ff`](156549d8ff)] **(SEMVER-MAJOR)** [#14358](#14358) * **HTTP/2** * The `--expose-http2` command-line argument is no longer required [[`f55ee6e24a`](f55ee6e24a)] **(SEMVER-MAJOR)** [#15535](#15535) * **Internationalization** * The `Intl.v8BreakIterator` class has been removed [[`668ad44922`](668ad44922)] **(SEMVER-MAJOR)** [#15238](#15238) * **OS** * `os.EOL` is now read-only [[`f6caeb9526`](f6caeb9526)] **(SEMVER-MAJOR)** [#14622](#14622) * **Process** * It is now possible to pass additional flags to `dlopen` [[`5f22375922`](5f22375922)] **(SEMVER-MAJOR)** [#12794](#12794) * **Timers** * Using a timeout duration larger than 32-bits will now emit a warning [[`ce3586da31`](ce3586da31)] **(SEMVER-MAJOR)** [#15627](#15627) * **TLS** * `parseCertString` has been deprecated [[`468110b327`](468110b327)] **(SEMVER-MAJOR)** [#14249](#14249) * Type-checking for `key`, `cert`, and `ca` options has been added [[`a7dccd040d`](a7dccd040d)] **(SEMVER-MAJOR)** [#14807](#14807)
This is my first PR on the Node repository, your feedback is greatly appreciated.
When using https.createServer, passing boolean values for
key
andcert
properties in the options object parameter doesn't throw an error an could lead to potential issues if they're accidentally passed.This PR aims to throw a reasonable error if a boolean was passed to either of those properties.
Fixes: #12802
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passesAffected core subsystem(s)
https