Aller au contenu

Chaos Scripting — Guide d'implémentation HMAC

NeoLoad (VuGen C), NeoLoad JavaScript, JMeter


1. Ce qu'il faut calculer

Niveau 3 — Expert

Clé       = "perfshop-chaos-expert-secret-2025"   ← statique, toujours la même
Payload   = sessionToken + ":" + csrfToken + ":" + stepToken
Signature = HMAC-SHA256(payload, clé)  →  Base64 URL-safe sans padding

Niveau 4 — Maestro

CléDerivée = HMAC-SHA256(sessionToken, "perfshop-chaos-expert-secret-2025")
Payload    = sessionToken + ":" + csrfToken + ":" + stepToken
Signature  = HMAC-SHA256(payload, cléDerivée)  →  Base64 URL-safe sans padding

Base64 URL-safe = remplacer + par -, / par _, supprimer les = de fin.

Séquence des étapes

Étape Endpoint Step envoyé Step après rotation
1 POST /api/checkout/address step1 step2
2 POST /api/checkout/shipping step2 step3
3 POST /api/checkout/payment step3 step4
4 POST /api/orders step4 (fin)

2. JMeter — JSR223 (Groovy, aucune dépendance)

Groovy est natif dans JMeter 3.1+. Pas d'import, pas de JAR supplémentaire.

Fonction HMAC réutilisable (à mettre dans un JSR223 Sampler ou PreProcessor)

import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import java.util.Base64

