actix-webでSSL/TLS通信をしてみたよ

# 概要

google社の透明性レポート「ウェブ上でのHTTPS暗号化」によると、2022年現在では95%以上のWebサイトがhttps通信に対応しています。残りの5%未満にならないようにhttps通信出来るようにしてみました。

https://transparencyreport.google.com/https/overview?hl=ja

actix-webは高速に動作するRust製のWebフレームワークとして知られていて、ダウンロード数やexampleプロジェクトも多いのでactix-webを利用することにしました。

# 環境

- rustc 1.62.0 (on docker container rust:1.62.0-slim)
- actix-web 4.1 (rust crate)
- openssl 0.10 (rust crate)
- openSSL 1.1.1
- pkg-config 0.29.2
- mkcert 1.4.4

# 準備

必要なパッケージをインストールします。**libssl-dev**と**pkg-config**は今回のRustプログラムのコンパイルに必要になります。**wget**、**curl**、**libnss3-tools**はmkcertのダウンロード&実行に必要なパッケージです。

```sh
$ apt update && apt install -y libssl-dev pkg-config wget curl libnss3-tools
```

# 自己署名証明書

環境に合わせて**mkcert**をダウンロードしてきます。ここではlinux-amd64をダウンロードしてきます。ダウンロードしてきたファイル名をmkcertに変更して実行権を与えます。最後にmkcertを/usr/local/binディレクトリに移動します。

```bash
$ curl -s https://api.github.com/repos/FiloSottile/mkcert/releases/latest | grep browser_download_url | grep linux-amd64 | cut -d '"' -f 4 | wget -qi - \
    && mv mkcert-v*-linux-amd64 mkcert \
    && chmod a+x mkcert \
    && mv mkcert /usr/local/bin/

$ mkcert --version
v1.4.4
```

## 自己認証局の証明書と秘密鍵

自己認証局の証明書と秘密鍵を**mkcert -install**コマンドで作成します。

```sh
$ mkcert -install
Created a new local CA 💥
The local CA is now installed in the system trust store! ⚡️
```

作成されたディレクトリは**mkcert -CAROOT**コマンドで確認できます。**rootCA-key.pem**(秘密鍵)と**rootCA.pem**(証明書)が作成されているのがわかります。

```bash
$ mkcert -CAROOT
/home/rustuser/.local/share/mkcert

$ ls /home/rustuser/.local/share/mkcert/
rootCA-key.pem  rootCA.pem
```

## サーバ証明書

**mkcert**コマンドでサーバ証明書を発行します。指定したドメイン(IPアドレス)の証明書を発行します。ここではローカル環境で使う予定なのでローカルホストを表す3つのドメイン（IPアドレス）、**localhost**、**127.0.0.1**、**::1**を指定しています。

```sh
$ mkcert localhost 127.0.0.1 ::1

Created a new certificate valid for the following names 📜
 - "localhost"
 - "127.0.0.1"
 - "::1"

The certificate is at "./localhost+2.pem" and the key at "./localhost+2-key.pem" ✅

It will expire on 20 October 2024 🗓
```

作成された**localhost+2.pem**(サーバ証明書)と**localhost+2-key.pem**(サーバ秘密鍵)はRustのソースコードで使用します。

# ソースコード

actix-webの公式サイトのHTTP/2のページを参考にしています。

https://actix.rs/docs/http2/

```rust:main.rs
use actix_web::{get, App, HttpRequest, HttpServer, Responder, HttpResponse};
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod};

#[get("/")]
async fn index(_req: HttpRequest) -> impl Responder {
    HttpResponse::Ok()
        .content_type("text/plain")
        .body("welcome!!!")
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let mut builder = SslAcceptor::mozilla_modern_v5(SslMethod::tls_server()).unwrap();
    builder.set_private_key_file("localhost+2-key.pem", SslFiletype::PEM).unwrap();
    builder.set_certificate_chain_file("localhost+2.pem").unwrap();

    HttpServer::new(|| App::new().service(index))
        .bind_openssl("0.0.0.0:8080", builder)?
        .run()
        .await
}
```

**SslAcceptor::mozilla_modern_v5**メソッドはMozillaのサーバサイドTLS勧告バージョン5で定義されているConfigurationのうちの一つのモダンを利用します。プロトコルはtlsとdtlsを選択できますがここでは**SslMethod::tls_server**メソッドでtlsを選択しています。**set_private_key_file**メソッドでサーバの秘密鍵(**localhost+2-key.pem**)、**set_certificate_chain_file**メソッドでサーバ証明書(**localhost+2.pem**)を指定します。

:::note info

MozillaのサーバサイドTLS勧告ではオールド、インターメディエイト、モダンの3つの構成を定義していますが詳しくはリンクを張っておきます。

:::

https://wiki.mozilla.org/Security/Server_Side_TLS


# ビルド&実行

**cargo run**コマンドでコンパイル&実行します。

```bash
$ cargo run
   Compiling web v0.1.0 (/web)
    Finished dev [unoptimized + debuginfo] target(s) in 3.28s
     Running `target/debug/web`
```

# ブラウザで接続

## 証明書リストへの追加

作成した自己認証局の証明書(rootCA.pem)をブラウザの証明書リストへ追加します。ここではChromeを使います。Chromeのアドレス欄に**chrome://settings/certificates**と入力して**認証局**-->**インポート**と進んで**rootCA.pem**を選択肢します。

![Screenshot from 2022-07-21 01-36-53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271649/519bebeb-2475-9247-8775-e0452f767c55.png)

**ウェブサイトの識別でこの証明書を信頼します**をチェックしてOKを押します。

![Screenshot from 2022-07-21 01-37-51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271649/143ccc3c-731c-d23d-62fb-513f3ed984a9.png)

認証局リストに**org-mkcert~~~**の項目があれば完了です。

![Screenshot from 2022-07-21 01-38-52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271649/7caf7b89-a064-9989-5bfa-4464857eaa95.png)

## 接続

chromeで **https://localhost:8080** に接続して「welcome!!!」が表示されて「この接続は保護されています」と出ているのでOKそうです。

![Screenshot from 2022-07-21 12-45-41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271649/5a8f203d-5319-c551-214d-686c1515fa0b.png)

:::note info

rustプログラムはdockerコンテナで動かしていて、ホストの8080番ポートとコンテナの8080番ポートを対応させているのでlocalhost:8080にアクセスしています。

:::

# curlで接続

curlコマンドでhttps接続するときは **--cacert** オプションで認証局証明書を指定すれば接続できます。

```bash
$ curl https://localhost:8080 --cacert rootCA.pem 
welcome!!!
```

# postmanで接続

postmanでhttps接続する場合もpostmanに認証局証明書を登録します。メニュー-->Settingsと進んでCertificatesタブの **CA Certificates**をONにします。 **PEM file**に **rootCA.pem** を指定します。この状態で**GET**メソッドで **https://localhost:8080** にアクセスすると「welcome!!!」が返ってくるのでOKそうです。

![Screenshot from 2022-07-21 14-37-49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271649/893c7f0c-c484-8b69-951f-6bd9b5b32e6a.png)

![Screenshot from 2022-07-21 14-38-31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271649/3aa762b0-30a4-843e-8e72-7f8456c13c2e.png)

![Screenshot from 2022-07-21 14-39-11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271649/53413e38-2702-6385-8ada-ece560fc027c.png)