Skip to content

Commit 98a14e0

Browse files
codedotaddaleax
authored andcommitted
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: #19102 Fixes: #19055 Refs: XRPLF/rippled#2413 Reviewed-By: Fedor Indutny <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent d3f174f commit 98a14e0

File tree

5 files changed

+161
-0
lines changed

5 files changed

+161
-0
lines changed

doc/api/tls.md

+35
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,23 @@ if called on a server socket. The supported types are `'DH'` and `'ECDH'`. The
583583

584584
For Example: `{ type: 'ECDH', name: 'prime256v1', size: 256 }`
585585

586+
### tlsSocket.getFinished()
587+
<!-- YAML
588+
added: REPLACEME
589+
-->
590+
591+
* Returns: {Buffer|undefined} The latest `Finished` message that has been
592+
sent to the socket as part of a SSL/TLS handshake, or `undefined` if
593+
no `Finished` message has been sent yet.
594+
595+
As the `Finished` messages are message digests of the complete handshake
596+
(with a total of 192 bits for TLS 1.0 and more for SSL 3.0), they can
597+
be used for external authentication procedures when the authentication
598+
provided by SSL/TLS is not desired or is not enough.
599+
600+
Corresponds to the `SSL_get_finished` routine in OpenSSL and may be used
601+
to implement the `tls-unique` channel binding from [RFC 5929][].
602+
586603
### tlsSocket.getPeerCertificate([detailed])
587604
<!-- YAML
588605
added: v0.11.4
@@ -628,6 +645,23 @@ For example:
628645

629646
If the peer does not provide a certificate, an empty object will be returned.
630647

648+
### tlsSocket.getPeerFinished()
649+
<!-- YAML
650+
added: REPLACEME
651+
-->
652+
653+
* Returns: {Buffer|undefined} The latest `Finished` message that is expected
654+
or has actually been received from the socket as part of a SSL/TLS handshake,
655+
or `undefined` if there is no `Finished` message so far.
656+
657+
As the `Finished` messages are message digests of the complete handshake
658+
(with a total of 192 bits for TLS 1.0 and more for SSL 3.0), they can
659+
be used for external authentication procedures when the authentication
660+
provided by SSL/TLS is not desired or is not enough.
661+
662+
Corresponds to the `SSL_get_peer_finished` routine in OpenSSL and may be used
663+
to implement the `tls-unique` channel binding from [RFC 5929][].
664+
631665
### tlsSocket.getProtocol()
632666
<!-- YAML
633667
added: v5.7.0
@@ -1368,3 +1402,4 @@ where `secure_socket` has the same API as `pair.cleartext`.
13681402
[specific attacks affecting larger AES key sizes]: https://www.schneier.com/blog/archives/2009/07/another_new_aes.html
13691403
[tls.Server]: #tls_class_tls_server
13701404
[`dns.lookup()`]: dns.html#dns_dns_lookup_hostname_options_callback
1405+
[RFC 5929]: https://tools.ietf.org/html/rfc5929

lib/_tls_wrap.js

+10
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,16 @@ TLSSocket.prototype.getPeerCertificate = function(detailed) {
692692
return null;
693693
};
694694

