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>

PR-URL: #37070
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
Reviewed-By: Filip Skokan <panva.ip@gmail.com>
  • Loading branch information
jasnell committed Feb 2, 2021
1 parent 84e41d2 commit c973d50
Show file tree
Hide file tree
Showing 9 changed files with 218 additions and 74 deletions.
10 changes: 10 additions & 0 deletions doc/api/crypto.md
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
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
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
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
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
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

0 comments on commit c973d50

Please sign in to comment.