// ── Fonction signature ──────────────────────────────────────────────
def hmacSHA256Base64Url(String payload, String key) {
    Mac mac = Mac.getInstance("HmacSHA256")
    mac.init(new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256"))
    byte[] raw = mac.doFinal(payload.getBytes("UTF-8"))
    return Base64.getUrlEncoder().withoutPadding().encodeToString(raw)
}

// ── Constante ───────────────────────────────────────────────────────
def HMAC_SECRET = "perfshop-chaos-expert-secret-2025"

// ── Lire les tokens extraits par les extracteurs JSON précédents ────
def session = vars.get("sessionToken")   // extrait du login
def action  = vars.get("actionToken")   // extrait du login
def csrf    = vars.get("csrfToken")     // extrait du login (puis de chaque réponse)
def step    = vars.get("stepToken")     // extrait du login (puis de chaque réponse)

// ── Calcul signature ────────────────────────────────────────────────
// Niveau 3 Expert :
String sigKey = HMAC_SECRET

// Niveau 4 Maestro (décommenter) :
// String sigKey = hmacSHA256Base64Url(session, HMAC_SECRET)

String payload   = session + ":" + csrf + ":" + step
String signature = hmacSHA256Base64Url(payload, sigKey)

// ── Stocker dans des variables JMeter ───────────────────────────────
vars.put("signature", signature)

log.info("Step=${step} CSRF=${csrf.take(8)}... Sig=${signature.take(8)}...")

Plan JMeter complet — niveau 3 Expert

Étape 1 — Login

Requête : POST /api/auth/login

Extracteurs JSON sur la réponse (Header Extractor) :

x-session-token  → sessionToken
x-action-token   → actionToken
x-csrf-token     → csrfToken
x-step-token     → stepToken

JSR223 PreProcessor avant chaque étape checkout :

import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import java.util.Base64

def hmac(String payload, String key) {
    Mac mac = Mac.getInstance("HmacSHA256")
    mac.init(new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256"))
    return Base64.getUrlEncoder().withoutPadding()
        .encodeToString(mac.doFinal(payload.getBytes("UTF-8")))
}

def HMAC_SECRET = "perfshop-chaos-expert-secret-2025"
def session     = vars.get("sessionToken")
def csrf        = vars.get("csrfToken")
def step        = vars.get("stepToken")

// Niveau 4 : sigKey = hmac(session, HMAC_SECRET)
def sigKey      = HMAC_SECRET

vars.put("signature", hmac(session + ":" + csrf + ":" + step, sigKey))

Headers sur chaque étape checkout :

X-Session-Token : ${sessionToken}
X-Action-Token  : ${actionToken}
X-CSRF-Token    : ${csrfToken}
X-Step-Token    : ${stepToken}
X-Signature     : ${signature}
X-Request-ID    : ${__UUID()}

Extracteurs JSON sur chaque réponse checkout (mettre à jour csrf, step, sig) :

Header : x-csrf-token  → csrfToken
Header : x-step-token  → stepToken
Header : x-signature   → signature   ← optionnel (recalculé de toute façon)


3. NeoLoad — Script JavaScript (moteur V8 intégré)

NeoLoad supporte JavaScript natif dans les "Advanced Actions". Le moteur V8 n'a pas de crypto natif, mais NeoLoad expose java.* directement.

Fichier : hmac_utils.js — à inclure une fois au début du scénario

// ── HMAC-SHA256 Base64 URL-safe via Java (disponible dans NeoLoad V8) ──
function hmacSHA256(payload, key) {
    var keyBytes     = new java.lang.String(key).getBytes("UTF-8");
    var payloadBytes = new java.lang.String(payload).getBytes("UTF-8");
    var mac = javax.crypto.Mac.getInstance("HmacSHA256");
    mac.init(new javax.crypto.spec.SecretKeySpec(keyBytes, "HmacSHA256"));
    var raw = mac.doFinal(payloadBytes);
    var b64 = java.util.Base64.getUrlEncoder().withoutPadding().encodeToString(raw);
    return String(b64); // convertir java.lang.String → JS String
}

// ── Constante ──
var HMAC_SECRET = "perfshop-chaos-expert-secret-2025";

Action "Checkout Address" (level 3)

// Récupérer les tokens depuis les variables NeoLoad
var session = context.variableManager.getValue("sessionToken");
var action  = context.variableManager.getValue("actionToken");
var csrf    = context.variableManager.getValue("csrfToken");
var step    = context.variableManager.getValue("stepToken");

// Niveau 3 Expert
var sigKey    = HMAC_SECRET;

// Niveau 4 Maestro (décommenter) :
// var sigKey = hmacSHA256(session, HMAC_SECRET);

var payload   = session + ":" + csrf + ":" + step;
var signature = hmacSHA256(payload, sigKey);

// Injecter dans les headers de la requête suivante
context.variableManager.setValue("signature", signature);

// Log pour debug
context.logger.info("Étape: " + step + " | Sig: " + signature.substring(0, 8) + "...");

Variables NeoLoad à configurer

Dans "Variable Extractors" → "From Header" sur chaque réponse :

Login response headers  → sessionToken, actionToken, csrfToken, stepToken
Address response headers → csrfToken, stepToken   (écrasent les précédentes)
Shipping response headers → csrfToken, stepToken
Payment response headers  → csrfToken, stepToken


4. VuGen — C (LoadRunner / NeoLoad VuGen)

VuGen C supporte lr_hmac_sha256() depuis LR 12.55. Si tu es sur une version antérieure ou NeoLoad VuGen, utilise l'approche web_custom_request + script externe, ou la méthode OpenSSL ci-dessous.

Option A — lr_hmac_sha256() natif (LR 12.55+)

#include "lrun.h"

/* Calcule HMAC-SHA256 et encode en Base64 URL-safe */
static char* compute_signature(const char* session, const char* csrf, const char* step) {
    static char payload[1024];
    static char sig_b64[256];
    static char sig_urlsafe[256];
    int  i;
    char raw_sig[32];
    int  raw_len = 32;

    /* Payload */
    sprintf(payload, "%s:%s:%s", session, csrf, step);

    /* HMAC-SHA256 */
    lr_hmac_sha256(
        (unsigned char*)"perfshop-chaos-expert-secret-2025", 36,
        (unsigned char*)payload, strlen(payload),
        (unsigned char*)raw_sig, &raw_len
    );

    /* Base64 standard */
    lr_base64_encode((unsigned char*)raw_sig, raw_len, sig_b64);

    /* Convertir en URL-safe : + → -, / → _, supprimer = */
    for (i = 0; sig_b64[i]; i++) {
        if      (sig_b64[i] == '+') sig_urlsafe[i] = '-';
        else if (sig_b64[i] == '/') sig_urlsafe[i] = '_';
        else if (sig_b64[i] == '=') { sig_urlsafe[i] = '\0'; break; }
        else                         sig_urlsafe[i] = sig_b64[i];
    }
    sig_urlsafe[i] = '\0';

    lr_save_string(sig_urlsafe, "signature");
    return sig_urlsafe;
}

/* Dans ton Action() : */
Action() {
    char* session = lr_eval_string("{sessionToken}");
    char* csrf    = lr_eval_string("{csrfToken}");
    char* step    = lr_eval_string("{stepToken}");

    compute_signature(session, csrf, step);

    web_custom_request("checkout_address",
        "URL=https://perfshop-api.perfshop.io/api/checkout/address",
        "Method=POST",
        "TargetFrame=",
        "Resource=0",
        "RecContentType=application/json",
        "Referer=",
        "Snapshot=",
        "Mode=HTTP",
        "EXTRARES",
        "Header=X-Session-Token: {sessionToken}",
        "Header=X-Action-Token:  {actionToken}",
        "Header=X-CSRF-Token:    {csrfToken}",
        "Header=X-Step-Token:    {stepToken}",
        "Header=X-Signature:     {signature}",
        "Header=X-Request-ID:    {NewUUID}",
        "Body={\"street\":\"12 rue de la Paix\",\"city\":\"Paris\",\"zip\":\"75001\"}",
        LAST);

    /* Extraire les tokens rotatifs de la réponse */
    web_reg_save_param_ex("ParamName=csrfToken",
        "LB=x-csrf-token: ", "RB=\r\n",
        SEARCH_FILTERS, "Scope=Headers", LAST);
    web_reg_save_param_ex("ParamName=stepToken",
        "LB=x-step-token: ", "RB=\r\n",
        SEARCH_FILTERS, "Scope=Headers", LAST);

    return 0;
}

Option B — Script PowerShell appelé par VuGen (sans dépendance LR)

Si lr_hmac_sha256 n'est pas disponible, déléguer à PowerShell via system() :

/* Génère la signature via PowerShell et lit le résultat */
static void compute_signature_ps(const char* session, const char* csrf, const char* step) {
    char cmd[2048];
    char ps_script[1024];

    sprintf(ps_script,
        "$k=[System.Text.Encoding]::UTF8.GetBytes('perfshop-chaos-expert-secret-2025');"
        "$p=[System.Text.Encoding]::UTF8.GetBytes('%s:%s:%s');"
        "$m=New-Object System.Security.Cryptography.HMACSHA256;"
        "$m.Key=$k;"
        "$r=$m.ComputeHash($p);"
        "$b=[Convert]::ToBase64String($r);"
        "$u=$b.Replace('+','-').Replace('/','_').TrimEnd('=');"
        "Set-Content -Path C:\\Temp\\sig.txt -Value $u",
        session, csrf, step);

    sprintf(cmd, "powershell -Command \"%s\"", ps_script);
    system(cmd);

    lr_load_string("C:\\Temp\\sig.txt", "signature");
}

5. Niveau 4 Maestro — différence par rapport à Expert

JMeter (Groovy)

// Remplacer une ligne dans le PreProcessor :
// AVANT (Expert) :
def sigKey = HMAC_SECRET
// APRÈS (Maestro) :
def sigKey = hmac(session, HMAC_SECRET)   // dérivation de la clé

NeoLoad JS

// AVANT (Expert) :
var sigKey = HMAC_SECRET;
// APRÈS (Maestro) :
var sigKey = hmacSHA256(session, HMAC_SECRET);

VuGen C

/* Dériver la clé d'abord, puis recalculer */
static char derived_key_b64[256];

void derive_hmac_key(const char* session_token) {
    char raw_key[32];
    int  raw_len = 32;
    lr_hmac_sha256(
        (unsigned char*)"perfshop-chaos-expert-secret-2025", 36,
        (unsigned char*)session_token, strlen(session_token),
        (unsigned char*)raw_key, &raw_len
    );
    /* Stocker la clé brute pour la réutiliser */
    memcpy(g_derived_key, raw_key, 32);  /* variable globale */
    g_derived_key_len = 32;
}

/* Puis dans compute_signature : utiliser g_derived_key au lieu de HMAC_SECRET */

6. Récapitulatif — checklist par requête

Login

  • [x] POST /api/auth/login avec email + password
  • [x] Extraire des headers de réponse : X-Session-Token, X-Action-Token, X-CSRF-Token, X-Step-Token
  • [x] Maestro uniquement : dériver sigKey = HMAC(sessionToken, SECRET)

Chaque étape checkout (address / shipping / payment / orders)

  • [x] Calculer signature = HMAC(session+":"+csrf+":"+step, sigKey)
  • [x] Envoyer les 6 headers : Session, Action, CSRF, Step, Signature, RequestID
  • [x] Extraire X-CSRF-Token et X-Step-Token de la réponse (ils ont changé)
  • [x] Recalculer la signature avant la prochaine requête

Codes d'erreur à gérer

Code Signification Action corrective
E-TKN-99 Pas de bundle → re-login Rejouer depuis le login
E-CSRF-01 CSRF invalide Vérifier l'extraction de la réponse précédente
E-STEP-04 Mauvais step Vérifier la séquence step1→2→3→4
E-SIG-07 Signature invalide (Expert) Vérifier payload et clé HMAC
E-HMAC-03 Signature invalide (Maestro) Vérifier la dérivation de clé