Zum Hauptinhalt springen

Zustellpunkt ermitteln

Zustellpunkt und destinationId über die Routing API ermitteln

Um eine Einreichung an die fachlich korrekte Stelle sicherzustellen und die technischen Parameter des richtigen Zustellpunkts zu ermitteln, muss die Destination-ID der zuständigen Stelle (destinationId) und die Adresse des zuständigen Zustelldienstes (submissionUrl) ermittelt werden.

Wichtig

Der Abruf von Routing-Informationen über die Routing-API ist in der produktiven Umgebung bereits jetzt möglich. Hierzu müssen die Zuständigkeitsinformationen zuvor in den Landesredaktionen konfiguriert werden. Übergangsweise kann die Destination-ID zu Testzwecken auch manuell im sendenden System hinterlegt werden.

Die über das Self-Service-Portal erstellten Zustellpunkte sind in der Testumgebung ebenfalls nicht automatisch über die Routing-API auffindbar. Dieses Feature ist zur Erleichterung von Anbindungstests als zukünftige Erweiterung geplant. Für eine Auffindbarkeit der Zustellpunkte über die Routing-API der Testumgebung ist derzeit eine manuelle Pflege der Zuständigkeitsinformationen in der Demo-Umgebung des Portalverbund Onlinegateway (PVOG) notwendig. Für die manuelle Eingabe sind Destination-ID, Adressierungsinformationen (destinationSignature), ARS und Leika notwendig. Diese können aus dem Zustellpunkt im Self-Service-Portal ausgelesen werden. Kontaktieren Sie mit diesen Daten oder für weitere Informationen unser Anbindungsmanagement (fit-connect@spotgroup.de).

Tipp

Sofern eine Destination-ID bereits bekannt ist, können die in einem Zustellpunkt hinterlegten technischen Parameter alternativ auch über den Endpunkt GET /v1/destinations/{destinationId} der Submission API des zuständigen Zustelldienstes abgerufen werden (siehe unten).

Die Ermittlung der destinationId und die Ermittlung der technischen Parameter über die Routing-API erfolgt über einen GET-Request auf den Endpunkt GET /routes des FIT-Connect Routingdienstes.

Der Endpunkt erwartet genau zwei Parameter:

Der Endpunkt GET /routes implementiert Pagination. Das Ergebnis der Anfrage enthält daher neben der eigentlichen (Teil-)Ergebnismenge der Routing-Informationen (routes) auch Informationen wie Anzahl (count), Gesamtanzahl (totalCount) und Startpunkt der Ergebnismenge (offset). Die zurückgegebene Teilergebnismenge ist standardmäßig auf 100 Einträge limitiert und kann über den GET-Parameter limit auf maximal 500 Einträge erweitert werden. Über den GET-Parameter offset können weitere Teilmengen der Ergebnismenge ermittelt werden.

Der Endpunkt GET /routes ist auf die Anzahl von Anfragen in Zeitfenstern beschränkt. Es kann also vorkommen, das der Dienst einen HTTP-Status-Code 429 zurückliefert. Um diese Beschränkung auswerten zu können liefert der Endpunkt entspechende RateLimit-Headers bei jeder Antwort zurück.

Zustellpunkte ermitteln

Beispiele für das Ermitteln der benötigten Daten:

Das folgende Beispiel zeigt, wie Sie das .NET-SDK nutzen, um die zuständigen Zustellpunkte für eine Verwaltungsleistung in einer bestimmten Region zu ermitteln.
Sie erzeugen zunächst eine Referenz auf den Routing-Client des SDKs. Dafür benötigen Sie die Endpunkte der jeweiligen Betriebsumgebungen der FIT-Connect-Infrastruktur. Eine Beschreibung der Umgebungen (Environments) finden Sie hier. Das folgende Beispiel nutzt FitConnectEnvironment.Testing. Der Parameter logger ist optional.

var routingClient = ClientFactory.GetRoutingClient(FitConnectEnvironment.Testing, logger);

Am Routing-Client rufen Sie dann die Methode FindDestinationsAsync(leikaKey, ars) auf:

 var routes = routingClient.FindDestinationsAsync(leikaKey, ars); 

Für den Aufruf dieser Methode benötigen Sie den Leistungsschlüssel, d. h., den Schlüssel für eine bestimmte Verwaltungsleistung, zum Beispiel 99400048079000.
Zudem benötigen Sie den Amtlichen Regionalschlüssel, den Amtlichen Gemeindeschlüssel oder die AreaId. Eine Definition der Begriffe finden Sie hier. Das Beispiel verwendet den Amtlichen Regionalschlüssel (ARS) und weist den Rückgabewert der Methode der Variablen routes zu.
Der Quellcode oben ist ein Auszug aus dem Projekt ConsoleAppExample, das im Repository Codebeispiele - examples der FITKO hinterlegt ist. Eine Beschreibung des .NET-SDKs finden Sie im Hauptmenü unter "SDKs > .NET-SDK".


Alternativ können Aufrufe auch über das "Try"-Feature in der API-Dokumentation des Endpunktes GET /routes durchgeführt werden.

Ein Beispiel für eine Antwort des Routingdienstes findet sich in der in der API-Dokumentation der Routing-API im Endpunkt GET /routes.

Hinweis

Sofern eine Destination-ID und die Adresse des zuständigen Zustelldienstes bereits bekannt sind, können die in einem Zustellpunkt hinterlegten technischen Parameter auch über den Endpunkt GET /v1/destinations/{destinationId} der Submission API des zuständigen Zustelldienstes abgerufen werden (siehe unten).

Aufbau der Zustellpunkt-Informationen

Die Zustellpunkt-Informationen bestehen aus:

  • Die Destination-ID (destinationId) des Zustellpunktes.
  • Die signierten Adressierungsinformationen (destinationSignature).
  • Das Zustellpunkt-Objekt (destinationParameters) mit folgenden Inhalten:
    • Die Adresse des zuständigen Zustelldienstes (submissionUrl).
    • Der Status (status) gibt an, ob der Zustellpunkt aktiv ist. Nur im Status active können neue Einreichungen versendet werden.
    • Die Verwaltungsleistungen (services), die über diesen Zustellpunkt abgebildet werden, bestehend aus:
      • einem Identifikator der Verwaltungsleitung (identifier): Typischerweise entspricht dieser einem Leistungsschlüssel aus dem FIM-Baustein Leistungen (siehe Glossar).
      • einer Liste an zulässigen Fachdatenschemata (submissionSchemas): Hiermit legt das empfangende System fest, welchem Schema die übergebenen Fachdatensätze entsprechen müssen. Welches der angegebenen Schemata verwendet werden muss, bestimmt das sendende System aus dem eigenen fachlichen Kontext heraus. Wenn bspw. ein Antrag für einen Schwerbehindertenausweis gestellt wird, muss der Fachdatensatz aus den dort hinterlegten Schemata gemäß dem dortigen Schema für den Schwerbehindertenausweis (bspw. ein FIM/XFall Schema) entsprechen.
      • einer Liste an Regionen (regions), für die die Verwaltungsleistung angeboten wird.
    • Schlüssel-ID (Key-ID, kid) des öffentlichen Verschlüsselungsschlüssels (encryptionKid): Empfangende Systeme veröffentlichen die Schlüssel-ID ihres Verschlüsselungsschlüssels für die Verschlüsselung von Einreichungen. Der dazugehörige JSON Web Key (JWK) ist in einer Antwort des Routingdienstes im Attribut publicKeys enthalten und kann auch über den Endpunkt GET /v1/destinations/{destinationId}/keys/{keyId} abgefragt werden.
    • Die Liste der öffentlichen Schlüssel des Zustellpunktes (publicKeys) als JSON Web Key Set (JWKS). Siehe Artikel Verschlüsseln.
    • Die Liste der unterstüzten Metadaten-Schema (metadataVersions). Siehe Artikel zum Metadatensatz.
    • Die Liste der unterstüzten Rückkanäle (replyChannels).
  • Die Signatur des Zustellpunkt-Objekts (destinationParametersSignature).
  • Der Name der zuständigen Fachbehörde (destinationName).
  • Die URL des Logos der zuständigen Fachbehörde (destinationLogo).

Adressierungsinformationen (Parameter destinationSignature)

Die im Parameter destinationSignature hinterlegten Adressierungsinformationen sind vom Self-Service-Portal (SSP) signiert und wurden zuvor von der für den Zustellpunkt zuständigen Stelle im Portalverbund hinterlegt.

Payload der destinationSignature

Der dekodierte Inhalt (Payload) der Adressierungsinformationen sieht beispielhaft wie folgt aus (Leerzeichen und Zeilenumbrüche dienen ausschließlich der besseren Lesbarkeit):

{
"submissionHost": "submission-api-testing.fit-connect.fitko.dev",
"iss": "https://portal.auth-testing.fit-connect.fitko.dev",
"services": [
{
"gebietIDs": [
"urn:de:bund:destatis:bevoelkerungsstatistik:schluessel:rs:150850055055"
],
"leistungIDs": [
"urn:de:fim:leika:leistung:99108012005000"
]
}
],
"destinationId": "9162e3c9-5364-489a-9e99-aeb24eacc85c",
"iat": 1639572681,
"jti": "5b47d038-6e4d-4060-9da3-6720689287a1"
}

