Skip to content

Commit c3dd21b

Browse files
committed
tls: add ability to get cert/peer cert as X509Certificate object
Signed-off-by: James M Snell <jasnell@gmail.com>
1 parent 8b65004 commit c3dd21b

File tree

9 files changed

+183
-39
lines changed

9 files changed

+183
-39
lines changed

doc/api/crypto.md

+10
Original file line numberDiff line numberDiff line change
@@ -1804,6 +1804,16 @@ added: v15.6.0
18041804

18051805
The issuer identification included in this certificate.
18061806

1807+
### `x509.issuerCertificate`
1808+
<!-- YAML
1809+
added: REPLACEME
1810+
-->
1811+
1812+
* Type: {X509Certificate}
1813+
1814+
The issuer certificate (if known). Will be `undefined` if the issuer
1815+
certificate is not available.
1816+
18071817
### `x509.keyUsage`
18081818
<!-- YAML
18091819
added: v15.6.0

doc/api/tls.md

+24
Original file line numberDiff line numberDiff line change
@@ -1213,6 +1213,30 @@ It may be useful for debugging.
12131213

12141214
See [Session Resumption][] for more information.
12151215

1216+
### `tlsSocket.getPeerX509Certificate()`
1217+
<!-- YAML
1218+
added: REPLACEME
1219+
-->
1220+
1221+
* Returns: {X509Certificate}
1222+
1223+
Returns the peer certificate as an {X509Certificate} object.
1224+
1225+
If there is no peer certificate, or the socket has been destroyed,
1226+
`undefined` will be returned.
1227+
1228+
### `tlsSocket.getX509Certificate()`
1229+
<!-- YAML
1230+
added: REPLACEME
1231+
-->
1232+
1233+
* Returns: {X509Certificate}
1234+
1235+
Returns the local certificate as an {X509Certificate} object.
1236+
1237+
If there is no local certificate, or the socket has been destroyed,
1238+
`undefined` will be returned.
1239+
12161240
### `tlsSocket.isSessionReused()`
12171241
<!-- YAML
12181242
added: v0.5.6

lib/_tls_wrap.js

+13
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ const {
8989
validateBuffer,
9090
validateUint32
9191
} = require('internal/validators');
92+
const {
93+
InternalX509Certificate
94+
} = require('internal/crypto/x509');
9295
const traceTls = getOptionValue('--trace-tls');
9396
const tlsKeylog = getOptionValue('--tls-keylog');
9497
const { appendFile } = require('fs');
@@ -999,6 +1002,16 @@ TLSSocket.prototype.getCertificate = function() {
9991002
return null;
10001003
};
10011004

1005+
TLSSocket.prototype.getPeerX509Certificate = function(detailed) {
1006+
const cert = this._handle?.getPeerX509Certificate();
1007+
return cert ? new InternalX509Certificate(cert) : undefined;
1008+
};
1009+
1010+
TLSSocket.prototype.getX509Certificate = function() {
1011+
const cert = this._handle?.getX509Certificate();
1012+
return cert ? new InternalX509Certificate(cert) : undefined;
1013+
};
1014+
10021015
// Proxy TLSSocket handle methods
10031016
function makeSocketMethodProxy(name) {
10041017
return function socketMethodProxy(...args) {

lib/internal/crypto/x509.js

+20-9
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,15 @@ function getFlags(options = {}) {
9090
return flags;
9191
}
9292

93+
class InternalX509Certificate extends JSTransferable {
94+
[kInternalState] = new SafeMap();
95+
96+
constructor(handle) {
97+
super();
98+
this[kHandle] = handle;
99+
}
100+
}
101+
93102
class X509Certificate extends JSTransferable {
94103
[kInternalState] = new SafeMap();
95104

@@ -168,6 +177,17 @@ class X509Certificate extends JSTransferable {
168177
return value;
169178
}
170179

180+
get issuerCertificate() {
181+
let value = this[kInternalState].get('issuerCertificate');
182+
if (value === undefined) {
183+
const cert = this[kHandle].getIssuerCert();
184+
if (cert)
185+
value = new InternalX509Certificate(this[kHandle].getIssuerCert());
186+
this[kInternalState].set('issuerCertificate', value);
187+
}
188+
return value;
189+
}
190+
171191
get infoAccess() {
172192
let value = this[kInternalState].get('infoAccess');
173193
if (value === undefined) {
@@ -313,15 +333,6 @@ class X509Certificate extends JSTransferable {
313333
}
314334
}
315335

316-
class InternalX509Certificate extends JSTransferable {
317-
[kInternalState] = new SafeMap();
318-
319-
constructor(handle) {
320-
super();
321-
this[kHandle] = handle;
322-
}
323-
}
324-
325336
InternalX509Certificate.prototype.constructor = X509Certificate;
326337
ObjectSetPrototypeOf(
327338
InternalX509Certificate.prototype,

src/crypto/crypto_tls.cc

+26
Original file line numberDiff line numberDiff line change
@@ -1591,6 +1591,20 @@ void TLSWrap::GetPeerCertificate(const FunctionCallbackInfo<Value>& args) {
15911591
args.GetReturnValue().Set(ret);
15921592
}
15931593

1594+
void TLSWrap::GetPeerX509Certificate(const FunctionCallbackInfo<Value>& args) {
1595+
TLSWrap* w;
1596+
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
1597+
Environment* env = w->env();
1598+
1599+
X509Certificate::GetPeerCertificateFlag flag = w->is_server()
1600+
? X509Certificate::GetPeerCertificateFlag::SERVER
1601+
: X509Certificate::GetPeerCertificateFlag::NONE;
1602+
1603+
Local<Value> ret;
1604+
if (X509Certificate::GetPeerCert(env, w->ssl_, flag).ToLocal(&ret))
1605+
args.GetReturnValue().Set(ret);
1606+
}
1607+
15941608
void TLSWrap::GetCertificate(const FunctionCallbackInfo<Value>& args) {
15951609
TLSWrap* w;
15961610
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
@@ -1601,6 +1615,15 @@ void TLSWrap::GetCertificate(const FunctionCallbackInfo<Value>& args) {
16011615
args.GetReturnValue().Set(ret);
16021616
}
16031617

1618+
void TLSWrap::GetX509Certificate(const FunctionCallbackInfo<Value>& args) {
1619+
TLSWrap* w;
1620+
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
1621+
Environment* env = w->env();
1622+
Local<Value> ret;
1623+
if (X509Certificate::GetCert(env, w->ssl_).ToLocal(&ret))
1624+
args.GetReturnValue().Set(ret);
1625+
}
1626+
16041627
void TLSWrap::GetFinished(const FunctionCallbackInfo<Value>& args) {
16051628
Environment* env = Environment::GetCurrent(args);
16061629

@@ -2051,11 +2074,14 @@ void TLSWrap::Initialize(
20512074
env->SetProtoMethodNoSideEffect(t, "getALPNNegotiatedProtocol",
20522075
GetALPNNegotiatedProto);
20532076
env->SetProtoMethodNoSideEffect(t, "getCertificate", GetCertificate);
2077+
env->SetProtoMethodNoSideEffect(t, "getX509Certificate", GetX509Certificate);
20542078
env->SetProtoMethodNoSideEffect(t, "getCipher", GetCipher);
20552079
env->SetProtoMethodNoSideEffect(t, "getEphemeralKeyInfo",
20562080
GetEphemeralKeyInfo);
20572081
env->SetProtoMethodNoSideEffect(t, "getFinished", GetFinished);
20582082
env->SetProtoMethodNoSideEffect(t, "getPeerCertificate", GetPeerCertificate);
2083+
env->SetProtoMethodNoSideEffect(t, "getPeerX509Certificate",
2084+
GetPeerX509Certificate);
20592085
env->SetProtoMethodNoSideEffect(t, "getPeerFinished", GetPeerFinished);
20602086
env->SetProtoMethodNoSideEffect(t, "getProtocol", GetProtocol);
20612087
env->SetProtoMethodNoSideEffect(t, "getSession", GetSession);

src/crypto/crypto_tls.h

+4
Original file line numberDiff line numberDiff line change
@@ -184,12 +184,16 @@ class TLSWrap : public AsyncWrap,
184184
static void GetALPNNegotiatedProto(
185185
const v8::FunctionCallbackInfo<v8::Value>& args);
186186
static void GetCertificate(const v8::FunctionCallbackInfo<v8::Value>& args);
187+
static void GetX509Certificate(
188+
const v8::FunctionCallbackInfo<v8::Value>& args);
187189
static void GetCipher(const v8::FunctionCallbackInfo<v8::Value>& args);
188190
static void GetEphemeralKeyInfo(
189191
const v8::FunctionCallbackInfo<v8::Value>& args);
190192
static void GetFinished(const v8::FunctionCallbackInfo<v8::Value>& args);
191193
static void GetPeerCertificate(
192194
const v8::FunctionCallbackInfo<v8::Value>& args);
195+
static void GetPeerX509Certificate(
196+
const v8::FunctionCallbackInfo<v8::Value>& args);
193197
static void GetPeerFinished(const v8::FunctionCallbackInfo<v8::Value>& args);
194198
static void GetProtocol(const v8::FunctionCallbackInfo<v8::Value>& args);
195199
static void GetServername(const v8::FunctionCallbackInfo<v8::Value>& args);

src/crypto/crypto_x509.cc

+34-26
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515

1616
namespace node {
1717

18-
using v8::Array;
1918
using v8::ArrayBufferView;
2019
using v8::Context;
2120
using v8::EscapableHandleScope;
@@ -82,6 +81,7 @@ Local<FunctionTemplate> X509Certificate::GetConstructorTemplate(
8281
env->SetProtoMethod(tmpl, "checkPrivateKey", CheckPrivateKey);
8382
env->SetProtoMethod(tmpl, "verify", Verify);
8483
env->SetProtoMethod(tmpl, "toLegacy", ToLegacy);
84+
env->SetProtoMethod(tmpl, "getIssuerCert", GetIssuerCert);
8585
env->set_x509_constructor_template(tmpl);
8686
}
8787
return tmpl;
@@ -93,14 +93,16 @@ bool X509Certificate::HasInstance(Environment* env, Local<Object> object) {
9393

9494
MaybeLocal<Object> X509Certificate::New(
9595
Environment* env,
96-
X509Pointer cert) {
96+
X509Pointer cert,
97+
STACK_OF(X509)* issuer_chain) {
9798
std::shared_ptr<ManagedX509> mcert(new ManagedX509(std::move(cert)));
98-
return New(env, std::move(mcert));
99+
return New(env, std::move(mcert), issuer_chain);
99100
}
100101

101102
MaybeLocal<Object> X509Certificate::New(
102103
Environment* env,
103-
std::shared_ptr<ManagedX509> cert) {
104+
std::shared_ptr<ManagedX509> cert,
105+
STACK_OF(X509)* issuer_chain) {
104106
EscapableHandleScope scope(env->isolate());
105107
Local<Function> ctor;
106108
if (!GetConstructorTemplate(env)->GetFunction(env->context()).ToLocal(&ctor))
@@ -110,7 +112,7 @@ MaybeLocal<Object> X509Certificate::New(
110112
if (!ctor->NewInstance(env->context()).ToLocal(&obj))
111113
return MaybeLocal<Object>();
112114

113-
new X509Certificate(env, obj, std::move(cert));
115+
new X509Certificate(env, obj, std::move(cert), issuer_chain);
114116
return scope.Escape(obj);
115117
}
116118

@@ -122,24 +124,20 @@ MaybeLocal<Object> X509Certificate::GetCert(
122124
if (cert == nullptr)
123125
return MaybeLocal<Object>();
124126

125-
X509Pointer ptr(cert);
127+
X509Pointer ptr(X509_dup(cert));
126128
return New(env, std::move(ptr));
127129
}
128130

129131
MaybeLocal<Object> X509Certificate::GetPeerCert(
130132
Environment* env,
131133
const SSLPointer& ssl,
132134
GetPeerCertificateFlag flag) {
133-
EscapableHandleScope scope(env->isolate());
134135
ClearErrorOnReturn clear_error_on_return;
135136
Local<Object> obj;
136137
MaybeLocal<Object> maybe_cert;
137138

138139
bool is_server =
139140
static_cast<int>(flag) & static_cast<int>(GetPeerCertificateFlag::SERVER);
140-
bool abbreviated =
141-
static_cast<int>(flag)
142-
& static_cast<int>(GetPeerCertificateFlag::ABBREVIATED);
143141

144142
X509Pointer cert(is_server ? SSL_get_peer_certificate(ssl.get()) : nullptr);
145143
STACK_OF(X509)* ssl_certs = SSL_get_peer_cert_chain(ssl.get());
@@ -148,23 +146,14 @@ MaybeLocal<Object> X509Certificate::GetPeerCert(
148146

149147
std::vector<Local<Value>> certs;
150148

151-
if (!cert) cert.reset(sk_X509_value(ssl_certs, 0));
152-
if (!X509Certificate::New(env, std::move(cert)).ToLocal(&obj))
153-
return MaybeLocal<Object>();
154-
155-
certs.push_back(obj);
156-
157-
int count = sk_X509_num(ssl_certs);
158-
if (!abbreviated) {
159-
for (int i = 0; i < count; i++) {
160-
cert.reset(X509_dup(sk_X509_value(ssl_certs, i)));
161-
if (!cert || !X509Certificate::New(env, std::move(cert)).ToLocal(&obj))
162-
return MaybeLocal<Object>();
163-
certs.push_back(obj);
164-
}
149+
if (!cert) {
150+
cert.reset(sk_X509_value(ssl_certs, 0));
151+
sk_X509_delete(ssl_certs, 0);
165152
}
166153

167-
return scope.Escape(Array::New(env->isolate(), certs.data(), certs.size()));
154+
return sk_X509_num(ssl_certs)
155+
? New(env, std::move(cert), ssl_certs)
156+
: New(env, std::move(cert));
168157
}
169158

170159
void X509Certificate::Parse(const FunctionCallbackInfo<Value>& args) {
@@ -475,13 +464,32 @@ void X509Certificate::ToLegacy(const FunctionCallbackInfo<Value>& args) {
475464
args.GetReturnValue().Set(ret);
476465
}
477466

467+
void X509Certificate::GetIssuerCert(const FunctionCallbackInfo<Value>& args) {
468+
X509Certificate* cert;
469+
ASSIGN_OR_RETURN_UNWRAP(&cert, args.Holder());
470+
if (cert->issuer_cert_)
471+
args.GetReturnValue().Set(cert->issuer_cert_->object());
472+
}
473+
478474
X509Certificate::X509Certificate(
479475
Environment* env,
480476
Local<Object> object,
481-
std::shared_ptr<ManagedX509> cert)
477+
std::shared_ptr<ManagedX509> cert,
478+
STACK_OF(X509)* issuer_chain)
482479
: BaseObject(env, object),
483480
cert_(std::move(cert)) {
484481
MakeWeak();
482+
483+
if (issuer_chain != nullptr && sk_X509_num(issuer_chain)) {
484+
X509Pointer cert(X509_dup(sk_X509_value(issuer_chain, 0)));
485+
sk_X509_delete(issuer_chain, 0);
486+
Local<Object> obj = sk_X509_num(issuer_chain)
487+
? X509Certificate::New(env, std::move(cert), issuer_chain)
488+
.ToLocalChecked()
489+
: X509Certificate::New(env, std::move(cert))
490+
.ToLocalChecked();
491+
issuer_cert_.reset(Unwrap<X509Certificate>(obj));
492+
}
485493
}
486494

487495
void X509Certificate::MemoryInfo(MemoryTracker* tracker) const {

src/crypto/crypto_x509.h

+9-4
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class ManagedX509 : public MemoryRetainer {
3838
class X509Certificate : public BaseObject {
3939
public:
4040
enum class GetPeerCertificateFlag {
41-
ABBREVIATED,
41+
NONE,
4242
SERVER
4343
};
4444

@@ -49,11 +49,13 @@ class X509Certificate : public BaseObject {
4949

5050
static v8::MaybeLocal<v8::Object> New(
5151
Environment* env,
52-
X509Pointer cert);
52+
X509Pointer cert,
53+
STACK_OF(X509)* issuer_chain = nullptr);
5354

5455
static v8::MaybeLocal<v8::Object> New(
5556
Environment* env,
56-
std::shared_ptr<ManagedX509> cert);
57+
std::shared_ptr<ManagedX509> cert,
58+
STACK_OF(X509)* issuer_chain = nullptr);
5759

5860
static v8::MaybeLocal<v8::Object> GetCert(
5961
Environment* env,
@@ -91,6 +93,7 @@ class X509Certificate : public BaseObject {
9193
static void CheckPrivateKey(const v8::FunctionCallbackInfo<v8::Value>& args);
9294
static void Verify(const v8::FunctionCallbackInfo<v8::Value>& args);
9395
static void ToLegacy(const v8::FunctionCallbackInfo<v8::Value>& args);
96+
static void GetIssuerCert(const v8::FunctionCallbackInfo<v8::Value>& args);
9497

9598
X509* get() { return cert_->get(); }
9699

@@ -124,9 +127,11 @@ class X509Certificate : public BaseObject {
124127
X509Certificate(
125128
Environment* env,
126129
v8::Local<v8::Object> object,
127-
std::shared_ptr<ManagedX509> cert);
130+
std::shared_ptr<ManagedX509> cert,
131+
STACK_OF(X509)* issuer_chain = nullptr);
128132

129133
std::shared_ptr<ManagedX509> cert_;
134+
BaseObjectPtr<X509Certificate> issuer_cert_;
130135
};
131136

132137
} // namespace crypto

0 commit comments

Comments
 (0)