tls: expose Finished messages in TLSSocket

Exposes SSL_get_finished and SSL_get_peer_finished routines in OpenSSL
as tlsSocket.getFinished and tlsSocket.getPeerFinished, respectively.

PR-URL: https://github.com/nodejs/node/pull/19102
Fixes: https://github.com/nodejs/node/issues/19055
Refs: https://github.com/ripple/rippled/issues/2413
Reviewed-By: Fedor Indutny <fedor.indutny@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
Anton Salikhmetov 2018-03-02 21:46:34 +02:00 committed by Anna Henningsen
parent d3f174faab
commit 98a14e026b
No known key found for this signature in database
GPG Key ID: 9C63F3A6CD2AD8F9
5 changed files with 161 additions and 0 deletions

View File

@ -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

View File

@ -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();

View File

@ -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);
@ -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);

View File

@ -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);

View File

@ -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));
});