Skip to content
Snippets Groups Projects
Commit a1ab0997 authored by Markus Flögel's avatar Markus Flögel
Browse files

Prüfung DVDV-Signature planning#85)

parent f5c49356
No related branches found
No related tags found
1 merge request!123Prüfung DVDV-Signature
...@@ -276,6 +276,198 @@ Im folgenden Beispiel wird die Bibliothek [nimbus-jose-jwt](https://connect2id.c ...@@ -276,6 +276,198 @@ Im folgenden Beispiel wird die Bibliothek [nimbus-jose-jwt](https://connect2id.c
</Tabs> </Tabs>
### Signaturprüfung der DVDV-Dienst gelieferten DestinationParameters (Header `jws-signature`)
Die vom DVDV-Dienst gelieferten Daten enthalten den `Zustellpunkt` sowie dessen Signatur `destinationParametersSignature`. Die destinationParametersSignature wird in diesem Fall im Header `jws-signature` der Response des Endpunktes `/destinations/{id}` zurückgegeben.
Bei der Signatur handelt es sich um eine Detached JSON Web Signature in der Compact Serialization (siehe https://datatracker.ietf.org/doc/html/rfc7515#section-3.1 und https://datatracker.ietf.org/doc/html/rfc7515#appendix-F).
Beispiel eines Zustellpunktes
```json
{
"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",
}
```
Beispiel einer Detached JWS aus dem Header der Response:
```
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 eine vollständige Signatur erfolgen. Für die Umwandlung muss der Payload (`Zustellpunkt`) als Bas64Encoded String der Signature hinzugefügt werden.
Dabei ist für das Json des Payload 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
- die Attribute des JSON-Objekts in alphabetischer Reihenfolge sortiert werden
<Tabs
defaultValue="java"
values={[
{ label: 'Java', value: 'java', },
]
}>
<TabItem value="java">
```java
int firstPoint = detachedSignature.indexOf(".");
Base64URL payload = Base64URL.encode(sortedAndCleanedPayloadJson):
SignedJWT signedJWT = new SignedJWT(
new Base64URL(headerParameterOfJWS.substring(0, firstPoint)),
payload,
new Base64URL(headerParameterOfJWS.substring(firstPoint + 2))
);
```
</TabItem>
</Tabs>
#### 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 /.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 Signatureprüfung müssen alle Grundvorraussetzungen des JWT bzw. des JWKS geprüft werden.
1. Prüfung auf erlaubten Algorithmus PS512 im Header des JWT.
2. Url zum JWKS aus dem Payload des JWT ermitteln und diese Url um den Pfad `/.well-known/jwks.json` erweitern. Dabei muss auch geprüft werden, ob die Url zum JWKS erlaubt ist.
3. Ermitteln des PublicKey über die KeyId die im Header hinterlegt ist.
4. Prüfung dass der PublicKey eine Länge von 4096 bit besitzt.
5. Prüfung das der PublicKey den Algorithmus PS512 verwendet.
6. Prüfung der Signatur.
<Tabs
defaultValue="java"
values={[
{ label: 'Java', value: 'java', },
]
}>
<TabItem value="java">
```java
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.fit-connect.example.org/v1/.well-known/jwks.json"};
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 ( PUBLICKEYSIZE != publicKey.size() )
throw new RuntimeException("The key specified for signature verification has'nt the size of 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.");
//6. Eigentliche Prüfung der Signatur
if (!signedJWT.verify(new RSASSAVerifier(publicKey.toRSAKey())))
throw new RuntimeException("Signature of payload not valid!");
}
```
```java
protected String getBaseKeyStoreUrl(SignedJWT signedJWT)
{
Map<String, Object> payload = signedJWT.getPayload().toJSONObject();
return String.valueOf(payload.get(DVDV_SUBMISSIONURL_KEY));
}
```
```java
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);
}
}
```
```java
public void validateKeyStoreUrl(URL url)
{
if ( !VALID_KEYSTORE_URLS.contains(String.valueOf(url)) )
throw new RuntimeException("KeyStoreUrl not valid!");
}
```
</TabItem>
</Tabs>
### Verwaltungspolitische Gebiete ermitteln ### 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 <ApiLink api="routing-api" to="/areas" /> nach passenden verwaltungspolitischen Gebieten gesucht werden. Falls für die Abfrage der `destinationId` kein amtlicher Gemeindeschlüssel oder ein amtlicher Regionalschlüssel bekannt ist, kann über den Endpunkt <ApiLink api="routing-api" to="/areas" /> nach passenden verwaltungspolitischen Gebieten gesucht werden.
......
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