├── entrypoint └── entrypoint.sh ├── certs └── dev-test.jks ├── src ├── main │ ├── java │ │ └── eu │ │ │ └── europa │ │ │ └── ec │ │ │ └── dgc │ │ │ └── validation │ │ │ ├── entity │ │ │ ├── KeyUse.java │ │ │ ├── KeyType.java │ │ │ ├── ValidationInquiry.java │ │ │ ├── ValueSetEntity.java │ │ │ ├── ShedlockEntity.java │ │ │ ├── BusinessRuleEntity.java │ │ │ └── SignerInformationEntity.java │ │ │ ├── restapi │ │ │ ├── dto │ │ │ │ ├── ServiceDescription.java │ │ │ │ ├── ValidationDevRequest.java │ │ │ │ ├── PublicKeyJwk.java │ │ │ │ ├── IdentityResponse.java │ │ │ │ ├── DccValidationRequest.java │ │ │ │ ├── VerificationMethod.java │ │ │ │ ├── ResultTypeIdentifier.java │ │ │ │ ├── AccessTokenPayload.java │ │ │ │ ├── ValidationInitRequest.java │ │ │ │ ├── ValidationInitResponse.java │ │ │ │ ├── AccessTokenType.java │ │ │ │ ├── KidDto.java │ │ │ │ ├── AcceptableType.java │ │ │ │ ├── ValidationStatusResponse.java │ │ │ │ ├── ValueSetListItemDto.java │ │ │ │ ├── BusinessRuleListItemDto.java │ │ │ │ └── AccessTokenConditions.java │ │ │ └── controller │ │ │ │ ├── IdentityController.java │ │ │ │ └── DccProvisioningController.java │ │ │ ├── service │ │ │ ├── TokenBlackListService.java │ │ │ ├── AccessTokenKeyProvider.java │ │ │ ├── RulesCache.java │ │ │ ├── ValueSetCache.java │ │ │ ├── ValidationStoreService.java │ │ │ ├── KeyProvider.java │ │ │ ├── impl │ │ │ │ ├── MemoryTokenBlackListService.java │ │ │ │ ├── MemoryValidationStoreService.java │ │ │ │ ├── RedisTokenBackListService.java │ │ │ │ ├── DgcgValueSetCache.java │ │ │ │ ├── DgcgRulesCache.java │ │ │ │ └── FixAccessTokenKeyProvider.java │ │ │ ├── DccValidationMessage.java │ │ │ ├── ValueSetsDownloadService.java │ │ │ ├── BusinessRulesDownloadService.java │ │ │ ├── SignerCertificateDownloadService.java │ │ │ ├── DccSign.java │ │ │ ├── DccCryptService.java │ │ │ ├── ResultCallbackService.java │ │ │ ├── SignerCertificateDownloadServiceGatewayImpl.java │ │ │ ├── ValueSetsDownloadServiceGatwayImpl.java │ │ │ ├── BusinessRulesDownloadServiceGatewayImpl.java │ │ │ └── ValueSetsDownloadServicePseImpl.java │ │ │ ├── cryptschemas │ │ │ ├── EncryptedData.java │ │ │ ├── CryptSchema.java │ │ │ └── RsaOaepWithSha256AesCbc.java │ │ │ ├── model │ │ │ ├── ValueSetItem.java │ │ │ └── BusinessRuleItem.java │ │ │ ├── config │ │ │ ├── RedisConfig.java │ │ │ ├── SchedulerConfig.java │ │ │ ├── ShedLockConfig.java │ │ │ ├── DccVerificationConfig.java │ │ │ ├── DgcConfigProperties.java │ │ │ ├── OpenApiConfig.java │ │ │ └── ErrorHandler.java │ │ │ ├── exception │ │ │ └── DccException.java │ │ │ ├── utils │ │ │ └── btp │ │ │ │ ├── SapCredential.java │ │ │ │ ├── JsonNodeDeserializer.java │ │ │ │ ├── CredentialStoreConfig.java │ │ │ │ ├── CredentialStore.java │ │ │ │ └── CredentialStoreCryptoUtil.java │ │ │ ├── client │ │ │ ├── dto │ │ │ │ ├── ValueSetResponseDto.java │ │ │ │ └── RulesResponseDto.java │ │ │ ├── ValueSetsRestClient.java │ │ │ ├── SignerCertificateRestClient.java │ │ │ ├── RestClientConfig.java │ │ │ └── BusinessRulesRestClient.java │ │ │ ├── repository │ │ │ ├── ValueSetRepository.java │ │ │ ├── BusinessRuleRepository.java │ │ │ └── SignerInformationRepository.java │ │ │ ├── token │ │ │ ├── AccessTokenParser.java │ │ │ ├── AccessTokenBuilder.java │ │ │ └── ResultTokenBuilder.java │ │ │ └── DgcaValidationServiceApplication.java │ └── resources │ │ ├── application-redis.yml │ │ ├── application-cloud.yml │ │ ├── db │ │ └── changelog.xml │ │ ├── application-pse.yml │ │ ├── application-btp.yml │ │ ├── application-gateway.yml │ │ ├── logback-spring.xml │ │ ├── messages │ │ └── dcc.properties │ │ └── application.yml └── test │ ├── resources │ ├── dcc-sign-test.jks │ ├── valuesets │ │ ├── disease-agent-targeted.json │ │ ├── covid-19-lab-test-type.json │ │ ├── covid-19-lab-result.json │ │ ├── valuesets.json │ │ ├── sct-vaccines-covid-19.json │ │ ├── vaccines-covid-19-names.json │ │ └── vaccines-covid-19-auth-holders.json │ ├── hcert.json │ ├── testrule.json │ ├── rule.json │ └── application.yml │ └── java │ └── eu │ └── europa │ └── ec │ └── dgc │ └── validation │ ├── DgcaValidationServiceApplicationTests.java │ ├── service │ ├── KeyStoreProviderTest │ ├── Mocks │ │ ├── BusinessRulesCacheMock.java │ │ └── ValueSetCacheMock.java │ ├── IdentityServiceTest.java │ ├── DccSignTest.java │ └── impl │ │ └── DynamicAccessTokenKeyProviderTest.java │ ├── JwtTest.java │ ├── DccEncryptionExampleTest.java │ ├── OpenApiTest.java │ ├── token │ └── AccessTokenParserTest.java │ ├── CertlogicTest.java │ ├── AesTest.java │ └── JwtNumbusTest.java ├── docker ├── Native └── CloudFoundry ├── THIRD-PARTY-NOTICES ├── NOTICE ├── nginx └── default.conf.template ├── .github ├── workflows │ ├── ci-release-notes.yml │ ├── ci-sonar.yml │ ├── ci-pull-request.yml │ ├── ci-deploy.yml │ ├── ci-dependency-check.yml │ ├── ci-openapi.yml │ ├── ci-main.yml │ └── ci-release.yml └── ISSUE_TEMPLATE │ ├── 04_question.md │ ├── 02_feature_request.md │ ├── 03_enhancement.md │ └── 01_bug.md ├── CODEOWNERS ├── .gitignore ├── settings.xml ├── .grenrc.js ├── templates └── file-header.txt ├── docker-compose.yml ├── owasp └── suppressions.xml └── CONTRIBUTING.md /entrypoint/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | sh docker-entrypoint.sh nginx & 3 | sh -c "java -jar /app/app.jar" 4 | -------------------------------------------------------------------------------- /certs/dev-test.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eu-digital-green-certificates/dgca-validation-service/HEAD/certs/dev-test.jks -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/entity/KeyUse.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.entity; 2 | 3 | public enum KeyUse { enc, sig } 4 | -------------------------------------------------------------------------------- /src/test/resources/dcc-sign-test.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eu-digital-green-certificates/dgca-validation-service/HEAD/src/test/resources/dcc-sign-test.jks -------------------------------------------------------------------------------- /src/main/resources/application-redis.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | redis: 3 | #database: 0 4 | host: localhost 5 | port: 6379 6 | #password: mypass 7 | #timeout: 60000 -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/restapi/dto/ServiceDescription.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.restapi.dto; 2 | 3 | public class ServiceDescription { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/entity/KeyType.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.entity; 2 | 3 | public enum KeyType { All, ValidationServiceEncKey, ValidationServiceSignKey } 4 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/service/TokenBlackListService.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.service; 2 | 3 | public interface TokenBlackListService { 4 | boolean checkPutBlacklist(String jti, long expire); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/service/AccessTokenKeyProvider.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.service; 2 | 3 | import java.security.PublicKey; 4 | 5 | public interface AccessTokenKeyProvider { 6 | PublicKey getPublicKey(String kid); 7 | } 8 | -------------------------------------------------------------------------------- /docker/Native: -------------------------------------------------------------------------------- 1 | FROM adoptopenjdk:11-jre-hotspot 2 | COPY ./target/*.jar /app/app.jar 3 | COPY ./certs/dev-test.jks /app/certs/dev-test.jks 4 | WORKDIR /app 5 | ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar ./app.jar" ] 6 | EXPOSE 8080 7 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/cryptschemas/EncryptedData.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.cryptschemas; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class EncryptedData { 7 | private byte[] dataEncrypted; 8 | private byte[] encKey; 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/restapi/dto/ValidationDevRequest.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.restapi.dto; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class ValidationDevRequest { 7 | private String dcc; 8 | private AccessTokenPayload accessTokenPayload; 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/service/RulesCache.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.service; 2 | 3 | import dgca.verifier.app.engine.data.Rule; 4 | import java.util.List; 5 | 6 | public interface RulesCache { 7 | public List provideRules(String countryOfArrival, String issuerCountry); 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/restapi/dto/PublicKeyJwk.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.restapi.dto; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class PublicKeyJwk { 7 | private String[] x5c; 8 | private String kid; 9 | private String alg; 10 | private String use; 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/model/ValueSetItem.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.model; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | @Getter 7 | @Setter 8 | public class ValueSetItem { 9 | 10 | private String hash; 11 | 12 | private String id; 13 | 14 | private String rawData; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/service/ValueSetCache.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.service; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | public interface ValueSetCache { 7 | public Map> provideValueSets(); 8 | 9 | public Map> getValueSets(); 10 | } 11 | -------------------------------------------------------------------------------- /THIRD-PARTY-NOTICES: -------------------------------------------------------------------------------- 1 | ThirdPartyNotices 2 | ----------------- 3 | This project uses third-party software or other resources that 4 | may be distributed under licenses different from this software. 5 | In the event that we overlooked to list a required notice, please bring this 6 | to our attention by contacting us via this email: 7 | opensource@telekom.de 8 | 9 | ---- -------------------------------------------------------------------------------- /src/main/resources/application-cloud.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | h2: 3 | console: 4 | enabled: false 5 | datasource: 6 | driver-class-name: org.postgresql.Driver 7 | url: jdbc:postgresql://localhost:5432/postgres 8 | username: postgres 9 | password: postgres 10 | jpa: 11 | database-platform: org.hibernate.dialect.PostgreSQLDialect 12 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/restapi/dto/IdentityResponse.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.restapi.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import java.util.List; 5 | import lombok.Data; 6 | 7 | @Data 8 | public class IdentityResponse { 9 | String id; 10 | List verificationMethod; 11 | } 12 | -------------------------------------------------------------------------------- /src/test/java/eu/europa/ec/dgc/validation/DgcaValidationServiceApplicationTests.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class DgcaValidationServiceApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/restapi/dto/DccValidationRequest.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.restapi.dto; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class DccValidationRequest { 7 | private String kid; 8 | private String dcc; 9 | private String sig; 10 | private String sigAlg; 11 | private String encScheme; 12 | private String encKey; 13 | } 14 | -------------------------------------------------------------------------------- /src/main/resources/db/changelog.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /src/main/resources/application-pse.yml: -------------------------------------------------------------------------------- 1 | dgc: 2 | businessRulesDownload: 3 | endpoint: "https://dgca-businessrule-service-eu-test.cfapps.eu10.hana.ondemand.com" 4 | valueSetsDownload: 5 | endpoint: "https://dgca-businessrule-service-eu-test.cfapps.eu10.hana.ondemand.com" 6 | certificatesDownloader: 7 | endpoint: "https://dgca-verifier-service-eu-test.cfapps.eu10.hana.ondemand.com" 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/test/resources/valuesets/disease-agent-targeted.json: -------------------------------------------------------------------------------- 1 | { 2 | "valueSetId": "disease-agent-targeted", 3 | "valueSetDate": "2021-04-27", 4 | "valueSetValues": { 5 | "840539006": { 6 | "display": "COVID-19", 7 | "lang": "en", 8 | "active": true, 9 | "version": "http://snomed.info/sct/900000000000207008/version/20210131", 10 | "system": "http://snomed.info/sct" 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /docker/CloudFoundry: -------------------------------------------------------------------------------- 1 | FROM adoptopenjdk:11-jre-hotspot as build 2 | COPY ./target/*.jar /app/app.jar 3 | WORKDIR /app 4 | 5 | FROM nginx:alpine 6 | COPY --from=build ./app /app 7 | COPY ./nginx/default.conf.template /etc/nginx/conf.d/default.conf 8 | COPY ./entrypoint/entrypoint.sh /entrypoint.sh 9 | COPY ./certs/dev-test.jks /certs/dev-test.jks 10 | RUN apk --no-cache add openjdk11-jre 11 | EXPOSE 80 12 | CMD sh ./entrypoint.sh 13 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/model/BusinessRuleItem.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.model; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | @Getter 7 | @Setter 8 | public class BusinessRuleItem { 9 | 10 | private String hash; 11 | 12 | private String identifier; 13 | 14 | private String version; 15 | 16 | private String country; 17 | 18 | private String rawData; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/resources/application-btp.yml: -------------------------------------------------------------------------------- 1 | dgc: 2 | gateway: 3 | connector: 4 | enabled: false 5 | sap: 6 | btp: 7 | credstore: 8 | namespace: DgcaIssuerServiceCredentialStore 9 | encrypted: false 10 | username: 11 | password: 12 | url: 13 | clientPrivateKey: 14 | serverPublicKey: 15 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 T-Systems International GmbH and all other contributors. 2 | 3 | This project is licensed under Apache License, Version 2.0; 4 | you may not use them except in compliance with the License. 5 | 6 | Contributors: 7 | ------------- 8 | 9 | Daniel Eder [daniel-eder], T-Mobile International Austria GmbH 10 | Andreas Scheibal [ascheibal], T-Systems International GmbH 11 | Simon Laurenz [slaurenz], T-Systems International GmbH -------------------------------------------------------------------------------- /src/test/java/eu/europa/ec/dgc/validation/service/KeyStoreProviderTest: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.service; 2 | 3 | import java.security.KeyPair; 4 | import java.security.KeyPairGenerator; 5 | import org.junit.jupiter.api.Test; 6 | 7 | 8 | 9 | import static org.junit.jupiter.api.Assertions.*; 10 | 11 | class KeyStoreProviderTest { 12 | 13 | @Test 14 | void OIDTest() throws Exception { 15 | 16 | 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/service/ValidationStoreService.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.service; 2 | 3 | import eu.europa.ec.dgc.validation.entity.ValidationInquiry; 4 | 5 | public interface ValidationStoreService { 6 | void storeValidation(ValidationInquiry validationInquiry); 7 | 8 | ValidationInquiry receiveValidation(String subject); 9 | 10 | void updateValidation(ValidationInquiry validationInquiry); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/cryptschemas/CryptSchema.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.cryptschemas; 2 | 3 | import java.security.PrivateKey; 4 | import java.security.PublicKey; 5 | 6 | public interface CryptSchema { 7 | EncryptedData encryptData(byte[] data, PublicKey publicKey, byte[] iv); 8 | 9 | byte[] decryptData(EncryptedData encryptedData, PrivateKey privateKey, byte[] iv); 10 | 11 | String getEncSchema(); 12 | } 13 | -------------------------------------------------------------------------------- /src/test/resources/hcert.json: -------------------------------------------------------------------------------- 1 | { 2 | "v": [ 3 | { 4 | "dn": 1, 5 | "ma": "ORG-100001699", 6 | "vp": "J07BX03", 7 | "dt": "2021-06-10", 8 | "co": "UA", 9 | "ci": "URN:UVCI:V1:DE:3RCVMTLAI70VB8D0L5N2K0VXNJ", 10 | "mp": "EU/1/20/1507", 11 | "is": "Issuer Certifcate", 12 | "sd": 2, 13 | "tg": "840539006" 14 | } 15 | ], 16 | "nam": { 17 | "fnt": "SARAPULOV", 18 | "gnt": "ALEX" 19 | }, 20 | "ver": "1.0.0", 21 | "dob": "1990-01-17" 22 | } -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/restapi/dto/VerificationMethod.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.restapi.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import lombok.Data; 5 | 6 | @Data 7 | public class VerificationMethod { 8 | String id; 9 | String type; 10 | String controller; 11 | @JsonInclude(JsonInclude.Include.NON_EMPTY) 12 | PublicKeyJwk publicKeyJwk; 13 | @JsonInclude(JsonInclude.Include.NON_EMPTY) 14 | String[] verificationMethods; 15 | } 16 | -------------------------------------------------------------------------------- /src/test/resources/testrule.json: -------------------------------------------------------------------------------- 1 | { 2 | "Identifier": "VR-DE-1", 3 | "Version": "1.0.0", 4 | "SchemaVersion":"1.0.0", 5 | "Engine":"CERTLOGIC", 6 | "EngineVersion":"1.0.0", 7 | "Type":"Acceptance", 8 | "Country":"DE", 9 | "CertificateType":"Vaccination", 10 | "Description":[{"lang":"en","desc":"Vaccination dn must be greater 1. Fake"}], 11 | "ValidFrom":"2021-06-27T07:46:40Z", 12 | "ValidTo":"2022-08-01T07:46:40Z", 13 | "AffectedFields":["dn"], 14 | "Logic":{ 15 | ">=":[ {"var": "payload.v.0.dn"} , 1 ] 16 | } 17 | } -------------------------------------------------------------------------------- /nginx/default.conf.template: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name localhost; 4 | location / { 5 | proxy_pass http://localhost:8080/identity; 6 | } 7 | location /identity { 8 | proxy_pass http://localhost:8080/identity; 9 | } 10 | location /validate { 11 | proxy_pass http://localhost:8080/validate; 12 | } 13 | location /status { 14 | proxy_pass http://localhost:8080/status; 15 | } 16 | location /initialize { 17 | proxy_pass http://localhost:8080/initialize; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.github/workflows/ci-release-notes.yml: -------------------------------------------------------------------------------- 1 | name: ci-release-notes 2 | on: 3 | release: 4 | types: 5 | - created 6 | jobs: 7 | release-notes: 8 | runs-on: ubuntu-20.04 9 | env: 10 | APP_VERSION: ${{ github.event.release.tag_name }} 11 | steps: 12 | - uses: actions/checkout@v2 13 | with: 14 | fetch-depth: 0 15 | - name: release-notes 16 | run: npx github-release-notes release --override --tags ${APP_VERSION} 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 19 | GREN_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 20 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This file provides an overview of code owners in this repository. 2 | 3 | # Each line is a file pattern followed by one or more owners. 4 | # The last matching pattern has the most precedence. 5 | # For more details, read the following article on GitHub: https://help.github.com/articles/about-codeowners/. 6 | 7 | # These are the default owners for the whole content of this repository. The default owners are automatically added as reviewers when you open a pull request, unless different owners are specified in the file. 8 | * @eu-digital-green-certificates/dgc-validation-service-members 9 | -------------------------------------------------------------------------------- /src/test/resources/valuesets/covid-19-lab-test-type.json: -------------------------------------------------------------------------------- 1 | { 2 | "valueSetId": "covid-19-lab-test-type", 3 | "valueSetDate": "2021-04-27", 4 | "valueSetValues": { 5 | "LP6464-4": { 6 | "display": "Nucleic acid amplification with probe detection", 7 | "lang": "en", 8 | "active": true, 9 | "version": "2.69", 10 | "system": "http://loinc.org" 11 | }, 12 | "LP217198-3": { 13 | "display": "Rapid immunoassay", 14 | "lang": "en", 15 | "active": true, 16 | "version": "2.69", 17 | "system": "http://loinc.org" 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/restapi/dto/ResultTypeIdentifier.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.restapi.dto; 2 | 3 | public enum ResultTypeIdentifier { 4 | TechnicalVerification("Technical Verification"), IssuerInvalidation("Issuer Invalidation"), 5 | DestinationAcceptance("Destination Acceptance (V/T/R)"), TravellerAcceptance("Traveler Acceptance"); 6 | 7 | private final String identifier; 8 | 9 | public String getIdentifier() { 10 | return identifier; 11 | } 12 | 13 | ResultTypeIdentifier(String identifier) { 14 | this.identifier = identifier; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/restapi/dto/AccessTokenPayload.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.restapi.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.Data; 5 | 6 | @Data 7 | public class AccessTokenPayload { 8 | private String jti; 9 | private String iss; 10 | private long iat; 11 | private String sub; 12 | private String aud; 13 | private long exp; 14 | @JsonProperty("t") 15 | private int type; 16 | @JsonProperty("v") 17 | private String version; 18 | @JsonProperty("vc") 19 | private AccessTokenConditions conditions; 20 | } 21 | -------------------------------------------------------------------------------- /src/test/resources/rule.json: -------------------------------------------------------------------------------- 1 | { 2 | "Identifier": "VR-DE-1", 3 | "Version": "1.0.0", 4 | "SchemaVersion":"1.0.0", 5 | "Engine":"CERTLOGIC", 6 | "EngineVersion":"1.0.0", 7 | "Type":"Acceptance", 8 | "Country":"DE", 9 | "CertificateType":"Vaccination", 10 | "Description":[{"lang":"en","desc":"Vaccination must be from June and doses must be 2"}], 11 | "ValidFrom":"2021-06-27T07:46:40Z", 12 | "ValidTo":"2021-08-01T07:46:40Z", 13 | "AffectedFields":["dt","dn"], 14 | "Logic":{ 15 | "and": [ 16 | {">=":[ {"var":"dt"}, "2021-06-01T00:00:00Z" ]}, 17 | {">=":[ {"var":"dn"}, 2 ]} 18 | ] 19 | } 20 | } -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/entity/ValidationInquiry.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.entity; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class ValidationInquiry { 7 | public enum ValidationStatus { OPEN, READY } 8 | 9 | private String subject; 10 | private ValidationStatus validationStatus; 11 | private String validationResult; 12 | /** 13 | * key generated by verifier app used to sign the dcc. 14 | */ 15 | private String publicKey; 16 | private String keyType; 17 | private String callbackUrl; 18 | private long exp; 19 | private byte[] nonce; 20 | } 21 | -------------------------------------------------------------------------------- /src/test/resources/valuesets/covid-19-lab-result.json: -------------------------------------------------------------------------------- 1 | { 2 | "valueSetId": "covid-19-lab-result", 3 | "valueSetDate": "2021-04-27", 4 | "valueSetValues": { 5 | "260415000": { 6 | "display": "Not detected", 7 | "lang": "en", 8 | "active": true, 9 | "version": "http://snomed.info/sct/900000000000207008/version/20210131", 10 | "system": "http://snomed.info/sct" 11 | }, 12 | "260373001": { 13 | "display": "Detected", 14 | "lang": "en", 15 | "active": true, 16 | "version": "http://snomed.info/sct/900000000000207008/version/20210131", 17 | "system": "http://snomed.info/sct" 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/** 5 | !**/src/test/** 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | .jpb 22 | 23 | ### NetBeans ### 24 | /nbproject/ 25 | /nbbuild/ 26 | /dist/ 27 | /.nb-gradle/ 28 | build/ 29 | 30 | ### VS Code ### 31 | .vscode/ 32 | 33 | ### Others ### 34 | ~$*.docx 35 | *.b64 36 | /testdata/ 37 | *.log 38 | 39 | /keystore 40 | 41 | /tools/* 42 | !/tools/*.bat 43 | !/tools/*.sh 44 | 45 | ### MAC OS ### 46 | .DS_STORE 47 | 48 | .settings.xml 49 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/config/RedisConfig.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.context.annotation.Profile; 6 | import org.springframework.data.redis.connection.RedisConnectionFactory; 7 | import org.springframework.data.redis.core.StringRedisTemplate; 8 | 9 | @Configuration 10 | @Profile("redis") 11 | public class RedisConfig { 12 | @Bean 13 | StringRedisTemplate template(RedisConnectionFactory connectionFactory) { 14 | return new StringRedisTemplate(connectionFactory); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/eu/europa/ec/dgc/validation/service/Mocks/BusinessRulesCacheMock.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.service.Mocks; 2 | 3 | import java.util.List; 4 | 5 | import dgca.verifier.app.engine.data.Rule; 6 | import eu.europa.ec.dgc.validation.service.RulesCache; 7 | 8 | public class BusinessRulesCacheMock implements RulesCache { 9 | 10 | private List rules; 11 | 12 | public BusinessRulesCacheMock(List rules) { 13 | this.rules = rules; 14 | } 15 | 16 | @Override 17 | public List provideRules(String countryOfArrival, String issuerCountry) { 18 | // TODO Auto-generated method stub 19 | return rules; 20 | } 21 | 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/restapi/dto/ValidationInitRequest.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.restapi.dto; 2 | 3 | import javax.validation.constraints.NotNull; 4 | import lombok.Data; 5 | 6 | @Data 7 | public class ValidationInitRequest { 8 | /** 9 | * ECDSA key generated by the 10 | * user/wallet. 11 | */ 12 | @NotNull 13 | private String pubKey; 14 | /** 15 | * Use Crypto Metod accoding to JWT definitions in RFC7518. 16 | * ES256/PS256/RS256 17 | */ 18 | @NotNull 19 | private String keyType; 20 | /** 21 | * Optional callback URL. 22 | */ 23 | private String callback; 24 | 25 | @NotNull 26 | private String nonce; 27 | } 28 | -------------------------------------------------------------------------------- /settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | false 5 | 6 | 7 | dgc-github 8 | ${app.packages.username} 9 | ${app.packages.password} 10 | 11 | 12 | ehd-github 13 | ${app.packages.username} 14 | ${app.packages.password} 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/test/java/eu/europa/ec/dgc/validation/service/Mocks/ValueSetCacheMock.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.service.Mocks; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | import eu.europa.ec.dgc.validation.service.ValueSetCache; 7 | 8 | public class ValueSetCacheMock implements ValueSetCache { 9 | 10 | private Map> valueSets; 11 | 12 | public ValueSetCacheMock(Map> valueSets) { 13 | this.valueSets = valueSets; 14 | } 15 | 16 | @Override 17 | public Map> provideValueSets() { 18 | return valueSets; 19 | } 20 | 21 | @Override 22 | public Map> getValueSets() { 23 | return valueSets; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/service/KeyProvider.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.service; 2 | 3 | 4 | import eu.europa.ec.dgc.validation.entity.KeyType; 5 | import eu.europa.ec.dgc.validation.entity.KeyUse; 6 | import java.security.PrivateKey; 7 | import java.security.cert.Certificate; 8 | import java.util.List; 9 | 10 | public interface KeyProvider { 11 | Certificate[] receiveCertificate(String keyName); 12 | 13 | PrivateKey receivePrivateKey(String keyName); 14 | 15 | String getKeyName(String kid); 16 | 17 | String[] getKeyNames(KeyType type); 18 | 19 | String getKid(String keyName); 20 | 21 | String getAlg(String keyName); 22 | 23 | String getActiveSignKey(); 24 | 25 | KeyUse getKeyUse(String keyName); 26 | } 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/04_question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U00002753 Question" 3 | about: If you have questions about pieces of the code or documentation for this component, please post them here. 4 | labels: question 5 | --- 6 | 7 | 13 | 14 | ## Your Question 15 | 16 | 17 | 18 | * Source File: 19 | * Line(s): 20 | * Question: 21 | -------------------------------------------------------------------------------- /src/main/resources/application-gateway.yml: -------------------------------------------------------------------------------- 1 | dgc: 2 | gateway: 3 | connector: 4 | enabled: true 5 | endpoint: ${DGC_GATEWAY_CONNECTOR_ENDPOINT} 6 | proxy: 7 | enabled: false 8 | max-cache-age: 300 9 | tls-trust-store: 10 | password: ${DGC_GATEWAY_CONNECTOR_TLSTRUSTSTORE_PASSWORD} 11 | path: ${DGC_GATEWAY_CONNECTOR_TLSTRUSTSTORE_PATH} 12 | tls-key-store: 13 | alias: ${DGC_GATEWAY_CONNECTOR_TLSKEYSTORE_ALIAS} 14 | password: ${DGC_GATEWAY_CONNECTOR_TLSKEYSTORE_PASSWORD} 15 | path: ${DGC_GATEWAY_CONNECTOR_TLSKEYSTORE_PATH} 16 | trust-anchor: 17 | alias: ${DGC_GATEWAY_CONNECTOR_TRUSTANCHOR_ALIAS} 18 | password: ${DGC_GATEWAY_CONNECTOR_TRUSTANCHOR_PASSWORD} 19 | path: ${DGC_GATEWAY_CONNECTOR_TRUSTANCHOR_PATH} -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/exception/DccException.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.exception; 2 | 3 | public class DccException extends RuntimeException { 4 | public int getStatus() { 5 | return status; 6 | } 7 | 8 | private int status = 500; 9 | 10 | public DccException(String message, Throwable inner) { 11 | super(message, inner); 12 | } 13 | 14 | public DccException(String message) { 15 | super(message); 16 | } 17 | 18 | public DccException(String message, Throwable inner, int status) { 19 | super(message, inner); 20 | this.status = status; 21 | } 22 | 23 | public DccException(String message, int status) { 24 | super(message); 25 | this.status = status; 26 | } 27 | 28 | 29 | } 30 | -------------------------------------------------------------------------------- /.grenrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "dataSource": "prs", 3 | "prefix": "", 4 | "onlyMilestones": false, 5 | "groupBy": { 6 | "Enhancements": [ 7 | "enhancement", 8 | "internal" 9 | ], 10 | "Bug Fixes": [ 11 | "bug" 12 | ], 13 | "Documentation": [ 14 | "documentation" 15 | ], 16 | "Others": [ 17 | "other" 18 | ] 19 | }, 20 | "changelogFilename": "CHANGELOG.md", 21 | "template": { 22 | commit: ({ message, url, author, name }) => `- [${message}](${url}) - ${author ? `@${author}` : name}`, 23 | issue: "- {{name}} [{{text}}]({{url}})", 24 | noLabel: "other", 25 | group: "\n#### {{heading}}\n", 26 | changelogTitle: "# Changelog\n\n", 27 | release: "## {{release}} ({{date}})\n{{body}}", 28 | releaseSeparator: "\n---\n\n" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /templates/file-header.txt: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * eu-digital-green-certificates / dgca-validation-service 4 | * --- 5 | * Copyright (C) 2021 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | -------------------------------------------------------------------------------- /src/test/java/eu/europa/ec/dgc/validation/service/IdentityServiceTest.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.service; 2 | 3 | import eu.europa.ec.dgc.validation.restapi.dto.IdentityResponse; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | 9 | import static org.junit.jupiter.api.Assertions.*; 10 | 11 | @SpringBootTest 12 | @Slf4j 13 | class IdentityServiceTest { 14 | @Autowired 15 | IdentityService identityService; 16 | 17 | @Test 18 | void testIdentity() throws Exception { 19 | IdentityResponse identity = identityService.getIdentity(null, null); 20 | assertNotNull(identity); 21 | assertEquals(4, identity.getVerificationMethod().size()); 22 | } 23 | } -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/restapi/dto/ValidationInitResponse.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.restapi.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import javax.validation.constraints.NotNull; 5 | import lombok.Data; 6 | 7 | @Data 8 | public class ValidationInitResponse { 9 | /** 10 | * Hexadecimal-encoded value. 11 | */ 12 | @NotNull 13 | private String subject; 14 | /** 15 | * Number of seconds since January. 16 | * 1, 1970 17 | */ 18 | private long exp; 19 | /** 20 | * Validation URL. 21 | */ 22 | @NotNull 23 | private String aud; 24 | 25 | @JsonInclude(JsonInclude.Include.NON_EMPTY) 26 | private PublicKeyJwk encKey; 27 | 28 | @JsonInclude(JsonInclude.Include.NON_EMPTY) 29 | private PublicKeyJwk sigKey; 30 | } 31 | -------------------------------------------------------------------------------- /src/test/resources/valuesets/valuesets.json: -------------------------------------------------------------------------------- 1 | [{"id":"country-2-codes","hash":"0c64fd2b81a778ea7732113df31352757dd9aa5b914cd1bb235c3a86cea09e26"},{"id":"covid-19-lab-result","hash":"64f946691cc68966335da6dfe16d4177de8c5d3ce6abc2cf18c30103a70763d6"},{"id":"covid-19-lab-test-manufacturer-and-name","hash":"8f3b837fe457a482bb6884a529a802c2db4a032a36dc6949bebe7893b2a8d3e3"},{"id":"covid-19-lab-test-type","hash":"3b30b9a612ff5ae09feb60977d1d4394761237dfcd489b83bed30d8e3508f49d"},{"id":"disease-agent-targeted","hash":"9810aafdbb7ad976845179c152c356987553eb82291b18d300db5044c4185bd8"},{"id":"sct-vaccines-covid-19","hash":"aad9707271d4e8434134efa0c2875882c322474ef1b8c36637d3a2e90db9be4f"},{"id":"vaccines-covid-19-auth-holders","hash":"9fb754e187eceeb656acf8d9c7e6df373b340bfb6492264ad631e03824b7da44"},{"id":"vaccines-covid-19-names","hash":"b13d5dbba9d6f2a515a97fa51d826b3fb9d8dc6a63175ae53caa4862fb58d097"}] -------------------------------------------------------------------------------- /src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/test/resources/valuesets/sct-vaccines-covid-19.json: -------------------------------------------------------------------------------- 1 | { 2 | "valueSetId": "sct-vaccines-covid-19", 3 | "valueSetDate": "2021-04-27", 4 | "valueSetValues": { 5 | "1119349007": { 6 | "display": "SARS-CoV-2 mRNA vaccine", 7 | "lang": "en", 8 | "active": true, 9 | "version": "http://snomed.info/sct/900000000000207008/version/20210131", 10 | "system": "http://snomed.info/sct" 11 | }, 12 | "1119305005": { 13 | "display": "SARS-CoV-2 antigen vaccine", 14 | "lang": "en", 15 | "active": true, 16 | "version": "http://snomed.info/sct/900000000000207008/version/20210131", 17 | "system": "http://snomed.info/sct" 18 | }, 19 | "J07BX03": { 20 | "display": "covid-19 vaccines", 21 | "lang": "en", 22 | "active": true, 23 | "version": "2021-01", 24 | "system": "http://www.whocc.no/atc" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/utils/btp/SapCredential.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.utils.btp; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | import java.util.Date; 6 | import lombok.Data; 7 | 8 | @Data 9 | public class SapCredential { 10 | 11 | private String id; 12 | private String name; 13 | private Date modifiedAt; 14 | private String value; 15 | private String status; 16 | private String username; 17 | private String format; 18 | private String category; 19 | private String type; 20 | 21 | public static SapCredential fromJson(String rawJson) { 22 | return gson().fromJson(rawJson, SapCredential.class); 23 | } 24 | 25 | private static Gson gson() { 26 | return new GsonBuilder() 27 | .enableComplexMapKeySerialization() 28 | .create(); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/restapi/dto/AccessTokenType.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.restapi.dto; 2 | 3 | public enum AccessTokenType { 4 | Structure(0), Cryptographic(1), Full(2); 5 | 6 | private final int intValue; 7 | 8 | AccessTokenType(int i) { 9 | intValue = i; 10 | } 11 | 12 | public int intValue() { 13 | return intValue; 14 | } 15 | 16 | /** 17 | * get Token For Int. 18 | * @param intValue intValue 19 | * @return AccessTokenType 20 | */ 21 | public static AccessTokenType getTokenForInt(int intValue) { 22 | for (AccessTokenType accessTokenType : AccessTokenType.values()) { 23 | if (accessTokenType.intValue == intValue) { 24 | return accessTokenType; 25 | } 26 | } 27 | throw new IllegalArgumentException("unknown token type: " + intValue); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/eu/europa/ec/dgc/validation/service/DccSignTest.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.service; 2 | 3 | import java.security.KeyPair; 4 | import java.security.KeyPairGenerator; 5 | 6 | import com.nimbusds.jose.util.Base64; 7 | 8 | import org.junit.jupiter.api.Test; 9 | 10 | import static org.junit.jupiter.api.Assertions.*; 11 | 12 | class DccSignTest { 13 | 14 | @Test 15 | void signTest() throws Exception { 16 | DccSign dccSign = new DccSign(); 17 | 18 | KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("EC"); 19 | keyPairGen.initialize(256); 20 | KeyPair keyPair = keyPairGen.generateKeyPair(); 21 | 22 | String dcc = "dccContent"; 23 | String dccSignature = dccSign.signDcc(dcc.getBytes(), keyPair.getPrivate()); 24 | assertTrue(dccSign.verifySignature(dcc.getBytes(), org.bouncycastle.util.encoders.Base64.decode(dccSignature), keyPair.getPublic())); 25 | } 26 | } -------------------------------------------------------------------------------- /src/test/java/eu/europa/ec/dgc/validation/service/impl/DynamicAccessTokenKeyProviderTest.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.service.impl; 2 | 3 | import static org.junit.jupiter.api.Assertions.*; 4 | 5 | import java.io.InputStream; 6 | import java.nio.charset.StandardCharsets; 7 | import org.apache.commons.io.IOUtils; 8 | import org.junit.jupiter.api.Test; 9 | 10 | class DynamicAccessTokenKeyProviderTest { 11 | @Test 12 | void loadKeysFromIdentityDoc() throws Exception { 13 | DynamicAccessTokenKeyProvider dynamicAccessTokenKeyProvider = 14 | new DynamicAccessTokenKeyProvider(null); 15 | 16 | InputStream inputStream = this.getClass().getResourceAsStream("/decorator-identity.json"); 17 | String identityJson = IOUtils.toString(inputStream, StandardCharsets.UTF_8); 18 | 19 | dynamicAccessTokenKeyProvider.loadKeysFrom(identityJson); 20 | assertNotNull(dynamicAccessTokenKeyProvider.getPublicKey("bS8D2/Wz5tY=")); 21 | 22 | } 23 | } -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/service/impl/MemoryTokenBlackListService.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.service.impl; 2 | 3 | import eu.europa.ec.dgc.validation.service.TokenBlackListService; 4 | import java.util.Collections; 5 | import java.util.HashSet; 6 | import java.util.Set; 7 | import org.springframework.context.annotation.Profile; 8 | import org.springframework.stereotype.Service; 9 | 10 | /** 11 | * In memory blacklist should be replaced by distributed service (redis) in production. 12 | */ 13 | @Service 14 | @Profile("!redis") 15 | public class MemoryTokenBlackListService implements TokenBlackListService { 16 | private Set blacklist = Collections.synchronizedSet(new HashSet<>()); 17 | 18 | /** 19 | * check and put jti in black list. 20 | * 21 | * @param jti jti 22 | * @return false if already in blacklist 23 | */ 24 | @Override 25 | public boolean checkPutBlacklist(String jti, long expire) { 26 | return blacklist.add(jti); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/utils/btp/JsonNodeDeserializer.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.utils.btp; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.JsonNode; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import com.google.gson.JsonDeserializationContext; 7 | import com.google.gson.JsonDeserializer; 8 | import com.google.gson.JsonElement; 9 | import com.google.gson.JsonParseException; 10 | import java.lang.reflect.Type; 11 | 12 | public class JsonNodeDeserializer implements JsonDeserializer { 13 | @Override 14 | public JsonNode deserialize(JsonElement jsonElement, Type type, 15 | JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { 16 | try { 17 | return new ObjectMapper().readTree(jsonElement.getAsJsonObject().toString()); 18 | } catch (JsonProcessingException e) { 19 | throw new JsonParseException(e); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/resources/messages/dcc.properties: -------------------------------------------------------------------------------- 1 | PREFIX = No HC1: prefix 2 | BASE45 = Wrong Base45 coding 3 | COMPRESSION = Can not decompress data 4 | COSE = Can not decode cose 5 | KID = Can not extract kid 6 | SCHEMA = schema invalid 7 | CBOR = can not decode cbor 8 | EXPIRED = Certificate Expired. 9 | NOTVALIDYET = Certificate not yet valid. 10 | UNKNOWNISSUERCOUNTRY = Issuer Country is unknown. 11 | HASH = dcc hash not provided for check type 0 12 | HASH_NOT_MATCH, "dcc hash does not match" 13 | NOTYETVALIDONDATE_TEST = Test collection date before condition validFrom 14 | EXPIREDONDATE_AFTER = Dcc exp date after validTo 15 | NOTYETVALIDONDATE_RECOVERY = Recovery validFrom before condition validFrom 16 | EXPIREDONDATE_RECOVERY = Recovery validTo after condition validTo 17 | SIGNATURE = "signature invalid" 18 | KID_UNKNOWN = "unknown dcc signing kid" 19 | EXPIREDONCLOCK = certificate expired for validation clock 20 | FNTNOMATCH = family name does not match 21 | GNTNOTMATCH = given name does not match 22 | DOBNOMATCH = data of birth does not match 23 | WRONGCERT = required acceptable cert type not provided -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | postgres: 5 | image: library/postgres:9.6 6 | container_name: dgca-businessrule-service-postgres 7 | ports: 8 | - 5432:5432 9 | environment: 10 | POSTGRES_DB: postgres 11 | POSTGRES_USER: postgres 12 | POSTGRES_PASSWORD: postgres 13 | restart: unless-stopped 14 | networks: 15 | persistence: 16 | 17 | backend: 18 | build: . 19 | image: eu-digital-green-certificates/dgca-businessrule-service 20 | container_name: dgca-businessrule-service 21 | ports: 22 | - 8080:8080 23 | volumes: 24 | - ./certs:/ec/prod/app/san/dgc 25 | environment: 26 | - SERVER_PORT=8080 27 | - SPRING_PROFILES_ACTIVE=cloud 28 | - SPRING_DATASOURCE_URL=jdbc:postgresql://dgca-businessrule-service-postgres:5432/postgres 29 | - SPRING_DATASOURCE_USERNAME=postgres 30 | - SPRING_DATASOURCE_PASSWORD=postgres 31 | 32 | depends_on: 33 | - postgres 34 | networks: 35 | backend: 36 | persistence: 37 | 38 | networks: 39 | backend: 40 | persistence: 41 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/02_feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F381 Feature Request" 3 | about: Do you have an idea for a new feature? 4 | labels: feature request 5 | --- 6 | 7 | 13 | 14 | ## Feature description 15 | 16 | 21 | 22 | ## Problem and motivation 23 | 24 | 28 | 29 | ## Is this something you're interested in working on 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/client/dto/ValueSetResponseDto.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * eu-digital-green-certificates / dgca-validation-service 4 | * --- 5 | * Copyright (C) 2021 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package eu.europa.ec.dgc.validation.client.dto; 22 | 23 | import lombok.Getter; 24 | import lombok.Setter; 25 | 26 | @Setter 27 | @Getter 28 | public class ValueSetResponseDto { 29 | String id; 30 | String hash; 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/service/DccValidationMessage.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.service; 2 | 3 | public enum DccValidationMessage { 4 | PREFIX, BASE45, COMPRESSION, COSE, KID, SCHEMA, CBOR, EXPIRED, NOTVALIDYET, UNKNOWNISSUERCOUNTRY, 5 | HASH, HASH_NOT_MATCH("HASH"), 6 | NOTYETVALIDONDATE_TEST("NOTYETVALIDONDATE"), 7 | NOTYETVALIDONDATE_RECOVERY("NOTYETVALIDONDATE"), 8 | EXPIREDONDATE_AFTER("EXPIREDONDATE"), 9 | EXPIREDONDATE_RECOVERY("EXPIREDONDATE"), 10 | SIGNATURE, 11 | KID_UNKNOWN("KID"), 12 | EXPIREDONCLOCK, 13 | FNTNOMATCH, 14 | GNTNOTMATCH, 15 | DOBNOMATCH, 16 | WRONGCERT; 17 | 18 | DccValidationMessage() { 19 | 20 | } 21 | 22 | DccValidationMessage(String identifier) { 23 | this.identifier = identifier; 24 | } 25 | 26 | private String identifier; 27 | 28 | /** 29 | * get identifier. 30 | * @return identifier 31 | */ 32 | public String identifier() { 33 | if (identifier != null) { 34 | return identifier; 35 | } else { 36 | return name(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/service/ValueSetsDownloadService.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * eu-digital-green-certificates / dgca-validation-service 4 | * --- 5 | * Copyright (C) 2021 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | 22 | package eu.europa.ec.dgc.validation.service; 23 | 24 | public interface ValueSetsDownloadService { 25 | 26 | /** 27 | * Synchronises the business rules with the gateway. 28 | */ 29 | void downloadValueSets(); 30 | 31 | } 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/03_enhancement.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\u23F1\uFE0F Enhancement" 3 | about: Do you have an idea for an enhancement? 4 | labels: enhancement 5 | --- 6 | 7 | 13 | 14 | ## Current Implementation 15 | 16 | 17 | 18 | ## Suggested Enhancement 19 | 20 | 24 | 25 | ## Expected Benefits 26 | 27 | 31 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/service/BusinessRulesDownloadService.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * eu-digital-green-certificates / dgca-validation-service 4 | * --- 5 | * Copyright (C) 2021 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | 22 | package eu.europa.ec.dgc.validation.service; 23 | 24 | public interface BusinessRulesDownloadService { 25 | 26 | /** 27 | * Synchronises the business rules with the gateway. 28 | */ 29 | void downloadBusinessRules(); 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/service/SignerCertificateDownloadService.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * eu-digital-green-certificates / dgca-validation-service 4 | * --- 5 | * Copyright (C) 2021 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | 22 | package eu.europa.ec.dgc.validation.service; 23 | 24 | public interface SignerCertificateDownloadService { 25 | 26 | /** 27 | * Synchronises the signer certificates with the gateway. 28 | */ 29 | void downloadCertificates(); 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/client/dto/RulesResponseDto.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * eu-digital-green-certificates / dgca-validation-service 4 | * --- 5 | * Copyright (C) 2021 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package eu.europa.ec.dgc.validation.client.dto; 22 | 23 | import lombok.Getter; 24 | import lombok.Setter; 25 | 26 | @Setter 27 | @Getter 28 | public class RulesResponseDto { 29 | String identifier; 30 | String version; 31 | String country; 32 | String hash; 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/ci-sonar.yml: -------------------------------------------------------------------------------- 1 | name: ci-sonar 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | types: 8 | - opened 9 | - synchronize 10 | - reopened 11 | jobs: 12 | sonar: 13 | runs-on: ubuntu-20.04 14 | steps: 15 | - uses: actions/setup-java@v2 16 | with: 17 | java-version: 11 18 | distribution: adopt 19 | - uses: actions/checkout@v2 20 | with: 21 | fetch-depth: 0 22 | - uses: actions/cache@v2 23 | with: 24 | path: | 25 | ~/.m2/repository 26 | key: ${{ runner.os }}-${{ hashFiles('**/pom.xml') }} 27 | - name: mvn 28 | run: |- 29 | mvn verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar \ 30 | --batch-mode \ 31 | --file ./pom.xml \ 32 | --settings ./settings.xml \ 33 | --define app.packages.username="${APP_PACKAGES_USERNAME}" \ 34 | --define app.packages.password="${APP_PACKAGES_PASSWORD}" 35 | env: 36 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 37 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 38 | APP_PACKAGES_USERNAME: ${{ github.actor }} 39 | APP_PACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }} 40 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/restapi/dto/KidDto.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * eu-digital-green-certificates / dgca-verifier-service 4 | * --- 5 | * Copyright (C) 2021 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package eu.europa.ec.dgc.validation.restapi.dto; 22 | 23 | import io.swagger.v3.oas.annotations.media.Schema; 24 | import lombok.Value; 25 | 26 | @Schema( 27 | name = "kid", 28 | type = "string", 29 | example = "8xYtW2837fc=" 30 | ) 31 | 32 | @Value 33 | public class KidDto { 34 | String kid; 35 | } 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/01_bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F6A8 Bug" 3 | about: Did you come across a bug or unexpected behaviour differing from the docs? 4 | labels: bug 5 | --- 6 | 7 | 13 | 14 | ## Describe the bug 15 | 16 | 17 | 18 | ## Expected behaviour 19 | 20 | 21 | 22 | ## Steps to reproduce the issue 23 | 24 | 25 | 26 | 32 | 33 | ## Technical details 34 | 35 | - Host Machine OS (Windows/Linux/Mac): 36 | 37 | ## Possible Fix 38 | 39 | 40 | 41 | ## Additional context 42 | 43 | 44 | -------------------------------------------------------------------------------- /.github/workflows/ci-pull-request.yml: -------------------------------------------------------------------------------- 1 | name: ci-pull-request 2 | on: 3 | pull_request: 4 | types: 5 | - opened 6 | - synchronize 7 | - reopened 8 | jobs: 9 | build: 10 | runs-on: ubuntu-20.04 11 | steps: 12 | - uses: actions/setup-java@v2 13 | with: 14 | java-version: 11 15 | distribution: adopt 16 | - uses: actions/checkout@v2 17 | with: 18 | fetch-depth: 0 19 | - uses: actions/cache@v2 20 | with: 21 | path: | 22 | ~/.m2/repository 23 | key: ${{ runner.os }}-${{ hashFiles('**/pom.xml') }} 24 | - name: mvn 25 | run: |- 26 | mvn clean package \ 27 | --batch-mode \ 28 | --file ./pom.xml \ 29 | --settings ./settings.xml \ 30 | --define app.packages.username="${APP_PACKAGES_USERNAME}" \ 31 | --define app.packages.password="${APP_PACKAGES_PASSWORD}" 32 | env: 33 | APP_PACKAGES_USERNAME: ${{ github.actor }} 34 | APP_PACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }} 35 | - name: docker 36 | run: |- 37 | docker build . \ 38 | --file ./docker/CloudFoundry 39 | - name: docker 40 | run: |- 41 | docker build . \ 42 | --file ./docker/Native -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/restapi/dto/AcceptableType.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.restapi.dto; 2 | 3 | public enum AcceptableType { 4 | Vaccination("v"), Test("t"), Recovery("r"), RATTest("tr"), PCRTest("tp"); 5 | 6 | /** 7 | * see covid-19-lab-test type value set. 8 | */ 9 | public static final String RAPID_TEST_TYPE = "LP217198-3"; 10 | public static final String PCR_TEST_TYPE = "LP6464-4"; 11 | 12 | private final String typeSymbol; 13 | 14 | AcceptableType(String typeSymbol) { 15 | this.typeSymbol = typeSymbol; 16 | } 17 | 18 | public String typeSymbol() { 19 | return typeSymbol; 20 | } 21 | 22 | /** 23 | * get Token For symbol. 24 | * @param typeSymbol typeSymbol 25 | * @return AcceptableType 26 | */ 27 | public static AcceptableType getTokenForSymbol(String typeSymbol) { 28 | for (AcceptableType accessTokenType : AcceptableType.values()) { 29 | if (accessTokenType.typeSymbol.equals(typeSymbol)) { 30 | return accessTokenType; 31 | } 32 | } 33 | throw new IllegalArgumentException("unknown token type: " + typeSymbol); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/restapi/dto/ValidationStatusResponse.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.restapi.dto; 2 | 3 | import java.util.List; 4 | import lombok.Data; 5 | 6 | /** 7 | * Payload of validation response jwt token. 8 | */ 9 | @Data 10 | public class ValidationStatusResponse { 11 | /** 12 | * Issuer of the validation. 13 | */ 14 | private String issuer; 15 | /** 16 | * Number of seconds since epoch. 17 | */ 18 | private int iat; 19 | /** 20 | * Value of the access token. 21 | */ 22 | private String sub; 23 | private List results; 24 | /** 25 | * JWT string. 26 | */ 27 | private String confirmation; 28 | 29 | @Data 30 | public static class Result { 31 | 32 | public enum ResultType { CHK, OK, NOK } 33 | 34 | /** 35 | * Identifier of the check. 36 | */ 37 | private String identifier; 38 | /** 39 | * Result of the check. 40 | */ 41 | private ResultType result; 42 | /** 43 | * Type of the check. 44 | */ 45 | private ResultTypeIdentifier type; 46 | /** 47 | * Description of the checkup. 48 | */ 49 | private String details; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/service/impl/MemoryValidationStoreService.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.service.impl; 2 | 3 | import eu.europa.ec.dgc.validation.entity.ValidationInquiry; 4 | import eu.europa.ec.dgc.validation.service.ValidationStoreService; 5 | import java.util.HashMap; 6 | import lombok.RequiredArgsConstructor; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.context.annotation.Profile; 9 | import org.springframework.stereotype.Service; 10 | 11 | @Service 12 | @Slf4j 13 | @RequiredArgsConstructor 14 | @Profile("!redis") 15 | public class MemoryValidationStoreService implements ValidationStoreService { 16 | private HashMap validationStore = new HashMap<>(); 17 | 18 | @Override 19 | public void storeValidation(ValidationInquiry validationInquiry) { 20 | validationStore.put(validationInquiry.getSubject(), validationInquiry); 21 | } 22 | 23 | @Override 24 | public ValidationInquiry receiveValidation(String subject) { 25 | return validationStore.get(subject); 26 | } 27 | 28 | @Override 29 | public void updateValidation(ValidationInquiry validationInquiry) { 30 | validationStore.put(validationInquiry.getSubject(), validationInquiry); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/service/impl/RedisTokenBackListService.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.service.impl; 2 | 3 | import eu.europa.ec.dgc.validation.service.TokenBlackListService; 4 | import java.time.Duration; 5 | import java.time.Instant; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.context.annotation.Profile; 8 | import org.springframework.data.redis.core.StringRedisTemplate; 9 | import org.springframework.stereotype.Service; 10 | 11 | @Profile("redis") 12 | @Service 13 | @RequiredArgsConstructor 14 | public class RedisTokenBackListService implements TokenBlackListService { 15 | private final StringRedisTemplate stringRedisTemplate; 16 | private static final String KEY_PREFIX = "jti:"; 17 | 18 | @Override 19 | public boolean checkPutBlacklist(String jti, long expire) { 20 | boolean success; 21 | String key = KEY_PREFIX + jti; 22 | String value = stringRedisTemplate.opsForValue().get(key); 23 | if (value == null) { 24 | long timeNow = Instant.now().getEpochSecond(); 25 | stringRedisTemplate.opsForValue().set(key, "jti", Duration.ofSeconds(expire - timeNow)); 26 | success = true; 27 | } else { 28 | success = false; 29 | } 30 | return success; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/config/SchedulerConfig.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * eu-digital-green-certificates / dgca-validation-service 4 | * --- 5 | * Copyright (C) 2021 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package eu.europa.ec.dgc.validation.config; 22 | 23 | import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock; 24 | import org.springframework.context.annotation.Configuration; 25 | import org.springframework.context.annotation.Profile; 26 | import org.springframework.scheduling.annotation.EnableScheduling; 27 | 28 | @Configuration 29 | @Profile("!test") 30 | @EnableScheduling 31 | @EnableSchedulerLock(defaultLockAtMostFor = "PT30S") 32 | public class SchedulerConfig { 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/ci-deploy.yml: -------------------------------------------------------------------------------- 1 | name: ci-deploy 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | version: 6 | required: true 7 | description: Version to deploy 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-20.04 11 | environment: dev 12 | env: 13 | APP_VERSION: ${{ github.event.inputs.version }} 14 | steps: 15 | - name: cf setup 16 | run: |- 17 | curl -sL "https://packages.cloudfoundry.org/stable?release=${CF_RELEASE}&version=${CF_VERSION}" | \ 18 | sudo tar -zx -C /usr/local/bin 19 | env: 20 | CF_VERSION: 7.2.0 21 | CF_RELEASE: linux64-binary 22 | - name: cf push 23 | run: |- 24 | cf api ${CF_API} 25 | cf auth 26 | cf target -o ${CF_ORG} -s ${CF_SPACE} 27 | cf push ${APP_NAME} --docker-image ${APP_IMAGE}:${APP_VERSION} --docker-username ${CF_DOCKER_USERNAME} 28 | env: 29 | APP_NAME: dgca-validation-service-eu-test 30 | APP_IMAGE: docker.pkg.github.com/${{ github.repository }}/dgca-validation-service-cloudfoundry 31 | CF_API: ${{ secrets.CF_API }} 32 | CF_ORG: ${{ secrets.CF_ORG }} 33 | CF_SPACE: ${{ secrets.CF_SPACE }} 34 | CF_USERNAME: ${{ secrets.CF_USERNAME }} 35 | CF_PASSWORD: ${{ secrets.CF_PASSWORD }} 36 | CF_DOCKER_USERNAME: ${{ secrets.CF_DOCKER_USERNAME }} 37 | CF_DOCKER_PASSWORD: ${{ secrets.CF_DOCKER_PASSWORD}} 38 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/restapi/dto/ValueSetListItemDto.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * eu-digital-green-certificates / dgca-businessrule-service 4 | * --- 5 | * Copyright (C) 2021 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package eu.europa.ec.dgc.validation.restapi.dto; 22 | 23 | import io.swagger.v3.oas.annotations.media.Schema; 24 | import lombok.Value; 25 | 26 | @Schema( 27 | name = "ValueSetListItem", 28 | type = "object", 29 | example = "{" 30 | + "\"id\":\"disease-agent-targeted\"," 31 | + "\"hash\":\"d4bfba1fd9f2eb29dfb2938220468ccb0b481d348f192e6015d36da4b911a83a\"," 32 | + "}" 33 | ) 34 | 35 | @Value 36 | public class ValueSetListItemDto { 37 | String id; 38 | String hash; 39 | } 40 | -------------------------------------------------------------------------------- /.github/workflows/ci-dependency-check.yml: -------------------------------------------------------------------------------- 1 | name: ci-dependency-check 2 | on: 3 | schedule: 4 | - cron: '0 1 * * 0' # Each Sunday at 01:00 UTC 5 | pull_request: 6 | types: 7 | - opened 8 | - synchronize 9 | - reopened 10 | jobs: 11 | build: 12 | runs-on: ubuntu-20.04 13 | steps: 14 | - uses: actions/setup-java@v2 15 | with: 16 | java-version: 11 17 | distribution: adopt 18 | - uses: actions/checkout@v2 19 | with: 20 | fetch-depth: 0 21 | - uses: actions/cache@v2 22 | with: 23 | path: | 24 | ~/.m2/repository 25 | key: ${{ runner.os }}-${{ hashFiles('**/pom.xml') }} 26 | - name: version 27 | run: |- 28 | APP_SHA=$(git rev-parse --short ${GITHUB_SHA}) 29 | APP_LATEST_REV=$(git rev-list --tags --max-count=1) 30 | APP_LATEST_TAG=$(git describe --tags ${APP_LATEST_REV} 2> /dev/null || echo 0.0.0) 31 | echo "APP_VERSION=${APP_LATEST_TAG}-${APP_SHA}" >> ${GITHUB_ENV} 32 | - name: mvn 33 | run: |- 34 | mvn dependency-check:check \ 35 | --batch-mode \ 36 | --file ./pom.xml \ 37 | --settings ./settings.xml \ 38 | --define app.packages.username="${APP_PACKAGES_USERNAME}" \ 39 | --define app.packages.password="${APP_PACKAGES_PASSWORD}" \ 40 | env: 41 | APP_PACKAGES_USERNAME: ${{ github.actor }} 42 | APP_PACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }} 43 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/repository/ValueSetRepository.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * eu-digital-green-certificates / dgca-businessrule-service 4 | * --- 5 | * Copyright (C) 2021 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package eu.europa.ec.dgc.validation.repository; 22 | 23 | 24 | import eu.europa.ec.dgc.validation.entity.ValueSetEntity; 25 | import eu.europa.ec.dgc.validation.restapi.dto.ValueSetListItemDto; 26 | import java.util.List; 27 | import org.springframework.data.jpa.repository.JpaRepository; 28 | 29 | public interface ValueSetRepository extends JpaRepository { 30 | 31 | List findAllByOrderByIdAsc(); 32 | 33 | ValueSetEntity findOneByHash(String hash); 34 | 35 | void deleteByHashNotIn(List hashes); 36 | } -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/restapi/dto/BusinessRuleListItemDto.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * eu-digital-green-certificates / dgca-businessrule-service 4 | * --- 5 | * Copyright (C) 2021 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package eu.europa.ec.dgc.validation.restapi.dto; 22 | 23 | import io.swagger.v3.oas.annotations.media.Schema; 24 | import lombok.Value; 25 | 26 | @Schema( 27 | name = "BusinessRuleListItem", 28 | type = "object", 29 | example = "{" 30 | + "\"identifier\":\"VR-DE-1\"," 31 | + "\"version\":\"1.0.0\"," 32 | + "\"country\":\"DE\"," 33 | + "\"hash\":\"6821d518570fe9f4417c482ff0d2582a7b6440f243a9034f812e0d71611b611f\"" 34 | + "}" 35 | ) 36 | 37 | @Value 38 | public class BusinessRuleListItemDto { 39 | String identifier; 40 | String version; 41 | String country; 42 | String hash; 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/repository/BusinessRuleRepository.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * eu-digital-green-certificates / dgca-businessrule-service 4 | * --- 5 | * Copyright (C) 2021 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package eu.europa.ec.dgc.validation.repository; 22 | 23 | import eu.europa.ec.dgc.validation.entity.BusinessRuleEntity; 24 | import eu.europa.ec.dgc.validation.restapi.dto.BusinessRuleListItemDto; 25 | import java.util.List; 26 | import org.springframework.data.jpa.repository.JpaRepository; 27 | 28 | public interface BusinessRuleRepository extends JpaRepository { 29 | 30 | List findAllByOrderByIdentifierAsc(); 31 | 32 | List findAllByCountryInOrderByIdentifierAsc(List countries); 33 | 34 | BusinessRuleEntity findOneByCountryAndHash(String country, String hash); 35 | 36 | void deleteByHashNotIn(List hashes); 37 | } -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/utils/btp/CredentialStoreConfig.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.utils.btp; 2 | 3 | import java.nio.charset.StandardCharsets; 4 | import java.util.Base64; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.boot.web.client.RestTemplateBuilder; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.context.annotation.Profile; 10 | import org.springframework.web.client.RestTemplate; 11 | 12 | @Configuration 13 | @Profile("btp") 14 | public class CredentialStoreConfig { 15 | 16 | @Value("${sap.btp.credstore.username}") 17 | private String username; 18 | 19 | @Value("${sap.btp.credstore.password}") 20 | private String password; 21 | 22 | @Value("${sap.btp.credstore.namespace}") 23 | private String namespace; 24 | 25 | @Bean 26 | RestTemplate restTemplate(RestTemplateBuilder builder) { 27 | RestTemplate restTemplate = builder.build(); 28 | restTemplate.getInterceptors().add((request, body, execution) -> { 29 | request.getHeaders().set("Authorization", "Basic " + getAuthToken()); 30 | request.getHeaders().set("sapcp-credstore-namespace", namespace); 31 | return execution.execute(request, body); 32 | }); 33 | 34 | return restTemplate; 35 | } 36 | 37 | private String getAuthToken() { 38 | String authHeader = username + ":" + password; 39 | return Base64.getEncoder().encodeToString(authHeader.getBytes(StandardCharsets.UTF_8)); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/repository/SignerInformationRepository.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * eu-digital-green-certificates / dgca-verifier-service 4 | * --- 5 | * Copyright (C) 2021 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package eu.europa.ec.dgc.validation.repository; 22 | 23 | 24 | import eu.europa.ec.dgc.validation.entity.SignerInformationEntity; 25 | import eu.europa.ec.dgc.validation.restapi.dto.KidDto; 26 | import java.util.List; 27 | import java.util.Optional; 28 | import org.springframework.data.jpa.repository.JpaRepository; 29 | 30 | 31 | public interface SignerInformationRepository extends JpaRepository { 32 | 33 | Optional findFirstByIdIsNotNullOrderByIdAsc(); 34 | 35 | Optional findFirstByIdGreaterThanOrderByIdAsc(Long id); 36 | 37 | List findAllByKid(String kid); 38 | 39 | List findAllByOrderByIdAsc(); 40 | 41 | void deleteByKidNotIn(List kids); 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/token/AccessTokenParser.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.token; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import eu.europa.ec.dgc.validation.exception.DccException; 6 | import eu.europa.ec.dgc.validation.restapi.dto.AccessTokenPayload; 7 | import io.jsonwebtoken.Jwt; 8 | import io.jsonwebtoken.Jwts; 9 | import java.security.PublicKey; 10 | import org.apache.http.HttpStatus; 11 | import org.springframework.stereotype.Service; 12 | 13 | @Service 14 | public class AccessTokenParser { 15 | private final ObjectMapper objectMapper = new ObjectMapper(); 16 | 17 | /** 18 | * parse Token. 19 | * @param jwtCompact jwtCompact 20 | * @param publicKey publicKey 21 | * @return AccessTokenPayload 22 | */ 23 | public AccessTokenPayload parseToken(String jwtCompact, PublicKey publicKey) { 24 | Jwt token = Jwts.parser().setSigningKey(publicKey).parse(jwtCompact); 25 | try { 26 | String payloadJson = objectMapper.writeValueAsString(token.getBody()); 27 | return objectMapper.readValue(payloadJson, AccessTokenPayload.class); 28 | } catch (JsonProcessingException e) { 29 | throw new DccException("can not parse access token " + e.getMessage(), HttpStatus.SC_BAD_REQUEST); 30 | } 31 | } 32 | 33 | /** 34 | * extract payload. 35 | * @param jwtCompact jwtCompact 36 | * @return JWT 37 | */ 38 | public Jwt extractPayload(String jwtCompact) { 39 | String[] splitToken = jwtCompact.split("\\."); 40 | String unsignedToken = splitToken[0] + "." + splitToken[1] + "."; 41 | return Jwts.parser().parse(unsignedToken); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/utils/btp/CredentialStore.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.utils.btp; 2 | 3 | import java.net.URLEncoder; 4 | import java.nio.charset.StandardCharsets; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.context.annotation.Profile; 10 | import org.springframework.stereotype.Component; 11 | import org.springframework.web.client.RestTemplate; 12 | 13 | @Component 14 | @Profile("btp") 15 | public class CredentialStore { 16 | 17 | private static final Logger log = LoggerFactory.getLogger(CredentialStore.class); 18 | 19 | @Value("${sap.btp.credstore.url}") 20 | private String url; 21 | 22 | private final CredentialStoreCryptoUtil cryptoUtil; 23 | 24 | private final RestTemplate restTemplate; 25 | 26 | @Autowired 27 | public CredentialStore(CredentialStoreCryptoUtil cryptoUtil, RestTemplate restTemplate) { 28 | this.cryptoUtil = cryptoUtil; 29 | this.restTemplate = restTemplate; 30 | } 31 | 32 | /** 33 | * Return the key located under the given name in the credential store. 34 | * 35 | * @param name the name of the key 36 | * @return the key from the credential store 37 | */ 38 | public SapCredential getKeyByName(String name) { 39 | log.debug("Querying key with name '{}'.", name); 40 | String response = restTemplate.getForEntity(url + "/key?name=" + URLEncoder.encode(name, 41 | StandardCharsets.UTF_8), String.class).getBody(); 42 | return SapCredential.fromJson(cryptoUtil.decrypt(response)); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/DgcaValidationServiceApplication.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * eu-digital-green-certificates / dgca-validation-service 4 | * --- 5 | * Copyright (C) 2021 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package eu.europa.ec.dgc.validation; 22 | 23 | import eu.europa.ec.dgc.validation.config.DgcConfigProperties; 24 | import org.springframework.boot.SpringApplication; 25 | import org.springframework.boot.autoconfigure.SpringBootApplication; 26 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 27 | import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; 28 | 29 | 30 | /** 31 | * The Application class. 32 | */ 33 | @SpringBootApplication 34 | @EnableConfigurationProperties({DgcConfigProperties.class}) 35 | public class DgcaValidationServiceApplication extends SpringBootServletInitializer { 36 | 37 | /** 38 | * The main Method. 39 | * 40 | * @param args the args for the main method 41 | */ 42 | public static void main(String[] args) { 43 | SpringApplication.run(DgcaValidationServiceApplication.class, args); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/entity/ValueSetEntity.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * eu-digital-green-certificates / dgca-businessrule-service 4 | * --- 5 | * Copyright (C) 2021 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package eu.europa.ec.dgc.validation.entity; 22 | 23 | import javax.persistence.Column; 24 | import javax.persistence.Entity; 25 | import javax.persistence.Id; 26 | import javax.persistence.Lob; 27 | import javax.persistence.Table; 28 | import lombok.AllArgsConstructor; 29 | import lombok.Getter; 30 | import lombok.NoArgsConstructor; 31 | import lombok.Setter; 32 | 33 | @Getter 34 | @Setter 35 | @AllArgsConstructor 36 | @NoArgsConstructor 37 | @Entity 38 | @Table(name = "valuesets_vs") 39 | public class ValueSetEntity { 40 | /** 41 | * SHA-256 Thumbprint of the valueset (hex encoded). 42 | */ 43 | @Id 44 | @Column(name = "hash", nullable = false, length = 64) 45 | private String hash; 46 | 47 | @Column(name = "identifier_name") 48 | private String id; 49 | 50 | @Lob 51 | @Column(name = "raw_data", nullable = false) 52 | String rawData; 53 | 54 | } -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/restapi/dto/AccessTokenConditions.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.restapi.dto; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class AccessTokenConditions { 7 | /** 8 | * hash of the dcc. 9 | * Not applicable for Type 1,2 10 | */ 11 | private String hash; 12 | /** 13 | * selected language. 14 | */ 15 | private String lang; 16 | /** 17 | * ICOA 930 transliterated surname (Familienname). 18 | */ 19 | private String fnt; 20 | /** 21 | * ICOA 930 transliterated given name. 22 | */ 23 | private String gnt; 24 | /** 25 | * Date of birth. 26 | */ 27 | private String dob; 28 | /** 29 | * Contry of Arrival. 30 | */ 31 | private String coa; 32 | /** 33 | * Country of Departure. 34 | */ 35 | private String cod; 36 | /** 37 | * Region of Arrival ISO 3166-2 without Country. 38 | */ 39 | private String roa; 40 | /** 41 | * Region of Departure ISO 3166-2 without Country. 42 | */ 43 | private String rod; 44 | /** 45 | * Acceptable Type of DCC. 46 | */ 47 | private String[] type; 48 | /** 49 | * Optional category which shall be reflected in the validation by additional rules/logic. 50 | * if null, Standard Business Rule Check will apply. 51 | */ 52 | private String[] category; 53 | /** 54 | * Date where te DCC must be validateable. 55 | */ 56 | private String validationClock; 57 | /** 58 | * DCC must be valid from this date (ISO8601 with offset). 59 | */ 60 | private String validFrom; 61 | /** 62 | * DCC must be valid minimum to this date (ISO8601 with offset). 63 | */ 64 | private String validTo; 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/entity/ShedlockEntity.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * eu-digital-green-certificates / dgca-verifier-service 4 | * --- 5 | * Copyright (C) 2021 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package eu.europa.ec.dgc.validation.entity; 22 | 23 | import java.util.Date; 24 | import javax.persistence.Column; 25 | import javax.persistence.Entity; 26 | import javax.persistence.GeneratedValue; 27 | import javax.persistence.GenerationType; 28 | import javax.persistence.Id; 29 | import javax.persistence.Table; 30 | 31 | @Entity 32 | @Table(name = "shedlock_vs") 33 | public class ShedlockEntity { 34 | 35 | @Id 36 | @GeneratedValue(strategy = GenerationType.IDENTITY) 37 | @Column(name = "id") 38 | private Long id; 39 | 40 | @Column(name = "name", length = 64, nullable = false, unique = true) 41 | private String name; 42 | 43 | @Column(name = "lock_until", nullable = false) 44 | private Date lockUntil; 45 | 46 | @Column(name = "locked_at", nullable = false) 47 | private Date lockedAt; 48 | 49 | @Column(name = "locked_by", nullable = false) 50 | private String lockedBy; 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/token/AccessTokenBuilder.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.token; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import eu.europa.ec.dgc.validation.exception.DccException; 6 | import eu.europa.ec.dgc.validation.restapi.dto.AccessTokenPayload; 7 | import io.jsonwebtoken.JwtBuilder; 8 | import io.jsonwebtoken.Jwts; 9 | import io.jsonwebtoken.SignatureAlgorithm; 10 | import java.security.PrivateKey; 11 | 12 | /** 13 | * Builder for AccessToken. 14 | */ 15 | public class AccessTokenBuilder { 16 | private final JwtBuilder builder; 17 | private final ObjectMapper objectMapper; 18 | 19 | /** 20 | * contructor. 21 | */ 22 | public AccessTokenBuilder() { 23 | builder = Jwts.builder(); 24 | builder.setHeaderParam("typ", "JWT"); 25 | objectMapper = new ObjectMapper(); 26 | } 27 | 28 | /** 29 | * set payload. 30 | * @param accessTokenPayload accessTokenPayload 31 | * @return self 32 | */ 33 | public AccessTokenBuilder payload(AccessTokenPayload accessTokenPayload) { 34 | try { 35 | builder.setPayload(objectMapper.writeValueAsString(accessTokenPayload)); 36 | } catch (JsonProcessingException e) { 37 | throw new DccException("can not serialize accessTokenPayload", e); 38 | } 39 | return this; 40 | } 41 | 42 | /** 43 | * build the token. 44 | * @param privateKey privateKey 45 | * @param kid kid 46 | * @return jwt string 47 | */ 48 | public String build(PrivateKey privateKey, String kid) { 49 | return builder 50 | .setHeaderParam("kid", kid) 51 | .signWith(SignatureAlgorithm.ES256, privateKey) 52 | .compact(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/service/DccSign.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.service; 2 | 3 | import eu.europa.ec.dgc.validation.exception.DccException; 4 | import java.security.InvalidKeyException; 5 | import java.security.NoSuchAlgorithmException; 6 | import java.security.PrivateKey; 7 | import java.security.PublicKey; 8 | import java.security.Signature; 9 | import java.security.SignatureException; 10 | import java.util.Base64; 11 | import org.springframework.stereotype.Service; 12 | 13 | @Service 14 | public class DccSign { 15 | public static final String SIG_ALG = "SHA256withECDSA"; 16 | 17 | /** 18 | * sign dcc. 19 | * @param data data 20 | * @param privateKey privateKey 21 | * @return signature as base64 22 | */ 23 | public String signDcc(byte[] data, PrivateKey privateKey) { 24 | try { 25 | Signature signature = Signature.getInstance(SIG_ALG); 26 | signature.initSign(privateKey); 27 | signature.update(data); 28 | return Base64.getEncoder().encodeToString(signature.sign()); 29 | } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) { 30 | throw new DccException("can not sign dcc", e); 31 | } 32 | } 33 | 34 | /** 35 | * verify Signature. 36 | * @param data data 37 | * @param sig sig 38 | * @param publicKey publicKey 39 | * @return true if ok 40 | */ 41 | public boolean verifySignature(byte[] data, byte[] sig, PublicKey publicKey) { 42 | try { 43 | Signature signature = Signature.getInstance(SIG_ALG); 44 | signature.initVerify(publicKey); 45 | signature.update(data); 46 | return signature.verify(sig); 47 | } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) { 48 | throw new DccException("can not sign dcc", e); 49 | } 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /owasp/suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | see https://github.com/jeremylong/DependencyCheck/issues/1827> 5 | CVE-2018-1258 6 | 7 | 8 | see https://github.com/jeremylong/DependencyCheck/issues/2952 9 | CVE-2011-2732 10 | CVE-2011-2731 11 | CVE-2012-5055 12 | 13 | 14 | see https://tomcat.apache.org/security-9.html#Apache_Tomcat_9.x_vulnerabilities vulnerability is fixed in tomcat 9.0.38 15 | CVE-2020-13943 16 | 17 | 18 | spring-boot and spring are excluded from cfenv artifact. Related issues can be omitted. 19 | da214a6f44ee5811c97f3b53a6dda31edf25ac9e 20 | CVE-2016-9878 21 | CVE-2018-1270 22 | CVE-2018-1271 23 | CVE-2018-1272 24 | CVE-2020-5421 25 | 26 | 27 | Vulnerability impacts WebFlux apps only and can be ignored here. 28 | CVE-2021-22118 29 | 30 | 31 | This CVE matches incorrectly because of term redis within spring-data-redis package 32 | CVE-2021-32626 33 | CVE-2021-43797 34 | CVE-2019-16869 35 | CVE-2015-2156 36 | CVE-2021-37136 37 | CVE-2014-3488 38 | CVE-2021-37137 39 | CVE-2019-20445 40 | CVE-2019-20444 41 | CVE-2021-21295 42 | CVE-2021-21409 43 | CVE-2021-21290 44 | 45 | 46 | Feature is not enabled in tomcat 47 | CVE-2022-23181 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8080 3 | spring: 4 | application: 5 | name: dgca-validation-service 6 | datasource: 7 | driver-class-name: org.h2.Driver 8 | url: jdbc:h2:mem:dgc;DB_CLOSE_ON_EXIT=FALSE;DB_CLOSE_DELAY=-1; 9 | username: sa 10 | password: '' 11 | jpa: 12 | database-platform: org.hibernate.dialect.H2Dialect 13 | hibernate: 14 | ddl-auto: create 15 | liquibase: 16 | change-log: classpath:db/changelog.xml 17 | database-change-log-table: BR_CHANGELOG 18 | database-change-log-lock-table: BR_CHANGELOG_LOCK 19 | h2: 20 | console: 21 | enabled: true 22 | path: /h2-console 23 | task: 24 | scheduling: 25 | pool: 26 | size: 5 27 | management: 28 | endpoint: 29 | info: 30 | enabled: true 31 | health: 32 | enabled: true 33 | endpoints: 34 | enabled-by-default: false 35 | web: 36 | base-path: /management 37 | exposure: 38 | include: info,health 39 | info: 40 | name: ${spring.application.name} 41 | profiles: ${spring.profiles.active} 42 | springdoc: 43 | api-docs: 44 | path: /api/docs 45 | enabled: true 46 | swagger-ui: 47 | path: /swagger 48 | dgc: 49 | businessRulesDownload: 50 | timeInterval: 1800000 51 | lockLimit: 3600000 52 | certificatesDownloader: 53 | timeInterval: 1800000 54 | lockLimit: 3600000 55 | valueSetsDownload: 56 | timeInterval: 1800000 57 | lockLimit: 3600000 58 | serviceUrl: http://localhost:8080 59 | keyStoreFile: certs/dev-test.jks 60 | keyStorePassword: dcc 61 | privateKeyPassword: dcc 62 | disableStatusResult: true 63 | encAliases: ValidationServiceEncKey-1 64 | signAliases: ValidationServiceSignKey-1 65 | activeSignKey: ValidationServiceSignKey-1 66 | accessKeys: "bS8D2/Wz5tY=:MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIPrtYsW9+Juwp/mt7h8FJ3LgFRIUl2Vlmcl1DUm5gNHl0LnHIL4Jff6mg6yVhehdQiMvkhUtTvmFIUWONSJEnw==" 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8080 3 | spring: 4 | application: 5 | name: dgca-validation-service 6 | datasource: 7 | driver-class-name: org.h2.Driver 8 | url: jdbc:h2:mem:dgc;DB_CLOSE_ON_EXIT=FALSE;DB_CLOSE_DELAY=-1; 9 | username: sa 10 | password: '' 11 | jpa: 12 | database-platform: org.hibernate.dialect.H2Dialect 13 | hibernate: 14 | ddl-auto: create 15 | liquibase: 16 | change-log: classpath:db/changelog.xml 17 | database-change-log-table: BR_CHANGELOG 18 | database-change-log-lock-table: BR_CHANGELOG_LOCK 19 | h2: 20 | console: 21 | enabled: true 22 | path: /h2-console 23 | task: 24 | scheduling: 25 | pool: 26 | size: 5 27 | management: 28 | endpoint: 29 | info: 30 | enabled: true 31 | health: 32 | enabled: true 33 | endpoints: 34 | enabled-by-default: false 35 | web: 36 | base-path: /management 37 | exposure: 38 | include: info,health 39 | info: 40 | name: ${spring.application.name} 41 | profiles: ${spring.profiles.active} 42 | springdoc: 43 | api-docs: 44 | path: /api/docs 45 | enabled: true 46 | swagger-ui: 47 | path: /swagger 48 | dgc: 49 | businessRulesDownload: 50 | timeInterval: 1800000 51 | lockLimit: 3600000 52 | certificatesDownloader: 53 | timeInterval: 1800000 54 | lockLimit: 3600000 55 | valueSetsDownload: 56 | timeInterval: 1800000 57 | lockLimit: 3600000 58 | serviceUrl: http://localhost:8080 59 | keyStoreFile: certs/dev-test.jks 60 | keyStorePassword: dcc 61 | privateKeyPassword: dcc 62 | encAliases: ValidationServiceEncKey-1 63 | signAliases: ValidationServiceSignKey-1 64 | activeSignKey: ValidationServiceSignKey-1 65 | disableStatusResult: true 66 | accessKeys: overwrite_my_by_env 67 | # decoratorUrl: https://decorator.server/identity 68 | accessKeysRefresh: 69 | timeInterval: 86400000 70 | lockLimit: 3600000 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/config/ShedLockConfig.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * eu-digital-green-certificates / dgca-validation-service 4 | * --- 5 | * Copyright (C) 2021 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package eu.europa.ec.dgc.validation.config; 22 | 23 | import static net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider.Configuration.builder; 24 | 25 | import javax.sql.DataSource; 26 | import net.javacrumbs.shedlock.core.LockProvider; 27 | import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider; 28 | import org.springframework.context.annotation.Bean; 29 | import org.springframework.context.annotation.Configuration; 30 | import org.springframework.jdbc.core.JdbcTemplate; 31 | 32 | @Configuration 33 | public class ShedLockConfig { 34 | 35 | /** 36 | * Creates a LockProvider for ShedLock. 37 | * 38 | * @param dataSource JPA datasource 39 | * @return LockProvider 40 | */ 41 | @Bean 42 | public LockProvider lockProvider(DataSource dataSource) { 43 | return new JdbcTemplateLockProvider(builder() 44 | .withTableName("shedlock_vs") 45 | .withJdbcTemplate(new JdbcTemplate(dataSource)) 46 | .usingDbTime() 47 | .build() 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/entity/BusinessRuleEntity.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * eu-digital-green-certificates / dgca-businessrule-service 4 | * --- 5 | * Copyright (C) 2021 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package eu.europa.ec.dgc.validation.entity; 22 | 23 | import javax.persistence.Column; 24 | import javax.persistence.Entity; 25 | import javax.persistence.Id; 26 | import javax.persistence.Lob; 27 | import javax.persistence.Table; 28 | import lombok.AllArgsConstructor; 29 | import lombok.Getter; 30 | import lombok.NoArgsConstructor; 31 | import lombok.Setter; 32 | 33 | @Getter 34 | @Setter 35 | @AllArgsConstructor 36 | @NoArgsConstructor 37 | @Entity 38 | @Table(name = "business_rules_vs") 39 | public class BusinessRuleEntity { 40 | 41 | /** 42 | * SHA-256 Thumbprint of the rule (hex encoded). 43 | */ 44 | @Id 45 | @Column(name = "hash", nullable = false, length = 64) 46 | private String hash; 47 | 48 | @Column(name = "identifier_name", nullable = false) 49 | private String identifier; 50 | 51 | @Column(name = "version", nullable = false) 52 | String version; 53 | 54 | @Column(name = "country_code", nullable = false, length = 2) 55 | String country; 56 | 57 | @Lob 58 | @Column(name = "raw_data", nullable = false) 59 | String rawData; 60 | 61 | } -------------------------------------------------------------------------------- /src/test/java/eu/europa/ec/dgc/validation/JwtTest.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation; 2 | 3 | import eu.europa.ec.dgc.validation.restapi.dto.ValidationInitResponse; 4 | import eu.europa.ec.dgc.validation.restapi.dto.ValidationStatusResponse; 5 | import io.jsonwebtoken.Jwt; 6 | import io.jsonwebtoken.Jwts; 7 | import io.jsonwebtoken.SignatureAlgorithm; 8 | 9 | import java.security.KeyPair; 10 | import java.security.KeyPairGenerator; 11 | import java.util.ArrayList; 12 | import java.util.Base64; 13 | import java.util.Date; 14 | import java.util.List; 15 | 16 | import org.junit.jupiter.api.Test; 17 | 18 | class JwtTest { 19 | @Test 20 | void createResultTokenJwt() throws Exception { 21 | KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("EC"); 22 | keyPairGen.initialize(256); 23 | KeyPair keyPair = keyPairGen.generateKeyPair(); 24 | System.out.println("public: " + Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded())); 25 | System.out.println("private: " + Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded())); 26 | 27 | List results = new ArrayList<>(); 28 | ValidationStatusResponse.Result result = new ValidationStatusResponse.Result(); 29 | result.setResult(ValidationStatusResponse.Result.ResultType.OK); 30 | results.add(result); 31 | 32 | String jwtString = Jwts.builder().claim("test", "test") 33 | .setHeaderParam("kid", "kid") 34 | .setHeaderParam("typ", "JWT") 35 | .setIssuedAt(new Date()) 36 | .setIssuer("issuer") 37 | .setSubject("sub").claim("confirmation", "confirmation-jwt") 38 | .claim("results", results) 39 | .signWith(SignatureAlgorithm.ES256, keyPair.getPrivate()) 40 | .compact(); 41 | System.out.println(jwtString); 42 | 43 | Jwt token = Jwts.parser().setSigningKey(keyPair.getPublic()).parse(jwtString); 44 | 45 | System.out.println(token); 46 | 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/entity/SignerInformationEntity.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * eu-digital-green-certificates / dgca-verifier-service 4 | * --- 5 | * Copyright (C) 2021 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package eu.europa.ec.dgc.validation.entity; 22 | 23 | import java.time.ZonedDateTime; 24 | import javax.persistence.Column; 25 | import javax.persistence.Entity; 26 | import javax.persistence.GeneratedValue; 27 | import javax.persistence.GenerationType; 28 | import javax.persistence.Id; 29 | import javax.persistence.Table; 30 | import lombok.AllArgsConstructor; 31 | import lombok.Data; 32 | import lombok.Getter; 33 | import lombok.NoArgsConstructor; 34 | import lombok.Setter; 35 | 36 | @Data 37 | @Getter 38 | @Setter 39 | @Entity 40 | @Table(name = "signer_information_vs") 41 | @AllArgsConstructor 42 | @NoArgsConstructor 43 | public class SignerInformationEntity { 44 | 45 | @Id 46 | @GeneratedValue(strategy = GenerationType.IDENTITY) 47 | @Column(name = "id") 48 | private Long id; 49 | 50 | /** 51 | * Unique Identifier of the cert. 52 | */ 53 | @Column(name = "kid", length = 50, nullable = false) 54 | private String kid; 55 | 56 | /** 57 | * Timestamp of the Record creation. 58 | */ 59 | @Column(name = "created_at", nullable = false) 60 | private ZonedDateTime createdAt = ZonedDateTime.now(); 61 | 62 | /** 63 | * Base64 encoded certificate raw data. 64 | */ 65 | @Column(name = "raw_data", nullable = false, length = 4096) 66 | String rawData; 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/client/ValueSetsRestClient.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * eu-digital-green-certificates / dgca-validation-service 4 | * --- 5 | * Copyright (C) 2021 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package eu.europa.ec.dgc.validation.client; 22 | 23 | import eu.europa.ec.dgc.validation.client.dto.ValueSetResponseDto; 24 | import java.util.List; 25 | import org.springframework.cloud.openfeign.FeignClient; 26 | import org.springframework.context.annotation.Profile; 27 | import org.springframework.http.MediaType; 28 | import org.springframework.http.ResponseEntity; 29 | import org.springframework.web.bind.annotation.GetMapping; 30 | import org.springframework.web.bind.annotation.PathVariable; 31 | 32 | @Profile("pse") 33 | @FeignClient( 34 | name = "valueset-download-client", 35 | url = "${dgc.valueSetsDownload.endpoint}", 36 | configuration = RestClientConfig.class 37 | ) 38 | public interface ValueSetsRestClient { 39 | /** 40 | * Gets the value sets list from the business rule service. 41 | * 42 | * @return List of trustListItems 43 | */ 44 | @GetMapping(value = "/valuesets", produces = MediaType.APPLICATION_JSON_VALUE) 45 | ResponseEntity> getValueSetsList(); 46 | 47 | /** 48 | * Gets the raw data of a value set from the business rule service. 49 | * 50 | * @param hash The hash value of the value set. 51 | * @return Raw value set data 52 | */ 53 | @GetMapping(value = "/valuesets/{hash}", produces = MediaType.APPLICATION_JSON_VALUE) 54 | ResponseEntity getValueSetData(@PathVariable("hash") String hash); 55 | } 56 | 57 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/client/SignerCertificateRestClient.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * eu-digital-green-certificates / dgca-validation-service 4 | * --- 5 | * Copyright (C) 2021 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package eu.europa.ec.dgc.validation.client; 22 | 23 | import eu.europa.ec.dgc.validation.client.dto.ValueSetResponseDto; 24 | import java.util.List; 25 | import org.springframework.cloud.openfeign.FeignClient; 26 | import org.springframework.context.annotation.Profile; 27 | import org.springframework.http.MediaType; 28 | import org.springframework.http.ResponseEntity; 29 | import org.springframework.web.bind.annotation.GetMapping; 30 | import org.springframework.web.bind.annotation.PathVariable; 31 | import org.springframework.web.bind.annotation.RequestHeader; 32 | 33 | @Profile("pse") 34 | @FeignClient( 35 | name = "signer-certificate-download-client", 36 | url = "${dgc.certificatesDownloader.endpoint}", 37 | configuration = RestClientConfig.class 38 | ) 39 | public interface SignerCertificateRestClient { 40 | /** 41 | * Gets the kid list of all valid signer certificates. 42 | * 43 | * @return List of kids 44 | */ 45 | @GetMapping(value = "/signercertificateStatus", produces = MediaType.APPLICATION_JSON_VALUE) 46 | ResponseEntity> getKidList(); 47 | 48 | /** 49 | * Gets a signer certificate. 50 | * 51 | * @return signer certificate string 52 | */ 53 | @GetMapping(value = "/signercertificateUpdate", produces = MediaType.TEXT_PLAIN_VALUE) 54 | ResponseEntity getCertificate(@RequestHeader("X-RESUME-TOKEN") String resumeToken); 55 | 56 | } 57 | 58 | -------------------------------------------------------------------------------- /.github/workflows/ci-openapi.yml: -------------------------------------------------------------------------------- 1 | name: ci-openapi 2 | on: 3 | workflow_dispatch: 4 | release: 5 | types: 6 | - created 7 | jobs: 8 | release: 9 | runs-on: ubuntu-20.04 10 | steps: 11 | - uses: actions/setup-java@v2 12 | with: 13 | java-version: 11 14 | distribution: adopt 15 | - uses: actions/checkout@v2 16 | with: 17 | fetch-depth: 0 18 | - uses: actions/cache@v2 19 | with: 20 | path: | 21 | ~/.m2/repository 22 | key: ${{ runner.os }}-${{ hashFiles('**/pom.xml') }} 23 | - name: version 24 | run: >- 25 | APP_SHA=$(git rev-parse --short ${GITHUB_SHA}); 26 | APP_TAG=${GITHUB_REF/refs\/tags\/} 27 | APP_VERSION=${APP_TAG}; 28 | echo "APP_SHA=${APP_SHA}" >> ${GITHUB_ENV}; 29 | echo "APP_TAG=${APP_TAG}" >> ${GITHUB_ENV}; 30 | echo "APP_VERSION=${APP_VERSION}" >> ${GITHUB_ENV}; 31 | - name: mvn 32 | run: >- 33 | mvn versions:set 34 | --batch-mode 35 | --file ./pom.xml 36 | --settings ./settings.xml 37 | --define newVersion="${APP_VERSION}"; 38 | mvn clean verify 39 | --batch-mode 40 | --file ./pom.xml 41 | --settings ./settings.xml 42 | --define app.packages.username="${APP_PACKAGES_USERNAME}" 43 | --define app.packages.password="${APP_PACKAGES_PASSWORD}"; 44 | env: 45 | APP_PACKAGES_USERNAME: ${{ github.actor }} 46 | APP_PACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }} 47 | - name: Upload Artifact 48 | uses: actions/upload-artifact@v2 49 | with: 50 | name: openapi.json 51 | path: target/openapi.json 52 | - name: Checkout OpenApi Doc Branch 53 | uses: actions/checkout@v2 54 | with: 55 | ref: openapi-doc 56 | - name: Delete existing openapi.json 57 | run: rm -f openapi.json 58 | - name: Download openapi.json 59 | uses: actions/download-artifact@v2 60 | with: 61 | name: openapi.json 62 | - name: Commit and Push changes 63 | run: | 64 | git config user.name github-actions 65 | git config user.email github-actions@github.com 66 | git commit -a --allow-empty -m "Update OpenAPI JSON" 67 | git push 68 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/client/RestClientConfig.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * eu-digital-green-certificates / dgca-validation-service 4 | * --- 5 | * Copyright (C) 2021 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | 22 | package eu.europa.ec.dgc.validation.client; 23 | 24 | import feign.Client; 25 | import feign.Logger; 26 | import feign.httpclient.ApacheHttpClient; 27 | import java.io.IOException; 28 | import java.security.KeyManagementException; 29 | import java.security.KeyStoreException; 30 | import java.security.NoSuchAlgorithmException; 31 | import java.security.UnrecoverableKeyException; 32 | import java.security.cert.CertificateException; 33 | import lombok.RequiredArgsConstructor; 34 | import org.apache.http.impl.client.HttpClientBuilder; 35 | import org.springframework.cloud.openfeign.EnableFeignClients; 36 | import org.springframework.context.annotation.Bean; 37 | import org.springframework.context.annotation.Configuration; 38 | import org.springframework.context.annotation.Profile; 39 | 40 | @Profile("pse") 41 | @Configuration 42 | @RequiredArgsConstructor 43 | @EnableFeignClients 44 | public class RestClientConfig { 45 | 46 | 47 | /** 48 | * Feign Client for connection to business rules service. 49 | * 50 | * @return Instance of HttpClient 51 | */ 52 | @Bean 53 | public Client client() throws 54 | UnrecoverableKeyException, CertificateException, 55 | IOException, NoSuchAlgorithmException, 56 | KeyStoreException, KeyManagementException { 57 | 58 | return new ApacheHttpClient(HttpClientBuilder.create() 59 | .build()); 60 | } 61 | 62 | @Bean 63 | Logger.Level feignLoggerLevel() { 64 | return Logger.Level.BASIC; 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/config/DccVerificationConfig.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.config; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.JsonNode; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; 7 | import dgca.verifier.app.decoder.JsonSchemaKt; 8 | import dgca.verifier.app.engine.AffectedFieldsDataRetriever; 9 | import dgca.verifier.app.engine.CertLogicEngine; 10 | import dgca.verifier.app.engine.DefaultAffectedFieldsDataRetriever; 11 | import dgca.verifier.app.engine.DefaultCertLogicEngine; 12 | import dgca.verifier.app.engine.DefaultJsonLogicValidator; 13 | import dgca.verifier.app.engine.JsonLogicValidator; 14 | import lombok.RequiredArgsConstructor; 15 | import org.springframework.context.annotation.Bean; 16 | import org.springframework.context.annotation.Configuration; 17 | import org.springframework.context.support.ResourceBundleMessageSource; 18 | 19 | @Configuration 20 | @RequiredArgsConstructor 21 | public class DccVerificationConfig { 22 | @Bean 23 | ObjectMapper objectMapper() { 24 | ObjectMapper objectMapper = new ObjectMapper(); 25 | objectMapper.registerModule(new JavaTimeModule()); 26 | return objectMapper; 27 | } 28 | 29 | @Bean 30 | AffectedFieldsDataRetriever affectedFieldsDataRetriever(ObjectMapper objectMapper) throws JsonProcessingException { 31 | JsonNode jsonNode = objectMapper.readTree(JsonSchemaKt.JSON_SCHEMA_V1); 32 | return new DefaultAffectedFieldsDataRetriever(jsonNode, objectMapper); 33 | } 34 | 35 | @Bean 36 | JsonLogicValidator jsonLogicValidator() { 37 | return new DefaultJsonLogicValidator(); 38 | } 39 | 40 | @Bean 41 | CertLogicEngine certLogicEngine(AffectedFieldsDataRetriever affectedFieldsDataRetriever, 42 | JsonLogicValidator jsonLogicValidator) { 43 | return new DefaultCertLogicEngine(affectedFieldsDataRetriever, jsonLogicValidator); 44 | } 45 | 46 | @Bean 47 | ResourceBundleMessageSource messageSource() { 48 | ResourceBundleMessageSource source = new ResourceBundleMessageSource(); 49 | source.setBasenames("messages/dcc"); 50 | source.setUseCodeAsDefaultMessage(true); 51 | return source; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/test/java/eu/europa/ec/dgc/validation/DccEncryptionExampleTest.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation; 2 | 3 | import eu.europa.ec.dgc.validation.cryptschemas.EncryptedData; 4 | import eu.europa.ec.dgc.validation.cryptschemas.RsaOaepWithSha256AesCbc; 5 | import eu.europa.ec.dgc.validation.cryptschemas.RsaOaepWithSha256AesGcm; 6 | 7 | import java.security.KeyPair; 8 | import java.security.KeyPairGenerator; 9 | import java.security.Security; 10 | import java.util.Random; 11 | 12 | import org.bouncycastle.jce.provider.BouncyCastleProvider; 13 | import org.junit.jupiter.api.Test; 14 | 15 | import static org.junit.jupiter.api.Assertions.assertArrayEquals; 16 | 17 | class DccEncryptionExampleTest { 18 | RsaOaepWithSha256AesCbc dccCryptService = new RsaOaepWithSha256AesCbc(); 19 | RsaOaepWithSha256AesGcm dccCryptService2 = new RsaOaepWithSha256AesGcm(); 20 | 21 | @Test 22 | void dccEncryptionCBC() throws Exception { 23 | Security.addProvider(new BouncyCastleProvider()); 24 | 25 | KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA"); 26 | keyPairGen.initialize(3072); 27 | KeyPair keyPair = keyPairGen.generateKeyPair(); 28 | 29 | Random random = new Random(); 30 | byte[] data = new byte[2000]; 31 | random.nextBytes(data); 32 | byte[] iv = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 33 | EncryptedData encryptedData = dccCryptService.encryptData(data, keyPair.getPublic(), iv); 34 | byte[] dataDecrypted = dccCryptService.decryptData(encryptedData, keyPair.getPrivate(), iv); 35 | 36 | assertArrayEquals(data, dataDecrypted); 37 | } 38 | 39 | @Test 40 | void dccEncryptionGCM() throws Exception { 41 | Security.addProvider(new BouncyCastleProvider()); 42 | 43 | KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA"); 44 | keyPairGen.initialize(3072); 45 | KeyPair keyPair = keyPairGen.generateKeyPair(); 46 | 47 | Random random = new Random(); 48 | byte[] data = new byte[2000]; 49 | random.nextBytes(data); 50 | byte[] iv = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 51 | EncryptedData encryptedData = dccCryptService2.encryptData(data, keyPair.getPublic(), iv); 52 | byte[] dataDecrypted = dccCryptService2.decryptData(encryptedData, keyPair.getPrivate(), iv); 53 | 54 | assertArrayEquals(data, dataDecrypted); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/client/BusinessRulesRestClient.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * eu-digital-green-certificates / dgca-validation-service 4 | * --- 5 | * Copyright (C) 2021 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package eu.europa.ec.dgc.validation.client; 22 | 23 | import eu.europa.ec.dgc.validation.client.dto.RulesResponseDto; 24 | import java.util.List; 25 | import org.springframework.cloud.openfeign.FeignClient; 26 | import org.springframework.context.annotation.Profile; 27 | import org.springframework.http.MediaType; 28 | import org.springframework.http.ResponseEntity; 29 | import org.springframework.web.bind.annotation.GetMapping; 30 | import org.springframework.web.bind.annotation.PathVariable; 31 | 32 | @Profile("pse") 33 | @FeignClient( 34 | name = "business-download-client", 35 | url = "${dgc.businessRulesDownload.endpoint}", 36 | configuration = RestClientConfig.class 37 | ) 38 | public interface BusinessRulesRestClient { 39 | /** 40 | * Gets the business rules index from the business rule service. 41 | * 42 | * @return List of business rule index 43 | */ 44 | @GetMapping(value = "/rules", produces = MediaType.APPLICATION_JSON_VALUE) 45 | ResponseEntity> getBusinessRulesList(); 46 | 47 | /** 48 | * Gets the raw data of a business rule from the business rule service. 49 | * 50 | * @param country The country of the rule 51 | * @param hash The hash value of the rule 52 | * @return Raw data of the rule 53 | */ 54 | @GetMapping(value = "/rules/{country}/{hash}", produces = MediaType.APPLICATION_JSON_VALUE) 55 | ResponseEntity getBusinessRulesItem(@PathVariable("country") String country, 56 | @PathVariable("hash") String hash); 57 | } 58 | 59 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/restapi/controller/IdentityController.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.restapi.controller; 2 | 3 | import eu.europa.ec.dgc.validation.restapi.dto.IdentityResponse; 4 | import eu.europa.ec.dgc.validation.service.IdentityService; 5 | import io.swagger.v3.oas.annotations.Operation; 6 | import io.swagger.v3.oas.annotations.responses.ApiResponse; 7 | import io.swagger.v3.oas.annotations.responses.ApiResponses; 8 | import lombok.AllArgsConstructor; 9 | import org.springframework.http.CacheControl; 10 | import org.springframework.http.ResponseEntity; 11 | import org.springframework.web.bind.annotation.GetMapping; 12 | import org.springframework.web.bind.annotation.PathVariable; 13 | import org.springframework.web.bind.annotation.RequestMapping; 14 | import org.springframework.web.bind.annotation.RestController; 15 | 16 | @RestController 17 | @RequestMapping("/") 18 | @AllArgsConstructor 19 | public class IdentityController { 20 | private final IdentityService identityService; 21 | 22 | private static final String PATH_ALL = "/identity"; 23 | private static final String PATH_ELEMENT = "/identity/{element}"; 24 | private static final String PATH_ELEMENT_TYPE = "/identity/{element}/{type}"; 25 | 26 | /** 27 | * get identity document. 28 | * @param element null or validationMethod 29 | * @param type null or key type 30 | * @return identity document 31 | */ 32 | @Operation( 33 | summary = "The identity endpoint provides the validation service identity document which contains the used" 34 | + "encryption schemas and the associated keys/verification methods.", 35 | description = "The identity document is downloaded by the wallet apps to encrypt the DCC with the right" 36 | + "crypto material." 37 | ) 38 | @ApiResponses(value = { 39 | @ApiResponse(responseCode = "200", description = "OK")}) 40 | @GetMapping(value = { PATH_ALL, PATH_ELEMENT, PATH_ELEMENT_TYPE }, produces = "application/json") 41 | public ResponseEntity identity( 42 | @PathVariable(name = "element", required = false) final String element, 43 | @PathVariable(name = "type", required = false) final String type) { 44 | return ResponseEntity.ok() 45 | .cacheControl(CacheControl.noCache()) 46 | .body(identityService.getIdentity(element, type)); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/config/DgcConfigProperties.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * eu-digital-green-certificates / dgca-validation-service 4 | * --- 5 | * Copyright (C) 2021 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package eu.europa.ec.dgc.validation.config; 22 | 23 | import java.time.Duration; 24 | import java.time.temporal.ChronoUnit; 25 | import lombok.Getter; 26 | import lombok.Setter; 27 | import org.springframework.beans.factory.annotation.Value; 28 | import org.springframework.boot.context.properties.ConfigurationProperties; 29 | import org.springframework.boot.convert.DurationUnit; 30 | 31 | @Getter 32 | @Setter 33 | @ConfigurationProperties("dgc") 34 | public class DgcConfigProperties { 35 | 36 | private final GatewayDownload businessRulesDownload = new GatewayDownload(); 37 | 38 | private final GatewayDownload valueSetsDownload = new GatewayDownload(); 39 | 40 | private final GatewayDownload certificatesDownloader = new GatewayDownload(); 41 | 42 | private final GatewayDownload accessKeysRefresh = new GatewayDownload(); 43 | 44 | @Getter 45 | @Setter 46 | public static class GatewayDownload { 47 | private Integer timeInterval; 48 | private Integer lockLimit; 49 | } 50 | 51 | private long validationExpire = 3600; 52 | private long confirmationExpire = 86400; 53 | 54 | private String serviceUrl; 55 | 56 | private String keyStoreFile; 57 | private String keyStorePassword; 58 | private String privateKeyPassword; 59 | private boolean disableStatusResult; 60 | @Value("${dgc.encAliases}") 61 | private String[] encAliases; 62 | @Value("${dgc.signAliases}") 63 | private String[] signAliases; 64 | private String activeSignKey; 65 | @Value("${dgc.accessKeys}") 66 | private String[] accessKeys; 67 | private String decoratorUrl; 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/config/OpenApiConfig.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * eu-digital-green-certificates / dgca-validation-service 4 | * --- 5 | * Copyright (C) 2021 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package eu.europa.ec.dgc.validation.config; 22 | 23 | import io.swagger.v3.oas.models.OpenAPI; 24 | import io.swagger.v3.oas.models.info.Info; 25 | import io.swagger.v3.oas.models.info.License; 26 | import java.util.Optional; 27 | import lombok.Generated; 28 | import lombok.RequiredArgsConstructor; 29 | import org.springframework.boot.info.BuildProperties; 30 | import org.springframework.context.annotation.Bean; 31 | import org.springframework.context.annotation.Configuration; 32 | 33 | @Generated 34 | @Configuration 35 | @RequiredArgsConstructor 36 | public class OpenApiConfig { 37 | 38 | private final Optional buildProperties; 39 | 40 | /** 41 | * Configure the OpenApi bean with title and version. 42 | * 43 | * @return the OpenApi bean. 44 | */ 45 | @Bean 46 | public OpenAPI openApi() { 47 | String version; 48 | if (buildProperties.isPresent()) { 49 | version = buildProperties.get().getVersion(); 50 | } else { 51 | // build properties is not available if starting from IDE without running mvn before (so fake this) 52 | version = "dev"; 53 | } 54 | return new OpenAPI() 55 | .info(new Info() 56 | .title("EU Digital COVID Certificate Validation Service") 57 | .description("The API provides functionalities for validating " 58 | + "EU digital COVID certificates.") 59 | .version(version) 60 | .license(new License() 61 | .name("Apache 2.0") 62 | .url("https://www.apache.org/licenses/LICENSE-2.0"))); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /.github/workflows/ci-main.yml: -------------------------------------------------------------------------------- 1 | name: ci-main 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | build: 8 | runs-on: ubuntu-20.04 9 | steps: 10 | - uses: actions/setup-java@v2 11 | with: 12 | java-version: 11 13 | distribution: adopt 14 | - uses: actions/checkout@v2 15 | with: 16 | fetch-depth: 0 17 | - uses: actions/cache@v2 18 | with: 19 | path: | 20 | ~/.m2/repository 21 | key: ${{ runner.os }}-${{ hashFiles('**/pom.xml') }} 22 | - name: version 23 | run: |- 24 | APP_SHA=$(git rev-parse --short ${GITHUB_SHA}) 25 | APP_LATEST_REV=$(git rev-list --tags --max-count=1) 26 | APP_LATEST_TAG=$(git describe --tags ${APP_LATEST_REV} 2> /dev/null || echo 0.0.0) 27 | echo "APP_VERSION=${APP_LATEST_TAG}-${APP_SHA}" >> ${GITHUB_ENV} 28 | - name: mvn 29 | run: |- 30 | mvn versions:set \ 31 | --batch-mode \ 32 | --file ./pom.xml \ 33 | --settings ./settings.xml \ 34 | --define newVersion="${APP_VERSION}" 35 | mvn clean verify \ 36 | --batch-mode \ 37 | --file ./pom.xml \ 38 | --settings ./settings.xml \ 39 | --define app.packages.username="${APP_PACKAGES_USERNAME}" \ 40 | --define app.packages.password="${APP_PACKAGES_PASSWORD}" 41 | env: 42 | APP_PACKAGES_USERNAME: ${{ github.actor }} 43 | APP_PACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }} 44 | - name: docker 45 | run: |- 46 | echo "${APP_PACKAGES_PASSWORD}" | docker login "${APP_PACKAGES_URL}" \ 47 | --username "${APP_PACKAGES_USERNAME}" \ 48 | --password-stdin 49 | docker build . \ 50 | --file ./docker/CloudFoundry \ 51 | --tag "${APP_PACKAGES_URL}:${APP_VERSION}" 52 | docker push "${APP_PACKAGES_URL}:${APP_VERSION}" 53 | env: 54 | APP_PACKAGES_URL: docker.pkg.github.com/${{ github.repository }}/dgca-validation-service-cloudfoundry 55 | APP_PACKAGES_USERNAME: ${{ github.actor }} 56 | APP_PACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }} 57 | - name: docker 58 | run: |- 59 | echo "${APP_PACKAGES_PASSWORD}" | docker login "${APP_PACKAGES_URL}" \ 60 | --username "${APP_PACKAGES_USERNAME}" \ 61 | --password-stdin 62 | docker build . \ 63 | --file ./docker/Native \ 64 | --tag "${APP_PACKAGES_URL}:${APP_VERSION}" 65 | docker push "${APP_PACKAGES_URL}:${APP_VERSION}" 66 | env: 67 | APP_PACKAGES_URL: docker.pkg.github.com/${{ github.repository }}/dgca-validation-service 68 | APP_PACKAGES_USERNAME: ${{ github.actor }} 69 | APP_PACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }} 70 | -------------------------------------------------------------------------------- /src/test/java/eu/europa/ec/dgc/validation/OpenApiTest.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation;/*- 2 | * ---license-start 3 | * eu-digital-green-certificates / dgca-businessrule-service 4 | * --- 5 | * Copyright (C) 2021 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | 22 | import eu.europa.ec.dgc.gateway.connector.DgcGatewayCountryListDownloadConnector; 23 | import eu.europa.ec.dgc.gateway.connector.DgcGatewayValidationRuleDownloadConnector; 24 | import eu.europa.ec.dgc.gateway.connector.DgcGatewayValueSetDownloadConnector; 25 | import java.io.BufferedInputStream; 26 | import java.io.FileOutputStream; 27 | import java.net.URL; 28 | import lombok.extern.slf4j.Slf4j; 29 | import org.junit.jupiter.api.Assertions; 30 | import org.junit.jupiter.api.Test; 31 | import org.springframework.boot.test.context.SpringBootTest; 32 | import org.springframework.boot.test.mock.mockito.MockBean; 33 | 34 | @Slf4j 35 | @SpringBootTest( 36 | properties = { 37 | "server.port=8080", 38 | "springdoc.api-docs.enabled=true", 39 | "springdoc.api-docs.path=/openapi" 40 | }, 41 | webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT 42 | ) 43 | class OpenApiTest { 44 | 45 | @MockBean 46 | DgcGatewayValidationRuleDownloadConnector dgcGatewayValidationRuleDownloadConnector; 47 | 48 | @MockBean 49 | DgcGatewayValueSetDownloadConnector dgcGatewayValueSetDownloadConnector; 50 | 51 | @MockBean 52 | DgcGatewayCountryListDownloadConnector dgcGatewayCountryListDownloadConnector; 53 | 54 | @Test 55 | void apiDocs() { 56 | try (BufferedInputStream in = new BufferedInputStream(new URL("http://localhost:8080/openapi").openStream()); 57 | FileOutputStream out = new FileOutputStream("target/openapi.json")) { 58 | byte[] buffer = new byte[1024]; 59 | int read; 60 | while ((read = in.read(buffer, 0, buffer.length)) != -1) { 61 | out.write(buffer, 0, read); 62 | } 63 | } catch (Exception e) { 64 | log.error("Failed to download openapi specification.", e); 65 | Assertions.fail(); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/service/DccCryptService.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.service; 2 | 3 | import eu.europa.ec.dgc.validation.cryptschemas.CryptSchema; 4 | import eu.europa.ec.dgc.validation.cryptschemas.EncryptedData; 5 | import eu.europa.ec.dgc.validation.cryptschemas.RsaOaepWithSha256AesCbc; 6 | import eu.europa.ec.dgc.validation.cryptschemas.RsaOaepWithSha256AesGcm; 7 | import eu.europa.ec.dgc.validation.exception.DccException; 8 | import java.security.PrivateKey; 9 | import java.security.PublicKey; 10 | import java.util.ArrayList; 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | import javax.annotation.PostConstruct; 15 | import org.springframework.stereotype.Service; 16 | 17 | @Service 18 | public class DccCryptService { 19 | private Map cryptSchemaMap; 20 | 21 | /** 22 | * init schemas. 23 | */ 24 | @PostConstruct 25 | public void initSchemas() { 26 | cryptSchemaMap = new HashMap<>(); 27 | CryptSchema cryptSchema = new RsaOaepWithSha256AesCbc(); 28 | CryptSchema cryptSchema2 = new RsaOaepWithSha256AesGcm(); 29 | cryptSchemaMap.put(cryptSchema.getEncSchema(), cryptSchema); 30 | cryptSchemaMap.put(cryptSchema2.getEncSchema(), cryptSchema2); 31 | } 32 | 33 | /** 34 | * Returns all available Crypto Schemes. 35 | * @return List of Crypto Schemes. 36 | */ 37 | public List getCryptSchemes() { 38 | return new ArrayList(cryptSchemaMap.keySet()); 39 | } 40 | 41 | /** 42 | * encrypt Data. 43 | * @param data data 44 | * @param publicKey publicKey 45 | * @param encSchema encSchema 46 | * @param iv iv 47 | * @return EncryptedData 48 | */ 49 | public EncryptedData encryptData(byte[] data, PublicKey publicKey, String encSchema, byte[] iv) { 50 | CryptSchema cryptSchema = cryptSchemaMap.get(encSchema); 51 | if (cryptSchema != null) { 52 | return cryptSchema.encryptData(data, publicKey, iv); 53 | } else { 54 | throw new DccException("encryption schema not supported " + encSchema); 55 | } 56 | } 57 | 58 | /** 59 | * decrypt Data. 60 | * @param encryptedData encryptedData 61 | * @param privateKey privateKey 62 | * @param encSchema encSchema 63 | * @param iv iv 64 | * @return decrypted data 65 | */ 66 | public byte[] decryptData(EncryptedData encryptedData, PrivateKey privateKey, String encSchema, byte[] iv) { 67 | CryptSchema cryptSchema = cryptSchemaMap.get(encSchema); 68 | if (cryptSchema != null) { 69 | return cryptSchema.decryptData(encryptedData, privateKey, iv); 70 | } else { 71 | throw new DccException("encryption schema not supported " + encSchema); 72 | } 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/service/impl/DgcgValueSetCache.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.service.impl; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import dgca.verifier.app.engine.data.source.remote.valuesets.ValueSetRemote; 6 | import eu.europa.ec.dgc.validation.entity.ValueSetEntity; 7 | import eu.europa.ec.dgc.validation.exception.DccException; 8 | import eu.europa.ec.dgc.validation.restapi.dto.ValueSetListItemDto; 9 | import eu.europa.ec.dgc.validation.service.ValueSetCache; 10 | import eu.europa.ec.dgc.validation.service.ValueSetService; 11 | import java.time.Duration; 12 | import java.time.LocalTime; 13 | import java.time.temporal.TemporalAmount; 14 | import java.util.ArrayList; 15 | import java.util.HashMap; 16 | import java.util.Iterator; 17 | import java.util.List; 18 | import java.util.Map; 19 | import lombok.RequiredArgsConstructor; 20 | import org.springframework.stereotype.Service; 21 | 22 | @Service 23 | @RequiredArgsConstructor 24 | public class DgcgValueSetCache implements ValueSetCache { 25 | private final ObjectMapper objectMapper; 26 | private final ValueSetService valueSetService; 27 | private Map> valueSets; 28 | private LocalTime expireTime; 29 | 30 | private static final TemporalAmount expireSpan = Duration.ofMinutes(15); 31 | 32 | /** 33 | * provide Value Sets. 34 | * @return value sets 35 | */ 36 | public Map> provideValueSets() { 37 | if (valueSets == null || expireTime == null || expireTime.isAfter(LocalTime.now())) { 38 | valueSets = getValueSets(); 39 | expireTime = LocalTime.now().plus(expireSpan); 40 | } 41 | return valueSets; 42 | } 43 | 44 | /** 45 | * get Value Sets. 46 | * @return value sets 47 | */ 48 | public Map> getValueSets() { 49 | Map> valueSets = new HashMap<>(); 50 | for (ValueSetListItemDto valueSetListItemDto : valueSetService.getValueSetsList()) { 51 | ValueSetEntity valueSetEntity = valueSetService.getValueSetByHash(valueSetListItemDto.getHash()); 52 | try { 53 | ValueSetRemote valueSet = objectMapper.readValue(valueSetEntity.getRawData(), ValueSetRemote.class); 54 | List ids = new ArrayList<>(); 55 | for (Iterator it = valueSet.getValueSetValues().fieldNames(); it.hasNext(); ) { 56 | String fieldName = it.next(); 57 | ids.add(fieldName); 58 | } 59 | valueSets.put(valueSetEntity.getId(), ids); 60 | } catch (JsonProcessingException e) { 61 | throw new DccException("can not parse value list", e); 62 | } 63 | } 64 | return valueSets; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/service/ResultCallbackService.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.service; 2 | 3 | import java.io.IOException; 4 | import java.net.MalformedURLException; 5 | import java.net.URI; 6 | import java.net.URL; 7 | import java.net.http.HttpClient; 8 | import java.net.http.HttpRequest; 9 | import java.net.http.HttpResponse; 10 | import java.util.concurrent.ExecutorService; 11 | import java.util.concurrent.Executors; 12 | import java.util.concurrent.TimeUnit; 13 | import javax.annotation.PreDestroy; 14 | import lombok.RequiredArgsConstructor; 15 | import lombok.extern.slf4j.Slf4j; 16 | import org.springframework.stereotype.Component; 17 | 18 | @Slf4j 19 | @RequiredArgsConstructor 20 | @Component 21 | public class ResultCallbackService { 22 | private final HttpClient client = HttpClient.newHttpClient(); 23 | private final ExecutorService executor = Executors.newFixedThreadPool(5); 24 | 25 | /** 26 | * schedule callback. 27 | * @param callbackUrl url 28 | * @param resultToken jwt token 29 | */ 30 | public void scheduleCallback(String callbackUrl, String resultToken) { 31 | log.debug("schedule callback"); 32 | try { 33 | URL url = new URL(callbackUrl); 34 | if (url.getProtocol().equals("http") || url.getProtocol().equals("https")) { 35 | executor.submit(() -> { 36 | makeCallback(callbackUrl, resultToken); 37 | }); 38 | } else { 39 | log.warn("unsupported callback protocol: " + callbackUrl); 40 | } 41 | } catch (MalformedURLException e) { 42 | log.warn("malformed callback url: {}", callbackUrl); 43 | } 44 | } 45 | 46 | /** 47 | * terminate. 48 | */ 49 | @PreDestroy 50 | public void terminateExecutor() { 51 | try { 52 | executor.awaitTermination(10, TimeUnit.SECONDS); 53 | } catch (InterruptedException e) { 54 | log.warn("can not shut down callback executor", e); 55 | } 56 | } 57 | 58 | private void makeCallback(String callbackUrl, String resultToken) { 59 | HttpRequest request = HttpRequest.newBuilder() 60 | .uri(URI.create(callbackUrl)) 61 | .header("Content-Type", "application/jwt") 62 | .header("X-Version","1.0") 63 | .PUT(HttpRequest.BodyPublishers.ofString(resultToken)) 64 | .build(); 65 | try { 66 | HttpResponse response = null; 67 | response = client.send(request, HttpResponse.BodyHandlers.ofString()); 68 | if (response.statusCode() != 200) { 69 | log.info("callback not successful to: {} status: ", callbackUrl, response.statusCode()); 70 | } 71 | } catch (IOException | InterruptedException e) { 72 | log.warn("can not call callback to: {} ", callbackUrl); 73 | } 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/test/java/eu/europa/ec/dgc/validation/token/AccessTokenParserTest.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.token; 2 | 3 | import eu.europa.ec.dgc.validation.restapi.dto.AccessTokenConditions; 4 | import eu.europa.ec.dgc.validation.restapi.dto.AccessTokenPayload; 5 | import eu.europa.ec.dgc.validation.restapi.dto.AccessTokenType; 6 | import eu.europa.ec.dgc.validation.service.ValidationServiceTest; 7 | 8 | import java.security.PrivateKey; 9 | import java.security.PublicKey; 10 | import java.time.Instant; 11 | 12 | import lombok.extern.slf4j.Slf4j; 13 | import org.junit.jupiter.api.Test; 14 | import org.springframework.boot.test.context.SpringBootTest; 15 | 16 | import static org.junit.jupiter.api.Assertions.*; 17 | 18 | class AccessTokenParserTest { 19 | public static final String KID_JUNIT = "kid-junit"; 20 | AccessTokenParser accessTokenParser = new AccessTokenParser(); 21 | AccessTokenBuilder accessTokenBuilder = new AccessTokenBuilder(); 22 | 23 | @Test 24 | void testAccessTokenParser() throws Exception { 25 | AccessTokenPayload accessTokenPayload = new AccessTokenPayload(); 26 | accessTokenPayload.setSub("sub"); 27 | accessTokenPayload.setIss("iss"); 28 | accessTokenPayload.setType(AccessTokenType.Cryptographic.intValue()); 29 | accessTokenPayload.setVersion("1.0"); 30 | accessTokenPayload.setJti("jti"); 31 | accessTokenPayload.setIat(Instant.now().getEpochSecond()); 32 | accessTokenPayload.setExp(Instant.now().getEpochSecond() + 60 * 60); 33 | 34 | AccessTokenConditions accessTokenConditions = new AccessTokenConditions(); 35 | accessTokenConditions.setHash("hash"); 36 | accessTokenConditions.setLang("en-en"); 37 | accessTokenConditions.setFnt("FNT"); 38 | accessTokenConditions.setGnt("GNT"); 39 | accessTokenConditions.setDob("12-12-2021"); 40 | accessTokenConditions.setCoa("NL"); 41 | accessTokenConditions.setCod("DE"); 42 | accessTokenConditions.setRoa("AW"); 43 | accessTokenConditions.setRod("BW"); 44 | accessTokenConditions.setType(new String[]{"v"}); 45 | accessTokenConditions.setValidationClock("2021-01-29T12:00:00+01:00"); 46 | accessTokenConditions.setValidFrom("2021-01-29T12:00:00+01:00"); 47 | accessTokenConditions.setValidTo("2021-01-30T12:00:00+01:00"); 48 | 49 | 50 | accessTokenPayload.setConditions(accessTokenConditions); 51 | 52 | PrivateKey privateKey = ValidationServiceTest.parsePrivateKey(ValidationServiceTest.EC_PRIVATE_KEY); 53 | String accessTokenCompact = accessTokenBuilder.payload(accessTokenPayload).build(privateKey, KID_JUNIT); 54 | 55 | System.out.println(accessTokenCompact); 56 | 57 | PublicKey publicKey = ValidationServiceTest.parsePublicKey(ValidationServiceTest.EC_PUBLIC_KEY); 58 | 59 | AccessTokenPayload accessTokenParsed = accessTokenParser.parseToken(accessTokenCompact, publicKey); 60 | assertNotNull(accessTokenParsed); 61 | assertNotNull(accessTokenParsed.getConditions()); 62 | } 63 | } -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/service/SignerCertificateDownloadServiceGatewayImpl.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * eu-digital-green-certificates / dgca-verifier-service 4 | * --- 5 | * Copyright (C) 2021 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package eu.europa.ec.dgc.validation.service; 22 | 23 | import eu.europa.ec.dgc.gateway.connector.DgcGatewayDownloadConnector; 24 | import eu.europa.ec.dgc.gateway.connector.model.TrustListItem; 25 | import java.util.List; 26 | import lombok.RequiredArgsConstructor; 27 | import lombok.extern.slf4j.Slf4j; 28 | import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; 29 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 30 | import org.springframework.context.annotation.Profile; 31 | import org.springframework.scheduling.annotation.Scheduled; 32 | import org.springframework.stereotype.Component; 33 | 34 | /** 35 | * A service to download the signer certificates from the digital green certificate gateway. 36 | */ 37 | @Slf4j 38 | @RequiredArgsConstructor 39 | @Component 40 | @ConditionalOnProperty("dgc.gateway.connector.enabled") 41 | @Profile("!btp") 42 | public class SignerCertificateDownloadServiceGatewayImpl implements SignerCertificateDownloadService { 43 | 44 | private final DgcGatewayDownloadConnector dgcGatewayConnector; 45 | private final SignerInformationService signerInformationService; 46 | 47 | @Override 48 | @Scheduled(fixedDelayString = "${dgc.certificatesDownloader.timeInterval}") 49 | @SchedulerLock(name = "SignerCertificateDownloadService_downloadCertificates", lockAtLeastFor = "PT0S", 50 | lockAtMostFor = "${dgc.certificatesDownloader.lockLimit}") 51 | public void downloadCertificates() { 52 | log.info("Certificates download started"); 53 | 54 | List trustedCerts = dgcGatewayConnector.getTrustedCertificates(); 55 | 56 | if (!trustedCerts.isEmpty()) { 57 | signerInformationService.updateTrustedCertsList(trustedCerts); 58 | } else { 59 | log.warn("The download of the Certificates seems to fail, as the download connector " 60 | + "returns an empty list.-> No data was changed."); 61 | } 62 | 63 | signerInformationService.updateTrustedCertsList(trustedCerts); 64 | 65 | log.info("Certificates download finished"); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/test/resources/valuesets/vaccines-covid-19-names.json: -------------------------------------------------------------------------------- 1 | { 2 | "valueSetId": "vaccines-covid-19-names", 3 | "valueSetDate": "2021-05-07", 4 | "valueSetValues": { 5 | "EU/1/20/1528": { 6 | "display": "Comirnaty", 7 | "lang": "en", 8 | "active": true, 9 | "system": "https://ec.europa.eu/health/documents/community-register/html/", 10 | "version": "" 11 | }, 12 | "EU/1/20/1507": { 13 | "display": "Spikevax (previously COVID-19 Vaccine Moderna)", 14 | "lang": "en", 15 | "active": true, 16 | "system": "https://ec.europa.eu/health/documents/community-register/html/", 17 | "version": "" 18 | }, 19 | "EU/1/21/1529": { 20 | "display": "Vaxzevria", 21 | "lang": "en", 22 | "active": true, 23 | "system": "https://ec.europa.eu/health/documents/community-register/html/", 24 | "version": "" 25 | }, 26 | "EU/1/20/1525": { 27 | "display": "COVID-19 Vaccine Janssen", 28 | "lang": "en", 29 | "active": true, 30 | "system": "https://ec.europa.eu/health/documents/community-register/html/", 31 | "version": "" 32 | }, 33 | "CVnCoV": { 34 | "display": "CVnCoV", 35 | "lang": "en", 36 | "active": true, 37 | "system": "http://ec.europa.eu/temp/vaccineproductname", 38 | "version": "1.0" 39 | }, 40 | "Sputnik-V": { 41 | "display": "Sputnik-V", 42 | "lang": "en", 43 | "active": true, 44 | "system": "http://ec.europa.eu/temp/vaccineproductname", 45 | "version": "1.0" 46 | }, 47 | "Convidecia": { 48 | "display": "Convidecia", 49 | "lang": "en", 50 | "active": true, 51 | "system": "http://ec.europa.eu/temp/vaccineproductname", 52 | "version": "1.0" 53 | }, 54 | "EpiVacCorona": { 55 | "display": "EpiVacCorona", 56 | "lang": "en", 57 | "active": true, 58 | "system": "http://ec.europa.eu/temp/vaccineproductname", 59 | "version": "1.0" 60 | }, 61 | "BBIBP-CorV": { 62 | "display": "BBIBP-CorV", 63 | "lang": "en", 64 | "active": true, 65 | "system": "http://ec.europa.eu/temp/vaccineproductname", 66 | "version": "1.0" 67 | }, 68 | "Inactivated-SARS-CoV-2-Vero-Cell": { 69 | "display": "Inactivated SARS-CoV-2 (Vero Cell)", 70 | "lang": "en", 71 | "active": true, 72 | "system": "http://ec.europa.eu/temp/vaccineproductname", 73 | "version": "1.0" 74 | }, 75 | "CoronaVac": { 76 | "display": "CoronaVac", 77 | "lang": "en", 78 | "active": true, 79 | "system": "http://ec.europa.eu/temp/vaccineproductname", 80 | "version": "1.0" 81 | }, 82 | "Covaxin": { 83 | "display": "Covaxin (also known as BBV152 A, B, C)", 84 | "lang": "en", 85 | "active": true, 86 | "system": "http://ec.europa.eu/temp/vaccineproductname", 87 | "version": "1.0" 88 | }, 89 | "Covishield": { 90 | "display": "Covishield (ChAdOx1_nCoV-19)", 91 | "lang": "en", 92 | "active": true, 93 | "system": "http://ec.europa.eu/temp/vaccineproductname", 94 | "version": "1.0" 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/utils/btp/CredentialStoreCryptoUtil.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.utils.btp; 2 | 3 | import com.nimbusds.jose.JOSEException; 4 | import com.nimbusds.jose.JWEObject; 5 | import com.nimbusds.jose.Payload; 6 | import com.nimbusds.jose.crypto.RSADecrypter; 7 | import java.security.KeyFactory; 8 | import java.security.NoSuchAlgorithmException; 9 | import java.security.PrivateKey; 10 | import java.security.PublicKey; 11 | import java.security.spec.InvalidKeySpecException; 12 | import java.security.spec.PKCS8EncodedKeySpec; 13 | import java.security.spec.X509EncodedKeySpec; 14 | import java.text.ParseException; 15 | import java.util.Base64; 16 | import javax.annotation.PostConstruct; 17 | import lombok.extern.slf4j.Slf4j; 18 | import org.apache.commons.lang3.NotImplementedException; 19 | import org.springframework.beans.factory.annotation.Value; 20 | import org.springframework.context.annotation.Profile; 21 | import org.springframework.stereotype.Component; 22 | 23 | @Slf4j 24 | @Component 25 | @Profile("btp") 26 | public class CredentialStoreCryptoUtil { 27 | 28 | @Value("${sap.btp.credstore.clientPrivateKey}") 29 | private String clientPrivateKeyBase64; 30 | 31 | @Value("${sap.btp.credstore.serverPublicKey}") 32 | private String serverPublicKeyBase64; 33 | 34 | @Value("${sap.btp.credstore.encrypted}") 35 | private boolean encryptionEnabled; 36 | 37 | private PrivateKey ownPrivateKey; 38 | 39 | private PublicKey serverPublicKey; 40 | 41 | @PostConstruct 42 | private void prepare() throws NoSuchAlgorithmException, InvalidKeySpecException { 43 | if (!encryptionEnabled) { 44 | return; 45 | } 46 | 47 | KeyFactory rsaKeyFactory = KeyFactory.getInstance("RSA"); 48 | PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.getDecoder() 49 | .decode(clientPrivateKeyBase64)); 50 | this.ownPrivateKey = rsaKeyFactory.generatePrivate(pkcs8EncodedKeySpec); 51 | 52 | X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.getDecoder() 53 | .decode(serverPublicKeyBase64)); 54 | this.serverPublicKey = rsaKeyFactory.generatePublic(x509EncodedKeySpec); 55 | } 56 | 57 | protected void encrypt() { 58 | throw new NotImplementedException("Encryption is still to be implemented yet."); 59 | } 60 | 61 | protected String decrypt(String jweResponse) { 62 | if (!encryptionEnabled) { 63 | return jweResponse; 64 | } 65 | 66 | JWEObject jweObject; 67 | 68 | try { 69 | RSADecrypter rsaDecrypter = new RSADecrypter(ownPrivateKey); 70 | jweObject = JWEObject.parse(jweResponse); 71 | jweObject.decrypt(rsaDecrypter); 72 | 73 | Payload payload = jweObject.getPayload(); 74 | return payload.toString(); 75 | } catch (ParseException | JOSEException e) { 76 | log.error("Failed to parse JWE response: {}.", e.getMessage()); 77 | throw new RuntimeException(e); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/service/impl/DgcgRulesCache.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.service.impl; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import dgca.verifier.app.engine.data.Rule; 6 | import dgca.verifier.app.engine.data.source.remote.rules.RuleRemote; 7 | import dgca.verifier.app.engine.data.source.remote.rules.RuleRemoteMapperKt; 8 | import eu.europa.ec.dgc.validation.entity.BusinessRuleEntity; 9 | import eu.europa.ec.dgc.validation.exception.DccException; 10 | import eu.europa.ec.dgc.validation.restapi.dto.BusinessRuleListItemDto; 11 | import eu.europa.ec.dgc.validation.service.BusinessRuleService; 12 | import eu.europa.ec.dgc.validation.service.RulesCache; 13 | import java.time.Duration; 14 | import java.time.LocalTime; 15 | import java.time.temporal.TemporalAmount; 16 | import java.util.ArrayList; 17 | import java.util.HashMap; 18 | import java.util.List; 19 | import java.util.Map; 20 | import lombok.RequiredArgsConstructor; 21 | import org.jetbrains.annotations.NotNull; 22 | import org.springframework.stereotype.Service; 23 | 24 | @Service 25 | @RequiredArgsConstructor 26 | public class DgcgRulesCache implements RulesCache { 27 | private final BusinessRuleService businessRuleService; 28 | private final ObjectMapper objectMapper; 29 | private Map> rulesMap = new HashMap<>(); 30 | private LocalTime expireTime; 31 | 32 | private static final TemporalAmount expireSpan = Duration.ofMinutes(15); 33 | 34 | /** 35 | * provide rules. 36 | * @param countryOfArrival countryOfArrival 37 | * @param issuerCountry issuerCountry 38 | * @return list of rules 39 | */ 40 | public List provideRules(String countryOfArrival, String issuerCountry) { 41 | List rules = rulesMap.get(countryOfArrival); 42 | if (rules == null || expireTime == null || expireTime.isAfter(LocalTime.now())) { 43 | rules = getRules(countryOfArrival, issuerCountry); 44 | rulesMap.put(countryOfArrival, rules); 45 | expireTime = LocalTime.now().plus(expireSpan); 46 | } 47 | return rules; 48 | } 49 | 50 | @NotNull 51 | private List getRules(String countryOfArrival, String issuerCountry) { 52 | List rulesDto = businessRuleService.getBusinessRulesListForCountry( 53 | countryOfArrival, issuerCountry); 54 | List rules = new ArrayList<>(); 55 | for (BusinessRuleListItemDto ruleDto : rulesDto) { 56 | BusinessRuleEntity ruleData = businessRuleService.getBusinessRuleByCountryAndHash( 57 | ruleDto.getCountry(), ruleDto.getHash()); 58 | if (ruleData != null) { 59 | try { 60 | RuleRemote ruleRemote = objectMapper.readValue(ruleData.getRawData(), RuleRemote.class); 61 | rules.add(RuleRemoteMapperKt.toRule(ruleRemote)); 62 | } catch (JsonProcessingException e) { 63 | throw new DccException("can not parse rule", e); 64 | } 65 | } 66 | } 67 | return rules; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/service/ValueSetsDownloadServiceGatwayImpl.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * eu-digital-green-certificates / dgca-businessrule-service 4 | * --- 5 | * Copyright (C) 2021 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package eu.europa.ec.dgc.validation.service; 22 | 23 | import eu.europa.ec.dgc.gateway.connector.DgcGatewayValueSetDownloadConnector; 24 | import eu.europa.ec.dgc.validation.model.ValueSetItem; 25 | import java.security.NoSuchAlgorithmException; 26 | import java.util.List; 27 | import lombok.RequiredArgsConstructor; 28 | import lombok.extern.slf4j.Slf4j; 29 | import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; 30 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 31 | import org.springframework.context.annotation.Profile; 32 | import org.springframework.scheduling.annotation.Scheduled; 33 | import org.springframework.stereotype.Component; 34 | 35 | 36 | /** 37 | * A service to download the valuesets, business rules and country list from the digital covid certificate gateway. 38 | */ 39 | @Slf4j 40 | @RequiredArgsConstructor 41 | @Component 42 | @ConditionalOnProperty("dgc.gateway.connector.enabled") 43 | @Profile("!btp") 44 | public class ValueSetsDownloadServiceGatwayImpl implements ValueSetsDownloadService { 45 | 46 | private final DgcGatewayValueSetDownloadConnector dgcValueSetConnector; 47 | 48 | private final ValueSetService valueSetService; 49 | 50 | @Override 51 | @Scheduled(fixedDelayString = "${dgc.valueSetsDownload.timeInterval}") 52 | @SchedulerLock(name = "GatewayDataDownloadService_downloadValueSets", lockAtLeastFor = "PT0S", 53 | lockAtMostFor = "${dgc.valueSetsDownload.lockLimit}") 54 | public void downloadValueSets() { 55 | List valueSetItems; 56 | log.info("Valuesets download started"); 57 | 58 | try { 59 | valueSetItems = valueSetService.createValueSetItemListFromMap(dgcValueSetConnector.getValueSets()); 60 | } catch (NoSuchAlgorithmException e) { 61 | log.error("Failed to hash business rules on download.", e); 62 | return; 63 | } 64 | 65 | if (!valueSetItems.isEmpty()) { 66 | valueSetService.updateValueSets(valueSetItems); 67 | } else { 68 | log.warn("The download of the value sets seems to fail, as the download connector " 69 | + "returns an empty value sets list.-> No data was changed."); 70 | } 71 | 72 | log.info("Valuesets download finished"); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/service/BusinessRulesDownloadServiceGatewayImpl.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * eu-digital-green-certificates / dgca-businessrule-service 4 | * --- 5 | * Copyright (C) 2021 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package eu.europa.ec.dgc.validation.service; 22 | 23 | 24 | import eu.europa.ec.dgc.gateway.connector.DgcGatewayValidationRuleDownloadConnector; 25 | import eu.europa.ec.dgc.validation.model.BusinessRuleItem; 26 | import java.security.NoSuchAlgorithmException; 27 | import java.util.List; 28 | import lombok.RequiredArgsConstructor; 29 | import lombok.extern.slf4j.Slf4j; 30 | import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; 31 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 32 | import org.springframework.context.annotation.Profile; 33 | import org.springframework.scheduling.annotation.Scheduled; 34 | import org.springframework.stereotype.Component; 35 | 36 | 37 | /** 38 | * A service to download the valuesets, business rules and country list from the digital covid certificate gateway. 39 | */ 40 | @Slf4j 41 | @RequiredArgsConstructor 42 | @Component 43 | @ConditionalOnProperty("dgc.gateway.connector.enabled") 44 | @Profile("!btp") 45 | public class BusinessRulesDownloadServiceGatewayImpl implements BusinessRulesDownloadService { 46 | 47 | private final DgcGatewayValidationRuleDownloadConnector dgcRuleConnector; 48 | 49 | private final BusinessRuleService businessRuleService; 50 | 51 | @Override 52 | @Scheduled(fixedDelayString = "${dgc.businessRulesDownload.timeInterval}") 53 | @SchedulerLock(name = "GatewayDataDownloadService_downloadBusinessRules", lockAtLeastFor = "PT0S", 54 | lockAtMostFor = "${dgc.businessRulesDownload.lockLimit}") 55 | public void downloadBusinessRules() { 56 | List ruleItems; 57 | 58 | log.info("Business rules download started"); 59 | 60 | try { 61 | ruleItems = businessRuleService.createBusinessRuleItemList(dgcRuleConnector.getValidationRules().flat()); 62 | } catch (NoSuchAlgorithmException e) { 63 | log.error("Failed to hash business rules on download.", e); 64 | return; 65 | } 66 | 67 | if (!ruleItems.isEmpty()) { 68 | businessRuleService.updateBusinessRules(ruleItems); 69 | } else { 70 | log.warn("The download of the business rules seems to fail, as the download connector " 71 | + "returns an empty business rules list.-> No data was changed."); 72 | } 73 | 74 | log.info("Business rules finished"); 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/service/impl/FixAccessTokenKeyProvider.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.service.impl; 2 | 3 | import eu.europa.ec.dgc.validation.config.DgcConfigProperties; 4 | import eu.europa.ec.dgc.validation.exception.DccException; 5 | import eu.europa.ec.dgc.validation.service.AccessTokenKeyProvider; 6 | import java.security.KeyFactory; 7 | import java.security.NoSuchAlgorithmException; 8 | import java.security.PublicKey; 9 | import java.security.spec.InvalidKeySpecException; 10 | import java.security.spec.X509EncodedKeySpec; 11 | import java.util.Base64; 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | import javax.annotation.PostConstruct; 15 | import lombok.RequiredArgsConstructor; 16 | import lombok.extern.slf4j.Slf4j; 17 | import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; 18 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 19 | import org.springframework.stereotype.Service; 20 | 21 | @Service 22 | @RequiredArgsConstructor 23 | @Slf4j 24 | @ConditionalOnProperty(prefix = "dgc", name = "decoratorUrl", matchIfMissing = true, havingValue = "fix") 25 | public class FixAccessTokenKeyProvider implements AccessTokenKeyProvider { 26 | private final Map publicKeys = new HashMap<>(); 27 | private static final String UNSET_KEYS_VALUE = "overwrite_me_by_env"; 28 | private final DgcConfigProperties dgcConfigProperties; 29 | 30 | /** 31 | * load Keys. 32 | * @throws NoSuchAlgorithmException NoSuchAlgorithmException 33 | * @throws InvalidKeySpecException InvalidKeySpecException 34 | */ 35 | @PostConstruct 36 | public void loadKeys() throws NoSuchAlgorithmException, InvalidKeySpecException { 37 | String[] keys = dgcConfigProperties.getAccessKeys(); 38 | KeyFactory kf = KeyFactory.getInstance("EC"); 39 | if (keys == null || UNSET_KEYS_VALUE.equals(keys[0])) { 40 | throw new IllegalArgumentException("please set env variable DGC_ACCESSKEYS for access keys " 41 | + "'kid1:publicKey1,kid2:publicKey2'"); 42 | } else { 43 | for (String key : keys) { 44 | String[] keysSplit = key.split(":"); 45 | if (keysSplit.length % 2 != 0) { 46 | throw new IllegalArgumentException("wrong format for access keys env variable: " 47 | + "DGC_ACCESSKEYS, expect: 'kid1:publicKey1'"); 48 | } 49 | for (int i = 0;i < keysSplit.length;i += 2) { 50 | String kid = keysSplit[i]; 51 | byte[] keyBytes = Base64.getDecoder().decode(keysSplit[i + 1]); 52 | X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes); 53 | PublicKey publicKey = kf.generatePublic(spec); 54 | publicKeys.put(kid,publicKey); 55 | log.info("access key with kid={} was registered",kid); 56 | } 57 | } 58 | } 59 | } 60 | 61 | @Override 62 | public PublicKey getPublicKey(String kid) { 63 | PublicKey publicKey = publicKeys.get(kid); 64 | if (publicKey == null) { 65 | throw new DccException("can not find access key with kid: " + kid); 66 | } 67 | return publicKey; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/test/resources/valuesets/vaccines-covid-19-auth-holders.json: -------------------------------------------------------------------------------- 1 | { 2 | "valueSetId": "vaccines-covid-19-auth-holders", 3 | "valueSetDate": "2021-05-07", 4 | "valueSetValues": { 5 | "ORG-100001699": { 6 | "display": "AstraZeneca AB", 7 | "lang": "en", 8 | "active": true, 9 | "system": "https://spor.ema.europa.eu/v1/organisations", 10 | "version": "" 11 | }, 12 | "ORG-100030215": { 13 | "display": "Biontech Manufacturing GmbH", 14 | "lang": "en", 15 | "active": true, 16 | "system": "https://spor.ema.europa.eu/v1/organisations", 17 | "version": "" 18 | }, 19 | "ORG-100001417": { 20 | "display": "Janssen-Cilag International", 21 | "lang": "en", 22 | "active": true, 23 | "system": "https://spor.ema.europa.eu/v1/organisations", 24 | "version": "" 25 | }, 26 | "ORG-100031184": { 27 | "display": "Moderna Biotech Spain S.L.", 28 | "lang": "en", 29 | "active": true, 30 | "system": "https://spor.ema.europa.eu/v1/organisations", 31 | "version": "" 32 | }, 33 | "ORG-100006270": { 34 | "display": "Curevac AG", 35 | "lang": "en", 36 | "active": true, 37 | "system": "https://spor.ema.europa.eu/v1/organisations", 38 | "version": "" 39 | }, 40 | "ORG-100013793": { 41 | "display": "CanSino Biologics", 42 | "lang": "en", 43 | "active": true, 44 | "system": "https://spor.ema.europa.eu/v1/organisations", 45 | "version": "" 46 | }, 47 | "ORG-100020693": { 48 | "display": "China Sinopharm International Corp. - Beijing location", 49 | "lang": "en", 50 | "active": true, 51 | "system": "https://spor.ema.europa.eu/v1/organisations", 52 | "version": "" 53 | }, 54 | "ORG-100010771": { 55 | "display": "Sinopharm Weiqida Europe Pharmaceutical s.r.o. - Prague location", 56 | "lang": "en", 57 | "active": true, 58 | "system": "https://spor.ema.europa.eu/v1/organisations", 59 | "version": "" 60 | }, 61 | "ORG-100024420": { 62 | "display": "Sinopharm Zhijun (Shenzhen) Pharmaceutical Co. Ltd. - Shenzhen location", 63 | "lang": "en", 64 | "active": true, 65 | "system": "https://spor.ema.europa.eu/v1/organisations", 66 | "version": "" 67 | }, 68 | "ORG-100032020": { 69 | "display": "Novavax CZ AS", 70 | "lang": "en", 71 | "active": true, 72 | "system": "https://spor.ema.europa.eu/v1/organisations", 73 | "version": "" 74 | }, 75 | "Gamaleya-Research-Institute": { 76 | "display": "Gamaleya Research Institute", 77 | "lang": "en", 78 | "active": true, 79 | "system": "http://ec.europa.eu/temp/vaccinemanufacturer", 80 | "version": "1.0" 81 | }, 82 | "Vector-Institute": { 83 | "display": "Vector Institute", 84 | "lang": "en", 85 | "active": true, 86 | "system": "http://ec.europa.eu/temp/vaccinemanufacturer", 87 | "version": "1.0" 88 | }, 89 | "Sinovac-Biotech": { 90 | "display": "Sinovac Biotech", 91 | "lang": "en", 92 | "active": true, 93 | "system": "http://ec.europa.eu/temp/vaccinemanufacturer", 94 | "version": "1.0" 95 | }, 96 | "Bharat-Biotech": { 97 | "display": "Bharat Biotech", 98 | "lang": "en", 99 | "active": true, 100 | "system": "http://ec.europa.eu/temp/vaccinemanufacturer", 101 | "version": "1.0" 102 | }, 103 | "ORG-100001981": { 104 | "display": "Serum Institute Of India Private Limited", 105 | "lang": "en", 106 | "active": true, 107 | "system": "https://spor.ema.europa.eu/v1/organisations", 108 | "version": "" 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/restapi/controller/DccProvisioningController.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.restapi.controller; 2 | 3 | 4 | import eu.europa.ec.dgc.validation.config.DgcConfigProperties; 5 | import eu.europa.ec.dgc.validation.restapi.dto.AccessTokenPayload; 6 | import eu.europa.ec.dgc.validation.restapi.dto.DccValidationRequest; 7 | import eu.europa.ec.dgc.validation.service.ValidationService; 8 | import io.swagger.v3.oas.annotations.Operation; 9 | import io.swagger.v3.oas.annotations.responses.ApiResponse; 10 | import io.swagger.v3.oas.annotations.responses.ApiResponses; 11 | import javax.validation.Valid; 12 | import lombok.AllArgsConstructor; 13 | import org.springframework.http.HttpStatus; 14 | import org.springframework.http.MediaType; 15 | import org.springframework.http.ResponseEntity; 16 | import org.springframework.web.bind.annotation.PathVariable; 17 | import org.springframework.web.bind.annotation.PostMapping; 18 | import org.springframework.web.bind.annotation.RequestBody; 19 | import org.springframework.web.bind.annotation.RequestHeader; 20 | import org.springframework.web.bind.annotation.RequestMapping; 21 | import org.springframework.web.bind.annotation.RestController; 22 | 23 | @RestController 24 | @RequestMapping("/") 25 | @AllArgsConstructor 26 | public class DccProvisioningController { 27 | private final ValidationService validationService; 28 | private final DgcConfigProperties dgcConfigProperties; 29 | 30 | /** 31 | * init Validation. 32 | * @param dccValidationRequest dccValidationRequest 33 | * @param accessToken accessToken 34 | * @param subject subject 35 | * @param version version 36 | * @return ResponseEntity 37 | */ 38 | @Operation( 39 | summary = "The provision endpoint is the public endpoint where DCCs can be provided for a subject. The " 40 | + "endpoint receives the encrypted DCC, validates it and creates the result for the subject.", 41 | description = "The provision endpoint is the public endpoint where DCCs can be provided for a subject. The " 42 | + "endpoint receives the encrypted DCC, validates it and creates the result for the subject." 43 | ) 44 | @ApiResponses(value = { 45 | @ApiResponse(responseCode = "200", description = "OK"), 46 | @ApiResponse(responseCode = "401", description = "Unauthorized, if no access token are provided"), 47 | @ApiResponse(responseCode = "400", description = "Bad Request, content of the provide data is malformed"), 48 | @ApiResponse(responseCode = "410", description = "Gone, Subject does not exists any more"), 49 | @ApiResponse(responseCode = "422", description = "Unprocessable Entity. Wrong Signature of the Subject," 50 | + " Wrong Encryption or any other problem with the encoding")}) 51 | @PostMapping(value = "/validate/{subject}", consumes = MediaType.APPLICATION_JSON_VALUE, 52 | produces = "application/jwt") 53 | public ResponseEntity validateDcc( 54 | @Valid @RequestBody DccValidationRequest dccValidationRequest, 55 | @RequestHeader("Authorization") String accessToken, 56 | @PathVariable String subject, 57 | @RequestHeader("X-Version") String version) { 58 | ResponseEntity result; 59 | 60 | AccessTokenPayload accessTokenPayload = 61 | validationService.validateAccessToken( 62 | dgcConfigProperties.getServiceUrl() + "/validate/" + subject, subject, accessToken); 63 | 64 | if (accessTokenPayload == null) { 65 | return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); 66 | } 67 | 68 | String resultToken = validationService.validate(dccValidationRequest, accessTokenPayload); 69 | if (resultToken != null) { 70 | result = ResponseEntity.ok(resultToken); 71 | } else { 72 | result = ResponseEntity.status(HttpStatus.GONE).build(); 73 | } 74 | return result; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/test/java/eu/europa/ec/dgc/validation/CertlogicTest.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; 6 | import dgca.verifier.app.engine.AffectedFieldsDataRetriever; 7 | import dgca.verifier.app.engine.CertLogicEngine; 8 | import dgca.verifier.app.engine.DefaultCertLogicEngine; 9 | import dgca.verifier.app.engine.DefaultJsonLogicValidator; 10 | import dgca.verifier.app.engine.JsonLogicValidator; 11 | import dgca.verifier.app.engine.ValidationResult; 12 | import dgca.verifier.app.engine.data.CertificateType; 13 | import dgca.verifier.app.engine.data.ExternalParameter; 14 | import dgca.verifier.app.engine.data.Rule; 15 | import dgca.verifier.app.engine.data.source.remote.rules.RuleRemote; 16 | import dgca.verifier.app.engine.data.source.remote.rules.RuleRemoteMapperKt; 17 | 18 | import java.io.File; 19 | import java.io.IOException; 20 | import java.nio.file.Files; 21 | import java.nio.file.Path; 22 | import java.time.ZonedDateTime; 23 | import java.util.ArrayList; 24 | import java.util.Collections; 25 | import java.util.List; 26 | 27 | import org.junit.jupiter.api.Test; 28 | 29 | import static eu.ehn.dcc.certlogic.CertlogicKt.evaluate; 30 | import static org.mockito.ArgumentMatchers.any; 31 | import static org.mockito.Mockito.doReturn; 32 | import static org.mockito.Mockito.mock; 33 | 34 | class CertlogicTest { 35 | private String STANDARD_VERSION = "1.0.0"; 36 | private ObjectMapper objectMapper = new ObjectMapper(); 37 | 38 | @Test 39 | public void testCertlogic() throws Exception { 40 | System.out.println("hallo"); 41 | 42 | String ruleJson = "{\n" + 43 | " \"if\": [{\n" + 44 | " \"var\": \"a\"\n" + 45 | " }, {\n" + 46 | " \">\": [3,{\"var\": \"a\"}]\n" + 47 | " },\n" + 48 | " false\n" + 49 | " ]\n" + 50 | "}"; 51 | String dataJson = "{\"a\": 5}"; 52 | 53 | JsonNode data = objectMapper.readTree(dataJson); 54 | JsonNode rule = objectMapper.readTree(ruleJson); 55 | JsonNode res = evaluate(rule, data); 56 | System.out.println(objectMapper.writeValueAsString(res)); 57 | } 58 | 59 | @Test 60 | public void certLogicEngine() throws Exception { 61 | objectMapper.registerModule(new JavaTimeModule()); 62 | 63 | String payload = Files.readString(Path.of("src/test/resources/hcert.json")); 64 | RuleRemote ruleRemote = objectMapper.readValue(new File("src/test/resources/rule.json"), RuleRemote.class); 65 | 66 | JsonLogicValidator jsonLogicValidator = new DefaultJsonLogicValidator(); 67 | AffectedFieldsDataRetriever affectedFieldsDataRetriever = mock(AffectedFieldsDataRetriever.class); 68 | //doReturn(true).when(jsonLogicValidator).isDataValid(any(), any()); 69 | doReturn("").when(affectedFieldsDataRetriever).getAffectedFieldsData(any(), any(), any()); 70 | 71 | CertLogicEngine certLogicEngine = new DefaultCertLogicEngine(affectedFieldsDataRetriever, jsonLogicValidator); 72 | 73 | List rules = new ArrayList<>(); 74 | rules.add(RuleRemoteMapperKt.toRule(ruleRemote)); 75 | 76 | ExternalParameter externalParameter = new ExternalParameter( 77 | ZonedDateTime.now(), 78 | Collections.emptyMap(), 79 | "de", 80 | ZonedDateTime.now(), 81 | ZonedDateTime.now(), 82 | "de", 83 | "kid", 84 | "" 85 | ); 86 | 87 | List validationResult = certLogicEngine.validate(CertificateType.VACCINATION, STANDARD_VERSION, rules, externalParameter, payload); 88 | System.out.println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(validationResult)); 89 | 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/config/ErrorHandler.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * EU Digital Green Certificate Issuance Service / dgca-issuance-service 4 | * --- 5 | * Copyright (C) 2021 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package eu.europa.ec.dgc.validation.config; 22 | 23 | import eu.europa.ec.dgc.gateway.connector.dto.ProblemReportDto; 24 | import eu.europa.ec.dgc.validation.exception.DccException; 25 | import io.jsonwebtoken.JwtException; 26 | import lombok.RequiredArgsConstructor; 27 | import lombok.extern.slf4j.Slf4j; 28 | import org.springframework.context.annotation.Configuration; 29 | import org.springframework.http.HttpHeaders; 30 | import org.springframework.http.HttpStatus; 31 | import org.springframework.http.MediaType; 32 | import org.springframework.http.ResponseEntity; 33 | import org.springframework.validation.FieldError; 34 | import org.springframework.web.bind.MethodArgumentNotValidException; 35 | import org.springframework.web.bind.annotation.ControllerAdvice; 36 | import org.springframework.web.bind.annotation.ExceptionHandler; 37 | import org.springframework.web.context.request.WebRequest; 38 | import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; 39 | 40 | @ControllerAdvice 41 | @Configuration 42 | @RequiredArgsConstructor 43 | @Slf4j 44 | public class ErrorHandler extends ResponseEntityExceptionHandler { 45 | 46 | /** 47 | * Exception Handler to handle {@link DccException} Exceptions. 48 | */ 49 | @ExceptionHandler(DccException.class) 50 | public ResponseEntity handleException(DccException e) { 51 | log.error(e.getMessage()); 52 | return ResponseEntity 53 | .status(e.getStatus()) 54 | .contentType(MediaType.APPLICATION_JSON) 55 | .body(new ProblemReportDto("", "Dcc Error", "", e.getMessage())); 56 | } 57 | 58 | /** 59 | * handle exception. 60 | * @param e exception 61 | * @return respone 62 | */ 63 | @ExceptionHandler(JwtException.class) 64 | public ResponseEntity handleException(JwtException e) { 65 | log.error(e.getMessage()); 66 | return ResponseEntity 67 | .status(HttpStatus.BAD_REQUEST) 68 | .contentType(MediaType.APPLICATION_JSON) 69 | .body(new ProblemReportDto("", "Dcc Error", "", e.getMessage())); 70 | } 71 | 72 | 73 | @Override 74 | protected ResponseEntity handleMethodArgumentNotValid( 75 | MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { 76 | StringBuilder sb = new StringBuilder(); 77 | ex.getBindingResult().getAllErrors().forEach((error) -> { 78 | String fieldName = ((FieldError) error).getField(); 79 | String errorMessage = error.getDefaultMessage(); 80 | 81 | sb 82 | .append(error.getObjectName()) 83 | .append('.') 84 | .append(fieldName) 85 | .append(' ') 86 | .append(errorMessage) 87 | .append(", "); 88 | }); 89 | return ResponseEntity 90 | .status(HttpStatus.BAD_REQUEST) 91 | .contentType(MediaType.APPLICATION_JSON) 92 | .body(new ProblemReportDto("", "Validation Error", "", sb.toString())); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/token/ResultTokenBuilder.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.token; 2 | 3 | import eu.europa.ec.dgc.validation.restapi.dto.ResultTypeIdentifier; 4 | import eu.europa.ec.dgc.validation.restapi.dto.ValidationStatusResponse; 5 | import eu.europa.ec.dgc.validation.restapi.dto.ValidationStatusResponse.Result.ResultType; 6 | import io.jsonwebtoken.JwtBuilder; 7 | import io.jsonwebtoken.Jwts; 8 | import io.jsonwebtoken.SignatureAlgorithm; 9 | import java.security.PrivateKey; 10 | import java.time.Instant; 11 | import java.util.ArrayList; 12 | import java.util.Date; 13 | import java.util.List; 14 | import java.util.UUID; 15 | import java.util.stream.Collectors; 16 | 17 | public class ResultTokenBuilder { 18 | private final JwtBuilder builder; 19 | private final JwtBuilder builder2; 20 | 21 | /** 22 | * constructor. 23 | */ 24 | public ResultTokenBuilder() { 25 | builder = Jwts.builder(); 26 | builder2 = Jwts.builder(); 27 | builder.setHeaderParam("typ", "JWT"); 28 | } 29 | 30 | /** 31 | * evaluate Result. 32 | * @param results results 33 | * @return resulting string symbol 34 | */ 35 | public static String evaluateResult(List results) { 36 | boolean nok = results != null ? results.stream().anyMatch(t -> t.getResult() == ResultType.NOK 37 | && t.getType() == ResultTypeIdentifier.TechnicalVerification 38 | || (t.getType() == ResultTypeIdentifier.IssuerInvalidation && t.getResult() == ResultType.NOK)) : false; 39 | boolean chk = results != null ? (results.stream().anyMatch(t -> t.getResult() == ResultType.CHK 40 | || (t.getResult() == ResultType.NOK 41 | && t.getType() != ResultTypeIdentifier.TechnicalVerification) 42 | ) || results.size() == 0) : true; 43 | 44 | String result = nok ? "NOK" : chk ? "CHK" : "OK"; 45 | return result; 46 | } 47 | 48 | /** 49 | * build the thing. 50 | * @param results results 51 | * @param subject subject 52 | * @param issuer issuer 53 | * @param privateKey privateKey 54 | * @param kid kid 55 | * @return jwt token 56 | */ 57 | public String build(List results, 58 | String subject, 59 | String issuer, 60 | String[] category, 61 | Date expiration, 62 | PrivateKey privateKey, 63 | String kid, 64 | boolean privacy) { 65 | 66 | String result = evaluateResult(results); 67 | 68 | List badResults = results 69 | .stream() 70 | .filter(r -> r.getResult() != ResultType.OK) 71 | .collect(Collectors.toList()); 72 | 73 | 74 | String confirmation = builder2.setHeaderParam("kid", kid) 75 | .setId(UUID.randomUUID().toString()) 76 | .setHeaderParam("alg", "ES256") 77 | .setSubject(subject) 78 | .setIssuer(issuer) 79 | .setIssuedAt(Date.from(Instant.now())) 80 | .setExpiration(expiration) 81 | .signWith(SignatureAlgorithm.ES256, privateKey) 82 | .claim("result", result) 83 | .claim("category",category) 84 | .compact(); 85 | 86 | return builder.setHeaderParam("kid", kid) 87 | .setHeaderParam("alg", "ES256") 88 | .setSubject(subject) 89 | .setIssuer(issuer) 90 | .setIssuedAt(Date.from(Instant.now())) 91 | .setExpiration(expiration) 92 | .signWith(SignatureAlgorithm.ES256, privateKey) 93 | .claim("category",category) 94 | .claim("confirmation", confirmation) 95 | .claim("results", privacy ? new ArrayList() : badResults) 96 | .claim("result", result) 97 | .compact(); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /.github/workflows/ci-release.yml: -------------------------------------------------------------------------------- 1 | name: ci-release 2 | on: 3 | release: 4 | types: 5 | - created 6 | jobs: 7 | build: 8 | runs-on: ubuntu-20.04 9 | env: 10 | APP_VERSION: ${{ github.event.release.tag_name }} 11 | steps: 12 | - uses: actions/setup-java@v2 13 | with: 14 | java-version: 11 15 | distribution: adopt 16 | - uses: actions/checkout@v2 17 | with: 18 | fetch-depth: 0 19 | - uses: actions/cache@v2 20 | with: 21 | path: | 22 | ~/.m2/repository 23 | key: ${{ runner.os }}-${{ hashFiles('**/pom.xml') }} 24 | - name: mvn 25 | run: |- 26 | mvn versions:set \ 27 | --batch-mode \ 28 | --file ./pom.xml \ 29 | --settings ./settings.xml \ 30 | --define newVersion="${APP_VERSION}" 31 | mvn clean deploy \ 32 | --batch-mode \ 33 | --file ./pom.xml \ 34 | --settings ./settings.xml \ 35 | --define app.packages.username="${APP_PACKAGES_USERNAME}" \ 36 | --define app.packages.password="${APP_PACKAGES_PASSWORD}" 37 | env: 38 | APP_PACKAGES_USERNAME: ${{ github.actor }} 39 | APP_PACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }} 40 | - name: docker 41 | run: |- 42 | echo "${APP_PACKAGES_PASSWORD}" | docker login "${APP_PACKAGES_URL}" \ 43 | --username "${APP_PACKAGES_USERNAME}" \ 44 | --password-stdin 45 | docker build . \ 46 | --file ./docker/Cloudfoundry \ 47 | --tag "${APP_PACKAGES_URL}:latest" \ 48 | --tag "${APP_PACKAGES_URL}:${APP_VERSION}" 49 | docker push "${APP_PACKAGES_URL}:latest" 50 | docker push "${APP_PACKAGES_URL}:${APP_VERSION}" 51 | env: 52 | APP_PACKAGES_URL: docker.pkg.github.com/${{ github.repository }}/dgca-validation-service-cloudfoundry 53 | APP_PACKAGES_USERNAME: ${{ github.actor }} 54 | APP_PACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }} 55 | - name: docker 56 | run: |- 57 | echo "${APP_PACKAGES_PASSWORD}" | docker login "${APP_PACKAGES_URL}" \ 58 | --username "${APP_PACKAGES_USERNAME}" \ 59 | --password-stdin 60 | docker build . \ 61 | --file ./docker/Native \ 62 | --tag "${APP_PACKAGES_URL}:latest" \ 63 | --tag "${APP_PACKAGES_URL}:${APP_VERSION}" 64 | docker push "${APP_PACKAGES_URL}:latest" 65 | docker push "${APP_PACKAGES_URL}:${APP_VERSION}" 66 | env: 67 | APP_PACKAGES_URL: docker.pkg.github.com/${{ github.repository }}/dgca-validation-service 68 | APP_PACKAGES_USERNAME: ${{ github.actor }} 69 | APP_PACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }} 70 | - name: assets 71 | run: |- 72 | gh release upload ${APP_VERSION} \ 73 | --clobber \ 74 | ./target/openapi.json#openapi-${APP_VERSION}.json \ 75 | ./target/generated-resources/licenses.xml#licenses-${APP_VERSION}.xml 76 | env: 77 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 78 | deploy: 79 | runs-on: ubuntu-20.04 80 | environment: dev 81 | needs: 82 | - build 83 | env: 84 | APP_VERSION: ${{ github.event.release.tag_name }} 85 | steps: 86 | - name: cf setup 87 | run: |- 88 | curl -sL "https://packages.cloudfoundry.org/stable?release=${CF_RELEASE}&version=${CF_VERSION}" | \ 89 | sudo tar -zx -C /usr/local/bin 90 | env: 91 | CF_VERSION: 7.2.0 92 | CF_RELEASE: linux64-binary 93 | - name: cf push 94 | run: |- 95 | cf api ${CF_API} 96 | cf auth 97 | cf target -o ${CF_ORG} -s ${CF_SPACE} 98 | cf push ${APP_NAME} --docker-image ${APP_IMAGE}:${APP_VERSION} --docker-username ${CF_DOCKER_USERNAME} 99 | env: 100 | APP_NAME: dgca-validation-service 101 | APP_IMAGE: docker.pkg.github.com/${{ github.repository }}/dgca-validation-service-cloudfoundry 102 | CF_API: ${{ secrets.CF_API }} 103 | CF_ORG: ${{ secrets.CF_ORG }} 104 | CF_SPACE: ${{ secrets.CF_SPACE }} 105 | CF_USERNAME: ${{ secrets.CF_USERNAME }} 106 | CF_PASSWORD: ${{ secrets.CF_PASSWORD }} 107 | CF_DOCKER_USERNAME: ${{ secrets.CF_DOCKER_USERNAME }} 108 | CF_DOCKER_PASSWORD: ${{ secrets.CF_DOCKER_PASSWORD }} 109 | -------------------------------------------------------------------------------- /src/test/java/eu/europa/ec/dgc/validation/AesTest.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation; 2 | 3 | import eu.europa.ec.dgc.validation.service.ValidationServiceTest; 4 | import org.bouncycastle.crypto.Digest; 5 | import org.bouncycastle.crypto.digests.SHA256Digest; 6 | import org.bouncycastle.crypto.generators.HKDFBytesGenerator; 7 | import org.bouncycastle.crypto.params.HKDFParameters; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import javax.crypto.Cipher; 11 | import javax.crypto.spec.GCMParameterSpec; 12 | import javax.crypto.spec.SecretKeySpec; 13 | import java.nio.charset.StandardCharsets; 14 | import java.security.*; 15 | import java.util.Base64; 16 | 17 | import static org.junit.jupiter.api.Assertions.assertEquals; 18 | import static org.junit.jupiter.api.Assertions.fail; 19 | 20 | public class AesTest { 21 | public static final int AES_KEY_SIZE = 128; 22 | public static final int GCM_IV_LENGTH = 12; 23 | public static final int GCM_TAG_LENGTH = 16; 24 | public static final String AES_CHIPPER = "AES/GCM/NoPadding"; 25 | 26 | @Test 27 | void testAas() throws Exception { 28 | String subject = "764472ab-6896-4b36-8359-29201318a47a"; 29 | PrivateKey privateKey = ValidationServiceTest.parsePrivateKey(ValidationServiceTest.EC_PRIVATE_KEY); 30 | 31 | AESSecrets secrets = deriveSecrets(subject, privateKey); 32 | 33 | String plainText = "DCCTest"; 34 | 35 | System.out.println("Original Text : " + plainText); 36 | 37 | byte[] cipherText = encrypt(plainText.getBytes(), secrets); 38 | System.out.println("Encrypted Text : " + Base64.getEncoder().encodeToString(cipherText)); 39 | 40 | secrets = deriveSecrets(subject, privateKey); 41 | String decryptedText = decrypt(cipherText, secrets); 42 | System.out.println("DeCrypted Text : " + decryptedText); 43 | assertEquals(plainText, decryptedText); 44 | 45 | KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("EC"); 46 | keyPairGen.initialize(256); 47 | KeyPair keyPair = keyPairGen.generateKeyPair(); 48 | AESSecrets secretsWrong = deriveSecrets(subject, keyPair.getPrivate()); 49 | try { 50 | String decryptedTextWrong = decrypt(cipherText, secretsWrong); 51 | fail("expect exception here"); 52 | } catch (GeneralSecurityException exception) { 53 | 54 | } 55 | } 56 | 57 | private AESSecrets deriveSecrets(String subject, PrivateKey privateKey) throws NoSuchAlgorithmException { 58 | AESSecrets secrets = new AESSecrets(); 59 | 60 | Digest digest = new SHA256Digest(); 61 | HKDFBytesGenerator hkdf = new HKDFBytesGenerator(digest); 62 | hkdf.init(new HKDFParameters(subject.getBytes(StandardCharsets.UTF_8), privateKey.getEncoded(), null)); 63 | 64 | secrets.iv = new byte[GCM_IV_LENGTH]; 65 | hkdf.generateBytes(secrets.iv, 0, GCM_IV_LENGTH); 66 | 67 | secrets.secretKey = new byte[AES_KEY_SIZE / 8]; 68 | hkdf.generateBytes(secrets.secretKey, 0, AES_KEY_SIZE / 8); 69 | 70 | return secrets; 71 | } 72 | 73 | public static byte[] encrypt(byte[] plaintext, AESSecrets secrets) throws Exception { 74 | // Get Cipher Instance 75 | Cipher cipher = Cipher.getInstance(AES_CHIPPER); 76 | 77 | // Create SecretKeySpec 78 | SecretKeySpec keySpec = new SecretKeySpec(secrets.secretKey, "AES"); 79 | 80 | // Create GCMParameterSpec 81 | GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, secrets.iv); 82 | 83 | // Initialize Cipher for ENCRYPT_MODE 84 | cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmParameterSpec); 85 | 86 | // Perform Encryption 87 | byte[] cipherText = cipher.doFinal(plaintext); 88 | 89 | return cipherText; 90 | } 91 | 92 | public static String decrypt(byte[] cipherText, AESSecrets secrets) throws Exception { 93 | // Get Cipher Instance 94 | Cipher cipher = Cipher.getInstance(AES_CHIPPER); 95 | 96 | // Create SecretKeySpec 97 | SecretKeySpec keySpec = new SecretKeySpec(secrets.secretKey, "AES"); 98 | 99 | // Create GCMParameterSpec 100 | GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, secrets.iv); 101 | 102 | // Initialize Cipher for DECRYPT_MODE 103 | cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec); 104 | 105 | // Perform Decryption 106 | byte[] decryptedText = cipher.doFinal(cipherText); 107 | 108 | return new String(decryptedText); 109 | } 110 | 111 | private static class AESSecrets { 112 | byte[] secretKey; 113 | byte[] iv; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Code of conduct 4 | 5 | All members of the project community must abide by the [Contributor Covenant, version 2.0](CODE_OF_CONDUCT.md). 6 | Only by respecting each other can we develop a productive, collaborative community. 7 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting [opensource@telekom.de](mailto:opensource@telekom.de) and/or a project maintainer. 8 | 9 | We appreciate your courtesy of avoiding political questions here. Issues which are not related to the project itself will be closed by our community managers. 10 | 11 | ## Engaging in our project 12 | 13 | We use GitHub to manage reviews of pull requests. 14 | 15 | * If you are a new contributor, see: [Steps to Contribute](#steps-to-contribute) 16 | 17 | * If you have a trivial fix or improvement, go ahead and create a pull request, addressing (with `@...`) a suitable maintainer of this repository (see [CODEOWNERS](CODEOWNERS) of the repository you want to contribute to) in the description of the pull request. 18 | 19 | * If you plan to do something more involved, please reach out to us and send an [email](mailto:opensource@telekom.de). This will avoid unnecessary work and surely give you and us a good deal of inspiration. 20 | 21 | * Relevant coding style guidelines are available in the respective sub-repositories as they are programming language-dependent. 22 | 23 | ## Steps to Contribute 24 | 25 | Should you wish to work on an issue, please claim it first by commenting on the GitHub issue that you want to work on. This is to prevent duplicated efforts from other contributors on the same issue. 26 | 27 | If you have questions about one of the issues, please comment on them, and one of the maintainers will clarify. 28 | 29 | We kindly ask you to follow the [Pull Request Checklist](#Pull-Request-Checklist) to ensure reviews can happen accordingly. 30 | 31 | ## Contributing Code 32 | 33 | You are welcome to contribute code in order to fix a bug or to implement a new feature. 34 | 35 | The following rule governs code contributions: 36 | 37 | * Contributions must be licensed under the [Apache 2.0 License](./LICENSE) 38 | * Newly created files must be opened by an instantiated version of the file 'templates/file-header.txt' 39 | * At least if you add a new file to the repository, add your name into the contributor section of the file NOTICE (please respect the preset entry structure) 40 | 41 | ## Contributing Documentation 42 | 43 | You are welcome to contribute documentation to the project. 44 | 45 | The following rule governs documentation contributions: 46 | 47 | * Contributions must be licensed under the same license as code, the [Apache 2.0 License](./LICENSE) 48 | 49 | ## Pull Request Checklist 50 | 51 | * Branch from the main branch and, if needed, rebase to the current main branch before submitting your pull request. If it doesn't merge cleanly with main you may be asked to rebase your changes. 52 | 53 | * Commits should be as small as possible while ensuring that each commit is correct independently (i.e., each commit should compile and pass tests). 54 | 55 | * Test your changes as thoroughly as possible before you commit them. Preferably, automate your test by unit/integration tests. If tested manually, provide information about the test scope in the PR description (e.g. “Test passed: Upgrade version from 0.42 to 0.42.23.”). 56 | 57 | * Create _Work In Progress [WIP]_ pull requests only if you need clarification or an explicit review before you can continue your work item. 58 | 59 | * If your patch is not getting reviewed or you need a specific person to review it, you can @-reply a reviewer asking for a review in the pull request or a comment, or you can ask for a review by contacting us via [email](mailto:opensource@telekom.de). 60 | 61 | * Post review: 62 | * If a review requires you to change your commit(s), please test the changes again. 63 | * Amend the affected commit(s) and force push onto your branch. 64 | * Set respective comments in your GitHub review to resolved. 65 | * Create a general PR comment to notify the reviewers that your amendments are ready for another round of review. 66 | 67 | ## Issues and Planning 68 | 69 | * We use GitHub issues to track bugs and enhancement requests. 70 | 71 | * Please provide as much context as possible when you open an issue. The information you provide must be comprehensive enough to reproduce that issue for the assignee. Therefore, contributors may use but aren't restricted to the issue template provided by the project maintainers. 72 | 73 | * When creating an issue, try using one of our issue templates which already contain some guidelines on which content is expected to process the issue most efficiently. If no template applies, you can of course also create an issue from scratch. 74 | 75 | * Please apply one or more applicable [labels](/../../labels) to your issue so that all community members are able to cluster the issues better. 76 | -------------------------------------------------------------------------------- /src/test/java/eu/europa/ec/dgc/validation/JwtNumbusTest.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation; 2 | 3 | import com.nimbusds.jose.EncryptionMethod; 4 | import com.nimbusds.jose.JWEAlgorithm; 5 | import com.nimbusds.jose.JWEEncrypter; 6 | import com.nimbusds.jose.JWEHeader; 7 | import com.nimbusds.jose.JWSAlgorithm; 8 | import com.nimbusds.jose.JWSHeader; 9 | import com.nimbusds.jose.JWSObject; 10 | import com.nimbusds.jose.JWSSigner; 11 | import com.nimbusds.jose.Payload; 12 | import com.nimbusds.jose.crypto.ECDHDecrypter; 13 | import com.nimbusds.jose.crypto.ECDHEncrypter; 14 | import com.nimbusds.jose.crypto.ECDSASigner; 15 | import com.nimbusds.jose.crypto.RSADecrypter; 16 | import com.nimbusds.jose.crypto.RSAEncrypter; 17 | import com.nimbusds.jose.jwk.Curve; 18 | import com.nimbusds.jose.jwk.ECKey; 19 | import com.nimbusds.jose.jwk.gen.ECKeyGenerator; 20 | import com.nimbusds.jwt.EncryptedJWT; 21 | import com.nimbusds.jwt.JWTClaimsSet; 22 | 23 | import java.security.interfaces.ECPrivateKey; 24 | import java.util.Arrays; 25 | import java.util.Date; 26 | import java.util.UUID; 27 | 28 | import net.minidev.json.JSONArray; 29 | import net.minidev.json.JSONObject; 30 | import org.bouncycastle.jcajce.provider.asymmetric.ec.KeyFactorySpi; 31 | import org.junit.jupiter.api.Test; 32 | 33 | class JwtNimbusTest { 34 | 35 | @Test 36 | void testJwtGen() throws Exception { 37 | ECKey ecJWK = new ECKeyGenerator(Curve.P_256) 38 | .keyID("123") 39 | .generate(); 40 | ECKey ecPublicJWK = ecJWK.toPublicJWK(); 41 | 42 | // Create the EC signer 43 | JWSSigner signer = new ECDSASigner(ecJWK); 44 | 45 | JSONObject jsonObject = new JSONObject(); 46 | jsonObject.put("sub", "subject"); 47 | JSONArray jsonArray = new JSONArray(); 48 | jsonArray.appendElement(2); 49 | jsonArray.appendElement(3); 50 | jsonObject.put("result", jsonArray); 51 | 52 | // Creates the JWS object with payload 53 | JWSObject jwsObject = new JWSObject( 54 | new JWSHeader.Builder(JWSAlgorithm.ES256).keyID(ecJWK.getKeyID()).build(), 55 | new Payload(jsonObject)); 56 | 57 | // Compute the EC signature 58 | jwsObject.sign(signer); 59 | 60 | // Serialize the JWS to compact form 61 | String s = jwsObject.serialize(); 62 | System.out.println(s); 63 | 64 | } 65 | 66 | @Test 67 | void testJwtEncryption() throws Exception { 68 | ECKey ecJWK = new ECKeyGenerator(Curve.P_256) 69 | .keyID("123") 70 | .generate(); 71 | ECKey ecPublicJWK = ecJWK.toPublicJWK(); 72 | 73 | JWEEncrypter encrypter = new ECDHEncrypter(ecPublicJWK); 74 | 75 | Date now = new Date(); 76 | 77 | JWTClaimsSet jwtClaims = new JWTClaimsSet.Builder() 78 | .issuer("https://openid.net") 79 | .subject("alice") 80 | .audience(Arrays.asList("https://app-one.com", "https://app-two.com")) 81 | .expirationTime(new Date(now.getTime() + 1000 * 60 * 10)) // expires in 10 minutes 82 | .notBeforeTime(now) 83 | .issueTime(now) 84 | .jwtID(UUID.randomUUID().toString()) 85 | .build(); 86 | 87 | System.out.println(jwtClaims.toJSONObject()); 88 | 89 | // Request JWT encrypted with RSA-OAEP-256 and 128-bit AES/GCM 90 | JWEHeader header = new JWEHeader(JWEAlgorithm.ECDH_ES_A256KW, EncryptionMethod.A256GCM); 91 | 92 | // Create the encrypted JWT object 93 | EncryptedJWT jwt = new EncryptedJWT(header, jwtClaims); 94 | 95 | // Do the actual encryption 96 | jwt.encrypt(encrypter); 97 | 98 | // Serialise to JWT compact form 99 | String jwtString = jwt.serialize(); 100 | 101 | System.out.println(jwtString); 102 | 103 | EncryptedJWT jwtDeparsed = EncryptedJWT.parse(jwtString); 104 | 105 | // Create a decrypter with the specified private RSA key 106 | ECDHDecrypter decrypter = new ECDHDecrypter((ECPrivateKey) ecJWK.toPrivateKey()); 107 | 108 | // Decrypt 109 | jwtDeparsed.decrypt(decrypter); 110 | 111 | System.out.println(jwtDeparsed.getJWTClaimsSet()); 112 | 113 | System.out.println(jwtDeparsed.getJWTClaimsSet().getIssuer()); 114 | ; 115 | System.out.println(jwtDeparsed.getJWTClaimsSet().getSubject()); 116 | System.out.println(jwtDeparsed.getJWTClaimsSet().getAudience().size()); 117 | System.out.println(jwtDeparsed.getJWTClaimsSet().getExpirationTime()); 118 | System.out.println(jwtDeparsed.getJWTClaimsSet().getNotBeforeTime()); 119 | System.out.println(jwtDeparsed.getJWTClaimsSet().getIssueTime()); 120 | System.out.println(jwtDeparsed.getJWTClaimsSet().getJWTID()); 121 | 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/service/ValueSetsDownloadServicePseImpl.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * eu-digital-green-certificates / dgca-validation-service 4 | * --- 5 | * Copyright (C) 2021 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package eu.europa.ec.dgc.validation.service; 22 | 23 | import eu.europa.ec.dgc.validation.client.ValueSetsRestClient; 24 | import eu.europa.ec.dgc.validation.client.dto.ValueSetResponseDto; 25 | import eu.europa.ec.dgc.validation.model.ValueSetItem; 26 | import feign.FeignException; 27 | import java.util.ArrayList; 28 | import java.util.List; 29 | import lombok.RequiredArgsConstructor; 30 | import lombok.extern.slf4j.Slf4j; 31 | import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; 32 | import org.springframework.context.annotation.Profile; 33 | import org.springframework.http.HttpStatus; 34 | import org.springframework.http.ResponseEntity; 35 | import org.springframework.scheduling.annotation.Scheduled; 36 | import org.springframework.stereotype.Component; 37 | 38 | @Slf4j 39 | @RequiredArgsConstructor 40 | @Component 41 | @Profile("pse") 42 | public class ValueSetsDownloadServicePseImpl implements ValueSetsDownloadService { 43 | 44 | private final ValueSetsRestClient valueSetsRestClient; 45 | private final ValueSetService valueSetService; 46 | 47 | @Override 48 | @Scheduled(fixedDelayString = "${dgc.valueSetsDownload.timeInterval}") 49 | @SchedulerLock(name = "GatewayDataDownloadService_downloadValueSets", lockAtLeastFor = "PT0S", 50 | lockAtMostFor = "${dgc.valueSetsDownload.lockLimit}") 51 | public void downloadValueSets() { 52 | 53 | ResponseEntity> responseEntity; 54 | 55 | try { 56 | responseEntity = valueSetsRestClient.getValueSetsList(); 57 | } catch (FeignException e) { 58 | log.error("Download of value sets failed with exception. Service responded with status code: {}", 59 | e.status()); 60 | return; 61 | } 62 | 63 | List valueSetsList = responseEntity.getBody(); 64 | if (responseEntity.getStatusCode() != HttpStatus.OK || valueSetsList == null) { 65 | log.error("Download of value sets failed. Service responded with status code: {}", 66 | responseEntity.getStatusCode()); 67 | return; 68 | } 69 | 70 | log.info("Got Response from Service, Value sets index contains sets: {}", valueSetsList.size()); 71 | 72 | List items = getValueSetItem(valueSetsList); 73 | 74 | if (!items.isEmpty()) { 75 | valueSetService.updateValueSets(items); 76 | } else { 77 | log.warn("The download of the bvalue sets seems to fail, as the download connector " 78 | + "returns an empty list. No data will be changed."); 79 | } 80 | 81 | log.info("Download finished, Downloaded value sets: {}", items.size()); 82 | 83 | } 84 | 85 | private List getValueSetItem(List valueSetsList) { 86 | 87 | List items = new ArrayList<>(); 88 | 89 | for (ValueSetResponseDto valueSetIndex : valueSetsList) { 90 | ValueSetItem item = getValueSetData(valueSetIndex); 91 | if (item != null) { 92 | items.add(item); 93 | } 94 | } 95 | return items; 96 | } 97 | 98 | private ValueSetItem getValueSetData(ValueSetResponseDto valueSetIndex) { 99 | ResponseEntity responseEntity; 100 | 101 | try { 102 | responseEntity = valueSetsRestClient.getValueSetData(valueSetIndex.getHash()); 103 | } catch (FeignException e) { 104 | log.error("Download of value set item failed with exception. Service responded with status code: {}", 105 | e.status()); 106 | return null; 107 | } 108 | 109 | String rawData = responseEntity.getBody(); 110 | 111 | if (responseEntity.getStatusCode() != HttpStatus.OK || rawData == null) { 112 | log.error("Download of value set item failed. Service responded with status code: {}", 113 | responseEntity.getStatusCode()); 114 | return null; 115 | } 116 | ValueSetItem item = new ValueSetItem(); 117 | item.setId(valueSetIndex.getId()); 118 | item.setHash(valueSetIndex.getHash()); 119 | item.setRawData(rawData); 120 | 121 | return item; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/validation/cryptschemas/RsaOaepWithSha256AesCbc.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.validation.cryptschemas; 2 | 3 | import eu.europa.ec.dgc.validation.exception.DccException; 4 | import java.security.InvalidAlgorithmParameterException; 5 | import java.security.InvalidKeyException; 6 | import java.security.NoSuchAlgorithmException; 7 | import java.security.PrivateKey; 8 | import java.security.PublicKey; 9 | import java.security.spec.InvalidKeySpecException; 10 | import java.security.spec.MGF1ParameterSpec; 11 | import javax.crypto.BadPaddingException; 12 | import javax.crypto.Cipher; 13 | import javax.crypto.IllegalBlockSizeException; 14 | import javax.crypto.KeyGenerator; 15 | import javax.crypto.NoSuchPaddingException; 16 | import javax.crypto.SecretKey; 17 | import javax.crypto.SecretKeyFactory; 18 | import javax.crypto.spec.IvParameterSpec; 19 | import javax.crypto.spec.OAEPParameterSpec; 20 | import javax.crypto.spec.PSource; 21 | import javax.crypto.spec.SecretKeySpec; 22 | 23 | public class RsaOaepWithSha256AesCbc implements CryptSchema { 24 | public static final String KEY_CIPHER = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"; 25 | public static final String DATA_CIPHER = "AES/CBC/PKCS5Padding"; 26 | public static final String ENC_SCHEMA = "RSAOAEPWithSHA256AESCBC"; 27 | 28 | /** 29 | * encrypt data. 30 | * @param data data 31 | * @param publicKey publicKey 32 | * @param iv initialization vector 33 | * @return encrypted data 34 | */ 35 | public EncryptedData encryptData(byte[] data, PublicKey publicKey, byte[] iv) { 36 | try { 37 | 38 | if (iv == null) { 39 | iv = new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 40 | } else if (iv.length > 16 || iv.length < 16 || iv.length % 8 > 0) { 41 | throw new InvalidKeySpecException(); 42 | } 43 | 44 | EncryptedData encryptedData = new EncryptedData(); 45 | 46 | KeyGenerator keyGen = KeyGenerator.getInstance("AES"); 47 | keyGen.init(256); // for example 48 | SecretKey secretKey = keyGen.generateKey(); 49 | 50 | IvParameterSpec ivspec = new IvParameterSpec(iv); 51 | Cipher cipher = Cipher.getInstance(DATA_CIPHER); 52 | cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivspec); 53 | encryptedData.setDataEncrypted(cipher.doFinal(data)); 54 | 55 | // encrypt RSA key 56 | Cipher keyCipher = Cipher.getInstance(KEY_CIPHER); 57 | OAEPParameterSpec oaepParameterSpec = new OAEPParameterSpec( 58 | "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT 59 | ); 60 | keyCipher.init(Cipher.ENCRYPT_MODE, publicKey, oaepParameterSpec); 61 | byte[] secretKeyBytes = secretKey.getEncoded(); 62 | encryptedData.setEncKey(keyCipher.doFinal(secretKeyBytes)); 63 | 64 | return encryptedData; 65 | } catch (NoSuchPaddingException | NoSuchAlgorithmException | IllegalBlockSizeException | BadPaddingException 66 | | InvalidKeyException | InvalidAlgorithmParameterException | InvalidKeySpecException e) { 67 | throw new DccException("encryption error", e); 68 | } 69 | } 70 | 71 | /** 72 | * decrypt data. 73 | * @param encryptedData encryptedData 74 | * @param privateKey privateKey 75 | * @param iv initialization vector 76 | * @return decrypted data 77 | */ 78 | public byte[] decryptData(EncryptedData encryptedData, PrivateKey privateKey, byte[] iv) { 79 | try { 80 | 81 | if (iv == null) { 82 | iv = new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 83 | } else if (iv.length > 16 || iv.length < 16 || iv.length % 8 > 0) { 84 | throw new InvalidKeySpecException(); 85 | } 86 | 87 | Cipher keyCipher = Cipher.getInstance(KEY_CIPHER); 88 | OAEPParameterSpec oaepParameterSpec = new OAEPParameterSpec( 89 | "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT 90 | ); 91 | keyCipher.init(Cipher.DECRYPT_MODE, privateKey, oaepParameterSpec); 92 | byte[] rsaKey = keyCipher.doFinal(encryptedData.getEncKey()); 93 | 94 | IvParameterSpec ivspec = new IvParameterSpec(iv); 95 | Cipher cipher = Cipher.getInstance(DATA_CIPHER); 96 | SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("AES"); 97 | SecretKeySpec secretKeySpec = new SecretKeySpec(rsaKey, 0, rsaKey.length, "AES"); 98 | SecretKey secretKey = secretKeyFactory.generateSecret(secretKeySpec); 99 | 100 | cipher.init(Cipher.DECRYPT_MODE, secretKey, ivspec); 101 | return cipher.doFinal(encryptedData.getDataEncrypted()); 102 | } catch (NoSuchPaddingException | NoSuchAlgorithmException | IllegalBlockSizeException | BadPaddingException 103 | | InvalidKeyException | InvalidAlgorithmParameterException | InvalidKeySpecException e) { 104 | throw new DccException("encryption error", e); 105 | } 106 | } 107 | 108 | public String getEncSchema() { 109 | return ENC_SCHEMA; 110 | } 111 | 112 | } 113 | --------------------------------------------------------------------------------