Obtendo a data de validade de um certificado

· 3 min de leitura

Existem alguns aplicativos da web que ajudam você a verificar o status atual dos certificados dos domínios que usamos, e temos alguns deles.

Criei uma Azure Function que roda uma vez por dia e verifica alguns domínios que preciso dar uma olhada, é uma aplicação de console super simples que se a data de validade do certificado for menor que 30 dias, ele enviará um email.

A lógica de funcionamento da função em si não será mostrada, gostaria de mostrar a função que faz a verificação básica do certificado e quais dados obtemos.

A função

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;
}

Este método CheckCertificateAsync nos retornará um certificado X509Certificate2 que nos permitirá fazer um monte de coisas, incluindo dar uma olhada na data de validade.

Resultado serializado

Este é o valor serializado do objeto 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"
}

Data de validade

Para saber o tempo de expiração, precisamos dar uma olhada no NotAfter e NotBefore, que estão dentro deste objeto:

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

Aplicativo de console

O trecho a seguir é um aplicativo de console simples desenvolvido em .NET 6, que produzirá o seguinte resultado, no qual você pode verificar qualquer uma das certificações desejadas:

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();

Projeto de demonstração

Você pode encontrar o aplicativo de console em meu Github, no repositório chamado expiration-date-certificate.