pruefeWebhookSignatur function

WebhookPruefung pruefeWebhookSignatur({
  1. required List<String> secrets,
  2. required String? signaturKopf,
  3. required List<int> rumpf,
  4. int? jetztSek,
  5. int toleranzSek = kWebhookToleranzSek,
})

Prueft die Signatur einer eingehenden Zustellung.

secrets ist das Secret aus createPartnerWebhook -- es verlaesst den Server genau einmal. Mehrere erlauben den Schluesselwechsel: es reicht, wenn einer passt.

rumpf ist der rohe Rumpf: die Bytes, wie sie ankamen. Nicht das Ergebnis von jsonDecode, und nichts, was danach wieder zusammengesetzt wurde.

jetztSek ist injizierbar -- kein Test dieses Pakets haengt an der Wanduhr.

Wirft nie: jede Ausnahme im Inneren wird zur Ablehnung.

Implementation

WebhookPruefung pruefeWebhookSignatur({
  required List<String> secrets,
  required String? signaturKopf,
  required List<int> rumpf,
  int? jetztSek,
  int toleranzSek = kWebhookToleranzSek,
}) {
  try {
    final gueltige = secrets.where((s) => s.isNotEmpty).toList(growable: false);
    if (gueltige.isEmpty) return const WebhookPruefung.nein(WebhookAblehnung.secretFehlt);

    if (signaturKopf == null || signaturKopf.trim().isEmpty) {
      return const WebhookPruefung.nein(WebhookAblehnung.kopfFehlt);
    }
    final kopf = leseSignaturKopf(signaturKopf);
    if (kopf == null) return const WebhookPruefung.nein(WebhookAblehnung.kopfUnbrauchbar);

    final jetzt = jetztSek ?? (DateTime.now().millisecondsSinceEpoch ~/ 1000);
    // In BEIDE Richtungen: eine vorgehende Uhr auf der Gegenseite darf ein
    // altes Ereignis nicht wieder gueltig machen.
    if ((jetzt - kopf.t).abs() > toleranzSek) {
      return const WebhookPruefung.nein(WebhookAblehnung.zeitfenster);
    }

    final nachricht = <int>[...utf8.encode('${kopf.t}.'), ...rumpf];
    for (final secret in gueltige) {
      final soll = Hmac(sha256, utf8.encode(secret)).convert(nachricht).bytes;
      for (final v1 in kopf.v1) {
        final ist = hexZuBytes(v1);
        if (ist != null && gleichZeitkonstant(ist, soll)) {
          return WebhookPruefung.ok(kopf.t);
        }
      }
    }
    return const WebhookPruefung.nein(WebhookAblehnung.signatur);
  } on Object {
    // Punkt 4 im Bibliothekskommentar: eine Ausnahme ist eine Ablehnung, nie
    // ein Ja.
    return const WebhookPruefung.nein(WebhookAblehnung.signatur);
  }
}