695+
TLSSocket.prototype.getFinished = function() {
696+
if (this._handle)
697+
return this._handle.getFinished();
698+
};
699+
700+
TLSSocket.prototype.getPeerFinished = function() {
701+
if (this._handle)
702+
return this._handle.getPeerFinished();
703+
};
704+
695705
TLSSocket.prototype.getSession = function() {
696706
if (this._handle) {
697707
return this._handle.getSession();

src/node_crypto.cc

+48
Original file line numberDiff line numberDiff line change
@@ -1606,6 +1606,8 @@ void SSLWrap<Base>::AddMethods(Environment* env, Local<FunctionTemplate> t) {
16061606
HandleScope scope(env->isolate());
16071607

16081608
env->SetProtoMethod(t, "getPeerCertificate", GetPeerCertificate);
1609+
env->SetProtoMethod(t, "getFinished", GetFinished);
1610+
env->SetProtoMethod(t, "getPeerFinished", GetPeerFinished);
16091611
env->SetProtoMethod(t, "getSession", GetSession);
16101612
env->SetProtoMethod(t, "setSession", SetSession);
16111613
env->SetProtoMethod(t, "loadSession", LoadSession);
@@ -2120,6 +2122,52 @@ void SSLWrap<Base>::GetPeerCertificate(
21202122
}
21212123

21222124

2125+
template <class Base>
2126+
void SSLWrap<Base>::GetFinished(const FunctionCallbackInfo<Value>& args) {
2127+
Environment* env = Environment::GetCurrent(args);
2128+
2129+
Base* w;
2130+
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
2131+
2132+
// We cannot just pass nullptr to SSL_get_finished()
2133+
// because it would further be propagated to memcpy(),
2134+
// where the standard requirements as described in ISO/IEC 9899:2011
2135+
// sections 7.21.2.1, 7.21.1.2, and 7.1.4, would be violated.
2136+
// Thus, we use a dummy byte.
2137+
char dummy[1];
2138+
size_t len = SSL_get_finished(w->ssl_, dummy, sizeof dummy);
2139+
if (len == 0)
2140+
return;
2141+
2142+
char* buf = Malloc(len);
2143+
CHECK_EQ(len, SSL_get_finished(w->ssl_, buf, len));
2144+
args.GetReturnValue().Set(Buffer::New(env, buf, len).ToLocalChecked());
2145+
}
2146+
2147+
2148+
template <class Base>
2149+
void SSLWrap<Base>::GetPeerFinished(const FunctionCallbackInfo<Value>& args) {
2150+
Environment* env = Environment::GetCurrent(args);
2151+
2152+
Base* w;
2153+
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
2154+
2155+
// We cannot just pass nullptr to SSL_get_peer_finished()
2156+
// because it would further be propagated to memcpy(),
2157+
// where the standard requirements as described in ISO/IEC 9899:2011
2158+
// sections 7.21.2.1, 7.21.1.2, and 7.1.4, would be violated.
2159+
// Thus, we use a dummy byte.
2160+
char dummy[1];
2161+
size_t len = SSL_get_peer_finished(w->ssl_, dummy, sizeof dummy);
2162+
if (len == 0)
2163+
return;
2164+
2165+
char* buf = Malloc(len);
2166+
CHECK_EQ(len, SSL_get_peer_finished(w->ssl_, buf, len));
2167+
args.GetReturnValue().Set(Buffer::New(env, buf, len).ToLocalChecked());
2168+
}
2169+
2170+
21232171
template <class Base>
21242172
void SSLWrap<Base>::GetSession(const FunctionCallbackInfo<Value>& args) {
21252173
Environment* env = Environment::GetCurrent(args);

src/node_crypto.h

+2
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,8 @@ class SSLWrap {
269269

270270
static void GetPeerCertificate(
271271
const v8::FunctionCallbackInfo<v8::Value>& args);
272+
static void GetFinished(const v8::FunctionCallbackInfo<v8::Value>& args);
273+
static void GetPeerFinished(const v8::FunctionCallbackInfo<v8::Value>& args);
272274
static void GetSession(const v8::FunctionCallbackInfo<v8::Value>& args);
273275
static void SetSession(const v8::FunctionCallbackInfo<v8::Value>& args);
274276
static void LoadSession(const v8::FunctionCallbackInfo<v8::Value>& args);

test/parallel/test-tls-finished.js

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const fixtures = require('../common/fixtures');
5+
6+
if (!common.hasCrypto)
7+
common.skip('missing crypto');
8+
9+
// This test ensures that tlsSocket.getFinished() and
10+
// tlsSocket.getPeerFinished() return undefined before
11+
// secure connection is established, and return non-empty
12+
// Buffer objects with Finished messages afterwards, also
13+
// verifying alice.getFinished() == bob.getPeerFinished()
14+
// and alice.getPeerFinished() == bob.getFinished().
15+
16+
const assert = require('assert');
17+
const tls = require('tls');
18+
19+
const msg = {};
20+
const pem = (n) => fixtures.readKey(`${n}.pem`);
21+
const server = tls.createServer({
22+
key: pem('agent1-key'),
23+
cert: pem('agent1-cert')
24+
}, common.mustCall((alice) => {
25+
msg.server = {
26+
alice: alice.getFinished(),
27+
bob: alice.getPeerFinished()
28+
};
29+
server.close();
30+
}));
31+
32+
server.listen(0, common.mustCall(() => {
33+
const bob = tls.connect({
34+
port: server.address().port,
35+
rejectUnauthorized: false
36+
}, common.mustCall(() => {
37+
msg.client = {
38+
alice: bob.getPeerFinished(),
39+
bob: bob.getFinished()
40+
};
41+
bob.end();
42+
}));
43+
44+
msg.before = {
45+
alice: bob.getPeerFinished(),
46+
bob: bob.getFinished()
47+
};
48+
}));
49+
50+
process.on('exit', () => {
51+
assert.strictEqual(undefined, msg.before.alice);
52+
assert.strictEqual(undefined, msg.before.bob);
53+
54+
assert(Buffer.isBuffer(msg.server.alice));
55+
assert(Buffer.isBuffer(msg.server.bob));
56+
assert(Buffer.isBuffer(msg.client.alice));
57+
assert(Buffer.isBuffer(msg.client.bob));
58+
59+
assert(msg.server.alice.length > 0);
60+
assert(msg.server.bob.length > 0);
61+
assert(msg.client.alice.length > 0);
62+
assert(msg.client.bob.length > 0);
63+
64+
assert(msg.server.alice.equals(msg.client.alice));
65+
assert(msg.server.bob.equals(msg.client.bob));
66+
});

0 commit comments

Comments
 (0)