O protocolo Web Bot Auth (WBA) permite que você comprove criptograficamente a identidade do seu agente ou bot usando assinaturas de mensagens HTTP (RFC 9421).
Quando você assina solicitações de saída, serviços como o Google Cloud Fraud Defense (a
evolução do reCAPTCHA) podem verificar seu agente diretamente sem depender
apenas de endereços IP ou strings User-Agent.
Para autenticar seu agente com a WBA, conclua as seguintes tarefas:
- Gere um par de chaves Ed25519 e formate a chave pública como um conjunto de chaves JSON da Web (JWK).
- Hospede seu diretório de chaves públicas em um endpoint HTTPS conhecido.
- Assine cada solicitação HTTP de saída com sua chave privada.
- Opcional: assine o cabeçalho experimental
Agent-Contextpara isolar a reputação entre locatários que compartilham uma chave de assinatura.
Gerar um par de chaves Ed25519
A WBA requer um par de chaves assimétricas Ed25519 (RFC 8032). Mantenha a chave privada em segredo para assinar solicitações HTTP de saída e publique a chave pública em um conjunto de chaves da Web JSON (JWK) (RFC 7517).
Gerar um par de chaves com OpenSSL e jwker
É possível gerar um par de chaves Ed25519 com o OpenSSL e converter a chave pública em
um JWK usando a ferramenta de código aberto jwker:
Gere uma chave privada Ed25519 no formato PEM:
openssl genpkey -algorithm ed25519 -out private-key.pemExtraia a chave pública no formato PEM:
openssl pkey -in private-key.pem -pubout -out public-key.pemSe você ainda não tiver o
jwkerinstalado, siga as instruções de instalação dojwker.Converta a chave pública PEM em um arquivo JWK usando
jwker:jwker public-key.pem public-key.jwkEsse comando grava um único objeto JWK em
public-key.jwk:{ "kty": "OKP", "crv": "Ed25519", "x": "16JuFxlu7PYOFULk_W6t3sZO4NF8Yj-2QpBxCAiXQfs" }Envolva o objeto JWK em uma matriz de conjunto de JWK
keys(jwks.json) e adicione os campos obrigatóriosalg,useekid:{ "keys": [ { "kty": "OKP", "crv": "Ed25519", "alg": "EdDSA", "use": "sig", "kid": "OE_x89FAZvHnzOd9gMU0zFW-AhHTESAL5cV5aL0dqyw", "x": "16JuFxlu7PYOFULk_W6t3sZO4NF8Yj-2QpBxCAiXQfs" } ] }
Referência de campo do conjunto de JWKs público
Cada objeto de chave pública na matriz keys do conjunto de JWK é compatível com os seguintes
campos:
| Campo | Valor | Descrição |
|---|---|---|
kty |
"OKP" |
Tipo de chave. Precisa ser "OKP" (par de chaves de octeto), conforme definido na RFC 8037. |
crv |
"Ed25519" |
Curva criptográfica. Precisa ser "Ed25519". |
alg |
"EdDSA" |
Algoritmo destinado ao uso com a chave. Precisa ser "EdDSA". |
use |
"sig" |
Uso pretendido da chave pública. Defina como "sig" (assinatura). |
x |
"BASE64URL_PUBLIC_KEY" |
A chave pública Ed25519 bruta de 32 bytes, codificada usando base64url sem preenchimento (RFC 4648, seção 5). |
kid |
"KEY_ID" |
Identificador de chave exclusivo que corresponde ao parâmetro keyid nas suas solicitações assinadas. Use a impressão digital JWK SHA-256 RFC 7638 (base64url sem preenchimento) para compatibilidade entre fornecedores. |
nbf |
UNIX_SECONDS |
Opcional. Carimbo de data/hora Unix em segundos antes do qual a chave não é válida. |
exp |
UNIX_SECONDS |
Opcional. Carimbo de data/hora Unix em segundos após o qual a chave expira. Os verificadores rejeitam chaves expiradas. |
Hospedar o diretório de chaves públicas
Os verificadores buscam suas chaves públicas em um endpoint HTTPS conhecido no domínio do seu agente.
Requisitos de endpoint e cabeçalho
Para hospedar o diretório de chaves, configure o servidor da Web para atender aos seguintes requisitos:
- Caminho do URL: disponibilize o JSON do conjunto de JWKs em
https://YOUR_AGENT_DOMAIN/.well-known/http-message-signatures-directory. - Segurança de transporte: disponibilize o endpoint por HTTPS na porta padrão
443. Não redirecione as solicitações para um host ou esquema diferente. - Tipo de conteúdo: defina o cabeçalho de resposta
Content-Typecomoapplication/http-message-signatures-directory+json(ouapplication/json). - Armazenamento em cache: defina o cabeçalho de resposta
Cache-Controlcom uma diretivamax-age(por exemplo,Cache-Control: public, max-age=3600). - Rotação de chaves: antes de assinar solicitações com uma nova chave privada, adicione a
JWK pública dela à matriz
keyse aguarde o TTL deCache-Controlexpirar. Remova as chaves revogadas ou expiradas da matrizkeys.
O exemplo a seguir mostra uma resposta 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"
}
]
}
Assinar uma solicitação HTTP de saída
Para autenticar uma solicitação HTTPS de saída com a WBA, anexe três cabeçalhos obrigatórios: Signature-Agent, Signature-Input e Signature.
Cabeçalhos HTTP
Signature-Agent: especifica a origem HTTPS do diretório de chaves como uma string entre aspas:Signature-Agent: "https://agent.example.com"Signature-Input: mapeia um rótulo de assinatura (comosig1) para uma lista interna de componentes HTTP cobertos e parâmetros de metadados de assinatura:Signature-Input: sig1=("@authority" "signature-agent");created=1772400000;expires=1772400300;keyid="OE_x89FAZvHnzOd9gMU0zFW-AhHTESAL5cV5aL0dqyw";tag="web-bot-auth"Inclua os seguintes componentes e parâmetros cobertos:
- Componentes cobertos:
"@authority": obrigatório. Vincula a assinatura ao host de destino (por exemplo,www.example.com)."signature-agent": obrigatório. Vincula a assinatura ao cabeçalhoSignature-Agent.
- Parâmetros de metadados:
created: carimbo de data/hora Unix atual em segundos (número inteiro).expires: carimbo de data/hora Unix de validade em segundos (número inteiro). Definaexpirescomo no máximo 300 segundos (5 minutos) apóscreated.keyid: a stringkidda chave pública do conjunto de JWK.tag: precisa ser"web-bot-auth".
- Componentes cobertos:
Signature: mapeia o mesmo rótulo de assinatura (sig1) para a assinatura Ed25519 codificada em base64 formatada como uma sequência de bytes RFC 8941 (envolto em dois pontos:):Signature: sig1=:BASE64_ENCODED_SIGNATURE:
Criar a base da assinatura
Antes de assinar, construa a string de base de assinatura canônica definida na
Seção 2.5 da RFC 9421. Cada
componente coberto aparece na própria linha em letras minúsculas, seguido pela
linha @signature-params sem nova linha 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"
Assine os bytes UTF-8 dessa string de base de assinatura usando sua chave privada Ed25519.
Exemplo de Go
O exemplo a seguir carrega o arquivo private-key.pem gerado pelo OpenSSL e
assina um *http.Request de saída usando o pacote crypto/ed25519 padrão do 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
}
Incluir o cabeçalho experimental Agent-Context
As plataformas multilocatárias (como provedores de navegadores hospedados ou ambientes de execução de agentes de nuvem)
costumam assinar solicitações de saída de vários agentes ou locatários de clientes
usando uma única chave de assinatura compartilhada. Por padrão, a WBA identifica apenas o domínio de assinatura (Signature-Agent), então todo o tráfego assinado por essa chave compartilha a mesma reputação.
Quando você inclui e assina o cabeçalho Agent-Context, o reCAPTCHA
pode avaliar a reputação separadamente em diferentes produtos, projetos, usuários ou
sessões que compartilham uma chave de assinatura comum na sua plataforma.
Formatar o cabeçalho Agent-Context
Formate Agent-Context como um
dicionário de campos estruturados RFC 8941,
em que cada chave é um token em letras minúsculas e cada valor é uma string ASCII entre aspas:
Agent-Context: product="ResearchBot", project="prj_89a0f", user="usr_29df3", session="sess_88b1"
Todos os campos são opcionais. Inclua apenas os campos que se aplicam à sua arquitetura:
| Campo | Descrição | Exemplo |
|---|---|---|
product |
Nome comercial ou do aplicativo do agente que faz a solicitação. | product="ResearchBot" |
project |
Identificador de conta, espaço de trabalho ou projeto de locatário pseudônimo na plataforma host. | project="prj_89a0f" |
user |
Identificador pseudônimo (como um UUID opaco ou hash com sal) para o cliente ou usuário final. Mantenha esse valor estável em todas as solicitações do mesmo usuário para criar reputação e nunca inclua PIIs reais. | user="usr_29df3" |
session |
Identificador temporário e opaco para uma única tarefa, execução de fluxo de trabalho ou sessão de navegação. | session="sess_88b1" |
Assinar o cabeçalho Agent-Context
O reCAPTCHA só usa Agent-Context para isolamento de reputação quando
você inclui "agent-context" na assinatura do WBA. Os cabeçalhos Agent-Context
não assinados são ignorados.
Para assinar o cabeçalho Agent-Context, siga estas etapas:
Defina o cabeçalho
Agent-Contextno*http.Requestde saída antes de chamarsignRequestEd25519:req.Header.Set("Agent-Context", `product="ResearchBot", project="prj_89a0f", user="usr_29df3", session="sess_88b1"`)Inclua
"agent-context"na lista de componentesSignature-Inpute na string de base de assinatura (processada automaticamente pelo exemplo em 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"
O exemplo a seguir mostra uma solicitação HTTP completa assinada com 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:
A seguir
Assinar as solicitações com o WBA comprova o controle criptográfico do domínio do seu agente, mas os sites também precisam de metadados sobre quem opera o agente e qual é a finalidade dele.
- Saiba como os proprietários de sites usam os indicadores de agente verificado:
- Monitore o tráfego do agente no painel Visão geral do agente.
- Configure políticas de desafio usando
a variável de condição
verified_bots.
- Leia as especificações do protocolo: