summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/tsc/dts/lib.deno.ns.d.ts33
-rw-r--r--cli/tsc/dts/lib.deno.unstable.d.ts25
-rw-r--r--ext/fetch/22_http_client.js7
-rw-r--r--ext/net/02_tls.js176
-rw-r--r--ext/net/lib.deno_net.d.ts139
-rw-r--r--ext/net/ops_tls.rs16
-rw-r--r--tests/unit/tls_test.ts2
7 files changed, 274 insertions, 124 deletions
diff --git a/cli/tsc/dts/lib.deno.ns.d.ts b/cli/tsc/dts/lib.deno.ns.d.ts
index 1d6b398f5..c63b4a261 100644
--- a/cli/tsc/dts/lib.deno.ns.d.ts
+++ b/cli/tsc/dts/lib.deno.ns.d.ts
@@ -6277,11 +6277,23 @@ declare namespace Deno {
* @category HTTP Server
*/
export interface ServeTlsOptions extends ServeOptions {
- /** Server private key in PEM format */
- cert: string;
+ /**
+ * Server private key in PEM format. Use {@linkcode TlsCertifiedKeyOptions} instead.
+ *
+ * @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.
+ */
+ cert?: string;
- /** Cert chain in PEM format */
- key: string;
+ /**
+ * Cert chain in PEM format. Use {@linkcode TlsCertifiedKeyOptions} instead.
+ *
+ * @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.
+ */
+ key?: string;
}
/**
@@ -6490,7 +6502,10 @@ declare namespace Deno {
* @category HTTP Server
*/
export function serve(
- options: ServeOptions | ServeTlsOptions,
+ options:
+ | ServeOptions
+ | ServeTlsOptions
+ | (ServeTlsOptions & TlsCertifiedKeyOptions),
handler: ServeHandler,
): HttpServer;
/** Serves HTTP requests with the given option bag.
@@ -6546,6 +6561,12 @@ declare namespace Deno {
* @category HTTP Server
*/
export function serve(
- options: ServeInit & (ServeOptions | ServeTlsOptions),
+ options:
+ & ServeInit
+ & (
+ | ServeOptions
+ | ServeTlsOptions
+ | (ServeTlsOptions & TlsCertifiedKeyOptions)
+ ),
): HttpServer;
}
diff --git a/cli/tsc/dts/lib.deno.unstable.d.ts b/cli/tsc/dts/lib.deno.unstable.d.ts
index 056d8e609..ae3f60d28 100644
--- a/cli/tsc/dts/lib.deno.unstable.d.ts
+++ b/cli/tsc/dts/lib.deno.unstable.d.ts
@@ -882,10 +882,6 @@ 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.
@@ -964,6 +960,27 @@ declare namespace Deno {
/** **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.
*
* @category Network
diff --git a/ext/fetch/22_http_client.js b/ext/fetch/22_http_client.js
index e1389bbe1..061a3dda8 100644
--- a/ext/fetch/22_http_client.js
+++ b/ext/fetch/22_http_client.js
@@ -25,12 +25,7 @@ const { ObjectDefineProperty } = primordials;
*/
function createHttpClient(options) {
options.caCerts ??= [];
- const keyPair = loadTlsKeyPair(
- options.cert,
- undefined,
- options.key,
- undefined,
- );
+ const keyPair = loadTlsKeyPair("Deno.createHttpClient", options);
return new HttpClient(
op_fetch_custom_client(
options,
diff --git a/ext/net/02_tls.js b/ext/net/02_tls.js
index 8d43e8604..4216cbe22 100644
--- a/ext/net/02_tls.js
+++ b/ext/net/02_tls.js
@@ -51,54 +51,46 @@ async function connectTls({
port,
hostname = "127.0.0.1",
transport = "tcp",
- certFile = undefined,
caCerts = [],
- certChain = undefined,
- privateKey = undefined,
+ alpnProtocols = undefined,
+ keyFormat = undefined,
cert = undefined,
+ certFile = undefined,
+ certChain = undefined,
key = undefined,
- alpnProtocols = undefined,
+ keyFile = undefined,
+ privateKey = undefined,
}) {
- if (certFile !== undefined) {
- internals.warnOnDeprecatedApi(
- "Deno.ConnectTlsOptions.certFile",
- new Error().stack,
- "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`",
+ 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) {
+ internals.warnOnDeprecatedApi(
+ "Deno.ConnectTlsOptions.certFile",
+ new Error().stack,
+ "Pass the cert file's contents to the `Deno.ConnectTlsOptions.caCerts` option instead.",
);
+
+ deprecatedCertFile = certFile;
+ certFile = undefined;
}
- cert ??= certChain;
- key ??= privateKey;
- const keyPair = loadTlsKeyPair(cert, undefined, key, undefined);
+
+ const keyPair = loadTlsKeyPair("Deno.connectTls", {
+ keyFormat,
+ cert,
+ certFile,
+ certChain,
+ key,
+ keyFile,
+ privateKey,
+ });
const { 0: rid, 1: localAddr, 2: remoteAddr } = await op_net_connect_tls(
{ hostname, port },
- { certFile, caCerts, cert, key, alpnProtocols },
+ { certFile: deprecatedCertFile, caCerts, alpnProtocols },
keyPair,
);
localAddr.transport = "tcp";
@@ -137,29 +129,96 @@ 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, "keyFile") || ReflectHas(options, "privateKey") ||
+ ReflectHas(options, "certChain"));
}
-function loadTlsKeyPair(
+/**
+ * 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,
cert,
certFile,
+ certChain,
key,
keyFile,
-) {
- if ((certFile !== undefined) ^ (keyFile !== undefined)) {
- throw new TypeError(
- "If certFile is specified, keyFile must also be specified",
- );
+ privateKey,
+}) {
+ // Check for "pem" format
+ if (keyFormat !== undefined && keyFormat !== "pem") {
+ throw new TypeError('If `keyFormat` is specified, it must be "pem"');
}
- if ((cert !== undefined) ^ (key !== undefined)) {
- throw new TypeError("If cert is specified, key 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}\`.`,
+ );
+ }
}
+ // 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) {
- return op_tls_key_static_from_file("Deno.listenTls", certFile, keyFile);
+ 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);
} else if (cert !== undefined) {
return op_tls_key_static(cert, key);
} else {
@@ -169,10 +228,6 @@ function loadTlsKeyPair(
function listenTls({
port,
- cert,
- certFile,
- key,
- keyFile,
hostname = "0.0.0.0",
transport = "tcp",
alpnProtocols = undefined,
@@ -181,22 +236,13 @@ function listenTls({
if (transport !== "tcp") {
throw new TypeError(`Unsupported transport: '${transport}'`);
}
- if (keyFile !== undefined) {
- internals.warnOnDeprecatedApi(
- "Deno.ListenTlsOptions.keyFile",
- new Error().stack,
- "Pass the key file contents to the `Deno.ListenTlsOptions.key` option instead.",
- );
- }
- if (certFile !== undefined) {
- internals.warnOnDeprecatedApi(
- "Deno.ListenTlsOptions.certFile",
- new Error().stack,
- "Pass the cert file contents to the `Deno.ListenTlsOptions.cert` option instead.",
+
+ if (!hasTlsKeyPairOptions(arguments[0])) {
+ throw new TypeError(
+ "A key and certificate are required for `Deno.listenTls`",
);
}
-
- const keyPair = loadTlsKeyPair(cert, certFile, key, keyFile);
+ const keyPair = loadTlsKeyPair("Deno.listenTls", arguments[0]);
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 597b3d348..4b88b154a 100644
--- a/ext/net/lib.deno_net.d.ts
+++ b/ext/net/lib.deno_net.d.ts
@@ -197,12 +197,50 @@ declare namespace Deno {
options: UnixListenOptions & { transport: "unix" },
): UnixListener;
- /** @category Network */
- export interface ListenTlsOptions extends TcpListenOptions {
- /** Server private key in PEM format */
- key?: string;
- /** Cert chain in PEM format */
- cert?: string;
+ /** 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 {
/** Path to a file containing a PEM formatted CA certificate. Requires
* `--allow-read`.
*
@@ -211,16 +249,45 @@ 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;
- /** Server private key file. Requires `--allow-read`.
+ certFile: string;
+ /** Path to a file containing a 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
@@ -246,7 +313,9 @@ declare namespace Deno {
* @tags allow-net
* @category Network
*/
- export function listenTls(options: ListenTlsOptions): TlsListener;
+ export function listenTls(
+ options: ListenTlsOptions & TlsCertifiedKeyOptions,
+ ): TlsListener;
/** @category Network */
export interface ConnectOptions {
@@ -343,9 +412,11 @@ declare namespace Deno {
*
* @default {"127.0.0.1"} */
hostname?: string;
- /**
- * Server certificate file.
+ /** 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`.
*
+ * @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.
@@ -361,26 +432,6 @@ 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
@@ -403,6 +454,30 @@ declare namespace Deno {
*/
export function connectTls(options: ConnectTlsOptions): Promise<TlsConn>;
+ /** 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<TlsConn>;
+
/** @category Network */
export interface StartTlsOptions {
/** A literal IP address or host name that can be resolved to an IP address.
diff --git a/ext/net/ops_tls.rs b/ext/net/ops_tls.rs
index c0ac31586..487adf3bc 100644
--- a/ext/net/ops_tls.rs
+++ b/ext/net/ops_tls.rs
@@ -10,6 +10,7 @@ use crate::tcp::TcpListener;
use crate::DefaultTlsOptions;
use crate::NetPermissions;
use crate::UnsafelyIgnoreCertificateErrors;
+use deno_core::anyhow::anyhow;
use deno_core::error::bad_resource;
use deno_core::error::custom_error;
use deno_core::error::generic_error;
@@ -448,18 +449,13 @@ where
.with_no_client_auth();
let mut tls_config = match keys {
- TlsKeys::Null => {
- unreachable!()
- }
- TlsKeys::Static(TlsKey(cert, key)) => {
- tls_config.with_single_cert(cert.clone(), key.clone())
- }
+ TlsKeys::Null => Err(anyhow!("Deno.listenTls requires a key")),
+ TlsKeys::Static(TlsKey(cert, key)) => tls_config
+ .with_single_cert(cert.clone(), key.clone())
+ .map_err(|e| anyhow!(e)),
}
.map_err(|e| {
- custom_error(
- "InvalidData",
- format!("Error creating TLS certificate: {:?}", e),
- )
+ custom_error("InvalidData", "Error creating TLS certificate").context(e)
})?;
if let Some(alpn_protocols) = args.alpn_protocols {
diff --git a/tests/unit/tls_test.ts b/tests/unit/tls_test.ts
index 8f0a296c7..5be05b73e 100644
--- a/tests/unit/tls_test.ts
+++ b/tests/unit/tls_test.ts
@@ -1336,7 +1336,7 @@ Deno.test(
});
},
TypeError,
- "Cannot specify both `privateKey` and `key`",
+ "Cannot specify both `key` and `privateKey` for `Deno.connectTls`.",
);
},
);