Skip to content
Snippets Groups Projects
Commit 639c2b92 authored by Marco Holz's avatar Marco Holz
Browse files

Vorgaben für Callbacks ausgebaut / aktualisiert

parent ea8fcd2d
No related branches found
No related tags found
1 merge request!33Sicherheitsvorgaben für Webhooks
---
title: Sicherheitsvorgaben für Callbacks
title: Verwendung von Callbacks
---
import ApiLink from '@site/src/components/ApiLink'
Der FIT-Connect Zustelldienst informiert sendende und empfangende Systeme (API-Clients) aktiv über [neue Einreichungen](../getting-started/receiving/query.mdx) oder [Statusupdates](../getting-started/sending/query-status.mdx).
Hierzu werden HTTP-Callbacks genutzt, die auch als [Webhooks](https://de.wikipedia.org/wiki/WebHooks) bezeichnet werden.
Webhooks ermöglichen es, API-Clients aktiv über diese Ereignisse zu informieren ohne dass eine regelmäßige Abfrage ([Polling](https://de.wikipedia.org/wiki/Polling_(Informatik))) nötig wäre.
......@@ -13,47 +12,130 @@ Technisch werden Webhooks als HTTP-POST-Request realisiert.
Im Folgenden verwenden wir die Begriffe *Callback* und *Webhook* synonym.
## Callback-URL
Hierzu stellen API-Clients einen HTTP-Endpunkt bereit, an den der Zustelldienst einen HTTP-POST-Request mit übermitteln kann.
API-Clients stellen zum Empfang von Callbacks einen HTTP-Endpunkt bereit, an den der Zustelldienst einen HTTP-POST-Request übermitteln kann.
API-Clients müssen auf eingehende Callbacks mit einer HTTP-Response mit Status Code `200 OK` antworten.
Die URL dieses HTTP-Endpunkts bezeichnen wir als Callback-URL (`callbackUrl`).
Sie wird von dem an FIT-Connect angebundenen System festgelegt.
Die URL dieses HTTP-Endpunkts (`callbackUrl`) wird von dem an FIT-Connect angebundenen System festgelegt.
Eine solche Callback-URL kann z.B. wie folgt aussehen:
```
https://fachverfahren.beispielstadt.example.org/callbacks/fit-connect
```
Die Callback-URL **MUSS** über HTTPS erreichbar sein.
Die Callback-URL **DARF NUR** über HTTPS erreichbar sein.
Der Zustelldienst wird Callbacks nur über eine via HTTPS verschlüsselte Verbindung auslösen.
## Konfiguration von Callbacks
TODO: Aushandlung des Callback Secret via API
Eine Konfiguration von Callbacks ist [über das Self-Service-Portal](../getting-started/receiving/destination.mdx) und über die API-Endpunkte <ApiLink to="/destinations/{destinationId}" withMethod="put" /> bzw. <ApiLink to="/destinations/{destinationId}" withMethod="patch" /> möglich.
Bei der Konfiguration werden die *Callback-URL* und ein *Callback-Secret* vom API-Client festgelegt.
Das *Callback-Secret* dient der Überprüfung der Echtheit (Authentizität) von eingehenden Callbacks (siehe nächster Abschnitt).
Das angegebene *Callback-Secret* kann über die API nur geschrieben und aktualisiert, aber nicht gelesen werden und **DARF NICHT** an Dritte weitergegeben werden.
Ein sicheres *Callback-Secret* kann über die folgenden Aufrufe erzeugt werden:
- Python: `python -c 'import secrets; print(secrets.token_urlsafe(32))'`
- Ruby: `ruby -rsecurerandom -e 'puts SecureRandom.hex(32)'`
- pwgen: `pwgen --secure 64 1`
Die Einrichtung von Callbacks im Self-Service-Portal wird im Artikel `TODO: Link einfügen` näher beschrieben.
### Konfiguration von Callbacks für Zustellpunkte
Über die API können Callbacks für Zustellpunkte wie folgt konfiguriert werden:
<Tabs
defaultValue="curl"
values={[
{label: 'curl', value: 'curl',},
{label: 'Java', value: 'java',},
{label: 'JavaScript', value: 'javascript',},
]}>
<TabItem value="curl" label="curl">
```shell
$ SERVICE_URL=...
$ JWT_TOKEN=...
$ DESTINATION_ID=...
$ CALLBACK_URL=https://fachverfahren.beispielstadt.example.org/callbacks/fit-connect
$ CALLBACK_SECRET=insecure_unsafe_qHScgrg_kP-R31jHUwp3GkVkGJolvBchz65b74Lzue0
$ curl -X PATCH \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $JWT_TOKEN" \
--data "{\"callback\": {\"url\": \"$CALLBACK_URL\", \"secret\": \"$CALLBACK_SECRET\"}}" \
"$SERVICE_URL/destinations/$DESTINATION_ID"
```
</TabItem>
<TabItem value="java" label="Java">
:::caution TODO
TODO: Codebeispiel ergänzen
:::
</TabItem>
<TabItem value="JavaScript" label="JavaScript">
:::caution TODO
TODO: Codebeispiel ergänzen
:::
</TabItem>
</Tabs>
## Prüfung von Callbacks
API-Clients, die Callbacks empfangen, **MÜSSEN** zwingend sicherstellen, dass ausgelöste Callbacks von einem vertrauenswürdigen Zustelldienst stammen.
Hierzu enthalten Callbacks einen Message Authentication Code (HMAC) gemäß [RFC 2104](https://datatracker.ietf.org/doc/html/rfc2104) auf Basis eines geheimen symmetrischen Schlüssels.
Der geheime Schlüssel, im Folgenden *Callback Secret* genannt, wird bei der Konfiguration eines Callbacks festgelegt **DARF NICHT** an Dritte weitergegeben werden.
Hierzu enthalten Callbacks einen Message Authentication Code (HMAC) gemäß [RFC 2104](https://datatracker.ietf.org/doc/html/rfc2104) auf Basis des angegebenen *Callback-Secrets*.
Ein Message Athentication Code kann als „symmetrische Signatur“ verstanden werden und ermöglicht die Prüfung der Herkunft und Integrität eines eingehende Callbacks.
Der Message Authentication Code wird im HTTP-Header `callback-authentication` übertragen.
Um [Replay-Angriffe](https://de.wikipedia.org/wiki/Replay-Angriff) zu vermeiden, enthält der Message Authentication Code einen aktuellen Timestamp.
Dieser Timestamp wird im HTTP-Header `callback-timestamp` übertragen.
Durch die Prüfung des Message Authentication Code können API-Clients die Herkunft und Integrität eines Callbacks verifizieren.
Bei der Prüfung der Echtheit des ausgelösten Callbacks **MÜSSEN** API-Clients prüfen, dass der angegebene Timestamp nicht älter als **5 Minuten** ist.
Das folgende Beispiel zeigt die Verwendung der HTTP-Header `callback-authentication` und `callback-timestamp`:
```http
POST /callbacks/fit-connect
callback-authentication: HMAC(key={shared-secret}, message={timestamp}.{http-body})
callback-timestamp: 1631283222821
callback-authentication: f4eig0ht6hdlsfz6DVqGjXi1j3RAombIQ7vjG1M2TFZx1fGurzg1nOEh00lPfLEulhio1RyTOav1e1aMi69SRg==
callback-timestamp: 1672527599
{"submissionIds":["..."]}
{"type":"https://schema.fitko.de/fit-connect/callbacks/new-submissions","submissionIds":["f39ab143-d91a-474a-b69f-b00f1a1873c2"]}
```
Der HMAC wird gebildet aus dem im HTTP-Header `callback-timestamp` übertragenen Zeitstempel und dem im HTTP-Body übertragenen Payload, getrennt durch das Zeichen `.` (Punkt).
Der HMAC wird gebildet aus dem im HTTP-Header `callback-timestamp` übertragenen Zeitstempel und dem im HTTP-Body übertragenen Payload, getrennt durch das Zeichen `.` (Punkt), jeweils UTF-8-kodiert.
Als Hash-Algorithmus wird SHA-512 verwendet.
```
callback-authentication = BASE64(HMAC(key={callback-secret}, message={timestamp}.{http-body}))
```
Um den Message Authentication Code (HMAC) zu verifizieren, bildet der API-Client mit Hilfe des *Callback Secret* den HMAC nach und vergleicht diesen mit dem im HTTP-Header `callback-authentication` übertragenen HMAC.
Dabei sind die folgenden Implementierungshinweise zwingend zu beachten:
- Bei der Erzeugung des HMAC *MUSS* der Hash-Algorithmus `SHA-512` verwendet werden.
- Es *MUSS* geprüft werden, dass der angegebene Zeitstempel nicht älter als **5 Minuten** ist.
- Beim Vergleich des übertragenen HMAC und des vom API-Client gebildeteten HMAC *MUSS* ein zeitlich konstanter Zeichenfolgenvergleich (*constant time string comparison*) verwendet werden. In Python kann dies über die Verwendung der Methode `hmac.compare_digest` erreicht werden.
Bei der Prüfung **MÜSSEN** die folgenden Implementierungshinweise zwingend zu beachten:
- Das Callback Secret **MUSS** in API-Clients konfigurierbar sein und **DARF NICHT** fest im Quellcode eines API-Clients einprogrammiert sein.
- Callbacks mit ungültigem Message Authentication Code **MÜSSEN** von API-Clients irgnoriert werden.
- Dies kann beispielsweise durch die Konfiguration des Callback Secret in einer Konfigurationsdatei oder über eine Umgebungsvariable (`$ export CALLBACK_SECRET=your_secret`) erreicht werden.
- Bei der Erzeugung des HMAC **MUSS** der Hash-Algorithmus `SHA-512` verwendet werden.
- Es **MUSS** geprüft werden, dass der angegebene Zeitstempel nicht älter als **5 Minuten** ist.
- Beim Vergleich des übertragenen HMAC und des vom API-Client gebildeteten HMAC **MUSS** ein zeitlich konstanter Zeichenfolgenvergleich (*constant time string comparison*) verwendet werden.
- In Python kann dies über die Verwendung der Methode [`hmac.compare_digest`](https://docs.python.org/2/library/hmac.html#hmac.compare_digest) erreicht werden.
- In Ruby kann dies über die Verwendung der Methode [`secure_compare`](https://rubydoc.info/github/rack/rack/master/Rack/Utils:secure_compare) erreicht werden.
- Callbacks mit ungültigem Message Authentication Code **MÜSSEN** von API-Clients ignoriert/verworfen werden.
Dabei ist zunächst der Zeitstempel (`callback-timestamp`-Header) und anschließend der HMAC (`callback-authentication`-Header) zu prüfen:
```python
# 1. Timestamp überprüfen
current_time_epoch = int(time.time())
seconds_five_minutes = 60 * 5
if current_time_epoch - request['headers']['callback-timestamp'] > seconds_five_minutes:
print('Error: timestamp too old')
sys.exit(1)
else:
print('timestamp ok')
# 2. HMAC berechnen
payload = str(request['headers']['callback-timestamp']) + '.' + request['body']
expected_hmac = hmac.digest(CALLBACK_SECRET.encode("utf-8"), payload.encode("utf-8"), digest=sha512)
expected_hmac_base64 = base64.b64encode(expected_hmac).decode()
print('hmac', expected_hmac_base64)
# 3. Berechneten HMAC mit HMAC aus HTTP-Header vergleichen
if not hmac.compare_digest(request['headers']['callback-authentication'], expected_hmac_base64):
print('Error: invalid hmac')
sys.exit(2)
else:
print('hmac ok')
```
Das dargestellte Script findet sich auch zur freien Verwendung im [FIT-Connect-Tools-Repository](https://git.fitko.de/fit-connect/fit-connect-tools/-/blob/main/validate-callback.py).
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment