1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-26 00:59:24 -05:00

feat(ext/net): support cert, key options in listenTls (#13740)

This commit is contained in:
Yoshiya Hinosawa 2022-02-24 13:16:56 +09:00 committed by GitHub
parent f8b73ab97e
commit 3e8180c793
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 113 additions and 19 deletions

View file

@ -15,6 +15,9 @@ import { TextProtoReader } from "../../../test_util/std/textproto/mod.ts";
const encoder = new TextEncoder();
const decoder = new TextDecoder();
const cert = await Deno.readTextFile("cli/tests/testdata/tls/localhost.crt");
const key = await Deno.readTextFile("cli/tests/testdata/tls/localhost.key");
const caCerts = [await Deno.readTextFile("cli/tests/testdata/tls/RootCA.pem")];
async function sleep(msec: number) {
await new Promise((res, _rej) => setTimeout(res, msec));
@ -184,11 +187,60 @@ Deno.test(
},
);
const conn = await Deno.connectTls({
hostname,
port,
caCerts: [Deno.readTextFileSync("cli/tests/testdata/tls/RootCA.pem")],
});
const conn = await Deno.connectTls({ hostname, port, caCerts });
assert(conn.rid > 0);
const w = new BufWriter(conn);
const r = new BufReader(conn);
const body = `GET / HTTP/1.1\r\nHost: ${hostname}:${port}\r\n\r\n`;
const writeResult = await w.write(encoder.encode(body));
assertEquals(body.length, writeResult);
await w.flush();
const tpr = new TextProtoReader(r);
const statusLine = await tpr.readLine();
assert(statusLine !== null, `line must be read: ${String(statusLine)}`);
const m = statusLine.match(/^(.+?) (.+?) (.+?)$/);
assert(m !== null, "must be matched");
const [_, proto, status, ok] = m;
assertEquals(proto, "HTTP/1.1");
assertEquals(status, "200");
assertEquals(ok, "OK");
const headers = await tpr.readMIMEHeader();
assert(headers !== null);
const contentLength = parseInt(headers.get("content-length")!);
const bodyBuf = new Uint8Array(contentLength);
await r.readFull(bodyBuf);
assertEquals(decoder.decode(bodyBuf), "Hello World\n");
conn.close();
listener.close();
await resolvable;
},
);
Deno.test(
{ permissions: { read: false, net: true } },
async function listenTlsWithCertAndKey() {
const resolvable = deferred();
const hostname = "localhost";
const port = 3500;
const listener = Deno.listenTls({ hostname, port, cert, key });
const response = encoder.encode(
"HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nHello World\n",
);
listener.accept().then(
async (conn) => {
assert(conn.remoteAddr != null);
assert(conn.localAddr != null);
await conn.write(response);
setTimeout(() => {
conn.close();
resolvable.resolve();
}, 0);
},
);
const conn = await Deno.connectTls({ hostname, port, caCerts });
assert(conn.rid > 0);
const w = new BufWriter(conn);
const r = new BufReader(conn);

View file

@ -65,7 +65,9 @@
function listenTls({
port,
cert,
certFile,
key,
keyFile,
hostname = "0.0.0.0",
transport = "tcp",
@ -73,7 +75,9 @@
}) {
const res = opListenTls({
port,
cert,
certFile,
key,
keyFile,
hostname,
transport,

View file

@ -93,11 +93,21 @@ declare namespace Deno {
): Listener;
export interface ListenTlsOptions extends ListenOptions {
/** 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`. */
certFile: string;
/** Server public key file. Requires `--allow-read`.*/
keyFile: string;
* `--allow-read`.
*
* @deprecated This option is deprecated and will be removed in Deno 2.0.
*/
certFile?: string;
/** Server private key file. Requires `--allow-read`.
*
* @deprecated This option is deprecated and will be removed in Deno 2.0.
*/
keyFile?: string;
transport?: "tcp";
}

View file

@ -1004,8 +1004,12 @@ pub struct ListenTlsArgs {
transport: String,
hostname: String,
port: u16,
cert_file: String,
key_file: String,
cert: Option<String>,
// TODO(kt3k): Remove this option at v2.0.
cert_file: Option<String>,
key: Option<String>,
// TODO(kt3k): Remove this option at v2.0.
key_file: Option<String>,
alpn_protocols: Option<Vec<String>>,
}
@ -1020,23 +1024,47 @@ where
assert_eq!(args.transport, "tcp");
let hostname = &*args.hostname;
let port = args.port;
let cert_file = &*args.cert_file;
let key_file = &*args.key_file;
let cert_file = args.cert_file.as_deref();
let key_file = args.key_file.as_deref();
let cert = args.cert.as_deref();
let key = args.key.as_deref();
{
let permissions = state.borrow_mut::<NP>();
permissions.check_net(&(hostname, Some(port)))?;
permissions.check_read(Path::new(cert_file))?;
permissions.check_read(Path::new(key_file))?;
if let Some(path) = cert_file {
permissions.check_read(Path::new(path))?;
}
if let Some(path) = key_file {
permissions.check_read(Path::new(path))?;
}
}
let cert_chain = if cert_file.is_some() && cert.is_some() {
return Err(generic_error("Both cert and certFile is specified. You can specify either one of them."));
} else if let Some(path) = cert_file {
load_certs_from_file(path)?
} else if let Some(cert) = cert {
load_certs(&mut BufReader::new(cert.as_bytes()))?
} else {
return Err(generic_error("`cert` is not specified."));
};
let key_der = if key_file.is_some() && key.is_some() {
return Err(generic_error(
"Both key and keyFile is specified. You can specify either one of them.",
));
} else if let Some(path) = key_file {
load_private_keys_from_file(path)?.remove(0)
} else if let Some(key) = key {
load_private_keys(key.as_bytes())?.remove(0)
} else {
return Err(generic_error("`key` is not specified."));
};
let mut tls_config = ServerConfig::builder()
.with_safe_defaults()
.with_no_client_auth()
.with_single_cert(
load_certs_from_file(cert_file)?,
load_private_keys_from_file(key_file)?.remove(0),
)
.with_single_cert(cert_chain, key_der)
.expect("invalid key or certificate");
if let Some(alpn_protocols) = args.alpn_protocols {
super::check_unstable(state, "Deno.listenTls#alpn_protocols");