diff options
-rw-r--r-- | doc/api/tls.md | 35 | ||||
-rw-r--r-- | lib/_tls_wrap.js | 10 | ||||
-rw-r--r-- | src/node_crypto.cc | 48 | ||||
-rw-r--r-- | src/node_crypto.h | 2 | ||||
-rw-r--r-- | test/parallel/test-tls-finished.js | 66 |
5 files changed, 161 insertions, 0 deletions
diff --git a/doc/api/tls.md b/doc/api/tls.md index 0cfab387f2..d3f3fdadff 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -583,6 +583,23 @@ if called on a server socket. The supported types are `'DH'` and `'ECDH'`. The For Example: `{ type: 'ECDH', name: 'prime256v1', size: 256 }` +### tlsSocket.getFinished() +<!-- YAML +added: REPLACEME +--> + +* Returns: {Buffer|undefined} The latest `Finished` message that has been +sent to the socket as part of a SSL/TLS handshake, or `undefined` if +no `Finished` message has been sent yet. + +As the `Finished` messages are message digests of the complete handshake +(with a total of 192 bits for TLS 1.0 and more for SSL 3.0), they can +be used for external authentication procedures when the authentication +provided by SSL/TLS is not desired or is not enough. + +Corresponds to the `SSL_get_finished` routine in OpenSSL and may be used +to implement the `tls-unique` channel binding from [RFC 5929][]. + ### tlsSocket.getPeerCertificate([detailed]) <!-- YAML added: v0.11.4 @@ -628,6 +645,23 @@ For example: If the peer does not provide a certificate, an empty object will be returned. +### tlsSocket.getPeerFinished() +<!-- YAML +added: REPLACEME +--> + +* Returns: {Buffer|undefined} The latest `Finished` message that is expected +or has actually been received from the socket as part of a SSL/TLS handshake, +or `undefined` if there is no `Finished` message so far. + +As the `Finished` messages are message digests of the complete handshake +(with a total of 192 bits for TLS 1.0 and more for SSL 3.0), they can +be used for external authentication procedures when the authentication +provided by SSL/TLS is not desired or is not enough. + +Corresponds to the `SSL_get_peer_finished` routine in OpenSSL and may be used +to implement the `tls-unique` channel binding from [RFC 5929][]. + ### tlsSocket.getProtocol() <!-- YAML added: v5.7.0 @@ -1368,3 +1402,4 @@ where `secure_socket` has the same API as `pair.cleartext`. [specific attacks affecting larger AES key sizes]: https://www.schneier.com/blog/archives/2009/07/another_new_aes.html [tls.Server]: #tls_class_tls_server [`dns.lookup()`]: dns.html#dns_dns_lookup_hostname_options_callback +[RFC 5929]: https://tools.ietf.org/html/rfc5929 diff --git a/lib/_tls_wrap.js b/lib/_tls_wrap.js index 79646ba3d2..8ac497902e 100644 --- a/lib/_tls_wrap.js +++ b/lib/_tls_wrap.js @@ -692,6 +692,16 @@ TLSSocket.prototype.getPeerCertificate = function(detailed) { return null; }; +TLSSocket.prototype.getFinished = function() { + if (this._handle) + return this._handle.getFinished(); +}; + +TLSSocket.prototype.getPeerFinished = function() { + if (this._handle) + return this._handle.getPeerFinished(); +}; + TLSSocket.prototype.getSession = function() { if (this._handle) { return this._handle.getSession(); diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 5c5981b76b..92e98e2eb2 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -1606,6 +1606,8 @@ void SSLWrap<Base>::AddMethods(Environment* env, Local<FunctionTemplate> t) { HandleScope scope(env->isolate()); env->SetProtoMethod(t, "getPeerCertificate", GetPeerCertificate); + env->SetProtoMethod(t, "getFinished", GetFinished); + env->SetProtoMethod(t, "getPeerFinished", GetPeerFinished); env->SetProtoMethod(t, "getSession", GetSession); env->SetProtoMethod(t, "setSession", SetSession); env->SetProtoMethod(t, "loadSession", LoadSession); @@ -2121,6 +2123,52 @@ void SSLWrap<Base>::GetPeerCertificate( template <class Base> +void SSLWrap<Base>::GetFinished(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + + Base* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + + // We cannot just pass nullptr to SSL_get_finished() + // because it would further be propagated to memcpy(), + // where the standard requirements as described in ISO/IEC 9899:2011 + // sections 7.21.2.1, 7.21.1.2, and 7.1.4, would be violated. + // Thus, we use a dummy byte. + char dummy[1]; + size_t len = SSL_get_finished(w->ssl_, dummy, sizeof dummy); + if (len == 0) + return; + + char* buf = Malloc(len); + CHECK_EQ(len, SSL_get_finished(w->ssl_, buf, len)); + args.GetReturnValue().Set(Buffer::New(env, buf, len).ToLocalChecked()); +} + + +template <class Base> +void SSLWrap<Base>::GetPeerFinished(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + + Base* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + + // We cannot just pass nullptr to SSL_get_peer_finished() + // because it would further be propagated to memcpy(), + // where the standard requirements as described in ISO/IEC 9899:2011 + // sections 7.21.2.1, 7.21.1.2, and 7.1.4, would be violated. + // Thus, we use a dummy byte. + char dummy[1]; + size_t len = SSL_get_peer_finished(w->ssl_, dummy, sizeof dummy); + if (len == 0) + return; + + char* buf = Malloc(len); + CHECK_EQ(len, SSL_get_peer_finished(w->ssl_, buf, len)); + args.GetReturnValue().Set(Buffer::New(env, buf, len).ToLocalChecked()); +} + + +template <class Base> void SSLWrap<Base>::GetSession(const FunctionCallbackInfo<Value>& args) { Environment* env = Environment::GetCurrent(args); diff --git a/src/node_crypto.h b/src/node_crypto.h index 05ea79f71f..668781aca9 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -269,6 +269,8 @@ class SSLWrap { static void GetPeerCertificate( const v8::FunctionCallbackInfo<v8::Value>& args); + static void GetFinished(const v8::FunctionCallbackInfo<v8::Value>& args); + static void GetPeerFinished(const v8::FunctionCallbackInfo<v8::Value>& args); static void GetSession(const v8::FunctionCallbackInfo<v8::Value>& args); static void SetSession(const v8::FunctionCallbackInfo<v8::Value>& args); static void LoadSession(const v8::FunctionCallbackInfo<v8::Value>& args); diff --git a/test/parallel/test-tls-finished.js b/test/parallel/test-tls-finished.js new file mode 100644 index 0000000000..8b52934b04 --- /dev/null +++ b/test/parallel/test-tls-finished.js @@ -0,0 +1,66 @@ +'use strict'; + +const common = require('../common'); +const fixtures = require('../common/fixtures'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +// This test ensures that tlsSocket.getFinished() and +// tlsSocket.getPeerFinished() return undefined before +// secure connection is established, and return non-empty +// Buffer objects with Finished messages afterwards, also +// verifying alice.getFinished() == bob.getPeerFinished() +// and alice.getPeerFinished() == bob.getFinished(). + +const assert = require('assert'); +const tls = require('tls'); + +const msg = {}; +const pem = (n) => fixtures.readKey(`${n}.pem`); +const server = tls.createServer({ + key: pem('agent1-key'), + cert: pem('agent1-cert') +}, common.mustCall((alice) => { + msg.server = { + alice: alice.getFinished(), + bob: alice.getPeerFinished() + }; + server.close(); +})); + +server.listen(0, common.mustCall(() => { + const bob = tls.connect({ + port: server.address().port, + rejectUnauthorized: false + }, common.mustCall(() => { + msg.client = { + alice: bob.getPeerFinished(), + bob: bob.getFinished() + }; + bob.end(); + })); + + msg.before = { + alice: bob.getPeerFinished(), + bob: bob.getFinished() + }; +})); + +process.on('exit', () => { + assert.strictEqual(undefined, msg.before.alice); + assert.strictEqual(undefined, msg.before.bob); + + assert(Buffer.isBuffer(msg.server.alice)); + assert(Buffer.isBuffer(msg.server.bob)); + assert(Buffer.isBuffer(msg.client.alice)); + assert(Buffer.isBuffer(msg.client.bob)); + + assert(msg.server.alice.length > 0); + assert(msg.server.bob.length > 0); + assert(msg.client.alice.length > 0); + assert(msg.client.bob.length > 0); + + assert(msg.server.alice.equals(msg.client.alice)); + assert(msg.server.bob.equals(msg.client.bob)); +}); |