Signaturprüfung der Adressierungsinformationen (Parameter destinationSignature)

Bei den vom Self-Service-Portal (SSP) signierten Adressierungsinformationen handelt es sich um einen signierten JSON Web Token (JWT). Durch eine Prüfung der Signatur des JWT kann eine Manipulation der Adressierungsinformationen durch die Systeme des Portalverbund sowie den Routingdienst ausgeschlossen werden.

Den zur Prüfung der Signatur benötigten öffentlichen Schlüssel (im Format JSON Web Key, kurz JWK) stellt das Self-Service-Portal stellt in einem JSON Web Key Set (JWKS) öffentlich zugänglich über den Endpunkt /.well-known/jwks.json bereit. Für unser Testsystem ist das JWKS z.B. hier verfügbar.

Ein Beispiel für ein JWKS ist in folgendem Ausschnitt dargestellt:

{
"keys": [
{
"alg": "PS512",
"e": "AQAB",
"key_ops": [
"verify"
],
"kid": "aeBUhQS8uaJvtzMcTyiEAN3KW4m65uDmL0X1AAIqdCE",
"kty": "RSA",
"n": "4Y0sJhadfrQnNZXeS7Pqh73FvtFPXLvLw11h7OiZM0DlqvRNgoYHO5k-kxJKOVCaFek0LjKM1_VQxMVpdChCkHeapdTg60oQTQZj3pG0boR3LStbqN3hNEx_JZC4aHH16kau0vqBBPiOOoq-ExUz-hXz_GMLsp9QVqIkw9okO_tzNPjQOo--GM8r4eSsKzgSHZzmepc9Gfk16eraGicBevlkclk32TmWIE_ErD31dtVbBlK-7GG2NUe-o_5rkiCJ2EwKRHZlLkBYJkkj_IjeUdKc4dawXoE8L83DSBPyapX47_L1VHTnT0hJdOVe6WHtvzzpusZ0Au-YDhp6LSwXnU9d0-VzBJmQvtrep1FM0d9aQrz0e0lVf8wCn13VdKO_FBZw9D7i0XRhF8JqQRblqhcCY7UGshbTTM8HORMFONHFmSQm10qfV29PLmztOhIuubMyYe1DPnlfRkpn5jnt8IPoopl6MliDKSc3m4dgG23KylBpTLr3U-XGQrTlerjrYh4t1LXiJ-jQhLefkak_WnExZJSXv601BgmbGj3GdIhS6lxdMX62cOuwKLVISOmHHxvimpQwhtYwiFR9OmGoKVgtCQ5eMKLwGWVwXSvUJ5YXH-yUyNW1_vOrt0DAtYmXwS_Ij0bMg9WoXKJ-5NtQpnnIzw1lr5bW5fNn2TgWpHk"
}
]
}

Zur Prüfung der Signatur der Adressierungsinformationen muss der passende Schlüssel mit der im kid-Header der Adressierungsinformationen hinterlegten Schlüssel-ID im JWKS ermittelt werden.

Vor der eigentlichen Signaturprüfung muss die Einhaltung einiger Grundvoraussetzungen geprüft werden:

  1. Prüfung auf erlaubten Algorithmus PS512 im Header des JWT gemäß den Vorgaben für kryptographische Verfahren.
  2. Prüfung, dass der öffentliche Schlüssel eine Länge von 4096 bit besitzt.
  3. Prüfung, dass der öffentliche Schlüssel eine Verwendung des Algorithmus PS512 erlaubt.

Anschließend kann mit dem ermittelten öffentlichen Schlüssel und einer entsprechenden Bibliothek eine Signaturprüfung erfolgen.

Prüfung von Leistungsschlüssel/ARS

Zusätzlich sollte geprüft werden, ob der gegenüber der Routing-API angefragte Leistungsschlüssel und ARS zu einer der in den Adressierungsinformationen angegeben Kombinationen aus Leistungsschlüssel und ARS passt. Erfolgt eine Anfrage auf Basis einer über die Routing API ermittelten Area-ID (anhand einer Postleitzahl bzw. eines Gebietsnamens), so muss an dieser Stelle auf das korrekte Mapping des Routingdienstes zw. Postleitzahl/Gebietsnamen und ARS vertraut werden, sodass eine Prüfung des ARS durch den Sender keinen Mehrwert bietet und daher ausgelassen werden kann.

Code-Beispiel

Diese Funktionalität wird durch das SDK bereits intern umgesetzt und ist durch einen Aufruf der SDK-Methode ClientFactory.GetRoutingClient(...).FindDestinationsAsync(...) bereits automatisch mit abgedeckt:

