🗒️
Azure Functionsクライアント認証の認可コード(自己CA署名編)
これらの記事の一部です。
- クライアント認証でAzure Functionsに接続する
- Azure Functionsクライアント認証の認可コード(自己署名編)
- Azure Functionsクライアント認証の認可コード(自己CA署名編)
前記事で、Azure Functions接続を相互認証にして、認証と認可を実装しました。
ただ、ちょっと気になることが。
コードにクライアント証明書を配列で持っていて、ルート証明書が一致するかどうかをループしてチェックしています。この実装方法だと、認証するクライアントの台数が増えると、処理時間も増加してしまいます。
そこで、クライアント証明書を自己署名から自己CAによる署名に変更してみましょう。
1. CA証明書とCA秘密鍵を生成
opensslで、CA秘密鍵を生成してから、CA証明書(自己署名)を作ります。自己署名のクライアント証明書を作る方法と一緒ですね。
openssl genrsa 2048 > ca.key
openssl req -new -key ca.key > ca.csr
openssl x509 -req -days 365 -signkey ca.key < ca.csr > ca.crt
2. クライアント証明書とクライアント秘密鍵を生成
opensslで、クライアント秘密鍵を生成してから、クライアント証明書(CA署名)を作ります。
opensslの署名に、先に作ったCA証明書をCA秘密鍵を使うところがポイントです。
openssl genrsa 2048 > client.key
openssl req -new -key client.key > client.csr
openssl x509 -req -days 365 -CA ca.crt -CAkey ca.key -CAcreateserial < client.csr > client.crt
3. クライアントの認可
VerifyClientCertificate(string?)
のrootsBase64
に、CA証明書を加えます。
string[] rootsBase64 = {
//"MIIC+TCCAeECFB4iFGrzB7C5viT5SnbztEknJ/a8MA0GCSqGSIb3DQEBCwUAMDkxCzAJBgNVBAYTAkpQMRMwEQYDVQQIDApLaXRhbmFnb3lhMRUwEwYDVQQKDAxtYXRzdWppcnVzaGkwHhcNMjQxMDI2MDI1MTU2WhcNMzQxMDI0MDI1MTU2WjA5MQswCQYDVQQGEwJKUDETMBEGA1UECAwKS2l0YW5hZ295YTEVMBMGA1UECgwMbWF0c3VqaXJ1c2hpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqPZDzSOc51p7nHmzzxm35pYlbimr4UtvQaXGBuTB4ESlvOspY5bOGZKWlkcLOseNDvVskiF4qkI6fsAPTE0OVit33oj4jArvb9h7o3HpPL6sn/WPcazkz14mkpnqIy1fZ+iM21b3CTJkxTYV7waesGVEZq1yD9mt+UvMpB4Dkz61Dbq6p3hq1QQsCiOL93xlDHq3IYTyQQbR4NwXASImpZRsLrqMV0D/FryiMsdsCW8XBnPhtMj7/11tozwDfkmrr2bPsy64f2Uvr//bdOsOTU/F6Dpf6Tp8rJYeLAlgmxN482rgxr6afhSPRDfc430MSATPMuvVL0Wu86s/v2Ur1wIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBSbGn2RTmjPgMHj6uub0JKbzny2Z4Wj4HhCSv/WrZVl+BcKmyae8u1qLnx9OyM5nO7WXs0Q76QzVl/nzkafML3CPEumIiGVxQ/tDjxamkH0nODG3EGMUXXn17i/gicI+tOHJXHHeIADtVdWiwukKJN5li4y9KflelcV4Ebi8mTWXfRaLvAE31thgAU1tV4yqqn8dzQ3u1escXaC68AyCPpF3cUXxiwf7A58DcLI7zw9ITDM75O48Mt6gm0Mg/BFKkRiQ2nMLUGTQ1pECh7ulujYxDKMJkgcT0+0qwM7Mw70ZWGaY1YXIZ+B0An09E54CicnCSK82fiCZxeCce+3AXR",
//"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",
};
4. curlでアクセス
クライアント証明書(CA署名)でアクセスするとSuccess.
となりました。
$ curl --key ./client.key --cert ./client.crt https://functionapp000000000000000.azurewebsites.net/api/HttpExample
Success.
Azure Functionsの完全なコード
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;
using System.Security.Cryptography.X509Certificates;
namespace FunctionApp1
{
public class HttpExample
{
private readonly ILogger<HttpExample> _logger;
public HttpExample(ILogger<HttpExample> logger)
{
_logger = logger;
}
[Function("HttpExample")]
public IActionResult Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequest req)
{
var clientCert = req.Headers["X-ARR-ClientCert"];
_logger.LogInformation($"ClientCert={clientCert}");
if (!VerifyClientCertificate(clientCert)) return new UnauthorizedObjectResult("Invalid client certificate.");
return new OkObjectResult("Success.");
}
private bool VerifyClientCertificate(string? clientBase64)
{
string[] rootsBase64 = {
//"MIIC+TCCAeECFB4iFGrzB7C5viT5SnbztEknJ/a8MA0GCSqGSIb3DQEBCwUAMDkxCzAJBgNVBAYTAkpQMRMwEQYDVQQIDApLaXRhbmFnb3lhMRUwEwYDVQQKDAxtYXRzdWppcnVzaGkwHhcNMjQxMDI2MDI1MTU2WhcNMzQxMDI0MDI1MTU2WjA5MQswCQYDVQQGEwJKUDETMBEGA1UECAwKS2l0YW5hZ295YTEVMBMGA1UECgwMbWF0c3VqaXJ1c2hpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqPZDzSOc51p7nHmzzxm35pYlbimr4UtvQaXGBuTB4ESlvOspY5bOGZKWlkcLOseNDvVskiF4qkI6fsAPTE0OVit33oj4jArvb9h7o3HpPL6sn/WPcazkz14mkpnqIy1fZ+iM21b3CTJkxTYV7waesGVEZq1yD9mt+UvMpB4Dkz61Dbq6p3hq1QQsCiOL93xlDHq3IYTyQQbR4NwXASImpZRsLrqMV0D/FryiMsdsCW8XBnPhtMj7/11tozwDfkmrr2bPsy64f2Uvr//bdOsOTU/F6Dpf6Tp8rJYeLAlgmxN482rgxr6afhSPRDfc430MSATPMuvVL0Wu86s/v2Ur1wIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBSbGn2RTmjPgMHj6uub0JKbzny2Z4Wj4HhCSv/WrZVl+BcKmyae8u1qLnx9OyM5nO7WXs0Q76QzVl/nzkafML3CPEumIiGVxQ/tDjxamkH0nODG3EGMUXXn17i/gicI+tOHJXHHeIADtVdWiwukKJN5li4y9KflelcV4Ebi8mTWXfRaLvAE31thgAU1tV4yqqn8dzQ3u1escXaC68AyCPpF3cUXxiwf7A58DcLI7zw9ITDM75O48Mt6gm0Mg/BFKkRiQ2nMLUGTQ1pECh7ulujYxDKMJkgcT0+0qwM7Mw70ZWGaY1YXIZ+B0An09E54CicnCSK82fiCZxeCce+3AXR",
//"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",
};
if (clientBase64 == null) return false;
X509Chain chain = new X509Chain();
foreach (var rootBase64 in rootsBase64)
{
chain.ChainPolicy.ExtraStore.Add(new X509Certificate2(Convert.FromBase64String(rootBase64)));
}
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
if (!chain.Build(new X509Certificate2(Convert.FromBase64String(clientBase64)))) return false;
var chainRoot = chain.ChainElements[chain.ChainElements.Count - 1].Certificate;
foreach (var root in chain.ChainPolicy.ExtraStore)
{
if (chainRoot.RawData.SequenceEqual(root.RawData)) return true;
}
return false;
}
}
}
Discussion