8. Défis de validation d'identifiants
Peu de types d'identifiants dans le monde disposent de mécanismes standardisés pour prouver la possession d'un identifiant donné. Dans pratiquement tous les cas pratiques, les CA s'appuient sur divers moyens pour tester si l'entité demandant un certificat pour un identifiant donné contrôle effectivement cet identifiant.
Les défis fournissent au serveur l'assurance que le titulaire du compte est également l'entité qui contrôle l'identifiant. Pour chaque type de défi, les conditions suivantes doivent être satisfaites : pour qu'une entité accomplisse avec succès le défi, l'entité doit simultanément :
- Détenir la clé privée de la paire de clés de compte utilisée pour répondre au défi, et
- Contrôler l'identifiant en question.
La section 10 documente comment les défis définis dans ce document satisfont ces exigences. Les nouveaux défis doivent documenter comment ils les satisfont.
ACME utilise un cadre extensible de défi/réponse pour la validation des identifiants. Le serveur présente un ensemble de défis (sous forme d'objets dans le tableau « challenges ») dans l'objet d'autorisation envoyé au client, et le client répond en envoyant un objet de réponse dans une requête POST à l'URL du défi.
Cette section décrit un ensemble initial de types de défis. La définition d'un type de défi comprend :
- Le contenu de l'objet de défi
- Le contenu de l'objet de réponse
- Comment le serveur utilise le défi et la réponse pour valider le contrôle de l'identifiant
Les objets de défi contiennent tous les champs de base suivants :
type (requis, chaîne) : Le type de défi encodé dans l'objet.
url (requis, chaîne) : L'URL à laquelle la réponse peut être envoyée en POST.
status (requis, chaîne) : Le statut de ce défi. Les valeurs possibles sont « pending », « processing », « valid » et « invalid » (voir section 7.1.6).
validated (optionnel, chaîne) : L'heure à laquelle le serveur a validé ce défi, encodée au format spécifié dans [RFC3339]. Ce champ est REQUIS si le champ « status » est « valid ».
error (optionnel, objet) : L'erreur survenue lors de la validation du défi par le serveur, le cas échéant, structurée comme un document de problème [RFC7807]. Des sous-problèmes (section 6.7.1) peuvent être utilisés pour indiquer plusieurs erreurs. Le statut d'un objet de défi avec une erreur DOIT être égal à « invalid ».
Tous les autres champs sont spécifiés par le type de défi. Si le serveur définit le « status » d'un défi à « invalid », il DEVRAIT également inclure le champ « error » pour aider le client à diagnostiquer la raison de l'échec du défi.
Différents défis permettent au serveur d'obtenir des preuves de différents aspects du contrôle de l'identifiant. Dans certains défis, comme HTTP et DNS, le client prouve directement sa capacité à effectuer certaines opérations liées à l'identifiant. Le choix des défis à présenter au client dans quelles circonstances relève de la politique du serveur.
Les défis de validation d'identifiants décrits dans cette section concernent tous la validation des noms de domaine. Si ACME est étendu à l'avenir pour prendre en charge d'autres types d'identifiants, de nouveaux types de défis seront nécessaires, et ils devront spécifier à quels types d'identifiants ils s'appliquent.
8.1. Autorisations de clé (Key Authorizations)
Tous les défis définis dans ce document utilisent une chaîne d'autorisation de clé. Une autorisation de clé est une chaîne qui concatène le jeton du défi avec une empreinte de clé, séparés par un caractère « . » :
keyAuthorization = token || '.' || base64url(Thumbprint(accountKey))
L'étape « Thumbprint » représente le calcul spécifié dans [RFC7638], en utilisant le condensat SHA-256 [FIPS180-4]. Comme décrit dans [RFC7518], tout octet de zéro de tête dans les champs d'objet JWK DOIT être supprimé avant d'effectuer le calcul.
Comme spécifié dans chaque défi individuel ci-dessous, le jeton d'un défi est une chaîne composée entièrement de caractères de l'alphabet base64url sécurisé pour les URL. L'opérateur « || » représente la concaténation de chaînes.
8.2. Nouvelles tentatives de défis (Retrying Challenges)
Les défis ACME exigent généralement que le client configure une ressource accessible sur le réseau que le serveur peut interroger pour vérifier que le client contrôle l'identifiant. En pratique, il n'est pas rare que la requête du serveur échoue lors de la configuration de la ressource, par exemple parce que les informations se propagent dans un cluster ou que les règles de pare-feu ne sont pas encore en place.
Les clients NE DEVRAIENT PAS répondre aux défis tant qu'ils ne croient pas que la requête du serveur réussira. Si la requête de validation initiale du serveur échoue, le serveur DEVRAIT réessayer la requête après un certain temps, pour tenir compte des délais de configuration des réponses (tels que les enregistrements DNS ou les ressources HTTP). Le calendrier exact des nouvelles tentatives est laissé à la discrétion du serveur, mais les opérateurs de serveur doivent garder à l'esprit les scénarios opérationnels que le calendrier tente d'accommoder. Étant donné que les nouvelles tentatives visent à résoudre des problèmes tels que les délais de propagation dans la configuration HTTP ou DNS, il ne devrait généralement pas y avoir de raison de réessayer plus d'une fois toutes les 5 ou 10 secondes. Pendant que le serveur tente encore, le statut du défi reste « processing » ; il n'est marqué « invalid » qu'après que le serveur a abandonné.
Le serveur DOIT fournir au client des informations sur son état de nouvelle tentative via le champ « error » dans le défi et le champ d'en-tête HTTP Retry-After dans les réponses aux requêtes de ressource de défi. Le serveur DOIT ajouter une entrée au champ « error » du défi après chaque requête de validation échouée. Le serveur DEVRAIT définir le champ d'en-tête Retry-After à un moment postérieur à la prochaine requête de validation du serveur, car le statut du défi ne changera pas avant ce moment.
Les clients peuvent demander explicitement une nouvelle tentative en renvoyant la réponse au défi dans une nouvelle requête POST (avec un nouveau nonce, etc.). Cela permet aux clients de demander une nouvelle tentative lorsqu'un changement d'état s'est produit (par exemple, après la mise à jour d'une règle de pare-feu). Le serveur DEVRAIT réessayer la requête immédiatement à la réception d'une telle requête POST. Pour éviter les attaques par déni de service via des nouvelles tentatives initiées par le client, le serveur DEVRAIT limiter le débit de telles requêtes.
8.3. Défi HTTP (HTTP Challenge)
Avec la validation HTTP, le client dans une transaction ACME prouve son contrôle sur un nom de domaine en prouvant qu'il peut configurer des ressources HTTP sur un serveur accessible sous ce nom de domaine. Le serveur ACME défie le client de configurer un fichier à un chemin spécifique, avec un contenu spécifique.
Étant donné qu'un nom de domaine peut se résoudre en plusieurs adresses IPv4 et IPv6, le serveur se connectera à sa discrétion à au moins un des hôtes trouvés dans les enregistrements DNS A et AAAA. Étant donné que de nombreux serveurs Web attribuent l'hôte virtuel HTTPS par défaut à des locataires spécifiques à faibles privilèges de manière subtile et non intuitive, le défi doit être accompli via HTTP plutôt que HTTPS.
type (requis, chaîne) : La chaîne « http-01 ».
token (requis, chaîne) : Une valeur aléatoire identifiant de manière unique le défi. Cette valeur DOIT avoir au moins 128 bits d'entropie. Elle NE DOIT PAS contenir de caractères en dehors de l'alphabet base64url, et NE DOIT PAS inclure de caractères de remplissage base64 (« = »). Voir [RFC4086] pour des informations supplémentaires sur les exigences d'aléatoire.
{
"type": "http-01",
"url": "https://example.com/acme/chall/prV_B7yEyA4",
"status": "pending",
"token": "LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0"
}
Le client accomplit ce défi en construisant une autorisation de clé à partir de la valeur « token » fournie dans le défi et de la clé de compte du client. Le client configure ensuite l'autorisation de clé comme ressource sur le serveur HTTP du nom de domaine en question.
Le chemin de la ressource configurée est composé du préfixe fixe « /.well-known/acme-challenge/ » suivi de la valeur « token » du défi. La valeur de la ressource DOIT être la représentation ASCII de l'autorisation de clé.
GET /.well-known/acme-challenge/LoqXcYV8...jxAjEuX0
Host: example.org
HTTP/1.1 200 OK
Content-Type: application/octet-stream
LoqXcYV8...jxAjEuX0.9jg46WB3...fm21mqTI
(Dans l'exemple ci-dessus, « ... » indique que le jeton et l'empreinte JWK dans l'autorisation de clé ont été tronqués pour tenir sur la page.)
Le client répond avec un objet vide ({}) pour confirmer que le serveur peut valider le défi.
POST /acme/chall/prV_B7yEyA4
Host: example.com
Content-Type: application/jose+json
{
"protected": base64url({
"alg": "ES256",
"kid": "https://example.com/acme/acct/evOfKhNU60wg",
"nonce": "UQI1PoRi5OuXzxuX7V7wL0",
"url": "https://example.com/acme/chall/prV_B7yEyA4"
}),
"payload": base64url({}),
"signature": "Q1bURgJoEslbD1c5...3pYdSMLio57mQNN4"
}
À la réception de la réponse, le serveur construit et stocke l'autorisation de clé à partir de la valeur « token » du défi et de la clé de compte client actuelle.
Étant donné la paire défi/réponse, le serveur valide le contrôle du client sur le nom de domaine en vérifiant que la ressource est configurée comme prévu.
-
Construire l'URL en remplissant le modèle d'URL [RFC6570]
"http://{domain}/.well-known/acme-challenge/{token}"où :- le champ domain est défini au nom de domaine en cours de validation ; et
- le champ token est défini au jeton dans le défi.
-
Vérifier que l'URL résultante est bien formée.
-
Déréférencer l'URL en utilisant une requête HTTP GET. Cette requête DOIT être envoyée au port TCP 80 du serveur HTTP.
-
Vérifier que le corps de la réponse est une autorisation de clé bien formée. Le serveur DEVRAIT ignorer les espaces blancs à la fin du corps.
-
Vérifier que l'autorisation de clé fournie par le serveur HTTP correspond à l'autorisation de clé stockée par le serveur.
Le serveur DEVRAIT suivre les redirections lors du déréférencement de l'URL. Par exemple, un client peut utiliser des redirections pour que la réponse puisse être servie par un serveur de gestion de certificats centralisé. Voir la section 10.2 pour les considérations de sécurité liées aux redirections.
Si toutes les validations ci-dessus réussissent, la validation est réussie. Si la requête échoue, ou si le corps ne passe pas ces vérifications, la validation échoue.
Le client DEVRAIT déprovisionner la ressource configurée pour ce défi une fois le défi terminé, c'est-à-dire une fois que la valeur du champ « status » du défi est « valid » ou « invalid ».
Notez que, puisque le jeton apparaît à la fois dans la requête envoyée par le serveur ACME et dans l'autorisation de clé dans la réponse, il est possible de construire un client qui copie le jeton de la requête vers la réponse. Les clients devraient éviter ce comportement, car il peut entraîner des vulnérabilités de type cross-site scripting ; au lieu de cela, les clients devraient effectuer une configuration explicite sur la base de chaque défi. Les clients qui copient effectivement le jeton de la requête vers la réponse DOIVENT vérifier que le jeton dans la requête correspond à la syntaxe de jeton ci-dessus (par exemple, qu'il ne contient que des caractères de l'alphabet base64url).
8.4. Défi DNS (DNS Challenge)
Lorsque l'identifiant en cours de validation est un nom de domaine, le client peut prouver son contrôle sur ce nom de domaine en configurant un enregistrement de ressource TXT contenant une valeur spécifiée pour un nom de domaine de validation spécifique.
type (requis, chaîne) : La chaîne « dns-01 ».
token (requis, chaîne) : Une valeur aléatoire identifiant de manière unique le défi. Cette valeur DOIT avoir au moins 128 bits d'entropie. Elle NE DOIT PAS contenir de caractères en dehors de l'alphabet base64url, y compris les caractères de remplissage (« = »). Voir [RFC4086] pour des informations supplémentaires sur les exigences d'aléatoire.
{
"type": "dns-01",
"url": "https://example.com/acme/chall/Rg5dV14Gh1Q",
"status": "pending",
"token": "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA"
}
Le client accomplit ce défi en construisant une autorisation de clé à partir de la valeur « token » fournie dans le défi et de la clé de compte du client. Le client calcule ensuite le condensat SHA-256 [FIPS180-4] de l'autorisation de clé.
L'enregistrement configuré dans le DNS contient l'encodage base64url de ce condensat. Le client construit le nom de domaine de validation en ajoutant l'étiquette « _acme-challenge » au nom de domaine en cours de validation, puis configure un enregistrement TXT avec la valeur du condensat sous ce nom. Par exemple, si le nom de domaine en cours de validation est « www.example.org », le client configurerait l'enregistrement DNS suivant :
_acme-challenge.www.example.org. 300 IN TXT "gfj9Xq...Rg85nM"
Le client répond avec un objet vide ({}) pour confirmer que le serveur peut valider le défi.
POST /acme/chall/Rg5dV14Gh1Q
Host: example.com
Content-Type: application/jose+json
{
"protected": base64url({
"alg": "ES256",
"kid": "https://example.com/acme/acct/evOfKhNU60wg",
"nonce": "SS2sSl1PtspvFZ08kNtzKd",
"url": "https://example.com/acme/chall/Rg5dV14Gh1Q"
}),
"payload": base64url({}),
"signature": "Q1bURgJoEslbD1c5...3pYdSMLio57mQNN4"
}
À la réception de la réponse, le serveur construit et stocke l'autorisation de clé à partir de la valeur « token » du défi et de la clé de compte client actuelle.
Pour valider le défi DNS, le serveur effectue les étapes suivantes :
-
Calculer le condensat SHA-256 [FIPS180-4] de l'autorisation de clé stockée
-
Interroger les enregistrements TXT du nom de domaine de validation
-
Vérifier que le contenu de l'un des enregistrements TXT correspond à la valeur du condensat
Si toutes les validations ci-dessus réussissent, la validation est réussie. Si aucun enregistrement DNS n'est trouvé, ou si les enregistrements DNS et la charge utile de réponse ne passent pas ces vérifications, la validation échoue.
Le client DEVRAIT déprovisionner l'enregistrement de ressource configuré pour ce défi une fois le défi terminé, c'est-à-dire une fois que la valeur du champ « status » du défi est « valid » ou « invalid ».