Azure Functions (App Service) 上で X509Certificate2 クラスを作成するときには

# 結論
アプリケーション設定より、 **WEBSITE_LOAD_USER_PROFILE = 1** と設定しましょう。
設定しない場合、下記のようなエラーメッセージが出力され、インスタンス作成に失敗する可能性があります。

> Exception calling ".ctor" with "3" argument(s): "The system cannot find the file specified.

※ この記事は [Microsoft Azure Tech Advent Calendar 2021](https://qiita.com/advent-calendar/2021/microsoft-azure-tech) の 15 日目の記事です。

# 事象
例えば、 Azure Key Vault から証明書情報を読み込み、 X509Certificate2 クラスを作成するような Azure Functions の関数があったとします。
アプリケーション設定で WEBSITE_LOAD_USER_PROFILE = 1 を設定していない場合、関数を実行すると下記のエラーが発生してしまう場合があります。

> Exception calling ".ctor" with "3" argument(s): "The system cannot find the file specified.

エラー メッセージから見ると、引数の証明書情報に不備があるように見えますが、 WEBSITE_LOAD_USER_PROFILE = 1 が設定されていない場合の Azure Functions (App Service) の想定された挙動となりますのでお気を付けください。

## サンプル
関数の実行環境として Powershell を選択した場合のサンプル コードは下記のようなものです。
内容はシンプルで Azure Key Vault から証明書の情報を取得し、それに基づいて`X509Certificate2` クラスを作成するというものです。

```powershell
$containerName = '<キーコンテナ名>'

$certName = '<証明書名>'

$certificate = Get-AzKeyVaultCertificate -VaultName $containerName -Name $certName

$secret = Get-AzKeyVaultSecret -VaultName $vaultName -Name $certificate.Name

$secretText = '';

$ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secret.SecretValue)

try {

    $secretValueText = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($ptr)

}

finally {

    [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($ssPtr)

}

$secretByte = [Convert]::FromBase64String($secretText)

$Certificate = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($secretByte, '', 'Exportable,PersistKeySet')
```

なお、上記テスト コードを実行する際には `Get-AzKeyVaultCertificate` `Get-AzKeyVaultSecret` コマンドを利用できるよう、依存関係を追加する必要があります。
下記のドキュメントを参考に `host.json` および `requirements.psd1` を編集してください。

- [Azure Functions の PowerShell 開発者向けリファレンス | Microsoft Docs](
https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-reference-powershell?tabs=portal#dependency-management)

なお、 `host.json` はデフォルトで`managedDependency` プロパティが `true` になっています。
また、`requirements.psd1` はデフォルトで `Az` コマンドがコメント アウトされているので、コメント インすれば OK です。
Azure ポータルから、[アプリ ファイル] から編集可能です (画像はコメント イン後)。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319966/4ce6001b-0543-eb2a-d8b9-a3469fd0e3ea.png)

依存関係の追加後、 Azure Functions を再起動しましょう。再起動後、追加された依存関係のダウンロードが開始されます。

※上記サンプルは KeyVault にアクセスする際に必要な認証情報は省略しております。
手軽に設定したい場合は Functions にマネージド ID を付与する方法が簡単です。
1. ID ブレードからシステム割り当てマネージド ID をオンに
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319966/9fc114b8-c217-3acc-cd95-98d20d9e932f.png)
2. KeyVault 側でアクセス ポリシー ブレードより 1 で登録したマネージド ID に対してアクセス許可を付与する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319966/a2fea192-84cd-a09c-77d2-5f3ca728b867.png)
3. 関数コード冒頭に `Connect-AzAccount -Identity` を追加する

### 実行結果
依存関係を追加して再起動後、上記の関数コードを実行し、結果を[モニター]より確認するとエラーが発生していることがわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319966/1fe1421b-49c6-0afe-963d-b94ae6df27e8.png)



## 原因
**WEBSITE_LOAD_USER_PROFILE = 1** が設定されていない場合、 Functions(App Service) ホストはユーザー プロファイルおよびその配下の証明書ストアの情報をロードしません。
しかし、 `X509Certificate2` クラスのコンストラクタはユーザー プロファイル内から証明書情報をインポートしようとします。
その結果、上述のエラーが発生した、ということになります。

ちなみに下記の記事にある通り、 X509Certificate2 クラスを作成した際に作成される証明書情報はデフォルトでは `X509KeyStorageFlags.UserKeySet` が設定されているためユーザー プロファイル内の証明書ストアに保存されます。

- [Seven tips for working with X.509 certificates in .NET]
(https://paulstovell.com/x509certificate2/)



## まとめ
Azure Functions (App Service) 上で `X509Certificate2` クラスを作成するときには、アプリケーション設定より**WEBSITE_LOAD_USER_PROFILE = 1** を設定すると、不可解なエラーに遭遇せず済む場合がありますので、ご検討ください。
局所的な内容となってしまいましたが、こちらの記事がエラーで困っているどなたかの助けとなればと思います。

### 参考文献
- [Seven tips for working with X.509 certificates in .NET](
https://paulstovell.com/x509certificate2/)
- [Best X509Certificate2 Practices · projectkudu/kudu Wiki]
(https://github.com/projectkudu/kudu/wiki/Best-X509Certificate2-Practices)
- [Configurable settings · projectkudu/kudu Wiki](
https://github.com/projectkudu/kudu/wiki/Configurable-settings#the-system-cannot-find-the-file-specified-issue-with-x509certificate2)
- [c# - How can constructing an X509Certificate2 from a PKCS#12 byte array throw CryptographicException("The system cannot find the file specified.")? - Stack Overflow](
https://stackoverflow.com/questions/3826321/how-can-constructing-an-x509certificate2-from-a-pkcs12-byte-array-throw-cryptog)
- [Use a TLS/SSL certificate in code - Azure App Service | Microsoft Docs](
https://docs.microsoft.com/ja-jp/azure/app-service/configure-ssl-certificate-in-code#load-certificate-from-file)
- [Azure App Services: How to determine if the client certificate is loaded | Microsoft Docs](https://docs.microsoft.com/ja-jp/archive/blogs/karansingh/azure-app-services-how-to-determine-if-the-client-certificate-is-loaded)