Aller au contenu Aller au menu principal Aller au menu secondaire Aller au pied de page

RDAP, obtenir des informations sur un nom de domaine

Accueil > Observatoire & ressources > Papiers d’experts > RDAP, obtenir des informations sur un nom de domaine
Le 08/11/2022

Pour obtenir des informations sur un nom de domaine, par exemple sur l’identité de son titulaire, ou bien la date d’expiration prévue, on peut utiliser des interfaces Web, qui conviennent bien pour l’usage épisodique. Mais si on veut effectuer un grand nombre de requêtes, ou bien si on veut les exécuter périodiquement sans intervention humaine, on se sert traditionnellement du protocole whois1. Or, celui-ci a désormais une alternative plus moderne, RDAP (Registration Data Access Protocol). Cet article va rapidement présenter RDAP et donner trois exemples d’utilisation, dont j’espère qu’ils inspireront les programmeuses et programmeurs pour développer leurs propres outils. Bien qu’il existe des clients RDAP tout faits (comme RDAP Browser sur Android ou nicinfo sur Unix), je me focalise ici sur le cas où on développe ses propres clients utilisant RDAP.

RDAP, c’est quoi

Avant RDAP, whois était la solution vedette pour des accès automatisés aux informations sur les noms de domaine. Ce protocole whois, normalisé dans le RFC 3912, est très simple, et écrire un client ou un serveur whois est donc un exercice que les enseignantes et enseignants de programmation donnent parfois. Mais whois a les défauts de sa simplicité :

  • Il n’y a pas de mécanisme standard pour trouver le serveur whois pour un nom de domaine donné ; chaque client whois utilise des heuristiques qui lui sont propres2, avec plus ou moins de succès.
  • Il n’y a pas de mécanisme standard pour indiquer au serveur des options de recherche.
  • Il n’y a aucune confidentialité, tout passe en clair.
  • Il n’y a pas d’authentification possible, à part sur l’adresse IP source ; on ne peut pas faire facilement des accès différenciés selon l’utilisateur.
  • Il n’y a pas d’internationalisation, car on ne peut pas indiquer le jeu de caractères et l’encodage utilisé3.
  • Les données ne sont pas structurées, c’est simplement du texte libre, ce qui rend leur analyse par un programme très complexe4.

RDAP résout tous ces problèmes. Il repose sur les techniques HTTPS (Hypertext Transfer Protocol Secure), avec les principes REST (REpresentational State Transfer), et le format JSON (JavaScript Object Notation) :

  • Un mécanisme standard de découverte du serveur, à partir d’un registre IANA, existe5.
  • Des options peuvent être indiquées dans l’URL.
  • Du fait de l’utilisation de HTTPS, tout est chiffré.
  • Du fait de l’utilisation de HTTP, on peut utiliser les diverses techniques d’authentification en HTTP, utilisées par exemple dans les innombrables API REST existantes.
  • Le jeu de caractères et son encodage sont normalisés (évidemment Unicode et UTF-8).
  • Du fait de l’utilisation de JSON, les données sont structurées et bien plus faciles à analyser.

RDAP est normalisé dans les RFC 9082 et RFC 9083.

Cet article étant destiné avant tout aux programmeurs et programmeuses, on va donc parler de code. Les programmes ne seront présentés que de manière relativement superficielle, le code complet étant en ligne. On utilisera ici le langage de programmation Python, pour sa simplicité de lecture, mais, évidemment, tout langage de programmation permettant de faire des requêtes HTTPS et d’analyser du JSON, c’est-à-dire tout langage de programmation utilisé aujourd’hui, peut servir à développer un client RDAP6.

Les trois programmes présentés dépendent de la même bibliothèque, qui se trouve dans le fichier ianardap.py. En effet, tout client RDAP doit commencer par savoir quel serveur RDAP sert le  domaine qui l’intéresse. Contrairement à whois, il y a une méthode normalisée pour cela : télécharger à l’IANA le fichier JSON qui contient la liste des serveurs RDAP7-8. En utilisant l’excellente bibliothèque Python requests pour faire de l’HTTP, c’est un simple :

