¿Cómo puedo decodificar un certificado SSL usando python?

¿Cómo puedo decodificar un certificado codificado en pem (base64) con Python? Por ejemplo esto aquí desde github.com:

-----BEGIN CERTIFICATE----- MIIHKjCCBhKgAwIBAgIQDnd2il0H8OV5WcoqnVCCtTANBgkqhkiG9w0BAQUFADBp MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 d3cuZGlnaWNlcnQuY29tMSgwJgYDVQQDEx9EaWdpQ2VydCBIaWdoIEFzc3VyYW5j ZSBFViBDQS0xMB4XDTExMDUyNzAwMDAwMFoXDTEzMDcyOTEyMDAwMFowgcoxHTAb BgNVBA8MFFByaXZhdGUgT3JnYW5pemF0aW9uMRMwEQYLKwYBBAGCNzwCAQMTAlVT MRswGQYLKwYBBAGCNzwCAQITCkNhbGlmb3JuaWExETAPBgNVBAUTCEMzMjY4MTAy MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2Fu IEZyYW5jaXNjbzEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRMwEQYDVQQDEwpnaXRo dWIuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7dOJw11wcgnz M08acnTZtlqVULtoYZ/3+x8Z4doEMa8VfBp/+XOvHeVDK1YJAEVpSujEW9/Cd1JR GVvRK9k5ZTagMhkcQXP7MrI9n5jsglsLN2Q5LLcQg3LN8OokS/rZlC7DhRU5qTr2 iNr0J4mmlU+EojdOfCV4OsmDbQIXlXh9R6hVg+4TyBkaszzxX/47AuGF+xFmqwld n0xD8MckXilyKM7UdWhPJHIprjko/N+NT02Dc3QMbxGbp91i3v/i6xfm/wy/wC0x O9ZZovLdh0pIe20zERRNNJ8yOPbIGZ3xtj3FRu9RC4rGM+1IYcQdFxu9fLZn6TnP pVKACvTqzQIDAQABo4IDajCCA2YwHwYDVR0jBBgwFoAUTFjLJfBBT1L0KMiBQ5um qKDmkuUwHQYDVR0OBBYEFIfRjxlu5IdvU4x3kQdQ36O/VUcgMCUGA1UdEQQeMByC CmdpdGh1Yi5jb22CDnd3dy5naXRodWIuY29tMIGBBggrBgEFBQcBAQR1MHMwJAYI KwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBLBggrBgEFBQcwAoY/ aHR0cDovL3d3dy5kaWdpY2VydC5jb20vQ0FDZXJ0cy9EaWdpQ2VydEhpZ2hBc3N1 cmFuY2VFVkNBLTEuY3J0MAwGA1UdEwEB/wQCMAAwYQYDVR0fBFowWDAqoCigJoYk aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL2V2MjAwOWEuY3JsMCqgKKAmhiRodHRw Oi8vY3JsNC5kaWdpY2VydC5jb20vZXYyMDA5YS5jcmwwggHEBgNVHSAEggG7MIIB tzCCAbMGCWCGSAGG/WwCATCCAaQwOgYIKwYBBQUHAgEWLmh0dHA6Ly93d3cuZGln aWNlcnQuY29tL3NzbC1jcHMtcmVwb3NpdG9yeS5odG0wggFkBggrBgEFBQcCAjCC AVYeggFSAEEAbgB5ACAAdQBzAGUAIABvAGYAIAB0AGgAaQBzACAAQwBlAHIAdABp AGYAaQBjAGEAdABlACAAYwBvAG4AcwB0AGkAdAB1AHQAZQBzACAAYQBjAGMAZQBw AHQAYQBuAGMAZQAgAG8AZgAgAHQAaABlACAARABpAGcAaQBDAGUAcgB0ACAAQwBQ AC8AQwBQAFMAIABhAG4AZAAgAHQAaABlACAAUgBlAGwAeQBpAG4AZwAgAFAAYQBy AHQAeQAgAEEAZwByAGUAZQBtAGUAbgB0ACAAdwBoAGkAYwBoACAAbABpAG0AaQB0 ACAAbABpAGEAYgBpAGwAaQB0AHkAIABhAG4AZAAgAGEAcgBlACAAaQBuAGMAbwBy AHAAbwByAGEAdABlAGQAIABoAGUAcgBlAGkAbgAgAGIAeQAgAHIAZQBmAGUAcgBl AG4AYwBlAC4wHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMBEGCWCGSAGG +EIBAQQEAwIGwDAOBgNVHQ8BAf8EBAMCBaAwDQYJKoZIhvcNAQEFBQADggEBABRS cR+GnW01Poa7ZhqLhZi5AEzLQrVG/AbnRDnI6FLYERQjs3KW6RSUni8AKPfVBEVA AMb0V0JC3gmJlxENFFxrvQv3GKNfZwLzCThjv8ESnTC6jqVUdFlTZ6EbUFsm2v0T flkXv0nvlH5FpP06STLwav+JjalhqaqblkbIHOAYHOb7gvQKq1KmyuhUItnbKj1a InuA6gcF1PnH8FNZX7t3ft6TcEFOI8t4eXnELurXZioY99HFfOISeIKNHeyCngGi 5QK+eKG5WVjFTG9PpTG0SVtemB4uOPYZxDmiSvt5BbjyWeUmEnCtwOh1Ix8Y0Qvg n2Xkw9dJh1tybLEvrG8= -----END CERTIFICATE----- 

