tls: get the local certificate after tls handshake

Add an API to get the local certificate chosen during TLS handshake from
the SSL context.

Fix: https://github.com/nodejs/node/issues/24095

PR-URL: https://github.com/nodejs/node/pull/24261
Fixes: https://github.com/nodejs/node/issues/24095
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Daniel Bevenius <daniel.bevenius@gmail.com>
This commit is contained in:
Sam Roberts 2018-11-08 13:40:46 -08:00
parent 98278584ee
commit db35fee1e1
8 changed files with 69 additions and 5 deletions

View File

@ -566,6 +566,22 @@ added: v0.11.4
Always returns `true`. This may be used to distinguish TLS sockets from regular
`net.Socket` instances.
### tlsSocket.getCertificate()
<!-- YAML
added: REPLACEME
-->
* Returns: {Object}
Returns an object representing the local certificate. The returned object has
some properties corresponding to the fields of the certificate.
See [`tls.TLSSocket.getPeerCertificate()`][] for an example of the certificate
structure.
If there is no local certificate, an empty object will be returned. If the
socket has been destroyed, `null` will be returned.
### tlsSocket.getCipher()
<!-- YAML
added: v0.11.4
@ -658,6 +674,7 @@ certificate.
```
If the peer does not provide a certificate, an empty object will be returned.
If the socket has been destroyed, `null` will be returned.
### tlsSocket.getPeerFinished()
<!-- YAML

View File

@ -202,6 +202,9 @@ exports.createSecureContext = function createSecureContext(options) {
return c;
};
// Translate some fields from the handle's C-friendly format into more idiomatic
// javascript object representations before passing them back to the user. Can
// be used on any cert object, but changing the name would be semver-major.
exports.translatePeerCertificate = function translatePeerCertificate(c) {
if (!c)
return null;

View File

@ -660,7 +660,17 @@ TLSSocket.prototype.setSession = function(session) {
TLSSocket.prototype.getPeerCertificate = function(detailed) {
if (this._handle) {
return common.translatePeerCertificate(
this._handle.getPeerCertificate(detailed));
this._handle.getPeerCertificate(detailed)) || {};
}
return null;
};
TLSSocket.prototype.getCertificate = function() {
if (this._handle) {
// It's not a peer cert, but the formatting is identical.
return common.translatePeerCertificate(
this._handle.getCertificate()) || {};
}
return null;

View File

@ -1409,6 +1409,7 @@ void SSLWrap<Base>::AddMethods(Environment* env, Local<FunctionTemplate> t) {
HandleScope scope(env->isolate());
env->SetProtoMethodNoSideEffect(t, "getPeerCertificate", GetPeerCertificate);
env->SetProtoMethodNoSideEffect(t, "getCertificate", GetCertificate);
env->SetProtoMethodNoSideEffect(t, "getFinished", GetFinished);
env->SetProtoMethodNoSideEffect(t, "getPeerFinished", GetPeerFinished);
env->SetProtoMethodNoSideEffect(t, "getSession", GetSession);
@ -1871,8 +1872,26 @@ void SSLWrap<Base>::GetPeerCertificate(
}
done:
if (result.IsEmpty())
result = Object::New(env->isolate());
args.GetReturnValue().Set(result);
}
template <class Base>
void SSLWrap<Base>::GetCertificate(
const FunctionCallbackInfo<Value>& args) {
Base* w;
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
Environment* env = w->ssl_env();
ClearErrorOnReturn clear_error_on_return;
Local<Object> result;
X509Pointer cert(SSL_get_certificate(w->ssl_.get()));
if (cert)
result = X509ToObject(env, cert.get());
args.GetReturnValue().Set(result);
}

View File

@ -272,6 +272,7 @@ class SSLWrap {
static void GetPeerCertificate(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetCertificate(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);

View File

@ -26,7 +26,6 @@ if (!common.hasCrypto)
const assert = require('assert');
const tls = require('tls');
const util = require('util');
const fixtures = require('../common/fixtures');
const options = {
@ -37,13 +36,22 @@ const options = {
const server = tls.createServer(options, function(cleartext) {
cleartext.end('World');
});
server.once('secureConnection', common.mustCall(function(socket) {
const cert = socket.getCertificate();
// The server's local cert is the client's peer cert.
assert.deepStrictEqual(
cert.subject.OU,
['Information Technology', 'Engineering', 'Marketing']
);
}));
server.listen(0, common.mustCall(function() {
const socket = tls.connect({
port: this.address().port,
rejectUnauthorized: false
}, common.mustCall(function() {
const peerCert = socket.getPeerCertificate();
console.error(util.inspect(peerCert));
assert.deepStrictEqual(
peerCert.subject.OU,
['Information Technology', 'Engineering', 'Marketing']

View File

@ -43,6 +43,8 @@ connect({
}, function(err, pair, cleanup) {
assert.ifError(err);
const socket = pair.client.conn;
const localCert = socket.getCertificate();
assert.deepStrictEqual(localCert, {});
let peerCert = socket.getPeerCertificate();
assert.ok(!peerCert.issuerCertificate);

View File

@ -22,6 +22,8 @@ const server = tls
rejectUnauthorized: false
},
common.mustCall(function(c) {
assert.strictEqual(c.getPeerCertificate().serialNumber,
'FAD50CC6A07F516C');
assert.strictEqual(c.authorizationError, null);
c.end();
})
@ -35,6 +37,8 @@ const server = tls
rejectUnauthorized: false
},
function() {
assert.strictEqual(client.getCertificate().serialNumber,
'FAD50CC6A07F516C');
client.end();
server.close();
}