IIS経由でhttpsのASP.NET Coreアプリを動作させる(自己証明書の設定)

個人的なトラブルシューティング・メモ。

WebとApiの2つのサーバをASP.NET Core 5.0で作っている。WebはASP.NET Core MVCで、ApiはASP.NET Core APIだ。

WebからApiを呼び出している。そしてそれらはhttps経由でアクセスする。

しかし、WebにアクセスするとHTTP Status 500 ERRORとなる。
ログを見ると、WebからApiにアクセスする場所で以下のエラーが出ている。

```
The remote certificate is invalid because of errors in the certificate chain: UntrustedRoot asp.net core
AuthenticationException: The remote certificate is invalid according to the validation procedure: RemoteCertificateNameMismatch, RemoteCertificateChainErrors
```

リモートの証明書が正しくなくてエラーになってる、というもので、原因としてはルート証明書に問題があるっぽい。

Windows 10 Proで localhostのIISで動作させようとしており、IISの管理画面から「Default Web Site」の「バインド」の設定画面を開き、httpsに対してSSL証明書「IIS Express Development Cetificate」を選択するところまではやっている。

IIS経由ではなくKestrel経由ならば動作するのだが…。

以下の対応で解決した。

・Microsoft管理コンソール（mmc）から以下の記事の「解決策＃2-IISExpress証明書を信頼するようにコンピューターを構成する」を実行する
https://blogs.iis.net/robert_mcmurray/how-to-trust-the-iis-express-self-signed-certificate

ただ、もっと簡単な方法があって、

(1) コンピュータ証明書の管理（certlm.msc）を起動
(2) 「個人」→「証明書」から使いたい証明書を右クリックコピー
(3) 「信頼されたルート証明機関」→「証明書」に張り付け

でもできる。

考え方としては、「個人」→「証明書」にある証明書は、ユーザーが使う為に作った証明書というだけで、それを信頼する為に自分自身で信頼する必要があり、それをするのが「信頼されたルート証明機関」にそれを追加する、という行為、ということになる。この辺の知識が足りていなかった為、右往左往してしまった。

# 追記：自己証明書の生成とIISからの利用手順

その後、このあたりについてもう少し知識が増えた。

根本的な原因は、そもそも上記で使われていた証明書が自己証明書だったことに起因する。

上記で使われていた証明書が、ちゃんとした公的な証明機関で署名されたものであったなら上記のようなことは必要なかったのだが、自分自身の秘密キーで署名した自己証明書だった為、それを自分のコンピュータ上の「信頼されたルート証明機関」として設定しておかないといけなかった、ということになる。

自己証明書の取り扱い方法について理解したことを、以下にメモとして残す。

## 自己証明書を生成する

PowerShellを管理者権限で起動し、以下のコマンドを実行する。

```
New-SelfSignedCertificate -DnsName localhost -FriendlyName "My Local Self-certificates" -NotAfter (get-date).Addyears(10)
```

オプションを全て省略した最もシンプルな形は以下となる。

```
New-SelfSignedCertificate -DnsName localhost
```

それぞれオプションの意味は次の通り。

```-DnsName localhost```は、証明書の「サブジェクト代替名(SAN)」を指定するもので、ここに指定したコンピュータ名やドメイン名でアクセスした時に証明書が検査される。my1.example.com, my2.example.com のようにカンマ区切りで指定できる。

```-FriendlyName "My Local Self-certificates"``` は証明書の表示名のようなもので、SSL証明書を選択するドロップダウンリストにはここで指定した名称が表示される。

```-NotAfter ...``` は、有効期限を設定している。見ればわかるが、現在から10年後を有効期限としている。省略すると1年後になる。

以前は ```-Subject localhost```のようにサブジェクト名を指定していた。これは証明書のCN値に使われる値となるが、Chromeの仕様変更でCNではなくSANをチェックするようになった為、```-DnsName```の指定に置き換えて使う。

```-CertStoreLocation "cert:\LocalMachine\My"```のパラメータを付けている解説記事も多いが、これは生成する証明書がどのストアに置かれるかを指定するものであり、"cert:\LocalMachine\My"は個人用のストアを表す。これは規定値なので省略しても個人用ストアに置かれる。

## 生成した証明書の確認と、信頼されたルート証明機関への複写

(1) 「ファイル名を指定して実行」から「certlm.msc」を起動する。
(2) 「個人 > 証明書」の中に上記で生成した証明書があるのを確認し、右クリックして「コピー」する。
(3) 同じく「信頼されたルート証明機関 > 証明書」に「貼り付け」をする。

## IIS上で証明書にバインドする

IISマネージャーから対象となるウェブサイトを選択し、右側のメニューから「バインド」を選択する。
httpsに対するサイトバインド編集画面を開き、「SSL証明書」から、先ほど生成した"My Local Self-certificates"を選択して「OK」を押下する。

## ブラウザから該当サイトを表示する

該当サイトが、何の警告も表示されずに通常通り表示されるのを確認する。

仕組みとしては以下の通りとなる。

①まず localhost に対して https でアクセスされると、IISマネージャーで選択した SSL証明書の確認が行われる。

