tls: add setKeyCert() to tls.Socket

PR-URL: https://github.com/nodejs/node/pull/53636
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Tim Perry <pimterry@gmail.com>
This commit is contained in:
mscdex 2024-07-15 11:17:59 -04:00 committed by GitHub
parent 4b4a9319d9
commit 6ba0af1354
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 110 additions and 0 deletions

View File

@ -1574,6 +1574,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

View File

@ -1144,6 +1144,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) {

View File

@ -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.This());
@ -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);

View File

@ -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);

View File

@ -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();
}));
});
})();