Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tls: add ability to get cert/peer cert as X509Certificate object #37070

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 or `undefined` if the issuer certificate is not
available.

### `x509.keyUsage`
<!-- YAML
added: v15.6.0
Expand Down
94 changes: 59 additions & 35 deletions doc/api/tls.md
Original file line number Diff line number Diff line change
Expand Up @@ -904,6 +904,41 @@ added: v0.11.4
Always returns `true`. This may be used to distinguish TLS sockets from regular
`net.Socket` instances.

### `tlsSocket.exportKeyingMaterial(length, label[, context])`
<!-- YAML
added:
- v13.10.0
- v12.17.0
-->

* `length` {number} number of bytes to retrieve from keying material
* `label` {string} an application specific label, typically this will be a
value from the
[IANA Exporter Label Registry](https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#exporter-labels).
* `context` {Buffer} Optionally provide a context.

* Returns: {Buffer} requested bytes of the keying material

Keying material is used for validations to prevent different kind of attacks in
network protocols, for example in the specifications of IEEE 802.1X.

Example

```js
const keyingMaterial = tlsSocket.exportKeyingMaterial(
128,
'client finished');

/**
Example return value of keyingMaterial:
<Buffer 76 26 af 99 c5 56 8e 42 09 91 ef 9f 93 cb ad 6c 7b 65 f8 53 f1 d8 d9
12 5a 33 b8 b5 25 df 7b 37 9f e0 e2 4f b8 67 83 a3 2f cd 5d 41 42 4c 91
74 ef 2c ... 78 more bytes>
*/
```
See the OpenSSL [`SSL_export_keying_material`][] documentation for more
information.

### `tlsSocket.getCertificate()`
<!-- YAML
added: v11.2.0
Expand Down Expand Up @@ -1113,6 +1148,18 @@ provided by SSL/TLS is not desired or is not enough.
Corresponds to the `SSL_get_peer_finished` routine in OpenSSL and may be used
to implement the `tls-unique` channel binding from [RFC 5929][].

### `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.getProtocol()`
<!-- YAML
added: v5.7.0
Expand Down Expand Up @@ -1164,41 +1211,6 @@ See
[SSL_get_shared_sigalgs](https://www.openssl.org/docs/man1.1.1/man3/SSL_get_shared_sigalgs.html)
for more information.

### `tlsSocket.exportKeyingMaterial(length, label[, context])`
<!-- YAML
added:
- v13.10.0
- v12.17.0
-->

* `length` {number} number of bytes to retrieve from keying material
* `label` {string} an application specific label, typically this will be a
value from the
[IANA Exporter Label Registry](https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#exporter-labels).
* `context` {Buffer} Optionally provide a context.

* Returns: {Buffer} requested bytes of the keying material

Keying material is used for validations to prevent different kind of attacks in
network protocols, for example in the specifications of IEEE 802.1X.

Example

```js
const keyingMaterial = tlsSocket.exportKeyingMaterial(
128,
'client finished');

/**
Example return value of keyingMaterial:
<Buffer 76 26 af 99 c5 56 8e 42 09 91 ef 9f 93 cb ad 6c 7b 65 f8 53 f1 d8 d9
12 5a 33 b8 b5 25 df 7b 37 9f e0 e2 4f b8 67 83 a3 2f cd 5d 41 42 4c 91
74 ef 2c ... 78 more bytes>
*/
```
See the OpenSSL [`SSL_export_keying_material`][] documentation for more
information.

### `tlsSocket.getTLSTicket()`
<!-- YAML
added: v0.11.4
Expand All @@ -1213,6 +1225,18 @@ It may be useful for debugging.

See [Session Resumption][] for more information.

### `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 @@ -90,6 +90,9 @@ const {
validateString,
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 @@ -998,6 +1001,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
Loading