②SSL証明書は正しいものだが、自己証明書として作成されている為、証明機関が自分自身の証明書になっている（＝自分自身の秘密鍵で署名されている）。

③その証明機関はこのコンピュータによって信頼されているかどうか、をチェックする為、このコンピュータの「信頼されたルート証明機関」がチェックされる。

④信頼されたルート証明機関に該当の証明書が存在する為、信頼されたものとして警告なしにサイトが表示される。

なのでもし、このサイトをlocalhostではなく、他のコンピュータから（ポートの開放などは行った上で）表示しようとすると、③④でひっかかって「この接続ではプライバシーが保護されません」となってしまう。そのままでも「詳細情報」ボタンを押下して、保護されていない通信としてアクセスは可能だ。

もしそれが困る場合、次のように、生成した自己証明書を他のコンピュータの「信頼されたルート証明機関」にインストールすれば良い。

## 自己証明書を他のコンピュータにも信頼させる

(1) certlm.msc から対象の自己証明書を右クリックし、「エクスポート」を行う。
(2) 秘密キーはエクスポートしない。
(3) DER encoded binary X.509(.CER)(D) を選択する。
(4) 「コンピュータ名_self_certificates.cer」のようなファイル名で保存する。
(5) 他のコンピュータ上でChromeを開き、「設定 > プライバシーとセキュリティ > セキュリティ > デバイス証明書の管理」を開く（Windowsメニューの「ファイル名を指定して実行」からcertlm.mscを起動してもよい）。
(6) (4)で出力したファイルを「信頼されたルート証明機関」にインポートする。

これで、そのコンピュータから元のコンピュータのウェブサイトをhttps経由で開いても、警告が表示されなくなる。

この方法で設定したサイトの通信は暗号化はされているものの、接続者によっては「本当に正しいサイトと通信しているのか」あやふやなままとなり、大変危険な状況であることには変わりがない。あくまでも個人的な目的、開発中の動作確認目的にのみ使用し、このような方法で制作したサイトを公開しないよう注意が必要である。

## 補足1：IISマネージャの「自己署名入り証明書の作成」について

ちなみに、IISマネージャーの「サーバー証明書」から「自己署名入り証明書の作成」を選択しても自己証明書は生成できるし、CNもSANも設定されるのだが、生成された証明書を「信頼されたルート証明機関」に複写して使用しても、「保護されていない通信」になってしまうことがある。

最初原因が分からなかったが、生成された証明書を「表示」して、実際のCN（サブジェクト）とSAN（サブジェクト代替名）がどうなっているかを確認すると、実際にブラウザのURLに入力しているホスト名（又はドメイン名）が、上記のSANに含まれていないことが分かった（具体的には、```https://mycomp1/```のようにホスト名だけでアクセスしているのに、SANの値は「DNS Name=mycomp1.company1.jp」だった。ここが「DNS Name=mycopm1」なら問題なかったはず）。その為に、証明書が有効になっていないものと推測される。

上記のCNやSANに設定される名前がどこから来るのか良くわかっていないのだが、IISマネージャーが勝手に設定するものなので、こちらから制御することができない。その場合は、New-SelfSignedCertificateコマンドを用いて生成することになる。

## 補足2：localhostへのhttpsアクセス

ローカルマシンのIIS Express上で```https://localhost/hogehoge``` しようとした場合、この記事の冒頭で書いたように、IIS Expressが用意している「IIS Express Development Cetrificate」という証明書を「信頼されたルート証明機関」に複写すれば、「保護されていない通信」が消える。

しかし、同じ手順をWindows Serverのmycomp1というコンピュータ上でやろうとして、IIS Express Development Certificateがないので、New-SelfSignedCertificateコマンドで自己証明書を作って設定すると、```https://mycomp1/hogehoge``` では問題ないのに、```https://localhost/hogehoge```だと「保護されていない通信」になったり、エラーになってしまったりすることがある。

「保護されていない通信」になる理由は、自己証明書のSAN（サブジェクト代替名）にlocalhostが含まれていない為だった。

```PewerShell:ダメだった例
New-SelfSignedCertificate -DnsName mycomp1 -FriendlyName "mycomp1_self_signed_cetificates"
```

上記のコマンドでは、-DnsName（サブジェクト代替名を指定するオプション）にmycomp1しか設定していない為、localhostが有効になっていなかった。

以下のようにカンマ区切りで「localhost」を追加して生成すると、SANにlocalhostが含まれるようになり、```https://localhost/hogehoge```でも「保護されていない通信」が消えるようになった。

```PewerShell:localhostでもアクセスできるようになった例
New-SelfSignedCertificate -DnsName mycomp1, localhost -FriendlyName "mycomp1_self_signed_cetificates"
```

ちなみに良く見てみると、そもそも「IIS Express Development Certificate」のSANを確認すると、中は「DNS Name=localhost」となっており、まさにlocalhost専用の自己証明書だった。

仕組みが分かってくると納得感のある結果だった。

尚、localhostでのアクセスがエラーになってしまったのは、証明書とは関係なく、単にIISマネージャーの「バインド」で、localhostに対する443の設定を行っていなかった為であった。この状態でいくらlocalhostの自己証明書を作成しても意味はないので注意したい。