├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── 01_bug.md │ ├── 02_feature_request.md │ ├── 03_enhancement.md │ └── 04_question.md └── workflows │ ├── ci-dependency-check.yml │ ├── ci-deploy.yml │ ├── ci-main.yml │ ├── ci-pull-request.yml │ ├── ci-release-notes.yml │ ├── ci-release.yml │ └── ci-sonar.yml ├── .gitignore ├── .grenrc.js ├── .ort.yml ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── NOTICE ├── README.md ├── THIRD-PARTY-NOTICES ├── certs └── test.jks ├── codestyle └── checkstyle.xml ├── context └── context.json ├── docker-compose.yml ├── docs ├── configuration.md ├── dev_config.md └── issuance-service-overview.svg ├── owasp └── suppressions.xml ├── pom.xml ├── settings.xml ├── src ├── main │ ├── java │ │ └── eu │ │ │ └── europa │ │ │ └── ec │ │ │ └── dgc │ │ │ └── issuance │ │ │ ├── DgcIssuanceApplication.java │ │ │ ├── config │ │ │ ├── ErrorHandler.java │ │ │ ├── HcertLibConfig.java │ │ │ ├── IssuanceConfigProperties.java │ │ │ ├── OpenApiConfig.java │ │ │ └── btp │ │ │ │ └── SapCredentialStoreCfEnvProcessor.java │ │ │ ├── entity │ │ │ ├── DgciEntity.java │ │ │ └── GreenCertificateType.java │ │ │ ├── repository │ │ │ └── DgciRepository.java │ │ │ ├── restapi │ │ │ ├── controller │ │ │ │ ├── CertController.java │ │ │ │ ├── CertPublisherController.java │ │ │ │ ├── ContextController.java │ │ │ │ ├── DgciBackendController.java │ │ │ │ ├── DgciController.java │ │ │ │ ├── DgciDidController.java │ │ │ │ └── WalletController.java │ │ │ └── dto │ │ │ │ ├── CertificateTypeDto.java │ │ │ │ ├── ClaimRequest.java │ │ │ │ ├── ClaimResponse.java │ │ │ │ ├── DgciIdentifier.java │ │ │ │ ├── DgciInit.java │ │ │ │ ├── DidAuthentication.java │ │ │ │ ├── DidDocument.java │ │ │ │ ├── EgcDecodeResult.java │ │ │ │ ├── EgdcCodeData.java │ │ │ │ ├── IssueData.java │ │ │ │ ├── ProblemReportDto.java │ │ │ │ ├── PublicKey.java │ │ │ │ ├── PublicKeyInfo.java │ │ │ │ └── SignatureData.java │ │ │ ├── service │ │ │ ├── CertKeyPublisherService.java │ │ │ ├── CertKeyPublisherServiceImpl.java │ │ │ ├── CertificatePrivateKeyProvider.java │ │ │ ├── CertificateService.java │ │ │ ├── ConfigurableCwtService.java │ │ │ ├── ContextService.java │ │ │ ├── DdcGatewayException.java │ │ │ ├── DgciConflict.java │ │ │ ├── DgciGenerator.java │ │ │ ├── DgciNotFound.java │ │ │ ├── DgciService.java │ │ │ ├── EdgcValidator.java │ │ │ ├── EhdCryptoService.java │ │ │ ├── ExpirationService.java │ │ │ ├── SigningService.java │ │ │ ├── Tan.java │ │ │ ├── WrongRequest.java │ │ │ └── impl │ │ │ │ ├── BtpAbstractKeyProvider.java │ │ │ │ ├── BtpCertKeyPublisherServiceImpl.java │ │ │ │ ├── BtpIssuerKeyProviderImpl.java │ │ │ │ ├── BtpUploadKeyProviderImpl.java │ │ │ │ ├── CertificatePrivateKeyProviderImpl.java │ │ │ │ ├── CopyDigest.java │ │ │ │ └── SigningServiceImpl.java │ │ │ └── utils │ │ │ ├── CborDumpService.java │ │ │ ├── DgciUtil.java │ │ │ ├── HashUtil.java │ │ │ └── btp │ │ │ ├── CredentialStore.java │ │ │ ├── CredentialStoreConfig.java │ │ │ ├── CredentialStoreCryptoUtil.java │ │ │ └── SapCredential.java │ └── resources │ │ ├── META-INF │ │ └── spring.factories │ │ ├── application-btp.yml │ │ ├── application-cloud.yml │ │ ├── application.yml │ │ ├── db │ │ ├── changelog.xml │ │ └── changelog │ │ │ └── init-tables.xml │ │ └── logback-spring.xml └── test │ ├── java │ ├── OpenApiTest.java │ └── eu │ │ └── europa │ │ └── ec │ │ └── dgc │ │ └── issuance │ │ ├── EncodingTest.java │ │ ├── GenerateWalletRequestTest.java │ │ ├── JwkCoreTest.java │ │ ├── restapi │ │ └── controller │ │ │ ├── CertControllerTest.java │ │ │ ├── ContextControllerTest.java │ │ │ └── DgciControllerTest.java │ │ ├── service │ │ ├── ContextServiceTest.java │ │ ├── DGCGenTest.java │ │ ├── DgciGeneratorTest.java │ │ ├── DgciServiceTest.java │ │ ├── EdgcValidatorTest.java │ │ ├── ExpirationServiceTest.java │ │ ├── SampleData.java │ │ ├── TanServiceTest.java │ │ └── impl │ │ │ └── SigningServiceImplTest.java │ │ └── utils │ │ └── btp │ │ └── SapCredentialParserTest.java │ └── resources │ ├── application.yml │ └── data │ └── fromCredStore.json └── templates └── file-header.txt /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | 10 | [*.java] 11 | indent_size = 4 12 | -------------------------------------------------------------------------------- /.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/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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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-issuance-service-eu-test 30 | APP_IMAGE: docker.pkg.github.com/${{ github.repository }}/dgca-issuance-service 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 | -------------------------------------------------------------------------------- /.github/workflows/ci-main.yml: -------------------------------------------------------------------------------- 1 | name: ci-main 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - main 7 | jobs: 8 | build: 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_LATEST_REV=$(git rev-list --tags --max-count=1) 27 | APP_LATEST_TAG=$(git describe --tags ${APP_LATEST_REV} 2> /dev/null || echo 0.0.0) 28 | echo "APP_VERSION=${APP_LATEST_TAG}-${APP_SHA}" >> ${GITHUB_ENV} 29 | - name: mvn 30 | run: |- 31 | mvn versions:set \ 32 | --batch-mode \ 33 | --file ./pom.xml \ 34 | --settings ./settings.xml \ 35 | --define newVersion="${APP_VERSION}" 36 | mvn clean verify \ 37 | --batch-mode \ 38 | --file ./pom.xml \ 39 | --settings ./settings.xml \ 40 | --define app.packages.username="${APP_PACKAGES_USERNAME}" \ 41 | --define app.packages.password="${APP_PACKAGES_PASSWORD}" 42 | env: 43 | APP_PACKAGES_USERNAME: ${{ github.actor }} 44 | APP_PACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }} 45 | - name: docker 46 | run: |- 47 | echo "${APP_PACKAGES_PASSWORD}" | docker login "${APP_PACKAGES_URL}" \ 48 | --username "${APP_PACKAGES_USERNAME}" \ 49 | --password-stdin 50 | docker build . \ 51 | --file ./Dockerfile \ 52 | --tag "${APP_PACKAGES_URL}:${APP_VERSION}" 53 | docker push "${APP_PACKAGES_URL}:${APP_VERSION}" 54 | env: 55 | APP_PACKAGES_URL: docker.pkg.github.com/${{ github.repository }}/dgca-issuance-service 56 | APP_PACKAGES_USERNAME: ${{ github.actor }} 57 | APP_PACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }} 58 | -------------------------------------------------------------------------------- /.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 ./Dockerfile 39 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 ./Dockerfile \ 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-issuance-service 53 | APP_PACKAGES_USERNAME: ${{ github.actor }} 54 | APP_PACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }} 55 | - name: assets 56 | run: |- 57 | gh release upload ${APP_VERSION} \ 58 | --clobber \ 59 | ./target/openapi.json#openapi-${APP_VERSION}.json \ 60 | ./target/generated-resources/licenses.xml#licenses-${APP_VERSION}.xml 61 | env: 62 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 63 | deploy: 64 | runs-on: ubuntu-20.04 65 | environment: dev 66 | needs: 67 | - build 68 | env: 69 | APP_VERSION: ${{ github.event.release.tag_name }} 70 | steps: 71 | - name: cf setup 72 | run: |- 73 | curl -sL "https://packages.cloudfoundry.org/stable?release=${CF_RELEASE}&version=${CF_VERSION}" | \ 74 | sudo tar -zx -C /usr/local/bin 75 | env: 76 | CF_VERSION: 7.2.0 77 | CF_RELEASE: linux64-binary 78 | - name: cf push 79 | run: |- 80 | cf api ${CF_API} 81 | cf auth 82 | cf target -o ${CF_ORG} -s ${CF_SPACE} 83 | cf push ${APP_NAME} --docker-image ${APP_IMAGE}:${APP_VERSION} --docker-username ${CF_DOCKER_USERNAME} 84 | env: 85 | APP_NAME: dgca-issuance-service-eu-test 86 | APP_IMAGE: docker.pkg.github.com/${{ github.repository }}/dgca-issuance-service 87 | CF_API: ${{ secrets.CF_API }} 88 | CF_ORG: ${{ secrets.CF_ORG }} 89 | CF_SPACE: ${{ secrets.CF_SPACE }} 90 | CF_USERNAME: ${{ secrets.CF_USERNAME }} 91 | CF_PASSWORD: ${{ secrets.CF_PASSWORD }} 92 | CF_DOCKER_USERNAME: ${{ secrets.CF_DOCKER_USERNAME }} 93 | CF_DOCKER_PASSWORD: ${{ secrets.CF_DOCKER_PASSWORD }} 94 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.ort.yml: -------------------------------------------------------------------------------- 1 | excludes: 2 | scopes: 3 | - pattern: "provided" 4 | reason: "PROVIDED_DEPENDENCY_OF" 5 | comment: "Packages provided at runtime by the JDK or container only." 6 | - pattern: "test" 7 | reason: "TEST_DEPENDENCY_OF" 8 | comment: "Packages for testing only." -------------------------------------------------------------------------------- /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 | 9 | * @eu-digital-green-certificates/dgca-issuance-service-members 10 | 11 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, religion, or sexual identity 11 | and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the 27 | overall community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or 32 | advances of any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email 36 | address, without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement at 64 | [opensource@telekom.de](mailto:opensource@telekom.de). 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series 87 | of actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or 94 | permanent ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within 114 | the community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 2.0, available at 120 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 121 | 122 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 123 | enforcement ladder](https://github.com/mozilla/diversity). 124 | 125 | [homepage]: https://www.contributor-covenant.org 126 | 127 | For answers to common questions about this code of conduct, see the FAQ at 128 | https://www.contributor-covenant.org/faq. Translations are available at 129 | https://www.contributor-covenant.org/translations. 130 | 131 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM adoptopenjdk:11-jre-hotspot 2 | COPY ./target/*.jar /app/app.jar 3 | COPY ./certs/test.jks /app/certs/test.jks 4 | COPY ./context/context.json /app/context/context.json 5 | WORKDIR /app 6 | ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar ./app.jar" ] 7 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | EU Digital COVID Certificate Issuance Service 3 |

4 | 5 |

6 | 7 | 8 | 9 | 10 |

11 | 12 |

13 | About • 14 | Development • 15 | Documentation • 16 | Support • 17 | Contribute • 18 | Contributors • 19 | Licensing 20 |

