6. Transport des messages
La communication entre les clients ACME et les serveurs ACME s'effectue via HTTPS, en utilisant la signature Web JSON (JSON Web Signature, JWS) [RFC7515] pour fournir des propriétés de sécurité supplémentaires aux messages envoyés du client vers le serveur. HTTPS assure l'authentification du serveur et la confidentialité. Grâce à quelques extensions spécifiques à ACME, JWS fournit l'authentification des charges utiles des requêtes client, la protection contre la relecture, ainsi que l'intégrité de l'URL de la requête HTTPS.
6.1. Requêtes HTTPS
Chaque fonctionnalité ACME est accomplie par le client envoyant une série de requêtes HTTPS au serveur [RFC2818], transportant des messages JSON [RFC8259]. L'utilisation de HTTPS est REQUISE. Chaque sous-section de la section 7 ci-dessous décrit le format des messages utilisés par cette fonctionnalité et l'ordre dans lequel les messages sont envoyés.
Dans la plupart des transactions HTTPS utilisées par ACME, le client ACME est le client HTTPS et le serveur ACME est le serveur HTTPS. Le serveur ACME agit en tant que client lors de la validation des défis : il est client HTTP lors de la validation du défi 'http-01', client DNS lors de la validation de 'dns-01', etc.
Les serveurs ACME DEVRAIENT suivre les recommandations de [RFC7525] lors de la configuration de leur implémentation TLS. Les serveurs ACME prenant en charge TLS 1.3 PEUVENT autoriser les clients à envoyer des données anticipées (0-RTT). Cela est sécurisé car le protocole ACME lui-même inclut une protection contre la relecture dans tous les cas où elle est nécessaire (voir section 6.5). Il n'y a donc aucune restriction sur les données ACME pouvant être transportées en 0-RTT.
Les clients ACME DOIVENT envoyer un champ d'en-tête User-Agent conformément à [RFC7231]. En plus du nom et de la version du logiciel client HTTP sous-jacent, ce champ d'en-tête DEVRAIT inclure le nom et la version du logiciel ACME.
Les clients ACME DEVRAIENT envoyer un champ d'en-tête Accept-Language conformément à [RFC7231], pour permettre la localisation des messages d'erreur.
Les serveurs ACME destinés à être universellement accessibles doivent utiliser le partage de ressources entre origines multiples (Cross-Origin Resource Sharing, CORS) pour être accessibles depuis des clients basés sur navigateur [W3C.REC-cors-20140116]. Ces serveurs DEVRAIENT définir le champ d'en-tête Access-Control-Allow-Origin à la valeur « * ».
Les champs binaires dans les objets JSON utilisés par ACME sont encodés en utilisant l'encodage base64url décrit à la section 5 de [RFC4648], selon le profil spécifié dans la section 2 de JSON Web Signature [RFC7515]. Cet encodage utilise un jeu de caractères sécurisé pour les URL. Les caractères de remplissage '=' de fin DOIVENT être supprimés. Les valeurs encodées contenant des caractères '=' de fin DOIVENT être rejetées comme mal encodées.
6.2. Authentification des requêtes
Toutes les requêtes ACME ayant un corps non vide DOIVENT encapsuler leur charge utile dans un objet JWS (JSON Web Signature) [RFC7515], signé avec la clé privée du compte, sauf indication contraire. Le serveur DOIT valider le JWS avant de traiter la requête. L'encapsulation du corps de la requête dans un JWS fournit l'authentification de la requête.
Les objets JWS envoyés comme corps de requête ACME DOIVENT satisfaire les critères supplémentaires suivants :
-
Le JWS DOIT utiliser la sérialisation JSON aplatie (Flattened JSON Serialization) [RFC7515]
-
Le JWS NE DOIT PAS avoir plusieurs signatures
-
L'option de charge utile non encodée JWS (JWS Unencoded Payload Option) [RFC7797] NE DOIT PAS être utilisée
-
L'en-tête non protégé JWS (JWS Unprotected Header) [RFC7515] NE DOIT PAS être utilisé
-
La charge utile JWS NE DOIT PAS être détachée
-
L'en-tête protégé JWS DOIT inclure les champs suivants :
-
« alg » (algorithme, Algorithm)
- Ce champ NE DOIT PAS contenir « none » ni un algorithme de code d'authentification de message (Message Authentication Code, MAC) (par exemple, les algorithmes dont la description dans le registre des algorithmes mentionne MAC/HMAC).
-
« nonce » (défini à la section 6.5)
-
« url » (défini à la section 6.4)
-
« jwk » (clé Web JSON, JSON Web Key) ou « kid » (identifiant de clé, Key ID), comme décrit ci-dessous
-
Les serveurs ACME DOIVENT implémenter l'algorithme de signature « ES256 » [RFC7518] et DEVRAIENT implémenter l'algorithme de signature « EdDSA » [RFC8037] utilisant la variante « Ed25519 » (indiquée par « crv »).
Les champs « jwk » et « kid » sont mutuellement exclusifs. Le serveur DOIT rejeter les requêtes contenant les deux.
Pour les requêtes newAccount et les requêtes revokeCert authentifiées par la clé du certificat, il DOIT y avoir un champ « jwk ». Ce champ DOIT contenir la clé publique correspondant à la clé privée utilisée pour signer le JWS.
Pour toutes les autres requêtes, la requête est signée avec un compte existant et il DOIT y avoir un champ « kid ». Ce champ DOIT contenir l'URL du compte reçue via POST vers la ressource newAccount.
Si un client envoie un JWS signé avec un algorithme non pris en charge par le serveur, le serveur DOIT retourner le code de statut 400 (Bad Request) et une erreur de type « urn:ietf:params:acme:error:badSignatureAlgorithm ». Le document de problème retourné avec l'erreur DOIT inclure un champ « algorithms » contenant un tableau des valeurs « alg » prises en charge. Voir la section 6.7 pour plus de détails sur la structure des réponses d'erreur.
Si le serveur prend en charge l'algorithme de signature « alg » mais ne prend pas en charge ou choisit de rejeter la clé publique « jwk », le serveur DOIT retourner le code de statut 400 (Bad Request) et une erreur de type « urn:ietf:params:acme:error:badPublicKey ». Les détails du document de problème DEVRAIENT décrire la raison du rejet de la clé publique ; quelques exemples de raisons :
-
« alg » est « RS256 » mais le module « n » est trop petit (par exemple, 512 bits)
-
« alg » est « ES256 » mais « jwk » ne contient pas une clé publique P-256 valide
-
« alg » est « EdDSA » et « crv » est « Ed448 », mais le serveur ne prend en charge que « EdDSA » avec « Ed25519 »
-
La clé privée correspondante est connue pour avoir été compromise
Étant donné que les requêtes client dans ACME transportent des objets JWS en sérialisation JSON aplatie, elles DOIVENT définir le champ d'en-tête Content-Type à « application/jose+json ». Si une requête ne satisfait pas cette exigence, le serveur DOIT retourner une réponse avec le code de statut 415 (Unsupported Media Type).
6.3. Requêtes GET et POST-as-GET
Notez que l'authentification via un corps de requête JWS signé signifie que les requêtes sans corps d'entité ne sont pas authentifiées, en particulier les requêtes GET. Sauf dans les cas décrits dans cette section, si le serveur reçoit une requête GET, il DOIT retourner le code de statut 405 (Method Not Allowed) et une erreur de type « malformed ».
Si un client souhaite récupérer une ressource auprès du serveur (ce qui serait normalement accompli avec un GET), il DOIT envoyer une requête POST avec un corps JWS comme décrit ci-dessus, où la charge utile du JWS est une chaîne d'octets de longueur zéro. En d'autres termes, le champ « payload » de l'objet JWS DOIT être présent et défini à la chaîne vide (« »).
Nous appelons ces requêtes « POST-as-GET ». À la réception d'une requête avec une charge utile de longueur zéro (donc non-JSON), le serveur DOIT authentifier l'expéditeur et vérifier toute règle de contrôle d'accès. Sinon, le serveur DOIT traiter cette requête comme ayant la même sémantique qu'une requête GET vers la même ressource.
Le serveur DOIT autoriser les requêtes GET vers les ressources directory et newNonce (voir section 7.1), ainsi que les requêtes POST-as-GET vers ces ressources. Cela permet aux clients de s'amorcer dans le système d'authentification ACME.
6.4. Intégrité de l'URL de la requête
Dans les déploiements, il est courant que l'entité qui termine le TLS pour HTTPS soit différente de celle qui exploite le serveur HTTPS logique, avec une couche de « routage des requêtes » entre les deux. Par exemple, une CA ACME peut avoir un réseau de distribution de contenu qui termine les connexions TLS des clients afin de pouvoir inspecter les requêtes clients pour la protection contre les attaques par déni de service (Denial-of-Service, DoS).
Ces intermédiaires peuvent également modifier des valeurs de requête non signées dans les requêtes HTTPS, telles que l'URL de la requête et les champs d'en-tête. ACME utilise JWS pour fournir un mécanisme d'intégrité qui empêche les intermédiaires de modifier l'URL de la requête vers une autre URL ACME.
Comme décrit à la section 6.2, tous les objets de requête ACME transportent un paramètre d'en-tête « url » dans leur en-tête protégé. Ce paramètre d'en-tête encode l'URL vers laquelle le client dirige la requête. À la réception d'un tel objet dans une requête HTTP, le serveur DOIT comparer le paramètre d'en-tête « url » avec l'URL de la requête. S'ils ne correspondent pas, le serveur DOIT rejeter la requête comme non autorisée.
À l'exception de la ressource directory, toutes les ressources ACME sont adressées à l'aide d'URL fournies par le serveur au client. Dans les requêtes POST envoyées à ces ressources, le client DOIT définir le paramètre d'en-tête « url » à la chaîne exacte fournie par le serveur (sans effectuer de ré-encodage de l'URL). Le serveur DEVRAIT effectuer une vérification d'égalité de chaîne correspondante, en configurant pour chaque ressource la chaîne d'URL fournie au client, et en demandant à la ressource de vérifier que la requête a la même chaîne dans son paramètre d'en-tête « url ». Si la vérification d'égalité de chaîne échoue, le serveur DOIT rejeter la requête comme non autorisée.
6.4.1. Paramètre d'en-tête JWS « url »
Le paramètre d'en-tête « url » spécifie l'URL [RFC3986] à laquelle cet objet JWS est destiné. Le paramètre d'en-tête « url » DOIT être transporté dans l'en-tête protégé du JWS. La valeur du paramètre d'en-tête « url » DOIT être une chaîne représentant l'URL cible.
6.5. Protection contre la relecture
Pour protéger les ressources ACME contre toute attaque par relecture possible, les requêtes ACME POST disposent d'un mécanisme anti-relecture obligatoire. Ce mécanisme est basé sur le fait que le serveur maintient une liste des nonces qu'il a émis et exige que toute requête signée du client transporte l'un de ces nonces.
Le serveur ACME fournit des nonces aux clients via le champ d'en-tête HTTP Replay-Nonce, comme décrit à la section 6.5.1. Le serveur DOIT inclure un champ d'en-tête Replay-Nonce dans chaque réponse réussie à une requête POST, et DEVRAIT également le fournir dans les réponses d'erreur.
Chaque JWS envoyé par un client ACME DOIT inclure un paramètre d'en-tête « nonce » dans son en-tête protégé, dont le contenu est défini à la section 6.5.2. Dans le cadre de la validation JWS, le serveur ACME DOIT vérifier que la valeur de l'en-tête « nonce » est une valeur que le serveur a précédemment fournie dans un champ d'en-tête Replay-Nonce. Une fois qu'une valeur de nonce apparaît dans une requête ACME, le serveur DOIT la considérer comme invalide, comme s'il ne l'avait jamais émise.
Lorsque le serveur rejette une requête en raison d'une valeur de nonce inacceptable (ou absente), il DOIT fournir le code de statut HTTP 400 (Bad Request) et indiquer le type d'erreur ACME « urn:ietf:params:acme:error:badNonce ». Une réponse d'erreur avec le type d'erreur « badNonce » DOIT contenir un champ d'en-tête Replay-Nonce avec un nonce frais que le serveur acceptera lors d'une nouvelle tentative de la requête originale (et éventuellement dans d'autres requêtes, selon la politique de portée des nonces du serveur). À la réception d'une telle réponse, le client DEVRAIT réessayer la requête avec le nouveau nonce.
La méthode précise utilisée pour générer et suivre les nonces est laissée à la discrétion du serveur. Par exemple, le serveur peut générer une valeur aléatoire de 128 bits pour chaque réponse, conserver une liste des nonces émis et supprimer les nonces de cette liste lorsqu'ils sont utilisés.
En dehors des contraintes ci-dessus concernant les nonces émis dans les réponses « badNonce », ACME ne limite pas la façon dont le serveur délimite la portée des nonces. Les clients PEUVENT supposer que les nonces ont une portée large, par exemple en utilisant un seul pool de nonces pour toutes les requêtes. Cependant, lors d'une nouvelle tentative suite à une erreur « badNonce », le client DOIT utiliser le nonce fourni dans la réponse d'erreur. Les serveurs DEVRAIENT délimiter la portée des nonces suffisamment largement pour que les nouvelles tentatives ne soient pas fréquemment nécessaires.
6.5.1. Champ d'en-tête Replay-Nonce
Le champ d'en-tête HTTP Replay-Nonce contient une valeur générée par le serveur que le serveur peut utiliser pour détecter les relectures non autorisées dans les futures requêtes client. Le serveur DOIT générer les valeurs fournies dans le champ d'en-tête Replay-Nonce de manière à ce qu'elles soient uniques pour chaque message avec une haute probabilité, et imprévisibles pour toute entité autre que le serveur. Par exemple, la génération aléatoire de Replay-Nonces est acceptable.
La valeur du champ d'en-tête Replay-Nonce DOIT être une chaîne d'octets encodée selon l'encodage base64url décrit à la section 2 de [RFC7515]. Les clients DOIVENT ignorer les valeurs Replay-Nonce invalides. La syntaxe ABNF [RFC5234] du champ d'en-tête Replay-Nonce est la suivante :
base64url = ALPHA / DIGIT / "-" / "_"
Replay-Nonce = 1*base64url
Le champ d'en-tête Replay-Nonce NE DEVRAIT PAS être inclus dans les messages de requête HTTP.
6.5.2. Paramètre d'en-tête JWS « nonce »
Le paramètre d'en-tête « nonce » fournit une valeur unique permettant au vérificateur du JWS d'identifier quand une relecture se produit. Le paramètre d'en-tête « nonce » DOIT être transporté dans l'en-tête protégé du JWS.
La valeur du paramètre d'en-tête « nonce » DOIT être une chaîne d'octets encodée selon l'encodage base64url décrit à la section 2 de [RFC7515]. Si la valeur du paramètre d'en-tête « nonce » est invalide selon cet encodage, le vérificateur DOIT rejeter le JWS comme mal formé.
6.6. Limites de débit
Les serveurs ACME PEUVENT imposer des limites de débit sur la création de ressources pour garantir une utilisation équitable et prévenir les abus. Une fois qu'une limite de débit est dépassée, le serveur DOIT répondre avec une erreur de type « urn:ietf:params:acme:error:rateLimited ». De plus, le serveur DEVRAIT envoyer un champ d'en-tête Retry-After [RFC7231] indiquant quand la requête actuelle pourrait à nouveau réussir. S'il y a plusieurs limites de débit, c'est le moment où toutes les limites de débit permettraient à la requête actuelle avec exactement les mêmes paramètres d'accéder à nouveau.
En plus du champ « detail » lisible par l'humain de la réponse d'erreur, le serveur PEUT envoyer une ou plusieurs relations de lien dans un champ d'en-tête Link [RFC8288], en utilisant le type de relation de lien « help » pour pointer vers la documentation sur la limite de débit spécifique déclenchée.
6.7. Erreurs
Les erreurs peuvent être signalées au niveau HTTP et dans les objets de défi, comme défini à la section 8. Les serveurs ACME PEUVENT retourner des réponses avec des codes de réponse d'erreur HTTP (4XX ou 5XX). Par exemple, si un client soumet une requête en utilisant une méthode non autorisée par ce document, le serveur PEUT retourner le code de statut 405 (Method Not Allowed).
Lorsque le serveur répond avec un statut d'erreur, il DEVRAIT fournir des informations supplémentaires en utilisant un document de problème [RFC7807]. Pour faciliter les réponses automatisées aux erreurs, ce document définit les jetons standard suivants pour le champ « type » (dans l'espace de noms URN ACME « urn:ietf:params:acme:error: ») :
| Type | Description |
|---|---|
| accountDoesNotExist | Le compte spécifié dans la requête n'existe pas |
| alreadyRevoked | Le certificat spécifié pour révocation a déjà été révoqué |
| badCSR | La CSR est inacceptable (par exemple, en raison d'une clé trop courte) |
| badNonce | Le client a envoyé un nonce anti-relecture inacceptable |
| badPublicKey | Le JWS a été signé par une clé publique non prise en charge par le serveur |
| badRevocationReason | La raison de révocation fournie n'est pas autorisée par le serveur |
| badSignatureAlgorithm | Le JWS a été signé avec un algorithme non pris en charge par le serveur |
| caa | Un enregistrement CAA (Certification Authority Authorization) interdit à la CA de délivrer le certificat |
| compound | Des conditions d'erreur spécifiques sont indiquées dans le tableau « subproblems » |
| connection | Le serveur n'a pas pu se connecter à la cible de validation |
| dns | Un problème est survenu lors d'une requête DNS pendant la validation de l'identifiant |
| externalAccountRequired | La requête doit inclure une valeur pour le champ « externalAccountBinding » |
| incorrectResponse | La réponse reçue ne correspond pas aux exigences du défi |
| invalidContact | L'URL de contact du compte est invalide |
| malformed | Le message de requête est mal formé |
| orderNotReady | La requête tente de finaliser une commande qui n'est pas encore prête à être finalisée |
| rateLimited | La requête dépasse une limite de débit |
| rejectedIdentifier | Le serveur ne délivrera pas de certificat pour cet identifiant |
| serverInternal | Le serveur a rencontré une erreur interne |
| tls | Le serveur a reçu une erreur TLS lors de la validation |
| unauthorized | Le client ne dispose pas d'une autorisation suffisante |
| unsupportedContact | L'URL de contact du compte utilise un schéma de protocole non pris en charge |
| unsupportedIdentifier | L'identifiant est d'un type non pris en charge |
| userActionRequired | Accéder à l'URL « instance » et y effectuer l'action spécifiée |
Cette liste n'est pas exhaustive. Les serveurs PEUVENT retourner des erreurs dont le champ « type » est défini à des URI autres que ceux définis ci-dessus. Les serveurs NE DOIVENT PAS utiliser l'espace de noms URN ACME pour des erreurs non répertoriées dans le registre IANA correspondant (voir section 9.6). Les clients DEVRAIENT afficher le champ « detail » de toutes les erreurs.
Dans le reste de ce document, nous utilisons les jetons du tableau ci-dessus pour faire référence aux types d'erreurs, plutôt que l'URN complet. Par exemple, « une erreur de type 'badCSR' » fait référence à un document d'erreur dont la valeur « type » est « urn:ietf:params:acme:error:badCSR ».
6.7.1. Sous-problèmes
Parfois, une CA peut avoir besoin de retourner plusieurs erreurs en réponse à une seule requête. De plus, une CA peut avoir besoin d'attribuer des erreurs à des identifiants spécifiques. Par exemple, une requête newOrder peut contenir plusieurs identifiants pour lesquels la CA ne peut pas délivrer de certificat. Dans ce cas, le document de problème ACME PEUT contenir un champ « subproblems », contenant un tableau JSON de documents de problème, chacun pouvant contenir un champ « identifier ». S'il est présent, le champ « identifier » DOIT contenir un identifiant ACME (section 9.7.7).
Le champ « identifier » NE DOIT PAS apparaître au niveau supérieur d'un document de problème ACME. Il ne peut apparaître que dans les sous-problèmes. Les sous-problèmes n'ont pas besoin d'avoir tous le même type, et ils n'ont pas besoin de correspondre au type de niveau supérieur.
Les clients ACME PEUVENT choisir d'utiliser le champ « identifier » d'un sous-problème comme indication que l'opération réussirait si cet identifiant était omis. Par exemple, si une commande contient dix identifiants DNS et que la requête newOrder retourne un document de problème avec deux sous-problèmes (référençant deux de ces identifiants), le client ACME PEUT choisir de soumettre une autre commande contenant uniquement les huit identifiants non répertoriés dans le document de problème.
HTTP/1.1 403 Forbidden
Content-Type: application/problem+json
Link: `https://example.com/acme/directory`;rel="index"
{
"type": "urn:ietf:params:acme:error:malformed",
"detail": "Some of the identifiers requested were rejected",
"subproblems": [
{
"type": "urn:ietf:params:acme:error:malformed",
"detail": "Invalid underscore in DNS name \"_example.org\"",
"identifier": {
"type": "dns",
"value": "_example.org"
}
},
{
"type": "urn:ietf:params:acme:error:rejectedIdentifier",
"detail": "This CA will not issue for \"example.net\"",
"identifier": {
"type": "dns",
"value": "example.net"
}
}
]
}