Qu’est-ce que Certificate Transparency, la base publique des certificats SSL / TLS ?

Qu’est-ce que Certificate Transparency, la base publique des certificats SSL / TLS ?
Lucas
février 03, 2022

Certificate Transparency est une initiative qui permet de conserver une trace de tous les certificats SSL / TLS

Certificate Transparency est une initiative lancée par Google et maintenant reprise par l’Internet Engineering Task Force (IETF).

Certificate Transparency vise à renforcer les contrôles sur les certificats émis par des autorités de certification, en stockant tous les certificats générés, dans des journaux accessibles ensuite uniquement en lecture seule.

Certificate Transparency permet de contrôler les certificats frauduleux

Les propriétaires de noms de domaine peuvent consulter très facilement les journaux de Certificate Transparency, afin de détecter si un certificat a été émis pour leur nom de domaine et sans leur accord.

De plus, les navigateurs web modernes peuvent vérifier à tout moment si le certificat d’un site internet consulté est bel et bien présent dans la base de Certificate Transparency, afin d’en contrôler l’authenticité. Le navigateur Google Chrome nécessite ainsi que les certificats émis après le 30 avril 2018 soient aussi présents dans un serveur de journalisation pour être considéré valide.

Pour en savoir plus sur la technologie « Certificate Transparency », visitez https://certificate.transparency.dev/.

Cyberwatch utilise Certificate Transparency pour construire une base de données des noms de domaine

Cyberwatch consulte les bases de Certificate Transparency pour construire une base contenant l’ensemble des noms de domaine qui ont un jour eu un certificat SSL / TLS.

En stockant l’ensemble des sites qui ont reçu un certificat SSL / TLS enregistré par Certificate Transparency, Cyberwatch dispose d’une liste interrogeable à tout moment.

Cette liste est aujourd’hui utilisée par Cyberwatch pour identifier les sous-domaines liés à un nom de domaine, et aider à établir la surface d’exposition de nos clients.

Comment utiliser Certificate Transparency pour construire une base des certificats ?

Cet article technique présente ici comment requêter Certificate Transparency afin d’obtenir une liste de certificats.

Certificate Transparency repose sur des serveurs de journalisation

Pour récupérer un certificat, il faut d’abord identifier un serveur de journalisation, aussi appelé serveur de “logs”. La liste des serveurs de logs validés par Google Chrome est diponible à l’adresse suivante : https://www.gstatic.com/ct/log_list/v2/log_list.json.

Cette liste contient actuellement 40 serveurs de logs :

$ curl -s https://www.gstatic.com/ct/log_list/v2/log_list.json | jq '.operators[].logs[].url' | wc -l
40

Dans notre cas, il n’est pas nécessaire de nous limiter aux serveurs reconnus par Google Chrome, puisque notre objectif est d’extraire un maximum de noms de domaines.

Nous allons donc utiliser la liste de tous les serveurs de logs, qui contient cette fois 104 serveurs : https://www.gstatic.com/ct/log_list/v2/all_logs_list.json.

$ curl -s https://www.gstatic.com/ct/log_list/v2/all_log_list.json | jq '.operators[].logs[].url' | wc -l
104

Attention : certains des serveurs listés ne sont plus fonctionnels!

Comment calculer le nombre de certificats stockés par Certificate Transparency ?

Afin de préparer une base de données adaptée, il faut d’abord estimer la taille des données à stocker.

Les certificats sont stockés sous forme d’une structure de données appelée “Arbre de Merkel”. Les requêtes permettant de parcourir cet arbre sont décrites dans la RFC 6962.

Nous interrogeons ainsi la route d’API ct/v1/get-sth pour obtenir la taille de l’arbre de Merkle contenant les certificats. 

$ curl -s https://ct.googleapis.com/aviator/ct/v1/get-sth | jq
{
  "tree_size": 46466472,
  "timestamp": 1480512258330,
  "sha256_root_hash": "LcGcZRsm+LGYmrlyC5LXhV1T6OD8iH5dNlb0sEJl9bA=",
  "tree_head_signature": "BAMASDBGAiEA/M0Nvt77aNe+9eYbKsv6rRpTzFTKa5CGqb56ea4hnt8CIQCJDE7pL6xgAewMd5i3G1lrBWgFooT2kd3+zliEz5Rw8w=="
}

En répétant cette opération pour chaque serveur de log fonctionnel, et en faisant la somme de toutes les tailles d’arbres de Merkel, on obtient 18 349 230 836 certificats.