response = requests.get("https://data.iana.org/rdap/dns.json")

On analyse ensuite ce fichier avec le module json de la bibliothèque standard de Python :

database = json.loads(content)

self.version = database["version"]

self.services = {}

for service in database["services"]:

    for tld in service[0]: # Plusieurs TLD peuvent partager le même serveur

         for server in service[1]:

            self.services[tld] = server

La classe IanaRDAPDatabase fournit une fonction find pour trouver le serveur d’un nom de domaine donné :

% python

>>> import ianardap

>>> base = ianardap.IanaRDAPDatabase()

>>> base.find("toto.fr")

'https://rdap.nic.fr/’

Armé de ce module, on peut passer aux programmes utiles. Trois exemples sont donnés ci-dessous.

Surveiller l’expiration

Notre premier exemple sera pour traiter un problème courant : superviser l’expiration d’un nom. Dans beaucoup de registres, les noms de domaine expirent s’ils ne sont pas renouvelés régulièrement. Parfois, le titulaire oublie ce renouvellement et le nom est alors supprimé, avec les conséquences qu’on imagine. Il est donc crucial de surveiller que le domaine ne va pas expirer prochainement9, surveillance qui peut être automatisée avec des outils comme Icinga ou Zabbix. Avec whois, si on supervise des domaines dans des TLD (Top-Level Domain) différents, il faut connaître les formats des divers registres, et analyser des dates qui vont toutes être dans des formats différents. Les choses sont plus simples avec RDAP : on fait une requête RDAP et, dans le membre events de l’objet JSON retourné, on cherche l’évènement expiration :

response = requests.get("%s/domain/%s" % (server, domain))

rdap = json.loads(response.content)

for event in rdap["events"]:

    if event["eventAction"] == "expiration":

        expiration = datetime.datetime.strptime(event["eventDate"], RFC3339)

On va ensuite regarder si cette date n’est pas trop proche dans le futur, les calculs étant faits avec le module datetime de la bibliothèque standard de Python :

now = datetime.datetime.now(tz=UTCINFO)  

rest = expiration - now

if rest < critical:

     print("CRITICAL: domain %s expires in %s, HURRY UP!!!" % (domain, rest))

     sys.exit(STATE_CRITICAL)

Un exemple d’utilisation en ligne de commande serait :

% ./check_expiration.py -H quimper.fr  

quimper.fr OK: expires in 14 days, 9:43:40.111753.

Ou, pour un domaine plus négligé :

% ./check_expiration.py -H nic.abogado

nic.abogado CRITICAL: domain nic.abogado is already expired (34 days, 8:22:36.758468 ago)

Le tout peut ensuite être utilisé depuis un programme de supervision automatique (l’exemple fourni utilise l’API de Nagios, commune à plusieurs programmes de supervision).

Obtenir des informations sur le pays du titulaire

Cherchons maintenant autre chose, le pays du titulaire d’un nom. Le problème est plus compliqué qu’il n’y paraît car, si RDAP normalise le format, il ne normalise pas les données envoyées ; certains registres ne distribuent pas cette information. Dans le cas de .fr, elle n’est donnée que pour les personnes morales, les informations sur les personnes physiques n’étant pas distribuées par défaut10. Le programme doit donc être « défensif » et se préparer à ne pas tout obtenir :

response = requests.get("%s/domain/%s" % (server, urllib.parse.quote(domain)))

rdap = json.loads(response.content)

if "entities" not in rdap:

    error("No entities in RDAP response")

if rdap["entities"] is not None:

    for entity in rdap["entities"]:

        if "registrant" in entity["roles"]:

            if "vcardArray" in entity:

                for item in entity["vcardArray"][1]:

                    if item[0] == "adr":

                        country = item[3][6]

