WindowsリモートデスクトップをLet's EncryptでSSL証明書を発行して使う

Windows 10 Proで使えるリモートデスクトップですが、デフォルトだとオレオレの自己証明書なので、接続時に証明書が検証できない旨の確認が出てきます。そのままでも利用できるのですが、なんか気持ち悪いのでLet's Encryptで証明書を発行して使いたいと思います。

### ソースコード
[cert-win-remote-desktop - GitHub](https://github.com/norocchi/cert-win-remote-desktop)

# 前提
- acme.shがサポートするDNSプロバイダを使っている
- WSLを利用している

[サポートしているDNSプロバイダのリストから](https://community.letsencrypt.org/t/dns-providers-who-easily-integrate-with-lets-encrypt-dns-validation/86438)見つけてください。日本の場合はMyDNS.JPが便利だと思います。独自ドメインをほかのところで取得していたとしても、DNSの設定をMyDNS.JPですれば問題ないです。

あなたのDNSプロバイダがacme.shにサポートされていない場合は、他のACMEクライアントを使い、適宜読み替えてください。
もしあなたのDNSプロバイダが上記のリストになく、80番ポートを開放可能ならばHTTP-01チャレンジを利用できます（ただし80番ポートをずっと開けておく必要があります）。

2020年9月時点で、Certbot for WindowsはDNS-01チャレンジをサポートしていないので、WSLでCertbotを利用することにします。

# 手順
1. acme.shをインストールして自動更新のためにcronを設定する
2. 証明書を発行する
3. pfx形式に証明書を変換する
4. pfxをインポートしていくつかの設定をする
5. 定期タスクの設定をする

## 1. acme.shをインストールして自動更新のためにcronを設定する
acme.shをWSLにインストールします。

```
$ wget -O -  https://get.acme.sh | sh
```

インストールが終わったらターミナルを再起動します。

### WSLでcronをセット
もし既にWSLでcronを起動しているのなら、acme.shの起動時にcronがセットされているはずです。
まだの場合は次のステップで、Windowsの起動時に自動起動させることができます。

一、sudoした時にパスワードを聞かれなくなるように設定する

```
$ sudo visudo
<username> ALL=NOPASSWD: ALL # add this line (put your username in <username>)
```

二、下記の内容で新しいbatファイルを作る。

```
wsl /bin/bash -l -c "sudo service cron start"
```

三、作ったファイルを`C:\Users\<win-user>\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup`へ移動する

## 2. 証明書を発行する
DNS-01チャレンジを利用するには、DNSのIDとパスワードなどを環境変数に設定する必要があります。DNSプロバイダによって必要なものが違いますので、以下のページから確認してください。
[dnsapi](https://github.com/acmesh-official/acme.sh/wiki/dnsapi)

MyDNS.JPの場合はこんな感じです。

```
$ export MYDNSJP_MasterID=MasterID
$ export MYDNSJP_Password=Password
```

そしたら、証明書を発行します。

```
$ acme.sh --issue --dns <your-dns-provider> -d <your-domain>
```

**注意**: 複数のドメインを指定しないでください。何故かわかりませんが、ここで複数ドメインを（example.comと*.example.comのように）指定して発行するとWindowsリモートデスクトップに接続できないエラーを吐きました。ワイルドカードはOKなので、`-d *.example.com`のようにすれば、使いまわししやすいでしょう。

acme.shが証明書の発行に成功すると、`/home/<user>/.acme.sh/<your-domain>`の中に証明書やキーが入っているはずです。

**更新 2021-05-22**
最近なぜかacme.shでの更新が上手くいかないので、上記でうまくいかない場合は以下の記事の方法を試してみることをおすすめします。
[【ポート開放なし】MyDNS.JPのワイルドカード証明書を簡単に取得する【Docker】](https://qiita.com/SogoK/items/8bcf92356dcb39621a56)

## 3. pfx形式に証明書を変換する
`WSL/convert_to_pfx.sh`を使って、cer(pem)形式の証明書をpfx形式に変換します。このスクリプトは証明書のSHA-1ハッシュ値を中身に持つ`fingerprint.txt`も同時に生成します。使う前に変数`domain`と`file_dir`を書き換えてください。

```
$ cd ~/
$ git clone https://github.com/norocchi/cert-win-remote-desktop.git
$ cd ~/cert-win-remote-desktop/wsl
$ nano convert_to_pfx.sh
# edit $domain and $file_dir
domain='<your-domain>'
file_dir='/home/<user>/cert-win-remote-desktop/wsl'
$ ./convert_to_pfx.sh
```

## 4. pfxをインポートしていくつかの設定をする
ここからはPowerShellターミナルを使いますが、その前に`powershell/cert.ps1`を編集して、書き換えてください。

```
$ cd ~/cert-win-remote-desktop/powershell
$ nano cert.ps1
# edit $CERTWSLPATH and $DOMAIN
$CERTWSLPATH = '\\wsl$\Ubuntu\home\<user>\cert-win-remote-desktop\wsl\keys'
$DOMAIN = '<your-domain>'
```

`powershell/cert.ps1`をWindows側に移します。エクスプローラーか、下記のようなPowerShellコマンドが使えます。

```
cp \\wsl$\Ubuntu\home\<user>\cert-win-remote-desktop\powershell\cert.ps1 C:\Users\<win-user>\path\to\your\folder
cd C:\Users\<win-user>\path\to\your\folder
```

移したら、コマンドを実行します。

```
./cert.ps1
```

mmc.exeを開いて、ファイル＞スナップインの追加と削除＞証明書＞追加＞コンピューターアカウント＞ローカルコンピューター＞完了 と進み、個人＞証明書を開きます。
追加したドメイン名が見えたら、pfxの証明書の追加に成功しています。
念のため、その証明書を右クリックして、すべてのタスク＞秘密キーの管理を選択して、`NETWORK SERVICE`というユーザ名があることも確認しておきましょう。

それから、レジストリエディタ（regedit）を開き、`HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp`まで行き、`SSLCertificateSHA1Hash`を見つけてください。見えている16進の数字が`fingerprint.txt`の値と同じことが確認できれば、スクリプトは正常に完了しています！

参考: これらのスクリプトは、[このページ](https://superuser.com/questions/1093159/how-to-provide-a-verified-server-certificate-for-remote-desktop-rdp-connection)と同じことをやっています。

## 5. 定期タスクの設定をする
それでは、自動更新のためにタスクをスケジューリングしましょう。
WSLのcronで、こんな感じで新たに毎日実行するジョブを作ります。

```
0 0 * * * /path/to/cert-win-remote-desktop/wsl/convert_to_pfx.sh
```

それから、Windowsのタスクスケジューラを開き、新しい基本タスクを作成します。
注意点としては、

- 毎日（`convert_to_pfx`実行後）
- 最上位の特権で
- ユーザーがログオンしているときのみ

実行することです。
プログラム/スクリプトは`C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe`、引数は`C:\Users\<win-user>\path\to\your\folder\cert.ps1`としてください。

おわり！おつかれさまでした！

## 参考文献
- [An ACME Shell script: acme.sh](https://github.com/acmesh-official/acme.sh)
- [How to provide a verified server certificate for Remote Desktop (RDP) connections to Windows 10](https://superuser.com/questions/1093159/how-to-provide-a-verified-server-certificate-for-remote-desktop-rdp-connection)
- [Certificate Provider](https://docs.microsoft.com/en-us/powershell/module/Microsoft.PowerShell.Security/About/about_Certificate_Provider?view=powershell-7)
- [DNS providers who easily integrate with Let’s Encrypt DNS validation](https://community.letsencrypt.org/t/dns-providers-who-easily-integrate-with-lets-encrypt-dns-validation/86438)
- [Working with Registry Entries](https://docs.microsoft.com/en-us/powershell/scripting/samples/working-with-registry-entries?view=powershell-7)
- [Import-PfxCertificate](https://docs.microsoft.com/en-us/powershell/module/pkiclient/import-pfxcertificate?view=win10-ps)
- [OpenSSLで証明書（PEM）をPKCS #12に変換](https://www.uramiraikan.net/Works/entry-2499.html)
- [Import a Signed Server Certificate into a Windows Certificate Store](https://docs.vmware.com/en/VMware-Horizon-7/7.12/horizon-scenarios-ssl-certificates/GUID-2D968AD7-ED62-46CA-B2B2-CCC526CA09F5.html)
- [Managing Windows PFX certificates through PowerShell](https://dev.to/iamthecarisma/managing-windows-pfx-certificates-through-powershell-3pj)
- [Export a PKCS#12 file without an export password?](https://stackoverflow.com/questions/27497723/export-a-pkcs12-file-without-an-export-password)
- [WSL で cron を利用する方法・Windows 起動時に自動実行する方法](https://loumo.jp/archives/24595)
- [sudo のパスワードを入力なしで使うには](https://qiita.com/RyodoTanaka/items/e9b15d579d17651650b7)
- [MyDNS.JP](https://www.mydns.jp/)