Skip to content

Commit 0602166

Browse files
codedotMylesBorins
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 49481d0 commit 0602166

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
@@ -584,6 +584,23 @@ if called on a server socket. The supported types are `'DH'` and `'ECDH'`. The
584584

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

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

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

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

lib/_tls_wrap.js

+10
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,16 @@ TLSSocket.prototype.getPeerCertificate = function(detailed) {
668668
return null;
669669
};
670670

671+
TLSSocket.prototype.getFinished = function() {
672+
if (this._handle)
673+
return this._handle.getFinished();
674+
};
675+
676+
TLSSocket.prototype.getPeerFinished = function() {
677+
if (this._handle)
678+
return this._handle.getPeerFinished();
679+
};
680+
671681
TLSSocket.prototype.getSession = function() {
672682
if (this._handle) {
673683
return this._handle.getSession();

src/node_crypto.cc

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

16171617
env->SetProtoMethod(t, "getPeerCertificate", GetPeerCertificate);
1618+
env->SetProtoMethod(t, "getFinished", GetFinished);
1619+
env->SetProtoMethod(t, "getPeerFinished", GetPeerFinished);
16181620
env->SetProtoMethod(t, "getSession", GetSession);
16191621
env->SetProtoMethod(t, "setSession", SetSession);
16201622
env->SetProtoMethod(t, "loadSession", LoadSession);
@@ -2133,6 +2135,52 @@ void SSLWrap<Base>::GetPeerCertificate(
21332135
}
21342136

21352137

2138+
template <class Base>
2139+
void SSLWrap<Base>::GetFinished(const FunctionCallbackInfo<Value>& args) {
2140+
Environment* env = Environment::GetCurrent(args);
2141+
2142+
Base* w;
2143+
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
2144+
2145+
// We cannot just pass nullptr to SSL_get_finished()
2146+
// because it would further be propagated to memcpy(),
2147+
// where the standard requirements as described in ISO/IEC 9899:2011
2148+
// sections 7.21.2.1, 7.21.1.2, and 7.1.4, would be violated.
2149+
// Thus, we use a dummy byte.
2150+
char dummy[1];
2151+
size_t len = SSL_get_finished(w->ssl_, dummy, sizeof dummy);
2152+
if (len == 0)
2153+
return;
2154+
2155+
char* buf = Malloc(len);
2156+
CHECK_EQ(len, SSL_get_finished(w->ssl_, buf, len));
2157+
args.GetReturnValue().Set(Buffer::New(env, buf, len).ToLocalChecked());
2158+
}
2159+
2160+
2161+
template <class Base>
2162+
void SSLWrap<Base>::GetPeerFinished(const FunctionCallbackInfo<Value>& args) {
2163+
Environment* env = Environment::GetCurrent(args);
2164+
2165+
Base* w;
2166+
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
2167+
2168+
// We cannot just pass nullptr to SSL_get_peer_finished()
2169+
// because it would further be propagated to memcpy(),
2170+
// where the standard requirements as described in ISO/IEC 9899:2011
2171+
// sections 7.21.2.1, 7.21.1.2, and 7.1.4, would be violated.
2172+
// Thus, we use a dummy byte.
2173+
char dummy[1];
2174+
size_t len = SSL_get_peer_finished(w->ssl_, dummy, sizeof dummy);
2175+
if (len == 0)
2176+
return;
2177+
2178+
char* buf = Malloc(len);
2179+
CHECK_EQ(len, SSL_get_peer_finished(w->ssl_, buf, len));
2180+
args.GetReturnValue().Set(Buffer::New(env, buf, len).ToLocalChecked());
2181+
}
2182+
2183+
21362184
template <class Base>
21372185
void SSLWrap<Base>::GetSession(const FunctionCallbackInfo<Value>& args) {
21382186
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)