-
Notifications
You must be signed in to change notification settings - Fork 29.6k
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: Expose the public key of a certificate & cert sha256 #17690
crypto: Expose the public key of a certificate & cert sha256 #17690
Conversation
src/node_crypto.cc
Outdated
@@ -1795,6 +1795,25 @@ static bool SafeX509ExtPrint(BIO* out, X509_EXTENSION* ext) { | |||
} | |||
|
|||
|
|||
static void addFingerprintDigest(unsigned char *md, |
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.
Nit: could you make the *
left-leaning?
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. Whops :) Thanks!
I've amended the commit
128749e
to
7ea2aba
Compare
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.
Calling i2d_RSA_PUBKEY()
is not very cheap, calling it twice even less so. Since you make it the default, there may be performance implications.
You might be able to claw back some of the overhead with i2d_RSA_PUBKEY_bio()
+ an in-memory BIO so it's only decoded once, but you'd have to measure that.
src/node_crypto.cc
Outdated
@@ -1795,6 +1795,25 @@ static bool SafeX509ExtPrint(BIO* out, X509_EXTENSION* ext) { | |||
} | |||
|
|||
|
|||
static void addFingerprintDigest(unsigned char* md, |
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.
Style: should be e.g.AddFingerprintDigest()
. md
can be const unsigned char*
.
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.
thanks!
src/node_crypto.cc
Outdated
fingerprint[(3*(md_size-1))+2] = '\0'; | ||
} else { | ||
fingerprint[0] = '\0'; | ||
} |
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.
Since you're here: just fingerprint[3*i] = '\0';
?
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'm not sure I understand.
You mean instead of fingerprint[0]
? Not sure how that makes it any more readable?
Note that 3*(md_size-1)+2 !== 3*i
src/node_crypto.cc
Outdated
|
||
int size = i2d_RSA_PUBKEY(rsa, nullptr); | ||
Local<Object> pubbuff = Buffer::New(env, size).ToLocalChecked(); | ||
unsigned char *pubserialized = reinterpret_cast<unsigned char *>( |
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.
Style: unsigned char*
(star leans left) on LHS and RHS and line break after the =
.
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.
done
src/node_crypto.cc
Outdated
@@ -1879,6 +1898,13 @@ static Local<Object> X509ToObject(Environment* env, X509* cert) { | |||
String::NewFromUtf8(env->isolate(), mem->data, | |||
String::kNormalString, mem->length)); | |||
USE(BIO_reset(bio)); | |||
|
|||
int size = i2d_RSA_PUBKEY(rsa, nullptr); |
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.
Can you CHECK_GE(size, 0)
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.
done
src/node_crypto.cc
Outdated
info->Set(env->fingerprint_string(), | ||
addFingerprintDigest(md, md_size, fingerprint); | ||
info->Set(env->fingerprint_string(), | ||
OneByteString(env->isolate(), fingerprint)); |
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.
Style: can you line up the arguments?
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.
done
doc/api/https.md
Outdated
|
||
const options = { | ||
checkServerIdentity: function(host, cert) { | ||
/* Make sure the certificate is issued to the host we are connected to */ |
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.
Can you use //
comments 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.
Done
doc/api/https.md
Outdated
} while (cert.fingerprint256 !== lastprint256); | ||
|
||
}, | ||
hostname: 'github.com', |
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'd put these before checkServerIdentity
, they get kind of obscured now.
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.
done
doc/api/https.md
Outdated
const pubkey256 = 'pL1+qb9HTMRZJmuC/bB/ZI9d302BYrrqiVuRyW+DGrU='; | ||
hash = crypto.createHash('sha256'); | ||
hash.update(cert.pubkey); | ||
if (hash.digest('base64') !== pubkey256) { |
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.
For legibility/succinctness/DRY, maybe put this in a separate function:
function sha256(s) {
return crypto.createHash('sha256').update(s).digest('base64');
}
And then you can write:
if (pubkey256 !== sha256(cert.pubkey)) {
And below:
console.log('\tPublic key ping-sha256:', sha256(cert.pubkey)); // also note the , instead of +
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.
Done, good idea.
src/node_crypto.cc
Outdated
unsigned char md[EVP_MAX_MD_SIZE]; | ||
unsigned int md_size; | ||
char fingerprint[EVP_MAX_MD_SIZE * 3]; |
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.
Since you're here, can you change that to EVP_MAX_MD_SIZE * 3 + 1
? There is a (hypothetical) write-after-end in case md_size == EVP_MAX_MD_SIZE
. (Hypothetical because that can only happen with SHA-512.)
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.
nice
src/node_crypto.cc
Outdated
@@ -1795,6 +1795,25 @@ static bool SafeX509ExtPrint(BIO* out, X509_EXTENSION* ext) { | |||
} | |||
|
|||
|
|||
static void addFingerprintDigest(unsigned char* md, | |||
unsigned int md_size, | |||
char* fingerprint) { |
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.
If you change this to char (*fingerprint)[3 * EVP_MAX_MD_SIZE + 1]
, then people can never inadvertently pass a buffer that's too small and the compiler is more likely to warn you when you try to write past the end. You index into it with (*fingerprint)[3*i] = ...
.
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.
Nice. Good catch
@Hannes-Magnusson-CK Can you address the comments by @bnoordhuis ? Thanks! |
Whooops. I lost track of this and missed the comments. Thanks for the review @bnoordhuis & @jasnell ! I'll try to address the comments by the end of week. |
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 believe I've addressed all the feedback.
Thank you @bnoordhuis so much for thorough review!
As for the performance of i2d_RSA_PUBKEY() I haven't been able to reproduce a performance issue there that isn't drowned out by the actual tls handshake itself.
doc/api/https.md
Outdated
|
||
const options = { | ||
checkServerIdentity: function(host, cert) { | ||
/* Make sure the certificate is issued to the host we are connected to */ |
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.
Done
doc/api/https.md
Outdated
const pubkey256 = 'pL1+qb9HTMRZJmuC/bB/ZI9d302BYrrqiVuRyW+DGrU='; | ||
hash = crypto.createHash('sha256'); | ||
hash.update(cert.pubkey); | ||
if (hash.digest('base64') !== pubkey256) { |
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.
Done, good idea.
doc/api/https.md
Outdated
} while (cert.fingerprint256 !== lastprint256); | ||
|
||
}, | ||
hostname: 'github.com', |
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.
done
src/node_crypto.cc
Outdated
@@ -1795,6 +1795,25 @@ static bool SafeX509ExtPrint(BIO* out, X509_EXTENSION* ext) { | |||
} | |||
|
|||
|
|||
static void addFingerprintDigest(unsigned char* md, |
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.
thanks!
src/node_crypto.cc
Outdated
@@ -1795,6 +1795,25 @@ static bool SafeX509ExtPrint(BIO* out, X509_EXTENSION* ext) { | |||
} | |||
|
|||
|
|||
static void addFingerprintDigest(unsigned char* md, | |||
unsigned int md_size, | |||
char* fingerprint) { |
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.
Nice. Good catch
src/node_crypto.cc
Outdated
|
||
int size = i2d_RSA_PUBKEY(rsa, nullptr); | ||
Local<Object> pubbuff = Buffer::New(env, size).ToLocalChecked(); | ||
unsigned char *pubserialized = reinterpret_cast<unsigned char *>( |
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.
done
src/node_crypto.cc
Outdated
unsigned char md[EVP_MAX_MD_SIZE]; | ||
unsigned int md_size; | ||
char fingerprint[EVP_MAX_MD_SIZE * 3]; |
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.
nice
src/node_crypto.cc
Outdated
info->Set(env->fingerprint_string(), | ||
addFingerprintDigest(md, md_size, fingerprint); | ||
info->Set(env->fingerprint_string(), | ||
OneByteString(env->isolate(), fingerprint)); |
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.
done
const fixtures = require('../common/fixtures'); | ||
if (!common.hasCrypto) { | ||
common.skip('missing crypto'); | ||
return; |
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.
done
':4C:A4:00:53:93:A9:66:07:A7:BC:13:32' | ||
); | ||
|
||
/* SHA256 fingerprint of the public key */ |
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.
Refactored it into sha256 function as you pointed out earlier
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.
Thanks, LGTM with some closing comments.
doc/api/https.md
Outdated
* Print the certificate and public key fingerprints of all certs in the | ||
* chain. Its common to pin the public key of the issuer on the public | ||
* internet, while pinning the public key of the service in sensitive | ||
* environments. |
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.
Can you use //
comments 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.
done
doc/api/https.md
Outdated
*/ | ||
do { | ||
console.log('Subject Common Name: ' + cert.subject.CN); | ||
console.log('\tCertificate SHA256 fingerprint: ' + cert.fingerprint256); |
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.
,
, not +
, and then you don't need the blank after the semicolon. Ditto three lines below.
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.
magic =)
done
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 also fixed the '\t' to 2spaces has the ci linter complained about tab being used in the output text
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.
Thanks for the thorough review !
doc/api/https.md
Outdated
* Print the certificate and public key fingerprints of all certs in the | ||
* chain. Its common to pin the public key of the issuer on the public | ||
* internet, while pinning the public key of the service in sensitive | ||
* environments. |
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.
done
doc/api/https.md
Outdated
*/ | ||
do { | ||
console.log('Subject Common Name: ' + cert.subject.CN); | ||
console.log('\tCertificate SHA256 fingerprint: ' + cert.fingerprint256); |
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.
magic =)
done
doc/api/https.md
Outdated
*/ | ||
do { | ||
console.log('Subject Common Name: ' + cert.subject.CN); | ||
console.log('\tCertificate SHA256 fingerprint: ' + cert.fingerprint256); |
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 also fixed the '\t' to 2spaces has the ci linter complained about tab being used in the output text
The two CI failures are known flakes. This should be good to go. |
@Hannes-Magnusson-CK this needs a rebase. |
7afb2cb
to
6ff9195
Compare
Rebased on current master, fixed the conflict, and squashed couple of commits |
@Hannes-Magnusson-CK I am very sorry but this needs another rebase :/ |
6ff9195
to
3afabc9
Compare
Simple rebase this time, done :) |
Hm, it seems like there was a older conflicting PR #16402. That one just landed and the current code has to be rebased. @Hannes-Magnusson-CK I have the feeling this PR is now obsolete but I might be wrong. If so: I am sorry that this was not detected earlier. Otherwise it would be great if you could check what is necessary and what not! Thanks for being so patient and for sticking to it! That is much appreciated. |
PR-URL: nodejs#17690 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]>
Should this be backported to |
it looks like 7876aeb already made itself into a release |
The other 3 commits don't seem to be included in that branch. |
Notable Changes: * crypto: - add cert.fingerprint256 as SHA256 fingerprint (Hannes Magnusson) #17690 * lib: - v8_prof_processor works again 🎉 (Anna Henningsen) #19059 * loader: - --inspect-brk now works properly for esmodules (Gus Caplan) #18949 * src: - handle exceptions in env-\>SetImmediates (James M Snell) #18297 - make process.dlopen() load well-known symbol (Ben Noordhuis) #18934 * trace_events: - add file pattern cli option (Andreas Madsen) #18480 PR-URL: #19181
Notable Changes: * crypto: - add cert.fingerprint256 as SHA256 fingerprint (Hannes Magnusson) #17690 * lib: - v8_prof_processor works again 🎉 (Anna Henningsen) #19059 * loader: - --inspect-brk now works properly for esmodules (Gus Caplan) #18949 * src: - handle exceptions in env-\>SetImmediates (James M Snell) #18297 - make process.dlopen() load well-known symbol (Ben Noordhuis) #18934 * trace_events: - add file pattern cli option (Andreas Madsen) #18480 PR-URL: #19181
Notable Changes: * crypto: - add cert.fingerprint256 as SHA256 fingerprint (Hannes Magnusson) #17690 * http2: - Fixed issues with aborted connections in the HTTP/2 implementation (Anna Henningsen) #18987 #19002 * loader: - --inspect-brk now works properly for esmodules (Gus Caplan) #18949 * src: - make process.dlopen() load well-known symbol (Ben Noordhuis) #18934 * trace_events: - add file pattern cli option (Andreas Madsen) #18480 * Added new collaborators: - Chen Gang (MoonBall) https://github.com/MoonBall PR-URL: #19181
Notable Changes: * crypto: - add cert.fingerprint256 as SHA256 fingerprint (Hannes Magnusson) #17690 * http2: - Fixed issues with aborted connections in the HTTP/2 implementation (Anna Henningsen) #18987 #19002 * loader: - --inspect-brk now works properly for esmodules (Gus Caplan) #18949 * src: - make process.dlopen() load well-known symbol (Ben Noordhuis) #18934 * trace_events: - add file pattern cli option (Andreas Madsen) #18480 * Added new collaborators: - Chen Gang (MoonBall) https://github.com/MoonBall PR-URL: #19181
@Hannes-Magnusson-CK no need to make a proposal for the release, what would really help would be making sure every commit from this PR made it on to the staging branch |
Looks like |
To clarify. This PR has not landed in it's entirety on v9.x. Could someone please open up a backport PR |
There was just one trivial conflict to solve, no need for a backport |
PR-URL: #17690 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]>
PR-URL: #17690 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]>
Include example on how to pin certificate and/or public key PR-URL: #17690 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]>
Notable changes: * cluster: - Add support for `NODE_OPTIONS="--inspect"` (Sameer Srivastava) #19165 * crypto: - Expose the public key of a certificate (Hannes Magnusson) #17690 * n-api: - Add `napi_fatal_exception` to trigger an `uncaughtException` in JavaScript (Mathias Buus) #19337 * path: - Fix regression in `posix.normalize` (Michaël Zasso) #19520 * stream: - Improve stream creation performance (Brian White) #19401 * Added new collaborators - [BethGriggs](https://github.com/BethGriggs) Beth Griggs
This is a security release. All Node.js users should consult the security release summary at: https://nodejs.org/en/blog/vulnerability/march-2018-security-releases/ for details on patched vulnerabilities. Fixes for the following CVEs are included in this release: * CVE-2018-7158 * CVE-2018-7159 * CVE-2018-7160 Notable changes: * Upgrade to OpenSSL 1.0.2o: Does not contain any security fixes that are known to impact Node.js. * **Fix for inspector DNS rebinding vulnerability (CVE-2018-7160)**: A malicious website could use a DNS rebinding attack to trick a web browser to bypass same-origin-policy checks and allow HTTP connections to localhost or to hosts on the local network, potentially to an open inspector port as a debugger, therefore gaining full code execution access. The inspector now only allows connections that have a browser `Host` value of `localhost` or `localhost6`. * **Fix for `'path'` module regular expression denial of service (CVE-2018-7158)**: A regular expression used for parsing POSIX an Windows paths could be used to cause a denial of service if an attacker were able to have a specially crafted path string passed through one of the impacted `'path'` module functions. * **Reject spaces in HTTP `Content-Length` header values (CVE-2018-7159)**: The Node.js HTTP parser allowed for spaces inside `Content-Length` header values. Such values now lead to rejected connections in the same way as non-numeric values. * **Update root certificates**: 5 additional root certificates have been added to the Node.js binary and 30 have been removed. * cluster: - Add support for `NODE_OPTIONS="--inspect"` (Sameer Srivastava) #19165 * crypto: - Expose the public key of a certificate (Hannes Magnusson) #17690 * n-api: - Add `napi_fatal_exception` to trigger an `uncaughtException` in JavaScript (Mathias Buus) #19337 * path: - Fix regression in `posix.normalize` (Michaël Zasso) #19520 * stream: - Improve stream creation performance (Brian White) #19401 * Added new collaborators - [BethGriggs](https://github.com/BethGriggs) Beth Griggs PR-URL: https://github.com/nodejs-private/node-private/pull/111
PR-URL: nodejs#17690 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]>
PR-URL: nodejs#17690 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]>
PR-URL: nodejs#17690 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]>
Include example on how to pin certificate and/or public key PR-URL: nodejs#17690 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]>
Notable Changes: * crypto: - add cert.fingerprint256 as SHA256 fingerprint (Hannes Magnusson) nodejs#17690 * http2: - Fixed issues with aborted connections in the HTTP/2 implementation (Anna Henningsen) nodejs#18987 nodejs#19002 * loader: - --inspect-brk now works properly for esmodules (Gus Caplan) nodejs#18949 * src: - make process.dlopen() load well-known symbol (Ben Noordhuis) nodejs#18934 * trace_events: - add file pattern cli option (Andreas Madsen) nodejs#18480 * Added new collaborators: - Chen Gang (MoonBall) https://github.com/MoonBall PR-URL: nodejs#19181
is this something we would want to backport to v8.x? |
Expose the raw public key of the certificate. This is needed for applications to be able to pin the public key rather then the exact certificate. This also makes it a lot easier to implement HPKP, but to be able to do proper HPKP we need to have access the the issuer certificate too, so we are now passing the "detailed" certificate to
checkServerIdentity
.The certificate object contains the SHA1 fingerprint of the certificate. That is getting a little date so I've added the SHA256 as
cert.fingerprint256
.Also added docs on how to do cert pinning and pubkey pinning.
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passesAffected core subsystem(s)