Según ssl-shopper debería ser algo como esto:

 Common Name: github.com Subject Alternative Names: github.com, www.github.com Organization: GitHub, Inc. Locality: San Francisco State: California Country: US Valid From: May 26, 2011 Valid To: July 29, 2013 

¿Cómo puedo obtener este texto simple usando python?

La biblioteca estándar de Python, incluso en la última versión, no incluye nada que pueda decodificar los certificados X.509. Sin embargo, el paquete de cryptography complemento soporta esto. Citando un ejemplo de la documentación :

 >>> from cryptography import x509 >>> from cryptography.hazmat.backends import default_backend >>> cert = x509.load_pem_x509_certificate(pem_data, default_backend()) >>> cert.serial_number 2 

Otro paquete adicional que podría ser una opción es pyopenssl . Esta es una envoltura delgada alrededor de OpenSSL C API, lo que significa que será posible hacer lo que quieras, pero espera pasar un par de días desgarrándote la documentación.

Si no puede instalar los paquetes complementarios de Python, pero tiene la utilidad de línea de comandos openssl ,

 import subprocess cert_txt = subprocess.check_output(["openssl", "x509", "-text", "-noout", "-in", certificate]) 

debería producir aproximadamente el mismo material que obtuvo de su utilidad web en cert_txt .

Incidentalmente, la razón por la que una deencoding base64 directa le da un binario engatusado es que aquí hay dos capas de encoding. Los certificados X.509 son estructuras de datos ASN.1 , serializadas al formato X.690 DER y luego, ya que DER es un formato binario, base64-blindado para facilitar la transferencia de archivos. (Muchos de los estándares en esta área se escribieron en los años noventa cuando no se podía enviar de manera confiable nada que no fuera ASCII de siete bits).

Puede usar los paquetes pyasn1 y pyasn1_modules para analizar este tipo de datos. Por ejemplo:

 from pyasn1_modules import pem, rfc2459 from pyasn1.codec.der import decoder substrate = pem.readPemFromFile(open('cert.pem')) cert = decoder.decode(substrate, asn1Spec=rfc2459.Certificate())[0] print(cert.prettyPrint()) 

Lea los documentos para pyasn1 para el rest.

Notas :

  • Todo depende de ( !!! indocumentado! ) ssl._ssl._test_decode_cert
    (presente en Python 3 (.7) / Python 2 ), no se necesitan módulos adicionales
  • Por favor, eche un vistazo a [SO]: no se puede recibir el certificado de igual en el cliente Python utilizando ssl.SSLContext () (@ la respuesta de CristiFati) de OpenSSL , que aborda un problema más amplio

Respecto al certificado ( PEM ) de la pregunta:

  • Lo guardé en un archivo llamado q016899247.crt (en la secuencia de comandos ( code.py ) dir)
  • La etiqueta final: (” —– END CERTIFICATE —- “) faltaba un guión ( ) al final; Corregido en la Pregunta @VERSION # 4. )

code.py :

 #!/usr/bin/env python3 import sys import os import ssl from pprint import pprint as pp def main(): cert_file_name = os.path.join(os.path.dirname(__file__), "q016899247.crt") try: cert_dict = ssl._ssl._test_decode_cert(cert_file_name) pp(cert_dict) except Exception as e: print("Error decoding certificate: {:}".format(e)) if __name__ == "__main__": print("Python {:s} on {:s}\n".format(sys.version, sys.platform)) main() 

Salida :

 [cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q016899247]> "e:\Work\Dev\VEnvs\py_064_03.07.02_test0\Scripts\python.exe" code.py Python 3.7.2 (tags/v3.7.2:9a3ffc0492, Dec 23 2018, 23:09:28) [MSC v.1916 64 bit (AMD64)] on win32 {'OCSP': ('http://ocsp.digicert.com',), 'caIssuers': ('http://www.digicert.com/CACerts/DigiCertHighAssuranceEVCA-1.crt',), 'crlDistributionPoints': ('http://crl3.digicert.com/ev2009a.crl', 'http://crl4.digicert.com/ev2009a.crl'), 'issuer': ((('countryName', 'US'),), (('organizationName', 'DigiCert Inc'),), (('organizationalUnitName', 'www.digicert.com'),), (('commonName', 'DigiCert High Assurance EV CA-1'),)), 'notAfter': 'Jul 29 12:00:00 2013 GMT', 'notBefore': 'May 27 00:00:00 2011 GMT', 'serialNumber': '0E77768A5D07F0E57959CA2A9D5082B5', 'subject': ((('businessCategory', 'Private Organization'),), (('jurisdictionCountryName', 'US'),), (('jurisdictionStateOrProvinceName', 'California'),), (('serialNumber', 'C3268102'),), (('countryName', 'US'),), (('stateOrProvinceName', 'California'),), (('localityName', 'San Francisco'),), (('organizationName', 'GitHub, Inc.'),), (('commonName', 'github.com'),)), 'subjectAltName': (('DNS', 'github.com'), ('DNS', 'www.github.com')), 'version': 3} 

Este código vuelca el contenido de un archivo cert:

 import OpenSSL.crypto cert = OpenSSL.crypto.load_certificate( OpenSSL.crypto.FILETYPE_PEM, open('/path/to/cert/file.crt').read() ) print OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_TEXT, cert) 

Darle una oportunidad.