aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/api/tls.md35
-rw-r--r--lib/_tls_wrap.js10
-rw-r--r--src/node_crypto.cc48
-rw-r--r--src/node_crypto.h2
-rw-r--r--test/parallel/test-tls-finished.js66
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));
+});