var routes = ClientFactory.GetRoutingClient(FitConnectEnvironment.Testing, logger)
.FindDestinationsAsync(leikaKey, ars);

Der Quellcode ist hier beschrieben. Eine Beschreibung des .NET-SDKs finden Sie im Hauptmenü unter "SDKs > .NET-SDK".


Signaturprüfung der vom DVDV gelieferten Zustellpunkt-Parameter (destinationParameters bzw. HTTP-Header jws-signature)

Die vom DVDV gelieferten Daten enthalten den Zustellpunkt sowie dessen Signatur (destinationParametersSignature). Die destinationParametersSignature wird in der Routing API im gleichnamigen Feld und in der API des DVDV über den HTTP-Header jws-signature zurückgegeben. Bei der Signatur handelt es sich um eine JSON Web Signature (JWS) in der Compact Serialization gemäß RFC 7515, Abschnitt 3.1. Die Signatur liegt als Detached Signature gemäß RFC 7515, Anlage F vor.

Beispiel eines Zustellpunktes (destinationParameters):

{
"encryptionKid": "NFNb7k84r61G9ayAAJItJCNGl7wKWif9HyBAgicJq_8",
"metadataVersions": ['1.0.0'],
"publicKeys":
{
"keys":
[
{
"alg": "RSA-OAEP-256",
"e": "AQAB",
"key_ops": ["wrapKey"],
"kid": "NFNb7k84r61G9ayAAJItJCNGl7wKWif9HyBAgicJq_8",
"kty": "RSA",
"n": "1f1070XZ4NpHN2WqdH5c8dBUBPH99TJEvVXSP_jjZdOEzRJztUwSpIabtAvgDnNGmPTLs-jLlVR3NQCyKwOwpHVi3FmudKmIPplBFpsEpZ9JYBGpg8_ZbDN9fwJhob0KjAlsSY9mBOTfqLCqqVIJrk4fxBjwNaroCLkSbS2RrfMtUEW5T5Vo1uw2lnYTKq1uyhr1PG02mvDCBb0LMAqcMXRR6bdme8GN55S3UNWhsaonpq04aa8_baVdjoJYTk03VLORMojnnrJjxyPPiHRs2Re9JQoaVPy6TUrbFV63zvt30XM8ZJnla09yhMmuBJXpdtyWXKnKyqj8m9D5Vg68xksQVeJozpCAoBlsJeAheE31XPQwCBvamy46K669ZCkfkdhQgoIJMt1AVSef0qcLDg__nQ-rfIuYxHrtn7jgI0NeCGFbscxmzl08_LSj3nlj2-ag2uVq4bbdH3tziNxy_rr84N-6AA5iQe5v1L_zYXYWxGzaAOUWzJt0QRiEC9pF6Zqfrn4mPHn5lm2jtdM9AlmgkZtmK92rByfcMzo5-yEK37K96NtqpDCsoABUkvC1TLiqaCkGkQd1DmGnfNyGJV_eNMwmZyotom8WLS-icbQD913F9YlSTRsQYhFzw78pDJHHo4AtldMiQcpUY4qoVVpfpPZlMWTq7idnq6iO4MM",
"x5c": [
"...(base64 encoded cert)...",
"...(base64 encoded intermediate cert)...",
"...(base64 encoded root cert)..."
]
},
{
"alg": "PS512",
"e": "AQAB",
"key_ops": ["verify"],
"kid": "QEIaM4Lz9KLSaPyDzis-6aqE1x8q82iGdTv8Gb64ves",
"kty": "RSA",
"n": "lvi7t8Xy9Ef36gooR-AcsL4BBXjBKO0wcpM_hwFyQAELRrC3l5TityJPGjhFZJo1HZSFWGFCvqG56KkPgT5UdqbHFN2watpSguNTalERFmr6cXeB65NdjCrglCsGBmMHVViyCZ2gaBlfppdpeo0BI_9gUqv0OhzzoJGunI7G2YwPQrCdyEWWRYNVLqd_4B1wPPyc5MONavF1pQ6e1Nlk4_c9nL4A51-LRXlmBODhQ41aAac1gjpLD-ht1bVIjGzTUKZku5THAOltsEcDhjYXyfEUTA83i3I8PY9KMoenDFbCrtSUwsX-usJbpOt3M85TNAGxJeWIMa8nIEDyp1vjjb6QcPwvQgfFkSzjTQchVO4cT3rQ-DzqaRso9PAqOS0J3xasaKsqdgcr0AYvAdME3Yw_Wg2YLsq4GKjVtCAdhZiHBxx1H6oYFNXjYsnrMs99u_TpipFaDhJ-iWqtY7Bo_aek2yEt3mAoKByFO6QoQZoOgYteqCvJVGKOzETYgR9OA3_CSzl9YdkF2J3BH3DeBSX80_hSD-aDHGuRClzQ8iMBnAPW4HiOZ9IBkll-Ma7pIcgIXWhAnwvUCxtnK7ZPk213uhcwPxdK8wxxldzjsvVisFZw3QeJ7t-XMpm5_0gL5jGjzq0BAyHcxFr8yJxCekBqrBLoEHjV5LP6dUN-i98",
"x5c": ["MIIFCTCCAvECBARRtXowDQYJKoZIhvcNAQENBQAwSTELMAkGA1UEBhMCREUxFTATBgNVBAoMDFRlc3RiZWhvZXJkZTEjMCEGA1UEAwwaRklUIENvbm5lY3QgVGVzdHplcnRpZmlrYXQwHhcNMjEwOTE2MTYwMTU1WhcNMzEwOTE0MTYwMTU1WjBJMQswCQYDVQQGEwJERTEVMBMGA1UECgwMVGVzdGJlaG9lcmRlMSMwIQYDVQQDDBpGSVQgQ29ubmVjdCBUZXN0emVydGlmaWthdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJb4u7fF8vRH9+oKKEfgHLC+AQV4wSjtMHKTP4cBckABC0awt5eU4rciTxo4RWSaNR2UhVhhQr6hueipD4E+VHamxxTdsGraUoLjU2pRERZq+nF3geuTXYwq4JQrBgZjB1VYsgmdoGgZX6aXaXqNASP/YFKr9Doc86CRrpyOxtmMD0KwnchFlkWDVS6nf+AdcDz8nOTDjWrxdaUOntTZZOP3PZy+AOdfi0V5ZgTg4UONWgGnNYI6Sw/obdW1SIxs01CmZLuUxwDpbbBHA4Y2F8nxFEwPN4tyPD2PSjKHpwxWwq7UlMLF/rrCW6TrdzPOUzQBsSXliDGvJyBA8qdb442+kHD8L0IHxZEs400HIVTuHE960Pg86mkbKPTwKjktCd8WrGirKnYHK9AGLwHTBN2MP1oNmC7KuBio1bQgHYWYhwccdR+qGBTV42LJ6zLPfbv06YqRWg4SfolqrWOwaP2npNshLd5gKCgchTukKEGaDoGLXqgryVRijsxE2IEfTgN/wks5fWHZBdidwR9w3gUl/NP4Ug/mgxxrkQpc0PIjAZwD1uB4jmfSAZJZfjGu6SHICF1oQJ8L1AsbZyu2T5Ntd7oXMD8XSvMMcZXc47L1YrBWcN0Hie7flzKZuf9IC+Yxo86tAQMh3MRa/MicQnpAaqwS6BB41eSz+nVDfovfAgMBAAEwDQYJKoZIhvcNAQENBQADggIBAD4iEnx80ouIv6AhENlQM2vSy8h3SeuKxKrzOiliLLyYnXcaLSGjb4i5xheOnSiKeLvd/pm+dQlXzDYHYBNWXNnWXocXQQGRhx8Vvi3hiR1pEPKCvZcGJYoLQ/9Qvoa0JTw+ikThdg7V8Q0qhvYrM3utf5SG4+P3M3xa8rsEqFW9BQchxxmPnHHoBT2bek1UOjqZp9FLUKI1dOX8XUl8ptVlA+YUU3HESDNcY981fp+fWqcEGDIlawXw4oNwZ+tbGdIJecb4FLbxVdKOWh4klLxgu+SBOtb0wXmmVcoRoxwu2/MCYF3j73FyhVw9YJbRAZyj2VraYjZRcanRxfVBfFBexSiTE7rwU3Uh1fZBvsKzq0KoebC/wQ32ANWS7OaEh6ryDxb2+etymnOplyrTX8KTEPePgT+MLdmhFkOTyYxw5naisSXNIZctHEKshtQOutlGoJyXbDu2t08/O1HlP0qjvFIYeN94ohdG29RydkecBu8ixrAd6YUkYEMLgv71xzx40RVVg6IKg4Rekmjx126oDAYAtUFqHK1MD0pkUOTvhD2G0u4FKeYxd/Wh0tcb9hfl6fYRXptYRq3dxFKOyhki0jwVftFtmJHkBzts1M1agR6v036A7eFA/nT6HRwGJ7B3P2SOGBbZSNKk9JUnK7d3o5lhs98tZ8oCRTi1e4Ht"]
}
]
},
"submissionSchemas": [
{
"mimeType": "application/xml",
"schemaUri": "urn:xoev-de:bmk:standard:xbau_2.2#baugenehmigung.antrag.0200"
}
],
"submissionUrl": "https://submission-api.fit-connect.example.org/v1",
}

Bei einer Detached Signature sind die eigentlichen Inhaltsdaten (Payload) nicht Teil der übertragenen Signatur, sondern werden separat übermittelt. Für eine Signaturprüfung müssen die separat übermittelten Inhaltsdaten (Payload) daher zunächst wieder zur Signatur hinzugefügt werden (siehe unten).

Beispiel einer Detached JWS (destinationParametersSignature):

eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjNTNjd6UVE3QWFMdHFuSkthV2Y3N0FjM1d3WTItTEtXVVZTM1dJRUsyY2sifQ..At8ecdCMaUHQIo-22FKHBIocZ7hFxIHQxo0u_61tMKvDUy_BStKt46Aqh7lt41OLfIwEu6b4ZjLmWTeMi5SV-KMEAdRnYp5_WAmVhJttDkpf3d32uBcql0xJChAd93mr4qUnMvo-p3ltYgiKkG6_IHt8lUPt6BaH_BqWvidmtM5_Hd1SyW92OkEm1d50eMIJMxwY2e7_4qGKWqnMTel-dmY4HXlfqwfkkwkfvzVxQgOLtw0pzPKx3qODxuiOx6CVzr_QTrs12ugt7YkVkXSuVT40vekPw006O6aDOyETITK6OmDGnAB3iRqEX_qcOco2GWT0o66J2IARbtd12Hd0OpI0seqLONmSGNxcIxMcQwp5SAQa92xKkGFHgW86zAi6DQGggAh-Pb7MMN-i20jv1iC5hEpcd_eSKFDAGp_paEJkjOy-WPyhsksssdpival9OkXCQmHQnGzqu-RS4CS5l3gkAL-q5HoZHGD-YbXaYXRac4nyCrvmYg5yDBFSOvi4v07bokXiIp52tVAhuagmishsIDPI52ke4hkhHP68mUIQaA8UBhekNxPoEpXWfNwBmJpMc4Aa17Ko5WGYPG01_w11EXZ2q80-T4eINWdfAZAhrrmJwSmPQwnsrnuwTTOpl89vSeXi17KM-VxAf9kGrvE5scAtwGEjTlr56LEUieI

