Apache Web サーバの mTLS 設定例

## 証明書の作成

- 自己認証局 秘密鍵・証明書作成

    ```bash
    openssl req \
        -new \
        -out ca.crt \
        -keyout ca.key \
        -x509 \
        -days 3650 \
        -newkey rsa:2048 \
        -nodes \
        -subj "/CN=PrivateCA"
    ```

- サーバ証明書作成

    - 秘密鍵・CSR作成

        ```bash
        openssl req \
            -new \
            -out example.local.csr \
            -keyout example.local.key \
            -newkey rsa:2048 \
            -nodes \
            -subj "/CN=example.local"
        ```

    - Subject Alternative Name の準備

        ここでは `san_server.txt` とします。

        ```bash
        echo "subjectAltName = DNS:localhost, DNS:example.local, IP:127.0.0.1, IP:192.168.1.10" > san_server.txt
        ```
     
     - CSR へ署名

        ```bash
        openssl x509 \
            -req \
            -days 3650 \
            -in example.local.csr \
            -out example.local.crt \
            -CA ca.crt \
            -CAkey ca.key \
            -CAcreateserial \
            -extfile san_server.txt
        ```

- クライアント証明書

    - 秘密鍵・CSR作成

        ```bash
        openssl req \
            -new \
            -out client.csr \
            -keyout client.key \
            -newkey rsa:2048 \
            -nodes \
            -subj "/CN=client.example.local"
        ```

    - Subject Alternative Name の準備

        ここでは `san_client.txt` とします。

        ```bash
        echo "subjectAltName = DNS:client.example.local, IP:192.168.1.100" > san_client.txt
        ```
     
     - CSR へ署名

        ```bash
        openssl x509 \
            -req \
            -days 3650 \
            -in client.csr \
            -out client.crt \
            -CA ca.crt \
            -CAkey ca.key \
            -CAcreateserial \
            -extfile san_client.txt
        ```


### 作成した証明書の確認

```bash
openssl x509 -in example.local.crt -text -noout
openssl x509 -in client.crt -text -noout
```

### OS の信頼する CA に PrivateCA を追加する

1. PrivateCA 証明書を `/usr/local/share/ca-certificates/` にコピー

    ```bash
    sudo cp ca.crt /usr/local/share/ca-certificates/privateca.crt
    ```

2. 証明書の更新

    ```bash
    sudo update-ca-certificates
    ```

3. 更新の確認

    ```bash
    ls /etc/ssl/certs/privateca.pem
    ```
    
    初めから PrivateCA 証明書を `/etc/ssl/certs/` にコピーすれば良いような気もしますが、更新コマンドが用意されているので、正しい手段を使用しています。 


## Apache の設定例

- Apache Web サーバのインストール

    ```bash
    sudo apt install -y apache2
    ```

- SSL モジュールの有効化

    ```bash
    sudo a2enmod ssl
    ```

- 証明書の格納

    ```bash
    sudo cp example.local.crt /etc/ssl/certs/
    sudo cp example.local.key /etc/ssl/private/
    ```

- TLS 設定

    /etc/apache2/sites-available/default-ssl.conf の編集
    
    ```diff
    --- /tmp/default-ssl.conf	2024-09-22 21:01:12.605553001 +0900
    +++ /etc/apache2/sites-available/default-ssl.conf	2024-09-22 21:03:21.713735006 +0900
    @@ -29,8 +29,8 @@
     		#   /usr/share/doc/apache2/README.Debian.gz for more info.
     		#   If both key and certificate are stored in the same file, only the
     		#   SSLCertificateFile directive is needed.
    -		SSLCertificateFile	/etc/ssl/certs/ssl-cert-snakeoil.pem
    -		SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
    +		SSLCertificateFile	/etc/ssl/certs/example.local.crt
    +		SSLCertificateKeyFile /etc/ssl/private/example.local.key
     
     		#   Server Certificate Chain:
     		#   Point SSLCertificateChainFile at a file containing the
    @@ -48,7 +48,7 @@
     		#   Note: Inside SSLCACertificatePath you need hash symlinks
     		#		 to point to the certificate files. Use the provided
     		#		 Makefile to update the hash symlinks after changes.
    -		#SSLCACertificatePath /etc/ssl/certs/
    +		SSLCACertificatePath /etc/ssl/certs/
     		#SSLCACertificateFile /etc/apache2/ssl.crt/ca-bundle.crt
     
     		#   Certificate Revocation Lists (CRL):
    ```

- SSL サイトの有効化

    ```bash
    sudo a2ensite default-ssl.conf
    sudo service apache2 reload
    ```

- 動作確認

    ```bash
    curl https://localhost/
    ```
    
    サーバ証明書の `Subject Alternative Name` で設定した FQDN と IP アドレスであれば `--insecure` 無しでもエラーにならないと思います。


### mTLS

- mTLS 設定 (クライアント証明書を必須にする)

    ```diff
    --- /tmp/default-ssl.conf	2024-09-22 21:19:36.582552012 +0900
    +++ /etc/apache2/sites-available/default-ssl.conf	2024-09-22 21:19:59.752602009 +0900
    @@ -66,8 +66,8 @@
     		#   none, optional, require and optional_no_ca.  Depth is a
     		#   number which specifies how deeply to verify the certificate
     		#   issuer chain before deciding the certificate is not valid.
    -		#SSLVerifyClient require
    -		#SSLVerifyDepth  10
    +		SSLVerifyClient require
    +		SSLVerifyDepth  2
     
     		#   SSL Engine Options:
     		#   Set various options for the SSL engine.
    ```

    ```bash
    sudo service apache2 reload
    ```

- 動作確認

    ```bash
    curl --cert client.crt --key client.key https://localhost/
    ```

---

## その他
### Python で動作確認をする場合

```python
import requests

url = "https://localhost/"
cert = ('./client.crt', './client.key')

response = requests.get(url, cert=cert, verify='/etc/ssl/certs/privateca.pem', timeout=5)

print(f"Status Code: {response.status_code}")
```

Python の Requests では、OS の信頼する CA リストを参照していないので、`verify=` で PrivateCA の証明書のファイルパスを指定するか、`/usr/local/lib/python3.10/dist-packages/certifi/cacert.pem` に PrivateCA の証明書を追加する必要があります。