From 5758470ee47063d4a0b65fcba1441dfb8a8ace54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Thu, 11 Apr 2024 21:31:11 +0100 Subject: [PATCH] =?UTF-8?q?Revert=20"refactor(ext/net):=20extract=20TLS=20?= =?UTF-8?q?key=20and=20certificate=20from=20inter=E2=80=A6=20(#23325)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …faces (#23296)" This reverts commit e190acbfa8b41f92291e73c405735ba0d7b5b172. Reverting because it broke stable API type declarations. We will reland it for v1.43 with updated interfaces --- cli/tsc/dts/lib.deno.ns.d.ts | 8 +- cli/tsc/dts/lib.deno.unstable.d.ts | 25 +--- ext/fetch/22_http_client.js | 7 +- ext/net/02_tls.js | 176 +++++++++++------------------ ext/net/lib.deno_net.d.ts | 139 ++++++----------------- tests/unit/tls_test.ts | 2 +- 6 files changed, 116 insertions(+), 241 deletions(-) diff --git a/cli/tsc/dts/lib.deno.ns.d.ts b/cli/tsc/dts/lib.deno.ns.d.ts index 4ec6873595..1d6b398f58 100644 --- a/cli/tsc/dts/lib.deno.ns.d.ts +++ b/cli/tsc/dts/lib.deno.ns.d.ts @@ -6276,7 +6276,13 @@ declare namespace Deno { * * @category HTTP Server */ - export type ServeTlsOptions = ServeOptions & TlsCertifiedKeyOptions; + export interface ServeTlsOptions extends ServeOptions { + /** Server private key in PEM format */ + cert: string; + + /** Cert chain in PEM format */ + key: string; + } /** * @category HTTP Server diff --git a/cli/tsc/dts/lib.deno.unstable.d.ts b/cli/tsc/dts/lib.deno.unstable.d.ts index ae3f60d283..056d8e6099 100644 --- a/cli/tsc/dts/lib.deno.unstable.d.ts +++ b/cli/tsc/dts/lib.deno.unstable.d.ts @@ -882,6 +882,10 @@ declare namespace Deno { caCerts?: string[]; /** A HTTP proxy to use for new connections. */ proxy?: Proxy; + /** Cert chain in PEM format. */ + cert?: string; + /** Server private key in PEM format. */ + key?: string; /** Sets the maximum numer of idle connections per host allowed in the pool. */ poolMaxIdlePerHost?: number; /** Set an optional timeout for idle sockets being kept-alive. @@ -958,27 +962,6 @@ declare namespace Deno { options: CreateHttpClientOptions, ): HttpClient; - /** **UNSTABLE**: New API, yet to be vetted. - * - * Create a custom HttpClient to use with {@linkcode fetch}. This is an - * extension of the web platform Fetch API which allows Deno to use custom - * TLS certificates and connect via a proxy while using `fetch()`. - * - * @example ```ts - * const caCert = await Deno.readTextFile("./ca.pem"); - * // Load a client key and certificate that we'll use to connect - * const key = await Deno.readTextFile("./key.key"); - * const cert = await Deno.readTextFile("./cert.crt"); - * const client = Deno.createHttpClient({ caCerts: [ caCert ], key, cert }); - * const response = await fetch("https://myserver.com", { client }); - * ``` - * - * @category Fetch API - */ - export function createHttpClient( - options: CreateHttpClientOptions & TlsCertifiedKeyOptions, - ): HttpClient; - /** **UNSTABLE**: New API, yet to be vetted. * * Represents membership of a IPv4 multicast group. diff --git a/ext/fetch/22_http_client.js b/ext/fetch/22_http_client.js index 061a3dda89..e1389bbe1f 100644 --- a/ext/fetch/22_http_client.js +++ b/ext/fetch/22_http_client.js @@ -25,7 +25,12 @@ const { ObjectDefineProperty } = primordials; */ function createHttpClient(options) { options.caCerts ??= []; - const keyPair = loadTlsKeyPair("Deno.createHttpClient", options); + const keyPair = loadTlsKeyPair( + options.cert, + undefined, + options.key, + undefined, + ); return new HttpClient( op_fetch_custom_client( options, diff --git a/ext/net/02_tls.js b/ext/net/02_tls.js index 003c5336a7..8d43e8604f 100644 --- a/ext/net/02_tls.js +++ b/ext/net/02_tls.js @@ -51,46 +51,54 @@ async function connectTls({ port, hostname = "127.0.0.1", transport = "tcp", - caCerts = [], - alpnProtocols = undefined, - keyFormat = undefined, - cert = undefined, certFile = undefined, + caCerts = [], certChain = undefined, - key = undefined, - keyFile = undefined, privateKey = undefined, + cert = undefined, + key = undefined, + alpnProtocols = undefined, }) { - if (transport !== "tcp") { - throw new TypeError(`Unsupported transport: '${transport}'`); - } - let deprecatedCertFile = undefined; - - // Deno.connectTls has an irregular option where you can just pass `certFile` and - // not `keyFile`. In this case it's used for `caCerts` rather than the client key. - if (certFile !== undefined && keyFile === undefined) { + if (certFile !== undefined) { internals.warnOnDeprecatedApi( "Deno.ConnectTlsOptions.certFile", new Error().stack, - "Pass the cert file's contents to the `Deno.ConnectTlsOptions.caCerts` option instead.", + "Pass the cert file contents to the `Deno.ConnectTlsOptions.cert` option instead.", ); - - deprecatedCertFile = certFile; - certFile = undefined; } - - const keyPair = loadTlsKeyPair("Deno.connectTls", { - keyFormat, - cert, - certFile, - certChain, - key, - keyFile, - privateKey, - }); + 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 keyPair = loadTlsKeyPair(cert, undefined, key, undefined); const { 0: rid, 1: localAddr, 2: remoteAddr } = await op_net_connect_tls( { hostname, port }, - { certFile: deprecatedCertFile, caCerts, alpnProtocols }, + { certFile, caCerts, cert, key, alpnProtocols }, keyPair, ); localAddr.transport = "tcp"; @@ -129,96 +137,29 @@ class TlsListener extends Listener { } } -/** - * Returns true if this object has the shape of one of the certified key material - * interfaces. - */ function hasTlsKeyPairOptions(options) { return (ReflectHas(options, "cert") || ReflectHas(options, "key") || ReflectHas(options, "certFile") || - ReflectHas(options, "keyFile") || ReflectHas(options, "privateKey") || - ReflectHas(options, "certChain")); + ReflectHas(options, "keyFile")); } -/** - * Loads a TLS keypair from one of the various options. If no key material is provided, - * returns a special Null keypair. - */ -function loadTlsKeyPair(api, { - keyFormat, +function loadTlsKeyPair( cert, certFile, - certChain, key, keyFile, - privateKey, -}) { - // Check for "pem" format - if (keyFormat !== undefined && keyFormat !== "pem") { - throw new TypeError('If `keyFormat` is specified, it must be "pem"'); +) { + if ((certFile !== undefined) ^ (keyFile !== undefined)) { + throw new TypeError( + "If certFile is specified, keyFile must also be specified", + ); } - - function exclusive(a1, a1v, a2, a2v) { - if (a1v !== undefined && a2v !== undefined) { - throw new TypeError( - `Cannot specify both \`${a1}\` and \`${a2}\` for \`${api}\`.`, - ); - } + if ((cert !== undefined) ^ (key !== undefined)) { + throw new TypeError("If cert is specified, key must also be specified"); } - // Ensure that only one pair is valid - exclusive("certChain", certChain, "cert", cert); - exclusive("certChain", certChain, "certFile", certFile); - exclusive("key", key, "keyFile", keyFile); - exclusive("key", key, "privateKey", privateKey); - - function both(a1, a1v, a2, a2v) { - if (a1v !== undefined && a2v === undefined) { - throw new TypeError( - `If \`${a1}\` is specified, \`${a2}\` must be specified as well for \`${api}\`.`, - ); - } - if (a1v === undefined && a2v !== undefined) { - throw new TypeError( - `If \`${a2}\` is specified, \`${a1}\` must be specified as well for \`${api}\`.`, - ); - } - } - - // Pick one pair of cert/key, certFile/keyFile or certChain/privateKey - both("cert", cert, "key", key); - both("certFile", certFile, "keyFile", keyFile); - both("certChain", certChain, "privateKey", privateKey); - if (certFile !== undefined) { - internals.warnOnDeprecatedApi( - "Deno.TlsCertifiedKeyOptions.keyFile", - new Error().stack, - "Pass the key file's contents to the `Deno.TlsCertifiedKeyPem.key` option instead.", - ); - internals.warnOnDeprecatedApi( - "Deno.TlsCertifiedKeyOptions.certFile", - new Error().stack, - "Pass the cert file's contents to the `Deno.TlsCertifiedKeyPem.cert` option instead.", - ); - return op_tls_key_static_from_file(api, certFile, keyFile); - } else if (certChain !== undefined) { - if (api !== "Deno.connectTls") { - throw new TypeError( - `Invalid options 'certChain' and 'privateKey' for ${api}`, - ); - } - internals.warnOnDeprecatedApi( - "Deno.TlsCertifiedKeyOptions.privateKey", - new Error().stack, - "Use the `Deno.TlsCertifiedKeyPem.key` option instead.", - ); - internals.warnOnDeprecatedApi( - "Deno.TlsCertifiedKeyOptions.certChain", - new Error().stack, - "Use the `Deno.TlsCertifiedKeyPem.cert` option instead.", - ); - return op_tls_key_static(certChain, privateKey); + return op_tls_key_static_from_file("Deno.listenTls", certFile, keyFile); } else if (cert !== undefined) { return op_tls_key_static(cert, key); } else { @@ -228,6 +169,10 @@ function loadTlsKeyPair(api, { function listenTls({ port, + cert, + certFile, + key, + keyFile, hostname = "0.0.0.0", transport = "tcp", alpnProtocols = undefined, @@ -236,11 +181,22 @@ function listenTls({ if (transport !== "tcp") { throw new TypeError(`Unsupported transport: '${transport}'`); } - - if (!hasTlsKeyPairOptions(arguments[0])) { - throw new TypeError("A key and certificate are required for `listenTls`"); + if (keyFile !== undefined) { + internals.warnOnDeprecatedApi( + "Deno.ListenTlsOptions.keyFile", + new Error().stack, + "Pass the key file contents to the `Deno.ListenTlsOptions.key` option instead.", + ); } - const keyPair = loadTlsKeyPair("Deno.listenTls", arguments[0]); + if (certFile !== undefined) { + internals.warnOnDeprecatedApi( + "Deno.ListenTlsOptions.certFile", + new Error().stack, + "Pass the cert file contents to the `Deno.ListenTlsOptions.cert` option instead.", + ); + } + + const keyPair = loadTlsKeyPair(cert, certFile, key, keyFile); const { 0: rid, 1: localAddr } = op_net_listen_tls( { hostname, port: Number(port) }, { alpnProtocols, reusePort }, diff --git a/ext/net/lib.deno_net.d.ts b/ext/net/lib.deno_net.d.ts index 4b88b154aa..597b3d3483 100644 --- a/ext/net/lib.deno_net.d.ts +++ b/ext/net/lib.deno_net.d.ts @@ -197,50 +197,12 @@ declare namespace Deno { options: UnixListenOptions & { transport: "unix" }, ): UnixListener; - /** Provides TLS certified keys, ie: a key that has been certified by a trusted certificate authority. - * A certified key generally consists of a private key and certificate part. - * - * @category Network - */ - export type TlsCertifiedKeyOptions = - | TlsCertifiedKeyPem - | TlsCertifiedKeyFromFile - | TlsCertifiedKeyConnectTls; - - /** - * Provides certified key material from strings. The key material is provided in - * `PEM`-format (Privacy Enhanced Mail, https://www.rfc-editor.org/rfc/rfc1422) which can be identified by having - * `-----BEGIN-----` and `-----END-----` markers at the beginning and end of the strings. This type of key is not compatible - * with `DER`-format keys which are binary. - * - * Deno supports RSA, EC, and PKCS8-format keys. - * - * ```ts - * const key = { - * key: "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n", - * cert: "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----\n" } - * }; - * ``` - * - * @category Network - */ - export interface TlsCertifiedKeyPem { - /** The format of this key material, which must be PEM. */ - keyFormat?: "pem"; - /** Private key in `PEM` format. RSA, EC, and PKCS8-format keys are supported. */ - key: string; - /** Certificate chain in `PEM` format. */ - cert: string; - } - - /** - * @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. - * - * @category Network - */ - export interface TlsCertifiedKeyFromFile { + /** @category Network */ + export interface ListenTlsOptions extends TcpListenOptions { + /** Server private key in PEM format */ + key?: string; + /** Cert chain in PEM format */ + cert?: string; /** Path to a file containing a PEM formatted CA certificate. Requires * `--allow-read`. * @@ -249,45 +211,16 @@ declare namespace Deno { * {@link https://docs.deno.com/runtime/manual/advanced/migrate_deprecations | Deno 1.x to 2.x Migration Guide} * for migration instructions. */ - certFile: string; - /** Path to a file containing a private key file. Requires `--allow-read`. + certFile?: string; + /** Server private key file. Requires `--allow-read`. * * @tags allow-read * @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. */ - keyFile: string; - } + keyFile?: string; - /** - * @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. - * - * @category Network - */ - export interface TlsCertifiedKeyConnectTls { - /** - * Certificate chain in `PEM` format. - * - * @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; - /** - * Private key in `PEM` format. RSA, EC, and PKCS8-format keys are supported. - * - * @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; - } - - /** @category Network */ - export interface ListenTlsOptions extends TcpListenOptions { transport?: "tcp"; /** Application-Layer Protocol Negotiation (ALPN) protocols to announce to @@ -313,9 +246,7 @@ declare namespace Deno { * @tags allow-net * @category Network */ - export function listenTls( - options: ListenTlsOptions & TlsCertifiedKeyOptions, - ): TlsListener; + export function listenTls(options: ListenTlsOptions): TlsListener; /** @category Network */ export interface ConnectOptions { @@ -412,11 +343,9 @@ declare namespace Deno { * * @default {"127.0.0.1"} */ hostname?: string; - /** Path to a file containing a PEM formatted list of root certificates that will - * be used in addition to the default root certificates to verify the peer's certificate. Requires - * `--allow-read`. + /** + * Server certificate file. * - * @tags allow-read * @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. @@ -432,6 +361,26 @@ declare namespace Deno { * TLS handshake. */ alpnProtocols?: string[]; + /** + * 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. + * + * @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 @@ -454,30 +403,6 @@ declare namespace Deno { */ export function connectTls(options: ConnectTlsOptions): Promise; - /** Establishes a secure connection over TLS (transport layer security) using - * an optional cert file, client certificate, hostname (default is "127.0.0.1") and - * port. The cert file is optional and if not included Mozilla's root certificates will - * be used (see also https://github.com/ctz/webpki-roots for specifics) - * - * ```ts - * const caCert = await Deno.readTextFile("./certs/my_custom_root_CA.pem"); - * const key = "----BEGIN PRIVATE KEY----..."; - * const cert = "----BEGIN CERTIFICATE----..."; - * const conn1 = await Deno.connectTls({ port: 80, key, cert }); - * const conn2 = await Deno.connectTls({ caCerts: [caCert], hostname: "192.0.2.1", port: 80, key, cert }); - * const conn3 = await Deno.connectTls({ hostname: "[2001:db8::1]", port: 80, key, cert }); - * const conn4 = await Deno.connectTls({ caCerts: [caCert], hostname: "golang.org", port: 80, key, cert }); - * ``` - * - * Requires `allow-net` permission. - * - * @tags allow-net - * @category Network - */ - export function connectTls( - options: ConnectTlsOptions & TlsCertifiedKeyOptions, - ): Promise; - /** @category Network */ export interface StartTlsOptions { /** A literal IP address or host name that can be resolved to an IP address. diff --git a/tests/unit/tls_test.ts b/tests/unit/tls_test.ts index 5be05b73e3..8f0a296c72 100644 --- a/tests/unit/tls_test.ts +++ b/tests/unit/tls_test.ts @@ -1336,7 +1336,7 @@ Deno.test( }); }, TypeError, - "Cannot specify both `key` and `privateKey` for `Deno.connectTls`.", + "Cannot specify both `privateKey` and `key`", ); }, );