Erzeugung der vollständigen Signatur inklusive Payload

Die Prüfung der Signatur kann nur auf einer vollständigen Signatur erfolgen. Für die Umwandlung einer Detached Signature in eine gewöhnliche JSON Web Signature muss der Payload (destinationParameters) als Base64-URL-Encoded String der Signature hinzugefügt werden.

Dabei ist für das JSON des Payloads zu beachten, dass

  • alle semantisch unbedeutenden nicht-druckbaren Zeichen (Leerzeichen, Tabs, Line Feed \n, Carriage Return \r) vor und nach den strukturierenden Zeichen ([, {, ], }, :, ,) aus dem JSON-Payload entfernt werden und
  • die Attribute des JSON-Objekts in alphabetischer Reihenfolge sortiert werden. Die Sortierung ist unabhängig von der Groß-/Kleinschreibung der Attribute.

Diese Funktionalität wird durch das SDK bereits intern umgesetzt und ist durch einen Aufruf der SDK-Methode ClientFactory.GetRoutingClient(...).FindDestinationsAsync(...) bereits automatisch mit abgedeckt:

var routes = ClientFactory.GetRoutingClient(FitConnectEnvironment.Testing, logger)
.FindDestinationsAsync(leikaKey, ars);

Der Quellcode ist hier beschrieben. Eine Beschreibung des .NET-SDKs finden Sie im Hauptmenü unter "SDKs > .NET-SDK".

Prüfung der vollständigen Signatur

Um die Signatur zu überprüfen, ist es notwendig auf die verwendeten Schlüssel (im Format JSON Web Key, kurz JWK) zugreifen zu können. Der Zustelldienst stellt ein JSON Web Key Set (JWKS) öffentlich zugänglich über den Endpunkt GET /.well-known/jwks.json bereit. Da der Zustelldienst mit mehreren Instanzen betrieben werden kann, kann es auch mehrere Endpunkte zum JWKS geben. Diese Endpunkte können dynamisch aus dem Payload (Zustellpunkt) erzeugt werden. Dabei setzt sich der Endpunkt aus dem Attribut submissionUrl + '/.well-known/jwks.json' zusammen.

Vor der eigentlichen Signaturprüfung muss die Einhaltung einiger Grundvoraussetzungen geprüft werden:

  1. Prüfung auf erlaubten Algorithmus PS512 im Header des JWT gemäß den Vorgaben für kryptographische Verfahren.
  2. Erweiterung der URL des zuständigen Zustelldienstes (submissionUrl) aus dem Payload des JWT (destinationParameters) um den Pfad /.well-known/jwks.json. Dabei muss geprüft werden, ob die URL zu einem vertrauenswürdigen Zustelldienst gehört.
  3. Ermitteln des öffentlichen Schlüssel über die Key-ID (kid), die im Header des JSON Web Signature hinterlegt ist.
  4. Prüfung, dass der öffentliche Schlüssel eine Länge von 4096 bit besitzt.
  5. Prüfung, dass der öffentliche Schlüssel eine Verwendung des Algorithmus PS512 erlaubt.

Anschließend kann mit dem ermittelten öffentlichen Schlüssel und einer entsprechenden Bibliothek eine Signaturprüfung erfolgen.

private final static int PUBLICKEYSIZE = 4096;
private final static String DVDV_SUBMISSIONURL_KEY = "submissionUrl";
private final static String KEYSTORE_URL_ENDING = ".well-known/jwks.json";
private final static String[] VALID_KEYSTORE_URLS = new String[]{ "https://submission-api-testing.fit-connect.fitko.dev/v1/.well-known/jwks.json"}; // this value depends on the environment (test, stage, prod) in which these checks are done. A list of valid submission API urls can be retrieved via https://portal.auth-{testing,refz,prod}.fit-connect.fitko.dev/v1/delivery-services

protected void validateBySignedJWT(SignedJWT signedJWT) throws BadJOSEException, JOSEException, IOException, ParseException
{
//1. Prüfung auf erlaubten Algorithmus PS512
if ( !JWSAlgorithm.PS512.equals(signedJWT.getHeader().getAlgorithm()) )
throw new RuntimeException("JWSAlgorithm should be PS512!");

//Key für den PublicKey aus dem Header des JWT ermitteln
String keyID = signedJWT.getHeader().getKeyID();
if ( keyID == null )
throw new RuntimeException("Header KeyId should not be null!");

//2. URL zum PublicKey Keystore ermitteln
Optional<URL> keyStoreUrl = getKeyStoreUrl(getBaseKeyStoreUrl(signedJWT));
if ( keyStoreUrl.isEmpty() )
throw new RuntimeException("No JWKSetUrl found in Payload!");

validateKeyStoreUrl(keyStoreUrl.get());

//KeyStore laden
JWKSet jwks = getJWKSet(keyStoreUrl.get());

//3. Public Key ermitteln
JWK publicKey = jwks.getKeyByKeyId(keyID);

if ( publicKey == null )
throw new RuntimeException("PublicKey should not be null!");

//4. Public Key muss eine Länge von 4096 bit haben!
if ( publicKey.size() < PUBLICKEYSIZE )
throw new RuntimeException("The key specified for signature verification is not of size 4096 bit.");

//5. Der PublicKey muss den Algorithmus PS512 verwenden!
if( !JWSAlgorithm.PS512.equals(publicKey.getAlgorithm()) )
throw new RuntimeException("The key specified for signature verification doesn't use/specify PS512 as algorithm.");

//Eigentliche Prüfung der Signatur
if (!signedJWT.verify(new RSASSAVerifier(publicKey.toRSAKey())))
throw new RuntimeException("Signature of payload not valid!");
}


protected String getBaseKeyStoreUrl(SignedJWT signedJWT)
{
Map<String, Object> payload = signedJWT.getPayload().toJSONObject();
return String.valueOf(payload.get(DVDV_SUBMISSIONURL_KEY));
}


protected Optional<URL> getKeyStoreUrl(String baseKeyStoreUrl)
{
if (baseKeyStoreUrl==null)
throw new RuntimeException("KeyStoreUrl not set!");
if (!baseKeyStoreUrl.endsWith("/"))
baseKeyStoreUrl+="/";
baseKeyStoreUrl+= KEYSTORE_URL_ENDING;

try
{
return Optional.of(new URL(baseKeyStoreUrl));
}
catch (MalformedURLException e)
{
throw new RuntimeException("KeyStoreUrl not valid! MalformedURLException for url " + baseKeyStoreUrl);
}
}


public void validateKeyStoreUrl(URL url)
{
if ( !VALID_KEYSTORE_URLS.contains(String.valueOf(url)) )
throw new RuntimeException("KeyStoreUrl not valid!");
}

Verwaltungspolitische Gebiete ermitteln

Falls für die Abfrage der destinationId kein amtlicher Gemeindeschlüssel oder ein amtlicher Regionalschlüssel bekannt ist, kann über den Endpunkt GET /areas nach passenden verwaltungspolitischen Gebieten gesucht werden.

Der Endpunkt GET /areas implementiert Pagination. Das Ergebnis der Anfrage enthält daher neben der eigentlichen (Teil-)Ergebnismenge der Gebiet-Informationen (areas) auch Informationen wie Anzahl (count), Gesamtanzahl (totalCount) und Startpunkt der Ergebnismenge (offset). Die zurückgegebene Teilergebnismenge ist standardmäßig auf 100 Einträge limitiert und kann über den GET-Parameter limit auf maximal 500 Einträge erweitert werden. Über den GET-Paramter offset können weitere Teilmengen der Ergebnismenge ermittelt werden.

Die id eines Gebietes aus der Ergebnismenge kann im Endpunkt GET /areas als Identifikator (areaId) eines verwaltungspolitischen Gebietes verwendet werden.

Der Endpunkt GET /areas ist auf die Anzahl von Anfragen in Zeitfenstern beschränkt. Es kann also vorkommen, das der Dienst einen HTTP-Status-Code 429 zurückliefert. Um diese Beschränkung auswerten zu können liefert der Endpunkt entspechende RateLimit-Headers bei jeder Antwort zurück.

Beispiele für die Suche:

Das folgende Beispiel zeigt, wie Sie die Methode GetAreas der Klasse Router des .NET-SDKs verwenden, um nach der AreaId anhand der Postleitzahl zu suchen. Falls nur der Anfang der Postleitzahl bekannt ist, können Sie das Zeichen * für die fehlenden Stellen setzen. Die Methode gibt alle AreaIds zurück mit diesem Anfang der Postleitzahl:


var router = ClientFactory.GetRoutingClient(FitConnectEnvironment.Testing, logger);

string filter = "061*";

var areaIds = router.GetAreas(filter).Result;

Sie können der Methode GetAreas auch einen Namen übergeben, um die AreaIds aller Gebiete mit diesem Namen zu erhalten:


var router = ClientFactory.GetRoutingClient(FitConnectEnvironment.Testing, logger);

string filter = "Halle";

var areaIds = router.GetAreas(filter).Result;

Sie können zudem der Methode GetAreas eine Postleitzahl (im folgenden Beispiel "06108") und einen Gebietsnamen übergeben, indem Sie dem Namen "&areaSearchexpression=" voranstellen:


var router = ClientFactory.GetRoutingClient(FitConnectEnvironment.Testing, logger);

string filter = "06108&areaSearchexpression=Halle";

var areaIds = router.GetAreas(filter).Result;


Zustellpunkt-Informationen über die Submission API ermitteln

Zum Abruf der Zustellpunkt-Informationen stellt die Submission API einen Endpunkt bereit, der über Angabe des Parameters destinationId die technischen Parameter der Einreichung für den jeweiligen Zustellpunkt ausgibt. Diese kann genutzt werden, wenn die destinationId bereits bekannt ist. Die angebotenen Informationen über eine Destination unterscheiden sich fachlich nicht von den Information aus der Routing API. Bei der Submission API muss lediglich der Verschlüsselungsschlüssel über einen zusätzlichen Endpunkt abgerufen werden, anstatt diesen zusammen mit den anderen Informationen in einer Response zu erhalten (siehe Artikel Verschlüsseln).

Hinweis

Die URL der Submission API findet sich im Artikel Betriebsumgebungen.

Über curl können diese Information mit dem folgenden Aufruf abgerufen werden.

$ export SUBMISSION_API=https://submission-api-testing.fit-connect.fitko.dev
$ export JWT_TOKEN=eyJhbGciOiJIUzI1NiJ9.eyJJc3N1Z...NL-MKFrDGvn9TvkA
$ export DESTINATION_ID=9162e3c9-5364-489a-9e99-aeb24eacc85c
$ curl \
-H "Authorization: Bearer $JWT_TOKEN" \
-H "Content-Type: application/json" \
-X GET "$SUBMISSION_API/v1/destinations/$DESTINATION_ID"

{
"destinationId": "9162e3c9-5364-489a-9e99-aeb24eacc85c",
"status": "created",
"services": [
{
"identifier": "urn:de:fim:leika:leistung:99108012005000",
"submissionSchemas": [
{
"schemaUri": "https://schema.fitko.de/fim/s17000098_1.0.schema.json",
"mimeType": "application/json"
}
],
"regions": [
"DE150850055055"
]
}
],
"encryptionKid": "rZ2DkPobwJRGSIC23MZtrWdlqbkzWovmvhPHNOwqxMA",
"metadataVersions": [
"1.0.0"
],
"replyChannels": {
"eMail": {
"usePgp": true
}
}
}