Ce chiffre sera toujours croissant car les serveurs de logs sont en ajout seul.

Comment télécharger les certificats depuis Certificate Transparency ?

Pour télécharger un certificat, la RFC 6962 définit la route d’API ct/v1/get-entries qui prend les deux arguments start et end. Les serveurs de logs limitent le nombre d’entrées récupérées par une seule requête. Pour identifier ce nombre, il suffit de compter le nombre d’entrées renvoyées par une requête qui en demanderait trop.

$ curl -s https://ct.googleapis.com/aviator/ct/v1/get-entries\?start\=0\&end\=10000 | jq ".entries[].leaf_input" | wc -l
32

Quelques serveurs acceptent de renvoyer jusqu’à 1024 entrées, mais très souvent leurs résultats sont limités à 32 entrées par requête.

Prenons par exemple la première entrée du serveur de log ct.googleapis.com/aviator. Une entrée est composée des deux champs « leaf_input » et « extra_data« . Nous allons nous concentrer sur le premier qui contient le certificat qui nous intéresse ainsi que la chaîne de certificats. Lorsqu’une autorité racine comme Let’s Encrypt souhaite ajouter un certificat dans un serveur de log, elle envoie d’abord un précertificat en échange d’un “signed certificate timestamp, c’est-à-dire un message signé par le serveur de log indiquant qu’il a bien reçu le certificat à un instant donné. Le serveur de log ajoute alors le certificat dans la structure de données. D’après la section 3.1 de la RFC 6962, le champ « leaf_input » est encodé en base64. Les deux premiers octets permettent de déterminer si l’entrée contient un précertificat ou un certificat.

$ curl -s https://ct.googleapis.com/aviator/ct/v1/get-entries\?start\=0\&end\=0 | jq ".entries[0]"
{
  "leaf_input": "AAAAAAFBbwW1uQAAAAU1MIIFMTCCBBmgAwIBAgIDDjxCMA0GCSqGSIb3DQEBBQUAMDwxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5HZW9UcnVzdCwgSW5jLjEUMBIGA1UEAxMLUmFwaWRTU0wgQ0EwHhcNMTMwOTI2MDQ1NDU1WhcNMTQwOTI4MjE0ODA5WjCBvjEpMCcGA1UEBRMgY1IvcENKQnBpUS9PaGpweThnZmoxd0FiQllSU0FsL0MxEzARBgNVBAsTCkdUMjMxNjg2OTMxMTAvBgNVBAsTKFNlZSB3d3cucmFwaWRzc2wuY29tL3Jlc291cmNlcy9jcHMgKGMpMTMxLzAtBgNVBAsTJkRvbWFpbiBDb250cm9sIFZhbGlkYXRlZCAtIFJhcGlkU1NMKFIpMRgwFgYDVQQDEw93d3cuaWNlLXBhYy5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDc6SEUQttsmRaubOUXwjHWkQ/agJ15HcOlTjbTvzM1/pRQgp3Vrl+CKFQWPCPH5OyI8xfJQqDzhmB9F5PvISi5kaaq43XA7eZQXVi2bpKcDdQrv3rhCfsQCq4n49IOgRWSA32D1ycACwi3+QH+FPhbDrDhCC6aecm/1yzO0XVbkbywXpm4IjSb3Bb3ZOmUl3c+X6W7PMrut0hxmzn7H8jNXNwT3k6sxW4Ysp/gTnanx+ESnbUB96SYORzzTmiEe2wfafcRxulWg5m40PuauKrWJMVmzNzrlwosQEFDf23BnMj7UD9C80pUQjMbq9UbktIeIp5jAEKKVIlivLLzt8ZxAgMBAAGjggG3MIIBszAfBgNVHSMEGDAWgBRraT1qGEJK3Y8CZTn9NSSGeJEWMDAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMCcGA1UdEQQgMB6CD3d3dy5pY2UtcGFjLmNvbYILaWNlLXBhYy5jb20wQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL3JhcGlkc3NsLWNybC5nZW90cnVzdC5jb20vY3Jscy9yYXBpZHNzbC5jcmwwHQYDVR0OBBYEFPuWgF6Xk2yEZIMSE9cDO5ATGXvMMAwGA1UdEwEB/wQCMAAweAYIKwYBBQUHAQEEbDBqMC0GCCsGAQUFBzABhiFodHRwOi8vcmFwaWRzc2wtb2NzcC5nZW90cnVzdC5jb20wOQYIKwYBBQUHMAKGLWh0dHA6Ly9yYXBpZHNzbC1haWEuZ2VvdHJ1c3QuY29tL3JhcGlkc3NsLmNydDBMBgNVHSAERTBDMEEGCmCGSAGG+EUBBzYwMzAxBggrBgEFBQcCARYlaHR0cDovL3d3dy5nZW90cnVzdC5jb20vcmVzb3VyY2VzL2NwczANBgkqhkiG9w0BAQUFAAOCAQEAJJGA2PDjRjjhsbcy07DwR5JzIZoGQH5WxaZOQzvHRG+XhQFAuKiWZKBH4kB2exF3HNWi8N5dU+xxiryVUYoluLocu3zaGpUAAdtfUWg9lMZi5dSBJRZCQSywAofUJ0t8uvLwZe9eryCDGQHchHGId3ILwdt064RYgAgqxwXWK58mUpNQ5HB4Ln/N/AF8IRcSM3lA7R2vVPtuXCbHMfYP0re+qdAT/tsgFp4own10Fpl94O4Epr+p95RF1orAPSE4MLjYHjRUMi58fV+dafNQbfbFZ4zLAvhrVZHhle2tsuQK08X0rFYSi0Wt5PzmD17/Z3l4ChwofBLJtZ/8lDe+YAAA",
  "extra_data": "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"
}

