Skip to content

Commit

Permalink
tls: add ability to get cert/peer cert as X509Certificate object
Browse files Browse the repository at this point in the history
Signed-off-by: James M Snell <jasnell@gmail.com>
  • Loading branch information
jasnell committed Jan 27, 2021
1 parent 8b65004 commit c3dd21b
Show file tree
Hide file tree
Showing 9 changed files with 183 additions and 39 deletions.
10 changes: 10 additions & 0 deletions doc/api/crypto.md
Original file line number Diff line number Diff line change
Expand Up @@ -1804,6 +1804,16 @@ added: v15.6.0

The issuer identification included in this certificate.

### `x509.issuerCertificate`
<!-- YAML
added: REPLACEME
-->

* Type: {X509Certificate}

The issuer certificate (if known). Will be `undefined` if the issuer
certificate is not available.

### `x509.keyUsage`
<!-- YAML
added: v15.6.0
Expand Down
24 changes: 24 additions & 0 deletions doc/api/tls.md
Original file line number Diff line number Diff line change
Expand Up @@ -1213,6 +1213,30 @@ It may be useful for debugging.

See [Session Resumption][] for more information.

### `tlsSocket.getPeerX509Certificate()`
<!-- YAML
added: REPLACEME
-->

* Returns: {X509Certificate}

Returns the peer certificate as an {X509Certificate} object.

If there is no peer certificate, or the socket has been destroyed,
`undefined` will be returned.

### `tlsSocket.getX509Certificate()`
<!-- YAML
added: REPLACEME
-->

* Returns: {X509Certificate}

Returns the local certificate as an {X509Certificate} object.

If there is no local certificate, or the socket has been destroyed,
`undefined` will be returned.

### `tlsSocket.isSessionReused()`
<!-- YAML
added: v0.5.6
Expand Down
13 changes: 13 additions & 0 deletions lib/_tls_wrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ const {
validateBuffer,
validateUint32
} = require('internal/validators');
const {
InternalX509Certificate
} = require('internal/crypto/x509');
const traceTls = getOptionValue('--trace-tls');
const tlsKeylog = getOptionValue('--tls-keylog');
const { appendFile } = require('fs');
Expand Down Expand Up @@ -999,6 +1002,16 @@ TLSSocket.prototype.getCertificate = function() {
return null;
};

TLSSocket.prototype.getPeerX509Certificate = function(detailed) {
const cert = this._handle?.getPeerX509Certificate();
return cert ? new InternalX509Certificate(cert) : undefined;
};

TLSSocket.prototype.getX509Certificate = function() {
const cert = this._handle?.getX509Certificate();
return cert ? new InternalX509Certificate(cert) : undefined;
};