21 | 22 | ## About 23 | 24 | This repository contains the source code of the EU Digital COVID Certificate Issuance Service. 25 | 26 | The issuer backend is accessed by the [issuer web frontend](https://github.com/eu-digital-green-certificates/dgca-issuance-web) and the respective wallet apps ( [Android](https://github.com/eu-digital-green-certificates/dgca-wallet-app-android), [iOS](https://github.com/eu-digital-green-certificates/dgca-wallet-app-ios) ) of the same member state. The backend itself publishes its public keys to the [DGCG](https://github.com/eu-digital-green-certificates/dgc-gateway) where they can be distributed to other member states. Each member state hosts its own issuer backend. The main function of the backend is to provide services for creating and signing new green certificates. 27 | 28 | ## Development 29 | 30 | ### Prerequisites 31 | 32 | - [Open JDK 11](https://openjdk.java.net) 33 | - [Maven](https://maven.apache.org) 34 | - [Docker](https://www.docker.com) 35 | - Authenticate to [Github Packages](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-apache-maven-registry) 36 | 37 | #### Authenticating in to GitHub Packages 38 | 39 | As some of the required libraries (and/or versions are pinned/available only from GitHub Packages) You need to authenticate 40 | to [GitHub Packages](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-apache-maven-registry) 41 | The following steps need to be followed 42 | 43 | - Create [PAT](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) with scopes: 44 | - `read:packages` for downloading packages 45 | 46 | ##### GitHub Maven 47 | 48 | - Copy/Augment `~/.m2/settings.xml` with the contents of `settings.xml` present in this repository 49 | - Replace `${app.packages.username}` with your github username 50 | - Replace `${app.packages.password}` with the generated PAT 51 | 52 | ##### GitHub Docker Registry 53 | 54 | - Run `docker login docker.pkg.github.com/eu-digital-green-certificates` before running further docker commands. 55 | - Use your GitHub username as username 56 | - Use the generated PAT as password 57 | 58 | ### Build 59 | 60 | Whether you cloned or downloaded the 'zipped' sources you will either find the sources in the chosen checkout-directory or get a zip file with the source code, which you can expand to a folder of your choice. 61 | 62 | In either case open a terminal pointing to the directory you put the sources in. The local build process is described afterwards depending on the way you choose. 63 | 64 | ### Build with maven 65 | * Check [settings.xml](settings.xml) in root folder and copy the servers to your own `~/.m2/settings.xml` to connect the GitHub repositories we use in our code. Provide your GitHub username and access token (see [GitHub Help](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token)) under the variables suggested. 66 | * Run `mvn clean package` from the project root folder 67 | 68 | ### Run with docker 69 | * Perform maven build as described above 70 | * Run `docker-compose up` from the project root folder 71 | 72 | After all containers have started you will be able to reach the application on your [local machine](http://localhost:8080/dgci/status) under port 8080. 73 | 74 | ## Documentation 75 | 76 | * [configuration manual](docs/configuration.md) 77 | * [developing configuration](docs/dev_config.md) 78 | 79 | ## Support and feedback 80 | 81 | The following channels are available for discussions, feedback, and support requests: 82 | 83 | | Type | Channel | 84 | | ------------------------ | ------------------------------------------------------ | 85 | | **Issues** | | 86 | | **Other requests** | | 87 | 88 | ## How to contribute 89 | 90 | Contribution and feedback is encouraged and always welcome. For more information about how to contribute, the project structure, as well as additional contribution information, see our [Contribution Guidelines](./CONTRIBUTING.md). By participating in this project, you agree to abide by its [Code of Conduct](./CODE_OF_CONDUCT.md) at all times. 91 | 92 | ## Contributors 93 | 94 | Our commitment to open source means that we are enabling -in fact encouraging- all interested parties to contribute and become part of its developer community. 95 | 96 | ## Licensing 97 | 98 | Copyright (C) 2021 T-Systems International GmbH and all other contributors 99 | 100 | Licensed under the **Apache License, Version 2.0** (the "License"); you may not use this file except in compliance with the License. 101 | 102 | You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0. 103 | 104 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the [LICENSE](./LICENSE) for the specific language governing permissions and limitations under the License. 105 | -------------------------------------------------------------------------------- /certs/test.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eu-digital-green-certificates/dgca-issuance-service/aed1ff285c0b7033c1ff2dc8c0efc6a532fcf948/certs/test.jks -------------------------------------------------------------------------------- /context/context.json: -------------------------------------------------------------------------------- 1 | { 2 | "origin": "DE", 3 | "versions": { 4 | "default": { 5 | "privacyUrl": "https://publications.europa.eu/en/web/about-us/legal-notices/eu-mobile-apps", 6 | "context": { 7 | "url": "https://issuance-dgca-test.cfapps.eu10.hana.ondemand.com/dgca-issuance-service/context", 8 | "pubKeys": [ 9 | "lKdU1EbQubxyDDm2q3N8KclZ2C94Num3xXjG0pk+3eI=", 10 | "r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=" 11 | ] 12 | }, 13 | "endpoints": { 14 | "claim": { 15 | "url": "https://dgca-issuance-web-eu-test.cfapps.eu10.hana.ondemand.com/dgca-issuance-service/dgci/wallet/claim", 16 | "pubKeys": [ 17 | "lKdU1EbQubxyDDm2q3N8KclZ2C94Num3xXjG0pk+3eI=", 18 | "r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=" 19 | ] 20 | }, 21 | "countryList": { 22 | "url": "https://dgca-businessrule-service-eu-test.cfapps.eu10.hana.ondemand.com/countrylist", 23 | "pubKeys": [ 24 | "lKdU1EbQubxyDDm2q3N8KclZ2C94Num3xXjG0pk+3eI=", 25 | "r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=" 26 | ] 27 | }, 28 | "rules": { 29 | "url": "https://dgca-businessrule-service-eu-test.cfapps.eu10.hana.ondemand.com/rules", 30 | "pubKeys": [ 31 | "lKdU1EbQubxyDDm2q3N8KclZ2C94Num3xXjG0pk+3eI=", 32 | "r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=" 33 | ] 34 | }, 35 | "valuesets": { 36 | "url": "https://dgca-businessrule-service-eu-test.cfapps.eu10.hana.ondemand.com/valuesets", 37 | "pubKeys": [ 38 | "lKdU1EbQubxyDDm2q3N8KclZ2C94Num3xXjG0pk+3eI=", 39 | "r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=" 40 | ] 41 | }, 42 | "revocation": { 43 | "url": "https://dgca-revocation-service-eu-test.cfapps.eu10.hana.ondemand.com", 44 | "pubKeys": [ 45 | "lKdU1EbQubxyDDm2q3N8KclZ2C94Num3xXjG0pk+3eI=", 46 | "r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=" 47 | ] 48 | } 49 | } 50 | }, 51 | "0.1.0": { 52 | "outdated": true 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | postgres: 5 | image: library/postgres:9.6 6 | container_name: dgc-issuance-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/dgc-issuance-service 20 | container_name: dgc-issuance-service 21 | ports: 22 | - 8080:8080 23 | environment: 24 | - SERVER_PORT=8080 25 | - SPRING_PROFILES_ACTIVE=cloud 26 | - SPRING_DATASOURCE_URL=jdbc:postgresql://dgc-issuance-service-postgres:5432/postgres 27 | - SPRING_DATASOURCE_USERNAME=postgres 28 | - SPRING_DATASOURCE_PASSWORD=postgres 29 | depends_on: 30 | - postgres 31 | networks: 32 | backend: 33 | persistence: 34 | restart: unless-stopped 35 | 36 | networks: 37 | backend: 38 | persistence: 39 | -------------------------------------------------------------------------------- /docs/configuration.md: -------------------------------------------------------------------------------- 1 | # Configuration Manual of Issuance Service 2 | 3 | # Introduction 4 | Issuance Service is developed as self-contained spring boot application. 5 | The issuance application consist of 2 parts 6 | * dgca-issuance-service - this spring boot application. Backend application that serves services as REST Endpoints. 7 | * [dgca-issuance-web](https://github.com/eu-digital-green-certificates/dgca-issuance-web) - Web frontend programmed using React framework 8 | The documentation here concern only dgca-issuance-service 9 | The configuration of issuance service is done using [spring boot configuration capabilities](https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config). 10 | 11 | # Default Configuration 12 | The default configuration (default profile) is defined by file src/main/resources/application.yml 13 | It is part of application deployment and is used as default. 14 | This default configuration is set to enable easy start of application out-of-the-box without additional dependencies but 15 | it is not usable for productive environment and miss some functionality. 16 | * usage of in-memory h2 database - all data are lost by restart 17 | * use public test keystore for signing - see certs/test.jks 18 | * no connection to dgc-gateway - the publish key endpoint is not usable 19 | 20 | # Configuring Signing Keys for EDGC (European Green Certificate) 21 | The issuance service needs private-public key to sign the EDGC. 22 | The keys are stored in jks-keystore file and protected by password. 23 | The private key is protected by additional password. 24 | There are cert/test.jks file that are provided for testing purposes only. 25 | You need to create own private key and keep it secret. 26 | The issuance service may use only one private key to sign the message. 27 | 28 | Following properties defines it (compare src/main/resources/application.yml) 29 | 30 | ``` 31 | issuance: 32 | keyStoreFile: certs/test.jks 33 | keyStorePassword: dgca 34 | certAlias: edgc_dev_ec 35 | privateKeyPassword: dgca 36 | ``` 37 | 38 | You may use the [Keystore Explorer](https://keystore-explorer.org/) to create jks keystore file and certificates. 39 | Following key types are supported 40 | * EC P-256 (for primary edgc algorihm) 41 | * RSA 2048 bit (for secondary egdc algorithm) 42 | 43 | For detailed informations see: https://ec.europa.eu/health/ehealth/covid-19_en 44 | 45 | # Configuring EDGC Parameters 46 | Following parameter configure the creation and handling of EDGC 47 | 48 | ``` 49 | issuance: 50 | dgciPrefix: dgci:V1:DE 51 | countryCode: DE 52 | tanExpirationHours: 2 53 | expiration: 54 | vaccination: 365 55 | recovery: 365 56 | test: 60 57 | ``` 58 | 59 | The dgciPrefix, countryCode and expiration are used to set up the EDGC fields. 60 | The tanExpirationHours is used to expire first TAN for wallet claim process. 61 | 62 | # Configuring Database 63 | The application needs a database to store dgci data and claim. 64 | The default database is in-memory H2 database and is usable for development only. 65 | In the spring profile "cloud" see src/main/resources/application-cloud.yml there are example postgres database configured. 66 | Consult [spring boot manuals](https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto-data-access) 67 | 68 | # Context File 69 | Context file is used by wallet app for url pinning. It will be server at /context endpoint without any other modification. 70 | If the context file is not configured and empty context json will be returned. 71 | 72 | ``` 73 | issuance: 74 | context: context.json 75 | ``` 76 | 77 | # Enabling and Disabling Endpoints 78 | 79 | The endpoints are grouped by functions and can be enabled or disabled per configuration. 80 | Default configuration for endpoints. 81 | 82 | ``` 83 | issuance: 84 | endpoints: 85 | frontendIssuing: true 86 | backendIssuing: false 87 | testTools: false 88 | wallet: true 89 | publishCert: true 90 | did: true 91 | ``` 92 | 93 | | group | endpoints | 94 | | ----------------|-------------------------- | 95 | | frontendIssuing | `POST /dgci`
`PUT /dgci/{id}` | 96 | | backendIssuing | `PUT /dgci` | 97 | | testTools | `POST /cert/dumpCBOR`
`POST /cert/decodeEGC`
`GET /cert/publicKey` | 98 | | wallet | `POST /dgci/wallet/claim` | 99 | | publishCert | `POST /dgci/certPublish` | 100 | | did | `HEAD /dgci/{dgciHash}`
`GET /dgci/{dgciHash}` | 101 | 102 | 103 | # Configuring Connection to EDGC Gateway 104 | The connection to EDGC is optional. 105 | The application uses DGC Gateway connector from dgc-lib to configure and use the dgc-gateway. 106 | If enabled you may use PUT /dgci/certPublish to public the public signing key to EDGC Gateway. 107 | 108 | For detailed information see: 109 | [dgc-lib repositoy](https://github.com/eu-digital-green-certificates/dgc-lib) 110 | 111 | There are no public free dgc-getway service therefore you will not find any connection parameters here 112 | 113 | # Access Control for REST Endpoints 114 | 115 | Overview of REST Connections and participating systems 116 | ![Issuance Service Overview](issuance-service-overview.svg) 117 | 118 | 119 | Access point for issue creation (must be protected) 120 | * /dgci/issue 121 | * /dgci/issue/* 122 | 123 | Access point to trigger public key publishing to EDGC Gateway (must be protected) 124 | * /dgci/certPublish 125 | 126 | Public Access point for wallet app 127 | * /dgci/wallet/* 128 | * /context 129 | * GET /dgci/{dgciHash} 130 | 131 | Developing/Test Endpoints 132 | * /cert/* 133 | 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /docs/dev_config.md: -------------------------------------------------------------------------------- 1 | # Configuring for Developing 2 | 3 | # Build 4 | The application use maven as build system. 5 | Some maven dependencies are github maven registry packages. The access to them need to be configured 6 | using github access token. 7 | See file settings.xml and consult the [gitub maven registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-apache-maven-registry) 8 | 9 | This 2 maven repository are used as dependencies (see also pom.xml) 10 | * https://github.com/eu-digital-green-certificates/dgc-lib 11 | * https://github.com/ehn-digital-green-development/hcert-kotlin 12 | 13 | # Running frontend and backend in dev mode 14 | 15 | Start dgca-issuance-service in your favorite java IDE. 16 | 17 | Main class: eu.europa.ec.dgc.issuance.DgcIssuanceApplication 18 | 19 | Pass following program argument to adapt endpoint prefix for need of dgca-issuance-web 20 | 21 | --server.servlet.context-path=/dgca-issuance-service 22 | 23 | The server starts on port 8080. See log output. 24 | 25 | Start [dgca-issuance-web](https://github.com/eu-digital-green-certificates/dgca-issuance-web) 26 | by using 27 | 28 | yarn start 29 | 30 | The frontend starts in developing mode and is available on http://localhost:3000. 31 | See frontend node developing server forward calls to backend (see package.json proxy entry) 32 | 33 | You can use the service by REST client as Postman or use frontend directly. 34 | You may also use swagger-ui 35 | 36 | * [The swagger ui (localhost)](http://localhost:8080/dgca-issuance-service/swagger) 37 | * [Open API endpoint (localhost)](http://localhost:8080/dgca-issuance-service/api/docs) 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /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 15 | tomcat 9.0.38 16 | 17 | CVE-2020-13943 18 | 19 | 20 | 21 | 22 | da214a6f44ee5811c97f3b53a6dda31edf25ac9e 23 | CVE-2016-9878 24 | CVE-2018-1270 25 | CVE-2018-1271 26 | CVE-2018-1272 27 | CVE-2020-5421 28 | 29 | 30 | the issues was fixed in used kotlin version 1.4.31 31 | CVE-2020-29582 32 | CVE-2019-10102 33 | CVE-2019-10101 34 | CVE-2019-10103 35 | 36 | 37 | H2 is only used for Unit Testing. Version 2.x includes major breaking changes. 38 | CVE-2021-23463 39 | CVE-2018-14335 40 | 41 | 42 | see https://github.com/jeremylong/DependencyCheck/issues/1827> 43 | CVE-2018-1258 44 | 45 | 46 | see https://github.com/jeremylong/DependencyCheck/issues/2952 47 | CVE-2011-2732 48 | CVE-2011-2731 49 | CVE-2012-5055 50 | 51 | 52 | 53 | 54 | da214a6f44ee5811c97f3b53a6dda31edf25ac9e 55 | CVE-2016-9878 56 | CVE-2018-1270 57 | CVE-2018-1271 58 | CVE-2018-1272 59 | CVE-2020-5421 60 | 61 | 62 | 63 | CVE-2021-22118 64 | 65 | 66 | Feature is not enabled in tomcat 67 | CVE-2022-23181 68 | 69 | 70 | -------------------------------------------------------------------------------- /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/main/java/eu/europa/ec/dgc/issuance/DgcIssuanceApplication.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.issuance; 22 | 23 | import eu.europa.ec.dgc.issuance.config.IssuanceConfigProperties; 24 | import io.swagger.v3.oas.annotations.OpenAPIDefinition; 25 | import io.swagger.v3.oas.annotations.info.Info; 26 | import io.swagger.v3.oas.annotations.info.License; 27 | import org.springframework.boot.SpringApplication; 28 | import org.springframework.boot.autoconfigure.SpringBootApplication; 29 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 30 | import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; 31 | 32 | /** 33 | * The Application class. 34 | */ 35 | @SpringBootApplication 36 | @EnableConfigurationProperties({IssuanceConfigProperties.class}) 37 | public class DgcIssuanceApplication extends SpringBootServletInitializer { 38 | 39 | /** 40 | * The main Method. 41 | * 42 | * @param args the args for the main method 43 | */ 44 | public static void main(String[] args) { 45 | SpringApplication.run(DgcIssuanceApplication.class, args); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/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.issuance.config; 22 | 23 | import eu.europa.ec.dgc.issuance.restapi.dto.ProblemReportDto; 24 | import eu.europa.ec.dgc.issuance.service.DdcGatewayException; 25 | import eu.europa.ec.dgc.issuance.service.DgciConflict; 26 | import eu.europa.ec.dgc.issuance.service.DgciNotFound; 27 | import eu.europa.ec.dgc.issuance.service.WrongRequest; 28 | import javax.validation.ConstraintViolationException; 29 | import lombok.RequiredArgsConstructor; 30 | import lombok.extern.slf4j.Slf4j; 31 | import org.springframework.context.annotation.Configuration; 32 | import org.springframework.http.HttpHeaders; 33 | import org.springframework.http.HttpStatus; 34 | import org.springframework.http.MediaType; 35 | import org.springframework.http.ResponseEntity; 36 | import org.springframework.validation.FieldError; 37 | import org.springframework.web.bind.MethodArgumentNotValidException; 38 | import org.springframework.web.bind.annotation.ControllerAdvice; 39 | import org.springframework.web.bind.annotation.ExceptionHandler; 40 | import org.springframework.web.context.request.WebRequest; 41 | import org.springframework.web.server.ResponseStatusException; 42 | import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; 43 | 44 | @ControllerAdvice 45 | @Configuration 46 | @RequiredArgsConstructor 47 | @Slf4j 48 | public class ErrorHandler extends ResponseEntityExceptionHandler { 49 | 50 | /** 51 | * Handles {@link ConstraintViolationException} when a validation failed. 52 | * 53 | * @param e the thrown {@link ConstraintViolationException} 54 | * @return A ResponseEntity with a ErrorMessage inside. 55 | */ 56 | @ExceptionHandler(ConstraintViolationException.class) 57 | public ResponseEntity handleException(ConstraintViolationException e) { 58 | log.error(e.getMessage()); 59 | return ResponseEntity 60 | .status(HttpStatus.BAD_REQUEST) 61 | .contentType(MediaType.APPLICATION_JSON) 62 | .body(new ProblemReportDto("", "Validation Error", "", e.getMessage())); 63 | } 64 | 65 | /** 66 | * Exception Handler to handle {@link DgciNotFound} Exceptions. 67 | */ 68 | @ExceptionHandler(DgciNotFound.class) 69 | public ResponseEntity handleException(DgciNotFound e) { 70 | log.error(e.getMessage()); 71 | return ResponseEntity 72 | .status(HttpStatus.NOT_FOUND) 73 | .contentType(MediaType.APPLICATION_JSON) 74 | .body(new ProblemReportDto("", "DGCI not found", "", e.getMessage())); 75 | } 76 | 77 | /** 78 | * Exception Handler to handle {@link DgciConflict} Exceptions. 79 | */ 80 | @ExceptionHandler(DgciConflict.class) 81 | public ResponseEntity handleException(DgciConflict e) { 82 | log.error(e.getMessage()); 83 | return ResponseEntity 84 | .status(HttpStatus.CONFLICT) 85 | .contentType(MediaType.APPLICATION_JSON) 86 | .body(new ProblemReportDto("", "DGCI conflict", "", e.getMessage())); 87 | } 88 | 89 | /** 90 | * Exception Handler to handle {@link DdcGatewayException} Exceptions. 91 | */ 92 | @ExceptionHandler(DdcGatewayException.class) 93 | public ResponseEntity handleException(DdcGatewayException e) { 94 | log.error(e.getMessage()); 95 | return ResponseEntity 96 | .status(HttpStatus.SERVICE_UNAVAILABLE) 97 | .contentType(MediaType.APPLICATION_JSON) 98 | .body(new ProblemReportDto("", "DGCGataway error: " + e.getMessage(), 99 | "", e.getCause() != null ? e.getCause().getMessage() : null)); 100 | } 101 | 102 | /** 103 | * Exception Handler to handle {@link WrongRequest} Exceptions. 104 | */ 105 | @ExceptionHandler(WrongRequest.class) 106 | public ResponseEntity handleException(WrongRequest e) { 107 | log.error(e.getMessage()); 108 | return ResponseEntity 109 | .status(HttpStatus.BAD_REQUEST) 110 | .contentType(MediaType.APPLICATION_JSON) 111 | .body(new ProblemReportDto("", "Wrong request", "", e.getMessage())); 112 | } 113 | 114 | /** 115 | * Global Exception Handler to wrap exceptions into a readable JSON Object. 116 | * 117 | * @param e the thrown exception 118 | * @return ResponseEntity with readable data. 119 | */ 120 | @ExceptionHandler(Exception.class) 121 | public ResponseEntity handleException(Exception e) { 122 | if (e instanceof ResponseStatusException) { 123 | return ResponseEntity 124 | .status(((ResponseStatusException) e).getStatus()) 125 | .contentType(MediaType.APPLICATION_JSON) 126 | .body(new ProblemReportDto("co", "prob", "val", "det")); 127 | } else { 128 | log.error("Uncatched exception", e); 129 | return ResponseEntity 130 | .status(HttpStatus.INTERNAL_SERVER_ERROR) 131 | .contentType(MediaType.APPLICATION_JSON) 132 | .body(new ProblemReportDto("0x500", "Internal Server Error", "", "")); 133 | } 134 | } 135 | 136 | @Override 137 | protected ResponseEntity handleMethodArgumentNotValid( 138 | MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { 139 | StringBuilder sb = new StringBuilder(); 140 | ex.getBindingResult().getAllErrors().forEach((error) -> { 141 | String fieldName = ((FieldError) error).getField(); 142 | String errorMessage = error.getDefaultMessage(); 143 | 144 | sb 145 | .append(error.getObjectName()) 146 | .append('.') 147 | .append(fieldName) 148 | .append(' ') 149 | .append(errorMessage) 150 | .append(", "); 151 | }); 152 | return ResponseEntity 153 | .status(HttpStatus.BAD_REQUEST) 154 | .contentType(MediaType.APPLICATION_JSON) 155 | .body(new ProblemReportDto("", "Validation Error", "", sb.toString())); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/config/HcertLibConfig.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.config; 2 | 3 | import ehn.techiop.hcert.kotlin.chain.Base45Service; 4 | import ehn.techiop.hcert.kotlin.chain.CborService; 5 | import ehn.techiop.hcert.kotlin.chain.CompressorService; 6 | import ehn.techiop.hcert.kotlin.chain.ContextIdentifierService; 7 | import ehn.techiop.hcert.kotlin.chain.CoseService; 8 | import ehn.techiop.hcert.kotlin.chain.HigherOrderValidationService; 9 | import ehn.techiop.hcert.kotlin.chain.SchemaValidationService; 10 | import ehn.techiop.hcert.kotlin.chain.impl.DefaultBase45Service; 11 | import ehn.techiop.hcert.kotlin.chain.impl.DefaultCborService; 12 | import ehn.techiop.hcert.kotlin.chain.impl.DefaultCompressorService; 13 | import ehn.techiop.hcert.kotlin.chain.impl.DefaultContextIdentifierService; 14 | import ehn.techiop.hcert.kotlin.chain.impl.DefaultCoseService; 15 | import ehn.techiop.hcert.kotlin.chain.impl.DefaultHigherOrderValidationService; 16 | import ehn.techiop.hcert.kotlin.chain.impl.DefaultSchemaValidationService; 17 | import eu.europa.ec.dgc.issuance.service.EhdCryptoService; 18 | import lombok.RequiredArgsConstructor; 19 | import org.springframework.context.annotation.Bean; 20 | import org.springframework.context.annotation.Configuration; 21 | 22 | @Configuration 23 | @RequiredArgsConstructor 24 | public class HcertLibConfig { 25 | private final EhdCryptoService ehdCryptoService; 26 | 27 | @Bean 28 | CoseService coseService() { 29 | return new DefaultCoseService(ehdCryptoService); 30 | } 31 | 32 | @Bean 33 | ContextIdentifierService contextIdentifierService() { 34 | return new DefaultContextIdentifierService(); 35 | } 36 | 37 | @Bean 38 | CompressorService compressorService() { 39 | return new DefaultCompressorService(); 40 | } 41 | 42 | @Bean 43 | Base45Service base45Service() { 44 | return new DefaultBase45Service(); 45 | } 46 | 47 | @Bean 48 | CborService cborService() { 49 | return new DefaultCborService(); 50 | } 51 | 52 | @Bean 53 | SchemaValidationService schemaValidationService() { 54 | return new DefaultSchemaValidationService(); 55 | } 56 | 57 | @Bean 58 | HigherOrderValidationService higherOrderValidationService() { 59 | return new DefaultHigherOrderValidationService(); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/config/IssuanceConfigProperties.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.issuance.config; 22 | 23 | import java.time.Duration; 24 | import java.time.temporal.ChronoUnit; 25 | import javax.validation.constraints.NotBlank; 26 | import javax.validation.constraints.NotNull; 27 | import javax.validation.constraints.Size; 28 | import lombok.Getter; 29 | import lombok.Setter; 30 | import org.springframework.boot.context.properties.ConfigurationProperties; 31 | import org.springframework.boot.convert.DurationUnit; 32 | 33 | @Getter 34 | @Setter 35 | @ConfigurationProperties("issuance") 36 | public class IssuanceConfigProperties { 37 | @NotBlank 38 | @Size(max = 20) 39 | private String dgciPrefix; 40 | private String keyStoreFile; 41 | private String keyStorePassword; 42 | private String certAlias; 43 | private String privateKeyPassword; 44 | @NotBlank 45 | @Size(max = 2) 46 | private String countryCode; 47 | @DurationUnit(ChronoUnit.HOURS) 48 | private Duration tanExpirationHours = Duration.ofHours(24); 49 | /** 50 | * JSON file that is provided to /context endpoint. 51 | */ 52 | private String contextFile; 53 | private String contextData = ""; 54 | 55 | @NotNull 56 | private Expiration expiration; 57 | 58 | @Getter 59 | @Setter 60 | public static class Expiration { 61 | @DurationUnit(ChronoUnit.DAYS) 62 | @NotNull 63 | private Duration vaccination; 64 | @DurationUnit(ChronoUnit.DAYS) 65 | @NotNull 66 | private Duration recovery; 67 | @DurationUnit(ChronoUnit.DAYS) 68 | @NotNull 69 | private Duration test; 70 | } 71 | 72 | @Getter 73 | @Setter 74 | @NotNull 75 | public static class Endpoints { 76 | private boolean frontendIssuing; 77 | private boolean backendIssuing; 78 | private boolean testTools; 79 | private boolean wallet; 80 | private boolean publishCert; 81 | private boolean did; 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/config/OpenApiConfig.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.config; 2 | 3 | import io.swagger.v3.oas.models.OpenAPI; 4 | import io.swagger.v3.oas.models.info.Info; 5 | import io.swagger.v3.oas.models.info.License; 6 | import java.util.Optional; 7 | import lombok.Generated; 8 | import lombok.RequiredArgsConstructor; 9 | import org.springframework.boot.info.BuildProperties; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | 13 | @Generated 14 | @Configuration 15 | @RequiredArgsConstructor 16 | public class OpenApiConfig { 17 | 18 | private final Optional buildProperties; 19 | 20 | /** 21 | * Configure the OpenApi bean with title and version. 22 | * 23 | * @return the OpenApi bean. 24 | */ 25 | @Bean 26 | public OpenAPI openApi() { 27 | String version; 28 | if (buildProperties.isPresent()) { 29 | version = buildProperties.get().getVersion(); 30 | } else { 31 | // build properties is not available if starting from IDE without running mvn before (so fake this) 32 | version = "dev"; 33 | } 34 | return new OpenAPI() 35 | .info(new Info() 36 | .title("Digital Green Certificate Issuance") 37 | .description("The API defines Issuance Service for digital green certificates.") 38 | .version(version) 39 | .license(new License() 40 | .name("Apache 2.0") 41 | .url("https://www.apache.org/licenses/LICENSE-2.0"))); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/config/btp/SapCredentialStoreCfEnvProcessor.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.config.btp; 2 | 3 | import io.pivotal.cfenv.core.CfCredentials; 4 | import io.pivotal.cfenv.core.CfService; 5 | import io.pivotal.cfenv.spring.boot.CfEnvProcessor; 6 | import io.pivotal.cfenv.spring.boot.CfEnvProcessorProperties; 7 | import java.util.Map; 8 | 9 | /** 10 | * Custom implementation of {@link CfEnvProcessor} for reading the SAP credential store parameters from the 11 | * VCAP_SERVICES environment variable and making them available as properties in the spring context. 12 | *

