Skip to content

Commit

Permalink
feat(ext/net): support cert, key options in listenTls (denoland#13740)
Browse files Browse the repository at this point in the history
  • Loading branch information
kt3k authored Feb 24, 2022
1 parent f8b73ab commit 3e8180c
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 19 deletions.
62 changes: 57 additions & 5 deletions cli/tests/unit/tls_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -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);
Expand Down
4 changes: 4 additions & 0 deletions ext/net/02_tls.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,19 @@

function listenTls({
port,
cert,
certFile,
key,
keyFile,
hostname = "0.0.0.0",
transport = "tcp",
alpnProtocols = undefined,
}) {
const res = opListenTls({
port,
cert,
certFile,
key,
keyFile,
hostname,
transport,
Expand Down
18 changes: 14 additions & 4 deletions ext/net/lib.deno_net.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
Expand Down
48 changes: 38 additions & 10 deletions ext/net/ops_tls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>>,
}

Expand All @@ -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");
Expand Down

0 comments on commit 3e8180c

Please sign in to comment.