// Proxy TLSSocket handle methods
function makeSocketMethodProxy(name) {
return function socketMethodProxy(...args) {
Expand Down
29 changes: 20 additions & 9 deletions lib/internal/crypto/x509.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,15 @@ function getFlags(options = {}) {
return flags;
}

class InternalX509Certificate extends JSTransferable {
[kInternalState] = new SafeMap();

constructor(handle) {
super();
this[kHandle] = handle;
}
}

class X509Certificate extends JSTransferable {
[kInternalState] = new SafeMap();

Expand Down Expand Up @@ -168,6 +177,17 @@ class X509Certificate extends JSTransferable {
return value;
}

get issuerCertificate() {
let value = this[kInternalState].get('issuerCertificate');
if (value === undefined) {
const cert = this[kHandle].getIssuerCert();
if (cert)
value = new InternalX509Certificate(this[kHandle].getIssuerCert());
this[kInternalState].set('issuerCertificate', value);
}
return value;
}

get infoAccess() {
let value = this[kInternalState].get('infoAccess');
if (value === undefined) {
Expand Down Expand Up @@ -313,15 +333,6 @@ class X509Certificate extends JSTransferable {
}
}

class InternalX509Certificate extends JSTransferable {
[kInternalState] = new SafeMap();

constructor(handle) {
super();
this[kHandle] = handle;
}
}

InternalX509Certificate.prototype.constructor = X509Certificate;
ObjectSetPrototypeOf(
InternalX509Certificate.prototype,
Expand Down
26 changes: 26 additions & 0 deletions src/crypto/crypto_tls.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1591,6 +1591,20 @@ void TLSWrap::GetPeerCertificate(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(ret);
}

void TLSWrap::GetPeerX509Certificate(const FunctionCallbackInfo<Value>& args) {
TLSWrap* w;
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
Environment* env = w->env();

X509Certificate::GetPeerCertificateFlag flag = w->is_server()
? X509Certificate::GetPeerCertificateFlag::SERVER
: X509Certificate::GetPeerCertificateFlag::NONE;

Local<Value> ret;
if (X509Certificate::GetPeerCert(env, w->ssl_, flag).ToLocal(&ret))
args.GetReturnValue().Set(ret);
}

void TLSWrap::GetCertificate(const FunctionCallbackInfo<Value>& args) {
TLSWrap* w;
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
Expand All @@ -1601,6 +1615,15 @@ void TLSWrap::GetCertificate(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(ret);
}

void TLSWrap::GetX509Certificate(const FunctionCallbackInfo<Value>& args) {
TLSWrap* w;
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
Environment* env = w->env();
Local<Value> ret;
if (X509Certificate::GetCert(env, w->ssl_).ToLocal(&ret))
args.GetReturnValue().Set(ret);
}

void TLSWrap::GetFinished(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);

Expand Down Expand Up @@ -2051,11 +2074,14 @@ void TLSWrap::Initialize(
env->SetProtoMethodNoSideEffect(t, "getALPNNegotiatedProtocol",
GetALPNNegotiatedProto);
env->SetProtoMethodNoSideEffect(t, "getCertificate", GetCertificate);
env->SetProtoMethodNoSideEffect(t, "getX509Certificate", GetX509Certificate);
env->SetProtoMethodNoSideEffect(t, "getCipher", GetCipher);
env->SetProtoMethodNoSideEffect(t, "getEphemeralKeyInfo",
GetEphemeralKeyInfo);
env->SetProtoMethodNoSideEffect(t, "getFinished", GetFinished);
env->SetProtoMethodNoSideEffect(t, "getPeerCertificate", GetPeerCertificate);
env->SetProtoMethodNoSideEffect(t, "getPeerX509Certificate",
GetPeerX509Certificate);
env->SetProtoMethodNoSideEffect(t, "getPeerFinished", GetPeerFinished);
env->SetProtoMethodNoSideEffect(t, "getProtocol", GetProtocol);
env->SetProtoMethodNoSideEffect(t, "getSession", GetSession);
Expand Down
4 changes: 4 additions & 0 deletions src/crypto/crypto_tls.h
Original file line number Diff line number Diff line change
Expand Up @@ -184,12 +184,16 @@ class TLSWrap : public AsyncWrap,
static void GetALPNNegotiatedProto(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetCertificate(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetX509Certificate(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetCipher(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetEphemeralKeyInfo(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetFinished(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetPeerCertificate(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetPeerX509Certificate(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetPeerFinished(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetProtocol(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetServername(const v8::FunctionCallbackInfo<v8::Value>& args);
Expand Down
60 changes: 34 additions & 26 deletions src/crypto/crypto_x509.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

namespace node {

using v8::Array;
using v8::ArrayBufferView;
using v8::Context;
using v8::EscapableHandleScope;
Expand Down Expand Up @@ -82,6 +81,7 @@ Local<FunctionTemplate> X509Certificate::GetConstructorTemplate(
env->SetProtoMethod(tmpl, "checkPrivateKey", CheckPrivateKey);
env->SetProtoMethod(tmpl, "verify", Verify);
env->SetProtoMethod(tmpl, "toLegacy", ToLegacy);
env->SetProtoMethod(tmpl, "getIssuerCert", GetIssuerCert);
env->set_x509_constructor_template(tmpl);
}
return tmpl;
Expand All @@ -93,14 +93,16 @@ bool X509Certificate::HasInstance(Environment* env, Local<Object> object) {

MaybeLocal<Object> X509Certificate::New(
Environment* env,
X509Pointer cert) {
X509Pointer cert,
STACK_OF(X509)* issuer_chain) {
std::shared_ptr<ManagedX509> mcert(new ManagedX509(std::move(cert)));
return New(env, std::move(mcert));
return New(env, std::move(mcert), issuer_chain);
}

MaybeLocal<Object> X509Certificate::New(
Environment* env,
std::shared_ptr<ManagedX509> cert) {
std::shared_ptr<ManagedX509> cert,
STACK_OF(X509)* issuer_chain) {
EscapableHandleScope scope(env->isolate());
Local<Function> ctor;
if (!GetConstructorTemplate(env)->GetFunction(env->context()).ToLocal(&ctor))
Expand All @@ -110,7 +112,7 @@ MaybeLocal<Object> X509Certificate::New(
if (!ctor->NewInstance(env->context()).ToLocal(&obj))
return MaybeLocal<Object>();

new X509Certificate(env, obj, std::move(cert));
new X509Certificate(env, obj, std::move(cert), issuer_chain);
return scope.Escape(obj);
}

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

X509Pointer ptr(cert);
X509Pointer ptr(X509_dup(cert));
return New(env, std::move(ptr));
}

MaybeLocal<Object> X509Certificate::GetPeerCert(
Environment* env,
const SSLPointer& ssl,
GetPeerCertificateFlag flag) {
EscapableHandleScope scope(env->isolate());
ClearErrorOnReturn clear_error_on_return;
Local<Object> obj;
MaybeLocal<Object> maybe_cert;

bool is_server =
static_cast<int>(flag) & static_cast<int>(GetPeerCertificateFlag::SERVER);
bool abbreviated =
static_cast<int>(flag)
& static_cast<int>(GetPeerCertificateFlag::ABBREVIATED);

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

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

if (!cert) cert.reset(sk_X509_value(ssl_certs, 0));
if (!X509Certificate::New(env, std::move(cert)).ToLocal(&obj))
return MaybeLocal<Object>();

certs.push_back(obj);

int count = sk_X509_num(ssl_certs);
if (!abbreviated) {
for (int i = 0; i < count; i++) {
cert.reset(X509_dup(sk_X509_value(ssl_certs, i)));
if (!cert || !X509Certificate::New(env, std::move(cert)).ToLocal(&obj))
return MaybeLocal<Object>();
certs.push_back(obj);
}
if (!cert) {
cert.reset(sk_X509_value(ssl_certs, 0));
sk_X509_delete(ssl_certs, 0);
}

return scope.Escape(Array::New(env->isolate(), certs.data(), certs.size()));
return sk_X509_num(ssl_certs)
? New(env, std::move(cert), ssl_certs)
: New(env, std::move(cert));
}

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

void X509Certificate::GetIssuerCert(const FunctionCallbackInfo<Value>& args) {
X509Certificate* cert;
ASSIGN_OR_RETURN_UNWRAP(&cert, args.Holder());
if (cert->issuer_cert_)
args.GetReturnValue().Set(cert->issuer_cert_->object());
}

X509Certificate::X509Certificate(
Environment* env,
Local<Object> object,
std::shared_ptr<ManagedX509> cert)
std::shared_ptr<ManagedX509> cert,
STACK_OF(X509)* issuer_chain)
: BaseObject(env, object),
cert_(std::move(cert)) {
MakeWeak();

if (issuer_chain != nullptr && sk_X509_num(issuer_chain)) {
X509Pointer cert(X509_dup(sk_X509_value(issuer_chain, 0)));
sk_X509_delete(issuer_chain, 0);
Local<Object> obj = sk_X509_num(issuer_chain)
? X509Certificate::New(env, std::move(cert), issuer_chain)
.ToLocalChecked()
: X509Certificate::New(env, std::move(cert))
.ToLocalChecked();
issuer_cert_.reset(Unwrap<X509Certificate>(obj));
}
}

void X509Certificate::MemoryInfo(MemoryTracker* tracker) const {
Expand Down
13 changes: 9 additions & 4 deletions src/crypto/crypto_x509.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class ManagedX509 : public MemoryRetainer {
class X509Certificate : public BaseObject {
public:
enum class GetPeerCertificateFlag {
ABBREVIATED,
NONE,
SERVER
};

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

static v8::MaybeLocal<v8::Object> New(
Environment* env,
X509Pointer cert);
X509Pointer cert,
STACK_OF(X509)* issuer_chain = nullptr);

static v8::MaybeLocal<v8::Object> New(
Environment* env,
std::shared_ptr<ManagedX509> cert);
std::shared_ptr<ManagedX509> cert,
STACK_OF(X509)* issuer_chain = nullptr);

static v8::MaybeLocal<v8::Object> GetCert(
Environment* env,
Expand Down Expand Up @@ -91,6 +93,7 @@ class X509Certificate : public BaseObject {
static void CheckPrivateKey(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Verify(const v8::FunctionCallbackInfo<v8::Value>& args);
static void ToLegacy(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetIssuerCert(const v8::FunctionCallbackInfo<v8::Value>& args);

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

Expand Down Expand Up @@ -124,9 +127,11 @@ class X509Certificate : public BaseObject {
X509Certificate(
Environment* env,
v8::Local<v8::Object> object,
std::shared_ptr<ManagedX509> cert);
std::shared_ptr<ManagedX509> cert,
STACK_OF(X509)* issuer_chain = nullptr);

std::shared_ptr<ManagedX509> cert_;
BaseObjectPtr<X509Certificate> issuer_cert_;
};

} // namespace crypto
Expand Down
Loading

0 comments on commit c3dd21b

Please sign in to comment.