1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-25 15:29:32 -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 = [], caCerts = [],
certChain = undefined, certChain = undefined,
privateKey = undefined, privateKey = undefined,
cert = undefined,
key = undefined,
alpnProtocols = undefined, alpnProtocols = undefined,
}) { }) {
if (certFile !== undefined) { if (certFile !== undefined) {
internals.warnOnDeprecatedApi( internals.warnOnDeprecatedApi(
"Deno.ConnectTlsOptions.certFile", "Deno.ConnectTlsOptions.certFile",
new Error().stack, 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") { if (transport !== "tcp") {
throw new TypeError(`Unsupported transport: '${transport}'`); 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( const { 0: rid, 1: localAddr, 2: remoteAddr } = await op_net_connect_tls(
{ hostname, port }, { hostname, port },
{ certFile, caCerts, certChain, privateKey, alpnProtocols }, { certFile, caCerts, cert, key, alpnProtocols },
); );
localAddr.transport = "tcp"; localAddr.transport = "tcp";
remoteAddr.transport = "tcp"; remoteAddr.transport = "tcp";

View file

@ -348,10 +348,26 @@ declare namespace Deno {
* TLS handshake. * TLS handshake.
*/ */
alpnProtocols?: string[]; 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; 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; 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 /** Establishes a secure connection over TLS (transport layer security) using

View file

@ -145,8 +145,8 @@ impl Resource for TlsStreamResource {
pub struct ConnectTlsArgs { pub struct ConnectTlsArgs {
cert_file: Option<String>, cert_file: Option<String>,
ca_certs: Vec<String>, ca_certs: Vec<String>,
cert_chain: Option<String>, cert: Option<String>,
private_key: Option<String>, key: Option<String>,
alpn_protocols: Option<Vec<String>>, alpn_protocols: Option<Vec<String>>,
} }
@ -297,24 +297,23 @@ where
let local_addr = tcp_stream.local_addr()?; let local_addr = tcp_stream.local_addr()?;
let remote_addr = tcp_stream.peer_addr()?; let remote_addr = tcp_stream.peer_addr()?;
let cert_chain_and_key = let cert_and_key = if args.cert.is_some() || args.key.is_some() {
if args.cert_chain.is_some() || args.private_key.is_some() { let cert = args
let cert_chain = args .cert
.cert_chain .ok_or_else(|| type_error("No certificate chain provided"))?;
.ok_or_else(|| type_error("No certificate chain provided"))?; let key = args
let private_key = args .key
.private_key .ok_or_else(|| type_error("No private key provided"))?;
.ok_or_else(|| type_error("No private key provided"))?; Some((cert, key))
Some((cert_chain, private_key)) } else {
} else { None
None };
};
let mut tls_config = create_client_config( let mut tls_config = create_client_config(
root_cert_store, root_cert_store,
ca_certs, ca_certs,
unsafely_ignore_certificate_errors, unsafely_ignore_certificate_errors,
cert_chain_and_key, cert_and_key,
SocketUse::GeneralSsl, 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( Deno.test(
{ permissions: { read: true, net: true } }, { permissions: { read: true, net: true } },
async function connectTLSBadPrivateKey(): Promise<void> { 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( Deno.test(
{ permissions: { read: true, net: true } }, { permissions: { read: true, net: true } },
async function connectTLSNotPrivateKey(): Promise<void> { 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( Deno.test(
{ permissions: { read: true, net: true } }, { permissions: { read: true, net: true } },
async function connectWithClientCert() { 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( Deno.test(
{ permissions: { read: true, net: true } }, { permissions: { read: true, net: true } },
async function connectTLSCaCerts() { async function connectTLSCaCerts() {