Obtener la fecha de vencimiento de un certificado
Existen algunas aplicaciones web que lo ayudan a verificar el estado actual de los certificados de los dominios que utilizamos, y tenemos algunas de ellas.
Creé una función de Azure que se ejecuta una vez al día y verifica algunos de los dominios que necesito revisar. Es una aplicación de consola súper simple que si la fecha de vencimiento del certificado es inferior a 30 días, enviará un correo electrónico.
No se mostrará la lógica misma de cómo funciona la función, me gustaría mostrar la función que realiza la verificación base del certificado y qué datos obtenemos.
La función
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 devolverá un certificado X509Certificate2 que nos permitirá hacer un montón de cosas, incluido echar un vistazo a la fecha de vencimiento.
Resultado serializado
Este es el valor serializado del 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"
}
Fecha de vencimiento
Para saber el tiempo de vencimiento, debemos echar un vistazo a los NotAfter y NotBefore, que se encuentran dentro de este objeto:
"NotAfter":"2022-08-05T10:50:35+01:00",
"NotBefore":"2022-05-07T10:50:36+01:00",
Aplicación de consola
El siguiente fragmento es una aplicación de consola simple construida en .NET 6, que producirá el siguiente resultado, en el que podrá verificar cualquiera de las certificaciones que desee:
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();
Proyecto de demostración
Puedes encontrar la aplicación de consola en mi Github, en el repositorio llamado fecha-de-expiración-certificado.