Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(tls): custom in memory CA certificates #12219

Merged
merged 9 commits into from
Sep 30, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 27 additions & 14 deletions cli/dts/lib.deno.unstable.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -767,11 +767,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;
}

Expand Down Expand Up @@ -1194,11 +1199,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.
Expand All @@ -1216,30 +1221,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.
Expand Down
2 changes: 1 addition & 1 deletion cli/file_fetcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
84 changes: 38 additions & 46 deletions cli/http_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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(),
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions cli/tests/unit/http_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
Expand Down
70 changes: 66 additions & 4 deletions cli/tests/unit/tls_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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]);

Expand Down Expand Up @@ -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")],
}),
]);

Expand Down Expand Up @@ -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(
{ perms: { 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(
{ perms: { 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(
{ perms: { 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(
{ perms: { 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");
Expand Down
Loading