🔒

DenoとHonoでlocalhostをhttpsにする

2022/09/07に公開

とあるDiscordサーバーで紹介したやつです。

$ deno --version
deno 1.25.1 (release, x86_64-unknown-linux-gnu)
v8 10.6.194.5
typescript 4.7.4

今回はDeno v1.25.0から追加されたUnstableなDeno.serveを使います。

$ openssl ecparam -genkey -name prime256v1 -out localhost.key
-----BEGIN EC PARAMETERS-----
...
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
...
-----END EC PRIVATE KEY-----

オレオレ証明書(と書いたらおっさんなので以降自己署名証明書と書きます)はおまじない感覚でこのように書くことが多いと思いますが、これはPKCS#8形式でないためDenoでは読み込んでくれません。

https://zenn.dev/tkithrta/articles/d5865b67d18d9c

なぜWebCryptoAPIはPKCS#8形式でないとダメなのかは以前紹介しましたが、DenoのServeTlsOptionsもWebCryptoを使っているのか確認していないので、あまりよくわかっていません(要出典)

2022/09/08追記:要出典について調べました。
$ openssl genrsa -out loacalhost.key 4096
$ openssl req -new -sha256 -subj /CN=localhost -key localhost.key -out localhost.csr
$ openssl x509 -req -signkey localhost.key -in localhost.csr -out localhost.crt

WebCryptoと違いRSAのPEM形式(PKCS#1形式)の場合でも問題なくhttpsにすることができました。

https://sourcegraph.com/github.com/denoland/deno@v1.25.1/-/blob/ext/tls/lib.rs?L27-29

なのでECDSAのPEM形式を実装していないだけのようです。

https://crates.io/crates/rustls-pemfile

ただrustls-pemfileのRelease Historyを見た感じだと0.3.0 (2022-02-05)に実装されたばかりなので順次追加されるだけのような気がします。

何にせよPKCS#8であればSSL/TLSに限らず様々な要件で使えるので今後秘密鍵はPKCS#8で作ることをオススメします。

ドキュメントではPEM formatでいいと書いていますがこれはPEM形式を指すのではなくPKCS#8形式を指すようです。

error: Uncaught (in promise) InvalidData: No keys found in key file

少なくとも上記秘密鍵を使うとこのようなエラーを返します。

$ openssl genpkey -algorithm ec -pkeyopt ec_paramgen_curve:prime256v1 -out localhost.key
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----

なのでこう書きます。

https://github.com/denoland/deno/tree/v1.25.1/cli/tests/testdata/tls

Denoのテストデータでは2048bitのRSAを使っているみたいですが今回は上記P-256のECDSAを使うことにします。

$ openssl genpkey -algorithm ec -pkeyopt ec_paramgen_curve:prime256v1 -out localhost.key
$ openssl req -new -sha256 -subj /CN=localhost -key localhost.key -out localhost.csr
$ openssl x509 -req -signkey localhost.key -in localhost.csr -out localhost.crt

コマンドを全部書くとこんな感じです。

$ ls -la
total 16
drwxr-xr-x 2 hono hono  85 Sep  7 12:00 .
drwxr-xr-x 3 hono hono  15 Sep  7 12:00 ..
-rw-r--r-- 1 hono hono 316 Sep  7 12:00 hello.ts
-rw-r--r-- 1 hono hono 453 Sep  7 12:00 localhost.crt
-rw-r--r-- 1 hono hono 355 Sep  7 12:00 localhost.csr
-rw------- 1 hono hono 241 Sep  7 12:00 localhost.key
import { Hono } from "https://deno.land/x/hono/mod.ts";

const app = new Hono();
app.get("/", (c) => c.text("Hello, World!"));

const cert = await Deno.readTextFile("localhost.crt");
const key = await Deno.readTextFile("localhost.key");
Deno.serve(app.fetch, {
  hostname: "localhost",
  port: 8443,
  cert,
  key,
});

hello.tsにDenoとHonoでこんな感じのコードを書きます。ものすごく簡単ですね。

$ deno run --unstable --allow-read --allow-net hello.ts

あとはUnstableオプションをつけて動かすだけです。

chrome://flags/#allow-insecure-localhost => Enabled

Google Chromeはlocalhostであろうと安全ではないことをページ全体で主張してくるのでこのフラグをEnabledにします。

edge://flags/#allow-insecure-localhost => 有効

Microsoft Edgeの場合はこう。

https://localhost:8443/

Not SecureでSecureなlocalhostができました。

Listening on http://localhost:8443/
TLS alert received: AlertMessagePayload {
    level: Fatal,
    description: CertificateUnknown,
}

Hono v2.1.4ではこんな感じのログが出ますがちゃんとhttpsです。

https://deno.land/

https://hono.dev/

https://speakerdeck.com/yusukebe/denodemobundemoiikara-zui-su-womu-zhi-su

どんどん速くなるDenoとHonoをよろしくお願いします。

Discussion