mirror of
https://github.com/denoland/deno.git
synced 2024-12-26 00:59:24 -05:00
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`.
This commit is contained in:
parent
62920e4ef5
commit
0d7a417f33
15 changed files with 266 additions and 160 deletions
47
cli/dts/lib.deno.unstable.d.ts
vendored
47
cli/dts/lib.deno.unstable.d.ts
vendored
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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" },
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
* @returns {HttpClient}
|
||||
*/
|
||||
function createHttpClient(options) {
|
||||
options.caCerts ??= [];
|
||||
return new HttpClient(core.opSync("op_create_http_client", options));
|
||||
}
|
||||
|
||||
|
|
|
@ -40,8 +40,6 @@ use serde::Serialize;
|
|||
use std::borrow::Cow;
|
||||
use std::cell::RefCell;
|
||||
use std::convert::From;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::pin::Pin;
|
||||
|
@ -87,7 +85,7 @@ pub fn init<P: FetchPermissions + 'static>(
|
|||
create_http_client(
|
||||
user_agent.clone(),
|
||||
root_cert_store.clone(),
|
||||
None,
|
||||
vec![],
|
||||
proxy.clone(),
|
||||
unsafely_ignore_certificate_errors.clone(),
|
||||
client_cert_chain_and_key.clone(),
|
||||
|
@ -465,13 +463,10 @@ impl HttpClientResource {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default, Debug)]
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(default)]
|
||||
pub struct CreateHttpClientOptions {
|
||||
ca_stores: Option<Vec<String>>,
|
||||
ca_file: Option<String>,
|
||||
ca_data: Option<ByteString>,
|
||||
ca_certs: Vec<String>,
|
||||
proxy: Option<Proxy>,
|
||||
cert_chain: Option<String>,
|
||||
private_key: Option<String>,
|
||||
|
@ -485,11 +480,6 @@ pub fn op_create_http_client<FP>(
|
|||
where
|
||||
FP: FetchPermissions + 'static,
|
||||
{
|
||||
if let Some(ca_file) = args.ca_file.clone() {
|
||||
let permissions = state.borrow_mut::<FP>();
|
||||
permissions.check_read(&PathBuf::from(ca_file))?;
|
||||
}
|
||||
|
||||
if let Some(proxy) = args.proxy.clone() {
|
||||
let permissions = state.borrow_mut::<FP>();
|
||||
let url = Url::parse(&proxy.url)?;
|
||||
|
@ -512,13 +502,16 @@ where
|
|||
};
|
||||
|
||||
let defaults = state.borrow::<HttpClientDefaults>();
|
||||
let cert_data =
|
||||
get_cert_data(args.ca_file.as_deref(), args.ca_data.as_deref())?;
|
||||
let ca_certs = args
|
||||
.ca_certs
|
||||
.into_iter()
|
||||
.map(|cert| cert.into_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let client = create_http_client(
|
||||
defaults.user_agent.clone(),
|
||||
defaults.root_cert_store.clone(),
|
||||
cert_data,
|
||||
ca_certs,
|
||||
args.proxy,
|
||||
defaults.unsafely_ignore_certificate_errors.clone(),
|
||||
client_cert_chain_and_key,
|
||||
|
@ -527,18 +520,3 @@ where
|
|||
let rid = state.resource_table.add(HttpClientResource::new(client));
|
||||
Ok(rid)
|
||||
}
|
||||
|
||||
fn get_cert_data(
|
||||
ca_file: Option<&str>,
|
||||
ca_data: Option<&[u8]>,
|
||||
) -> Result<Option<Vec<u8>>, AnyError> {
|
||||
if let Some(ca_data) = ca_data {
|
||||
Ok(Some(ca_data.to_vec()))
|
||||
} else if let Some(ca_file) = ca_file {
|
||||
let mut buf = Vec::new();
|
||||
File::open(ca_file)?.read_to_end(&mut buf)?;
|
||||
Ok(Some(buf))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
hostname = "127.0.0.1",
|
||||
transport = "tcp",
|
||||
certFile = undefined,
|
||||
caCerts = [],
|
||||
certChain = undefined,
|
||||
privateKey = undefined,
|
||||
}) {
|
||||
|
@ -36,6 +37,7 @@
|
|||
hostname,
|
||||
transport,
|
||||
certFile,
|
||||
caCerts,
|
||||
certChain,
|
||||
privateKey,
|
||||
});
|
||||
|
@ -70,12 +72,13 @@
|
|||
|
||||
async function startTls(
|
||||
conn,
|
||||
{ hostname = "127.0.0.1", certFile } = {},
|
||||
{ hostname = "127.0.0.1", certFile = undefined, caCerts = [] } = {},
|
||||
) {
|
||||
const res = await opStartTls({
|
||||
rid: conn.rid,
|
||||
hostname,
|
||||
certFile,
|
||||
caCerts,
|
||||
});
|
||||
return new Conn(res.rid, res.remoteAddr, res.localAddr);
|
||||
}
|
||||
|
|
17
ext/net/lib.deno_net.d.ts
vendored
17
ext/net/lib.deno_net.d.ts
vendored
|
@ -121,8 +121,18 @@ declare namespace Deno {
|
|||
/** 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[];
|
||||
}
|
||||
|
||||
/** Establishes a secure connection over TLS (transport layer security) using
|
||||
|
@ -131,10 +141,11 @@ declare namespace Deno {
|
|||
* 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 conn1 = await Deno.connectTls({ port: 80 });
|
||||
* const conn2 = await Deno.connectTls({ certFile: "./certs/my_custom_root_CA.pem", hostname: "192.0.2.1", port: 80 });
|
||||
* const conn2 = await Deno.connectTls({ caCerts: [caCert], hostname: "192.0.2.1", port: 80 });
|
||||
* const conn3 = await Deno.connectTls({ hostname: "[2001:db8::1]", port: 80 });
|
||||
* const conn4 = await Deno.connectTls({ certFile: "./certs/my_custom_root_CA.pem", hostname: "golang.org", port: 80});
|
||||
* const conn4 = await Deno.connectTls({ caCerts: [caCert], hostname: "golang.org", port: 80});
|
||||
* ```
|
||||
*
|
||||
* Requires `allow-net` permission.
|
||||
|
|
|
@ -649,6 +649,7 @@ pub struct ConnectTlsArgs {
|
|||
hostname: String,
|
||||
port: u16,
|
||||
cert_file: Option<String>,
|
||||
ca_certs: Vec<String>,
|
||||
cert_chain: Option<String>,
|
||||
private_key: Option<String>,
|
||||
}
|
||||
|
@ -658,6 +659,7 @@ pub struct ConnectTlsArgs {
|
|||
struct StartTlsArgs {
|
||||
rid: ResourceId,
|
||||
cert_file: Option<String>,
|
||||
ca_certs: Vec<String>,
|
||||
hostname: String,
|
||||
}
|
||||
|
||||
|
@ -685,13 +687,16 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
let ca_data = match cert_file {
|
||||
Some(path) => {
|
||||
let mut buf = Vec::new();
|
||||
File::open(path)?.read_to_end(&mut buf)?;
|
||||
Some(buf)
|
||||
}
|
||||
_ => None,
|
||||
let mut ca_certs = args
|
||||
.ca_certs
|
||||
.into_iter()
|
||||
.map(|s| s.into_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if let Some(path) = cert_file {
|
||||
let mut buf = Vec::new();
|
||||
File::open(path)?.read_to_end(&mut buf)?;
|
||||
ca_certs.push(buf);
|
||||
};
|
||||
|
||||
let hostname_dns = DNSNameRef::try_from_ascii_str(hostname)
|
||||
|
@ -724,7 +729,7 @@ where
|
|||
|
||||
let tls_config = Arc::new(create_client_config(
|
||||
root_cert_store,
|
||||
ca_data,
|
||||
ca_certs,
|
||||
unsafely_ignore_certificate_errors,
|
||||
)?);
|
||||
let tls_stream =
|
||||
|
@ -786,13 +791,16 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
let ca_data = match cert_file {
|
||||
Some(path) => {
|
||||
let mut buf = Vec::new();
|
||||
File::open(path)?.read_to_end(&mut buf)?;
|
||||
Some(buf)
|
||||
}
|
||||
_ => None,
|
||||
let mut ca_certs = args
|
||||
.ca_certs
|
||||
.into_iter()
|
||||
.map(|s| s.into_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if let Some(path) = cert_file {
|
||||
let mut buf = Vec::new();
|
||||
File::open(path)?.read_to_end(&mut buf)?;
|
||||
ca_certs.push(buf);
|
||||
};
|
||||
|
||||
let root_cert_store = state
|
||||
|
@ -812,7 +820,7 @@ where
|
|||
let remote_addr = tcp_stream.peer_addr()?;
|
||||
let mut tls_config = create_client_config(
|
||||
root_cert_store,
|
||||
ca_data,
|
||||
ca_certs,
|
||||
unsafely_ignore_certificate_errors,
|
||||
)?;
|
||||
|
||||
|
|
|
@ -136,7 +136,7 @@ pub fn create_default_root_cert_store() -> RootCertStore {
|
|||
|
||||
pub fn create_client_config(
|
||||
root_cert_store: Option<RootCertStore>,
|
||||
ca_data: Option<Vec<u8>>,
|
||||
ca_certs: Vec<Vec<u8>>,
|
||||
unsafely_ignore_certificate_errors: Option<Vec<String>>,
|
||||
) -> Result<ClientConfig, AnyError> {
|
||||
let mut tls_config = ClientConfig::new();
|
||||
|
@ -144,11 +144,11 @@ pub fn create_client_config(
|
|||
tls_config.root_store =
|
||||
root_cert_store.unwrap_or_else(create_default_root_cert_store);
|
||||
|
||||
// If a custom cert is specified, add it to the store
|
||||
if let Some(cert) = ca_data {
|
||||
// If custom certs are specified, add them to the store
|
||||
for cert in ca_certs {
|
||||
let reader = &mut BufReader::new(Cursor::new(cert));
|
||||
// This function does not return specific errors, if it fails give a generic message.
|
||||
if let Err(_err) = tls_config.root_store.add_pem_file(reader) {
|
||||
if let Err(()) = tls_config.root_store.add_pem_file(reader) {
|
||||
return Err(anyhow!("Unable to add pem file to certificate store"));
|
||||
}
|
||||
}
|
||||
|
@ -215,14 +215,14 @@ pub fn load_private_keys(bytes: &[u8]) -> Result<Vec<PrivateKey>, AnyError> {
|
|||
pub fn create_http_client(
|
||||
user_agent: String,
|
||||
root_cert_store: Option<RootCertStore>,
|
||||
ca_data: Option<Vec<u8>>,
|
||||
ca_certs: Vec<Vec<u8>>,
|
||||
proxy: Option<Proxy>,
|
||||
unsafely_ignore_certificate_errors: Option<Vec<String>>,
|
||||
client_cert_chain_and_key: Option<(String, String)>,
|
||||
) -> Result<Client, AnyError> {
|
||||
let mut tls_config = create_client_config(
|
||||
root_cert_store,
|
||||
ca_data,
|
||||
ca_certs,
|
||||
unsafely_ignore_certificate_errors,
|
||||
)?;
|
||||
|
||||
|
|
|
@ -252,7 +252,7 @@ where
|
|||
Some("wss") => {
|
||||
let tls_config = create_client_config(
|
||||
root_cert_store,
|
||||
None,
|
||||
vec![],
|
||||
unsafely_ignore_certificate_errors,
|
||||
)?;
|
||||
let tls_connector = TlsConnector::from(Arc::new(tls_config));
|
||||
|
|
|
@ -59,6 +59,7 @@ const REDIRECT_ABSOLUTE_PORT: u16 = 4550;
|
|||
const AUTH_REDIRECT_PORT: u16 = 4551;
|
||||
const TLS_CLIENT_AUTH_PORT: u16 = 4552;
|
||||
const BASIC_AUTH_REDIRECT_PORT: u16 = 4554;
|
||||
const TLS_PORT: u16 = 4557;
|
||||
const HTTPS_PORT: u16 = 5545;
|
||||
const HTTPS_CLIENT_AUTH_PORT: u16 = 5552;
|
||||
const WS_PORT: u16 = 4242;
|
||||
|
@ -451,6 +452,62 @@ async fn run_tls_client_auth_server() {
|
|||
}
|
||||
}
|
||||
|
||||
/// This server responds with 'PASS' if client authentication was successful. Try it by running
|
||||
/// test_server and
|
||||
/// curl --cacert cli/tests/testdata/tls/RootCA.crt https://localhost:4553/
|
||||
async fn run_tls_server() {
|
||||
let cert_file = "tls/localhost.crt";
|
||||
let key_file = "tls/localhost.key";
|
||||
let ca_cert_file = "tls/RootCA.pem";
|
||||
let tls_config = get_tls_config(cert_file, key_file, ca_cert_file)
|
||||
.await
|
||||
.unwrap();
|
||||
let tls_acceptor = TlsAcceptor::from(tls_config);
|
||||
|
||||
// Listen on ALL addresses that localhost can resolves to.
|
||||
let accept = |listener: tokio::net::TcpListener| {
|
||||
async {
|
||||
let result = listener.accept().await;
|
||||
Some((result, listener))
|
||||
}
|
||||
.boxed()
|
||||
};
|
||||
|
||||
let host_and_port = &format!("localhost:{}", TLS_PORT);
|
||||
|
||||
let listeners = tokio::net::lookup_host(host_and_port)
|
||||
.await
|
||||
.expect(host_and_port)
|
||||
.inspect(|address| println!("{} -> {}", host_and_port, address))
|
||||
.map(tokio::net::TcpListener::bind)
|
||||
.collect::<futures::stream::FuturesUnordered<_>>()
|
||||
.collect::<Vec<_>>()
|
||||
.await
|
||||
.into_iter()
|
||||
.map(|s| s.unwrap())
|
||||
.map(|listener| futures::stream::unfold(listener, accept))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
println!("ready: tls"); // Eye catcher for HttpServerCount
|
||||
|
||||
let mut listeners = futures::stream::select_all(listeners);
|
||||
|
||||
while let Some(Ok((stream, _addr))) = listeners.next().await {
|
||||
let acceptor = tls_acceptor.clone();
|
||||
tokio::spawn(async move {
|
||||
match acceptor.accept(stream).await {
|
||||
Ok(mut tls_stream) => {
|
||||
tls_stream.write_all(b"PASS").await.unwrap();
|
||||
}
|
||||
|
||||
Err(e) => {
|
||||
eprintln!("TLS accept error: {:?}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async fn absolute_redirect(
|
||||
req: Request<Body>,
|
||||
) -> hyper::Result<Response<Body>> {
|
||||
|
@ -1016,6 +1073,7 @@ pub async fn run_all_servers() {
|
|||
let ws_close_addr = SocketAddr::from(([127, 0, 0, 1], WS_CLOSE_PORT));
|
||||
let ws_close_server_fut = run_ws_close_server(&ws_close_addr);
|
||||
|
||||
let tls_server_fut = run_tls_server();
|
||||
let tls_client_auth_server_fut = run_tls_client_auth_server();
|
||||
let client_auth_server_https_fut = wrap_client_auth_https_server();
|
||||
let main_server_fut = wrap_main_server();
|
||||
|
@ -1026,6 +1084,7 @@ pub async fn run_all_servers() {
|
|||
redirect_server_fut,
|
||||
ws_server_fut,
|
||||
wss_server_fut,
|
||||
tls_server_fut,
|
||||
tls_client_auth_server_fut,
|
||||
ws_close_server_fut,
|
||||
another_redirect_server_fut,
|
||||
|
@ -1182,7 +1241,7 @@ impl HttpServerCount {
|
|||
if line.starts_with("ready:") {
|
||||
ready_count += 1;
|
||||
}
|
||||
if ready_count == 5 {
|
||||
if ready_count == 6 {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
|
|
Loading…
Reference in a new issue