L’extrait de code Python suivant permet ensuite d’analyser le contenu du champ « leaf_input » pour en extraire le certificat :

#!/usr/bin/env python3 
from base64 import b64decode
from construct import (Byte, Bytes, Embedded, Enum, GreedyBytes, GreedyRange,
                       Int16ub, Int24ub, Int64ub, Struct, Terminated, this)
from OpenSSL import crypto
from cryptography import x509
 
MerkleTreeHeader = Struct(
    "Version" / Byte,
    "MerkleLeafType" / Byte,
    "Timestamp" / Int64ub,
    "LogEntryType" / Enum(Int16ub, X509LogEntryType=0, PrecertLogEntryType=1),
    "Entry" / GreedyBytes,
)
 
Certificate = Struct("Length" / Int24ub, "CertData" / Bytes(this.Length))
 
CertificateChain = Struct(
    "ChainLength" / Int24ub,
    "Chain" / GreedyRange(Certificate),
)
 
PreCertEntry = Struct("LeafCert" / Certificate, Embedded(CertificateChain), Terminated)
 
 
def extract_cert_from_log_entry(log_entry: str) -> x509.Certificate:
    """Extract the main certificate from the entry.
    Returns a certificate object or None if the entry does not contain a X509."""
 
    mtl = MerkleTreeHeader.parse(b64decode(log_entry))
    # Two types of entry exists. We only consider X509LogEntry Here
    if mtl.LogEntryType == "X509LogEntryType":
        return x509.load_der_x509_certificate(
                Certificate.parse(mtl.Entry).CertData
        )
    return None
 
