Le protocole Web Bot Auth (WBA) vous permet de prouver l'identité de votre agent ou de votre robot de manière cryptographique à l'aide de signatures de messages HTTP (RFC 9421).
Lorsque vous signez des requêtes sortantes, des services tels que Google Cloud Fraud Defense (l'évolution de reCAPTCHA) peuvent valider votre agent directement sans se fier uniquement aux adresses IP ou aux chaînes User-Agent.
Pour authentifier votre agent avec WBA, procédez comme suit :
- Générez une paire de clés Ed25519 et formatez la clé publique en tant que jeu de clés Web JSON (JWK).
- Hébergez votre répertoire de clés publiques sur un point de terminaison HTTPS connu.
- Signez chaque requête HTTP sortante avec votre clé privée.
- Facultatif : Signez l'en-tête expérimental
Agent-Contextpour isoler la réputation entre les locataires qui partagent une clé de signature.
Générer une paire de clés Ed25519
WBA nécessite une paire de clés asymétriques Ed25519 (RFC 8032). Vous gardez la clé privée secrète pour signer les requêtes HTTP sortantes et vous publiez la clé publique dans un ensemble de clés Web JSON (JWK) (RFC 7517).
Générer une paire de clés avec OpenSSL et jwker
Vous pouvez générer une paire de clés Ed25519 avec OpenSSL et convertir la clé publique en JWK à l'aide de l'outil Open Source jwker :
Générez une clé privée Ed25519 au format PEM :
openssl genpkey -algorithm ed25519 -out private-key.pemExtrayez la clé publique au format PEM :
openssl pkey -in private-key.pem -pubout -out public-key.pemSi vous n'avez pas encore installé
jwker, installez-le en suivant les instructions d'installation dejwker.Convertissez la clé publique PEM en fichier JWK à l'aide de
jwker:jwker public-key.pem public-key.jwkCette commande écrit un seul objet JWK dans
public-key.jwk:{ "kty": "OKP", "crv": "Ed25519", "x": "16JuFxlu7PYOFULk_W6t3sZO4NF8Yj-2QpBxCAiXQfs" }Encapsulez l'objet JWK dans un tableau d'ensemble JWK
keys(jwks.json) et ajoutez les champsalg,useetkidrequis :{ "keys": [ { "kty": "OKP", "crv": "Ed25519", "alg": "EdDSA", "use": "sig", "kid": "OE_x89FAZvHnzOd9gMU0zFW-AhHTESAL5cV5aL0dqyw", "x": "16JuFxlu7PYOFULk_W6t3sZO4NF8Yj-2QpBxCAiXQfs" } ] }
Documentation de référence sur les champs de l'ensemble JWK public
Chaque objet de clé publique de votre tableau JWK Set keys accepte les champs suivants :
| Champ | Valeur | Description |
|---|---|---|
kty |
"OKP" |
Type de clé. Doit être "OKP" (paire de clés d'octets), comme défini dans la RFC 8037. |
crv |
"Ed25519" |
Courbe cryptographique. doit être "Ed25519" |
alg |
"EdDSA" |
Algorithme destiné à être utilisé avec la clé. doit être "EdDSA" |
use |
"sig" |
Utilisation prévue de la clé publique. Définissez cet élément sur "sig" (signature). |
x |
"BASE64URL_PUBLIC_KEY" |
Clé publique Ed25519 brute de 32 octets, encodée en base64url sans marge (RFC 4648, section 5). |
kid |
"KEY_ID" |
Identifiant de clé unique correspondant au paramètre keyid dans vos requêtes signées. Utilisez l'empreinte JWK SHA-256 RFC 7638 (base64url sans remplissage) pour assurer la compatibilité entre les fournisseurs. |
nbf |
UNIX_SECONDS |
Facultatif. Code temporel Unix en secondes avant lequel la clé n'est pas valide. |
exp |
UNIX_SECONDS |
Facultatif. Code temporel Unix en secondes après lequel la clé expire. Les vérificateurs rejettent les clés expirées. |
Héberger votre répertoire de clés publiques
Les vérificateurs récupèrent vos clés publiques à partir d'un point de terminaison HTTPS connu sur le domaine de votre agent.
Exigences relatives aux points de terminaison et aux en-têtes
Pour héberger votre répertoire de clés, configurez votre serveur Web de sorte qu'il réponde aux exigences suivantes :
- Chemin d'URL : diffusez le JSON du jeu de JWK sur
https://YOUR_AGENT_DOMAIN/.well-known/http-message-signatures-directory. - Sécurité du transport : diffusez le point de terminaison via HTTPS sur le port par défaut
443. Ne redirigez pas les requêtes vers un autre hôte ou schéma. - Type de contenu : définissez l'en-tête de réponse
Content-Typesurapplication/http-message-signatures-directory+json(ouapplication/json). - Mise en cache : définissez l'en-tête de réponse
Cache-Controlavec une directivemax-age(par exemple,Cache-Control: public, max-age=3600). - Rotation des clés : avant de signer des requêtes avec une nouvelle clé privée, ajoutez sa clé JWK publique au tableau
keyset attendez l'expiration de votre TTLCache-Control. Supprimez les clés révoquées ou expirées du tableaukeys.
L'exemple suivant montre une réponse HTTP de GET /.well-known/http-message-signatures-directory :
HTTP/1.1 200 OK
Content-Type: application/http-message-signatures-directory+json
Cache-Control: public, max-age=3600
{
"keys": [
{
"kty": "OKP",
"crv": "Ed25519",
"alg": "EdDSA",
"use": "sig",
"kid": "OE_x89FAZvHnzOd9gMU0zFW-AhHTESAL5cV5aL0dqyw",
"x": "16JuFxlu7PYOFULk_W6t3sZO4NF8Yj-2QpBxCAiXQfs"
}
]
}
Signer une requête HTTP sortante
Pour authentifier une requête HTTPS sortante avec WBA, associez trois en-têtes obligatoires : Signature-Agent, Signature-Input et Signature.
En-têtes HTTP
Signature-Agent: spécifie l'origine HTTPS de votre annuaire de clés sous forme de chaîne entre guillemets :Signature-Agent: "https://agent.example.com"Signature-Input: mappe un libellé de signature (tel quesig1) à une liste interne de composants HTTP couverts et de paramètres de métadonnées de signature :Signature-Input: sig1=("@authority" "signature-agent");created=1772400000;expires=1772400300;keyid="OE_x89FAZvHnzOd9gMU0zFW-AhHTESAL5cV5aL0dqyw";tag="web-bot-auth"Incluez les composants et paramètres couverts suivants :
- Composants couverts :
"@authority": valeur obligatoire. Lie la signature à l'hôte de destination (par exemple,www.example.com)."signature-agent": valeur obligatoire. Lie la signature à votre en-têteSignature-Agent.
- Paramètres de métadonnées :
created: code temporel Unix actuel en secondes (entier).expires: code temporel Unix d'expiration en secondes (entier). Définissezexpiressur 300 secondes (5 minutes) au maximum aprèscreated.keyid: chaînekidde votre clé publique à partir de votre ensemble JWK.tag: doit être défini sur"web-bot-auth".
- Composants couverts :
Signature: mappe le même libellé de signature (sig1) à la signature Ed25519 encodée en base64, formatée en tant que séquence d'octets RFC 8941 (encadrée par des deux-points:) :Signature: sig1=:BASE64_ENCODED_SIGNATURE:
Construire la base de la signature
Avant de signer, créez la chaîne de base de signature canonique définie dans la section 2.5 de la norme RFC 9421. Chaque composant couvert apparaît sur sa propre ligne en minuscules, suivi de la ligne @signature-params sans saut de ligne final :
"@authority": www.example.com
"signature-agent": "https://agent.example.com"
"@signature-params": ("@authority" "signature-agent");created=1772400000;expires=1772400300;keyid="OE_x89FAZvHnzOd9gMU0zFW-AhHTESAL5cV5aL0dqyw";tag="web-bot-auth"
Signez les octets UTF-8 de cette chaîne de base de signature à l'aide de votre clé privée Ed25519.
Exemple Go
L'exemple suivant charge le fichier private-key.pem généré par OpenSSL et signe un *http.Request sortant à l'aide du package crypto/ed25519 standard de Go et de github.com/dunglas/httpsfv :
package main
import (
"crypto/ed25519"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
"net/http"
"strings"
"time"
"github.com/dunglas/httpsfv"
)
// loadPrivateKey parses a PKCS#8 PEM-encoded Ed25519 private key
// generated by OpenSSL.
func loadPrivateKey(pemBytes []byte) (ed25519.PrivateKey, error) {
// Decode the PEM armor to get the raw DER bytes.
block, _ := pem.Decode(pemBytes)
if block == nil {
return nil, fmt.Errorf("failed to decode PEM block")
}
// OpenSSL generates private keys in PKCS#8 format by default.
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
// Confirm that the parsed key is an Ed25519 private key.
privKey, ok := key.(ed25519.PrivateKey)
if !ok {
return nil, fmt.Errorf("not an Ed25519 private key")
}
return privKey, nil
}
// signRequestEd25519 signs req with WBA headers.
func signRequestEd25519(
req *http.Request,
privKey ed25519.PrivateKey,
keyID string,
) error {
const (
// sigLabel links the Signature-Input and Signature entries.
sigLabel = "sig1"
// agentURL is the HTTPS origin where your JWK Set is hosted.
agentURL = "https://agent.example.com"
)
// 1. Set the Signature-Agent header as a quoted string.
sigAgentHeader := fmt.Sprintf("%q", agentURL)
req.Header.Set("Signature-Agent", sigAgentHeader)
// 2. List the HTTP components to cover in the signature.
// Always cover "@authority" (the target host) and "signature-agent".
authority := strings.ToLower(req.URL.Host)
items := []httpsfv.Item{
httpsfv.NewItem("@authority"),
httpsfv.NewItem("signature-agent"),
}
baseLines := []string{
fmt.Sprintf(`"@authority": %s`, authority),
fmt.Sprintf(`"signature-agent": %s`, sigAgentHeader),
}
// If the optional Agent-Context header is set, sign it too.
if agentCtx := req.Header.Get("Agent-Context"); agentCtx != "" {
items = append(items, httpsfv.NewItem("agent-context"))
line := fmt.Sprintf(`"agent-context": %s`, agentCtx)
baseLines = append(baseLines, line)
}
// 3. Set the signature metadata parameters.
// Set expires to 300 seconds (5 minutes) after created.
now := time.Now().Unix()
params := httpsfv.NewParams()
params.Add("created", now)
params.Add("expires", now+300)
params.Add("keyid", keyID)
params.Add("tag", "web-bot-auth")
// Serialize the covered components and parameters for Signature-Input.
innerList := httpsfv.InnerList{
Items: items,
Params: params,
}
sigParamsStr, err := httpsfv.Marshal(innerList)
if err != nil {
return err
}
// 4. Build the RFC 9421 signature base string.
// Join each covered component line and @signature-params with "\n".
// Don't add a trailing newline after the @signature-params line.
paramLine := fmt.Sprintf(`"@signature-params": %s`, sigParamsStr)
baseLines = append(baseLines, paramLine)
sigBase := strings.Join(baseLines, "\n")
// 5. Sign the UTF-8 bytes of the signature base with your private key.
sigBytes := ed25519.Sign(privKey, []byte(sigBase))
sigB64 := base64.StdEncoding.EncodeToString(sigBytes)
// 6. Attach the Signature-Input and Signature headers to the request.
req.Header.Set("Signature-Input", fmt.Sprintf("%s=%s", sigLabel, sigParamsStr))
req.Header.Set("Signature", fmt.Sprintf("%s=:%s:", sigLabel, sigB64))
return nil
}
Inclure l'en-tête expérimental Agent-Context
Les plates-formes mutualisées (comme les fournisseurs de navigateurs hébergés ou les environnements d'exécution d'agents cloud) signent souvent les requêtes sortantes de plusieurs agents ou locataires clients à l'aide d'une seule clé de signature partagée. Par défaut, WBA n'identifie que le domaine de signature (Signature-Agent). Par conséquent, tout le trafic signé par cette clé partage la même réputation.
Lorsque vous incluez et signez l'en-tête Agent-Context, reCAPTCHA peut évaluer la réputation séparément pour différents produits, projets, utilisateurs ou sessions qui partagent une clé de signature commune sur votre plate-forme.
Formater l'en-tête Agent-Context
Mettez en forme Agent-Context en tant que dictionnaire de champs structurés RFC 8941, où chaque clé est un jeton en minuscules et chaque valeur est une chaîne ASCII entre guillemets :
Agent-Context: product="ResearchBot", project="prj_89a0f", user="usr_29df3", session="sess_88b1"
Tous les champs sont facultatifs. N'incluez que les champs qui s'appliquent à votre architecture :
| Champ | Description | Exemple |
|---|---|---|
product |
Nom de l'application ou nom commercial de l'agent à l'origine de la requête. | product="ResearchBot" |
project |
Identifiant de compte, d'espace de travail ou de projet de locataire pseudonymisé sur la plate-forme hôte. | project="prj_89a0f" |
user |
Identifiant pseudonymisé (tel qu'un UUID opaque ou un hachage salé) du client ou de l'utilisateur final. Conservez cette valeur stable pour les requêtes d'un même utilisateur afin de renforcer la réputation, et n'incluez jamais d'informations permettant d'identifier l'utilisateur réelles. | user="usr_29df3" |
session |
Identifiant éphémère et opaque pour une tâche, une exécution de workflow ou une session de navigation uniques. | session="sess_88b1" |
Signer l'en-tête Agent-Context
reCAPTCHA n'utilise Agent-Context pour l'isolation de la réputation que si vous incluez "agent-context" dans votre signature WBA. Les en-têtes Agent-Context non signés sont ignorés.
Pour signer l'en-tête Agent-Context, procédez comme suit :
Définissez l'en-tête
Agent-Contextsur votre*http.Requestsortant avant d'appelersignRequestEd25519:req.Header.Set("Agent-Context", `product="ResearchBot", project="prj_89a0f", user="usr_29df3", session="sess_88b1"`)Incluez
"agent-context"dans la liste des composantsSignature-Inputet la chaîne de base de la signature (gérée automatiquement par l'exemple Go) :"@authority": www.example.com "signature-agent": "https://agent.example.com" "agent-context": product="ResearchBot", project="prj_89a0f", user="usr_29df3", session="sess_88b1" "@signature-params": ("@authority" "signature-agent" "agent-context");created=1772400000;expires=1772400300;keyid="OE_x89FAZvHnzOd9gMU0zFW-AhHTESAL5cV5aL0dqyw";tag="web-bot-auth"
L'exemple suivant montre une requête HTTP complète signée avec Agent-Context :
GET /api/data HTTP/1.1
Host: www.example.com
Signature-Agent: "https://agent.example.com"
Agent-Context: product="ResearchBot", project="prj_89a0f", user="usr_29df3", session="sess_88b1"
Signature-Input: sig1=("@authority" "signature-agent" "agent-context");created=1772400000;expires=1772400300;keyid="OE_x89FAZvHnzOd9gMU0zFW-AhHTESAL5cV5aL0dqyw";tag="web-bot-auth"
Signature: sig1=:BASE64_ENCODED_SIGNATURE:
Étapes suivantes
La signature de vos requêtes avec WBA prouve le contrôle cryptographique du domaine de votre agent, mais les sites Web ont également besoin de métadonnées sur l'opérateur de votre agent et son objectif.
- Découvrez comment les propriétaires de sites utilisent les signaux d'agent validé :
- Surveillez le trafic de l'agent dans le tableau de bord Présentation de l'agent.
- Configurez des règles de test à l'aide de la variable de condition
verified_bots.
- Consultez les spécifications du protocole :