├── .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 | 
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