mirror of
https://github.com/denoland/deno.git
synced 2025-08-04 19:08:15 +00:00
fix(ext/node): Support mTLS connections node compatibility (#28937)
Support for running Node.js code that requires mTLS connections on Deno. Ref #21497 Closes https://github.com/denoland/deno/issues/26472 Closes https://github.com/denoland/deno/issues/29148 --------- Co-authored-by: Satya Rohith <me@satyarohith.com> Co-authored-by: Divy Srivastava <dj.srivastava23@gmail.com>
This commit is contained in:
parent
2edc70e679
commit
730f48b170
6 changed files with 139 additions and 5 deletions
|
@ -187,7 +187,7 @@ async function startTls(
|
|||
hostname,
|
||||
caCerts,
|
||||
alpnProtocols,
|
||||
});
|
||||
}, null);
|
||||
return new TlsConn(rid, remoteAddr, localAddr);
|
||||
}
|
||||
|
||||
|
|
|
@ -196,7 +196,7 @@ pub fn op_tls_key_null() -> TlsKeysHolder {
|
|||
TlsKeysHolder::from(TlsKeys::Null)
|
||||
}
|
||||
|
||||
#[op2]
|
||||
#[op2(reentrant)]
|
||||
#[cppgc]
|
||||
pub fn op_tls_key_static(
|
||||
#[string] cert: &str,
|
||||
|
@ -258,6 +258,7 @@ pub fn op_tls_cert_resolver_resolve_error(
|
|||
pub fn op_tls_start<NP>(
|
||||
state: Rc<RefCell<OpState>>,
|
||||
#[serde] args: StartTlsArgs,
|
||||
#[cppgc] key_pair: Option<&TlsKeysHolder>,
|
||||
) -> Result<(ResourceId, IpAddr, IpAddr), NetError>
|
||||
where
|
||||
NP: NetPermissions + 'static,
|
||||
|
@ -304,11 +305,13 @@ where
|
|||
let local_addr = tcp_stream.local_addr()?;
|
||||
let remote_addr = tcp_stream.peer_addr()?;
|
||||
|
||||
let tls_null = TlsKeysHolder::from(TlsKeys::Null);
|
||||
let key_pair = key_pair.unwrap_or(&tls_null);
|
||||
let mut tls_config = create_client_config(
|
||||
root_cert_store,
|
||||
ca_certs,
|
||||
unsafely_ignore_certificate_errors,
|
||||
TlsKeys::Null,
|
||||
key_pair.take(),
|
||||
SocketUse::GeneralSsl,
|
||||
)?;
|
||||
|
||||
|
|
|
@ -9,6 +9,8 @@ import {
|
|||
op_node_http_await_response,
|
||||
op_node_http_fetch_response_upgrade,
|
||||
op_node_http_request_with_conn,
|
||||
op_tls_key_null,
|
||||
op_tls_key_static,
|
||||
op_tls_start,
|
||||
} from "ext:core/ops";
|
||||
|
||||
|
@ -515,12 +517,21 @@ class ClientRequest extends OutgoingMessage {
|
|||
|
||||
let baseConnRid = handle[kStreamBaseField][internalRidSymbol];
|
||||
if (this._encrypted) {
|
||||
const hasCaCerts = this.agent?.options?.ca !== undefined;
|
||||
const caCerts = hasCaCerts
|
||||
? [this.agent.options.ca.toString("UTF-8")]
|
||||
: [];
|
||||
const hasTlsKey = this.agent?.options?.key !== undefined &&
|
||||
this.agent?.options?.cert !== undefined;
|
||||
const keyPair = hasTlsKey
|
||||
? op_tls_key_static(this.agent.options.cert, this.agent.options.key)
|
||||
: op_tls_key_null();
|
||||
[baseConnRid] = op_tls_start({
|
||||
rid: baseConnRid,
|
||||
hostname: parsedUrl.hostname,
|
||||
caCerts: [],
|
||||
caCerts: caCerts,
|
||||
alpnProtocols: ["http/1.0", "http/1.1"],
|
||||
});
|
||||
}, keyPair);
|
||||
}
|
||||
|
||||
this._req = await op_node_http_request_with_conn(
|
||||
|
|
|
@ -170,6 +170,27 @@ export function request(...args: any[]) {
|
|||
}
|
||||
|
||||
options._defaultAgent = globalAgent;
|
||||
if (options.agent === undefined) {
|
||||
if (options.key !== undefined) {
|
||||
options._defaultAgent.options.key = options.key;
|
||||
}
|
||||
if (options.cert !== undefined) {
|
||||
options._defaultAgent.options.cert = options.cert;
|
||||
}
|
||||
if (options.ca !== undefined) {
|
||||
options._defaultAgent.options.ca = options.ca;
|
||||
}
|
||||
} else {
|
||||
if (options.key !== undefined) {
|
||||
options.agent.options.key = options.key;
|
||||
}
|
||||
if (options.cert !== undefined) {
|
||||
options.agent.options.cert = options.cert;
|
||||
}
|
||||
if (options.ca !== undefined) {
|
||||
options.agent.options.ca = options.ca;
|
||||
}
|
||||
}
|
||||
args.unshift(options);
|
||||
|
||||
return new HttpsClientRequest(args[0], args[1]);
|
||||
|
|
|
@ -75,6 +75,7 @@ util::unit_test_factory!(
|
|||
fs_test,
|
||||
fetch_test,
|
||||
http_test,
|
||||
http_no_cert_flag_test,
|
||||
http2_test,
|
||||
inspector_test,
|
||||
_randomBytes_test = internal / _randomBytes_test,
|
||||
|
|
98
tests/unit_node/http_no_cert_flag_test.ts
Normal file
98
tests/unit_node/http_no_cert_flag_test.ts
Normal file
|
@ -0,0 +1,98 @@
|
|||
// Copyright 2018-2025 the Deno authors. MIT license.
|
||||
|
||||
import https from "node:https";
|
||||
import net from "node:net";
|
||||
|
||||
import { assert, assertEquals } from "@std/assert";
|
||||
|
||||
Deno.test("[node/https] request directly with key and cert as arguments", async () => {
|
||||
let body = "";
|
||||
const deferred1 = Promise.withResolvers<void>();
|
||||
const deferred2 = Promise.withResolvers<void>();
|
||||
const server = https.createServer(
|
||||
{
|
||||
cert: Deno.readTextFileSync("tests/testdata/tls/localhost.crt"),
|
||||
key: Deno.readTextFileSync("tests/testdata/tls/localhost.key"),
|
||||
},
|
||||
(req, res) => {
|
||||
// @ts-ignore: It exists on TLSSocket
|
||||
assert(req.socket.encrypted);
|
||||
res.end("success!");
|
||||
},
|
||||
);
|
||||
server.listen(() => {
|
||||
const req = https.request({
|
||||
method: "GET",
|
||||
hostname: "localhost",
|
||||
port: (server.address() as net.AddressInfo).port,
|
||||
key: Deno.readTextFileSync("tests/testdata/tls/localhost.key"),
|
||||
cert: Deno.readTextFileSync("tests/testdata/tls/localhost.crt"),
|
||||
ca: Deno.readTextFileSync("tests/testdata/tls/RootCA.pem"),
|
||||
}, (resp) => {
|
||||
resp.on("data", (chunk) => {
|
||||
body += chunk;
|
||||
});
|
||||
|
||||
resp.on("end", () => {
|
||||
deferred2.resolve();
|
||||
server.close();
|
||||
});
|
||||
});
|
||||
req.on("error", (e) => deferred2.reject(e));
|
||||
req.end();
|
||||
});
|
||||
|
||||
server.on("close", () => deferred1.resolve());
|
||||
server.on("error", (e) => deferred1.reject(e));
|
||||
|
||||
await deferred1.promise;
|
||||
await deferred2.promise;
|
||||
assertEquals(body, "success!");
|
||||
});
|
||||
|
||||
Deno.test("[node/https] request directly with key and cert as agent", async () => {
|
||||
let body = "";
|
||||
const deferred1 = Promise.withResolvers<void>();
|
||||
const deferred2 = Promise.withResolvers<void>();
|
||||
const server = https.createServer(
|
||||
{
|
||||
cert: Deno.readTextFileSync("tests/testdata/tls/localhost.crt"),
|
||||
key: Deno.readTextFileSync("tests/testdata/tls/localhost.key"),
|
||||
},
|
||||
(req, res) => {
|
||||
// @ts-ignore: It exists on TLSSocket
|
||||
assert(req.socket.encrypted);
|
||||
res.end("success!");
|
||||
},
|
||||
);
|
||||
server.listen(() => {
|
||||
const req = https.request({
|
||||
method: "GET",
|
||||
hostname: "localhost",
|
||||
port: (server.address() as never as net.AddressInfo).port,
|
||||
agent: new https.Agent({
|
||||
key: Deno.readTextFileSync("tests/testdata/tls/localhost.key"),
|
||||
cert: Deno.readTextFileSync("tests/testdata/tls/localhost.crt"),
|
||||
ca: Deno.readTextFileSync("tests/testdata/tls/RootCA.pem"),
|
||||
}),
|
||||
}, (resp) => {
|
||||
resp.on("data", (chunk) => {
|
||||
body += chunk;
|
||||
});
|
||||
|
||||
resp.on("end", () => {
|
||||
deferred2.resolve();
|
||||
server.close();
|
||||
});
|
||||
});
|
||||
req.on("error", (e) => deferred2.reject(e));
|
||||
req.end();
|
||||
});
|
||||
|
||||
server.on("close", () => deferred1.resolve());
|
||||
server.on("error", (e) => deferred1.reject(e));
|
||||
|
||||
await deferred1.promise;
|
||||
await deferred2.promise;
|
||||
assertEquals(body, "success!");
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue