証明書から有効期限を取得する

· 2分で読める

弊社が使用しているドメインの証明書の現在のステータスを確認するのに役立つ Web アプリがいくつかあります。

1 日に 1 回実行され、確認する必要があるいくつかのドメインをチェックする Azure 関数を作成しました。これは、証明書の有効期限が 30 日未満の場合に電子メールを送信する、非常にシンプルなコンソール アプリケーションです。

関数がどのように動作するかのロジック自体は示されませんが、証明書の基本チェックを行う関数と取得するデータを示したいと思います。

関数

static async Task<X509Certificate2> CheckCertificateAsync(string urlPath)
{
    var certificate = new X509Certificate2();
    var httpClientHandler = new HttpClientHandler
    {
        ServerCertificateCustomValidationCallback = (request, cert, chain, policyErrors) =>
            {
                certificate = new X509Certificate2(cert);
                return true;
            }
    };

    using HttpClient httpClient = new HttpClient(httpClientHandler);
    await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, urlPath));

    return certificate;
}

このメソッド CheckCertificateAsyncX509Certificate2 証明書を返します。これにより、有効期限の確認など、さまざまな操作が可能になります。

シリアル化された結果

これは certificate オブジェクトのシリアル化された値です。

{
   "Archived":false,
   "Extensions":[
      {
         "KeyUsages":160,
         "Critical":true,
         "Oid":{
            "Value":"2.5.29.15",
            "FriendlyName":"Key Usage"
         },
         "RawData":"AwIFoA=="
      },
      {
         "EnhancedKeyUsages":[
            {
               "Value":"1.3.6.1.5.5.7.3.1",
               "FriendlyName":"Server Authentication"
            },
            {
               "Value":"1.3.6.1.5.5.7.3.2",
               "FriendlyName":"Client Authentication"
            }
         ],
         "Critical":false,
         "Oid":{
            "Value":"2.5.29.37",
            "FriendlyName":"Enhanced Key Usage"
         },
         "RawData":"MBQGCCsGAQUFBwMBBggrBgEFBQcDAg=="
      },
      {
         "CertificateAuthority":false,
         "HasPathLengthConstraint":false,
         "PathLengthConstraint":0,
         "Critical":true,
         "Oid":{
            "Value":"2.5.29.19",
            "FriendlyName":"Basic Constraints"
         },
         "RawData":"MAA="
      },
      {
         "SubjectKeyIdentifier":"634E1585565AA49402C21642A4A5979A38025797",
         "Critical":false,
         "Oid":{
            "Value":"2.5.29.14",
            "FriendlyName":"Subject Key Identifier"
         },
         "RawData":"BBRjThWFVlqklALCFkKkpZeaOAJXlw=="
      },
      {
         "Critical":false,
         "Oid":{
            "Value":"2.5.29.35",
            "FriendlyName":"Authority Key Identifier"
         },
         "RawData":"MBaAFBQusxe3WFbLrlAJQOYfr52LFMLG"
      },
      {
         "Critical":false,
         "Oid":{
            "Value":"1.3.6.1.5.5.7.1.1",
            "FriendlyName":"Authority Information Access"
         },
         "RawData":"MEcwIQYIKwYBBQUHMAGGFWh0dHA6Ly9yMy5vLmxlbmNyLm9yZzAiBggrBgEFBQcwAoYWaHR0cDovL3IzLmkubGVuY3Iub3JnLw=="
      },
      {
         "Critical":false,
         "Oid":{
            "Value":"2.5.29.17",
            "FriendlyName":"Subject Alternative Name"
         },
         "RawData":"MB6CHGJsb2cuZW1pbGlhbm9tb250ZXNkZW9jYS5jb20="
      },
      {
         "Critical":false,
         "Oid":{
            "Value":"2.5.29.32",
            "FriendlyName":"Certificate Policies"
         },
         "RawData":"MEMwCAYGZ4EMAQIBMDcGCysGAQQBgt8TAQEBMCgwJgYIKwYBBQUHAgEWGmh0dHA6Ly9jcHMubGV0c2VuY3J5cHQub3Jn"
      },
      {
         "Critical":false,
         "Oid":{
            "Value":"1.3.6.1.4.1.11129.2.4.2",
            "FriendlyName":"SCT List"
         },
         "RawData":"BIH0APIAdwBByMqx3yJGShDGoToJQodeTjGLGwPr60vHaPCQYpYG9gAAAYCeJIwYAAAEAwBIMEYCIQCG8sf4iBitUjNCc1dsxVd5mdRQCKapRqqnTHKxSKHjHgIhAJFGNXEZkCHKygT1T7bE4orpd6p2l1+GmifMEIuRsgHbAHcARqVV63X6kSAwtaKJafTzfREsQXS+/Um4havy/HD+bUcAAAGAniSMNgAABAMASDBGAiEAoxv1LBn/vfyR7s67kRLB/n1tq3eicuA/8/V0S2YzQCYCIQDXaS3FZbdIVNxQvKxPFxM1awBO/sGxBXafz0lspOoWSA=="
      }
   ],
   "FriendlyName":"",
   "HasPrivateKey":false,
   "PrivateKey":null,
   "IssuerName":{
      "Name":"CN=R3, O=Let's Encrypt, C=US",
      "Oid":{
         "Value":null,
         "FriendlyName":null
      },
      "RawData":"MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJSMw=="
   },
   "NotAfter":"2022-08-05T10:50:35+01:00",
   "NotBefore":"2022-05-07T10:50:36+01:00",
   "PublicKey":{
      "EncodedKeyValue":{
         "Oid":{
            "Value":"1.2.840.113549.1.1.1",
            "FriendlyName":"RSA"
         },
         "RawData":"MIIBCgKCAQEAq8cbDO3GAfjqqbPPCBdPost8NMRmEubv85gXecll7mZMH5qSfTPuB/ouFWL3tPMf1U8usWeoSUK/48yatzBGwmj1KKlkaW9MS2QkydztRp+kH8LvbzbQvGknuOLWGHBALLT17o/3DYxuA5LnXdY+vLvJWygQoFr2N/XhnhUjcm6OaQEJpIykydfbBQGQSEuQIIw4egpgdHkYJjCOYAsXuSSggN8/FADTCec0RzVjfFTSoJ3hV9HLE9M8MCSXjuo0AJ/MbAxq91S8XmDcRjHCCd7Zw+NjHo8cxZCQ6NqGvn3xwx8ahmmbC+CyDEcIyJJZK2Yv+qE4oS8QZfaX/RaHMwIDAQAB"
      },
      "EncodedParameters":{
         "Oid":{
            "Value":"1.2.840.113549.1.1.1",
            "FriendlyName":"RSA"
         },
         "RawData":"BQA="
      },
      "Key":{
         "Key":{
            "Algorithm":{
               "Algorithm":"RSA"
            },
            "AlgorithmGroup":{
               "AlgorithmGroup":"RSA"
            },
            "ExportPolicy":0,
            "Handle":{
               "IsInvalid":false,
               "IsClosed":false
            },
            "IsEphemeral":true,
            "IsMachineKey":false,
            "KeyName":null,
            "KeySize":2048,
            "KeyUsage":16777215,
            "ParentWindowHandle":{
               "value":0
            },
            "Provider":{
               "Provider":"Microsoft Software Key Storage Provider"
            },
            "ProviderHandle":{
               "IsInvalid":false,
               "IsClosed":false
            },
            "UIPolicy":{
               "ProtectionLevel":0,
               "FriendlyName":null,
               "Description":null,
               "UseContext":null,
               "CreationTitle":null
            },
            "UniqueName":null
         },
         "LegalKeySizes":[
            {
               "MinSize":512,
               "MaxSize":16384,
               "SkipSize":64
            }
         ],
         "KeyExchangeAlgorithm":"RSA",
         "SignatureAlgorithm":"RSA",
         "KeySize":2048
      },
      "Oid":{
         "Value":"1.2.840.113549.1.1.1",
         "FriendlyName":"RSA"
      }
   },
   "RawData":"MIIFQDCCBCigAwIBAgISBNwTmwP/RTcrEeIgAdMrpaFtMA0GCSqGSIb3DQEBCwUAMDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJSMzAeFw0yMjA1MDcwOTUwMzZaFw0yMjA4MDUwOTUwMzVaMCcxJTAjBgNVBAMTHGJsb2cuZW1pbGlhbm9tb250ZXNkZW9jYS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrxxsM7cYB+Oqps88IF0+iy3w0xGYS5u/zmBd5yWXuZkwfmpJ9M+4H+i4VYve08x/VTy6xZ6hJQr/jzJq3MEbCaPUoqWRpb0xLZCTJ3O1Gn6Qfwu9vNtC8aSe44tYYcEAstPXuj/cNjG4Dkudd1j68u8lbKBCgWvY39eGeFSNybo5pAQmkjKTJ19sFAZBIS5AgjDh6CmB0eRgmMI5gCxe5JKCA3z8UANMJ5zRHNWN8VNKgneFX0csT0zwwJJeO6jQAn8xsDGr3VLxeYNxGMcIJ3tnD42MejxzFkJDo2oa+ffHDHxqGaZsL4LIMRwjIklkrZi/6oTihLxBl9pf9FoczAgMBAAGjggJZMIICVTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFGNOFYVWWqSUAsIWQqSll5o4AleXMB8GA1UdIwQYMBaAFBQusxe3WFbLrlAJQOYfr52LFMLGMFUGCCsGAQUFBwEBBEkwRzAhBggrBgEFBQcwAYYVaHR0cDovL3IzLm8ubGVuY3Iub3JnMCIGCCsGAQUFBzAChhZodHRwOi8vcjMuaS5sZW5jci5vcmcvMCcGA1UdEQQgMB6CHGJsb2cuZW1pbGlhbm9tb250ZXNkZW9jYS5jb20wTAYDVR0gBEUwQzAIBgZngQwBAgEwNwYLKwYBBAGC3xMBAQEwKDAmBggrBgEFBQcCARYaaHR0cDovL2Nwcy5sZXRzZW5jcnlwdC5vcmcwggEGBgorBgEEAdZ5AgQCBIH3BIH0APIAdwBByMqx3yJGShDGoToJQodeTjGLGwPr60vHaPCQYpYG9gAAAYCeJIwYAAAEAwBIMEYCIQCG8sf4iBitUjNCc1dsxVd5mdRQCKapRqqnTHKxSKHjHgIhAJFGNXEZkCHKygT1T7bE4orpd6p2l1+GmifMEIuRsgHbAHcARqVV63X6kSAwtaKJafTzfREsQXS+/Um4havy/HD+bUcAAAGAniSMNgAABAMASDBGAiEAoxv1LBn/vfyR7s67kRLB/n1tq3eicuA/8/V0S2YzQCYCIQDXaS3FZbdIVNxQvKxPFxM1awBO/sGxBXafz0lspOoWSDANBgkqhkiG9w0BAQsFAAOCAQEAjSEID5MWonbSiyHbmPYWO8ImCCOjkLGxgY8WJODbrWxFy+xU44UwrWOCkqYZUlv2LRmPqSyZDrIeeHK9VMbGh71oXX+XovikgAr6PpI0Mp897nPWj0XvOBaSYG0s+f+CXMtyt0tWCsQOcl+iT82+Ja71f8gbVL6l7xESewEE78pTKEH8EqD22r8VSD7FNICD8EYQr13v3AuVWObSU/R8Td6SrSVEknw1HgJS4e9nvmrMxBGKOJ+aWrAGiUydehg8M9o2gbGckMhz6D7cwB5l618cYaXKkW1dEOYZHl++qUj1/VPK+FNkiDZOPVNN//PbZuOLwAUIlZvhqGWX5/9PBg==",
   "SerialNumber":"04DC139B03FF45372B11E22001D32BA5A16D",
   "SignatureAlgorithm":{
      "Value":"1.2.840.113549.1.1.11",
      "FriendlyName":"sha256RSA"
   },
   "SubjectName":{
      "Name":"CN=blog.emilianomontesdeoca.com",
      "Oid":{
         "Value":null,
         "FriendlyName":null
      },
      "RawData":"MCcxJTAjBgNVBAMTHGJsb2cuZW1pbGlhbm9tb250ZXNkZW9jYS5jb20="
   },
   "Thumbprint":"28CF960F772ABFF22AA193C291492C27F8E13D4D",
   "Version":3,
   "Handle":{
      "value":2658150705632
   },
   "Issuer":"CN=R3, O=Let's Encrypt, C=US",
   "Subject":"CN=blog.emilianomontesdeoca.com"
}

有効期限

有効期限を知るには、このオブジェクト内にある NotAfterNotBefore を確認する必要があります。

   "NotAfter":"2022-08-05T10:50:35+01:00",
   "NotBefore":"2022-05-07T10:50:36+01:00",

コンソール アプリケーション

次のスニペットは、.NET 6 上に構築された単純なコンソール アプリケーションであり、次の結果が生成され、必要な認定を確認できます。

using Newtonsoft.Json;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Text.Json;

var url = "https://blog.emilianomontesdeoca.com/";

static async Task<X509Certificate2> CheckCertificateAsync(string urlPath)
{
    var certificate = new X509Certificate2();
    var httpClientHandler = new HttpClientHandler
    {
        ServerCertificateCustomValidationCallback = (request, cert, chain, policyErrors) =>
            {
                certificate = new X509Certificate2(cert);
                return true;
            }
    };

    using HttpClient httpClient = new HttpClient(httpClientHandler);
    await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, urlPath));

    return certificate;
}

var cert = await CheckCertificateAsync(url);

var serializedValue = JsonConvert.SerializeObject(cert);

Console.WriteLine(serializedValue);

Console.ReadLine();

デモプロジェクト

コンソール アプリケーションは、私の Github の expiration-date-certificate というリポジトリにあります。