Skip to content

Commit

Permalink
tls: add setKeyCert() to tls.Socket
Browse files Browse the repository at this point in the history
PR-URL: #53636
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Tim Perry <pimterry@gmail.com>
  • Loading branch information
mscdex authored and marco-ippolito committed Aug 19, 2024
1 parent e31f97b commit f862f66
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 0 deletions.
14 changes: 14 additions & 0 deletions doc/api/tls.md
Original file line number Diff line number Diff line change
Expand Up @@ -1533,6 +1533,20 @@ When running as the server, the socket will be destroyed with an error after
For TLSv1.3, renegotiation cannot be initiated, it is not supported by the
protocol.

### `tlsSocket.setKeyCert(context)`

<!-- YAML
added: REPLACEME
-->

* `context` {Object|tls.SecureContext} An object containing at least `key` and
`cert` properties from the [`tls.createSecureContext()`][] `options`, or a
TLS context object created with [`tls.createSecureContext()`][] itself.

The `tlsSocket.setKeyCert()` method sets the private key and certificate to use
for the socket. This is mainly useful if you wish to select a server certificate
from a TLS server's `ALPNCallback`.

### `tlsSocket.setMaxSendFragment(size)`

<!-- YAML
Expand Down
11 changes: 11 additions & 0 deletions lib/_tls_wrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -1151,6 +1151,17 @@ TLSSocket.prototype.getX509Certificate = function() {
return cert ? new InternalX509Certificate(cert) : undefined;
};

TLSSocket.prototype.setKeyCert = function(context) {
if (this._handle) {
let secureContext;
if (context instanceof common.SecureContext)
secureContext = context;
else
secureContext = tls.createSecureContext(context);
this._handle.setKeyCert(secureContext.context);
}
};

// Proxy TLSSocket handle methods
function makeSocketMethodProxy(name) {
return function socketMethodProxy(...args) {
Expand Down
28 changes: 28 additions & 0 deletions src/crypto/crypto_tls.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1595,6 +1595,33 @@ void TLSWrap::SetALPNProtocols(const FunctionCallbackInfo<Value>& args) {
}
}

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

if (w->is_client()) return;

if (args.Length() < 1 || !args[0]->IsObject())
return env->ThrowTypeError("Must give a SecureContext as first argument");

Local<Value> ctx = args[0];
if (UNLIKELY(ctx.IsEmpty())) return;

Local<FunctionTemplate> cons = env->secure_context_constructor_template();
if (cons->HasInstance(ctx)) {
SecureContext* sc = Unwrap<SecureContext>(ctx.As<Object>());
CHECK_NOT_NULL(sc);
if (!UseSNIContext(w->ssl_, BaseObjectPtr<SecureContext>(sc)) ||
!w->SetCACerts(sc)) {
unsigned long err = ERR_get_error(); // NOLINT(runtime/int)
return ThrowCryptoError(env, err, "SetKeyCert");
}
} else {
return env->ThrowTypeError("Must give a SecureContext as first argument");
}
}

void TLSWrap::GetPeerCertificate(const FunctionCallbackInfo<Value>& args) {
TLSWrap* w;
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
Expand Down Expand Up @@ -2130,6 +2157,7 @@ void TLSWrap::Initialize(
SetProtoMethod(isolate, t, "renegotiate", Renegotiate);
SetProtoMethod(isolate, t, "requestOCSP", RequestOCSP);
SetProtoMethod(isolate, t, "setALPNProtocols", SetALPNProtocols);
SetProtoMethod(isolate, t, "setKeyCert", SetKeyCert);
SetProtoMethod(isolate, t, "setOCSPResponse", SetOCSPResponse);
SetProtoMethod(isolate, t, "setServername", SetServername);
SetProtoMethod(isolate, t, "setSession", SetSession);
Expand Down
1 change: 1 addition & 0 deletions src/crypto/crypto_tls.h
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ class TLSWrap : public AsyncWrap,
static void Renegotiate(const v8::FunctionCallbackInfo<v8::Value>& args);
static void RequestOCSP(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetALPNProtocols(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetKeyCert(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetOCSPResponse(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetServername(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetSession(const v8::FunctionCallbackInfo<v8::Value>& args);
Expand Down
56 changes: 56 additions & 0 deletions test/parallel/test-tls-server-setkeycert.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
'use strict';
const common = require('../common');

if (!common.hasCrypto)
common.skip('missing crypto');

const assert = require('assert');
const { X509Certificate } = require('crypto');
const tls = require('tls');
const fixtures = require('../common/fixtures');

const altKeyCert = {
key: fixtures.readKey('agent2-key.pem'),
cert: fixtures.readKey('agent2-cert.pem'),
minVersion: 'TLSv1.2',
};

const altKeyCertVals = [
altKeyCert,
tls.createSecureContext(altKeyCert),
];

(function next() {
if (!altKeyCertVals.length)
return;
const altKeyCertVal = altKeyCertVals.shift();
const options = {
key: fixtures.readKey('agent1-key.pem'),
cert: fixtures.readKey('agent1-cert.pem'),
minVersion: 'TLSv1.3',
ALPNCallback: common.mustCall(function({ servername, protocols }) {
this.setKeyCert(altKeyCertVal);
assert.deepStrictEqual(protocols, ['acme-tls/1']);
return protocols[0];
}),
};

tls.createServer(options, (s) => s.end()).listen(0, function() {
this.on('connection', common.mustCall((socket) => this.close()));

tls.connect({
port: this.address().port,
rejectUnauthorized: false,
ALPNProtocols: ['acme-tls/1'],
}, common.mustCall(function() {
assert.strictEqual(this.getProtocol(), 'TLSv1.3');
const altCert = new X509Certificate(altKeyCert.cert);
assert.strictEqual(
this.getPeerX509Certificate().raw.equals(altCert.raw),
true
);
this.end();
next();
}));
});
})();

0 comments on commit f862f66

Please sign in to comment.