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' 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). 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. 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. 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. ...@@ -13,47 +12,130 @@ Technisch werden Webhooks als HTTP-POST-Request realisiert.
Im Folgenden verwenden wir die Begriffe *Callback* und *Webhook* synonym. Im Folgenden verwenden wir die Begriffe *Callback* und *Webhook* synonym.
## Callback-URL ## 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: Eine solche Callback-URL kann z.B. wie folgt aussehen:
``` ```
https://fachverfahren.beispielstadt.example.org/callbacks/fit-connect 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. Der Zustelldienst wird Callbacks nur über eine via HTTPS verschlüsselte Verbindung auslösen.
## Konfiguration von Callbacks ## 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 ## Prüfung von Callbacks
API-Clients, die Callbacks empfangen, **MÜSSEN** zwingend sicherstellen, dass ausgelöste Callbacks von einem vertrauenswürdigen Zustelldienst stammen. 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. Hierzu enthalten Callbacks einen Message Authentication Code (HMAC) gemäß [RFC 2104](https://datatracker.ietf.org/doc/html/rfc2104) auf Basis des angegebenen *Callback-Secrets*.
Der geheime Schlüssel, im Folgenden *Callback Secret* genannt, wird bei der Konfiguration eines Callbacks festgelegt **DARF NICHT** an Dritte weitergegeben werden. 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. 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. 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. Dieser Timestamp wird im HTTP-Header `callback-timestamp` übertragen.
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.
Durch die Prüfung des Message Authentication Code können API-Clients die Herkunft und Integrität eines Callbacks verifizieren.
Das folgende Beispiel zeigt die Verwendung der HTTP-Header `callback-authentication` und `callback-timestamp`: Das folgende Beispiel zeigt die Verwendung der HTTP-Header `callback-authentication` und `callback-timestamp`:
```http ```http
POST /callbacks/fit-connect POST /callbacks/fit-connect
callback-authentication: HMAC(key={shared-secret}, message={timestamp}.{http-body}) callback-authentication: f4eig0ht6hdlsfz6DVqGjXi1j3RAombIQ7vjG1M2TFZx1fGurzg1nOEh00lPfLEulhio1RyTOav1e1aMi69SRg==
callback-timestamp: 1631283222821 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. 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 Prüfung **MÜSSEN** 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.
- Das Callback Secret **MUSS** in API-Clients konfigurierbar sein und **DARF NICHT** fest im Quellcode eines API-Clients einprogrammiert sein. - 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