Skip to content
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

tls: expose Finished messages in TLSSocket #19102

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions doc/api/tls.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
10 changes: 10 additions & 0 deletions lib/_tls_wrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,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();
Expand Down
48 changes: 48 additions & 0 deletions src/node_crypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -2120,6 +2122,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);
Expand Down
2 changes: 2 additions & 0 deletions src/node_crypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
66 changes: 66 additions & 0 deletions test/parallel/test-tls-finished.js
Original file line number Diff line number Diff line change
@@ -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));
});