verifyInventoryWebhookSignature function
Prueft die Signatur einer Zustellung. true nur, wenn Kopf, Zeitfenster
und HMAC stimmen; jede schlechte Eingabe und jede Ausnahme ist false,
nie ein Wurf.
secret: das Secret auscreateWebhook/rotateWebhookSecretalsString, oder eine Liste davon fuer den eigenen Wechsel (es reicht, wenn eines passt).header: der Wert vonX-Kasseneck-Signature, unveraendert. Mehrerev1=-Anteile sind erlaubt.rawBody: der rohe Rumpf alsStringoder als Bytes (List<int>, etwaUint8List).now: jetzt; Vorgabe ist die Systemuhr.
Implementation
bool verifyInventoryWebhookSignature(
Object? secret,
String? header,
Object? rawBody, {
int toleranceSec = webhookToleranceSec,
DateTime? now,
}) {
try {
final secrets = <String>[
if (secret is String) secret else if (secret is Iterable) ...secret.whereType<String>(),
].where((s) => s.isNotEmpty).toList();
if (secrets.isEmpty || header == null || header.trim().isEmpty) return false;
final kopf = _kopfLesen(header);
if (kopf == null) return false;
final List<int> rumpf;
if (rawBody is String) {
rumpf = utf8.encode(rawBody);
} else if (rawBody is List<int>) {
rumpf = rawBody;
} else {
return false;
}
// Abgerundet wie Math.floor(ms / 1000) im JS-Zwilling.
final jetzt = ((now ?? DateTime.now()).millisecondsSinceEpoch / 1000).floor();
// In BEIDE Richtungen: eine vorgehende Uhr auf der Gegenseite darf ein
// altes Ereignis nicht wieder gueltig machen.
if ((jetzt - kopf.t).abs() > toleranceSec) return false;
final nachricht = [...utf8.encode('${kopf.t}.'), ...rumpf];
for (final s in secrets) {
final soll = Hmac(sha256, utf8.encode(s)).convert(nachricht).bytes;
for (final v1 in kopf.v1) {
final ist = _hexZuBytes(v1);
if (ist != null && _gleichZeitkonstant(ist, soll)) return true;
}
}
return false;
} on Object {
// Punkt 4 oben: eine Ausnahme ist eine Ablehnung, nie ein Ja (auch ein
// Byte ausserhalb 0–255 in rawBody).
return false;
}
}