Signature et vérification des demandes
La signature de la requête prouve l'authenticité de l'expéditeur et l'intégrité du contenu. Contrairement à TLS (qui protège le canal), la signature appliquée rend chaque message vérifiable et résistant au proxy, au cache et à la livraison différée.
Objectifs :1. Authenticité (qui a envoyé) et intégrité (n'a pas changé).
2. Non-répétition (protection contre les répliques).
3. Déconnexion des transports (fonctionne sur HTTP, files d'attente, webhooks).
4. Vérifiable (vérification reproductible des mois plus tard).
1) Modèle de menace (minimum)
Échange de corps/titres sur le chemin à suivre.
Replay (répétition d'une requête légitime).
Downgrade/strip des titres de signature.
Vol de secrets d'intégration.
Horloge non synchronisée (clock skew) et files d'attente longues.
2) Sélection de la primitive
HMAC (symétrie) : simple et rapide, la clé est stockée des deux côtés. Convient aux webhooks B2B et API internes.
RSA/ECDSA (asymétrie) : clé privée de l'expéditeur, publique du destinataire. Convient aux intégrations ouvertes et quand il est important de ne pas partager de secret.
mTLS : authentification mutuelle au niveau du transport ; souvent combiné avec la signature NMAS/corps.
JWT/JWS : pratique pour les tokens bearer et les marques autosuffisantes ; pour la signature du corps, il est préférable d'utiliser la canonisation + JWS Detached/HTTP Message Signatures.
HTTP Message Signatures (signature des parties sélectionnées de la requête) : approche moderne pour REST.
Recommandation : pour les webhooks - HMAC + timestamp + nonce + canonisation du corps ; pour l'API publique, HTTP Message Signatures ou JWS ; à haut risque - ajoutez mTLS.
3) Canonisation (exactement ce que nous signons)
Vous devez signer une chaîne déterministe restaurée de la même manière par les deux parties.
Composition de référence :
method \n path_with_query_normalized \n content-type \n digest: SHA-256=BASE64(SHA256(body)) \n x-ts: <unix iso> \n x-nonce: <uuid> \n host \n x-tenant: <tenant_id> \n
Ligne finale :
canonical = join("\n", fields)
signature = HMAC(secret, canonical) # или ECDSA_sign(private_key, canonical)
Règles :
- Normalisez le chemin et l'ordre des paramètres query.
- Espaces/unicode/registre : cochez (par exemple, lower-case en-têtes, trim).
- Grands corps - hachez (Digest) plutôt que d'inclure « tel quel ».
4) Format des titres
Exemple pour HMAC :
X-Signature-Alg: hmac-sha256
X-Signature: v1=hex(hmac),ts=1730379005,nonce=550e8400-e29b-41d4-a716-446655440000,kid=prov_42
Digest: SHA-256=BASE64(SHA256(body))
X-Tenant: brand_eu
Exemple pour l'asymétrie (ECDSA P-256) :
Signature: keyId="prov_42", alg="ecdsa-p256-sha256",
ts="2025-10-31T12:30:05Z", nonce="550e...", headers="(request-target) host digest x-tenant",
sig="BASE64(raw_signature)"
Où 'kid '/' keyId' permet de sélectionner une clé dans le Registre (voir rotation).
5) Vérification côté réception
Pseudo-code :python def verify(request):
1) Basic assert abs (now () - request. ts) <= ALLOWED_SKEW # напр., 300 с assert not replayed(request. nonce, window = TTL) # store nonce/ts in KV
2) Restore canonical canonical = build_canonical (
method=request. method,
path=normalize_path(request. path, request. query),
content_type=request. headers["content-type"],
digest=hash_body(request. body),
ts=request. ts,
nonce=request. nonce,
host=request. headers["host"],
tenant=request. headers. get("x-tenant")
)
3) Get the key key = key_registry. get(request. kid) # secret (HMAC) или public key (ECDSA)
4) Verify if request signature. alg. startswith("hmac"):
ok = hmac_compare(key. secret, canonical, request. signature)
else:
ok = asym_verify(key. public, canonical, request. signature)
5) Solution if not ok: return 401, "SIGNATURE_INVALID"
return 200, "OK"
Comparatif constant-time HMAC, stockage 'nonce '/' (ts, event_id)' en KV rapides (TTL ≥ fenêtre de livraison).
6) Anti-relais et fenêtres
Timestamp + Nonce : refuser les demandes plus anciennes que '± Δ' (par exemple, 5 min) et les répétitions de nonce dans cette fenêtre.
Pour les webhooks : utilisez la table stable 'event _ id' et inbox est plus fiable que nonce seulement.
La réadmission doit utiliser les mêmes ts/nonce/event_id plutôt que d'en générer de nouvelles.
7) Multi-tenants et régions
Stockez les clés per tenant/region : 'kid = <tenant> : <region> : <key _ id>'.
Séparez les pools de secrets et les limites ; respectez la résidence de données.
Dans les rubriques/canonicalisation, indiquez « X-Tenant » et la région fait partie du contexte vérifié.
8) Gestion des clés et rotation
Registre de clés (KMS/Vault) : 'kid', type, algorithme, état ('active', 'deprecating', 'retired'), 'valid _ from/valid _ to'.
Double-secret : gardez simultanément la clé courante et la clé suivante (le récepteur accepte les deux).
Rotation programmée et par événement (compromission).
Pinning key (si possible) et limitation de l'accès aux matériaux clés.
Logs d'accès aux clés et d'action avec elles.
9) Combinaison avec mTLS et OAuth
mTLS vérifie le canal et « qui vous êtes » au niveau du certificat.
La signature protège le message (utile via proxy/cashi/files d'attente).
OAuth/JWT complète l'authentification/autorisation, mais ne garantit pas en soi l'intégrité du corps (à moins qu'il ne soit signé dans la canonisation).
Meilleures pratiques : mTLS + signature du corps (Digest) + HMAC/ECDSA + court 't' -interval.
10) Erreurs et codes de réponse
'401 SIGNATURE_INVALID' est une signature/algorithme incorrecte.
'401 KEY_REVOKED' -' kid 'est invalide/périmé.
'400 TIMESTAMP_OUT_OF_RANGE' - horloge/fenêtre.
'409 NONCE_REPLAYED' - Répétition détectée.
"400 DIGEST_MISMATCH' - le corps a été modifié.
'415 UNSUPPORTED_ALGORITHM' est un 'alg' non résolu.
'429 TOO_MANY_ATTEMPTS' - Trottling par clé/tenant.
Donnez un coup de pied à la raison exacte à machinno-lu ' error_code '; ne pas rendre les secrets/canonisation « tels quels ».
11) Observation et audit
Métriques :- `verify_p95_ms`, `verify_error_rate`, `digest_mismatch_rate`, `replay_blocked_rate`, `alg_usage{hmac,ecdsa}`, `clock_skew_ms`.
- Logs (structurels) : 'kid', 'alg', 'tenant', 'region', 't',' nonce ',' digest _ hash ',' decision ',' reason '.
- Tracing : attributs 'signature. kid`, `signature. alg`, `signature. ts_skew`.
- Audit : journal immuable des rotations, de l'utilisation des clés et des indicateurs de tolérance.
12) Performance
Hachez le corps en streaming (ne le gardez pas en mémoire).
Mettez en cache les clés publiques par 'kid' avec un court TTL et un handicap par événement.
Sur edge/gateway, faites des vérifications préliminaires (ts/nonce/format).
HMAC plus rapide que l'ECDSA ; ECDSA est plus pratique pour les intégrations externes et les clés « non séparables ».
13) Tests
Jeux de fictions : les mêmes requêtes → la même canonisation/signature ; les espaces « sales »/ordre query/titres sont → résistants.
Negative : incorrect 'kid/alg', corps modifié/hôte, répétition nonce, obsolète ts, clock skew.
Property-based : toute requête équivalente donne une chaîne canonique.
Interop : vérifications en langage croisé (Go/Java/Node/Python).
Chaos : retards, retraits, changement de clé « à la volée ».
14) Pleybooks (runbooks)
1. Éclair 'SIGNATURE _ INVALID'
Vérifiez la rotation des clés, le clock dissynchrone, les modifications de canonisation de l'expéditeur.
Activer temporairement 'dual-accept' pour l'ancien 'kid', avertir le partenaire.
2. Croissance 'REPLAYED'
Augmentez le stockage TTL nonce, vérifiez les retreiners de l'expéditeur, vérifiez le clock skew.
Bloquer l'IP/ASN abusif sur edge.
3. 'DIGEST _ MISMATCH'massivement
Vérifier le proxy/compression/écrasement des titres ; enregistrer la version de la canonicalisation.
Désactiver les intermédiaires qui perturbent le corps/en-têtes.
4. Compromis de clé
Immédiatement revoke 'kid', traduire en 'next _ kid', régénérer tous les secrets/tokens, vérifier l'accès.
15) Erreurs typiques
Signer la « partie du corps » ou JSON sans fixer l'ordre → la vulnérabilité à la permutation des champs.
L'absence de 'Digest' → proxy peut changer le corps de manière invisible.
Une longue fenêtre de 't'sans nonce est → ouverte par une réplique.
Gardez les secrets dans des variables d'environnement sans KMS/Vault.
Comparer une signature non constante-time.
Ignorer 'host'/' path' dans la canonisation → l'attaque de transfert.
Mélanger « kid » de différents tenants et régions.
16) Chèque-liste avant la vente
- Le format de canonisation est défini (method, path + query, content-type, Digest, ts, nonce, host, tenant).
- Implémenté par HMAC/ECDSA avec 'kid', registre de clés et dual-secret.
- Activé anti-repli (nonce + ts) et stockage de inbox/event_id pour les webhooks.
- Les codes d'erreur/politiques de rétroaction et de trottling per tenant/key sont configurés.
- Observabilité : métriques verify, logs, traçage, alertes sur les éclats.
- La rotation des clés est automatisée ; l'audit et les droits d'accès sont limités.
- Kits de test de canonisation et de compatibilité interlingue.
- Documentation à l'intention des intégrateurs avec exemple en 3-4 langues et fiches.
- mTLS inclus pour les intégrations sensibles ; JWT est utilisé uniquement comme complément, pas comme remplacement de la signature du corps.
Conclusion
La signature et la vérification des requêtes ne sont pas un « titre unique », mais une discipline : canonisation claire, fenêtres temporelles courtes, anti-repli, rotation des clés et observabilité. Construisez une norme unique pour toutes les intégrations (API et webhooks), utilisez 'kid '/KMS, acceptez deux clés lors de la rotation, et vos contours deviennent résistants aux substitutions, prévisibles et faciles à vérifier.