Skip to content

Commit 5f5d3c9

Browse files
committed
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: nodejs#24095 PR-URL: nodejs#24261 Fixes: nodejs#24095 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Daniel Bevenius <daniel.bevenius@gmail.com>
1 parent f47e208 commit 5f5d3c9

File tree

8 files changed

+69
-5
lines changed

8 files changed

+69
-5
lines changed

doc/api/tls.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,22 @@ added: v0.11.4
659659
Always returns `true`. This may be used to distinguish TLS sockets from regular
660660
`net.Socket` instances.
661661

662+
### tlsSocket.getCertificate()
663+
<!-- YAML
664+
added: REPLACEME
665+
-->
666+
667+
* Returns: {Object}
668+
669+
Returns an object representing the local certificate. The returned object has
670+
some properties corresponding to the fields of the certificate.
671+
672+
See [`tls.TLSSocket.getPeerCertificate()`][] for an example of the certificate
673+
structure.
674+
675+
If there is no local certificate, an empty object will be returned. If the
676+
socket has been destroyed, `null` will be returned.
677+
662678
### tlsSocket.getCipher()
663679
<!-- YAML
664680
added: v0.11.4
@@ -751,6 +767,7 @@ certificate.
751767
```
752768

753769
If the peer does not provide a certificate, an empty object will be returned.
770+
If the socket has been destroyed, `null` will be returned.
754771

755772
### tlsSocket.getPeerFinished()
756773
<!-- YAML

lib/_tls_common.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,9 @@ exports.createSecureContext = function createSecureContext(options, context) {
236236
return c;
237237
};
238238

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

lib/_tls_wrap.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -668,7 +668,17 @@ TLSSocket.prototype.setSession = function(session) {
668668
TLSSocket.prototype.getPeerCertificate = function(detailed) {
669669
if (this._handle) {
670670
return common.translatePeerCertificate(
671-
this._handle.getPeerCertificate(detailed));
671+
this._handle.getPeerCertificate(detailed)) || {};
672+
}
673+
674+
return null;
675+
};
676+
677+
TLSSocket.prototype.getCertificate = function() {
678+
if (this._handle) {
679+
// It's not a peer cert, but the formatting is identical.
680+
return common.translatePeerCertificate(
681+
this._handle.getCertificate()) || {};
672682
}
673683

674684
return null;

src/node_crypto.cc

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1396,6 +1396,7 @@ void SSLWrap<Base>::AddMethods(Environment* env, Local<FunctionTemplate> t) {
13961396
HandleScope scope(env->isolate());
13971397

13981398
env->SetProtoMethodNoSideEffect(t, "getPeerCertificate", GetPeerCertificate);
1399+
env->SetProtoMethodNoSideEffect(t, "getCertificate", GetCertificate);
13991400
env->SetProtoMethodNoSideEffect(t, "getFinished", GetFinished);
14001401
env->SetProtoMethodNoSideEffect(t, "getPeerFinished", GetPeerFinished);
14011402
env->SetProtoMethodNoSideEffect(t, "getSession", GetSession);
@@ -1858,8 +1859,26 @@ void SSLWrap<Base>::GetPeerCertificate(
18581859
}
18591860

18601861
done:
1861-
if (result.IsEmpty())
1862-
result = Object::New(env->isolate());
1862+
args.GetReturnValue().Set(result);
1863+
}
1864+
1865+
1866+
template <class Base>
1867+
void SSLWrap<Base>::GetCertificate(
1868+
const FunctionCallbackInfo<Value>& args) {
1869+
Base* w;
1870+
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
1871+
Environment* env = w->ssl_env();
1872+
1873+
ClearErrorOnReturn clear_error_on_return;
1874+
1875+
Local<Object> result;
1876+
1877+
X509Pointer cert(SSL_get_certificate(w->ssl_.get()));
1878+
1879+
if (cert)
1880+
result = X509ToObject(env, cert.get());
1881+
18631882
args.GetReturnValue().Set(result);
18641883
}
18651884

src/node_crypto.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ class SSLWrap {
272272

273273
static void GetPeerCertificate(
274274
const v8::FunctionCallbackInfo<v8::Value>& args);
275+
static void GetCertificate(const v8::FunctionCallbackInfo<v8::Value>& args);
275276
static void GetFinished(const v8::FunctionCallbackInfo<v8::Value>& args);
276277
static void GetPeerFinished(const v8::FunctionCallbackInfo<v8::Value>& args);
277278
static void GetSession(const v8::FunctionCallbackInfo<v8::Value>& args);

test/parallel/test-tls-peer-certificate-multi-keys.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ if (!common.hasCrypto)
2626

2727
const assert = require('assert');
2828
const tls = require('tls');
29-
const util = require('util');
3029
const fixtures = require('../common/fixtures');
3130

3231
const options = {
@@ -37,13 +36,22 @@ const options = {
3736
const server = tls.createServer(options, function(cleartext) {
3837
cleartext.end('World');
3938
});
39+
40+
server.once('secureConnection', common.mustCall(function(socket) {
41+
const cert = socket.getCertificate();
42+
// The server's local cert is the client's peer cert.
43+
assert.deepStrictEqual(
44+
cert.subject.OU,
45+
['Information Technology', 'Engineering', 'Marketing']
46+
);
47+
}));
48+
4049
server.listen(0, common.mustCall(function() {
4150
const socket = tls.connect({
4251
port: this.address().port,
4352
rejectUnauthorized: false
4453
}, common.mustCall(function() {
4554
const peerCert = socket.getPeerCertificate();
46-
console.error(util.inspect(peerCert));
4755
assert.deepStrictEqual(
4856
peerCert.subject.OU,
4957
['Information Technology', 'Engineering', 'Marketing']

test/parallel/test-tls-peer-certificate.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ connect({
4343
}, function(err, pair, cleanup) {
4444
assert.ifError(err);
4545
const socket = pair.client.conn;
46+
const localCert = socket.getCertificate();
47+
assert.deepStrictEqual(localCert, {});
4648
let peerCert = socket.getPeerCertificate();
4749
assert.ok(!peerCert.issuerCertificate);
4850

test/parallel/test-tls-pfx-authorizationerror.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ const server = tls
2222
rejectUnauthorized: false
2323
},
2424
common.mustCall(function(c) {
25+
assert.strictEqual(c.getPeerCertificate().serialNumber,
26+
'ECC9B856270DA9A8');
2527
assert.strictEqual(c.authorizationError, null);
2628
c.end();
2729
})
@@ -35,6 +37,8 @@ const server = tls
3537
rejectUnauthorized: false
3638
},
3739
function() {
40+
assert.strictEqual(client.getCertificate().serialNumber,
41+
'FAD50CC6A07F516C');
3842
client.end();
3943
server.close();
4044
}

0 commit comments

Comments
 (0)