mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00:00
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:
parent
d3f174faab
commit
98a14e026b
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
66
test/parallel/test-tls-finished.js
Normal file
66
test/parallel/test-tls-finished.js
Normal 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));
|
||||
});
|
Loading…
Reference in New Issue
Block a user