summaryrefslogtreecommitdiff
path: root/cli
diff options
context:
space:
mode:
authorLuca Casonato <hello@lcas.dev>2021-09-30 09:26:15 +0200
committerGitHub <noreply@github.com>2021-09-30 09:26:15 +0200
commit0d7a417f332a57fb3e89250a1ce250b929d0b2f7 (patch)
tree8f63043fcf6c5419d6d213a196c54a8b421e3d8b /cli
parent62920e4ef5bed131c125c4b8b5bdb8250584946f (diff)
feat(tls): custom in memory CA certificates (#12219)
This adds support for using in memory CA certificates for `Deno.startTLS`, `Deno.connectTLS` and `Deno.createHttpClient`. `certFile` is deprecated in `startTls` and `connectTls`, and removed from `Deno.createHttpClient`.
Diffstat (limited to 'cli')
-rw-r--r--cli/dts/lib.deno.unstable.d.ts47
-rw-r--r--cli/file_fetcher.rs2
-rw-r--r--cli/http_util.rs84
-rw-r--r--cli/tests/unit/README.md2
-rw-r--r--cli/tests/unit/fetch_test.ts39
-rw-r--r--cli/tests/unit/http_test.ts4
-rw-r--r--cli/tests/unit/tls_test.ts70
7 files changed, 147 insertions, 101 deletions
diff --git a/cli/dts/lib.deno.unstable.d.ts b/cli/dts/lib.deno.unstable.d.ts
index bfc4536f0..db59980ec 100644
--- a/cli/dts/lib.deno.unstable.d.ts
+++ b/cli/dts/lib.deno.unstable.d.ts
@@ -754,7 +754,8 @@ declare namespace Deno {
* A custom HttpClient for use with `fetch`.
*
* ```ts
- * const client = Deno.createHttpClient({ caData: await Deno.readTextFile("./ca.pem") });
+ * const caCert = await Deno.readTextFile("./ca.pem");
+ * const client = Deno.createHttpClient({ caCerts: [ caCert ] });
* const req = await fetch("https://myserver.com", { client });
* ```
*/
@@ -767,11 +768,16 @@ declare namespace Deno {
* The options used when creating a [HttpClient].
*/
export interface CreateHttpClientOptions {
- /** A certificate authority to use when validating TLS certificates. Certificate data must be PEM encoded.
- */
- caData?: string;
+ /** A list of root certificates that will be used in addition to the
+ * default root certificates to verify the peer's certificate.
+ *
+ * Must be in PEM format. */
+ caCerts?: string[];
+ /** A HTTP proxy to use for new connections. */
proxy?: Proxy;
+ /** PEM formatted client certificate chain. */
certChain?: string;
+ /** PEM formatted (RSA or PKCS8) private key of client certificate. */
privateKey?: string;
}
@@ -789,7 +795,8 @@ declare namespace Deno {
* Create a custom HttpClient for to use with `fetch`.
*
* ```ts
- * const client = Deno.createHttpClient({ caData: await Deno.readTextFile("./ca.pem") });
+ * const caCert = await Deno.readTextFile("./ca.pem");
+ * const client = Deno.createHttpClient({ caCerts: [ caCert ] });
* const response = await fetch("https://myserver.com", { client });
* ```
*
@@ -1194,11 +1201,11 @@ declare namespace Deno {
options: ConnectOptions | UnixConnectOptions,
): Promise<Conn>;
- export interface ConnectTlsClientCertOptions {
+ export interface ConnectTlsOptions {
/** PEM formatted client certificate chain. */
- certChain: string;
+ certChain?: string;
/** PEM formatted (RSA or PKCS8) private key of client certificate. */
- privateKey: string;
+ privateKey?: string;
}
/** **UNSTABLE** New API, yet to be vetted.
@@ -1216,30 +1223,38 @@ declare namespace Deno {
*
* Requires `allow-net` permission.
*/
- export function connectTls(
- options: ConnectTlsOptions & ConnectTlsClientCertOptions,
- ): Promise<Conn>;
+ export function connectTls(options: ConnectTlsOptions): Promise<Conn>;
export interface StartTlsOptions {
/** A literal IP address or host name that can be resolved to an IP address.
* If not specified, defaults to `127.0.0.1`. */
hostname?: string;
- /** Server certificate file. */
+ /**
+ * @deprecated This option is deprecated and will be removed in a future
+ * release.
+ *
+ * Server certificate file.
+ */
certFile?: string;
+ /** A list of root certificates that will be used in addition to the
+ * default root certificates to verify the peer's certificate.
+ *
+ * Must be in PEM format. */
+ caCerts?: string[];
}
/** **UNSTABLE**: new API, yet to be vetted.
*
* Start TLS handshake from an existing connection using
- * an optional cert file, hostname (default is "127.0.0.1"). 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)
+ * an optional cert file, hostname (default is "127.0.0.1"). Specifying CA
+ * certs is optional. By default the configured root certificates are used.
* Using this function requires that the other end of the connection is
* prepared for TLS handshake.
*
* ```ts
* const conn = await Deno.connect({ port: 80, hostname: "127.0.0.1" });
- * const tlsConn = await Deno.startTls(conn, { certFile: "./certs/my_custom_root_CA.pem", hostname: "localhost" });
+ * const caCert = await Deno.readTextFile("./certs/my_custom_root_CA.pem");
+ * const tlsConn = await Deno.startTls(conn, { caCerts: [caCert], hostname: "localhost" });
* ```
*
* Requires `allow-net` permission.
diff --git a/cli/file_fetcher.rs b/cli/file_fetcher.rs
index 131685e49..c4c89fec1 100644
--- a/cli/file_fetcher.rs
+++ b/cli/file_fetcher.rs
@@ -255,7 +255,7 @@ impl FileFetcher {
http_client: create_http_client(
get_user_agent(),
root_cert_store,
- None,
+ vec![],
None,
unsafely_ignore_certificate_errors,
None,
diff --git a/cli/http_util.rs b/cli/http_util.rs
index 61b1abcbe..521acadfa 100644
--- a/cli/http_util.rs
+++ b/cli/http_util.rs
@@ -143,11 +143,11 @@ mod tests {
use deno_tls::create_http_client;
use std::fs::read;
- fn create_test_client(ca_data: Option<Vec<u8>>) -> Client {
+ fn create_test_client() -> Client {
create_http_client(
"test_client".to_string(),
None,
- ca_data,
+ vec![],
None,
None,
None,
@@ -160,7 +160,7 @@ mod tests {
let _http_server_guard = test_util::http_server();
// Relies on external http server. See target/debug/test_server
let url = Url::parse("http://127.0.0.1:4545/fixture.json").unwrap();
- let client = create_test_client(None);
+ let client = create_test_client();
let result = fetch_once(FetchOnceArgs {
client,
url,
@@ -184,7 +184,7 @@ mod tests {
// Relies on external http server. See target/debug/test_server
let url = Url::parse("http://127.0.0.1:4545/053_import_compression/gziped")
.unwrap();
- let client = create_test_client(None);
+ let client = create_test_client();
let result = fetch_once(FetchOnceArgs {
client,
url,
@@ -209,7 +209,7 @@ mod tests {
async fn test_fetch_with_etag() {
let _http_server_guard = test_util::http_server();
let url = Url::parse("http://127.0.0.1:4545/etag_script.ts").unwrap();
- let client = create_test_client(None);
+ let client = create_test_client();
let result = fetch_once(FetchOnceArgs {
client: client.clone(),
url: url.clone(),
@@ -245,7 +245,7 @@ mod tests {
// Relies on external http server. See target/debug/test_server
let url = Url::parse("http://127.0.0.1:4545/053_import_compression/brotli")
.unwrap();
- let client = create_test_client(None);
+ let client = create_test_client();
let result = fetch_once(FetchOnceArgs {
client,
url,
@@ -274,7 +274,7 @@ mod tests {
let url = Url::parse("http://127.0.0.1:4546/fixture.json").unwrap();
// Dns resolver substitutes `127.0.0.1` with `localhost`
let target_url = Url::parse("http://localhost:4545/fixture.json").unwrap();
- let client = create_test_client(None);
+ let client = create_test_client();
let result = fetch_once(FetchOnceArgs {
client,
url,
@@ -336,15 +336,13 @@ mod tests {
let client = create_http_client(
version::get_user_agent(),
None,
- Some(
- read(
- test_util::testdata_path()
- .join("tls/RootCA.pem")
- .to_str()
- .unwrap(),
- )
- .unwrap(),
- ),
+ vec![read(
+ test_util::testdata_path()
+ .join("tls/RootCA.pem")
+ .to_str()
+ .unwrap(),
+ )
+ .unwrap()],
None,
None,
None,
@@ -375,7 +373,7 @@ mod tests {
let client = create_http_client(
version::get_user_agent(),
None, // This will load mozilla certs by default
- None,
+ vec![],
None,
None,
None,
@@ -408,7 +406,7 @@ mod tests {
let client = create_http_client(
version::get_user_agent(),
Some(deno_tls::rustls::RootCertStore::empty()), // no certs loaded at all
- None,
+ vec![],
None,
None,
None,
@@ -439,15 +437,13 @@ mod tests {
let client = create_http_client(
version::get_user_agent(),
None,
- Some(
- read(
- test_util::testdata_path()
- .join("tls/RootCA.pem")
- .to_str()
- .unwrap(),
- )
- .unwrap(),
- ),
+ vec![read(
+ test_util::testdata_path()
+ .join("tls/RootCA.pem")
+ .to_str()
+ .unwrap(),
+ )
+ .unwrap()],
None,
None,
None,
@@ -480,15 +476,13 @@ mod tests {
let client = create_http_client(
version::get_user_agent(),
None,
- Some(
- read(
- test_util::testdata_path()
- .join("tls/RootCA.pem")
- .to_str()
- .unwrap(),
- )
- .unwrap(),
- ),
+ vec![read(
+ test_util::testdata_path()
+ .join("tls/RootCA.pem")
+ .to_str()
+ .unwrap(),
+ )
+ .unwrap()],
None,
None,
None,
@@ -534,15 +528,13 @@ mod tests {
let client = create_http_client(
version::get_user_agent(),
None,
- Some(
- read(
- test_util::testdata_path()
- .join("tls/RootCA.pem")
- .to_str()
- .unwrap(),
- )
- .unwrap(),
- ),
+ vec![read(
+ test_util::testdata_path()
+ .join("tls/RootCA.pem")
+ .to_str()
+ .unwrap(),
+ )
+ .unwrap()],
None,
None,
None,
@@ -574,7 +566,7 @@ mod tests {
let _g = test_util::http_server();
let url_str = "http://127.0.0.1:4545/bad_redirect";
let url = Url::parse(url_str).unwrap();
- let client = create_test_client(None);
+ let client = create_test_client();
let result = fetch_once(FetchOnceArgs {
client,
url,
diff --git a/cli/tests/unit/README.md b/cli/tests/unit/README.md
index fef3ea98b..adaa1ed0e 100644
--- a/cli/tests/unit/README.md
+++ b/cli/tests/unit/README.md
@@ -16,7 +16,7 @@ unitTest(function simpleTestFn(): void {
unitTest(
{
ignore: Deno.build.os === "windows",
- perms: { read: true, write: true },
+ permissions: { read: true, write: true },
},
function complexTestFn(): void {
// test code here
diff --git a/cli/tests/unit/fetch_test.ts b/cli/tests/unit/fetch_test.ts
index 5bce2af43..a2bd1741b 100644
--- a/cli/tests/unit/fetch_test.ts
+++ b/cli/tests/unit/fetch_test.ts
@@ -997,40 +997,16 @@ unitTest(function fetchResponseEmptyConstructor() {
assertEquals([...response.headers], []);
});
-// TODO(lucacasonato): reenable this test
unitTest(
- { permissions: { net: true }, ignore: true },
+ { permissions: { net: true, read: true } },
async function fetchCustomHttpClientParamCertificateSuccess(): Promise<
void
> {
- const client = Deno.createHttpClient(
- {
- caData: `-----BEGIN CERTIFICATE-----
-MIIDIzCCAgugAwIBAgIJAMKPPW4tsOymMA0GCSqGSIb3DQEBCwUAMCcxCzAJBgNV
-BAYTAlVTMRgwFgYDVQQDDA9FeGFtcGxlLVJvb3QtQ0EwIBcNMTkxMDIxMTYyODIy
-WhgPMjExODA5MjcxNjI4MjJaMCcxCzAJBgNVBAYTAlVTMRgwFgYDVQQDDA9FeGFt
-cGxlLVJvb3QtQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDMH/IO
-2qtHfyBKwANNPB4K0q5JVSg8XxZdRpTTlz0CwU0oRO3uHrI52raCCfVeiQutyZop
-eFZTDWeXGudGAFA2B5m3orWt0s+touPi8MzjsG2TQ+WSI66QgbXTNDitDDBtTVcV
-5G3Ic+3SppQAYiHSekLISnYWgXLl+k5CnEfTowg6cjqjVr0KjL03cTN3H7b+6+0S
-ws4rYbW1j4ExR7K6BFNH6572yq5qR20E6GqlY+EcOZpw4CbCk9lS8/CWuXze/vMs
-OfDcc6K+B625d27wyEGZHedBomT2vAD7sBjvO8hn/DP1Qb46a8uCHR6NSfnJ7bXO
-G1igaIbgY1zXirNdAgMBAAGjUDBOMB0GA1UdDgQWBBTzut+pwwDfqmMYcI9KNWRD
-hxcIpTAfBgNVHSMEGDAWgBTzut+pwwDfqmMYcI9KNWRDhxcIpTAMBgNVHRMEBTAD
-AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQB9AqSbZ+hEglAgSHxAMCqRFdhVu7MvaQM0
-P090mhGlOCt3yB7kdGfsIrUW6nQcTz7PPQFRaJMrFHPvFvPootkBUpTYR4hTkdce
-H6RCRu2Jxl4Y9bY/uezd9YhGCYfUtfjA6/TH9FcuZfttmOOlxOt01XfNvVMIR6RM
-z/AYhd+DeOXjr35F/VHeVpnk+55L0PYJsm1CdEbOs5Hy1ecR7ACuDkXnbM4fpz9I
-kyIWJwk2zJReKcJMgi1aIinDM9ao/dca1G99PHOw8dnr4oyoTiv8ao6PWiSRHHMi
-MNf4EgWfK+tZMnuqfpfO9740KzfcVoMNo4QJD4yn5YxroUOO/Azi
------END CERTIFICATE-----
-`,
- },
- );
- const response = await fetch(
- "https://localhost:5545/fixture.json",
- { client },
- );
+ const caCert = Deno.readTextFileSync("cli/tests/testdata/tls/RootCA.pem");
+ const client = Deno.createHttpClient({ caCerts: [caCert] });
+ const response = await fetch("https://localhost:5545/fixture.json", {
+ client,
+ });
const json = await response.json();
assertEquals(json.name, "deno");
client.close();
@@ -1250,6 +1226,7 @@ unitTest(
void
> {
const data = "Hello World";
+ const caCert = await Deno.readTextFile("cli/tests/testdata/tls/RootCA.crt");
const client = Deno.createHttpClient({
certChain: await Deno.readTextFile(
"cli/tests/testdata/tls/localhost.crt",
@@ -1257,7 +1234,7 @@ unitTest(
privateKey: await Deno.readTextFile(
"cli/tests/testdata/tls/localhost.key",
),
- caData: await Deno.readTextFile("cli/tests/testdata/tls/RootCA.crt"),
+ caCerts: [caCert],
});
const response = await fetch("https://localhost:5552/echo_server", {
client,
diff --git a/cli/tests/unit/http_test.ts b/cli/tests/unit/http_test.ts
index 7cca8d89e..fe6f1aba2 100644
--- a/cli/tests/unit/http_test.ts
+++ b/cli/tests/unit/http_test.ts
@@ -233,8 +233,8 @@ unitTest(
listener.close();
})();
- const caData = Deno.readTextFileSync("cli/tests/testdata/tls/RootCA.pem");
- const client = Deno.createHttpClient({ caData });
+ const caCert = Deno.readTextFileSync("cli/tests/testdata/tls/RootCA.pem");
+ const client = Deno.createHttpClient({ caCerts: [caCert] });
const resp = await fetch(`https://${hostname}:${port}/`, {
client,
headers: { "connection": "close" },
diff --git a/cli/tests/unit/tls_test.ts b/cli/tests/unit/tls_test.ts
index 391c51504..b2382833f 100644
--- a/cli/tests/unit/tls_test.ts
+++ b/cli/tests/unit/tls_test.ts
@@ -182,7 +182,7 @@ unitTest(
const conn = await Deno.connectTls({
hostname,
port,
- certFile: "cli/tests/testdata/tls/RootCA.pem",
+ caCerts: [Deno.readTextFileSync("cli/tests/testdata/tls/RootCA.pem")],
});
assert(conn.rid > 0);
const w = new BufWriter(conn);
@@ -230,7 +230,7 @@ async function tlsPair(): Promise<[Deno.Conn, Deno.Conn]> {
const connectPromise = Deno.connectTls({
hostname: "localhost",
port,
- certFile: "cli/tests/testdata/tls/RootCA.pem",
+ caCerts: [Deno.readTextFileSync("cli/tests/testdata/tls/RootCA.pem")],
});
const endpoints = await Promise.all([acceptPromise, connectPromise]);
@@ -570,7 +570,7 @@ async function tlsWithTcpFailureTestImpl(
Deno.connectTls({
hostname: "localhost",
port: tcpPort,
- certFile: "cli/tests/testdata/tls/RootCA.crt",
+ caCerts: [Deno.readTextFileSync("cli/tests/testdata/tls/RootCA.pem")],
}),
]);
@@ -1052,7 +1052,69 @@ unitTest(
privateKey: await Deno.readTextFile(
"cli/tests/testdata/tls/localhost.key",
),
- certFile: "cli/tests/testdata/tls/RootCA.crt",
+ caCerts: [Deno.readTextFileSync("cli/tests/testdata/tls/RootCA.pem")],
+ });
+ const result = decoder.decode(await readAll(conn));
+ assertEquals(result, "PASS");
+ conn.close();
+ },
+);
+
+unitTest(
+ { permissions: { read: true, net: true } },
+ async function connectTLSCaCerts() {
+ const conn = await Deno.connectTls({
+ hostname: "localhost",
+ port: 4557,
+ caCerts: [Deno.readTextFileSync("cli/tests/testdata/tls/RootCA.pem")],
+ });
+ const result = decoder.decode(await readAll(conn));
+ assertEquals(result, "PASS");
+ conn.close();
+ },
+);
+
+unitTest(
+ { permissions: { read: true, net: true } },
+ async function connectTLSCertFile() {
+ const conn = await Deno.connectTls({
+ hostname: "localhost",
+ port: 4557,
+ certFile: "cli/tests/testdata/tls/RootCA.pem",
+ });
+ const result = decoder.decode(await readAll(conn));
+ assertEquals(result, "PASS");
+ conn.close();
+ },
+);
+
+unitTest(
+ { permissions: { read: true, net: true } },
+ async function startTLSCaCerts() {
+ const plainConn = await Deno.connect({
+ hostname: "localhost",
+ port: 4557,
+ });
+ const conn = await Deno.startTls(plainConn, {
+ hostname: "localhost",
+ caCerts: [Deno.readTextFileSync("cli/tests/testdata/tls/RootCA.pem")],
+ });
+ const result = decoder.decode(await readAll(conn));
+ assertEquals(result, "PASS");
+ conn.close();
+ },
+);
+
+unitTest(
+ { permissions: { read: true, net: true } },
+ async function startTLSCertFile() {
+ const plainConn = await Deno.connect({
+ hostname: "localhost",
+ port: 4557,
+ });
+ const conn = await Deno.startTls(plainConn, {
+ hostname: "localhost",
+ certFile: "cli/tests/testdata/tls/RootCA.pem",
});
const result = decoder.decode(await readAll(conn));
assertEquals(result, "PASS");