log_entry = (
    "AAAAAAFBbwW1uQAAAAU1MIIFMTCCBBmgAwIBAgIDDjxCMA0GCSqGSIb3DQEBBQUAMDwxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5HZW9UcnVzdCwgSW5j"
    "LjEUMBIGA1UEAxMLUmFwaWRTU0wgQ0EwHhcNMTMwOTI2MDQ1NDU1WhcNMTQwOTI4MjE0ODA5WjCBvjEpMCcGA1UEBRMgY1IvcENKQnBpUS9PaGpweThn"
    "Zmoxd0FiQllSU0FsL0MxEzARBgNVBAsTCkdUMjMxNjg2OTMxMTAvBgNVBAsTKFNlZSB3d3cucmFwaWRzc2wuY29tL3Jlc291cmNlcy9jcHMgKGMpMTMx"
    "LzAtBgNVBAsTJkRvbWFpbiBDb250cm9sIFZhbGlkYXRlZCAtIFJhcGlkU1NMKFIpMRgwFgYDVQQDEw93d3cuaWNlLXBhYy5jb20wggEiMA0GCSqGSIb3"
    "DQEBAQUAA4IBDwAwggEKAoIBAQDc6SEUQttsmRaubOUXwjHWkQ/agJ15HcOlTjbTvzM1/pRQgp3Vrl+CKFQWPCPH5OyI8xfJQqDzhmB9F5PvISi5kaaq"
    "43XA7eZQXVi2bpKcDdQrv3rhCfsQCq4n49IOgRWSA32D1ycACwi3+QH+FPhbDrDhCC6aecm/1yzO0XVbkbywXpm4IjSb3Bb3ZOmUl3c+X6W7PMrut0hx"
    "mzn7H8jNXNwT3k6sxW4Ysp/gTnanx+ESnbUB96SYORzzTmiEe2wfafcRxulWg5m40PuauKrWJMVmzNzrlwosQEFDf23BnMj7UD9C80pUQjMbq9UbktIe"
    "Ip5jAEKKVIlivLLzt8ZxAgMBAAGjggG3MIIBszAfBgNVHSMEGDAWgBRraT1qGEJK3Y8CZTn9NSSGeJEWMDAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYw"
    "FAYIKwYBBQUHAwEGCCsGAQUFBwMCMCcGA1UdEQQgMB6CD3d3dy5pY2UtcGFjLmNvbYILaWNlLXBhYy5jb20wQwYDVR0fBDwwOjA4oDagNIYyaHR0cDov"
    "L3JhcGlkc3NsLWNybC5nZW90cnVzdC5jb20vY3Jscy9yYXBpZHNzbC5jcmwwHQYDVR0OBBYEFPuWgF6Xk2yEZIMSE9cDO5ATGXvMMAwGA1UdEwEB/wQC"
    "MAAweAYIKwYBBQUHAQEEbDBqMC0GCCsGAQUFBzABhiFodHRwOi8vcmFwaWRzc2wtb2NzcC5nZW90cnVzdC5jb20wOQYIKwYBBQUHMAKGLWh0dHA6Ly9y"
    "YXBpZHNzbC1haWEuZ2VvdHJ1c3QuY29tL3JhcGlkc3NsLmNydDBMBgNVHSAERTBDMEEGCmCGSAGG+EUBBzYwMzAxBggrBgEFBQcCARYlaHR0cDovL3d3"
    "dy5nZW90cnVzdC5jb20vcmVzb3VyY2VzL2NwczANBgkqhkiG9w0BAQUFAAOCAQEAJJGA2PDjRjjhsbcy07DwR5JzIZoGQH5WxaZOQzvHRG+XhQFAuKiW"
    "ZKBH4kB2exF3HNWi8N5dU+xxiryVUYoluLocu3zaGpUAAdtfUWg9lMZi5dSBJRZCQSywAofUJ0t8uvLwZe9eryCDGQHchHGId3ILwdt064RYgAgqxwXW"
    "K58mUpNQ5HB4Ln/N/AF8IRcSM3lA7R2vVPtuXCbHMfYP0re+qdAT/tsgFp4own10Fpl94O4Epr+p95RF1orAPSE4MLjYHjRUMi58fV+dafNQbfbFZ4zL"
    "AvhrVZHhle2tsuQK08X0rFYSi0Wt5PzmD17/Z3l4ChwofBLJtZ/8lDe+YAAA"
)
 
cert = extract_cert_from_log_entry(log_entry)
print(cert.subject.get_attributes_for_oid(crypto.x509.OID_COMMON_NAME)[0].value)

Ce petit script extrait le certificat depuis la chaîne de caractères encodée en base64 et affiche le champ COMMON_NAME du certificat (ici le nom de domaine), comme le montre la ligne ci-dessous.

$ python3 extract-domain-from-log-entry.py
www.ice-pac.com

En pratique, tous les certificats n’utilisent pas le champ COMMON_NAME pour leur nom de domaine (parfois ce champ peut même être vide) et certains certificats contiennent aussi des noms de domaines alternatifs. Il est en pratique possible d’extraire des noms de domaines depuis le champ COMMON_NAME et depuis l’extension SAN (Subject Alternative Name) des certificats.

C’est ce mécanisme qui est à la base du Scan de découverte “Enumération DNS” de Cyberwatch, disponible dans nos solutions Cyberwatch Vulnerability Manager et Cyberwatch Compliance Manager.

Si cet article vous a plu, et que vous souhaitez vous aussi travailler sur des sujets avancés en sécurité informatique, n’hésitez pas à consulter nos offres d’emplois !

Consultez également notre article Certificate Transparency : comment traiter une liste de 18 milliards de certificats ?

Vous avez des questions ?

Vous souhaitez une démonstration ?

Contactez-nous et nos experts reviendront vers vous sous 24h.

Votre demande