Skip to content
Snippets Groups Projects

docs(event-log): Start documenting the event log and interaction with signed JWTs

Merged David Schwarzmann requested to merge feat/security-event-tokens into main
Files
13
+ 227
0
---
title: Event Log
---
Im Ereignisprotokoll (Event Log) werden relevante Ereignisse (events) aufgezeichnet. Beim Abruf des Ereignisprotokolls
liefert die API ein Array von JSON Web Token (JWT) gemäß [RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519). Der
JWT ist einen Security-Event-Token (SET) gemäß [RFC 8417](https://datatracker.ietf.org/doc/html/rfc8417). Wie mit den
Security-Event-Token (SET) umgegangen wird, wird in diesem Abschnitt beschrieben.
## Events
Das folgende Statusdiagramm zeigt die Status und Ereignisse einer Einreichung. Die Status sind als orange Ovale
dargestellt. Rechtecke stehen für Ereignisse. Blau dargestellte Ereignisse werden vom Zustelldienst, grüne vom
empfangenden System erstellt und signiert.
![Statusdiagramm](/images/status/status.svg)
Das Akzeptieren oder Zurückweisen von Einreichungen oder Antworten passiert auf einer rein technischen Ebene und trifft
keine Aussage über die fachliche Korrektheit der Einreichungen. Gründe für technische Rückweisungen wären beispielsweise
Probleme bei der Entschlüsselung oder Validierungsfehler der Datenstrukturen. In der folgenden Tabelle sind die Ereignisse,
ihre Beschreibungen und die Autoren aufgeführt und beschrieben.
| Event | Autor | Bedeutung |
|-----------------------------------------------------------------|---------------------|-------------------------------|
| `https://schema.fitko.de/fit-connect/events/create-submission` | Zustelldienst | Die Einreichung wurde durch den Sender angelegt. |
| `https://schema.fitko.de/fit-connect/events/submit-submission` | Zustelldienst | Die Einreichung wurde durch den Sender abgesendet. |
| `https://schema.fitko.de/fit-connect/events/notify-submission` | Zustelldienst | Der Empfänger wurde per Webhook über die Einreichung informiert. |
| `https://schema.fitko.de/fit-connect/events/forward-submission` | | Ein nachgelagertes System hat die Einreichung zur Weiterleitung übernommen. |
| `https://schema.fitko.de/fit-connect/events/reject-submission` | Empfangendes System | Die Einreichung wurde durch den Empfänger zurückgewiesen. |
| `https://schema.fitko.de/fit-connect/events/accept-submission` | Empfangendes System | Die Einreichung wurde durch den Empfänger akzeptiert. |
| `https://schema.fitko.de/fit-connect/events/delete-submission` | Zustelldienst | Die Einreichung wurde durch den Zustelldienst gelöscht. |
<details>
<summary>🚧 Weitere Ereignisse, relevant für die Zukunft ...</summary>
### Ereignisse einer Antwort (Reply Events)
| Event | Autor | Bedeutung |
|-----------------------------------------------------------------|-------------------------|-------------------------------|
| `https://schema.fitko.de/fit-connect/events/create-reply` | Zustelldienst | Die Antwort wurde durch den Sender angelegt. |
| `https://schema.fitko.de/fit-connect/events/submit-reply` | Zustelldienst | Die Antwort wurde durch den Sender abgesendet. |
| `https://schema.fitko.de/fit-connect/events/notify-reply` | Zustelldienst | Der Empfänger wurde per Webhook über die Antwort informiert. |
| `https://schema.fitko.de/fit-connect/events/forward-reply` | Adressat der Reply[^1] | Ein nachgelagertes System hat die Antwort zur Weiterleitung übernommen. |
| `https://schema.fitko.de/fit-connect/events/reject-reply` | Adressat der Reply[^1] | Die Antwort wurde durch den Empfänger zurückgewiesen. |
| `https://schema.fitko.de/fit-connect/events/accept-reply` | Adressat der Reply[^1] | Die Antwort wurde durch den Empfänger akzeptiert. |
| `https://schema.fitko.de/fit-connect/events/delete-reply` | Zustelldienst | Die Antwort wurde durch den Zustelldienst gelöscht. |
[^1]: Wird das Reply vom Sender zum Subscriber gesendet, ist der Subscriber der Adressat; wird es vom Subscriber zum Sender gesendet ist der Sender der Adressat.
Das Akzeptieren oder Zurückweisen von Einreichungen oder Antworten passiert auf einer rein technischen Ebene und trifft keine Aussage über die fachliche Korrektheit der Einreichungen.
Gründe für technische Rückweisungen wären beispielsweise Probleme bei der Entschlüsselung oder Validierungsfehler der Datenstrukturen.
### Ereignisse eines Vorgangs (Case Events)
| Event | Autor | Bedeutung |
|---------------------------------------------------------|-------------------------------------------------|-----------------------------|
| `https://schema.fitko.de/fit-connect/events/close-case` | Das System, das die Vorgangsreferenz schließt. | Die Vorgangsreferenz wurde geschlossen, es dürfen keine weiteren Replies mehr erfolgen. |
</details>
## Prüfung eines Security-Event-Token (SET) {#set-validation}
Um eine vollständige Prüfung eines Security-Event-Tokens durchzuführen, MUSS zwingend sowohl die Einhaltung der
(kryptografischen) Vorgaben als auch die Signatur geprüft werden. Die Prüfung der Signatur des SET ist abhängig vom
ausstellenden System (Zustelldienst, Subscriber oder Sender). Sowohl die Prüfung der kryptografischen Vorgaben, als auch
die Prüfung der Signatur darf KEINESFALLS ausgelassen werden.
### Prüfung der Einhaltung von kryptografischen Vorgaben und der Struktur
Alle generierten Security-Event-Tokens MÜSSEN den Vorgaben aus [RFC 7519](https://tools.ietf.org/html/rfc7519)
entsprechen und über folgende Header-Attribute verfügen:
| Feld | Inhalt | Erläuterung |
|-------|-------------------------------------|---------------------------------------------------------------------------------------|
| `typ` | `secevent+jwt` | Wird gemäß [RFC 8417, Abschnitt 2.3](https://datatracker.ietf.org/doc/html/rfc8417#section-2.3) auf den festen Wert "`secevent+jwt`" gesetzt. |
| `alg` | `PS512` | Zur Signaturerstellung wird der Signaturalgorithmus RSASSA-PSS mit SHA-512 und MGF1 mit SHA-512 verwendet. Vorgabe gemäß [BSI TR-02102](https://www.bsi.bund.de/SharedDocs/Downloads/DE/BSI/Publikationen/TechnischeRichtlinien/TR02102/BSI-TR-02102.html) in der Version 2021-01 (Stand 24. März 2021). |
| `kid` | Key-ID des zugehörigen Public Keys | Die Key-ID des Public Key, mit dem die Signatur des JWT geprüft werden kann. |
Im Payload des signierten SET MÜSSEN die folgenden [standardisierten Felder](https://www.iana.org/assignments/jwt/jwt.xhtml)
gesetzt sein:
| Feld | Inhalt | Erläuterung |
|--------|------------------------------------------------|---------------------------------------------------------------|
| iss | Id des Token Issuers | Diese Angabe dient dazu, um herauszufinden, wer den Token ausgestellt hat. Für SETs, die vom Zustelldienst ausgestellt sind, wird die Host-Adresse (API-URL) verwendet. Bei SETs von empfangenden Systemen ist die `destinationId`, an der dieser die Submission schickt. |
| iat | Timestamp (UNIX-Format) | Zeitpunkt der Ausstellung des SET. |
| jti | UUID des Token | Die JWT ID ist eine eindeutige ID des SET bzw. JWT. Es wird eine zufällige UUID verwendet. |
| sub | URI, die den Gegenstand des SET identifiziert | Das Subject eines SWT ist entweder eine Übertragung (submission), eine Antwort (reply) oder eine Vorgangsreferenz (case id). Die Angabe besteht jeweils aus Typ und ID (UUID) der Resource. |
| events | JSON-Objekt der Events in diesem Event-Token | Das Objekt "events" beschreibt eine oder mehrere Ereignisse zu einem logischen Sachverhalt bzw. Gesamtereignis, wie bspw. der Versendung einer Einreichung wurde durch den Sender. Dieses Objekt beinhaltet immer zwingend eine URI, die das jeweilige Gesamtereignis eindeutig identifiziert. Das Objekt der URI des Gesamtereignisses ist aktuell leer, kann aber zukünftig weitere Details zu einem Gesamtereignis enthalten. |
| txn | URI, die den Vorgang identifiziert | Als "Transaction Identifier" wird die Vorgangsreferenz angegeben, auch wenn das Subject selbst die Vorgangsreferenz ist. In diesem Fall sind Subject und Transaction Identifier gleich. |
:::note SET Beispiel
```json title="SET Header"
{
"typ": "secevent+jwt"
"alg": "PS512",
"kid": "dd0409e5-410e-4d98-85b6-f81a40b8d980",
}
```
```json title="SET Payload"
{
"iss": "https://api.fitko.de/fit-connect/",
"iat": 1622796532,
"jti": "0BF6DBF6-CE7E-44A3-889F-82FE74C3E715",
"sub": "submission:F65FEAB2-4883-4DFF-85FB-169448545D9F",
"events": {
"https://schema.fitko.de/fit-connect/events/accept-submission": {}
},
"txn": "case:F73D30C6-8894-4444-8687-00AE756FEA90"
}
```
:::
Im folgenden Beispiel kann die allgemeine Struktur eines SET über folgenden Code validiert werden.
```java
SignedJWT securityEventToken = SignedJWT.parse(eventToken);
JWTClaimsSet payload = securityEventToken.getJWTClaimsSet();
UUID keyId = UUID.fromString(securityEventToken.getHeader().getKeyID());
validateTokenStructure(securityEventToken);
verifySignature(securityEventToken, keyId); // Abhängig von der Quelle
```
```java
boolean validateTokenStructure(SignedJWT securityEventToken) {
try {
validateHeader(signedJWT.getHeader());
validatePayload(signedJWT.getJWTClaimsSet());
} catch (ParseException e) {
throw new SecurityEventTokenValidationException("The payload of the SET could not get parsed properly.");
}
}
private void validateHeader(JWSHeader header) {
validateTrueOrElseThrow(header.getAlgorithm() == JWSAlgorithm.PS512, "The provided alg in the SET header is not allowed.");
validateTrueOrElseThrow(header.getType().toString().equals("secevent+jwt"), "The provided typ in the SET header is not secevent+jwt");
validateTrueOrElseThrow(header.getKeyID() != null, "The kid the SET was signed with is not set.");
}
private void validatePayload(JWTClaimsSet payload) throws ParseException {
validateTrueOrElseThrow(payload.getClaim("iss") != null, "The claim iss is missing in the payload of th SET.");
validateTrueOrElseThrow(payload.getClaim("iat") != null, "The claim iat is missing in the payload of th SET.");
validateTrueOrElseThrow(payload.getClaim("jti") != null, "The claim jti is missing in the payload of th SET.");
validateTrueOrElseThrow(payload.getClaim("sub") != null, "The claim sub is missing in the payload of th SET.");
validateTrueOrElseThrow(payload.getClaim("txn") != null, "The claim txn is missing in the payload of the SET.");
validateTrueOrElseThrow(payload.getClaim("events") != null, "The claim events is missing in the payload of the SET.");
validateTrueOrElseThrow(payload.getJSONObjectClaim("events").keySet().size() == 1, "Only exactly one event is allowed.");
String uuidPattern = "\\b[0-9a-fA-F]{8}\\b-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-\\b[0-9a-fA-F]{12}\\b";
String subject = payload.getStringClaim("sub");
validateTrueOrElseThrow(subject.matches("(submission|case|reply):" + uuidPattern), "The provided subject does not match the allowed pattern.");
String transactionId = payload.getStringClaim("txn");
validateTrueOrElseThrow(transactionId.matches("case:" + uuidPattern), "The provided txn does not match the allowed pattern.");
}
private void validateTrueOrElseThrow(boolean expression, String msg) {
if (!expression) {
throw new SecurityEventTokenValidationException(msg);
}
}
```
### Signaturprüfung eines vom Zustelldienst ausgestellten SET
Um die Signatur eines SET zu überprüfen, welches vom Zustelldienst ausgestellt wurde, ist es notwendig auf die
verwendeten Schlüssel zugreifen zu können. Der Zustelldienst stellt ein JSON Web Key (JWK) Set öffentlich zugänglich
unter der URL `/.well-known/jwks.json` bereit. Ein Beispiel für ein JWK Set ist in folgendem Ausschnitt dargestellt:
```json
{
"keys": [
{
"alg": "PS512",
"e": "AQAB",
"key_ops": [
"verify"
],
"kid": "6508dbcd-ab3b-4edb-a42b-37bc69f38fed",
"kty": "RSA",
"n": "65rmDz943SDKYWt8KhmaU…ga16_y9bAdoQJZRpcRr3_v9Q"
},
{
"alg": "PS512",
"e": "AQAB",
"key_ops": [
"verify"
],
"kid": "14a70431-01e6-4d67-867d-d678a3686f4b",
"kty": "RSA",
"n": "wnqKgmQHSqJhvCfdUWWyi8q…yVv3TrQVvGtsjrJVjvJR-s_D7rWoBcJVM"
}
]
}
```
Mit diesem JWK Set kann die Signatur eines Security-Event-Tokens überprüft werden. Hierfür muss der Schlüssel mit der
passenden `kid` aus dem Header des SET’s im JWK Set gesucht werden. Dann kann man mit diesem und einer entsprechenden
Bibliothek eine Signaturprüfung durchführen. Im folgenden Beispiel wird die
Bibliothek [nimbus-jose-jwt](https://connect2id.com/products/nimbus-jose-jwt) für die Prüfung genutzt.
```java
static final ZUSTELLDIENST_BASE_URL = "https://zustelldienst.example.com";
boolean verifySignature(SignedJWT securityEventToken, String keyId) {
JWKSet jwks = JWKSet.load(ZUSTELLDIENST_BASE_URL + "/.well-known/jwks.json");
JWK publicKey = jwks.getKeyByKeyId(keyId)
if (publicKey.getAlgorithm() != JWSAlgorithm.PS512) {
throw new RuntimeException("The key specified for signature verification doesn't use/specify PS512 as algorithm.")
}
JWSVerifier jwsVerifier = new RSASSAVerifier(publicKey.toRSAKey());
return signedJWT.verify(jwsVerifier);
}
```
### :construction: Signaturprüfung eines vom Senders/Subscriber ausgestellten SET
TBD
Loading