Eh oui, les données « sociales » concernant une entité (ici, le titulaire) ne sont pas faciles à analyser. RDAP utilise le format jCard11, qui est une adaptation à JSON du très ancien format vCard12, utilisé dans tous les carnets d’adresse. Plusieurs informations importantes sont dans des tableaux de taille fixe (comme l’adresse, qui est forcément dans un tableau de six éléments), plutôt que dans des dictionnaires. Le contenu des éléments n’est pas non plus toujours bien homogène. Par exemple, alors que vCard précise que le pays doit être indiqué en toutes lettres, en langue naturelle, la plupart des serveurs RDAP envoient plutôt un code à deux lettres tiré de la norme ISO 316613. Voici quelques exemples de résultats du programme ci-dessus :

% ./country.py ssi.gouv.fr

FR

 

% ./country.py amazon.fr

LU

 

% ./country.py durand.fr 

durand.fr: No country found for durand.fr, may be restricted access for privacy reasons

Dans le premier cas, on voit que le titulaire du domaine de l’ANSSI est en France, ce qui est logique. Dans le deuxième, le titulaire est dans un paradis fiscal. Dans le troisième, le titulaire est une personne physique et aucune information sur son pays n’a été donnée. Regardons en dehors de la France :

% ./country.py guaranifc.com.br

BR

Le titulaire du domaine de ce club de football est au Brésil14.

Contrôler la cohérence d’un domaine

Un des principaux risques de sécurité liés aux noms de domaine est celui d’un détournement du nom comme dans le cas célèbre des noms de domaine de gouvernements moyen-orientaux en 2018-2019. Il existe plusieurs méthodes pour limiter les risques15 mais, de toute façon, il est prudent de suivre ses domaines critiques et de voir s’ils ont été changés au niveau du registre16. On peut utiliser RDAP pour comparer automatiquement la liste théorique des serveurs de nom du domaine et ce que connaît le registre, ce qui permet d’être alerté en cas de modification.

Pour cela, on demande à l’utilisateur de fournir la liste des serveurs de noms attendus, sous forme d’un objet JSON, puis on effectue une requête RDAP. On construit un dictionnaire Python indexé par les noms des serveurs :

r = requests.get("%s/domain/%s" % (server, urllib.parse.quote(domain)))

nameservers = {}

response = json.loads(r.text)

for ns in response["nameservers"]:

        nameservers[ns["ldhName"]] = []

        for family in ["v4", "v6"]:

            if family in ns["ipAddresses"]:

                for addr in ns["ipAddresses"][family]:

                    nameservers[ns["ldhName"]].append(addr)

Et on compare la liste retournée par RDAP (nameservers) avec la liste attendue (delegation), celle donnée par l’utilisateur :

for ns in nameservers:

        if ns not in delegation:

            error += "Name server %s should not be there\n" % ns

        else:

            if nameservers[ns] is not None:

                for addr in nameservers[ns]:

                    if delegation[ns] is None or addr not in delegation[ns]:

                        error += "Glue record %s should not be there for server %s " % (addr, ns)

    for ns in delegation:

        if ns not in nameservers:

            error += "Name server %s is missing\n" % ns

        else:

            if delegation[ns] is not None:

                for addr in delegation[ns]:

                    if nameservers[ns] is None or addr not in nameservers[ns]:

                        error += "Glue record %s is missing for server %s " % (addr, ns)

Un exemple d’utilisation17 depuis la ligne de commande (mais, bien sûr, en utilisation réelle, on le lancera périodiquement depuis un programme de supervision) :

% ./check_domain_delegation.py  -H ssi.gouv.fr -D '{"dns1.ssi.gouv.fr": ["213.56.166.96"], "dns2.ssi.gouv.fr": ["86.65.182.91"], "ns6.gandi.net": []}' ssi.gouv.fr OK - No error

Quelques petites remarques pour conclure

Limitation de trafic

Une requête RDAP peut nécessiter un travail assez important pour le serveur. Pour éviter les goinfres qui feraient des requêtes RDAP à un rythme trop intense, de nombreux serveurs RDAP mettent en place une limitation de trafic. Vos programmes doivent donc se comporter de manière raisonnable, et réagir proprement s’ils reçoivent un code de retour HTTP 42918, qui indique un usage trop intense.

429
Figure 1: Une des illustrations du site http.cat, qui présente une image de chat par code de retour HTTP.

Déploiement de RDAP dans les TLD

RADAP ccTLD
Figure 2: Carte du déploiement de RDAP dans les ccTLD, en octobre 2022, tirée du site https://deployment.rdap.org/

Tous les TLD n’ont pas encore un serveur RDAP. Outre ceux sous contrat avec l’ICANN, qui ont l’obligation du déploiement d’un tel serveur, seuls certains TLD de pays, comme .fr, ont un serveur RDAP.


1 – Notez que les interfaces Web d’accès à l’information sont parfois appelées whois, par abus de langage, alors qu’elles n’ont pas de rapport avec whois. Les confusions que cela génère sont fréquentes. On voit ainsi des gens parler de « base whois » ou de « modifier le whois », ce qui n’a aucun sens.

2 – Par exemple, GNU whois a une liste pré-définie de serveurs, gérée manuellement par l’auteur du logiciel, et le whois de FreeBSD consulte une liste distribuée par le DNS, dans le domaine whois-servers.net.

3 – En pratique, beaucoup de clients et de serveurs utilisent le jeu Unicode et l’encodage UTF-8, ce qui fonctionne… souvent.

4 – Notons toutefois qu’il existe des bibliothèques en logiciel libre, comme Net::DRI pour le langage Perl, qui simplifient considérablement cette tâche.

5 – Il est normalisé dans le RFC 9224.

6 – Les utilisatrices et utilisateurs du shell Unix pourront faire bien des choses avec curl et jq, par exemple.

7 – Le module mémorise ce fichier, en tenant compte des directives envoyées par l’IANA, pour éviter de surcharger le serveur de celle-ci.

8 – L’IANA a également un fichier contenant tous les BE (Bureaux d’Enregistrement) accrédités par l’ICANN et qui indique leur serveur RDAP, si on souhaite court-circuiter celui du registre, ou bien si le TLD, comme noté à la fin de cet article, n’a pas RDAP mais que le BE l’a.

9 – Bien sûr, il est recommandé d’activer le renouvellement automatique. Toutefois, il est préférable d’avoir ceinture et bretelles. Renouveler et surveiller.

10 – Et ce n’est pas en raison du RGPD, c’était le cas bien avant la publication de ce règlement.

11 – Normalisé dans le RFC 7095. L’IETF (Internet Engineering Task Force) a un projet de remplacement de jCard par JSContact.

12 – Normalisé, lui, dans le RFC 6350.

13 – Ce qui semble plus raisonnable, car ces codes sont internationaux et sans ambiguité.

14 – Certains ccTLD (Country-Code Top-Level Domain) imposent que le titulaire réside dans le pays, d’autres demandent une résidence dans un espace plus large, comme l’Union Européenne, et d’autres encore ne fixent pas de limite.

15 – Comme le verrouillage du nom, méthode très efficace et recommandée par l’ANSSI dans son guide de bonnes pratiques pour l’acquisition et l’exploitation de noms de domaine.

16 – Notez que cela ne protège que contre certains détournements, ceux où l’attaquant modifie la liste des serveurs de noms. Si l’attaque réussie a porté sur, par exemple, l’hébergeur DNS, l’attaquant ne modifiera peut-être que des adresses IP ou des enregistrements MX, sans toucher à la base de données du registre.

17 – Vous noterez un point important avec un tel programme : si on change la liste des serveurs de noms, ou bien leurs adresses IP pour ceux qui sont sous le domaine servi, il faudra modifier les paramètres du programme de supervision. L’utilisation d’un tel programme demande donc une certaine rigueur dans la gestion du domaine.

18 – Normalisé dans le RFC 6585.