13 | * The following properties are available in the context after the processor is done: 14 | * 15 | *
    16 | *
  • sap.btp.credstore.url
  • 17 | *
  • sap.btp.credstore.password
  • 18 | *
  • sap.btp.credstore.username
  • 19 | *
  • sap.btp.credstore.clientPrivateKey
  • 20 | *
  • sap.btp.credstore.serverPublicKey
  • 21 | *
22 | *
23 | * 24 | * @see CfEnvProcessor 25 | */ 26 | public class SapCredentialStoreCfEnvProcessor implements CfEnvProcessor { 27 | 28 | private static final String CRED_STORE_SCHEME = "credstore"; 29 | private static final String CRED_STORE_PROPERTY_PREFIX = "sap.btp.credstore"; 30 | 31 | @Override 32 | public boolean accept(CfService service) { 33 | return service.existsByTagIgnoreCase(CRED_STORE_SCHEME, "securestore", "keystore", "credentials") 34 | || service.existsByLabelStartsWith(CRED_STORE_SCHEME) 35 | || service.existsByUriSchemeStartsWith(CRED_STORE_SCHEME); 36 | } 37 | 38 | @Override 39 | public void process(CfCredentials cfCredentials, Map properties) { 40 | properties.put(CRED_STORE_PROPERTY_PREFIX + ".url", cfCredentials.getString("url")); 41 | properties.put(CRED_STORE_PROPERTY_PREFIX + ".password", cfCredentials.getString("password")); 42 | properties.put(CRED_STORE_PROPERTY_PREFIX + ".username", cfCredentials.getString("username")); 43 | 44 | @SuppressWarnings("unchecked") 45 | Map encryption = (Map) cfCredentials.getMap().get("encryption"); 46 | if (encryption == null) { 47 | // Encryption features have been disabled on this BTP instance. 48 | properties.put(CRED_STORE_PROPERTY_PREFIX + ".clientPrivateKey", "encryption-disabled"); 49 | properties.put(CRED_STORE_PROPERTY_PREFIX + ".serverPublicKey", "encryption-disabled"); 50 | return; 51 | } 52 | 53 | String clientPrivateKey = encryption.get("client_private_key").toString(); 54 | String serverPublicKey = encryption.get("server_public_key").toString(); 55 | 56 | properties.put(CRED_STORE_PROPERTY_PREFIX + ".clientPrivateKey", clientPrivateKey); 57 | properties.put(CRED_STORE_PROPERTY_PREFIX + ".serverPublicKey", serverPublicKey); 58 | } 59 | 60 | @Override 61 | public CfEnvProcessorProperties getProperties() { 62 | return CfEnvProcessorProperties.builder() 63 | .propertyPrefixes(CRED_STORE_PROPERTY_PREFIX) 64 | .serviceName("CredentialStore") 65 | .build(); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/entity/DgciEntity.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.issuance.entity; 22 | 23 | import java.time.ZonedDateTime; 24 | import javax.persistence.Column; 25 | import javax.persistence.Entity; 26 | import javax.persistence.EnumType; 27 | import javax.persistence.Enumerated; 28 | import javax.persistence.GeneratedValue; 29 | import javax.persistence.GenerationType; 30 | import javax.persistence.Id; 31 | import javax.persistence.Table; 32 | import lombok.Data; 33 | 34 | @Data 35 | @Entity 36 | @Table(name = "dgci") 37 | public class DgciEntity { 38 | 39 | @Id 40 | @GeneratedValue(strategy = GenerationType.IDENTITY) 41 | @Column(name = "id") 42 | private Long id; 43 | 44 | @Column(name = "dgci", nullable = false, unique = true) 45 | private String dgci; 46 | 47 | @Column(name = "dgci_hash", nullable = false, unique = true, length = 512) 48 | private String dgciHash; 49 | 50 | @Column(name = "created_at", nullable = false) 51 | private ZonedDateTime createdAt = ZonedDateTime.now(); 52 | 53 | @Column(name = "expires_at") 54 | private ZonedDateTime expiresAt; 55 | 56 | @Column(name = "cert_hash", length = 512) 57 | private String certHash; 58 | 59 | @Column(name = "hashed_tan", length = 512) 60 | private String hashedTan; 61 | 62 | @Column(name = "green_certificate_type") 63 | @Enumerated(EnumType.STRING) 64 | private GreenCertificateType greenCertificateType; 65 | 66 | @Column(name = "retry_counter") 67 | private int retryCounter; 68 | 69 | @Column(name = "public_key", length = 1024) 70 | private String publicKey; 71 | 72 | @Column(name = "revoked") 73 | private boolean revoked; 74 | 75 | @Column(name = "claimed") 76 | private boolean claimed; 77 | 78 | @Column(name = "locked") 79 | private boolean locked; 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/entity/GreenCertificateType.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.issuance.entity; 22 | 23 | public enum GreenCertificateType { 24 | Vaccination, Recovery, Test 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/repository/DgciRepository.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.issuance.repository; 22 | 23 | import eu.europa.ec.dgc.issuance.entity.DgciEntity; 24 | import java.util.Optional; 25 | import org.springframework.data.jpa.repository.JpaRepository; 26 | 27 | public interface DgciRepository extends JpaRepository { 28 | 29 | Optional findByDgci(String dgci); 30 | 31 | Optional findByDgciHash(String dgciHash); 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/restapi/controller/CertController.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.issuance.restapi.controller; 22 | 23 | import eu.europa.ec.dgc.issuance.restapi.dto.EgcDecodeResult; 24 | import eu.europa.ec.dgc.issuance.restapi.dto.PublicKeyInfo; 25 | import eu.europa.ec.dgc.issuance.service.CertificateService; 26 | import eu.europa.ec.dgc.issuance.service.EdgcValidator; 27 | import eu.europa.ec.dgc.issuance.utils.CborDumpService; 28 | import io.swagger.v3.oas.annotations.Operation; 29 | import java.io.IOException; 30 | import java.io.StringWriter; 31 | import java.util.Base64; 32 | import lombok.AllArgsConstructor; 33 | import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; 34 | import org.springframework.http.MediaType; 35 | import org.springframework.http.ResponseEntity; 36 | import org.springframework.web.bind.annotation.GetMapping; 37 | import org.springframework.web.bind.annotation.PostMapping; 38 | import org.springframework.web.bind.annotation.RequestBody; 39 | import org.springframework.web.bind.annotation.RequestMapping; 40 | import org.springframework.web.bind.annotation.RestController; 41 | 42 | /** 43 | * The endpoint here are not public API and should be used only for developing testing purposes. 44 | */ 45 | @RestController 46 | @RequestMapping("/cert") 47 | @AllArgsConstructor 48 | @ConditionalOnExpression("${issuance.endpoints.testTools:false}") 49 | public class CertController { 50 | 51 | private final CertificateService certificateService; 52 | private final CborDumpService cborDumpService; 53 | private final EdgcValidator edgcValidator; 54 | 55 | /** 56 | * Rest Controller to decode CBOR. 57 | */ 58 | @Operation( 59 | summary = "dump base64 cbor byte stream, developing tool" 60 | ) 61 | @PostMapping(value = "dumpCBOR", consumes = MediaType.APPLICATION_JSON_VALUE) 62 | public ResponseEntity decodeCbor(@RequestBody String cbor) throws IOException { 63 | StringWriter stringWriter = new StringWriter(); 64 | 65 | cborDumpService.dumpCbor(Base64.getDecoder().decode(cbor), stringWriter); 66 | 67 | return ResponseEntity.ok(stringWriter.getBuffer().toString()); 68 | } 69 | 70 | /** 71 | * decode and debug edgc. 72 | * This method tries decode and debug the edgc certificate. 73 | * It tries to provide as much usable information as possible. 74 | * 75 | * @param prefixedEncodedCompressedCose edgc 76 | * @return decode result 77 | */ 78 | @Operation( 79 | summary = "decode edgc, developing tool", 80 | description = "decode and validate edgc raw string, extract raw data of each decode stage" 81 | ) 82 | @PostMapping(value = "decodeEGC", consumes = MediaType.APPLICATION_JSON_VALUE) 83 | public ResponseEntity decodeEgCert( 84 | @RequestBody String prefixedEncodedCompressedCose) { 85 | 86 | EgcDecodeResult egcDecodeResult = edgcValidator.decodeEdgc(prefixedEncodedCompressedCose); 87 | return ResponseEntity.ok(egcDecodeResult); 88 | } 89 | 90 | 91 | /** 92 | * Rest Controller to get Public Key Information. 93 | */ 94 | @Operation( 95 | summary = "get information about edgc public key, developing tool" 96 | ) 97 | @GetMapping(value = "publicKey") 98 | public ResponseEntity getPublic() { 99 | PublicKeyInfo result = new PublicKeyInfo( 100 | certificateService.getKidAsBase64(), 101 | certificateService.getAlgorithmIdentifier(), 102 | certificateService.getCertficate().getPublicKey().getAlgorithm(), 103 | certificateService.getCertficate().getPublicKey().getFormat(), 104 | Base64.getEncoder().encodeToString(certificateService.getCertficate().getPublicKey().getEncoded())); 105 | 106 | return ResponseEntity.ok(result); 107 | } 108 | 109 | 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/restapi/controller/CertPublisherController.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.restapi.controller; 2 | 3 | 4 | import eu.europa.ec.dgc.issuance.service.CertKeyPublisherService; 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.boot.autoconfigure.condition.ConditionalOnExpression; 10 | import org.springframework.http.HttpStatus; 11 | import org.springframework.http.ResponseEntity; 12 | import org.springframework.web.bind.annotation.PutMapping; 13 | import org.springframework.web.bind.annotation.RequestMapping; 14 | import org.springframework.web.bind.annotation.RestController; 15 | 16 | @RestController 17 | @RequestMapping("/dgci/certPublish") 18 | @AllArgsConstructor 19 | @ConditionalOnExpression("${issuance.endpoints.publishCert:false}") 20 | public class CertPublisherController { 21 | private final CertKeyPublisherService certKeyPublisherService; 22 | 23 | @Operation( 24 | summary = "publish edgc signing public key to dgc gateway", 25 | description = "public key need to be published so the created edgc can be validated." 26 | + " The signature is validated agains published keys" 27 | ) 28 | @ApiResponses(value = { 29 | @ApiResponse(responseCode = "204", description = "cert published")} 30 | ) 31 | @PutMapping(value = "") 32 | public ResponseEntity publishEdgcKeyToGateway() { 33 | certKeyPublisherService.publishKey(); 34 | return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/restapi/controller/ContextController.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.restapi.controller; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import eu.europa.ec.dgc.issuance.service.ContextService; 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.boot.autoconfigure.condition.ConditionalOnExpression; 10 | import org.springframework.http.ResponseEntity; 11 | import org.springframework.web.bind.annotation.GetMapping; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | import org.springframework.web.bind.annotation.RestController; 14 | 15 | @RestController 16 | @RequestMapping("/context") 17 | @AllArgsConstructor 18 | @ConditionalOnExpression("${issuance.endpoints.wallet:false}") 19 | public class ContextController { 20 | private final ContextService contextService; 21 | 22 | @Operation( 23 | summary = "provide configuration information for wallet app", 24 | description = "list of claim endpoints for wallet app" 25 | ) 26 | @ApiResponses(value = { 27 | @ApiResponse(responseCode = "200", description = "server list")} 28 | ) 29 | @GetMapping(value = "") 30 | public ResponseEntity context() { 31 | return ResponseEntity.ok(contextService.getContextDefinition()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/restapi/controller/DgciBackendController.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.restapi.controller; 2 | 3 | import eu.europa.ec.dgc.issuance.restapi.dto.EgdcCodeData; 4 | import eu.europa.ec.dgc.issuance.service.DgciService; 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.boot.autoconfigure.condition.ConditionalOnExpression; 10 | import org.springframework.http.MediaType; 11 | import org.springframework.http.ResponseEntity; 12 | import org.springframework.web.bind.annotation.PutMapping; 13 | import org.springframework.web.bind.annotation.RequestBody; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | import org.springframework.web.bind.annotation.RestController; 16 | 17 | @RestController 18 | @RequestMapping("/dgci") 19 | @AllArgsConstructor 20 | @ConditionalOnExpression("${issuance.endpoints.backendIssuing:false}") 21 | public class DgciBackendController { 22 | private final DgciService dgciService; 23 | 24 | @Operation( 25 | summary = "create qr code of edgc", 26 | description = "create edgc for given data" 27 | ) 28 | @ApiResponses(value = { 29 | @ApiResponse(responseCode = "200", description = "signed edgc qr code created"), 30 | @ApiResponse(responseCode = "400", description = "wrong issue data")}) 31 | @PutMapping(value = "/issue", consumes = MediaType.APPLICATION_JSON_VALUE) 32 | public ResponseEntity createEdgc(@RequestBody String eudgc) { 33 | EgdcCodeData egdcCodeData = dgciService.createEdgc(eudgc); 34 | return ResponseEntity.ok(egdcCodeData); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/restapi/controller/DgciController.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.issuance.restapi.controller; 22 | 23 | import eu.europa.ec.dgc.issuance.restapi.dto.DgciIdentifier; 24 | import eu.europa.ec.dgc.issuance.restapi.dto.DgciInit; 25 | import eu.europa.ec.dgc.issuance.restapi.dto.IssueData; 26 | import eu.europa.ec.dgc.issuance.restapi.dto.SignatureData; 27 | import eu.europa.ec.dgc.issuance.service.DgciService; 28 | import io.swagger.v3.oas.annotations.Operation; 29 | import io.swagger.v3.oas.annotations.responses.ApiResponse; 30 | import io.swagger.v3.oas.annotations.responses.ApiResponses; 31 | import javax.validation.Valid; 32 | import lombok.AllArgsConstructor; 33 | import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; 34 | import org.springframework.http.MediaType; 35 | import org.springframework.http.ResponseEntity; 36 | import org.springframework.web.bind.annotation.PathVariable; 37 | import org.springframework.web.bind.annotation.PostMapping; 38 | import org.springframework.web.bind.annotation.PutMapping; 39 | import org.springframework.web.bind.annotation.RequestBody; 40 | import org.springframework.web.bind.annotation.RequestMapping; 41 | import org.springframework.web.bind.annotation.RestController; 42 | 43 | @RestController 44 | @RequestMapping("/dgci") 45 | @AllArgsConstructor 46 | @ConditionalOnExpression("${issuance.endpoints.frontendIssuing:false}") 47 | public class DgciController { 48 | 49 | private final DgciService dgciService; 50 | 51 | @Operation( 52 | summary = "Prepares an DGCI for the Code Generation in Frontend", 53 | description = "Creates new dgci and return meta data for certificate creation" 54 | ) 55 | @PostMapping(value = "/issue", consumes = MediaType.APPLICATION_JSON_VALUE) 56 | public ResponseEntity initDgci(@Valid @RequestBody DgciInit dgciInit) { 57 | return ResponseEntity.ok(dgciService.initDgci(dgciInit)); 58 | } 59 | 60 | @Operation( 61 | summary = "Completes the issuing process", 62 | description = "calculate cose signature for given certificate hash, " 63 | + "generate TAN and update DGCI Registry database" 64 | ) 65 | @ApiResponses(value = { 66 | @ApiResponse(responseCode = "201", description = "signature created"), 67 | @ApiResponse(responseCode = "404", description = "dgci with related id not found"), 68 | @ApiResponse(responseCode = "400", description = "wrong issue data")}) 69 | @PutMapping(value = "/issue/{id}", consumes = MediaType.APPLICATION_JSON_VALUE) 70 | public ResponseEntity finalizeDgci(@PathVariable String id, @Valid @RequestBody IssueData issueData) 71 | throws Exception { 72 | return ResponseEntity.ok(dgciService.finishDgci(id, issueData)); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/restapi/controller/DgciDidController.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.restapi.controller; 2 | 3 | import com.nimbusds.jose.util.Base64URL; 4 | import eu.europa.ec.dgc.issuance.restapi.dto.DidDocument; 5 | import eu.europa.ec.dgc.issuance.service.DgciService; 6 | import io.swagger.v3.oas.annotations.Operation; 7 | import io.swagger.v3.oas.annotations.Parameter; 8 | import io.swagger.v3.oas.annotations.responses.ApiResponse; 9 | import io.swagger.v3.oas.annotations.responses.ApiResponses; 10 | import java.util.Base64; 11 | import lombok.AllArgsConstructor; 12 | import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; 13 | import org.springframework.http.HttpStatus; 14 | import org.springframework.http.ResponseEntity; 15 | import org.springframework.web.bind.annotation.GetMapping; 16 | import org.springframework.web.bind.annotation.PathVariable; 17 | import org.springframework.web.bind.annotation.RequestMapping; 18 | import org.springframework.web.bind.annotation.RequestMethod; 19 | import org.springframework.web.bind.annotation.RestController; 20 | 21 | @RestController 22 | @RequestMapping("/dgci") 23 | @AllArgsConstructor 24 | @ConditionalOnExpression("${issuance.endpoints.did:false}") 25 | public class DgciDidController { 26 | private final DgciService dgciService; 27 | 28 | /** 29 | * dgci status. 30 | * @param dgciHash hash 31 | * @return response 32 | */ 33 | @Operation( 34 | summary = "Checks the status of DGCI", 35 | description = "Produce status HTTP code message" 36 | ) 37 | @ApiResponses(value = { 38 | @ApiResponse(responseCode = "204", description = "dgci exists"), 39 | @ApiResponse(responseCode = "424", description = "dgci locked"), 40 | @ApiResponse(responseCode = "404", description = "dgci not found")}) 41 | @RequestMapping(value = "/{dgciHash}",method = RequestMethod.HEAD) 42 | public ResponseEntity dgciStatus( 43 | @Parameter(description = "Base64URL encoded SHA-256 hash from dgci alias uvci", required = true) 44 | @PathVariable(name = "dgciHash") String dgciHash) { 45 | String dgciHashBase64 = Base64.getEncoder().encodeToString(Base64URL.from(dgciHash).decode()); 46 | HttpStatus httpStatus; 47 | switch (dgciService.checkDgciStatus(dgciHashBase64)) { 48 | case EXISTS: 49 | httpStatus = HttpStatus.NO_CONTENT; 50 | break; 51 | case LOCKED: 52 | httpStatus = HttpStatus.LOCKED; 53 | break; 54 | case NOT_EXISTS: 55 | httpStatus = HttpStatus.NOT_FOUND; 56 | break; 57 | default: 58 | throw new IllegalArgumentException("unknown dgci status"); 59 | } 60 | return ResponseEntity.status(httpStatus).build(); 61 | } 62 | 63 | @Operation( 64 | summary = "Returns a DID document", 65 | description = "Return a DID document" 66 | ) 67 | @GetMapping(value = "/{dgciHash}") 68 | public ResponseEntity getDidDocument( 69 | @Parameter(description = "Base64URL encoded SHA-256 hash from dgci alias uvci", required = true) 70 | @PathVariable(name = "dgciHash") String dgciHash) { 71 | String dgciHashBase64 = Base64.getEncoder().encodeToString(Base64URL.from(dgciHash).decode()); 72 | return ResponseEntity.ok(dgciService.getDidDocument(dgciHashBase64)); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/restapi/controller/WalletController.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.issuance.restapi.controller; 22 | 23 | import eu.europa.ec.dgc.issuance.restapi.dto.ClaimRequest; 24 | import eu.europa.ec.dgc.issuance.restapi.dto.ClaimResponse; 25 | import eu.europa.ec.dgc.issuance.service.DgciService; 26 | import io.swagger.v3.oas.annotations.Operation; 27 | import io.swagger.v3.oas.annotations.responses.ApiResponse; 28 | import io.swagger.v3.oas.annotations.responses.ApiResponses; 29 | import javax.validation.Valid; 30 | import lombok.AllArgsConstructor; 31 | import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; 32 | import org.springframework.http.MediaType; 33 | import org.springframework.http.ResponseEntity; 34 | import org.springframework.web.bind.annotation.PostMapping; 35 | import org.springframework.web.bind.annotation.RequestBody; 36 | import org.springframework.web.bind.annotation.RequestMapping; 37 | import org.springframework.web.bind.annotation.RestController; 38 | 39 | @RestController 40 | @RequestMapping("/dgci/wallet") 41 | @AllArgsConstructor 42 | @ConditionalOnExpression("${issuance.endpoints.wallet:false}") 43 | public class WalletController { 44 | private final DgciService dgciService; 45 | 46 | @Operation( 47 | summary = "Claims the DGCI for a TAN and certificate Holder", 48 | description = "claim, check signatue, cert hash, TAN, assign dgci public key " 49 | ) 50 | @ApiResponses(value = { 51 | @ApiResponse(responseCode = "204", description = "successful claim"), 52 | @ApiResponse(responseCode = "404", description = "dgci not found"), 53 | @ApiResponse(responseCode = "400", description = "wrong claim data")}) 54 | @PostMapping(value = "/claim", consumes = MediaType.APPLICATION_JSON_VALUE) 55 | public ResponseEntity claim(@Valid @RequestBody ClaimRequest claimRequest) { 56 | return ResponseEntity.ok(dgciService.claim(claimRequest)); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/restapi/dto/CertificateTypeDto.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.issuance.restapi.dto; 22 | 23 | public enum CertificateTypeDto { 24 | AUTHENTICATION, 25 | UPLOAD, 26 | CSCA 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/restapi/dto/ClaimRequest.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.issuance.restapi.dto; 22 | 23 | import com.fasterxml.jackson.annotation.JsonProperty; 24 | import javax.validation.constraints.NotBlank; 25 | import javax.validation.constraints.NotNull; 26 | import javax.validation.constraints.Size; 27 | import lombok.Data; 28 | 29 | @Data 30 | public class ClaimRequest { 31 | @JsonProperty("DGCI") 32 | @NotBlank 33 | @Size(max = 100) 34 | private String dgci; 35 | @NotBlank 36 | @Size(max = 100) 37 | @JsonProperty("certhash") 38 | private String certHash; 39 | @NotBlank 40 | @Size(max = 100) 41 | @JsonProperty("TANHash") 42 | private String tanHash; 43 | @NotNull 44 | private PublicKey publicKey; 45 | @NotBlank 46 | private String sigAlg; 47 | @NotBlank 48 | private String signature; 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/restapi/dto/ClaimResponse.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.issuance.restapi.dto; 22 | 23 | import lombok.Data; 24 | 25 | @Data 26 | public class ClaimResponse { 27 | String tan; 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/restapi/dto/DgciIdentifier.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.issuance.restapi.dto; 22 | 23 | import lombok.AllArgsConstructor; 24 | import lombok.Data; 25 | 26 | @Data 27 | @AllArgsConstructor 28 | public class DgciIdentifier { 29 | private String id; 30 | private String dgci; 31 | private String kid; 32 | private int algId; 33 | private String countryCode; 34 | private long expired; 35 | private long expiredDuration; 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/restapi/dto/DgciInit.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.issuance.restapi.dto; 22 | 23 | import eu.europa.ec.dgc.issuance.entity.GreenCertificateType; 24 | import javax.validation.constraints.NotNull; 25 | import lombok.Data; 26 | 27 | @Data 28 | public class DgciInit { 29 | @NotNull 30 | private GreenCertificateType greenCertificateType; 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/restapi/dto/DidAuthentication.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.issuance.restapi.dto; 22 | 23 | import com.fasterxml.jackson.databind.JsonNode; 24 | import lombok.Data; 25 | 26 | @Data 27 | public class DidAuthentication { 28 | private String type; 29 | private String controller; 30 | private String expires; 31 | private JsonNode publicKeyJsw; 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/restapi/dto/DidDocument.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.issuance.restapi.dto; 22 | 23 | import com.fasterxml.jackson.annotation.JsonProperty; 24 | import com.fasterxml.jackson.annotation.JsonPropertyOrder; 25 | import java.util.List; 26 | import lombok.Data; 27 | 28 | @Data 29 | @JsonPropertyOrder({"@context", "id", "controller"}) 30 | public class DidDocument { 31 | @JsonProperty("@context") 32 | private String context; 33 | private String id; 34 | private String controller; 35 | private List authentication; 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/restapi/dto/EgcDecodeResult.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.restapi.dto; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import lombok.Data; 5 | 6 | @Data 7 | public class EgcDecodeResult { 8 | private boolean validated; 9 | private String cborDump; 10 | private JsonNode cborJson; 11 | private String cborHex; 12 | private String cborBase64; 13 | private String coseHex; 14 | private String coseProtected; 15 | private JsonNode coseProtectedJson; 16 | private String coseUnprotected; 17 | private JsonNode coseUnprotectedJson; 18 | private String errorMessage; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/restapi/dto/EgdcCodeData.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.restapi.dto; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class EgdcCodeData { 7 | String dgci; 8 | String qrCode; 9 | String tan; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/restapi/dto/IssueData.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.issuance.restapi.dto; 22 | 23 | import javax.validation.constraints.NotBlank; 24 | import javax.validation.constraints.Size; 25 | import lombok.Data; 26 | 27 | @Data 28 | public class IssueData { 29 | @NotBlank 30 | @Size(max = 512) 31 | private String hash; 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/restapi/dto/ProblemReportDto.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.issuance.restapi.dto; 22 | 23 | import io.swagger.v3.oas.annotations.media.Schema; 24 | import lombok.AllArgsConstructor; 25 | import lombok.Data; 26 | 27 | @Schema( 28 | name = "ProblemReport", 29 | type = "object", 30 | example = "{\n" 31 | + "\"code\":\"0x001\",\n" 32 | + "\"problem\":\"[PROBLEM]\",\n" 33 | + "\"sent value\":\"[Sent Value]\",\n" 34 | + "\"details\":\"...\"\n" 35 | + "}" 36 | ) 37 | @Data 38 | @AllArgsConstructor 39 | public class ProblemReportDto { 40 | 41 | private String code; 42 | 43 | private String problem; 44 | 45 | private String sendValue; 46 | 47 | private String details; 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/restapi/dto/PublicKey.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.issuance.restapi.dto; 22 | 23 | import javax.validation.constraints.NotBlank; 24 | import javax.validation.constraints.Size; 25 | import lombok.Data; 26 | 27 | @Data 28 | public class PublicKey { 29 | @NotBlank 30 | @Size(max = 100) 31 | private String type; 32 | @NotBlank 33 | @Size(max = 512) 34 | private String value; 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/restapi/dto/PublicKeyInfo.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.restapi.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | @Data 7 | @AllArgsConstructor 8 | public class PublicKeyInfo { 9 | private String kid; 10 | private int algid; 11 | private String keyType; 12 | private String publicKeyFormat; 13 | private String publicKeyEncoded; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/restapi/dto/SignatureData.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.issuance.restapi.dto; 22 | 23 | import lombok.AllArgsConstructor; 24 | import lombok.Data; 25 | 26 | @Data 27 | @AllArgsConstructor 28 | public class SignatureData { 29 | private String tan; 30 | private String signature; 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/service/CertKeyPublisherService.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.service; 2 | 3 | public interface CertKeyPublisherService { 4 | /** 5 | * Publishes the signing certificate to the DGC gateway. 6 | */ 7 | void publishKey(); 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/service/CertKeyPublisherServiceImpl.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.service; 2 | 3 | 4 | import eu.europa.ec.dgc.gateway.connector.DgcGatewayUploadConnector; 5 | import java.util.Optional; 6 | import lombok.RequiredArgsConstructor; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.context.annotation.Profile; 9 | import org.springframework.stereotype.Component; 10 | 11 | @Component 12 | @Profile("!btp") 13 | @Slf4j 14 | @RequiredArgsConstructor 15 | public class CertKeyPublisherServiceImpl implements CertKeyPublisherService { 16 | private final CertificateService certificateService; 17 | private final Optional dgcGatewayUploadConnector; 18 | 19 | @Override 20 | public void publishKey() { 21 | if (dgcGatewayUploadConnector.isPresent()) { 22 | log.info("start publish certificate to gateway"); 23 | DgcGatewayUploadConnector connector = dgcGatewayUploadConnector.get(); 24 | try { 25 | connector.uploadTrustedCertificate(certificateService.getCertficate()); 26 | log.info("certificate uploaded to gateway"); 27 | } catch (DgcGatewayUploadConnector.DgcCertificateUploadException e) { 28 | log.error("can not upload certificate to gateway", e); 29 | throw new DdcGatewayException("error during gateway connector communication", e); 30 | } 31 | } else { 32 | log.warn("can not publish certificate to gateway, because the gateway connector is not enabled"); 33 | throw new DdcGatewayException("gateway connector is configured as disabled"); 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/service/CertificatePrivateKeyProvider.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.service; 2 | 3 | import java.security.PrivateKey; 4 | import java.security.cert.Certificate; 5 | 6 | public interface CertificatePrivateKeyProvider { 7 | Certificate getCertificate(); 8 | 9 | PrivateKey getPrivateKey(); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/service/CertificateService.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.issuance.service; 22 | 23 | import COSE.AlgorithmID; 24 | import eu.europa.ec.dgc.utils.CertificateUtils; 25 | import java.security.PrivateKey; 26 | import java.security.PublicKey; 27 | import java.security.cert.X509Certificate; 28 | import java.security.interfaces.ECPublicKey; 29 | import java.security.interfaces.RSAPublicKey; 30 | import java.util.Base64; 31 | import javax.annotation.PostConstruct; 32 | import lombok.RequiredArgsConstructor; 33 | import lombok.extern.slf4j.Slf4j; 34 | import org.springframework.beans.factory.annotation.Autowired; 35 | import org.springframework.beans.factory.annotation.Qualifier; 36 | import org.springframework.stereotype.Component; 37 | 38 | @Component 39 | @Slf4j 40 | public class CertificateService { 41 | private final CertificatePrivateKeyProvider certificatePrivateKeyProvider; 42 | private final SigningService signingService; 43 | private byte[] kid; 44 | 45 | @Autowired 46 | public CertificateService(@Qualifier("issuerKeyProvider") CertificatePrivateKeyProvider 47 | certificatePrivateKeyProvider, SigningService signingService) { 48 | this.certificatePrivateKeyProvider = certificatePrivateKeyProvider; 49 | this.signingService = signingService; 50 | } 51 | 52 | /** 53 | * compute kid. 54 | * key identifier needed for cose 55 | */ 56 | @PostConstruct 57 | public void computeKid() { 58 | CertificateUtils certificateUtils = new CertificateUtils(); 59 | String kidBase64 = certificateUtils.getCertKid(getCertficate()); 60 | kid = Base64.getDecoder().decode(kidBase64); 61 | } 62 | 63 | public byte[] getKid() { 64 | return kid; 65 | } 66 | 67 | public String getKidAsBase64() { 68 | return Base64.getEncoder().encodeToString(kid); 69 | } 70 | 71 | public X509Certificate getCertficate() { 72 | return (X509Certificate) certificatePrivateKeyProvider.getCertificate(); 73 | } 74 | 75 | public PublicKey getPublicKey() { 76 | return certificatePrivateKeyProvider.getCertificate().getPublicKey(); 77 | } 78 | 79 | public PrivateKey getPrivateKey() { 80 | return certificatePrivateKeyProvider.getPrivateKey(); 81 | } 82 | 83 | /** 84 | * sign hash. 85 | * 86 | * @param base64Hash base64Hash 87 | * @return signature as base64 88 | */ 89 | public String signHash(String base64Hash) { 90 | byte[] hashBytes = Base64.getDecoder().decode(base64Hash); 91 | byte[] signature = signingService.signHash(hashBytes, certificatePrivateKeyProvider.getPrivateKey()); 92 | return Base64.getEncoder().encodeToString(signature); 93 | } 94 | 95 | public byte[] publicKey() { 96 | return certificatePrivateKeyProvider.getCertificate().getPublicKey().getEncoded(); 97 | } 98 | 99 | /** 100 | * Method to get the Algorithm Identifier of Public Key. 101 | * 102 | * @return CBOR AlgorithmID As Integer 103 | */ 104 | public int getAlgorithmIdentifier() { 105 | PublicKey publicKey = certificatePrivateKeyProvider.getCertificate().getPublicKey(); 106 | if (publicKey instanceof RSAPublicKey) { 107 | return AlgorithmID.RSA_PSS_256.AsCBOR().AsInt32(); 108 | } else if (publicKey instanceof ECPublicKey) { 109 | return AlgorithmID.ECDSA_256.AsCBOR().AsInt32(); 110 | } else { 111 | throw new IllegalArgumentException("unsupported key type"); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/service/ConfigurableCwtService.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.service; 2 | 3 | import com.upokecenter.cbor.CBORObject; 4 | import ehn.techiop.hcert.kotlin.chain.CwtService; 5 | import ehn.techiop.hcert.kotlin.chain.VerificationResult; 6 | import ehn.techiop.hcert.kotlin.crypto.CwtHeaderKeys; 7 | import ehn.techiop.hcert.kotlin.data.CborObject; 8 | import ehn.techiop.hcert.kotlin.data.GreenCertificate; 9 | import eu.europa.ec.dgc.issuance.config.IssuanceConfigProperties; 10 | import kotlinx.serialization.json.Json; 11 | import lombok.RequiredArgsConstructor; 12 | import org.jetbrains.annotations.NotNull; 13 | import org.springframework.stereotype.Service; 14 | 15 | @Service 16 | @RequiredArgsConstructor 17 | public class ConfigurableCwtService implements CwtService { 18 | private final ExpirationService expirationService; 19 | private final IssuanceConfigProperties issuanceConfigProperties; 20 | 21 | @NotNull 22 | @Override 23 | public CborObject decode(@NotNull byte[] bytes, @NotNull VerificationResult verificationResult) { 24 | throw new UnsupportedOperationException("decoding not supported"); 25 | } 26 | 27 | @NotNull 28 | @Override 29 | public byte[] encode(@NotNull byte[] bytes) { 30 | CBORObject cwtMap = CBORObject.NewMap(); 31 | cwtMap.Add(CwtHeaderKeys.ISSUER.getIntVal(), issuanceConfigProperties.getCountryCode()); 32 | CBORObject dcc = CBORObject.DecodeFromBytes(bytes); 33 | GreenCertificate greenCertificate = Json.Default.decodeFromString(GreenCertificate.Companion.serializer(), 34 | dcc.ToJSONString()); 35 | ExpirationService.CwtTimeFields cwtTimes = expirationService.calculateCwtExpiration(greenCertificate); 36 | 37 | cwtMap.Add(CwtHeaderKeys.ISSUED_AT.getIntVal(), cwtTimes.issuedAt); 38 | cwtMap.Add(CwtHeaderKeys.EXPIRATION.getIntVal(), cwtTimes.expiration); 39 | CBORObject hcertMap = CBORObject.NewMap(); 40 | hcertMap.Add(CwtHeaderKeys.EUDGC_IN_HCERT.getIntVal(),dcc); 41 | cwtMap.Add(CwtHeaderKeys.HCERT.getIntVal(), hcertMap); 42 | return cwtMap.EncodeToBytes(); 43 | } 44 | 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/service/ContextService.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.service; 2 | 3 | 4 | import com.fasterxml.jackson.databind.JsonNode; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import com.fasterxml.jackson.databind.node.JsonNodeFactory; 7 | import com.fasterxml.jackson.databind.node.ObjectNode; 8 | import eu.europa.ec.dgc.issuance.config.IssuanceConfigProperties; 9 | import java.io.File; 10 | import java.io.IOException; 11 | import javax.annotation.PostConstruct; 12 | import lombok.RequiredArgsConstructor; 13 | import lombok.extern.slf4j.Slf4j; 14 | import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; 15 | import org.springframework.stereotype.Component; 16 | 17 | @Component 18 | @Slf4j 19 | @RequiredArgsConstructor 20 | @ConditionalOnExpression("${issuance.endpoints.wallet:false}") 21 | public class ContextService { 22 | private final IssuanceConfigProperties issuanceConfigProperties; 23 | private JsonNode contextDefinition; 24 | 25 | /** 26 | * load json context file. 27 | */ 28 | @PostConstruct 29 | public void loadContextFile() { 30 | if (issuanceConfigProperties.getContextData().isEmpty()) { 31 | if (issuanceConfigProperties.getContextFile() != null 32 | && issuanceConfigProperties.getContextFile().length() > 0) { 33 | File contextFile = new File(issuanceConfigProperties.getContextFile()); 34 | if (!contextFile.isFile()) { 35 | throw new IllegalArgumentException("configured context file can not be found: " + contextFile); 36 | } 37 | ObjectMapper mapper = new ObjectMapper(); 38 | try { 39 | contextDefinition = mapper.readTree(contextFile); 40 | log.info("context file loaded from: " + contextFile); 41 | } catch (IOException e) { 42 | throw new IllegalArgumentException("can not read json context file: " + contextFile, e); 43 | } 44 | } else { 45 | log.warn("the context json file not configured (property: issuance.contextFile)." 46 | + " The empty context file is generated instead"); 47 | JsonNodeFactory jsonNodeFactory = JsonNodeFactory.instance; 48 | ObjectNode contextObj = jsonNodeFactory.objectNode(); 49 | contextObj.set("Origin", jsonNodeFactory.textNode(issuanceConfigProperties.getCountryCode())); 50 | contextObj.set("versions", jsonNodeFactory.objectNode()); 51 | contextDefinition = contextObj; 52 | } 53 | } else { 54 | ObjectMapper mapper = new ObjectMapper(); 55 | try { 56 | contextDefinition = mapper.readTree(issuanceConfigProperties.getContextData()); 57 | log.info("context file loaded from Environment variable 'ContextData'"); 58 | } catch (IOException e) { 59 | throw new IllegalArgumentException("can not read json from Environment variable 'ContextData'", e); 60 | } 61 | } 62 | 63 | 64 | } 65 | 66 | public JsonNode getContextDefinition() { 67 | return contextDefinition; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/service/DdcGatewayException.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.service; 2 | 3 | public class DdcGatewayException extends RuntimeException { 4 | public DdcGatewayException(String message) { 5 | super(message); 6 | } 7 | 8 | public DdcGatewayException(String message, Throwable inner) { 9 | super(message, inner); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/service/DgciConflict.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.service; 2 | 3 | public class DgciConflict extends RuntimeException { 4 | public DgciConflict(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/service/DgciGenerator.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.issuance.service; 22 | 23 | import eu.europa.ec.dgc.issuance.config.IssuanceConfigProperties; 24 | import eu.europa.ec.dgc.issuance.utils.DgciUtil; 25 | import java.util.UUID; 26 | import javax.annotation.PostConstruct; 27 | import lombok.RequiredArgsConstructor; 28 | import org.springframework.stereotype.Component; 29 | 30 | @Component 31 | @RequiredArgsConstructor 32 | public class DgciGenerator { 33 | private final IssuanceConfigProperties issuanceConfigProperties; 34 | 35 | private static final String CODE_POINTS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/:"; 36 | 37 | /** 38 | * Check if dgci prefix contains character suitable for checksum calculation. 39 | */ 40 | @PostConstruct 41 | public void checkPrefix() { 42 | String dgciPrefix = issuanceConfigProperties.getDgciPrefix(); 43 | if (dgciPrefix != null) { 44 | for (int i = 0;i < dgciPrefix.length();i++) { 45 | if (CODE_POINTS.indexOf(dgciPrefix.charAt(i)) < 0) { 46 | throw new IllegalArgumentException("configured DGCI prefix '" 47 | + dgciPrefix + "' contains invalid character '" 48 | + dgciPrefix.charAt(i) + "' only following are supported " + CODE_POINTS); 49 | } 50 | } 51 | } 52 | } 53 | 54 | /** 55 | * Generates a new DGCI. 56 | * 57 | * @return DGCI as String 58 | */ 59 | public String newDgci() { 60 | StringBuilder sb = new StringBuilder(); 61 | sb.append(issuanceConfigProperties.getDgciPrefix()).append(':'); 62 | sb.append(DgciUtil.encodeDgci(UUID.randomUUID())); 63 | sb.append(generateCheckCharacter(sb.toString())); 64 | return sb.toString(); 65 | } 66 | 67 | // see https://en.wikipedia.org/wiki/Luhn_mod_N_algorithm 68 | private char generateCheckCharacter(String input) { 69 | int factor = 2; 70 | int sum = 0; 71 | int n = CODE_POINTS.length(); 72 | 73 | // Starting from the right and working leftwards is easier since 74 | // the initial "factor" will always be "2". 75 | for (int i = input.length() - 1; i >= 0; i--) { 76 | int codePoint = codePointFromCharacter(input.charAt(i)); 77 | int addend = factor * codePoint; 78 | 79 | // Alternate the "factor" that each "codePoint" is multiplied by 80 | factor = (factor == 2) ? 1 : 2; 81 | 82 | // Sum the digits of the "addend" as expressed in base "n" 83 | addend = (addend / n) + (addend % n); 84 | sum += addend; 85 | } 86 | 87 | // Calculate the number that must be added to the "sum" 88 | // to make it divisible by "n". 89 | int remainder = sum % n; 90 | int checkCodePoint = (n - remainder) % n; 91 | 92 | return characterFromCodePoint(checkCodePoint); 93 | } 94 | 95 | private char characterFromCodePoint(int checkCodePoint) { 96 | return CODE_POINTS.charAt(checkCodePoint); 97 | } 98 | 99 | private int codePointFromCharacter(char charAt) { 100 | int codePoint = CODE_POINTS.indexOf(charAt); 101 | if (codePoint < 0) { 102 | throw new IllegalArgumentException("unsupported character for checksum: " + charAt); 103 | } 104 | return codePoint; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/service/DgciNotFound.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.issuance.service; 22 | 23 | public class DgciNotFound extends RuntimeException { 24 | public DgciNotFound(String message, Throwable inner) { 25 | super(message, inner); 26 | } 27 | 28 | public DgciNotFound(String message) { 29 | super(message); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/service/EhdCryptoService.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.issuance.service; 22 | 23 | import COSE.AlgorithmID; 24 | import com.upokecenter.cbor.CBORObject; 25 | import ehn.techiop.hcert.kotlin.chain.CryptoService; 26 | import ehn.techiop.hcert.kotlin.chain.VerificationResult; 27 | import ehn.techiop.hcert.kotlin.crypto.CertificateAdapter; 28 | import ehn.techiop.hcert.kotlin.crypto.CoseHeaderKeys; 29 | import ehn.techiop.hcert.kotlin.crypto.JvmPrivKey; 30 | import ehn.techiop.hcert.kotlin.crypto.JvmPubKey; 31 | import ehn.techiop.hcert.kotlin.crypto.PrivKey; 32 | import ehn.techiop.hcert.kotlin.crypto.PubKey; 33 | import java.security.PrivateKey; 34 | import java.security.cert.X509Certificate; 35 | import java.security.interfaces.RSAPrivateCrtKey; 36 | import java.util.Arrays; 37 | import java.util.List; 38 | import kotlin.Pair; 39 | import org.springframework.stereotype.Component; 40 | 41 | @Component 42 | public class EhdCryptoService implements CryptoService { 43 | private final X509Certificate cert; 44 | private final byte[] kid; 45 | private final List> headers; 46 | private final PrivateKey privateKey; 47 | 48 | /** 49 | * the constructor. 50 | * 51 | * @param certificateService certificateService 52 | */ 53 | public EhdCryptoService(CertificateService certificateService) { 54 | this.cert = certificateService.getCertficate(); 55 | this.privateKey = certificateService.getPrivateKey(); 56 | kid = certificateService.getKid(); 57 | if (this.privateKey instanceof RSAPrivateCrtKey) { 58 | headers = Arrays.asList(new Pair<>(CoseHeaderKeys.ALGORITHM, AlgorithmID.RSA_PSS_256.AsCBOR()), 59 | new Pair<>(CoseHeaderKeys.KID, CBORObject.FromObject(kid))); 60 | } else { 61 | headers = Arrays.asList(new Pair<>(CoseHeaderKeys.ALGORITHM, AlgorithmID.ECDSA_256.AsCBOR()), 62 | new Pair<>(CoseHeaderKeys.KID, CBORObject.FromObject(kid))); 63 | } 64 | } 65 | 66 | 67 | @Override 68 | public List> getCborHeaders() { 69 | return headers; 70 | } 71 | 72 | @Override 73 | public PrivKey getCborSigningKey() { 74 | return new JvmPrivKey(privateKey); 75 | } 76 | 77 | @Override 78 | public PubKey getCborVerificationKey(byte[] bytes, VerificationResult verificationResult) { 79 | if (Arrays.compare(this.kid, kid) == 0) { 80 | return new JvmPubKey(cert.getPublicKey()); 81 | } else { 82 | throw new IllegalArgumentException("unknown kid"); 83 | } 84 | } 85 | 86 | @Override 87 | public CertificateAdapter getCertificate() { 88 | return new CertificateAdapter(cert); 89 | } 90 | 91 | @Override 92 | public String exportCertificateAsPem() { 93 | return null; 94 | } 95 | 96 | @Override 97 | public String exportPrivateKeyAsPem() { 98 | return null; 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/service/ExpirationService.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.service; 2 | 3 | import ehn.techiop.hcert.kotlin.data.GreenCertificate; 4 | import eu.europa.ec.dgc.issuance.config.IssuanceConfigProperties; 5 | import eu.europa.ec.dgc.issuance.entity.GreenCertificateType; 6 | import java.time.Duration; 7 | import java.time.Instant; 8 | import java.time.ZoneOffset; 9 | import java.time.temporal.ChronoUnit; 10 | import lombok.Data; 11 | import lombok.RequiredArgsConstructor; 12 | import org.springframework.stereotype.Component; 13 | 14 | @Component 15 | @RequiredArgsConstructor 16 | public class ExpirationService { 17 | 18 | private final IssuanceConfigProperties issuanceConfigProperties; 19 | 20 | /** 21 | * expiration duration for given edgc type. 22 | * @param greenCertificateType edgc type 23 | * @return Duration 24 | */ 25 | public Duration expirationForType(GreenCertificateType greenCertificateType) { 26 | Duration duration; 27 | if (issuanceConfigProperties.getExpiration() != null) { 28 | switch (greenCertificateType) { 29 | case Test: 30 | duration = issuanceConfigProperties.getExpiration().getTest(); 31 | break; 32 | case Vaccination: 33 | duration = issuanceConfigProperties.getExpiration().getVaccination(); 34 | break; 35 | case Recovery: 36 | duration = issuanceConfigProperties.getExpiration().getRecovery(); 37 | break; 38 | default: 39 | throw new IllegalArgumentException("unsupported cert type for expiration: " + greenCertificateType); 40 | } 41 | } else { 42 | duration = null; 43 | } 44 | if (duration == null) { 45 | duration = Duration.of(365, ChronoUnit.DAYS); 46 | } 47 | return duration; 48 | } 49 | 50 | /** 51 | * calulate cbor web token expiration fields. 52 | * It depends partly on configuration and for test and recovery also from DGC Json data 53 | * @param eudgc json data of dgc 54 | * @return the times 55 | */ 56 | public CwtTimeFields calculateCwtExpiration(GreenCertificate eudgc) { 57 | CwtTimeFields result = new CwtTimeFields(); 58 | GreenCertificateType greenCertificateType; 59 | long expirationTime; 60 | long issueTime = Instant.now().getEpochSecond(); 61 | long expirationStartTime = issueTime; 62 | 63 | if (eudgc.getTests() != null && eudgc.getTests().length > 0) { 64 | greenCertificateType = GreenCertificateType.Test; 65 | expirationStartTime = extractTimesSec(eudgc.getTests()[0].getDateTimeSample(),expirationStartTime); 66 | expirationTime = expirationStartTime + expirationForType(greenCertificateType).get(ChronoUnit.SECONDS); 67 | } else if (eudgc.getRecoveryStatements() != null && eudgc.getRecoveryStatements().length > 0) { 68 | greenCertificateType = GreenCertificateType.Recovery; 69 | expirationTime = expirationStartTime + expirationForType(greenCertificateType).get(ChronoUnit.SECONDS); 70 | expirationTime = extractTimesSec(eudgc.getRecoveryStatements()[0].getCertificateValidUntil(), 71 | expirationTime); 72 | } else if (eudgc.getVaccinations() != null && eudgc.getVaccinations().length > 0) { 73 | greenCertificateType = GreenCertificateType.Vaccination; 74 | expirationStartTime = extractTimesSec(eudgc.getVaccinations()[0].getDate(),expirationStartTime); 75 | expirationTime = expirationStartTime + expirationForType(greenCertificateType).get(ChronoUnit.SECONDS); 76 | } else { 77 | greenCertificateType = GreenCertificateType.Vaccination; 78 | expirationTime = expirationStartTime + expirationForType(greenCertificateType).get(ChronoUnit.SECONDS); 79 | } 80 | result.setIssuedAt(issueTime); 81 | result.setExpiration(expirationTime); 82 | return result; 83 | } 84 | 85 | private long extractTimesSec(kotlinx.datetime.Instant date, long defaultTimeSec) { 86 | long timeSec; 87 | if (date != null) { 88 | timeSec = date.getEpochSeconds(); 89 | } else { 90 | timeSec = defaultTimeSec; 91 | } 92 | return timeSec; 93 | } 94 | 95 | private long extractTimesSec(kotlinx.datetime.LocalDate localDate, long defaultTimeSec) { 96 | long timeSec; 97 | if (localDate != null) { 98 | timeSec = localDate.getValue$kotlinx_datetime().atStartOfDay().toEpochSecond(ZoneOffset.UTC); 99 | } else { 100 | timeSec = defaultTimeSec; 101 | } 102 | return timeSec; 103 | } 104 | 105 | 106 | @Data 107 | public static class CwtTimeFields { 108 | long issuedAt; 109 | long expiration; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/service/SigningService.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.service; 2 | 3 | import java.security.PrivateKey; 4 | 5 | public interface SigningService { 6 | /** 7 | * continue signing on already SHA256 generated content hash. 8 | * The it is only the Part of regular signing functionality. 9 | * Do not use regular Signing API because it will cause to hash the data twice and produce wrong 10 | * signature 11 | * @param hash SHA256 hash of content 12 | * @param privateKey RSA or EC 13 | * @return signature as raw byte array 14 | */ 15 | byte[] signHash(byte[] hash, PrivateKey privateKey); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/service/Tan.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.service; 2 | 3 | import eu.europa.ec.dgc.issuance.utils.HashUtil; 4 | import java.security.SecureRandom; 5 | import org.apache.commons.lang3.RandomStringUtils; 6 | 7 | public final class Tan { 8 | 9 | private static final int TAN_LENGTH = 8; 10 | private static final String HASH_ALGORITHM = "SHA-256"; 11 | private static final char[] CHAR_SET_FOR_TAN = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789".toCharArray(); 12 | 13 | private String rawTan; 14 | private String hashedTan; 15 | 16 | private Tan() { 17 | } 18 | 19 | /** 20 | * Create new TAN object with a TAN and the hash of the TAN. The TAN is constructed from a charset consisting 21 | * of A-Z (exclcuding I and O) and 2-9. 22 | * 23 | * @return the newly created TAN object 24 | */ 25 | public static Tan create() { 26 | Tan retVal = new Tan(); 27 | retVal.rawTan = retVal.generateNewTan(); 28 | retVal.hashedTan = HashUtil.sha256Base64(retVal.rawTan); 29 | return retVal; 30 | } 31 | 32 | private String generateNewTan() { 33 | SecureRandom random = new SecureRandom(); 34 | long rnd = random.nextLong(); 35 | int radixLen = CHAR_SET_FOR_TAN.length; 36 | StringBuilder tan = new StringBuilder(); 37 | while (tan.length() < TAN_LENGTH) { 38 | if (rnd == 0) { 39 | rnd = random.nextLong(); 40 | continue; 41 | } 42 | tan.append(CHAR_SET_FOR_TAN[Math.abs((int) (rnd % radixLen))]); 43 | rnd /= radixLen; 44 | } 45 | return tan.toString(); 46 | } 47 | 48 | public String getRawTan() { 49 | return rawTan; 50 | } 51 | 52 | public String getHashedTan() { 53 | return hashedTan; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/service/WrongRequest.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.issuance.service; 22 | 23 | public class WrongRequest extends RuntimeException { 24 | public WrongRequest(String message, Throwable inner) { 25 | super(message, inner); 26 | } 27 | 28 | public WrongRequest(String message) { 29 | super(message); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/service/impl/BtpAbstractKeyProvider.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.service.impl; 2 | 3 | import eu.europa.ec.dgc.issuance.service.CertificatePrivateKeyProvider; 4 | import eu.europa.ec.dgc.issuance.utils.btp.CredentialStore; 5 | import eu.europa.ec.dgc.issuance.utils.btp.SapCredential; 6 | import java.io.ByteArrayInputStream; 7 | import java.security.KeyFactory; 8 | import java.security.NoSuchAlgorithmException; 9 | import java.security.PrivateKey; 10 | import java.security.cert.Certificate; 11 | import java.security.cert.CertificateException; 12 | import java.security.cert.CertificateFactory; 13 | import java.security.spec.InvalidKeySpecException; 14 | import java.security.spec.PKCS8EncodedKeySpec; 15 | import java.util.ArrayList; 16 | import java.util.Arrays; 17 | import java.util.Base64; 18 | import java.util.List; 19 | import org.slf4j.Logger; 20 | import org.slf4j.LoggerFactory; 21 | 22 | /** 23 | *

Abstract class with interfaces to the SAP BTP {@link CredentialStore}. It provides methods to get certificates 24 | * as well as private keys from the credential store. Implementations of {@link CertificatePrivateKeyProvider} 25 | * inheriting from this abstract class do not have to implement a connection to the credential store themselves.

26 | *

Note: Keys in the credential store are supposed to be in X.509 or RSA format and base64 encoded. Raw keys 27 | * will be stripped off line breaks and -----BEGIN / END KEY----- phrases.

28 | */ 29 | public abstract class BtpAbstractKeyProvider implements CertificatePrivateKeyProvider { 30 | 31 | private static final Logger log = LoggerFactory.getLogger(BtpAbstractKeyProvider.class); 32 | 33 | private static final List ALLOWED_ALGORITHMS = Arrays.asList("EC", "RSA"); 34 | 35 | protected final CredentialStore credentialStore; 36 | 37 | public BtpAbstractKeyProvider(CredentialStore credentialStore) { 38 | this.credentialStore = credentialStore; 39 | } 40 | 41 | protected Certificate getCertificateFromStore(String certName) { 42 | SapCredential cert = credentialStore.getKeyByName(certName); 43 | String certContent = cleanKeyString(cert.getValue()); 44 | 45 | try { 46 | byte[] certDecoded = Base64.getDecoder().decode(certContent); 47 | CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); 48 | return certFactory.generateCertificate(new ByteArrayInputStream(certDecoded)); 49 | } catch (CertificateException e) { 50 | log.error("Error building certificate: {}.", e.getMessage()); 51 | throw new RuntimeException(e); 52 | } 53 | } 54 | 55 | protected PrivateKey getPrivateKeyFromStore(String keyName) { 56 | SapCredential key = credentialStore.getKeyByName(keyName); 57 | if (!ALLOWED_ALGORITHMS.contains(key.getFormat())) { 58 | throw new IllegalArgumentException("Key Format not supported: " + key.getFormat()); 59 | } 60 | 61 | try { 62 | KeyFactory kf = KeyFactory.getInstance(key.getFormat()); 63 | PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.getDecoder() 64 | .decode(cleanKeyString(key.getValue()))); 65 | return kf.generatePrivate(pkcs8EncodedKeySpec); 66 | } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { 67 | log.error("Error building private key: {}", e.getMessage()); 68 | throw new RuntimeException(e); 69 | } 70 | } 71 | 72 | private String cleanKeyString(String rawKey) { 73 | return rawKey.replaceAll("\\n", "") 74 | .replace("-----BEGIN PRIVATE KEY-----", "") 75 | .replace("-----BEGIN PUBLIC KEY-----", "") 76 | .replace("-----END PUBLIC KEY-----", "") 77 | .replace("-----END PRIVATE KEY-----", ""); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/service/impl/BtpCertKeyPublisherServiceImpl.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.service.impl; 2 | 3 | import com.sap.cloud.sdk.cloudplatform.connectivity.DestinationAccessor; 4 | import com.sap.cloud.sdk.cloudplatform.connectivity.HttpClientAccessor; 5 | import com.sap.cloud.sdk.cloudplatform.connectivity.HttpDestination; 6 | import eu.europa.ec.dgc.issuance.service.CertKeyPublisherService; 7 | import eu.europa.ec.dgc.issuance.service.CertificatePrivateKeyProvider; 8 | import eu.europa.ec.dgc.signing.SignedCertificateMessageBuilder; 9 | import eu.europa.ec.dgc.utils.CertificateUtils; 10 | import java.io.IOException; 11 | import java.nio.charset.StandardCharsets; 12 | import java.security.cert.CertificateEncodingException; 13 | import java.security.cert.X509Certificate; 14 | import lombok.extern.slf4j.Slf4j; 15 | import org.apache.http.HttpResponse; 16 | import org.apache.http.HttpStatus; 17 | import org.apache.http.client.HttpClient; 18 | import org.apache.http.client.methods.HttpUriRequest; 19 | import org.apache.http.client.methods.RequestBuilder; 20 | import org.apache.http.entity.StringEntity; 21 | import org.bouncycastle.cert.X509CertificateHolder; 22 | import org.springframework.beans.factory.annotation.Qualifier; 23 | import org.springframework.context.annotation.Profile; 24 | import org.springframework.stereotype.Component; 25 | 26 | /** 27 | * Publishes the issuer's public certificate to the DGC gateway. The public certificate will be signed with the upload 28 | * key provided by the upload key provider. 29 | * 30 | * @see BtpUploadKeyProviderImpl 31 | */ 32 | @Component 33 | @Profile("btp") 34 | @Slf4j 35 | public class BtpCertKeyPublisherServiceImpl implements CertKeyPublisherService { 36 | 37 | private static final String DGCG_DESTINATION = "dgcg-destination"; 38 | private static final String DGCG_UPLOAD_ENDPOINT = "/signerCertificate"; 39 | 40 | private final CertificatePrivateKeyProvider uploadKeyProvider; 41 | private final CertificatePrivateKeyProvider issuerKeyProvider; 42 | private final CertificateUtils certificateUtils; 43 | 44 | /** 45 | * Initializes the publisher service with all key provider and utilities needed for uploading certificates to 46 | * the gateway. 47 | * 48 | * @param uploadKeyProvider the upload certificate needed to sign the request 49 | * @param issuerKeyProvider the issuer certificate beeing uploaded 50 | * @param certificateUtils utilities to convert different certificate formats 51 | */ 52 | public BtpCertKeyPublisherServiceImpl( 53 | @Qualifier("uploadKeyProvider") CertificatePrivateKeyProvider uploadKeyProvider, 54 | @Qualifier("issuerKeyProvider") CertificatePrivateKeyProvider issuerKeyProvider, 55 | CertificateUtils certificateUtils) { 56 | this.uploadKeyProvider = uploadKeyProvider; 57 | this.issuerKeyProvider = issuerKeyProvider; 58 | this.certificateUtils = certificateUtils; 59 | } 60 | 61 | @Override 62 | public void publishKey() { 63 | log.debug("Uploading key to gateway."); 64 | HttpDestination httpDestination = DestinationAccessor.getDestination(DGCG_DESTINATION).asHttp(); 65 | HttpClient httpClient = HttpClientAccessor.getHttpClient(httpDestination); 66 | 67 | try { 68 | X509CertificateHolder issuerCertHolder = certificateUtils 69 | .convertCertificate((X509Certificate) issuerKeyProvider.getCertificate()); 70 | X509CertificateHolder uploadCertHolder = certificateUtils 71 | .convertCertificate((X509Certificate) uploadKeyProvider.getCertificate()); 72 | 73 | String payload = new SignedCertificateMessageBuilder() 74 | .withPayloadCertificate(issuerCertHolder) 75 | .withSigningCertificate(uploadCertHolder, uploadKeyProvider.getPrivateKey()).buildAsString(); 76 | 77 | HttpUriRequest postRequest = RequestBuilder.post(DGCG_UPLOAD_ENDPOINT) 78 | .addHeader("Content-type", "application/cms") 79 | .setEntity(new StringEntity(payload, StandardCharsets.UTF_8)) 80 | .build(); 81 | 82 | HttpResponse response = httpClient.execute(postRequest); 83 | if (response.getStatusLine().getStatusCode() == HttpStatus.SC_CREATED) { 84 | log.info("Successfully upload certificate to gateway."); 85 | } else { 86 | log.warn("Gateway returned 'HTTP {}: {}'.", response.getStatusLine().getStatusCode(), 87 | response.getStatusLine().getReasonPhrase()); 88 | } 89 | 90 | } catch (CertificateEncodingException | IOException e) { 91 | log.error("Error while upload certificate to gateway: '{}'.", e.getMessage()); 92 | throw new RuntimeException(e); 93 | } 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/service/impl/BtpIssuerKeyProviderImpl.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.service.impl; 2 | 3 | import eu.europa.ec.dgc.issuance.utils.btp.CredentialStore; 4 | import java.security.PrivateKey; 5 | import java.security.cert.Certificate; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.context.annotation.Profile; 9 | import org.springframework.stereotype.Component; 10 | 11 | @Slf4j 12 | @Component("issuerKeyProvider") 13 | @Profile("btp") 14 | public class BtpIssuerKeyProviderImpl extends BtpAbstractKeyProvider { 15 | 16 | private static final String ISSUER_KEY_NAME = "issuer-key"; 17 | private static final String ISSUER_CERT_NAME = "issuer-cert"; 18 | 19 | @Autowired 20 | public BtpIssuerKeyProviderImpl(CredentialStore credentialStore) { 21 | super(credentialStore); 22 | } 23 | 24 | @Override 25 | public Certificate getCertificate() { 26 | return this.getCertificateFromStore(ISSUER_CERT_NAME); 27 | } 28 | 29 | @Override 30 | public PrivateKey getPrivateKey() { 31 | return getPrivateKeyFromStore(ISSUER_KEY_NAME); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/service/impl/BtpUploadKeyProviderImpl.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.service.impl; 2 | 3 | import eu.europa.ec.dgc.issuance.utils.btp.CredentialStore; 4 | import java.security.PrivateKey; 5 | import java.security.cert.Certificate; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.context.annotation.Profile; 9 | import org.springframework.stereotype.Component; 10 | 11 | @Component("uploadKeyProvider") 12 | @Profile("btp") 13 | @Slf4j 14 | public class BtpUploadKeyProviderImpl extends BtpAbstractKeyProvider { 15 | 16 | private static final String UPLOAD_KEY_NAME = "upload-key"; 17 | private static final String UPLOAD_CERT_NAME = "upload-cert"; 18 | 19 | @Autowired 20 | public BtpUploadKeyProviderImpl(CredentialStore credentialStore) { 21 | super(credentialStore); 22 | } 23 | 24 | @Override 25 | public Certificate getCertificate() { 26 | return getCertificateFromStore(UPLOAD_CERT_NAME); 27 | } 28 | 29 | @Override 30 | public PrivateKey getPrivateKey() { 31 | return getPrivateKeyFromStore(UPLOAD_KEY_NAME); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/service/impl/CertificatePrivateKeyProviderImpl.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.service.impl; 2 | 3 | import eu.europa.ec.dgc.issuance.config.IssuanceConfigProperties; 4 | import eu.europa.ec.dgc.issuance.service.CertificatePrivateKeyProvider; 5 | import eu.europa.ec.dgc.issuance.service.DgciNotFound; 6 | import eu.europa.ec.dgc.utils.CertificateUtils; 7 | import java.io.File; 8 | import java.io.FileInputStream; 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | import java.security.KeyStore; 12 | import java.security.KeyStoreException; 13 | import java.security.NoSuchAlgorithmException; 14 | import java.security.PrivateKey; 15 | import java.security.PublicKey; 16 | import java.security.Security; 17 | import java.security.UnrecoverableEntryException; 18 | import java.security.cert.Certificate; 19 | import java.security.cert.CertificateException; 20 | import java.security.cert.X509Certificate; 21 | import javax.annotation.PostConstruct; 22 | import lombok.RequiredArgsConstructor; 23 | import lombok.extern.slf4j.Slf4j; 24 | import org.bouncycastle.jce.provider.BouncyCastleProvider; 25 | import org.springframework.context.annotation.Profile; 26 | import org.springframework.stereotype.Component; 27 | 28 | @Component("issuerKeyProvider") 29 | @Profile("!btp") 30 | @Slf4j 31 | @RequiredArgsConstructor 32 | public class CertificatePrivateKeyProviderImpl implements CertificatePrivateKeyProvider { 33 | private Certificate cert; 34 | private final IssuanceConfigProperties issuanceConfigProperties; 35 | private PublicKey publicKey; 36 | private PrivateKey privateKey; 37 | 38 | /** 39 | * PostConstruct method to load KeyStore for issuing certificates. 40 | */ 41 | @PostConstruct 42 | public void loadKeyStore() throws KeyStoreException, IOException, 43 | CertificateException, NoSuchAlgorithmException, UnrecoverableEntryException { 44 | final char[] keyStorePassword = issuanceConfigProperties.getKeyStorePassword().toCharArray(); 45 | final String keyName = issuanceConfigProperties.getCertAlias(); 46 | 47 | Security.addProvider(new BouncyCastleProvider()); 48 | Security.setProperty("crypto.policy", "unlimited"); 49 | 50 | KeyStore keyStore = KeyStore.getInstance("JKS"); 51 | 52 | File keyFile = new File(issuanceConfigProperties.getKeyStoreFile()); 53 | if (!keyFile.isFile()) { 54 | log.error("keyfile not found on: {} please adapt the configuration property: issuance.keyStoreFile", 55 | keyFile); 56 | throw new DgciNotFound("keyfile not found on: " + keyFile 57 | + " please adapt the configuration property: issuance.keyStoreFile"); 58 | } 59 | try (InputStream is = new FileInputStream(issuanceConfigProperties.getKeyStoreFile())) { 60 | final char[] privateKeyPassword = issuanceConfigProperties.getPrivateKeyPassword().toCharArray(); 61 | keyStore.load(is, privateKeyPassword); 62 | KeyStore.PasswordProtection keyPassword = 63 | new KeyStore.PasswordProtection(keyStorePassword); 64 | 65 | KeyStore.PrivateKeyEntry privateKeyEntry = 66 | (KeyStore.PrivateKeyEntry) keyStore.getEntry(keyName, keyPassword); 67 | cert = keyStore.getCertificate(keyName); 68 | publicKey = cert.getPublicKey(); 69 | privateKey = privateKeyEntry.getPrivateKey(); 70 | CertificateUtils certificateUtils = new CertificateUtils(); 71 | String kidBase64 = certificateUtils.getCertKid((X509Certificate) cert); 72 | } 73 | } 74 | 75 | @Override 76 | public Certificate getCertificate() { 77 | return cert; 78 | } 79 | 80 | @Override 81 | public PrivateKey getPrivateKey() { 82 | return privateKey; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/service/impl/CopyDigest.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.issuance.service.impl; 22 | 23 | 24 | import java.io.ByteArrayOutputStream; 25 | import org.bouncycastle.crypto.Digest; 26 | import org.bouncycastle.crypto.digests.SHA256Digest; 27 | import org.bouncycastle.util.Arrays; 28 | 29 | public class CopyDigest implements Digest { 30 | private final OpenByteArrayOutputStream binaryOut = new OpenByteArrayOutputStream(); 31 | private final SHA256Digest sha256Digest = new SHA256Digest(); 32 | private boolean wasReset = false; 33 | 34 | public String getAlgorithmName() { 35 | return "NULL"; 36 | } 37 | 38 | public int getDigestSize() { 39 | return 32; 40 | } 41 | 42 | /** 43 | * Updates the Message Digest with one byte. 44 | * 45 | * @param in byte to update. 46 | */ 47 | public void update(byte in) { 48 | if (wasReset) { 49 | sha256Digest.update(in); 50 | } else { 51 | binaryOut.write(in); 52 | } 53 | } 54 | 55 | /** 56 | * Updates the Message Digest with a byte array. 57 | * 58 | * @param in byte array to insert 59 | * @param offset Offset 60 | * @param length length 61 | */ 62 | public void update(byte[] in, int offset, int length) { 63 | if (wasReset) { 64 | sha256Digest.update(in, offset, length); 65 | } else { 66 | binaryOut.write(in, offset, length); 67 | } 68 | } 69 | 70 | /** 71 | * close the digest, producing the final digest value. The doFinal 72 | * call leaves the digest reset. 73 | * 74 | * @param out the array the digest is to be copied into. 75 | * @param offset the offset into the out array the digest is to start at. 76 | * @return size of output 77 | */ 78 | public int doFinal(byte[] out, int offset) { 79 | if (wasReset) { 80 | return sha256Digest.doFinal(out, offset); 81 | } else { 82 | int size = binaryOut.size(); 83 | binaryOut.copy(out, offset); 84 | reset(); 85 | return size; 86 | } 87 | } 88 | 89 | /** 90 | * Resets the Message Digest. 91 | */ 92 | public void reset() { 93 | if (wasReset) { 94 | sha256Digest.reset(); 95 | } else { 96 | if (binaryOut.size() > 0) { 97 | wasReset = true; 98 | binaryOut.reset(); 99 | } 100 | } 101 | } 102 | 103 | private static class OpenByteArrayOutputStream 104 | extends ByteArrayOutputStream { 105 | public synchronized void reset() { 106 | super.reset(); 107 | 108 | Arrays.clear(buf); 109 | } 110 | 111 | void copy(byte[] out, int outOff) { 112 | System.arraycopy(buf, 0, out, outOff, this.size()); 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/service/impl/SigningServiceImpl.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.service.impl; 2 | 3 | import eu.europa.ec.dgc.issuance.service.SigningService; 4 | import java.math.BigInteger; 5 | import java.security.PrivateKey; 6 | import java.security.interfaces.RSAPrivateCrtKey; 7 | import org.bouncycastle.crypto.CryptoException; 8 | import org.bouncycastle.crypto.Digest; 9 | import org.bouncycastle.crypto.digests.SHA256Digest; 10 | import org.bouncycastle.crypto.engines.RSABlindedEngine; 11 | import org.bouncycastle.crypto.params.ECDomainParameters; 12 | import org.bouncycastle.crypto.params.ECPrivateKeyParameters; 13 | import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters; 14 | import org.bouncycastle.crypto.signers.ECDSASigner; 15 | import org.bouncycastle.crypto.signers.PSSSigner; 16 | import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util; 17 | import org.bouncycastle.jce.spec.ECParameterSpec; 18 | import org.springframework.stereotype.Component; 19 | 20 | @Component 21 | public class SigningServiceImpl implements SigningService { 22 | @Override 23 | public byte[] signHash(byte[] hashBytes, PrivateKey privateKey) { 24 | byte[] signature; 25 | try { 26 | if (privateKey instanceof RSAPrivateCrtKey) { 27 | signature = signRsapss(hashBytes, privateKey); 28 | } else { 29 | signature = signEc(hashBytes, privateKey); 30 | } 31 | } catch (CryptoException e) { 32 | throw new IllegalArgumentException("error during signing ", e); 33 | } 34 | return signature; 35 | } 36 | 37 | private byte[] signRsapss(byte[] hashBytes, PrivateKey privateKey) throws CryptoException { 38 | Digest contentDigest = new CopyDigest(); 39 | Digest mgfDigest = new SHA256Digest(); 40 | RSAPrivateCrtKey k = (RSAPrivateCrtKey) privateKey; 41 | RSAPrivateCrtKeyParameters keyparam = new RSAPrivateCrtKeyParameters(k.getModulus(), 42 | k.getPublicExponent(), k.getPrivateExponent(), 43 | k.getPrimeP(), k.getPrimeQ(), k.getPrimeExponentP(), k.getPrimeExponentQ(), k.getCrtCoefficient()); 44 | RSABlindedEngine rsaBlindedEngine = new RSABlindedEngine(); 45 | rsaBlindedEngine.init(true, keyparam); 46 | PSSSigner pssSigner = new PSSSigner(rsaBlindedEngine, contentDigest, mgfDigest, 32, (byte) (-68)); 47 | pssSigner.init(true, keyparam); 48 | pssSigner.update(hashBytes, 0, hashBytes.length); 49 | return pssSigner.generateSignature(); 50 | } 51 | 52 | private byte[] signEc(byte[] hash, PrivateKey privateKey) { 53 | java.security.interfaces.ECPrivateKey privKey = (java.security.interfaces.ECPrivateKey) privateKey; 54 | ECParameterSpec s = EC5Util.convertSpec(privKey.getParams()); 55 | ECPrivateKeyParameters keyparam = new ECPrivateKeyParameters( 56 | privKey.getS(), 57 | new ECDomainParameters(s.getCurve(), s.getG(), s.getN(), s.getH(), s.getSeed())); 58 | ECDSASigner ecdsaSigner = new ECDSASigner(); 59 | ecdsaSigner.init(true, keyparam); 60 | BigInteger[] result3BI = ecdsaSigner.generateSignature(hash); 61 | byte[] rvarArr = result3BI[0].toByteArray(); 62 | byte[] svarArr = result3BI[1].toByteArray(); 63 | // we need to convert it to 2*32 bytes array. This can 33 with leading 0 or shorter so padding is needed 64 | byte[] sig = new byte[64]; 65 | System.arraycopy(rvarArr, rvarArr.length == 33 ? 1 : 0, sig, 66 | Math.max(0, 32 - rvarArr.length), Math.min(32, rvarArr.length)); 67 | System.arraycopy(svarArr, svarArr.length == 33 ? 1 : 0, sig, 68 | 32 + Math.max(0, 32 - svarArr.length), Math.min(32, svarArr.length)); 69 | 70 | return sig; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/utils/CborDumpService.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.issuance.utils; 22 | 23 | import com.upokecenter.cbor.CBORObject; 24 | import java.io.IOException; 25 | import java.io.Writer; 26 | import org.springframework.stereotype.Service; 27 | 28 | @Service 29 | public class CborDumpService { 30 | 31 | /** 32 | * Method to write a CBOR Array into a {@link Writer} (e.g. {@link java.io.StringWriter}). 33 | * 34 | * @param cb cbor Byte Array to write 35 | * @param writer destination 36 | */ 37 | public void dumpCbor(byte[] cb, Writer writer) throws IOException { 38 | CBORObject cborObject = CBORObject.DecodeFromBytes(cb); 39 | writer 40 | .append("") 41 | .append(String.valueOf(cborObject)); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/utils/DgciUtil.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.utils; 2 | 3 | import java.math.BigInteger; 4 | import java.nio.ByteBuffer; 5 | import java.util.UUID; 6 | 7 | public class DgciUtil { 8 | 9 | /** 10 | * Encode UUID to charset of A-Z and 0-9. 11 | * 12 | * @param uuid the UUID to hash 13 | * @return the hashed UUID 14 | */ 15 | public static String encodeDgci(UUID uuid) { 16 | ByteBuffer bb = ByteBuffer.wrap(new byte[16]); 17 | bb.putLong(uuid.getMostSignificantBits()); 18 | bb.putLong(uuid.getLeastSignificantBits()); 19 | BigInteger bint = new BigInteger(1, bb.array()); 20 | int radix = 10 + ('Z' - 'A'); 21 | return bint.toString(radix).toUpperCase(); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/utils/HashUtil.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.utils; 2 | 3 | import java.nio.charset.StandardCharsets; 4 | import java.security.MessageDigest; 5 | import java.security.NoSuchAlgorithmException; 6 | import java.util.Base64; 7 | 8 | public class HashUtil { 9 | 10 | /** 11 | * Generates a SHA-256 hash and returns it as Base64 encoded string. 12 | * 13 | * @param raw the raw input 14 | * @return the Base64 encode hash 15 | */ 16 | public static String sha256Base64(String raw) { 17 | try { 18 | final MessageDigest digest = MessageDigest.getInstance("SHA-256"); 19 | final byte[] hashBytes = digest.digest(raw.getBytes(StandardCharsets.UTF_8)); 20 | return Base64.getEncoder().encodeToString(hashBytes); 21 | } catch (NoSuchAlgorithmException e) { 22 | throw new IllegalArgumentException(e); 23 | } 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/eu/europa/ec/dgc/issuance/utils/btp/CredentialStore.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.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/issuance/utils/btp/CredentialStoreConfig.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.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/issuance/utils/btp/CredentialStoreCryptoUtil.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.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/issuance/utils/btp/SapCredential.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.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/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | io.pivotal.cfenv.spring.boot.CfEnvProcessor=eu.europa.ec.dgc.issuance.config.btp.SapCredentialStoreCfEnvProcessor 2 | -------------------------------------------------------------------------------- /src/main/resources/application-btp.yml: -------------------------------------------------------------------------------- 1 | sap: 2 | btp: 3 | credstore: 4 | namespace: DgcaIssuerServiceCredentialStore 5 | encrypted: false 6 | username: 7 | password: 8 | url: 9 | clientPrivateKey: 10 | serverPublicKey: 11 | -------------------------------------------------------------------------------- /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/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8080 3 | spring: 4 | mvc: 5 | pathmatch: 6 | matching-strategy: ant_path_matcher 7 | application: 8 | name: dgca-issuance-service 9 | datasource: 10 | driver-class-name: org.h2.Driver 11 | url: jdbc:h2:mem:dgc;DB_CLOSE_ON_EXIT=FALSE;DB_CLOSE_DELAY=-1; 12 | username: sa 13 | password: '' 14 | jpa: 15 | database-platform: org.hibernate.dialect.H2Dialect 16 | hibernate: 17 | ddl-auto: validate 18 | liquibase: 19 | change-log: classpath:db/changelog.xml 20 | h2: 21 | console: 22 | enabled: true 23 | path: /h2-console 24 | management: 25 | endpoint: 26 | info: 27 | enabled: true 28 | health: 29 | enabled: true 30 | endpoints: 31 | enabled-by-default: false 32 | web: 33 | base-path: /management 34 | exposure: 35 | include: info,health 36 | info: 37 | name: ${spring.application.name} 38 | profiles: ${spring.profiles.active} 39 | springdoc: 40 | api-docs: 41 | path: /api/docs 42 | enabled: true 43 | swagger-ui: 44 | path: /swagger 45 | issuance: 46 | dgciPrefix: URN:UVCI:V1:DE 47 | keyStoreFile: certs/test.jks 48 | keyStorePassword: dgca 49 | certAlias: dev_ec 50 | privateKeyPassword: dgca 51 | countryCode: DE 52 | tanExpirationHours: 2 53 | contextFile: context/context.json 54 | expiration: 55 | vaccination: 365 56 | recovery: 365 57 | test: 3 58 | endpoints: 59 | frontendIssuing: true 60 | backendIssuing: false 61 | testTools: false 62 | wallet: true 63 | publishCert: true 64 | did: true 65 | dgc: 66 | gateway: 67 | connector: 68 | enabled: false 69 | # endpoint: https://dgc-gateway.example.com 70 | # proxy: 71 | # enabled: false 72 | # max-cache-age: 300 73 | # tls-trust-store: 74 | # password: dgcg-p4ssw0rd 75 | # path: classpath:tls_trust_store.p12 76 | # tls-key-store: 77 | # alias: 1 78 | # password: dgcg-p4ssw0rd 79 | # path: classpath:tls_key_store.p12 80 | # trust-anchor: 81 | # alias: ta 82 | # password: dgcg-p4ssw0rd 83 | # path: classpath:trust_anchor.jks 84 | -------------------------------------------------------------------------------- /src/main/resources/db/changelog.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/main/resources/db/changelog/init-tables.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /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/java/OpenApiTest.java: -------------------------------------------------------------------------------- 1 | import eu.europa.ec.dgc.issuance.DgcIssuanceApplication; 2 | import java.io.BufferedInputStream; 3 | import java.io.FileOutputStream; 4 | import java.net.URL; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.junit.jupiter.api.Assertions; 7 | import org.junit.jupiter.api.Test; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | 10 | @Slf4j 11 | @SpringBootTest( 12 | classes = DgcIssuanceApplication.class, 13 | properties = { 14 | "server.port=8080", 15 | "springdoc.api-docs.enabled=true", 16 | "springdoc.api-docs.path=/openapi" 17 | }, 18 | webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT 19 | ) 20 | public class OpenApiTest { 21 | 22 | @Test 23 | public void apiDocs() { 24 | try (BufferedInputStream in = new BufferedInputStream(new URL("http://localhost:8080/openapi").openStream()); 25 | FileOutputStream out = new FileOutputStream("target/openapi.json")) { 26 | byte[] buffer = new byte[1024]; 27 | int read; 28 | while ((read = in.read(buffer, 0, buffer.length)) != -1) { 29 | out.write(buffer, 0, read); 30 | } 31 | } catch (Exception e) { 32 | log.error("Failed to download openapi specification.", e); 33 | Assertions.fail(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/eu/europa/ec/dgc/issuance/EncodingTest.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.issuance; 22 | 23 | import com.nimbusds.jose.util.Base64URL; 24 | import eu.europa.ec.dgc.issuance.utils.DgciUtil; 25 | import eu.europa.ec.dgc.issuance.utils.HashUtil; 26 | import java.nio.charset.StandardCharsets; 27 | import java.security.MessageDigest; 28 | import java.util.UUID; 29 | import org.junit.jupiter.api.Test; 30 | 31 | import static org.junit.jupiter.api.Assertions.assertArrayEquals; 32 | import static org.junit.jupiter.api.Assertions.assertEquals; 33 | 34 | class EncodingTest { 35 | 36 | public static final String TEST_TAN = "U7ULCYZY"; 37 | public static final String TEST_TAN_HASHED = "avmGz38ugM7uBePwKKlvh3IB8+7O+WFhQEbjIxhTxgY="; 38 | 39 | public static final String TEST_UUID = "cd7737d4-51ca-45f8-9f74-3a173b9a1f47"; 40 | public static final String TEST_DGCI_REP = "NW393C1D87A44870V7TTFQMYC"; 41 | 42 | @Test 43 | void testCreateSHA256Hash() { 44 | String output = HashUtil.sha256Base64(TEST_TAN); 45 | assertEquals(TEST_TAN_HASHED, output); 46 | } 47 | 48 | @Test 49 | void dgciEncoding() { 50 | UUID uuid = UUID.fromString(TEST_UUID); 51 | String dgciRep = DgciUtil.encodeDgci(uuid); 52 | assertEquals(25, dgciRep.length()); 53 | assertEquals(TEST_DGCI_REP, dgciRep); 54 | } 55 | 56 | @Test 57 | void testBase64URL() throws Exception { 58 | String dgci= "URN:UVCI:V1:DE:NW513NLDH01JY3JCMU4M67WOHA"; 59 | MessageDigest digest = MessageDigest.getInstance("SHA-256"); 60 | byte[] hash = digest.digest(dgci.getBytes(StandardCharsets.UTF_8)); 61 | String hashBase64URL = Base64URL.encode(hash).toString(); 62 | System.out.println(hashBase64URL); 63 | assertArrayEquals(hash,Base64URL.from(hashBase64URL).decode()); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/test/java/eu/europa/ec/dgc/issuance/GenerateWalletRequestTest.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.issuance; 22 | 23 | import com.fasterxml.jackson.databind.ObjectMapper; 24 | import com.fasterxml.jackson.databind.SerializationFeature; 25 | import eu.europa.ec.dgc.issuance.restapi.dto.ClaimRequest; 26 | import eu.europa.ec.dgc.issuance.restapi.dto.PublicKey; 27 | import java.nio.charset.StandardCharsets; 28 | import java.security.InvalidKeyException; 29 | import java.security.KeyPair; 30 | import java.security.KeyPairGenerator; 31 | import java.security.MessageDigest; 32 | import java.security.NoSuchAlgorithmException; 33 | import java.security.PrivateKey; 34 | import java.security.Signature; 35 | import java.security.SignatureException; 36 | import java.util.Base64; 37 | import org.junit.jupiter.api.Test; 38 | 39 | class GenerateWalletRequestTest { 40 | // This can be used to generate valid json structure for claim 41 | @Test 42 | void testGenerateWalletRequest() throws Exception { 43 | // Please adapt this to your certificate (the values can be get from browser network log 44 | // see POST /dgci 45 | // and PUT /dgci/{id} 46 | String dgci = "dgci:V1:DE:2e974b3b-d932-4bc9-bbae-d387f93f8bf3:edbcb873196f24be"; 47 | String certHash = "mfg0MI7wPFexNkOa4n9OKojrzhe9a9lcim4JzJO3WtY="; 48 | String tan = "U7ULCYZY"; 49 | String tanHash = "avmGz38ugM7uBePwKKlvh3IB8+7O+WFhQEbjIxhTxgY="; 50 | 51 | KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA"); 52 | keyPairGen.initialize(2048); 53 | KeyPair keyPair = keyPairGen.generateKeyPair(); 54 | byte[] publicKeyBytes = keyPair.getPublic().getEncoded(); 55 | String sigAlg = "SHA256WithRSA"; 56 | 57 | ClaimRequest claimRequest = new ClaimRequest(); 58 | claimRequest.setDgci(dgci); 59 | claimRequest.setTanHash(tanHash); 60 | PublicKey publicKeyDTO = new PublicKey(); 61 | publicKeyDTO.setType(keyPair.getPublic().getAlgorithm()); 62 | publicKeyDTO.setValue(Base64.getEncoder().encodeToString(publicKeyBytes)); 63 | claimRequest.setPublicKey(publicKeyDTO); 64 | 65 | claimRequest.setSigAlg(sigAlg); 66 | claimRequest.setCertHash(certHash); 67 | createClaimSignature(claimRequest, keyPair.getPrivate(), sigAlg); 68 | 69 | ObjectMapper objectMapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT); 70 | System.out.println(objectMapper.writeValueAsString(claimRequest)); 71 | } 72 | 73 | public static void main(String[] args) { 74 | try { 75 | final MessageDigest digest = MessageDigest.getInstance("SHA-256"); 76 | final byte[] hashBytes = digest.digest("U7ULCYZY".getBytes(StandardCharsets.UTF_8)); 77 | System.out.println(Base64.getEncoder().encodeToString(hashBytes)); 78 | } catch (NoSuchAlgorithmException e) { 79 | throw new IllegalArgumentException(e); 80 | } 81 | } 82 | 83 | private void createClaimSignature(ClaimRequest claimRequest, PrivateKey privateKey, String sigAlg) throws InvalidKeyException, NoSuchAlgorithmException, SignatureException { 84 | StringBuilder sigValue = new StringBuilder(); 85 | sigValue.append(claimRequest.getTanHash()) 86 | .append(claimRequest.getCertHash()) 87 | .append(claimRequest.getPublicKey().getValue()); 88 | Signature signature = Signature.getInstance(sigAlg); 89 | signature.initSign(privateKey); 90 | signature.update(sigValue.toString().getBytes(StandardCharsets.UTF_8)); 91 | byte[] sigData = signature.sign(); 92 | claimRequest.setSignature(Base64.getEncoder().encodeToString(sigData)); 93 | } 94 | 95 | @Test 96 | void testGenerateWalletRequestEC() throws Exception { 97 | // Please adapt this to your certificate (the values can be get from browser network log 98 | // see POST /dgci 99 | // and PUT /dgci/{id} 100 | String dgci = "dgci:V1:DE:2e974b3b-d932-4bc9-bbae-d387f93f8bf3:edbcb873196f24be"; 101 | String certHash = "mfg0MI7wPFexNkOa4n9OKojrzhe9a9lcim4JzJO3WtY="; 102 | String tan = "U7ULCYZY"; 103 | String tanHash = "avmGz38ugM7uBePwKKlvh3IB8+7O+WFhQEbjIxhTxgY="; 104 | 105 | KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("EC"); 106 | keyPairGen.initialize(256); 107 | KeyPair keyPair = keyPairGen.generateKeyPair(); 108 | byte[] publicKeyBytes = keyPair.getPublic().getEncoded(); 109 | String sigAlg = "SHA256withECDSA"; 110 | 111 | ClaimRequest claimRequest = new ClaimRequest(); 112 | claimRequest.setDgci(dgci); 113 | claimRequest.setTanHash(tanHash); 114 | PublicKey publicKeyDTO = new PublicKey(); 115 | publicKeyDTO.setType(keyPair.getPublic().getAlgorithm()); 116 | publicKeyDTO.setValue(Base64.getEncoder().encodeToString(publicKeyBytes)); 117 | claimRequest.setPublicKey(publicKeyDTO); 118 | 119 | claimRequest.setSigAlg(sigAlg); 120 | claimRequest.setCertHash(certHash); 121 | createClaimSignature(claimRequest,keyPair.getPrivate(),sigAlg); 122 | 123 | ObjectMapper objectMapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT); 124 | System.out.println(objectMapper.writeValueAsString(claimRequest)); 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /src/test/java/eu/europa/ec/dgc/issuance/JwkCoreTest.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.fasterxml.jackson.databind.SerializationFeature; 6 | import com.nimbusds.jose.jwk.Curve; 7 | import com.nimbusds.jose.jwk.ECKey; 8 | import com.nimbusds.jose.jwk.RSAKey; 9 | import java.security.KeyPair; 10 | import java.security.KeyPairGenerator; 11 | import java.security.interfaces.ECPublicKey; 12 | import java.security.interfaces.RSAPublicKey; 13 | import org.junit.jupiter.api.Test; 14 | 15 | class JwkCoreTest { 16 | @Test 17 | void testRSAgeneration() throws Exception { 18 | KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA"); 19 | keyPairGen.initialize(2048); 20 | KeyPair keyPair = keyPairGen.generateKeyPair(); 21 | 22 | RSAKey jwkKey = new RSAKey.Builder((RSAPublicKey) keyPair.getPublic()).build(); 23 | ObjectMapper objectMapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT); 24 | String jwkString = jwkKey.toJSONString(); 25 | JsonNode jwkElem = objectMapper.readTree(jwkString); 26 | System.out.println(jwkString); 27 | System.out.println(objectMapper.writeValueAsString(jwkElem)); 28 | } 29 | 30 | @Test 31 | void testECgeneration() throws Exception { 32 | KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("EC"); 33 | keyPairGen.initialize(256); 34 | KeyPair keyPair = keyPairGen.generateKeyPair(); 35 | ECPublicKey ecPublicKey = (ECPublicKey) keyPair.getPublic(); 36 | Curve curve = Curve.forECParameterSpec(ecPublicKey.getParams()); 37 | 38 | ECKey jwkKey = new ECKey.Builder(curve,(ECPublicKey) keyPair.getPublic()).build(); 39 | ObjectMapper objectMapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT); 40 | String jwkString = jwkKey.toJSONString(); 41 | JsonNode jwkElem = objectMapper.readTree(jwkString); 42 | System.out.println(objectMapper.writeValueAsString(jwkElem)); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/eu/europa/ec/dgc/issuance/restapi/controller/CertControllerTest.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.restapi.controller; 2 | 3 | import eu.europa.ec.dgc.issuance.restapi.dto.PublicKeyInfo; 4 | import java.security.KeyFactory; 5 | import java.security.PublicKey; 6 | import java.security.spec.X509EncodedKeySpec; 7 | import java.util.Base64; 8 | import org.junit.jupiter.api.Test; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.springframework.http.HttpStatus; 12 | import org.springframework.http.ResponseEntity; 13 | 14 | import static org.junit.jupiter.api.Assertions.assertEquals; 15 | import static org.junit.jupiter.api.Assertions.assertNotNull; 16 | 17 | @SpringBootTest 18 | class CertControllerTest { 19 | @Autowired 20 | CertController certController; 21 | 22 | @Test 23 | public void testPublicKey() throws Exception { 24 | ResponseEntity publicKeyResponse = certController.getPublic(); 25 | assertEquals(HttpStatus.OK,publicKeyResponse.getStatusCode()); 26 | publicKeyResponse.getBody(); 27 | PublicKeyInfo publicKey = publicKeyResponse.getBody(); 28 | 29 | byte[] keyBytes = Base64.getDecoder().decode(publicKey.getPublicKeyEncoded()); 30 | X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes); 31 | KeyFactory kf = KeyFactory.getInstance(publicKey.getKeyType()); 32 | PublicKey publicKeySec = kf.generatePublic(spec); 33 | assertNotNull(publicKeySec); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/eu/europa/ec/dgc/issuance/restapi/controller/ContextControllerTest.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.restapi.controller; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.http.MediaType; 8 | import org.springframework.test.web.servlet.MockMvc; 9 | 10 | import static org.junit.jupiter.api.Assertions.*; 11 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 12 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; 13 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 14 | 15 | @SpringBootTest 16 | @AutoConfigureMockMvc 17 | class ContextControllerTest { 18 | 19 | @Autowired 20 | private MockMvc mockMvc; 21 | 22 | @Test 23 | void getContext() throws Exception { 24 | mockMvc.perform(get("/context")) 25 | .andExpect(status().isOk()) 26 | .andExpect(content().contentType(MediaType.APPLICATION_JSON)) 27 | .andExpect(content().json("{\"Origin\":\"DE\",\"versions\":{}}")); 28 | 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/eu/europa/ec/dgc/issuance/restapi/controller/DgciControllerTest.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.restapi.controller; 2 | 3 | import eu.europa.ec.dgc.issuance.restapi.dto.EgdcCodeData; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.http.ResponseEntity; 8 | 9 | import static org.junit.jupiter.api.Assertions.assertNotNull; 10 | 11 | @SpringBootTest 12 | public class DgciControllerTest { 13 | @Autowired 14 | DgciBackendController dgciController; 15 | 16 | @Test 17 | void checkBackendIssuing() throws Exception { 18 | String edgc = "{\"ver\":\"1.0.0\",\"nam\":{\"fn\":\"Garcia\",\"fnt\":\"GARCIA\"," + 19 | "\"gn\":\"Francisco\",\"gnt\":\"FRANCISCO\"},\"dob\":\"1991-01-01\",\"v\":[{\"tg\":\"840539006\"," + 20 | "\"vp\":\"1119305005\",\"mp\":\"EU/1/20/1507\",\"ma\":\"ORG-100001699\",\"dn\":1,\"sd\":2,\"dt\":" + 21 | "\"2021-05-14\",\"co\":\"CY\",\"is\":\"Neha\",\"ci\":\"dgci:V1:CY:HIP4OKCIS8CXKQMJSSTOJXAMP:03\"}]}"; 22 | ResponseEntity responseEntity = dgciController.createEdgc(edgc); 23 | assertNotNull(responseEntity.getBody()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/eu/europa/ec/dgc/issuance/service/ContextServiceTest.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.service; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | 8 | import static org.junit.jupiter.api.Assertions.*; 9 | 10 | @SpringBootTest( 11 | properties = { 12 | "issuance.contextData = {\"test\":\"Data\"}" 13 | } 14 | ) 15 | class ContextServiceTest { 16 | 17 | @Autowired 18 | ContextService contextService; 19 | 20 | @Test 21 | void getContextFromEnvironment() { 22 | JsonNode json = contextService.getContextDefinition(); 23 | assertEquals("{\"test\":\"Data\"}",json.toString()); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/eu/europa/ec/dgc/issuance/service/DGCGenTest.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.service; 2 | 3 | import com.upokecenter.cbor.CBORObject; 4 | import com.upokecenter.cbor.CBORType; 5 | import ehn.techiop.hcert.kotlin.chain.Base45Service; 6 | import ehn.techiop.hcert.kotlin.chain.impl.DefaultBase45Service; 7 | import eu.europa.ec.dgc.issuance.entity.GreenCertificateType; 8 | import eu.europa.ec.dgc.issuance.restapi.dto.DgciIdentifier; 9 | import eu.europa.ec.dgc.issuance.restapi.dto.DgciInit; 10 | import eu.europa.ec.dgc.issuance.restapi.dto.EgcDecodeResult; 11 | import eu.europa.ec.dgc.issuance.restapi.dto.IssueData; 12 | import eu.europa.ec.dgc.issuance.restapi.dto.SignatureData; 13 | import java.io.ByteArrayInputStream; 14 | import java.io.IOException; 15 | import java.nio.charset.StandardCharsets; 16 | import java.time.Duration; 17 | import java.time.ZonedDateTime; 18 | import java.time.temporal.ChronoUnit; 19 | import java.util.Base64; 20 | import java.util.zip.Deflater; 21 | import java.util.zip.DeflaterInputStream; 22 | import org.junit.jupiter.api.Test; 23 | import org.springframework.beans.factory.annotation.Autowired; 24 | import org.springframework.boot.test.context.SpringBootTest; 25 | 26 | import static org.junit.jupiter.api.Assertions.assertNull; 27 | import static org.junit.jupiter.api.Assertions.assertTrue; 28 | 29 | /** 30 | * The test shows how produce edgc from json the same way the frontend is doing 31 | * using CBOR primitives 32 | */ 33 | @SpringBootTest 34 | class DGCGenTest { 35 | @Autowired 36 | DgciService dgciService; 37 | 38 | @Autowired 39 | EdgcValidator edgcValidator; 40 | 41 | @Autowired 42 | CertificateService certificateService; 43 | 44 | @Test 45 | void genEDGC() throws IOException { 46 | String edgcJson = "{\"ver\":\"1.0.0\",\"nam\":{\"fn\":\"Garcia\",\"fnt\":\"GARCIA\"," + 47 | "\"gn\":\"Francisco\",\"gnt\":\"FRANCISCO\"},\"dob\":\"1991-01-01\",\"v\":[{\"tg\":\"840539006\"," + 48 | "\"vp\":\"1119305005\",\"mp\":\"EU/1/20/1507\",\"ma\":\"ORG-100001699\",\"dn\":1,\"sd\":2,\"dt\":" + 49 | "\"2021-05-14\",\"co\":\"CY\",\"is\":\"Neha\",\"ci\":\"dgci:V1:CY:HIP4OKCIS8CXKQMJSSTOJXAMP:03\"}]}"; 50 | String countryCode = "DE"; 51 | ZonedDateTime now = ZonedDateTime.now(); 52 | ZonedDateTime expiration = now.plus(Duration.of(365, ChronoUnit.DAYS)); 53 | long issuedAt = now.toInstant().getEpochSecond(); 54 | long expirationSec = expiration.toInstant().getEpochSecond(); 55 | byte[] keyId = "key".getBytes(StandardCharsets.UTF_8); 56 | int algId = -7; 57 | 58 | DgciInit dgciInit = new DgciInit(); 59 | dgciInit.setGreenCertificateType(GreenCertificateType.Test); 60 | DgciIdentifier certData = dgciService.initDgci(dgciInit); 61 | 62 | byte[] dgcCbor = genDGCCbor(edgcJson, countryCode, issuedAt, certData.getExpired()); 63 | byte[] coseBytes = genCoseUnsigned(dgcCbor, Base64.getDecoder().decode(certData.getKid()) 64 | ,certData.getAlgId()); 65 | byte[] hash = dgciService.computeCoseSignHash(coseBytes); 66 | IssueData issueData = new IssueData(); 67 | issueData.setHash(Base64.getEncoder().encodeToString(hash)); 68 | SignatureData sign = dgciService.finishDgci(certData.getId(), issueData); 69 | byte[] coseSigned = genSetSignature(coseBytes,Base64.getDecoder().decode(sign.getSignature())); 70 | String edgcQR = coseToQRCode(coseSigned); 71 | 72 | EgcDecodeResult validationResult = edgcValidator.decodeEdgc(edgcQR); 73 | assertTrue(validationResult.isValidated()); 74 | assertNull(validationResult.getErrorMessage()); 75 | } 76 | 77 | private byte[] genDGCCbor(String edgcJson, String countryCode, long issuedAt, long expirationSec) { 78 | CBORObject map = CBORObject.NewMap(); 79 | map.set(CBORObject.FromObject(1),CBORObject.FromObject(countryCode)); 80 | map.set(CBORObject.FromObject(6),CBORObject.FromObject(issuedAt)); 81 | map.set(CBORObject.FromObject(4),CBORObject.FromObject(expirationSec)); 82 | CBORObject hcertVersion = CBORObject.NewMap(); 83 | CBORObject hcert = CBORObject.FromJSONString(edgcJson); 84 | hcertVersion.set(CBORObject.FromObject(1),hcert); 85 | map.set(CBORObject.FromObject(-260),hcertVersion); 86 | return map.EncodeToBytes(); 87 | } 88 | 89 | private byte[] genCoseUnsigned(byte[] payload,byte[] keyId,int algId) { 90 | CBORObject protectedHeader = CBORObject.NewMap(); 91 | protectedHeader.set(CBORObject.FromObject(1),CBORObject.FromObject(algId)); 92 | protectedHeader.set(CBORObject.FromObject(4),CBORObject.FromObject(keyId)); 93 | byte[] protectedHeaderBytes = protectedHeader.EncodeToBytes(); 94 | 95 | CBORObject coseObject = CBORObject.NewArray(); 96 | coseObject.Add(protectedHeaderBytes); 97 | coseObject.Add(CBORObject.NewMap()); 98 | coseObject.Add(CBORObject.FromObject(payload)); 99 | byte[] sigDummy = new byte[0]; 100 | coseObject.Add(CBORObject.FromObject(sigDummy)); 101 | return CBORObject.FromObjectAndTag(coseObject,18).EncodeToBytes(); 102 | } 103 | 104 | private byte[] genSetSignature(byte[] coseData,byte[] signature) { 105 | CBORObject cborObject = CBORObject.DecodeFromBytes(coseData); 106 | if (cborObject.getType() == CBORType.Array && cborObject.getValues().size()==4) { 107 | cborObject.set(3,CBORObject.FromObject(signature)); 108 | } else { 109 | throw new IllegalArgumentException("seems not to be cose"); 110 | } 111 | return cborObject.EncodeToBytes(); 112 | } 113 | 114 | private String coseToQRCode(byte[] cose) throws IOException { 115 | ByteArrayInputStream bis = new ByteArrayInputStream(cose); 116 | DeflaterInputStream compessedInput = new DeflaterInputStream(bis, new Deflater(9)); 117 | byte[] coseCompressed = compessedInput.readAllBytes(); 118 | Base45Service base45Service = new DefaultBase45Service(); 119 | String coded = base45Service.encode(coseCompressed); 120 | return "HC1:"+coded; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/test/java/eu/europa/ec/dgc/issuance/service/DgciGeneratorTest.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.issuance.service; 22 | 23 | import eu.europa.ec.dgc.issuance.config.IssuanceConfigProperties; 24 | import org.junit.jupiter.api.Test; 25 | 26 | import static org.junit.jupiter.api.Assertions.assertNotNull; 27 | import static org.junit.jupiter.api.Assertions.assertTrue; 28 | 29 | class DgciGeneratorTest { 30 | @Test 31 | void testGenerateDGCI() { 32 | IssuanceConfigProperties issuanceConfigProperties = new IssuanceConfigProperties(); 33 | issuanceConfigProperties.setDgciPrefix("URN:UVCI:V1:DE"); 34 | DgciGenerator dgciGenerator = new DgciGenerator(issuanceConfigProperties); 35 | String dgci = dgciGenerator.newDgci(); 36 | assertNotNull(dgci); 37 | assertTrue(dgci.startsWith(issuanceConfigProperties.getDgciPrefix())); 38 | assertTrue(dgci.length() <= 50,"dgci too long"); 39 | System.out.println(dgci); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/eu/europa/ec/dgc/issuance/service/EdgcValidatorTest.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.service; 2 | 3 | import eu.europa.ec.dgc.issuance.restapi.dto.EgcDecodeResult; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | 8 | import static org.junit.jupiter.api.Assertions.*; 9 | 10 | 11 | @SpringBootTest 12 | class EdgcValidatorTest { 13 | @Autowired 14 | EdgcValidator edgcValidator; 15 | 16 | @Test 17 | void testValidateSamples() throws Exception { 18 | String testData = "HC1:6BFOXN%TSMAHN-H/P8JU6+BS.5E9%UD82.7JJ59W2TT+FM*4/IQ0YVKQCPTHCV4*XUA2PWKP/HLIJL8JF8J" + 19 | "F7LPMIH-O92UQ7QQ%NH0LA5O6/UIGSU7QQ7NGWWBA 7.UIAYU3X3SH90THYZQ H9+W3.G8MSGPRAAUICO1DV59UE6Q1M650 LHZA0" + 20 | "D9E2LBHHGKLO-K%FGLIA5D8MJKQJK JMDJL9GG.IA.C8KRDL4O54O4IGUJKJGI.IAHLCV5GVWN.FKP123NJ%HBX/KR968X2-36/-K" + 21 | "KTCY73$80PU6QW6H+932QDONAC5287T:7N95*K64POPGI*%DC*G2KV SU1Y6B.QEN7+SQ4:4P2C:8UFOFC072.T2PE0*J65UY.2ED" + 22 | "TYJDK8W$WKF.VUV9L+VF3TY71NSFIM2F:47*J0JLV50M1WB*C"; 23 | 24 | EgcDecodeResult result = edgcValidator.decodeEdgc(testData); 25 | assertTrue(result.isValidated()); 26 | assertNull(result.getErrorMessage()); 27 | } 28 | 29 | @Test 30 | void testValidateAUSamples() throws Exception { 31 | String testData = "HC1:NCFOXN%TS3DHZN4HAF*PQFKKGTNA.Q/R8WRU2FCGJ9ZU6+9GNH5%DW+70ZMIN9HNO4*J8OX4W$C2VL*LA 4" + 32 | "3/IE%TE6UG+ZEAT1HQ13W1:O1YUI%F1PN1/T1J$HTR9/O14SI.J9DYHZROVZ05QNZ 20OP748$NI4L6RXKYQ8FRKBYOBM4T$7U-N0" + 33 | "O4RK43%JTXO$WOS%H*-VZIEQKERQ8IY1I$HH%U8 9PS5OH6*ZUFZFEPG:YN/P3JRH8LHGL2-LH/CJTK96L6SR9MU9DV5 R1:PI/E2" + 34 | "$4J6AL.+I9UV6$0+BNPHNBC7CTR3$VDY0DUFRLN/Y0Y/K9/IIF0%:K6*K$X4FUTD14//E3:FL.B$JDBLEH-BL1H6TK-CI:ULOPD6LF" + 35 | "20HFJC3DAYJDPKDUDBQEAJJKHHGEC8ZI9$JAQJKZ%K.CPM+81727KGB9S3EM.KV+LP:14JME*2T/5OFDSM*E.4RYXRN2IMA5GDUGS" + 36 | "0/X6*CW0Z3XZFK+FT6GF-I5U9:0J.LIVB0+2T+1K4OUWGQUKM.P6V20KK1R3"; 37 | 38 | EgcDecodeResult result = edgcValidator.decodeEdgc(testData); 39 | // it can be not validated because different key 40 | assertFalse(result.isValidated()); 41 | assertNull(result.getErrorMessage()); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/eu/europa/ec/dgc/issuance/service/ExpirationServiceTest.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.service; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import ehn.techiop.hcert.kotlin.data.GreenCertificate; 5 | import java.time.LocalDateTime; 6 | import java.time.ZoneOffset; 7 | import kotlinx.serialization.json.Json; 8 | import org.junit.jupiter.api.Test; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | 12 | import static org.junit.jupiter.api.Assertions.assertNotNull; 13 | 14 | @SpringBootTest 15 | class ExpirationServiceTest { 16 | @Autowired 17 | ExpirationService expirationService; 18 | ObjectMapper objectMapper = new ObjectMapper(); 19 | 20 | @Test 21 | void testExpriationCalculation() throws Exception { 22 | String vacDataJson = SampleData.vaccination; 23 | GreenCertificate eudgc = testCalculation("vactination",vacDataJson); 24 | System.out.println(eudgc.getVaccinations()[0].getDate()); 25 | String recoveryDataJson = SampleData.recovery; 26 | eudgc = testCalculation("recovery",recoveryDataJson); 27 | System.out.println(eudgc.getRecoveryStatements()[0].getCertificateValidUntil()); 28 | String testDataJson = SampleData.testNaa; 29 | eudgc = testCalculation("test",testDataJson); 30 | System.out.println(eudgc.getTests()[0].getDateTimeSample()); 31 | assertNotNull(eudgc); 32 | 33 | } 34 | 35 | private GreenCertificate testCalculation(String description, String vacDataJson) throws com.fasterxml.jackson.core.JsonProcessingException { 36 | System.out.println("testing: "+description); 37 | GreenCertificate eudgc = Json.Default.decodeFromString(GreenCertificate.Companion.serializer(), vacDataJson); 38 | ExpirationService.CwtTimeFields expTime = expirationService.calculateCwtExpiration(eudgc); 39 | LocalDateTime issuedAt = LocalDateTime.ofEpochSecond(expTime.getIssuedAt(), 0, ZoneOffset.UTC); 40 | assertNotNull(issuedAt); 41 | System.out.println(issuedAt); 42 | LocalDateTime expired = LocalDateTime.ofEpochSecond(expTime.getExpiration(), 0, ZoneOffset.UTC); 43 | System.out.println(expired); 44 | return eudgc; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/eu/europa/ec/dgc/issuance/service/SampleData.java: -------------------------------------------------------------------------------- 1 | package eu.europa.ec.dgc.issuance.service; 2 | 3 | public class SampleData { 4 | public static final String recovery = "{\n" + 5 | " \"ver\": \"1.2.1\",\n" + 6 | " \"nam\": {\n" + 7 | " \"fn\": \"Musterfrau-G\\u00f6\\u00dfinger\",\n" + 8 | " \"gn\": \"Gabriele\",\n" + 9 | " \"fnt\": \"MUSTERFRAU