Il protocollo Web Bot Auth (WBA) ti consente di dimostrare tramite crittografia l'identità del tuo agente o bot utilizzando le firme dei messaggi HTTP (RFC 9421).
Quando firmi le richieste in uscita, servizi come Google Cloud Fraud Defense (l'evoluzione di reCAPTCHA) possono verificare direttamente il tuo agente senza fare affidamento solo sugli indirizzi IP o sulle stringhe User-Agent.
Per autenticare l'agente con WBA, completa le seguenti attività:
- Genera una coppia di chiavi Ed25519 e formatta la chiave pubblica come set di chiavi web JSON (JWK).
- Ospita la directory delle chiavi pubbliche in un endpoint HTTPS noto.
- Firma ogni richiesta HTTP in uscita con la tua chiave privata.
- (Facoltativo) Firma l'intestazione sperimentale
Agent-Contextper isolare la reputazione tra i tenant che condividono una chiave di firma.
Genera una coppia di chiavi Ed25519
WBA richiede una coppia di chiavi asimmetriche Ed25519 (RFC 8032). La chiave privata viene mantenuta segreta per firmare le richieste HTTP in uscita, mentre la chiave pubblica viene pubblicata in un set di chiavi web JSON (JWK) (RFC 7517).
Genera una coppia di chiavi con OpenSSL e jwker
Puoi generare una coppia di chiavi Ed25519 con OpenSSL e convertire la chiave pubblica in un JWK utilizzando lo strumento open source jwker:
Genera una chiave privata Ed25519 in formato PEM:
openssl genpkey -algorithm ed25519 -out private-key.pemEstrai la chiave pubblica in formato PEM:
openssl pkey -in private-key.pem -pubout -out public-key.pemSe non hai ancora installato
jwker, installalo seguendo le istruzioni di installazione dijwker.Converti la chiave pubblica PEM in un file JWK utilizzando
jwker:jwker public-key.pem public-key.jwkQuesto comando scrive un singolo oggetto JWK in
public-key.jwk:{ "kty": "OKP", "crv": "Ed25519", "x": "16JuFxlu7PYOFULk_W6t3sZO4NF8Yj-2QpBxCAiXQfs" }Aggrega l'oggetto JWK all'interno di un array JWK Set
keys(jwks.json) e aggiungi i campi obbligatorialg,useekid:{ "keys": [ { "kty": "OKP", "crv": "Ed25519", "alg": "EdDSA", "use": "sig", "kid": "OE_x89FAZvHnzOd9gMU0zFW-AhHTESAL5cV5aL0dqyw", "x": "16JuFxlu7PYOFULk_W6t3sZO4NF8Yj-2QpBxCAiXQfs" } ] }
Riferimento per il campo Set di JWK pubblici
Ogni oggetto chiave pubblica nell'array JWK Set keys supporta i seguenti campi:
| Campo | Valore | Descrizione |
|---|---|---|
kty |
"OKP" |
Tipo di chiave. Deve essere "OKP" (Octet Key Pair) come definito nella RFC 8037. |
crv |
"Ed25519" |
Curva crittografica. Deve essere "Ed25519". |
alg |
"EdDSA" |
Algoritmo destinato all'utilizzo con la chiave. Deve essere "EdDSA". |
use |
"sig" |
Uso previsto della chiave pubblica. Impostalo su "sig" (firma). |
x |
"BASE64URL_PUBLIC_KEY" |
La chiave pubblica Ed25519 non elaborata a 32 byte, codificata utilizzando base64url senza spaziatura interna (RFC 4648 Sezione 5). |
kid |
"KEY_ID" |
Identificatore univoco della chiave che corrisponde al parametro keyid nelle richieste firmate. Utilizza l'impronta JWK SHA-256 RFC 7638 (base64url senza spaziatura interna) per la compatibilità tra fornitori. |
nbf |
UNIX_SECONDS |
Facoltativo. Timestamp Unix in secondi prima del quale la chiave non è valida. |
exp |
UNIX_SECONDS |
Facoltativo. Timestamp Unix in secondi dopo il quale la chiave scade. I verificatori rifiutano le chiavi scadute. |
Ospita la directory delle chiavi pubbliche
I verificatori recuperano le tue chiavi pubbliche da un endpoint HTTPS noto sul dominio del tuo agente.
Requisiti per endpoint e intestazioni
Per ospitare la directory delle chiavi, configura il server web in modo che soddisfi i seguenti requisiti:
- Percorso dell'URL: pubblica il set di JWK JSON all'indirizzo
https://YOUR_AGENT_DOMAIN/.well-known/http-message-signatures-directory. - Sicurezza del trasporto: l'endpoint deve essere accessibile tramite HTTPS sulla porta predefinita
443. Non reindirizzare le richieste a un host o uno schema diverso. - Tipo di contenuti: imposta l'intestazione della risposta
Content-Typesuapplication/http-message-signatures-directory+json(oapplication/json). - Memorizzazione nella cache: imposta l'intestazione della risposta
Cache-Controlcon un'istruzionemax-age(ad esempio,Cache-Control: public, max-age=3600). - Rotazione delle chiavi: prima di firmare le richieste con una nuova chiave privata, aggiungi la relativa JWK pubblica all'array
keyse attendi la scadenza del TTLCache-Control. Rimuovi le chiavi revocate o scadute dall'arraykeys.
L'esempio seguente mostra una risposta HTTP da 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"
}
]
}
Firma una richiesta HTTP in uscita
Per autenticare una richiesta HTTPS in uscita con WBA, allega tre intestazioni obbligatorie: Signature-Agent, Signature-Input e Signature.
Intestazioni HTTP
Signature-Agent: specifica l'origine HTTPS della directory delle chiavi come stringa tra virgolette:Signature-Agent: "https://agent.example.com"Signature-Input: mappa un'etichetta di firma (ad esempiosig1) a un elenco interno di componenti HTTP coperti e parametri dei metadati della firma:Signature-Input: sig1=("@authority" "signature-agent");created=1772400000;expires=1772400300;keyid="OE_x89FAZvHnzOd9gMU0zFW-AhHTESAL5cV5aL0dqyw";tag="web-bot-auth"Includi i seguenti componenti e parametri coperti:
- Componenti coperti:
"@authority": obbligatorio. Associa la firma all'host di destinazione (ad esempio,www.example.com)."signature-agent": obbligatorio. Associa la firma all'intestazioneSignature-Agent.
- Parametri dei metadati:
created: timestamp Unix corrente in secondi (numero intero).expires: timestamp Unix di scadenza in secondi (numero intero). Impostaexpiressu un massimo di 300 secondi (5 minuti) dopocreated.keyid: la stringakiddella chiave pubblica dal set JWK.tag: deve essere"web-bot-auth".
- Componenti coperti:
Signature: mappa la stessa etichetta della firma (sig1) alla firma Ed25519 con codifica Base64 formattata come sequenza di byte RFC 8941 (racchiusa tra due punti:):Signature: sig1=:BASE64_ENCODED_SIGNATURE:
Costruisci la base della firma
Prima di firmare, crea la stringa di base della firma canonica definita nella sezione 2.5 di RFC 9421. Ogni componente coperto viene visualizzato su una riga separata in minuscolo, seguito dalla riga @signature-params senza nuova riga finale:
"@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"
Firma i byte UTF-8 di questa stringa di base della firma utilizzando la tua chiave privata Ed25519.
Esempio di Go
L'esempio seguente carica il file private-key.pem generato da OpenSSL e firma un *http.Request in uscita utilizzando il pacchetto crypto/ed25519 standard di Go e 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
}
Includi l'intestazione Agent-Context sperimentale
Le piattaforme multi-tenant (come i provider di browser in hosting o i runtime degli agenti cloud) spesso firmano le richieste in uscita da più agenti o tenant dei clienti utilizzando una singola chiave di firma condivisa. Per impostazione predefinita, WBA identifica solo il dominio di firma (Signature-Agent), pertanto tutto il traffico firmato da quella chiave condivide la stessa reputazione.
Quando includi e firmi l'intestazione Agent-Context, reCAPTCHA può valutare la reputazione separatamente per prodotti, progetti, utenti o sessioni distinti che condividono una chiave di firma comune sulla tua piattaforma.
Formatta l'intestazione Agent-Context
Formatta Agent-Context come dizionario di campi strutturati RFC 8941 in cui ogni chiave è un token in minuscolo e ogni valore è una stringa ASCII tra virgolette:
Agent-Context: product="ResearchBot", project="prj_89a0f", user="usr_29df3", session="sess_88b1"
Tutti i campi sono facoltativi. Includi solo i campi applicabili alla tua architettura:
| Campo | Descrizione | Esempio |
|---|---|---|
product |
Nome dell'applicazione o nome commerciale dell'agente che effettua la richiesta. | product="ResearchBot" |
project |
Identificatore pseudonimizzato dell'account, dello spazio di lavoro o del progetto tenant sulla piattaforma host. | project="prj_89a0f" |
user |
Identificatore pseudonimo (ad esempio un UUID opaco o un hash con salt) per il cliente o l'utente finale. Mantieni questo valore stabile tra le richieste dello stesso utente per creare reputazione e non includere mai PII reali. | user="usr_29df3" |
session |
Identificatore temporaneo e opaco per una singola attività, un'esecuzione del workflow o una sessione di navigazione. | session="sess_88b1" |
Firma l'intestazione Agent-Context
reCAPTCHA utilizza Agent-Context solo per l'isolamento della reputazione quando includi "agent-context" nella firma WBA. Le intestazioni Agent-Context non firmate vengono ignorate.
Per firmare l'intestazione Agent-Context, completa i seguenti passaggi:
Imposta l'intestazione
Agent-Contextsulla*http.Requestin uscita prima di chiamaresignRequestEd25519:req.Header.Set("Agent-Context", `product="ResearchBot", project="prj_89a0f", user="usr_29df3", session="sess_88b1"`)Includi
"agent-context"nell'elenco dei componentiSignature-Inpute nella stringa di base della firma (gestita automaticamente dall'esempio 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'esempio seguente mostra una richiesta HTTP completa firmata con 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:
Passaggi successivi
La firma delle richieste con WBA dimostra il controllo crittografico del dominio del tuo agente, ma i siti web hanno anche bisogno di metadati su chi gestisce l'agente e per quale scopo.
- Scopri come i proprietari dei siti utilizzano gli indicatori di Verified Agent:
- Monitora il traffico dell'agente nella dashboard Panoramica dell'agente.
- Configura le policy di verifica utilizzando la variabile di condizione
verified_bots.
- Leggi le specifiche del protocollo: