1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-21 15:04:11 -05:00

feat: Deno.ConnectTlsOptions.{cert,key} (#22274)

Towards #22197
This commit is contained in:
Asher Gomez 2024-02-19 01:30:58 +11:00 committed by GitHub
parent 3c7057d583
commit 9a43a2b495
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 185 additions and 19 deletions

View file

@ -51,21 +51,49 @@ async function connectTls({
caCerts = [],
certChain = undefined,
privateKey = undefined,
cert = undefined,
key = undefined,
alpnProtocols = undefined,
}) {
if (certFile !== undefined) {
internals.warnOnDeprecatedApi(
"Deno.ConnectTlsOptions.certFile",
new Error().stack,
"Pass the cert file contents to the `Deno.ConnectTlsOptions.certChain` option instead.",
"Pass the cert file contents to the `Deno.ConnectTlsOptions.cert` option instead.",
);
}
if (certChain !== undefined) {
internals.warnOnDeprecatedApi(
"Deno.ConnectTlsOptions.certChain",
new Error().stack,
"Use the `Deno.ConnectTlsOptions.cert` option instead.",
);
}
if (privateKey !== undefined) {
internals.warnOnDeprecatedApi(
"Deno.ConnectTlsOptions.privateKey",
new Error().stack,
"Use the `Deno.ConnectTlsOptions.key` option instead.",
);
}
if (transport !== "tcp") {
throw new TypeError(`Unsupported transport: '${transport}'`);
}
if (certChain !== undefined && cert !== undefined) {
throw new TypeError(
"Cannot specify both `certChain` and `cert`",
);
}
if (privateKey !== undefined && key !== undefined) {
throw new TypeError(
"Cannot specify both `privateKey` and `key`",
);
}
cert ??= certChain;
key ??= privateKey;
const { 0: rid, 1: localAddr, 2: remoteAddr } = await op_net_connect_tls(
{ hostname, port },
{ certFile, caCerts, certChain, privateKey, alpnProtocols },
{ certFile, caCerts, cert, key, alpnProtocols },
);
localAddr.transport = "tcp";
remoteAddr.transport = "tcp";

View file

@ -348,10 +348,26 @@ declare namespace Deno {
* TLS handshake.
*/
alpnProtocols?: string[];
/** PEM formatted client certificate chain. */
/**
* PEM formatted client certificate chain.
*
* @deprecated This will be removed in Deno 2.0. See the
* {@link https://docs.deno.com/runtime/manual/advanced/migrate_deprecations | Deno 1.x to 2.x Migration Guide}
* for migration instructions.
*/
certChain?: string;
/** PEM formatted (RSA or PKCS8) private key of client certificate. */
/**
* PEM formatted (RSA or PKCS8) private key of client certificate.
*
* @deprecated This will be removed in Deno 2.0. See the
* {@link https://docs.deno.com/runtime/manual/advanced/migrate_deprecations | Deno 1.x to 2.x Migration Guide}
* for migration instructions.
*/
privateKey?: string;
/** Server private key in PEM format. */
key?: string;
/** Cert chain in PEM format. */
cert?: string;
}
/** Establishes a secure connection over TLS (transport layer security) using

View file

@ -145,8 +145,8 @@ impl Resource for TlsStreamResource {
pub struct ConnectTlsArgs {
cert_file: Option<String>,
ca_certs: Vec<String>,
cert_chain: Option<String>,
private_key: Option<String>,
cert: Option<String>,
key: Option<String>,
alpn_protocols: Option<Vec<String>>,
}
@ -297,24 +297,23 @@ where
let local_addr = tcp_stream.local_addr()?;
let remote_addr = tcp_stream.peer_addr()?;
let cert_chain_and_key =
if args.cert_chain.is_some() || args.private_key.is_some() {
let cert_chain = args
.cert_chain
.ok_or_else(|| type_error("No certificate chain provided"))?;
let private_key = args
.private_key
.ok_or_else(|| type_error("No private key provided"))?;
Some((cert_chain, private_key))
} else {
None
};
let cert_and_key = if args.cert.is_some() || args.key.is_some() {
let cert = args
.cert
.ok_or_else(|| type_error("No certificate chain provided"))?;
let key = args
.key
.ok_or_else(|| type_error("No private key provided"))?;
Some((cert, key))
} else {
None
};
let mut tls_config = create_client_config(
root_cert_store,
ca_certs,
unsafely_ignore_certificate_errors,
cert_chain_and_key,
cert_and_key,
SocketUse::GeneralSsl,
)?;

View file

@ -1174,6 +1174,22 @@ Deno.test(
},
);
Deno.test(
{ permissions: { read: true, net: true } },
async function connectTLSBadCertKey(): Promise<void> {
await assertRejects(async () => {
await Deno.connectTls({
hostname: "deno.land",
port: 443,
cert: "bad data",
key: await Deno.readTextFile(
"tests/testdata/tls/localhost.key",
),
});
}, Deno.errors.InvalidData);
},
);
Deno.test(
{ permissions: { read: true, net: true } },
async function connectTLSBadPrivateKey(): Promise<void> {
@ -1190,6 +1206,22 @@ Deno.test(
},
);
Deno.test(
{ permissions: { read: true, net: true } },
async function connectTLSBadKey(): Promise<void> {
await assertRejects(async () => {
await Deno.connectTls({
hostname: "deno.land",
port: 443,
cert: await Deno.readTextFile(
"tests/testdata/tls/localhost.crt",
),
key: "bad data",
});
}, Deno.errors.InvalidData);
},
);
Deno.test(
{ permissions: { read: true, net: true } },
async function connectTLSNotPrivateKey(): Promise<void> {
@ -1206,6 +1238,22 @@ Deno.test(
},
);
Deno.test(
{ permissions: { read: true, net: true } },
async function connectTLSNotKey(): Promise<void> {
await assertRejects(async () => {
await Deno.connectTls({
hostname: "deno.land",
port: 443,
cert: await Deno.readTextFile(
"tests/testdata/tls/localhost.crt",
),
key: "",
});
}, Deno.errors.InvalidData);
},
);
Deno.test(
{ permissions: { read: true, net: true } },
async function connectWithClientCert() {
@ -1231,6 +1279,81 @@ Deno.test(
},
);
Deno.test(
{ permissions: { read: true, net: true } },
async function connectWithCert() {
// The test_server running on port 4552 responds with 'PASS' if client
// authentication was successful. Try it by running test_server and
// curl --key cli/tests/testdata/tls/localhost.key \
// --cert cli/tests/testdata/tls/localhost.crt \
// --cacert cli/tests/testdata/tls/RootCA.crt https://localhost:4552/
const conn = await Deno.connectTls({
hostname: "localhost",
port: 4552,
cert: await Deno.readTextFile(
"tests/testdata/tls/localhost.crt",
),
key: await Deno.readTextFile(
"tests/testdata/tls/localhost.key",
),
caCerts: [Deno.readTextFileSync("tests/testdata/tls/RootCA.pem")],
});
const result = decoder.decode(await readAll(conn));
assertEquals(result, "PASS");
conn.close();
},
);
Deno.test(
{ permissions: { read: true, net: true } },
async function connectTlsConflictingCertOptions(): Promise<void> {
await assertRejects(
async () => {
await Deno.connectTls({
hostname: "deno.land",
port: 443,
cert: await Deno.readTextFile(
"tests/testdata/tls/localhost.crt",
),
certChain: await Deno.readTextFile(
"tests/testdata/tls/localhost.crt",
),
key: await Deno.readTextFile(
"tests/testdata/tls/localhost.key",
),
});
},
TypeError,
"Cannot specify both `certChain` and `cert`",
);
},
);
Deno.test(
{ permissions: { read: true, net: true } },
async function connectTlsConflictingKeyOptions(): Promise<void> {
await assertRejects(
async () => {
await Deno.connectTls({
hostname: "deno.land",
port: 443,
cert: await Deno.readTextFile(
"tests/testdata/tls/localhost.crt",
),
privateKey: await Deno.readTextFile(
"tests/testdata/tls/localhost.crt",
),
key: await Deno.readTextFile(
"tests/testdata/tls/localhost.key",
),
});
},
TypeError,
"Cannot specify both `privateKey` and `key`",
);
},
);
Deno.test(
{ permissions: { read: true, net: true } },
async function connectTLSCaCerts() {