diff --git a/.github/workflows/ci-main.yml b/.github/workflows/ci-main.yml
index 4dd73cce..f5d7a949 100644
--- a/.github/workflows/ci-main.yml
+++ b/.github/workflows/ci-main.yml
@@ -1,7 +1,6 @@
name: ci-main
on:
workflow_dispatch:
-
push:
branches:
- main
diff --git a/.gitignore b/.gitignore
index dd5934d9..f5d73387 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,3 +43,4 @@ build/
!/tools/*.sh
certs/*
+.DS_Store
diff --git a/THIRD-PARTY.md b/THIRD-PARTY.md
index e0bc36dc..9c865dcb 100644
--- a/THIRD-PARTY.md
+++ b/THIRD-PARTY.md
@@ -18,7 +18,7 @@ ThirdParty Licenses
| com.fasterxml:classmate:1.5.1 | Apache License, Version 2.0 |
| com.fasterxml.jackson.core:jackson-annotations:2.13.2 | The Apache Software License, Version 2.0 |
| com.fasterxml.jackson.core:jackson-core:2.13.2 | The Apache Software License, Version 2.0 |
-| com.fasterxml.jackson.core:jackson-databind:2.13.2.2 | The Apache Software License, Version 2.0 |
+| com.fasterxml.jackson.core:jackson-databind:2.13.2.1 | The Apache Software License, Version 2.0 |
| com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.13.2 | The Apache Software License, Version 2.0 |
| com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.13.2 | The Apache Software License, Version 2.0 |
| com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.2 | The Apache Software License, Version 2.0 |
@@ -47,7 +47,7 @@ ThirdParty Licenses
| io.github.openfeign:feign-slf4j:11.8 | The Apache Software License, Version 2.0 |
| io.github.openfeign.form:feign-form:3.8.0 | The Apache Software License, Version 2.0 |
| io.github.openfeign.form:feign-form-spring:3.8.0 | The Apache Software License, Version 2.0 |
-| io.micrometer:micrometer-core:1.8.4 | The Apache Software License, Version 2.0 |
+| io.micrometer:micrometer-core:1.8.5 | The Apache Software License, Version 2.0 |
| io.swagger.core.v3:swagger-annotations:2.1.12 | Apache License 2.0 |
| io.swagger.core.v3:swagger-core:2.1.12 | Apache License 2.0 |
| io.swagger.core.v3:swagger-models:2.1.12 | Apache License 2.0 |
@@ -74,9 +74,9 @@ ThirdParty Licenses
| org.apache.httpcomponents:httpcore:4.4.15 | Apache License, Version 2.0 |
| org.apache.logging.log4j:log4j-api:2.17.2 | Apache License, Version 2.0 |
| org.apache.logging.log4j:log4j-to-slf4j:2.17.2 | Apache License, Version 2.0 |
-| org.apache.tomcat.embed:tomcat-embed-core:9.0.60 | Apache License, Version 2.0 |
-| org.apache.tomcat.embed:tomcat-embed-el:9.0.60 | Apache License, Version 2.0 |
-| org.apache.tomcat.embed:tomcat-embed-websocket:9.0.60 | Apache License, Version 2.0 |
+| org.apache.tomcat.embed:tomcat-embed-core:9.0.62 | Apache License, Version 2.0 |
+| org.apache.tomcat.embed:tomcat-embed-el:9.0.62 | Apache License, Version 2.0 |
+| org.apache.tomcat.embed:tomcat-embed-websocket:9.0.62 | Apache License, Version 2.0 |
| org.apiguardian:apiguardian-api:1.1.2 | The Apache License, Version 2.0 |
| org.aspectj:aspectjweaver:1.9.7 | Eclipse Public License - v 2.0 |
| org.assertj:assertj-core:3.21.0 | Apache License, Version 2.0 |
@@ -115,45 +115,45 @@ ThirdParty Licenses
| org.springdoc:springdoc-openapi-common:1.6.6 | The Apache License, Version 2.0 |
| org.springdoc:springdoc-openapi-ui:1.6.6 | The Apache License, Version 2.0 |
| org.springdoc:springdoc-openapi-webmvc-core:1.6.6 | The Apache License, Version 2.0 |
-| org.springframework:spring-aop:5.3.18 | Apache License, Version 2.0 |
-| org.springframework:spring-aspects:5.3.18 | Apache License, Version 2.0 |
-| org.springframework:spring-beans:5.3.18 | Apache License, Version 2.0 |
-| org.springframework:spring-context:5.3.18 | Apache License, Version 2.0 |
-| org.springframework:spring-core:5.3.18 | Apache License, Version 2.0 |
-| org.springframework:spring-expression:5.3.18 | Apache License, Version 2.0 |
-| org.springframework:spring-jcl:5.3.18 | Apache License, Version 2.0 |
-| org.springframework:spring-jdbc:5.3.18 | Apache License, Version 2.0 |
-| org.springframework:spring-orm:5.3.18 | Apache License, Version 2.0 |
-| org.springframework:spring-test:5.3.18 | Apache License, Version 2.0 |
-| org.springframework:spring-tx:5.3.18 | Apache License, Version 2.0 |
-| org.springframework:spring-web:5.3.18 | Apache License, Version 2.0 |
-| org.springframework:spring-webmvc:5.3.18 | Apache License, Version 2.0 |
-| org.springframework.boot:spring-boot:2.6.6 | Apache License, Version 2.0 |
-| org.springframework.boot:spring-boot-actuator:2.6.6 | Apache License, Version 2.0 |
-| org.springframework.boot:spring-boot-actuator-autoconfigure:2.6.6 | Apache License, Version 2.0 |
-| org.springframework.boot:spring-boot-autoconfigure:2.6.6 | Apache License, Version 2.0 |
-| org.springframework.boot:spring-boot-starter:2.6.6 | Apache License, Version 2.0 |
-| org.springframework.boot:spring-boot-starter-actuator:2.6.6 | Apache License, Version 2.0 |
-| org.springframework.boot:spring-boot-starter-aop:2.6.6 | Apache License, Version 2.0 |
-| org.springframework.boot:spring-boot-starter-data-jpa:2.6.6 | Apache License, Version 2.0 |
-| org.springframework.boot:spring-boot-starter-jdbc:2.6.6 | Apache License, Version 2.0 |
-| org.springframework.boot:spring-boot-starter-json:2.6.6 | Apache License, Version 2.0 |
-| org.springframework.boot:spring-boot-starter-logging:2.6.6 | Apache License, Version 2.0 |
-| org.springframework.boot:spring-boot-starter-test:2.6.6 | Apache License, Version 2.0 |
-| org.springframework.boot:spring-boot-starter-tomcat:2.6.6 | Apache License, Version 2.0 |
-| org.springframework.boot:spring-boot-starter-validation:2.6.6 | Apache License, Version 2.0 |
-| org.springframework.boot:spring-boot-starter-web:2.6.6 | Apache License, Version 2.0 |
-| org.springframework.boot:spring-boot-test:2.6.6 | Apache License, Version 2.0 |
-| org.springframework.boot:spring-boot-test-autoconfigure:2.6.6 | Apache License, Version 2.0 |
+| org.springframework:spring-aop:5.3.19 | Apache License, Version 2.0 |
+| org.springframework:spring-aspects:5.3.19 | Apache License, Version 2.0 |
+| org.springframework:spring-beans:5.3.19 | Apache License, Version 2.0 |
+| org.springframework:spring-context:5.3.19 | Apache License, Version 2.0 |
+| org.springframework:spring-core:5.3.19 | Apache License, Version 2.0 |
+| org.springframework:spring-expression:5.3.19 | Apache License, Version 2.0 |
+| org.springframework:spring-jcl:5.3.19 | Apache License, Version 2.0 |
+| org.springframework:spring-jdbc:5.3.19 | Apache License, Version 2.0 |
+| org.springframework:spring-orm:5.3.19 | Apache License, Version 2.0 |
+| org.springframework:spring-test:5.3.19 | Apache License, Version 2.0 |
+| org.springframework:spring-tx:5.3.19 | Apache License, Version 2.0 |
+| org.springframework:spring-web:5.3.19 | Apache License, Version 2.0 |
+| org.springframework:spring-webmvc:5.3.19 | Apache License, Version 2.0 |
+| org.springframework.boot:spring-boot:2.6.7 | Apache License, Version 2.0 |
+| org.springframework.boot:spring-boot-actuator:2.6.7 | Apache License, Version 2.0 |
+| org.springframework.boot:spring-boot-actuator-autoconfigure:2.6.7 | Apache License, Version 2.0 |
+| org.springframework.boot:spring-boot-autoconfigure:2.6.7 | Apache License, Version 2.0 |
+| org.springframework.boot:spring-boot-starter:2.6.7 | Apache License, Version 2.0 |
+| org.springframework.boot:spring-boot-starter-actuator:2.6.7 | Apache License, Version 2.0 |
+| org.springframework.boot:spring-boot-starter-aop:2.6.7 | Apache License, Version 2.0 |
+| org.springframework.boot:spring-boot-starter-data-jpa:2.6.7 | Apache License, Version 2.0 |
+| org.springframework.boot:spring-boot-starter-jdbc:2.6.7 | Apache License, Version 2.0 |
+| org.springframework.boot:spring-boot-starter-json:2.6.7 | Apache License, Version 2.0 |
+| org.springframework.boot:spring-boot-starter-logging:2.6.7 | Apache License, Version 2.0 |
+| org.springframework.boot:spring-boot-starter-test:2.6.7 | Apache License, Version 2.0 |
+| org.springframework.boot:spring-boot-starter-tomcat:2.6.7 | Apache License, Version 2.0 |
+| org.springframework.boot:spring-boot-starter-validation:2.6.7 | Apache License, Version 2.0 |
+| org.springframework.boot:spring-boot-starter-web:2.6.7 | Apache License, Version 2.0 |
+| org.springframework.boot:spring-boot-test:2.6.7 | Apache License, Version 2.0 |
+| org.springframework.boot:spring-boot-test-autoconfigure:2.6.7 | Apache License, Version 2.0 |
| org.springframework.cloud:spring-cloud-commons:3.1.1 | Apache License, Version 2.0 |
| org.springframework.cloud:spring-cloud-context:3.1.1 | Apache License, Version 2.0 |
| org.springframework.cloud:spring-cloud-openfeign-core:3.1.1 | Apache License, Version 2.0 |
| org.springframework.cloud:spring-cloud-starter:3.1.1 | Apache License, Version 2.0 |
| org.springframework.cloud:spring-cloud-starter-openfeign:3.1.1 | Apache License, Version 2.0 |
-| org.springframework.data:spring-data-commons:2.6.3 | Apache License, Version 2.0 |
-| org.springframework.data:spring-data-jpa:2.6.3 | Apache License, Version 2.0 |
-| org.springframework.security:spring-security-core:5.6.2 | Apache License, Version 2.0 |
-| org.springframework.security:spring-security-crypto:5.6.2 | Apache License, Version 2.0 |
+| org.springframework.data:spring-data-commons:2.6.4 | Apache License, Version 2.0 |
+| org.springframework.data:spring-data-jpa:2.6.4 | Apache License, Version 2.0 |
+| org.springframework.security:spring-security-core:5.6.3 | Apache License, Version 2.0 |
+| org.springframework.security:spring-security-crypto:5.6.3 | Apache License, Version 2.0 |
| org.springframework.security:spring-security-rsa:1.0.10.RELEASE | Apache 2.0 |
| org.springframework.security:spring-security-web:5.6.2 | Apache License, Version 2.0 |
| org.webjars:swagger-ui:4.5.0 | Apache 2.0 |
diff --git a/pom.xml b/pom.xml
index 1ffd88ce..8b1ff19e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,7 +7,7 @@
org.springframework.boot
spring-boot-starter-parent
- 2.6.6
+ 2.6.7
@@ -210,6 +210,11 @@
spring-boot-starter-test
test
+
+ org.junit.jupiter
+ junit-jupiter-params
+ test
+
org.liquibase
liquibase-core
diff --git a/src/main/java/eu/europa/ec/dgc/gateway/client/AssetManagerClient.java b/src/main/java/eu/europa/ec/dgc/gateway/client/AssetManagerClient.java
index e5249f8a..70be55bf 100644
--- a/src/main/java/eu/europa/ec/dgc/gateway/client/AssetManagerClient.java
+++ b/src/main/java/eu/europa/ec/dgc/gateway/client/AssetManagerClient.java
@@ -28,6 +28,7 @@
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
@@ -52,14 +53,22 @@ ResponseEntity uploadFile(@RequestHeader(HttpHeaders.AUTHORIZATION) String
@RequestBody byte[] file);
@PostMapping(
- value = "/ocs/v2.php/apps/files/api/v2/synchronize",
- consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
- produces = MediaType.APPLICATION_JSON_VALUE
+ value = "/ocs/v2.php/apps/files/api/v2/synchronize",
+ consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE
)
ResponseEntity synchronize(
- @RequestHeader(HttpHeaders.AUTHORIZATION) String authHeader,
- @RequestHeader("OCS-APIRequest") String ocsApiRequest,
- @RequestBody SynchronizeFormData formData);
+ @RequestHeader(HttpHeaders.AUTHORIZATION) String authHeader,
+ @RequestHeader("OCS-APIRequest") String ocsApiRequest,
+ @RequestBody SynchronizeFormData formData);
+
+ @GetMapping(
+ value = "/remote.php/dav/files/{uid}/{path}/{filename}",
+ produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
+ ResponseEntity downloadFile(@RequestHeader(HttpHeaders.AUTHORIZATION) String authHeader,
+ @PathVariable("uid") String uid,
+ @PathVariable("path") String path,
+ @PathVariable("filename") String filename);
@Getter
@AllArgsConstructor
diff --git a/src/main/java/eu/europa/ec/dgc/gateway/config/DgcConfigProperties.java b/src/main/java/eu/europa/ec/dgc/gateway/config/DgcConfigProperties.java
index 781f2fe0..6b2c2e13 100644
--- a/src/main/java/eu/europa/ec/dgc/gateway/config/DgcConfigProperties.java
+++ b/src/main/java/eu/europa/ec/dgc/gateway/config/DgcConfigProperties.java
@@ -49,6 +49,8 @@ public static class Publication {
private KeyStoreWithAlias keystore = new KeyStoreWithAlias();
private Boolean enabled;
private Boolean synchronizeEnabled;
+ private Boolean downloadEnabled;
+ private String downloadPath;
private String url;
private String amngrUid;
private String path;
diff --git a/src/main/java/eu/europa/ec/dgc/gateway/service/PublishingService.java b/src/main/java/eu/europa/ec/dgc/gateway/service/PublishingService.java
index fe768bdc..fa3e226e 100644
--- a/src/main/java/eu/europa/ec/dgc/gateway/service/PublishingService.java
+++ b/src/main/java/eu/europa/ec/dgc/gateway/service/PublishingService.java
@@ -33,8 +33,11 @@
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
@@ -92,6 +95,11 @@ public void publishGatewayData() {
byte[] signature = calculateSignature(zip);
uploadGatewayData(zip, signature);
+ if (Boolean.TRUE.equals(properties.getPublication().getDownloadEnabled())) {
+ downloadFile(properties.getPublication().getArchiveFilename());
+ downloadFile(properties.getPublication().getSignatureFilename());
+ }
+
log.info("Finished publishing of packed Gateway data");
}
@@ -277,10 +285,52 @@ private void uploadGatewayData(byte[] zip, byte[] signature) {
log.info("Upload and Synchronize successful");
}
+ private void downloadFile(String filename) {
+ log.info("Downloading uploaded DGCG Publication File: {}", filename);
+
+ ResponseEntity downloadResponse;
+ try {
+ downloadResponse = assetManagerClient.downloadFile(getAuthHeader(),
+ properties.getPublication().getAmngrUid(), properties.getPublication().getPath(), filename);
+
+ if (downloadResponse.getStatusCode().is2xxSuccessful()) {
+ log.info("Download of file {} was successful.", filename);
+ } else {
+ log.error("Failed to download file: {}", downloadResponse.getStatusCode());
+ }
+ } catch (FeignException.FeignServerException e) {
+ log.error("Failed to Download file: {}", e.status());
+ return;
+ }
+
+ File targetFile = Paths.get(properties.getPublication().getDownloadPath(), filename).toFile();
+
+ try {
+ Files.deleteIfExists(targetFile.toPath());
+ } catch (IOException e) {
+ log.error("Failed to delete existing file: {}, {}", targetFile.getAbsolutePath(), e.getMessage());
+ return;
+ }
+
+ if (downloadResponse.hasBody() && downloadResponse.getBody() != null) {
+ try (FileOutputStream fileOutputStream = new FileOutputStream(targetFile)) {
+ fileOutputStream.write(downloadResponse.getBody());
+ log.info("Saved file {} to {} ({} Bytes)",
+ filename, targetFile.getAbsolutePath(), downloadResponse.getBody().length);
+ } catch (IOException e) {
+ log.error("Failed to write downloaded file to disk: {}, {}",
+ targetFile.getAbsolutePath(), e.getMessage());
+ }
+ } else {
+ log.error("Download Response does not contain any body");
+ }
+
+ }
+
private String getAuthHeader() {
String header = "Basic ";
header += Base64.getEncoder().encodeToString((properties.getPublication().getUser() + ":"
- + properties.getPublication().getPassword()).getBytes(StandardCharsets.UTF_8));
+ + properties.getPublication().getPassword()).getBytes(StandardCharsets.UTF_8));
return header;
}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 76ffa734..91e4bcbe 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -42,6 +42,7 @@ dgc:
publication:
enbaled: false
synchronizeEnabled: false
+ downloadEnabled: false
keystore:
keyStorePath: /ec/prod/app/san/dgcg/dgc-publication.jks
keyStorePass: dgc-p4ssw0rd
diff --git a/src/main/resources/publication/License.txt b/src/main/resources/publication/License.txt
index bfff2005..85d68d40 100644
--- a/src/main/resources/publication/License.txt
+++ b/src/main/resources/publication/License.txt
@@ -1,44 +1,45 @@
-DATA LICENSE for the database of public keys of digital signer certificates, originally
-available from https://ec.europa.eu/assets/eu-dcc/dcc_database.zip
-
-1. This data license shall govern the use of the database of public keys of digital signer certificates, originally
-available from https://ec.europa.eu/assets/eu-dcc/dcc_database.zip. The use of the copyright and database right
-material provided in this database (the "Data") indicates that you accept the terms and conditions of this license.
-You may not amend this license under any circumstances.
-
-2. Licensor of this database is the European Commission. The Licensor grants you a worldwide, royalty-free, perpetual,
-non-exclusive licence to use the Data subject to the conditions below.
-
-3. This license grants the exercise of the following rights regarding the Data, subject to the observation of the
-conditions in sections 4:
-
- a) to copy, publish, distribute and transmit the Data;
- b) to incorporate the data in commercially and non-commercially available products and/or services.
-
-These rights are restricted to the Data itself and shall not be construed to extend beyond implicitly.
-
-4. Any exercise of the rights granted in section 3 shall be conditional upon the observance and fulfilment of the
-following obligations:
-
- a) to acknowledge the use of the database and attribute the copyright and database rights of the European
- Commission in an appropriate manner and with an appropriate notice;
- b) to acknowledge and to notify in an appropriate manner and with an appropriate notice that the use of the
- database does not constitute endorsement of any products and/or services by the European commission;
- c) to provide the text of this license in an appropriate manner and with an appropriate notice to any intended
- recipient of the Data.
-
-You may use the following notices at your discretion:
-
- "Contains data provided by the European Commission under the following license:
- https://ec.europa.eu/assets/eu-dcc/dcc_database.zip/license.txt.
- The European Commission does not endorse any products and/or services by providing this data."
-
-5. Foremost, this license is governed by the law of the European Union and additionally by the law of the Member State
-of the European Union you or an intended recipient resides in. If you or any intended recipient do not reside in a
-Member State of the European Union, the laws of the Kingdom of Belgium shall apply in addition to the law of the
-European Union.
-
-6. The European Commission shall not be liable in any capacity for any use of the Data by third parties. It shall only
-be liable for any deliberate and intentional damage caused by a deliberate and intentional default of the Data under any
-statutory law applicable according to section 5. Any further liability, representation, warranty or obligation of
-the European Commission is explicitly excluded.
+DATA LICENSE for the database of public keys of digital signer certificates, originally
+available from https://ec.europa.eu/health/ehealth/covid-19_en
+
+1. This DATA LICENSE shall govern the use of the database of public keys of digital signer certificates.
+The use of the copyright and database right material provided in this database (the "Data") indicates that
+you accept the terms and conditions of this license. You may not amend this license under any circumstances.
+
+2. Licensor of this database is the European Commission. The Licensor grants you a worldwide, royalty-free, perpetual,
+non-exclusive license to use the Data subject to the conditions below.
+
+3. This license grants the exercise of the following rights regarding the Data, subject to the observation of the
+conditions in section 4:
+
+ a) to copy, publish, distribute and transmit the Data;
+ b) to incorporate the data in commercially and non-commercially available products and/or services.
+
+These rights are restricted to the Data itself and shall not be construed to extend beyond implicitly.
+
+4. Any exercise of the rights granted in section 3 shall be conditional upon the observance and fulfillment of the
+following obligations:
+
+ a) to acknowledge the use of the database and attribute the copyright and database rights of the European
+ Commission in an appropriate manner and with appropriate notice;
+ b) to acknowledge and notify in an appropriate manner and with an appropriate notice that the use of the
+ the database does not constitute endorsement of any products and/or services by the European Commission;
+ c) to provide the text of this license in an appropriate manner and with appropriate notice to any intended
+ recipient of the Data.
+
+You may use the following notices at your discretion:
+
+ "Contains data provided by the European Commission under the DATA LICENSE. The European Commission
+ does not endorse any products and/or services by providing this data."
+
+5. Foremost, this license is governed by the law of the European Union and additionally by the law of the Member State
+of the European Union you or an intended recipient resides in. If you or any intended recipient do not reside in a
+Member State of the European Union, the laws of the Kingdom of Belgium shall apply in addition to the law of the
+European Union.
+
+6. The European Commission shall not be liable in any capacity for any use of the Data by third parties. It shall only
+be liable for any deliberate and intentional damage caused by a deliberate and intentional default of the Data under any
+statutory law applicable according to section 5. Any further liability, representation, warranty, or obligation of
+the European Commission is explicitly excluded.
+
+7. This license does not constitute a legal basis pursuant to Article 6 of Regulation (EU) 2016/679 (GDPR) to process
+the personal data contained in an EU Digital COVID Certificate.
diff --git a/src/main/resources/publication/Readme.txt b/src/main/resources/publication/Readme.txt
index ed1627ab..01ac3e1b 100644
--- a/src/main/resources/publication/Readme.txt
+++ b/src/main/resources/publication/Readme.txt
@@ -1,63 +1,63 @@
-EU Digital Covid Certificates Signer Certificates Archive
-
-The archive is published under the license described in License.txt - Please be aware of this license when distributing
-this archive or contents of it.
-
-
-Content:
-
- 1. Intention
- 2. Structure of archive
- 3. How to verify integrity of DCC
- 4. How to verify integrity of this archive
-
-1. Intention
- The content of this archive can be used to verify that a Digital Covid Certificate (DCC) was issued by an authorized
- issuer.
-
-2. Structure of archive
- This archive contains two different certificate types: Digital Signer Certificate (DSC) and Country Signing
- Certificate Authority (CSCA). The archive is structured by certificate type (DSC or CSCA), domain (currently just
- DCC) and the 2-digit country code. The certificates are encoded as PKCS#8 saved in pem files named by there
- certificate SHA-256 thumbprint.
-
- CSCA
- ∟ DCC
- ∟ CC
- ∟ 6d3644ee122d1263267c6f42974c42acc3ca1a08675264fe34360239b5605e0e.pem
- DSC
- ∟ DCC
- ∟ CC
- ∟ 6493815d2ecfdbab6507e541a5f53e68b03d057b45e16d39b35b91ee61f78ab0.pem
-
-3. How to verify integrity of DCC
- A. Extract Signature from DCC
- B. Get KID from DCC, Convert Base64 string to hex, search for DSC file starting with the resulting hex string
- C. Verify that DCC was signed by the DSC
- D. Verify that the matching DSC was issued by one of the CSCA
-
-4. How to verify integrity of this archive
- This archive and all of its contents are signed by a certificate of the European Commission.
- The signature file will be seperatly distributed. You can find it on the same download page as this archive
- (https://ec.europa.eu/assets/eu-dcc/dcc_database.zip.sig.txt). The signature file contains a base64 encoded
- CMS-Message with detached payload (PKCS#7).
-
- There are two options to verify the integrity of the archive:
-
- A: DGC-CLI (recommended, needs DGC-CLI to be installed)
- - Install DGC-CLI: https://github.com/eu-digital-green-certificates/dgc-cli#installation
- - Verify integrity
- dgc signing validate-file -i dcc_database.zip.sig.txt -p dcc_database.zip
-
- The command will output only the verification result and the subject and thumbprint of the signer certificate.
- The thumbprint should be checked against the published signer certificate.
-
- B: OpenSSL (Needs OpenSSL CLI to be installed)
- - Convert signature file from base64 encoded to plain DER file
- openssl base64 -a -A -d -in dcc_database.zip.sig.txt -out dcc_database.zip.sig.der
- - Verify integrity
- openssl cms -verify -in dcc_database.zip.sig.der -inform DER -content dcc_database.zip -binary -CAfile eu_signer.pem
-
- The output of the verify command contains the whole binary data of the zip file.
- At the end of the output you should find: "Verification successful"
-
+EU Digital Covid Certificates Signer Certificates Archive
+
+The archive is published under the license described in License.txt - Please be aware of this license when distributing
+this archive or contents of it.
+
+
+Content:
+
+ 1. Intention
+ 2. Structure of archive
+ 3. How to verify integrity of DCC
+ 4. How to verify integrity of this archive
+
+1. Intention
+ The content of this archive can be used to verify that a Digital Covid Certificate (DCC) was issued by an authorized
+ issuer. Note that in order to lawfully process the personal data contained in a DCC, verifiers need a legal basis pursuant
+ to Article 6 of Regulation (EU) 2016/679 (GDPR).
+
+2. Structure of archive
+ This archive contains two different certificate types: Digital Signer Certificate (DSC) and Country Signing Certificate
+ Authority (CSCA). The archive is structured by certificate type (DSC or CSCA), domain (currently just DCC) and the
+ 2-digit country code.
+ The certificates are encoded as PKCS#8 saved in pem files named by there certificate SHA-256 thumbprint.
+
+ CSCA
+ ∟ DCC
+ ∟ CC
+ ∟ 6d3644ee122d1263267c6f42974c42acc3ca1a08675264fe34360239b5605e0e.pem
+ DSC
+ ∟ DCC
+ ∟ CC
+ ∟ 6493815d2ecfdbab6507e541a5f53e68b03d057b45e16d39b35b91ee61f78ab0.pem
+
+3. How to verify integrity of DCC
+ A. Extract Signature from DCC
+ B. Get KID from DCC, Convert Base64 string to hex, search for DSC file starting with the resulting hex string
+ C. Verify that DCC was signed by the DSC
+ D. Verify that the matching DSC was issued by one of the CSCA
+
+4. How to verify integrity of this archive
+ This archive and all of its contents are signed by a certificate of the European Commission.
+ The signature file will be seperatly distributed. You can find it on the same download page as this archive ([URL]).
+ The signature file contains a base64 encoded CMS-Message with detached payload (PKCS#7).
+
+ There are two options to verify the integrity of the archive:
+
+ A: DGC-CLI (recommended, needs DGC-CLI to be installed)
+ - Install DGC-CLI: https://github.com/eu-digital-green-certificates/dgc-cli#installation
+ - Verify integrity
+ dgc signing validate-file -i dcc_database.zip.sig.txt -p dcc_database.zip
+
+ The command will output only the verification result and the subject and thumbprint of the signer certificate.
+ The thumbprint should be checked against the published signer certificate.
+
+ B: OpenSSL (Needs OpenSSL CLI to be installed)
+ - Convert signature file from base64 encoded to plain DER file
+ openssl base64 -a -A -d -in dcc_database.zip.sig.txt -out dcc_database.zip.sig.der
+ - Verify integrity
+ openssl cms -verify -in dcc_database.zip.sig.der -inform DER -content dcc_database.zip -binary -CAfile eu_signer.pem
+
+ The output of the verify command contains the whole binary data of the zip file.
+ At the end of the output you should find: "Verification successful"
+
diff --git a/src/test/java/eu/europa/ec/dgc/gateway/publishing/ArchivePublishingTest.java b/src/test/java/eu/europa/ec/dgc/gateway/publishing/ArchivePublishingTest.java
index 0c732149..f0981d41 100644
--- a/src/test/java/eu/europa/ec/dgc/gateway/publishing/ArchivePublishingTest.java
+++ b/src/test/java/eu/europa/ec/dgc/gateway/publishing/ArchivePublishingTest.java
@@ -20,11 +20,6 @@
package eu.europa.ec.dgc.gateway.publishing;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
import eu.europa.ec.dgc.gateway.client.AssetManagerClient;
import eu.europa.ec.dgc.gateway.config.DgcConfigProperties;
import eu.europa.ec.dgc.gateway.entity.TrustedPartyEntity;
@@ -40,9 +35,11 @@
import eu.europa.ec.dgc.signing.SignedMessageParser;
import eu.europa.ec.dgc.utils.CertificateUtils;
import java.io.ByteArrayInputStream;
+import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
+import java.nio.file.Paths;
import java.security.KeyPairGenerator;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
@@ -61,8 +58,13 @@
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
import org.mockito.ArgumentCaptor;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import org.mockito.Mockito;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
@@ -70,16 +72,17 @@
import org.springframework.util.ResourceUtils;
@SpringBootTest(properties = {
- "dgc.publication.enabled=true",
- "dgc.publication.synchronizeEnabled=true",
- "dgc.publication.user=user",
- "dgc.publication.password=pass",
- "dgc.publication.amngruid=uid",
- "dgc.publication.path=path/a/b",
- "dgc.publication.archiveFilename=db.zip",
- "dgc.publication.signatureFilename=db.zip.sig.txt",
- "dgc.publication.notifyEmails[0]=u1@c1.de",
- "dgc.publication.notifyEmails[1]=u1@c2.de"
+ "dgc.publication.enabled=true",
+ "dgc.publication.synchronizeEnabled=true",
+ "dgc.publication.downloadEnabled=true",
+ "dgc.publication.user=user",
+ "dgc.publication.password=pass",
+ "dgc.publication.amngruid=uid",
+ "dgc.publication.path=path/a/b",
+ "dgc.publication.archiveFilename=db.zip",
+ "dgc.publication.signatureFilename=db.zip.sig.txt",
+ "dgc.publication.notifyEmails[0]=u1@c1.de",
+ "dgc.publication.notifyEmails[1]=u1@c2.de"
})
@Slf4j
public class ArchivePublishingTest {
@@ -114,8 +117,11 @@ public class ArchivePublishingTest {
@Autowired
DgcConfigProperties properties;
+ @TempDir
+ File tempDir;
+
private static final String expectedAuthHeader =
- "Basic " + Base64.getEncoder().encodeToString("user:pass".getBytes(StandardCharsets.UTF_8));
+ "Basic " + Base64.getEncoder().encodeToString("user:pass".getBytes(StandardCharsets.UTF_8));
private static final String expectedUid = "uid";
private static final String expectedPath = "path/a/b";
private static final String expectedArchiveName = "db.zip";
@@ -126,6 +132,8 @@ public class ArchivePublishingTest {
@BeforeEach
public void setup() throws Exception {
+ properties.getPublication().setDownloadPath(tempDir.getAbsolutePath());
+
trustedPartyRepository.deleteAll();
signerInformationRepository.deleteAll();
@@ -157,21 +165,31 @@ public void testArchiveContainsRequiredFiles() throws Exception {
ArgumentCaptor uploadArchiveArgumentCaptor = ArgumentCaptor.forClass(byte[].class);
ArgumentCaptor uploadSignatureArgumentCaptor = ArgumentCaptor.forClass(byte[].class);
ArgumentCaptor synchronizeFormDataArgumentCaptor = ArgumentCaptor.forClass(AssetManagerClient.SynchronizeFormData.class);
+ byte[] dummyByteArrayArchive = new byte[]{0xd, 0xe, 0xa, 0xd, 0xb, 0xe, 0xe, 0xf};
+ byte[] dummyByteArraySignature = new byte[]{0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa};
when(assetManagerClientMock.uploadFile(eq(expectedAuthHeader), eq(expectedUid), eq(expectedPath), eq(expectedArchiveName), uploadArchiveArgumentCaptor.capture()))
- .thenReturn(ResponseEntity.ok(null));
+ .thenReturn(ResponseEntity.ok(null));
when(assetManagerClientMock.uploadFile(eq(expectedAuthHeader), eq(expectedUid), eq(expectedPath), eq(expectedSignatureName), uploadSignatureArgumentCaptor.capture()))
- .thenReturn(ResponseEntity.ok(null));
+ .thenReturn(ResponseEntity.ok(null));
when(assetManagerClientMock.synchronize(eq(expectedAuthHeader), eq("true"), synchronizeFormDataArgumentCaptor.capture()))
- .thenReturn(ResponseEntity.ok(new AssetManagerSynchronizeResponseDto("OK", 200, "Message", expectedPath, "token")));
+ .thenReturn(ResponseEntity.ok(new AssetManagerSynchronizeResponseDto("OK", 200, "Message", expectedPath, "token")));
+
+ when(assetManagerClientMock.downloadFile(expectedAuthHeader, expectedUid, expectedPath, expectedArchiveName))
+ .thenReturn(ResponseEntity.ok(dummyByteArrayArchive));
+
+ when(assetManagerClientMock.downloadFile(expectedAuthHeader, expectedUid, expectedPath, expectedSignatureName))
+ .thenReturn(ResponseEntity.ok(dummyByteArraySignature));
publishingService.publishGatewayData();
verify(assetManagerClientMock).uploadFile(eq(expectedAuthHeader), eq(expectedUid), eq(expectedPath), eq(expectedArchiveName), any());
verify(assetManagerClientMock).uploadFile(eq(expectedAuthHeader), eq(expectedUid), eq(expectedPath), eq(expectedSignatureName), any());
verify(assetManagerClientMock).synchronize(eq(expectedAuthHeader), eq("true"), any());
+ verify(assetManagerClientMock).downloadFile(expectedAuthHeader, expectedUid, expectedPath, expectedArchiveName);
+ verify(assetManagerClientMock).downloadFile(expectedAuthHeader, expectedUid, expectedPath, expectedSignatureName);
Assertions.assertNotNull(uploadArchiveArgumentCaptor.getValue());
Assertions.assertNotNull(uploadSignatureArgumentCaptor.getValue());
@@ -239,16 +257,33 @@ public void testArchiveContainsRequiredFiles() throws Exception {
Assertions.assertEquals(SignedMessageParser.ParserState.SUCCESS, parser.getParserState());
Assertions.assertArrayEquals(dgcTestKeyStore.getPublicationSigner().getEncoded(), parser.getSigningCertificate().getEncoded());
Assertions.assertTrue(parser.isSignatureVerified());
+
+ /*
+ * Check Downloaded files
+ */
+ byte[] downloadedArchiveFile = FileUtils.readFileToByteArray(
+ Paths.get(tempDir.getAbsolutePath(), properties.getPublication().getArchiveFilename()).toFile());
+ Assertions.assertArrayEquals(dummyByteArrayArchive, downloadedArchiveFile);
+
+ byte[] downloadedSignatureFile = FileUtils.readFileToByteArray(
+ Paths.get(tempDir.getAbsolutePath(), properties.getPublication().getSignatureFilename()).toFile());
+ Assertions.assertArrayEquals(dummyByteArraySignature, downloadedSignatureFile);
}
@Test
public void testSynchronizeDisabled() {
when(assetManagerClientMock.uploadFile(eq(expectedAuthHeader), eq(expectedUid), eq(expectedPath), eq(expectedArchiveName), any()))
- .thenReturn(ResponseEntity.ok(null));
+ .thenReturn(ResponseEntity.ok(null));
when(assetManagerClientMock.uploadFile(eq(expectedAuthHeader), eq(expectedUid), eq(expectedPath), eq(expectedSignatureName), any()))
- .thenReturn(ResponseEntity.ok(null));
+ .thenReturn(ResponseEntity.ok(null));
+
+ when(assetManagerClientMock.downloadFile(expectedAuthHeader, expectedUid, expectedPath, expectedArchiveName))
+ .thenReturn(ResponseEntity.ok(new byte[]{}));
+
+ when(assetManagerClientMock.downloadFile(expectedAuthHeader, expectedUid, expectedPath, expectedSignatureName))
+ .thenReturn(ResponseEntity.ok(new byte[]{}));
properties.getPublication().setSynchronizeEnabled(false);