diff --git a/doc/api/tls.markdown b/doc/api/tls.markdown index 2c9814e59deb2b..bac0ace8a5ed63 100644 --- a/doc/api/tls.markdown +++ b/doc/api/tls.markdown @@ -494,6 +494,24 @@ Example: If the peer does not provide a certificate, it returns `null` or an empty object. +### tlsSocket.getProtocol() + +Returns a string containing the negotiated SSL/TLS protocol version of the +current connection. `'unknown'` will be returned for connected sockets that have +not completed the handshaking process. `null` will be returned for server +sockets or disconnected client sockets. + +Examples: +``` +'SSLv3' +'TLSv1' +'TLSv1.1' +'TLSv1.2' +'unknown' +``` + +See https://www.openssl.org/docs/manmaster/ssl/SSL_get_version.html for more +information. ### tlsSocket.getSession() diff --git a/lib/_tls_wrap.js b/lib/_tls_wrap.js index dd293121cbc782..a888dc1554a495 100644 --- a/lib/_tls_wrap.js +++ b/lib/_tls_wrap.js @@ -649,6 +649,13 @@ TLSSocket.prototype.getEphemeralKeyInfo = function() { return null; }; +TLSSocket.prototype.getProtocol = function() { + if (this._handle) + return this._handle.getProtocol(); + + return null; +}; + // TODO: support anonymous (nocert) and PSK diff --git a/src/node_crypto.cc b/src/node_crypto.cc index c1afb6a4ed44e1..b4e6662009cd10 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -1192,6 +1192,7 @@ void SSLWrap::AddMethods(Environment* env, Local t) { env->SetProtoMethod(t, "setOCSPResponse", SetOCSPResponse); env->SetProtoMethod(t, "requestOCSP", RequestOCSP); env->SetProtoMethod(t, "getEphemeralKeyInfo", GetEphemeralKeyInfo); + env->SetProtoMethod(t, "getProtocol", GetProtocol); #ifdef SSL_set_max_send_fragment env->SetProtoMethod(t, "setMaxSendFragment", SetMaxSendFragment); @@ -1954,6 +1955,15 @@ void SSLWrap::GetCurrentCipher(const FunctionCallbackInfo& args) { } +template +void SSLWrap::GetProtocol(const FunctionCallbackInfo& args) { + Base* w = Unwrap(args.Holder()); + + const char* tls_version = SSL_get_version(w->ssl_); + args.GetReturnValue().Set(OneByteString(args.GetIsolate(), tls_version)); +} + + #ifdef OPENSSL_NPN_NEGOTIATED template int SSLWrap::AdvertiseNextProtoCallback(SSL* s, diff --git a/src/node_crypto.h b/src/node_crypto.h index aaadc904dd3980..8a6c66703720b0 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -248,6 +248,7 @@ class SSLWrap { static void RequestOCSP(const v8::FunctionCallbackInfo& args); static void GetEphemeralKeyInfo( const v8::FunctionCallbackInfo& args); + static void GetProtocol(const v8::FunctionCallbackInfo& args); #ifdef SSL_set_max_send_fragment static void SetMaxSendFragment( diff --git a/test/parallel/test-tls-getprotocol.js b/test/parallel/test-tls-getprotocol.js new file mode 100644 index 00000000000000..67592143ee4875 --- /dev/null +++ b/test/parallel/test-tls-getprotocol.js @@ -0,0 +1,43 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +if (!common.hasCrypto) { + console.log('1..0 # Skipped: missing crypto'); + return; +} + +const tls = require('tls'); +const fs = require('fs'); + +const clientConfigs = [ + { secureProtocol: 'TLSv1_method', version: 'TLSv1' }, + { secureProtocol: 'TLSv1_1_method', version: 'TLSv1.1' }, + { secureProtocol: 'TLSv1_2_method', version: 'TLSv1.2' } +]; + +const serverConfig = { + key: fs.readFileSync(common.fixturesDir + '/keys/agent2-key.pem'), + cert: fs.readFileSync(common.fixturesDir + '/keys/agent2-cert.pem') +}; + +const server = tls.createServer(serverConfig, common.mustCall(function() { + +}, clientConfigs.length)).listen(common.PORT, common.localhostIPv4, function() { + let connected = 0; + clientConfigs.forEach(function(v) { + tls.connect({ + host: common.localhostIPv4, + port: common.PORT, + rejectUnauthorized: false, + secureProtocol: v.secureProtocol + }, common.mustCall(function() { + assert.strictEqual(this.getProtocol(), v.version); + this.on('end', common.mustCall(function() { + assert.strictEqual(this.getProtocol(), null); + })).end(); + if (++connected === clientConfigs.length) + server.close(); + })); + }); +});