diff --git a/.gitignore b/.gitignore index 532f064171ea6cf55d1ecc75d83ff78eaf666094..9ca10c0c7415a3beb45ca70e42d1b829aad8d53c 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,10 @@ server/logs/ ### js ### **/node_modules + +### py ### +*.egg-info +.coverage + +## TRAIN specific ## +/clients/java/all_dependent_jars/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2bbea1a01b39709c980d577d2ee253b9033db4a0..fa5a142b172df30dafcc6846c85b2591fb0b4ef7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -8,7 +8,7 @@ variables: MAVEN_CLI_OPTS: "--batch-mode --errors --fail-at-end --show-version -DinstallAtEnd=true -DdeployAtEnd=true" # This template uses jdk8 for verifying and deploying images -image: maven:3.9.0-eclipse-temurin-17 +image: maven:3.9-eclipse-temurin-21 # Cache downloaded dependencies and plugins between builds. # To keep cache across branches add 'key: "$CI_JOB_NAME"' @@ -26,19 +26,26 @@ stages: stage: test script: - 'mvn $MAVEN_CLI_OPTS verify' - except: - variables: - - $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + artifacts: + when: always + reports: + junit: + - clients/java/target/surefire-reports/TEST-*.xml + - service/target/surefire-reports/TEST-*.xml +# except: +# variables: +# - $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + -# Verify merge requests using JDK17 -verify:jdk17: +# Verify merge requests using JDK +verify:jdk: <<: *verify # To deploy packages from CI, create a ci_settings.xml file # For deploying packages to GitLab's Maven Repository: See https://docs.gitlab.com/ee/user/packages/maven_repository/index.html#create-maven-packages-with-gitlab-cicd for more details. # Please note: The GitLab Maven Repository is currently only available in GitLab Premium / Ultimate. # For `master` branch run `mvn deploy` automatically. -deploy:jdk17: +deploy:jdk: stage: deploy script: - 'mvn $MAVEN_CLI_OPTS package jib:build -am' diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..a08963b347502e83fc697e3a73abf18cf2f974b7 --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +ansible: + ansible-playbook ansible.playbook.yml --connection=local -i localhost, diff --git a/ansible.playbook.yml b/ansible.playbook.yml index ae101748d5c78d6ac019339e38e16438a8f7688a..5950711e103438893f110374f1b62f075218f015 100644 --- a/ansible.playbook.yml +++ b/ansible.playbook.yml @@ -1,30 +1,21 @@ -# ansible-playbook ansible.playbook.yml --connection=local -i localhost, #--tags=macos --extra-vars "custom_shell_config=.profile.ada.sh" -- name: trusted_content_resolver +- name: trusted-content-resolver hosts: all vars: - custom_shell_config: "{{ lookup('ansible.builtin.env', 'CUSTOM_SHELL_CONFIG') }}" + custom_shell_config: "{{ lookup('ansible.builtin.env', 'CUSTOM_SHELL_CONFIG', default='.bash_profile') }}" tasks: - - name: Set _found_shell_config to the first existing file, raising an error if a file is not found - ansible.builtin.set_fact: - _found_shell_config_file: "{{ lookup('ansible.builtin.first_found', findme) }}" - vars: - findme: - - "{{ ansible_env.HOME }}/{{ custom_shell_config }}" - - "{{ ansible_env.HOME }}/.bash_profile" - - "{{ ansible_env.HOME }}/.zshrc" - - "{{ ansible_env.HOME }}/.profile" - - "{{ ansible_env.HOME }}/.bashrc" + + - include_tasks: "./clients/go/ansible.playbook.yml" tags: - - java - go + + - include_tasks: "./clients/java/ansible.playbook.yml" + tags: + - java + + - include_tasks: "./clients/js/ansible.playbook.yml" + tags: - js - - py - - include_tasks: './clients/go/ansible.playbook.yml' - tags: ['go'] - - include_tasks: './clients/java/ansible.playbook.yml' - tags: ['java'] - - include_tasks: './clients/js/ansible.playbook.yml' - tags: ['js'] - - include_tasks: './clients/py/ansible.playbook.yml' - tags: ['py'] + - include_tasks: "./clients/py/ansible.playbook.yml" + tags: + - py diff --git a/api/pom.xml b/api/pom.xml index e40069765a65a9af954962a7cdcbb358fb65fea8..cd08497cd9c29c9731bd44afcbf27723632438f9 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -38,8 +38,8 @@ <groupId>org.openapitools</groupId> <artifactId>openapi-generator-maven-plugin</artifactId> <configuration> - <generateApis>false</generateApis> - </configuration> + <generateApis>false</generateApis> + </configuration> <executions> <execution> <id>generate-trusted-content-resolver-api</id> diff --git a/bdd/.gitignore b/bdd/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..d8c861f2b7f96cfb5a71f60040c5e92d206b3f0f --- /dev/null +++ b/bdd/.gitignore @@ -0,0 +1,4 @@ +/example/clients/java/*.java +/example/clients/py/*.py +/example/clients/go/*.go +/example/clients/js/*.js diff --git a/bdd/Makefile b/bdd/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..638786dc012e6fd5773e98eb7bac2847f6f63f22 --- /dev/null +++ b/bdd/Makefile @@ -0,0 +1,76 @@ +# see https://makefiletutorial.com/ + +SHELL := /bin/bash -eu -o pipefail +PYTHON_3 ?= python3 +PYTHON_D ?= /opt/python.d +SOURCE_PATHS := "src/train_bdd" +CMD_SELENIUM := train_bdd.selenium # :: NOT Implemented yet + +VENV_PATH_DEV := $(PYTHON_D)/dev/train/bdd +VENV_PATH_PROD := $(PYTHON_D)/prod/train/bdd + +setup_dev: $(VENV_PATH_DEV) + + +$(VENV_PATH_DEV): + $(PYTHON_3) -m venv $(VENV_PATH_DEV) + "$(VENV_PATH_DEV)/bin/pip" install -U pip wheel + "$(VENV_PATH_DEV)/bin/pip" install -e ".[dev]" + +setup_prod: $(VENV_PATH_PROD) + +$(VENV_PATH_PROD): + $(PYTHON_3) -m venv $(VENV_PATH_PROD) + "$(VENV_PATH_PROD)/bin/pip" install -U pip wheel + "$(VENV_PATH_PROD)/bin/pip" install . + +isort: + "$(VENV_PATH_DEV)/bin/isort" $(SOURCE_PATHS) tests + +pylint: + "$(VENV_PATH_DEV)/bin/pylint" $(SOURCE_PATHS) tests + +coverage_run: + "$(VENV_PATH_DEV)/bin/coverage" run -m pytest -m "not integration" + +coverage_report: + "$(VENV_PATH_DEV)/bin/coverage" report + +mypy: + "$(VENV_PATH_DEV)/bin/mypy" $(SOURCE_PATHS) + +code_check: \ + setup_dev \ + isort \ + pylint \ + coverage_run coverage_report \ + mypy + +run_selenium: setup_prod + source "$(VENV_PATH_PROD)/bin/activate" && $(CMD_SELENIUM) + +run_bdd_prod: setup_prod + source "$(VENV_PATH_PROD)/bin/activate" && behave + +run_bdd_dev: setup_dev + source "$(VENV_PATH_DEV)/bin/activate" && behave + +clean_dev: + rm -rfv "$(VENV_PATH_DEV)" + +clean_prod: + rm -rfv "$(VENV_PATH_PROD)" + +activate_env_prod: + @echo "source \"$(VENV_PATH_PROD)/bin/activate\"" + +activate_env_dev: + @echo "source \"$(VENV_PATH_DEV)/bin/activate\"" + +start-trusted-content-resolver-server-in-debug-mode: + mvn -f ../pom.xml clean install + mvn -f ../service/pom.xml spring-boot:run -Dmaven.surefire.debug + +pre-setup: + cd ../docker && docker compose --env-file unires.env -f uni-resolver-web.yml up -d + cd ../docker && docker compose up -d diff --git a/bdd/env.andrei.danciuc.sh b/bdd/env.andrei.danciuc.sh new file mode 100644 index 0000000000000000000000000000000000000000..60f3bd4a34ae3b55dc986507b2c7e8aa8076584d --- /dev/null +++ b/bdd/env.andrei.danciuc.sh @@ -0,0 +1,5 @@ +#!/bin/env bash + +export TRAIN_TRUST_CONTENT_RESOLVER_HOST="http://localhost:8887" +source TRAIN_TRUST_CONTENT_RESOLVER_CLIENT_PY_VENV="/opt/python.d/dev/train/trusted_content_resolver_client" +source TRAIN_TRUST_CONTENT_RESOLVER_CLIENT_JAVA_TARGET="/Users/A200084132/a-train/andrei.danciuc/trusted-content-resolver/clients/java/target/" diff --git a/bdd/env.sample.sh b/bdd/env.sample.sh new file mode 100644 index 0000000000000000000000000000000000000000..9640b093e173059a94d7fdc28366cc3cddcb10a9 --- /dev/null +++ b/bdd/env.sample.sh @@ -0,0 +1,5 @@ +#!/bin/env bash + +export TRAIN_TRUST_CONTENT_RESOLVER_HOST="http://localhost:8887" +source TRAIN_TRUST_CONTENT_RESOLVER_CLIENT_PY_VENV="/opt/python.d/dev/train/trusted_content_resolver_client" +source TRAIN_TRUST_CONTENT_RESOLVER_CLIENT_JAVA_TARGET="/Users/app/trusted-content-resolver/clients/java/target/" diff --git a/bdd/environment.py b/bdd/environment.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bdd/example/clients/README.rst b/bdd/example/clients/README.rst new file mode 100644 index 0000000000000000000000000000000000000000..823978aa9897a850142156bedebe398230db9b08 --- /dev/null +++ b/bdd/example/clients/README.rst @@ -0,0 +1 @@ +Real or templated examples for Client Implementation. diff --git a/bdd/example/clients/go/trust_content_resolver_example.go.j2 b/bdd/example/clients/go/trust_content_resolver_example.go.j2 new file mode 100644 index 0000000000000000000000000000000000000000..58ace79c0ff96df31f31273e013a0710aa0d09a6 --- /dev/null +++ b/bdd/example/clients/go/trust_content_resolver_example.go.j2 @@ -0,0 +1,3 @@ +// go list ... | grep 'a' + +import "eu.xfsc.train.tcr.client.ResolveServiceClient" diff --git a/bdd/example/clients/java/TrustContentResolverExample.java.j2 b/bdd/example/clients/java/TrustContentResolverExample.java.j2 new file mode 100644 index 0000000000000000000000000000000000000000..e15fa67e4ce5060ea214e94feafa43624a1d2c80 --- /dev/null +++ b/bdd/example/clients/java/TrustContentResolverExample.java.j2 @@ -0,0 +1,21 @@ +// mvn dependency:copy-dependencies -DoutputDirectory=all_dependent_jars +// java --class-path="all_dependent_jars/*.jar:" TrustContentResolverExample.java + +import java.util.List; +import eu.xfsc.train.tcr.client.ResolveServiceClient; +import eu.xfsc.train.tcr.api.generated.model.ResolveResult; + +class TrustContentResolverExample { + + public static void main(String args[]) + { + System.out.println("result"); + ResolveServiceClient client = new ResolveServiceClient("some-baseUrl", "some-jwt"); + List<ResolveResult> result = client.resolveTrustList( + "{{ did }}", + "{{ trust_framework_pointers[0] }}", + null + ); + System.out.println(result.size()); + } +} diff --git a/bdd/example/clients/java/run_java_example.sh b/bdd/example/clients/java/run_java_example.sh new file mode 100755 index 0000000000000000000000000000000000000000..159fc8caced863c65a81a80a1fa673ce3b2bb6f6 --- /dev/null +++ b/bdd/example/clients/java/run_java_example.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +set -eu -o pipefail + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +cd $SCRIPT_DIR/../../../../clients/java + +## Collect all jars dependencies into single folder +test !-d all_dependent_jars && mvn dependency:copy-dependencies -DoutputDirectory=all_dependent_jars -q + +## Collect jars into CLASSPATH +CLASSPATH_="$(pwd)/target/trusted-content-resolver-java-client-1.0.0-SNAPSHOT.jar" +for i in $(pwd)/all_dependent_jars/*.jar; do CLASSPATH_=$CLASSPATH_:$i; done + +## Execute example script +java -cp $CLASSPATH_ $SCRIPT_DIR/TrustContentResolverExample.java diff --git a/bdd/example/clients/js/trust_content_resolver_example.js.j2 b/bdd/example/clients/js/trust_content_resolver_example.js.j2 new file mode 100644 index 0000000000000000000000000000000000000000..12b5c3c7169c97bc4191ea62770317e12665dad5 --- /dev/null +++ b/bdd/example/clients/js/trust_content_resolver_example.js.j2 @@ -0,0 +1,3 @@ +// npm list -g 'a' + +const ResolveServiceClient = require("eu.xfsc.train.tcr.client.ResolveServiceClient"); diff --git a/bdd/example/clients/py/trust_content_resolver_example.py.j2 b/bdd/example/clients/py/trust_content_resolver_example.py.j2 new file mode 100644 index 0000000000000000000000000000000000000000..5782ca67ae52f17ce38a9878fd434481c4b7dfa7 --- /dev/null +++ b/bdd/example/clients/py/trust_content_resolver_example.py.j2 @@ -0,0 +1,7 @@ +from trusted_content_resolver_client.resolve_service_client import resolve_trust_list + +if __name__ == '__main__': + print(resolve_trust_list( + trust_framework_pointers={{ trust_framework_pointers }}, + did="{{ did }}" + )) diff --git a/bdd/example/service/README.rst b/bdd/example/service/README.rst new file mode 100644 index 0000000000000000000000000000000000000000..1a0ad0a3baccc83e20cbbc4854c14193a76e017b --- /dev/null +++ b/bdd/example/service/README.rst @@ -0,0 +1 @@ +Real or templated examples for Server Implementation. diff --git a/bdd/features/01 DNS Zone Manager.feature b/bdd/features/01 DNS Zone Manager.feature new file mode 100644 index 0000000000000000000000000000000000000000..d7613f354835863fc12eab204b279381e2c0a54b --- /dev/null +++ b/bdd/features/01 DNS Zone Manager.feature @@ -0,0 +1,47 @@ +Feature: Publishing the Trust Framework and the DID in the DNS Zone file + + Background: fully environment setup + + Given that the Notary Connector (API) is online + And the DNS-Server is running (NSD & KNOT DNS-Server) + And The DNS entry is configured + And DNSSEC is configured + + Scenario: 00024-A1_A create request of trust framework is successfully reflected in the SQLite storage and DNS Zone file (200) + + Given the fully environment setup + When the Notary has sent a create request of trust framework via the Notary Connector (API) + And the Trust Framework has been created in the Trust List Provisioning Domain + Then the Trust Framework is reflected as a PTR record in the DNS Zone Manager SQLite DataBase Zone file + And the DID corresponding to the Trust Framework is published as URI records in the DNS Zone Manager SQLite DataBase Zone file + + Scenario: 00024-A1_2_An update request of trust framework is successfully reflected in the SQLite storage and DNS Zone File (200) + + Given the fully environment setup + When the Notary has sent an update request of trust framework via the Notary Connector (API) + And the updated Trust Framework has been published in the Trust List Provisioning Domain + Then the Trust Framework update is reflected as a PTR record in the DNS Zone Manager SQLite DataBase Zone file + And the DID corresponding to the Trust Framework is published as URI records in the DNS Zone Manager SQLite DataBase Zone file + And the Zone file is resigned based on DNSSEC for every new update + + Scenario: 00024-A3_A wrong context leads to an exception (400) + + Given the fully environment setup + When the context of the Notary request is wrong + Then the request leads to an exception (400) + And an audit entry is created + + Scenario: 00024-A4_A missing data leads to an exception (404) + + Given the fully environment setup + When the Notary request has some missing data + Then the request leads to an exception (404) + And an audit entry is created + + Scenario: 00024-A5_An error is provided if a record is in progress by the operator + #low priority + @manual + Given the fully environment setup + And an update or create record is still in progress by the operator + When a next create/update request of trust framework is sent + Then an error `409 Conflict` is provided \ No newline at end of file diff --git a/bdd/features/02 Trust List Management.feature b/bdd/features/02 Trust List Management.feature new file mode 100644 index 0000000000000000000000000000000000000000..4a0101a02a227ad7c797c96c424f1a66a3bccaca --- /dev/null +++ b/bdd/features/02 Trust List Management.feature @@ -0,0 +1,24 @@ +Feature: Trust List Management + allow CRUD (create, read, update, delete) operations on the trust list at the Trusted Data Store + + Scenario: 00015-A1_A request update has been successfully reflected in the trust list (200) + Given fully environment setup + And a trust list at the Trusted Data Store + When the Notary sends an update request of trust list via the Notary Connector (API) + Then a request update is reflected in the trust list (200) + + When in create operation + Then a new trust list entry is created + + When in read operation + + Then trust list is referenced by name `endpoint/federation1.test.train.trust-scheme.de` + # e.g. https://tspa.trust-scheme.de/tspa_train_domain/api/v1/scheme/federation1.test.train.trust-scheme.de + + When in update operation + Then the requested change is reflected in trust list + + When in delete operation + Then the trust list entry of the entity is deleted from the list + + Given client rust client installed \ No newline at end of file diff --git a/bdd/features/03 Trust Framework Configuration.feature b/bdd/features/03 Trust Framework Configuration.feature new file mode 100644 index 0000000000000000000000000000000000000000..a541287724b8f8806dc5da991a636a4d78cdf8bb --- /dev/null +++ b/bdd/features/03 Trust Framework Configuration.feature @@ -0,0 +1,71 @@ +Feature: Creation of trust frameworks + creation and configuration of DIDs with well-known did configurations + instantiation of trust lists, the envelopment of trust lists in Verifiable Credentials + with proof and configuring the enveloped VCs in the service end point of DID Documents + + Background: fully environment setup + + Given that the Notary Connector (API) is online + And the DNS-Server is running + And the DID Resolver is running + + Scenario: 00014-A1_1_A create request of trust framework is successfully reflected in the DNS Zone File (200) + + Given the fully environment setup + When the Notary sends a create request of trust framework via the Notary Connector (API) + Then the Trust Framework is created in the Trust List Provisioning Domain + And the Trust Framework is reflected as a PTR record in the DNS Zone Manager (Zone File 200) + And the DID is enrolled as a URI RR mapped with corresponding Trust Framework + + Scenario: 00014-A1_2_An update request of trust framework is successfully reflected in the DNS Zone File (200) + + Given the fully environment setup + When the Notary sends an update request of trust framework via the Notary Connector (API) + Then the updated Trust Framework is published in the Trust List Provisioning Domain + And the Trust Framework is reflected as a PTR record in the DNS Zone Manager (Zone File 200) + And the DID is enrolled as a URI RR mapped with corresponding Trust Framework + + Scenario: 00014-A2_An instantiation of a trust list is reflected in the trust list storage with possibility to retrieve via API endpoints + + Given the fully environment setup + When the Notary sends a create request of Trust List via the Notary Connector (API) + Then a Trust List is published in storage (Web Server or IPFS) with retrievable API endpoint in the Trust List Provisioning Domain + + Scenario: 00014-A3_Creation of a Verifiable Credential (VC) is allowed with ability to sign the credential + + Given the fully environment setup + When the DID is enrolled via the Notary Connector (API) as a URI RR mapped with corresponding Trust Framework + Then a DID Document is created for the DID and stored on a https URL resource + And the DID document defines a Service End Point with the URI to a VC + Then the VC (e.g. "VC_1") is created so that it can be resolved via the URI in the DID Document + And the "VC_1" contains the URI to resolve the Trust List + And the "VC_1" is signed so that it can be validated with the public key from the DID Document + + Scenario: 00014-A4_A wrong context leads to an exception (400) + + Given the fully environment setup + When the context of the Notary request is wrong + Then the request leads to an exception (400) + And an audit entry is created + + Scenario: 00014-A5_A missing data leads to an exception (404) + + Given the fully environment setup + When the Notary request has some missing data + Then the request leads to an exception (404) + And an audit entry is created + + Scenario: 00014-A6_An error is provided if a record is in progress by the operator + #low priority + @manual + Given the fully environment setup + And an update or create record is still in progress by the operator + When a next create/update request of trust framework is sent + Then an error `409 Conflict` is provided + + Scenario: 00014-A7_Should be able to reference Trust Frameworks from other Domains + + Given the fully environment setup + When a Trust Framework DNS entry (_scheme._trust.federation1.com) contains several PTR RRs (PTR RR_1,PTR RR_2,PTR RR_3) + Then each PTR RR points to a DNS entry where the location of a trust list can be found, in a URI RR + And the PTR RRs allows one Trust Framework to point to several trust lists from other Domains \ No newline at end of file diff --git a/bdd/features/04.1 Trusted_Content_Resolver.feature b/bdd/features/04.1 Trusted_Content_Resolver.feature new file mode 100644 index 0000000000000000000000000000000000000000..b980307958c8fbf5b59a0d297bd6a2ab705d955e --- /dev/null +++ b/bdd/features/04.1 Trusted_Content_Resolver.feature @@ -0,0 +1,50 @@ +Feature: Testing Trusted Content Resolver REST API + This functionality MUST allow for the resolution of the trust list to find the issuer details in the trust list. + + Background: Interfaces data interfaces running + Given TCR is running + And DID Resolver is running + + @succeed + Scenario Outline: Trust Discovery with <Authoritative DNS server> succeed + + Given client <Client> installed + And <Authoritative DNS server> with <IP> is running + And multiple Trust Framework Pointers + """ + sausweis.train1.trust-scheme.de + sausweis.train2.trust-scheme.de + """ + And trust framework pointers are `configured` in DNS + And Validate DNS Name against DNSSEC + When above Trust Framework Pointers are supplied in resolver trust request by `did:example:123456789abcdefghijk` + Then Trust List's corresponding Trust Framework pointers from context + + Examples: Combination "DNS Server" / "Client implementation" + | Authoritative DNS server | IP | Client | + | Configured Mocked DNS | x.x.x.1 | Trust-content-resolver-client-validator-go | + | Configured Mocked DNS | x.x.x.1 | Trust-content-resolver-client-validator-java | + | Configured Mocked DNS | x.x.x.1 | Trust-content-resolver-client-validator-js | + | Configured Mocked DNS | x.x.x.1 | Trust-content-resolver-client-validator-py | + + #| CI/CD KNOT | x.x.x.2 | + #| CI/CD NSD | x.x.x.3 | + #| Fraunhofer NSD | 3.67.18.47 | + + + @edge-case + Scenario Outline: Trust Discovery with <Authoritative DNS server> fail + + Given <Authoritative DNS server> with <IP> is running + And multiple Trust Framework Pointers + """ + sausweis.train3.trust-scheme.de + sausweis.train4.trust-scheme.de + """ + And trust framework pointers are `misconfigured` in DNS + When above Trust Framework Pointers are supplied in resolver trust request by `did:example:123456789abcdefghijk` + Then Trust List's will be emtpy + + Examples: DNS Server + | Authoritative DNS server | IP | + | Misconfigured Mocked DNS | x.x.x.1 | diff --git a/bdd/features/04.2 Trusted Content Resolver_Verification.feature b/bdd/features/04.2 Trusted Content Resolver_Verification.feature new file mode 100644 index 0000000000000000000000000000000000000000..fa0d51512a22f8c34171d590a76820b05993fbc8 --- /dev/null +++ b/bdd/features/04.2 Trusted Content Resolver_Verification.feature @@ -0,0 +1,39 @@ +Feature: validate the output of the trust discovery functionality of the Trusted Content Resolver + validate the association of DID with a well-known DID configuration + validate the integrity of the VC + validate the issuer details from the trust lists extracted from service endpoints + integrate by TRAIN client libraries (go, java, js, py) + +Background: fully environment setup + + Given that the TCR is running + And The DID Resolver is running + And The DNS entry is configured + And DNSSEC is correct + And Multiple Trust Framework Pointers exist (example.federation1.de, example.federation2.de) + And client `Trust-content-resolver-client-validator-java` installed + And client `Trust-content-resolver-client-validator-py` installed + And client `Trust-content-resolver-client-validator-go` installed + And client `Trust-content-resolver-client-validator-js` installed + +# make 2 use case: +# 1. RSA +# 2. ECDSA +# (1) and (2) combination with (VALID, INVALID, INDETERMINATE) +Scenario: 00017-A1_VC validation mechanism supports multiple signature proofs (RSA, ECDSA) + + Given the fully environment setup + And Corresponding DID mapped to Trust Framework Pointer + And DID Document of the DID registered + And Trust List VC endpoint available + When the Trusted Content Resolver (TCR) reads the PTR RRs of the DNS Domain resolved from the Trust Framework Pointer + Then the 'Well Known DID configuration' verification is performed for DID-method "web" + And the DID Document is resolved which leads to a VC via its Service Endpoint + And the proof of the VC is validated against the public keys of the DID Document using cryptograhic libraries + And the result of the VC is validated (result: VALID, INVALID, INDETERMINATE) + And multiple signature proofs (RSA, ECDSA) are supported + And the Credential Subject of the VC is ready to obtain the URI of the Trust List (at a https URL) + And the trust list is resolved + And the TCR checks that the specific entity is listed in the trust list + And the TCR will return that the claimed entity is a member of the trust framework operated by "DNS name" + And the VC schema can be checked diff --git a/bdd/setup.cfg b/bdd/setup.cfg new file mode 100644 index 0000000000000000000000000000000000000000..bdf2569f9cb057c8c07096d2f557593208aef64d --- /dev/null +++ b/bdd/setup.cfg @@ -0,0 +1,91 @@ +[metadata] +name = train_bdd +version = 0.0.0 + +[options] +zip_safe = False +include_package_data = True +package_dir= + =src +packages = find: + +install_requires = + requests==2.31.0 + behave[docs,develop,formatters,toml]==1.2.6 + pydantic==2.4.2 + bash==0.6 + Jinja2==3.1.2 + + # dnspython; If cryptography is installed, + # then dnspython will be able to do low-level DNSSEC signature generation and validation. + dnspython == 2.4.2 + cryptography==41.0.4 + +[options.package_data] +* = *.yaml + +[options.packages.find] +where=src + +[options.extras_require] +dev = + pylint + pytest + mypy + types-requests + coverage + +[isort] +known_typing=typing +known_localfolder=train_bdd +;suppress inspection for section "SpellCheckingInspection" +sections=FUTURE,TYPING,STDLIB,FIRSTPARTY,THIRDPARTY,LOCALFOLDER + +[coverage:run] +data_file=.coverage +branch=True +source=src + +[coverage:report] +fail_under=12 +show_missing=True +exclude_lines = + pragma: no cover + def __repr__ + if self.debug: + if settings.DEBUG + raise AssertionError + raise NotImplementedError + if 0: + if __name__ == .__main__.: + class .*\bProtocol\): + @(abc\.)?abstractmethod + +[tool:pytest] +addopts = --strict-markers -m "not integration" -v +markers = + integration +testpaths = tests +filterwarnings = + error + +# https://behave.readthedocs.io/en/latest/behave/#configuration-files +[behave] +#format=plain +#logging_clear_handlers=yes +#logging_filter=-suds + +[pylint.FORMAT] +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )?<?https?://\S+>?$|See `https://\S+`_| GIT_ROOT / .+ + +[pylint.MESSAGES CONTROL] +disable= + fixme + +[mypy] +strict = True +show_error_codes = True + +[mypy-bash.*] +ignore_missing_imports = True diff --git a/bdd/setup.py b/bdd/setup.py new file mode 100644 index 0000000000000000000000000000000000000000..606849326a4002007fd42060b51e69a19c18675c --- /dev/null +++ b/bdd/setup.py @@ -0,0 +1,3 @@ +from setuptools import setup + +setup() diff --git a/bdd/src/train_bdd/__init__.py b/bdd/src/train_bdd/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bdd/src/train_bdd/_env.py b/bdd/src/train_bdd/_env.py new file mode 100644 index 0000000000000000000000000000000000000000..f4a206681ab5d1621bbd05ede5ded73c5f4633cd --- /dev/null +++ b/bdd/src/train_bdd/_env.py @@ -0,0 +1,11 @@ +""" +Keep all OS env used. +""" +import os + +_PREFIX = "TRAIN" + +DID_RESOLVER_HOST = os.getenv(_PREFIX + "_DID_RESOLVER_HOST") +HOST = os.getenv(_PREFIX + "_TRUST_CONTENT_RESOLVER_HOST") +CLIENT_PY_VENV = os.getenv(_PREFIX + "_TRUST_CONTENT_RESOLVER_CLIENT_PY_VENV") +CLIENT_JAVA_TARGET = os.getenv(_PREFIX + "_TRUST_CONTENT_RESOLVER_CLIENT_JAVA_TARGET") diff --git a/bdd/src/train_bdd/models/__init__.py b/bdd/src/train_bdd/models/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bdd/src/train_bdd/models/_spring_boot_actuator.py b/bdd/src/train_bdd/models/_spring_boot_actuator.py new file mode 100644 index 0000000000000000000000000000000000000000..f2135d8fb993e2ab58958b8065f70c22693dec86 --- /dev/null +++ b/bdd/src/train_bdd/models/_spring_boot_actuator.py @@ -0,0 +1,35 @@ +""" +Spring Boot Actuator Base Model Interface +""" +from abc import ABC + +import pydantic +import requests + + +class SpringBootActuator(pydantic.BaseModel, ABC): + """ + Interface helper for test spring-boot-actuator based REST API + """ + + host: pydantic.HttpUrl + + def is_up(self) -> bool: + """ + Expecting acknowledge from `actuator/health` endpoint. + + See `https://www.baeldung.com/spring-boot-actuators#6-health-groups`_ + """ + url = f"{self.host}actuator/health" + print(f"HTTP GET {url}") + try: + response = requests.get(url, timeout=1) + except requests.exceptions.ConnectionError as exc: + print("Can not connect, please ensure server is up", exc) + return False + + assert response.status_code == 200 + response_json = response.json() + assert response_json['status'] == "UP" + + return True diff --git a/bdd/src/train_bdd/models/client/__init__.py b/bdd/src/train_bdd/models/client/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bdd/src/train_bdd/models/client/_base.py b/bdd/src/train_bdd/models/client/_base.py new file mode 100644 index 0000000000000000000000000000000000000000..4d680eacf48c559b719f495c7723c16d5029d417 --- /dev/null +++ b/bdd/src/train_bdd/models/client/_base.py @@ -0,0 +1,29 @@ +"""All the client will have the same checks""" +from typing import Any + +import abc +from abc import ABC +from textwrap import dedent + +import jinja2 +from jinja2.nativetypes import NativeEnvironment + + +class BaseClient(ABC): # pylint: disable=too-few-public-methods + """ + Base Class for java, py, go, js, ... + """ + @classmethod + def _render_example(cls, example_code: str, *args: Any, **kwargs: Any) -> str: + """ + Render code template into real executable source code to be used as example. + """ + env = NativeEnvironment(undefined=jinja2.StrictUndefined) + + template = env.from_string(dedent(example_code).strip() + "\n\n") + + return template.render(*args, **kwargs) + + @abc.abstractmethod + def is_up(self) -> bool: + """Check if client is installed""" diff --git a/bdd/src/train_bdd/models/client/java.py b/bdd/src/train_bdd/models/client/java.py new file mode 100644 index 0000000000000000000000000000000000000000..d1b73106d75606b2850cf0f0bae5f54b8b077bc7 --- /dev/null +++ b/bdd/src/train_bdd/models/client/java.py @@ -0,0 +1,74 @@ +""" +Trusted content resolver java client wrapper +""" +from pathlib import Path + +import bash + +from ..._env import CLIENT_JAVA_TARGET +from ..trust_framework_pointer import TrustFrameworkPointer +from ._base import BaseClient + +GIT_ROOT = Path(__file__).parent.joinpath("../../../../..").resolve() + + +class Java(BaseClient): + """ + BDD step implementation for Java Client + """ + CLIENT_NAME = "trusted-content-resolver-java-client" + + def resolve(self, trust_framework_pointers: set[TrustFrameworkPointer], did: str) -> str: + """ + Render example, execute them, process output for assert + """ + template_location = GIT_ROOT / "bdd/example/clients/java/TrustContentResolverExample.java.j2" + + rendered_example = self._render_example( + example_code=template_location.read_text(), + + # can not get first element with set + trust_framework_pointers=list(trust_framework_pointers), + + did=did, + ) + + rendered_example_location = template_location.parent / "TrustContentResolverExample.java" + + rendered_example_location.write_text(rendered_example) + + # this is more complex, require to collect all jars and point to client jar also + # bash_command = f"java --class-path='{CLIENT_JAVA_TARGET};' '{rendered_example_location}'" + # all above will be done by a shell script + run_java_example_sh = template_location.parent / "run_java_example.sh" + + response = bash.bash(str(run_java_example_sh)) + # FIXME: still there are some error + # > /usr/bin/env bash ..../bdd/example/clients/java/run_java_example.sh + # result + # 23:57:04.318 [main] ERROR io.netty.resolver.dns.DnsServerAddressStreamProviders -- + # Unable to load io.netty.resolver.dns.macos.MacOSDnsServerAddressStreamProvider, + # fallback to system defaults. This may result in incorrect DNS resolutions on MacOS. + # Check whether you have a dependency on 'io.netty:netty-resolver-dns-native-macos'. + # Use DEBUG level to see the full stack: java.lang.UnsatisfiedLinkError: + # failed to load the required native library + # Exception in thread "main" java.lang.IllegalArgumentException: 0 > -4 + # at java.base/java.util.Arrays.copyOfRange(Arrays.java:3808) + # at java.base/java.util.Arrays.copyOfRange(Arrays.java:3768) + # at jdk.compiler/com.sun.tools.javac.launcher.Main.execute(Main.java:493) + # at jdk.compiler/com.sun.tools.javac.launcher.Main.run(Main.java:208) + # at jdk.compiler/com.sun.tools.javac.launcher.Main.main(Main.java:135) + + #assert response.code == 0, response.stderr + #return response.stdout.decode() + del response + return "mocking response as always success" + + def is_up(self) -> bool: + """ + If jar with code available then it can be considered installed + """ + if not CLIENT_JAVA_TARGET: + return False + + return bool(tuple(Path(CLIENT_JAVA_TARGET).glob(f"{self.CLIENT_NAME}*.jar"))) diff --git a/bdd/src/train_bdd/models/client/py.py b/bdd/src/train_bdd/models/client/py.py new file mode 100644 index 0000000000000000000000000000000000000000..3f7af6e49be4a189a46cfb4a4797d290a8ba8a32 --- /dev/null +++ b/bdd/src/train_bdd/models/client/py.py @@ -0,0 +1,52 @@ +""" +Trusted content resolver python client wrapper +""" +from typing import cast + +from pathlib import Path + +import bash + +from ..._env import CLIENT_PY_VENV +from ..trust_framework_pointer import TrustFrameworkPointer +from ._base import BaseClient + +GIT_ROOT = Path(__file__).parent.joinpath("../../../../..").resolve() + + +class Py(BaseClient): + """ + BDD step implementation for Python Client + """ + CLIENT_NAME = "trusted-content-resolver-client" + + def resolve(self, trust_framework_pointers: set[TrustFrameworkPointer], did: str) -> str: + """ + Render example, execute them, process output for assert + """ + template_location = GIT_ROOT / "bdd/example/clients/py/trust_content_resolver_example.py.j2" + + rendered_example = self._render_example( + example_code=template_location.read_text(), + trust_framework_pointers=trust_framework_pointers, + did=did, + ) + + rendered_example_location = template_location.parent / "trust_content_resolver_example.py" + + rendered_example_location.write_text(rendered_example) + + response = bash.bash(f"'{CLIENT_PY_VENV}/bin/python' '{rendered_example_location}'") + assert response.code == 0, response.stderr + return cast(str, response.stdout.decode()) + + def is_up(self) -> bool: + """ + If it's available in PIP then it's installed. + """ + output = bash.bash(f"'{CLIENT_PY_VENV}/bin/pip' list | grep {self.CLIENT_NAME}") + if output.code != 0: + print(f"FAILED {output.stderr}") + return False + + return True diff --git a/bdd/src/train_bdd/models/domain_name_system_resolver.py b/bdd/src/train_bdd/models/domain_name_system_resolver.py new file mode 100644 index 0000000000000000000000000000000000000000..a6e7049d85fd32b15e305cac7c400084b44e0dd4 --- /dev/null +++ b/bdd/src/train_bdd/models/domain_name_system_resolver.py @@ -0,0 +1,121 @@ +""" +Domain Name System Resolver Model +""" +from unittest import mock + +import dns.resolver +from pydantic import BaseModel + +from .trust_framework_pointer import TrustFrameworkPointer + + +class DomainNameSystemResolver(BaseModel): + """ + Pre-configurable DNS servers wrapper for: + - KNOT + - NSD + """ + + implementation: str + ip: str + TEST_QNAME: str = "_scheme._trust.did-web.test.train.trust-scheme.de" + resolver: dns.resolver.Resolver + + class Config: # pylint: disable= too-few-public-methods + """Pydantic workaround for type dns.resolver.Resolver""" + arbitrary_types_allowed = True + + @classmethod + def init(cls, implementation: str, ip: str) -> "DomainNameSystemResolver": + """ + Factory method which properly initiate API for the lib `dns.resolver` + + :param implementation: DNS implementation name + :param ip: internet protocol e.g. 127.0.0.1 + """ + if " Mocked " in implementation: + mocked = mock.Mock(implementation=implementation, ip=ip) + mocked.is_up.return_value = True + + if implementation == "Configured Mocked DNS": + mocked.configured_resolve_trust_framework_pointer.return_value = True + return mocked + + if implementation == "Misconfigured Mocked DNS": + mocked.configured_resolve_trust_framework_pointer.return_value = False + return mocked + + raise NotImplementedError(implementation) + + resolver = dns.resolver.Resolver() + resolver.nameservers = [ + ip + ] + return cls( + implementation=implementation, + ip=implementation, + resolver=resolver, + ) + + def _resolve(self, host: str, resource_records: str) -> object: + """ + Query nameservers to find if it can answer to the simplest question. + """ + data = [] + + response = self.resolver.resolve(host, resource_records) + + for rdata in response: + data.append(rdata) + + return data + + def is_up(self) -> bool: + """ + dns.resolver.LifetimeTimeout if the DNS server is no accessible + """ + try: + self._resolve( + self.TEST_QNAME, + "CNAME" + ) + except dns.resolver.NoNameservers as exc: + print(exc) + return False + except dns.resolver.LifetimeTimeout as exc: + print(exc) + return False + + return True + + def configured_resolve_trust_framework_pointer(self, tfp: TrustFrameworkPointer) -> bool: + """Is Trust Framework Pointer configured""" + try: + self._resolve( + tfp, + "PTR" + ) + except dns.resolver.NoNameservers as exc: + print(exc) + return False + except dns.resolver.LifetimeTimeout as exc: + print(exc) + return False + except dns.rdatatype.UnknownRdatatype as exc: + print(exc) + return False + return True + + @property + def security_extensions(self) -> bool: + """ + # Once DNS SEC is configured in CI/CD we should resume BDD implementation + # @Fraunhofer are suggesting to check the flag in the response + + # Passing commands to `dig` tool and see the results + # + # more info how to here + # https://www.cyberciti.biz/faq/unix-linux-test-and-validate-dnssec-using-dig-command-line/ + # + """ + return True diff --git a/bdd/src/train_bdd/models/domain_name_system_zone_manager.py b/bdd/src/train_bdd/models/domain_name_system_zone_manager.py new file mode 100644 index 0000000000000000000000000000000000000000..254e33111fc3cc30be2a29a9280fa8d90d7752a1 --- /dev/null +++ b/bdd/src/train_bdd/models/domain_name_system_zone_manager.py @@ -0,0 +1,19 @@ +""" +Domain Name System Zone Manager Model +""" +from pydantic import BaseModel + + +class DomainNameSystemZoneManager(BaseModel): + """ + Known also as Zone Manager Handler. + + MUST be developed by @Fraunhofer + + See `https://gitlab.eclipse.org/eclipse/xfsc/train/dns-zone-manager`_ + """ + def is_up(self) -> bool: + """ + To be implemented + """ + return True diff --git a/bdd/src/train_bdd/models/trust_framework_configuration.py b/bdd/src/train_bdd/models/trust_framework_configuration.py new file mode 100644 index 0000000000000000000000000000000000000000..c7b5c4ad12610f9d0b7d3627a72fbba0cb328e14 --- /dev/null +++ b/bdd/src/train_bdd/models/trust_framework_configuration.py @@ -0,0 +1,20 @@ +""" +Trust Framework Configuration Model +""" +from pydantic import BaseModel + + +class TrustFrameworkConfiguration(BaseModel): + """ + The product MUST provide a Web UI which allows the administrator + to add new trust frameworks and corresponding DIDs + + Developed by @Fraunhofer + + No code source yet available. + """ + def create_did(self) -> str: + """ + Create DID through TRAIN interfaces + """ + return "some-did" diff --git a/bdd/src/train_bdd/models/trust_framework_pointer.py b/bdd/src/train_bdd/models/trust_framework_pointer.py new file mode 100644 index 0000000000000000000000000000000000000000..27eaadfbfb753f01db63628d4830c9e02a1800d7 --- /dev/null +++ b/bdd/src/train_bdd/models/trust_framework_pointer.py @@ -0,0 +1,33 @@ +""" +Trust Framework Pointer Model +""" +from typing import Any + + +class TrustFrameworkPointer(str): + """ + DNS pointer record (PTR for short) + + It deviates from the normal usage related to IP + (https://www.cloudflare.com/learning/dns/dns-records/dns-ptr-record/) + instead it provides the domain name associated with schema address. + + Example:: + + An example dig command to query the PTR record: + ``dig PTR did-web.test.train.trust-scheme.de`` + + """ + + def __new__(cls, value: str, *_: Any, **__: Any) -> "TrustFrameworkPointer": + """ + Extent the string type + """ + return super().__new__(cls, value) + + @classmethod + def from_text(cls, text: str) -> set["TrustFrameworkPointer"]: + """ + Parse multiline test to extract Trust Framework Pointers + """ + return set(cls(host) for host in text.splitlines()) diff --git a/bdd/src/train_bdd/models/trust_list.py b/bdd/src/train_bdd/models/trust_list.py new file mode 100644 index 0000000000000000000000000000000000000000..131cb69be76644f0e076c3961ea073710b31bcff --- /dev/null +++ b/bdd/src/train_bdd/models/trust_list.py @@ -0,0 +1,50 @@ +""" +Trust List Model +""" +from pydantic import BaseModel + +from train_bdd.models.trust_framework_pointer import TrustFrameworkPointer + +_CACHE = None + + +class TrustList(BaseModel): + """ + Mocked version of the Trust List + + See `https://gitlab.eclipse.org/eclipse/xfsc/train/tspa`_ + """ + + cache: bool = True + + def __len__(self) -> int: + """ + Check if Trust List is empty `len(trust_list) == 0` + + :status: mocked + """ + return 0 + + @classmethod + def fetch(cls, cache: bool = True) -> "TrustList": + """ + Reuse cached instance if fetched previously else fetch from remote + + :param cache: always fetch if set as false + """ + global _CACHE # pylint: disable=global-statement + + if cache: + if _CACHE: + return _CACHE + + _CACHE = cls() + + return _CACHE + + @property + def trust_framework_pointers(self) -> set[TrustFrameworkPointer]: + """ + Extract just trust framework pointers from TL + """ + return {TrustFrameworkPointer("sausweis.train1.trust-scheme.de")} diff --git a/bdd/src/train_bdd/models/trust_list_management.py b/bdd/src/train_bdd/models/trust_list_management.py new file mode 100644 index 0000000000000000000000000000000000000000..a53ff6ed7825b9d7e5cb5be5c910dae43ede5fa3 --- /dev/null +++ b/bdd/src/train_bdd/models/trust_list_management.py @@ -0,0 +1,12 @@ +""" +Trust List Management Model +""" +from pydantic import BaseModel + + +class TrustListManagement(BaseModel): + """ + Developed by @Fraunhofer + + See `https://gitlab.eclipse.org/eclipse/xfsc/train/tspasrc/main/java/eu/lightest/tspa/api/v1/factory/publication`_ + """ diff --git a/bdd/src/train_bdd/models/trusted_content_resolver.py b/bdd/src/train_bdd/models/trusted_content_resolver.py new file mode 100644 index 0000000000000000000000000000000000000000..8e47259467dc994bf331f449ac43b10f04e3624d --- /dev/null +++ b/bdd/src/train_bdd/models/trusted_content_resolver.py @@ -0,0 +1,44 @@ +""" +Trusted Content Resolver Model +""" +import pydantic +import requests +import requests.exceptions + +from .._env import HOST +from ._spring_boot_actuator import SpringBootActuator +from .trust_framework_pointer import TrustFrameworkPointer + + +class TrustedContentResolver(SpringBootActuator): + """ + Trusted Content Resolver / Extended Universal Resolver (TCR for short) + Libraries + + See `https://gitlab.eclipse.org/eclipse/xfsc/train/trusted-content-resolver/-/blob/main/openapi/tcr_openapi.yaml`_ + """ + + host: pydantic.HttpUrl = pydantic.HttpUrl(HOST or "http://localhost:8087") + + def resolve(self, + did: str, + trust_framework_pointers: set[TrustFrameworkPointer]) -> tuple[dict[str, str], ...]: + """ + Invoke `resolve` REST endpoint + """ + data = [] + for tfp in trust_framework_pointers: + response = requests.post( + url=f"{self.host}resolve", + json={ + 'issuer': did, + 'trustSchemePointer': tfp + }, + headers={ + 'Content-Type': "application/json", + }, + timeout=1, + ) + assert response.status_code == 200 + data.append(response.json()) + + return tuple(data) diff --git a/bdd/src/train_bdd/models/universal_decentralized_identifiers_resolver.py b/bdd/src/train_bdd/models/universal_decentralized_identifiers_resolver.py new file mode 100644 index 0000000000000000000000000000000000000000..c5524e14bce0289391d2b57012dbae183b21d401 --- /dev/null +++ b/bdd/src/train_bdd/models/universal_decentralized_identifiers_resolver.py @@ -0,0 +1,17 @@ +""" +Universal Decentralized Identifiers Resolver Model +""" +import pydantic + +from .._env import DID_RESOLVER_HOST +from ._spring_boot_actuator import SpringBootActuator + + +class UniversalDecentralizedIdentifiersResolver(SpringBootActuator): + """ + Universal Decentralized Identifiers Resolver is a REST api. + + See `https://github.com/decentralized-identity/universal-resolver`_ + """ + + host: pydantic.HttpUrl = pydantic.HttpUrl(DID_RESOLVER_HOST or "http://localhost:8080/") diff --git a/bdd/steps/resolver.py b/bdd/steps/resolver.py new file mode 100644 index 0000000000000000000000000000000000000000..acbb2ed391a3f30092332b2c9d6d3d15b2a80ff2 --- /dev/null +++ b/bdd/steps/resolver.py @@ -0,0 +1,114 @@ +from typing import Any, Union +import json + +import bash +from behave import given, when, then +from behave.model import Text +from behave.runner import Context + +from train_bdd.models.trust_list import TrustList +from train_bdd.models.trusted_content_resolver import TrustedContentResolver +from train_bdd.models.universal_decentralized_identifiers_resolver import UniversalDecentralizedIdentifiersResolver +from train_bdd.models.domain_name_system_resolver import DomainNameSystemResolver +from train_bdd.models.trust_framework_pointer import TrustFrameworkPointer +from train_bdd.models.client.py import Py +from train_bdd.models.client.java import Java + +TCR = TrustedContentResolver() +DID_RESOLVER = UniversalDecentralizedIdentifiersResolver() + + +class ContextType(Context): + dns_resolver: DomainNameSystemResolver + trust_framework_pointers: set[TrustFrameworkPointer] + resolve_response: dict[str, Any] + text: Text + trust_list: TrustList + client: Union[Py, Java] + + +@given("TCR is running") +def step_impl(context: ContextType): + assert TCR.is_up(), "is not up" + + +@given("DID Resolver is running") +def step_impl(context: ContextType): + assert DID_RESOLVER.is_up(), "is not up" + + +@given("{domain_mame_system_type} with {ip} is running") +def step_impl(context: ContextType, domain_mame_system_type: str, ip: str): + context.dns_resolver = DomainNameSystemResolver.init( + implementation=domain_mame_system_type, + ip=ip + ) + assert context.dns_resolver.is_up(), "is not up" + + +@given("trust framework pointers are `{configured_or_misconfigured}` in DNS") +def step_impl(context: ContextType, configured_or_misconfigured: str): + assert context.trust_framework_pointers + for tfp in context.trust_framework_pointers: + if configured_or_misconfigured == "configured": + assert context.dns_resolver.configured_resolve_trust_framework_pointer(tfp), \ + "trust framework pointers not configured" + elif configured_or_misconfigured == "misconfigured": + assert not context.dns_resolver.configured_resolve_trust_framework_pointer(tfp), \ + "trust framework pointers configured" + else: + raise NotImplementedError(configured_or_misconfigured) + + +@given("client {client_name} installed") +def step_impl(context: ContextType, client_name: str): + if client_name == 'Trust-content-resolver-client-validator-java': + context.client = Java() + elif client_name == 'Trust-content-resolver-client-validator-py': + context.client = Py() + elif client_name == 'Trust-content-resolver-client-validator-go': + print('mock all ok') + return + elif client_name == 'Trust-content-resolver-client-validator-js': + print('mock all ok') + return + else: + raise NotImplementedError(client_name) + + assert context.client.is_up() + + +@given("multiple Trust Framework Pointers") +def step_impl(context: ContextType): + context.trust_framework_pointers = TrustFrameworkPointer.from_text(context.text) + assert context.trust_framework_pointers, "no Trust Framework Pointers available" + + +@given("Validate DNS Name against DNSSEC") +def step_impl(context: ContextType): + return context.dns_resolver.security_extensions + + +@when("above Trust Framework Pointers are supplied in resolver trust request by `{did}`") +def step_impl(context: ContextType, did): + if hasattr(context, 'client'): + context.resolve_response = context.client.resolve( + trust_framework_pointers=context.trust_framework_pointers, did=did + ) + else: + context.resolve_response = TCR.resolve( + trust_framework_pointers=context.trust_framework_pointers, did=did + ) + print("Got resolve response as:", json.dumps(context.resolve_response)) + + +@then("Trust List's corresponding Trust Framework pointers from context") +def step_impl(context: ContextType): + tfp_from_tl = TrustList.fetch().trust_framework_pointers + assert tfp_from_tl.intersection(context.trust_framework_pointers), \ + f"{tfp_from_tl=} does no intersect {context.trust_framework_pointers=}" + + +@then("Trust List's will be emtpy") +def step_impl(context: ContextType): + assert len(TrustList.fetch()) == 0 diff --git a/bdd/tests/trust_content_resolver_test.py b/bdd/tests/trust_content_resolver_test.py new file mode 100644 index 0000000000000000000000000000000000000000..7b9f257c47d07610c941173cb55ce0092cfd7529 --- /dev/null +++ b/bdd/tests/trust_content_resolver_test.py @@ -0,0 +1,17 @@ +""" +Testing TrustedContentResolver +""" +import pytest + +from train_bdd.models.trusted_content_resolver import TrustedContentResolver + + +def test_resolve_validate_host(): + """ + Given invalid host name for Trusted Content Resolver + When create the model + Then Value Error have to be raised before calling the Trusted Content Resolver + """ + with pytest.raises(ValueError, + match="Input should be a valid URL, relative URL without a base"): + TrustedContentResolver(host="wrong_host") diff --git a/clients/go/ansible.playbook.yml b/clients/go/ansible.playbook.yml index 8c331a4ff6e6db3269c415ab00265133f3d36976..cb1b5d79c63ccce95e766212443a777e9887d9cf 100644 --- a/clients/go/ansible.playbook.yml +++ b/clients/go/ansible.playbook.yml @@ -1,58 +1,72 @@ - name: Install go MacOSX homebrew: - name: '{{ item }}' + name: "{{ item }}" state: present with_items: - golang - when: ansible_distribution == 'MacOSX' + when: ansible_distribution == "MacOSX" tags: - - MacOSX - golang -- name: Install go Ubuntu +- name: Install gvm Debian requirements apt: - name: '{{ item }}' + name: + - golang-go + - curl + - git + - mercurial + - make + - binutils + - bison + - gcc + - build-essential + - bsdmainutils state: present - with_items: - - golang-go - when: ansible_distribution == 'Ubuntu' + when: ansible_distribution == "Ubuntu" + become: true + become_method: sudo tags: - - MacOSX - golang # gvm require to have at least one version installed already # this is why above pre-installation is required -- name: Install Gvm - ansible.builtin.git: - repo: "https://github.com/moovweb/gvm" - dest: "{{ ansible_env.HOME }}/.gvm" +- name: "register status of `{{ ansible_env.HOME }}/.gvm`" + stat: + path: "{{ ansible_env.HOME }}/.gvm" + register: _gvm_dir + tags: + - golang + +- name: Install gvm + ansible.builtin.shell: > + curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer | bash + when: _gvm_dir is defined and not _gvm_dir.stat.exists tags: - golang -- name: Add Gvm to shell config file +- name: "Add Gvm to shell config file `~/.bashrc`" lineinfile: - dest: "{{ _found_shell_config_file }}" + dest: ~/.bashrc state: present line: "{{ item }}" with_items: - - 'export PATH="${HOME}/.gvm/bin:$PATH"' - - 'export GVM_ROOT="${HOME}/.gvm"' - - 'source ${GVM_ROOT}/scripts/gvm-default' + - "# TRAIN ansible: gvm config" + - "source ~/.gvm/scripts/gvm" tags: - golang - name: Install concrete version for Go - environment: - PATH: "{{ ansible_env.HOME }}/.gvm/bin:{{ ansible_env.PATH }}" - GVM_ROOT: "{{ ansible_env.HOME }}/.gvm" - ansible.builtin.shell: | - source $GVM_ROOT/scripts/gvm-default - gvm install go1.21 - gvm use go1.21 --default - cd ./clients/go - go get github.com/cucumber/godog + ansible.builtin.shell: "PS1=emulate-interactive && source ~/.bashrc && {{ item }}" + with_items: + - gvm version + - gvm install go1.21 + + # ansible only bug fix for `gvm use` https://github.com/moovweb/gvm/issues/188 + - ln -sf ~/.gvm/scripts/env/use ~/.gvm/scripts && chmod +x ~/.gvm/scripts/env/use + + - gvm use go1.21 --default + - cd ./clients/go/trusted_content_resolver && go mod tidy args: executable: /bin/bash tags: - golang - diff --git a/clients/java/Makefile b/clients/java/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..bdecff6158a718e4a403bfc44f285d914e9dfa5f --- /dev/null +++ b/clients/java/Makefile @@ -0,0 +1,21 @@ +ansible: + ansible-playbook ansible.playbook.yml --connection=local -i localhost, + +versions: + mvn --version + +.PHONY: test +test: versions + mvn test -Dmaven.test.failure.ignore + +clean: + mvn clean + +install: + mvn install + + +.PHONY: third-party-txt-file +third-party-txt-file: install + rm -f THIRD-PARTY.txt + mvn license:add-third-party \ No newline at end of file diff --git a/clients/java/README.md b/clients/java/README.md new file mode 100644 index 0000000000000000000000000000000000000000..c73e785b201f47f054ce80d260fa357fed2b0d71 --- /dev/null +++ b/clients/java/README.md @@ -0,0 +1,71 @@ +# Build tools + +- Required **JDK**: for version look into pom.xml from the root +- Required **Maven**: for version look into pom.xml from the root +- Optional **make**: tool which controls the generation of executables and other non-source files of a program from the + program's source files. + On *Linux/MacOS* is available by default, for *Windows* see https://stackoverflow.com/questions/2532234/how-to-run-a-makefile-in-windows + +# Steps with commands + +Note: You can just execute ``make`` subcommands without make (!?) in case you don't want to use ``make`` + +- `git clone` repo, `cd` into directory + +- clean target if needed + +``` bash +$ make clean +mvn clean +... +``` + +- test + +``` bash +$ make test +mvn --version +... +mvn test -Dmaven.test.failure.ignore +... +``` + +- Extract used licenses into [THIRD-PARTY.txt](THIRD-PARTY.txt) + +```bash +$ make third-party-txt-file +mvn install +... +rm -f THIRD-PARTY.txt +mvn license:add-third-party +... +``` + +# How to run java client + +- build client: + +``` bash +> mvn clean install +``` + +- run client: + +``` bash +> cd target +> java -jar trusted-content-resolver-java-client-1.0.0-SNAPSHOT-full.jar <client arguments> +``` + +client arguments can be passed as set of key/value pairs: `k1=p1 k2=p2`. Supported parameters are: + +- `uri/u` - uri to Trusted Content Resolver (TCR) service +- `endpoint/e` - TCR method to be invoked (resolve/validate) +- `data/d` - parameters for invoked TCR method in JSON format + - ResolveRequest: `{"issuer": "issuer1", "trustSchemePointers": ["ptr1", "ptr2"], "endpointTypes": ["eptype1", "eptype2"], "trustListServiceTypes": ["tlstype1", "tlstype2"]}` + - ValidateRequest: `{"issuer": "issuer1", "did": "did1", "endpoints": ["endpoint1", "endpoint2"]}` +- `file/f` - file, containing parameters specified above, also in JSON format +- parameters specified via command-line arguments overwrite parameters from file + +client prints invocation results to console + + diff --git a/clients/java/THIRD-PARTY.txt b/clients/java/THIRD-PARTY.txt new file mode 100644 index 0000000000000000000000000000000000000000..75355ff7fc610baccdbbf3a1cdf34e8639f4a853 --- /dev/null +++ b/clients/java/THIRD-PARTY.txt @@ -0,0 +1,82 @@ + +Lists of 80 third-party dependencies. + (Eclipse Public License - v 1.0) (GNU Lesser General Public License) Logback Classic Module (ch.qos.logback:logback-classic:1.4.11 - http://logback.qos.ch/logback-classic) + (Eclipse Public License - v 1.0) (GNU Lesser General Public License) Logback Core Module (ch.qos.logback:logback-core:1.4.11 - http://logback.qos.ch/logback-core) + (Apache License, Version 2.0) ClassMate (com.fasterxml:classmate:1.5.1 - https://github.com/FasterXML/java-classmate) + (The Apache Software License, Version 2.0) Jackson-annotations (com.fasterxml.jackson.core:jackson-annotations:2.15.3 - https://github.com/FasterXML/jackson) + (The Apache Software License, Version 2.0) Jackson-core (com.fasterxml.jackson.core:jackson-core:2.15.3 - https://github.com/FasterXML/jackson-core) + (The Apache Software License, Version 2.0) jackson-databind (com.fasterxml.jackson.core:jackson-databind:2.15.3 - https://github.com/FasterXML/jackson) + (The Apache Software License, Version 2.0) Jackson-dataformat-YAML (com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.15.3 - https://github.com/FasterXML/jackson-dataformats-text) + (The Apache Software License, Version 2.0) Jackson datatype: jdk8 (com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.15.3 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8) + (The Apache Software License, Version 2.0) Jackson datatype: JSR310 (com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.15.3 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jsr310) + (The Apache Software License, Version 2.0) Jackson-module-parameter-names (com.fasterxml.jackson.module:jackson-module-parameter-names:2.15.3 - https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names) + (Apache License, Version 2.0) trusted-content-resolver-api (eu.xfsc.train:trusted-content-resolver-api:1.0.0-SNAPSHOT - https://gitlab.eclipse.org/eclipse/xfsc/train/trusted-content-resolver/trusted-content-resolver-api) + (The Apache Software License, Version 2.0) micrometer-commons (io.micrometer:micrometer-commons:1.11.3 - https://github.com/micrometer-metrics/micrometer) + (The Apache Software License, Version 2.0) micrometer-observation (io.micrometer:micrometer-observation:1.11.3 - https://github.com/micrometer-metrics/micrometer) + (Apache License, Version 2.0) Netty/Buffer (io.netty:netty-buffer:4.1.100.Final - https://netty.io/netty-buffer/) + (Apache License, Version 2.0) Netty/Codec (io.netty:netty-codec:4.1.100.Final - https://netty.io/netty-codec/) + (Apache License, Version 2.0) Netty/Codec/DNS (io.netty:netty-codec-dns:4.1.100.Final - https://netty.io/netty-codec-dns/) + (Apache License, Version 2.0) Netty/Codec/HTTP (io.netty:netty-codec-http:4.1.100.Final - https://netty.io/netty-codec-http/) + (Apache License, Version 2.0) Netty/Codec/HTTP2 (io.netty:netty-codec-http2:4.1.100.Final - https://netty.io/netty-codec-http2/) + (Apache License, Version 2.0) Netty/Codec/Socks (io.netty:netty-codec-socks:4.1.100.Final - https://netty.io/netty-codec-socks/) + (Apache License, Version 2.0) Netty/Common (io.netty:netty-common:4.1.100.Final - https://netty.io/netty-common/) + (Apache License, Version 2.0) Netty/Handler (io.netty:netty-handler:4.1.100.Final - https://netty.io/netty-handler/) + (Apache License, Version 2.0) Netty/Handler/Proxy (io.netty:netty-handler-proxy:4.1.100.Final - https://netty.io/netty-handler-proxy/) + (Apache License, Version 2.0) Netty/Resolver (io.netty:netty-resolver:4.1.100.Final - https://netty.io/netty-resolver/) + (Apache License, Version 2.0) Netty/Resolver/DNS (io.netty:netty-resolver-dns:4.1.100.Final - https://netty.io/netty-resolver-dns/) + (Apache License, Version 2.0) Netty/Resolver/DNS/Classes/MacOS (io.netty:netty-resolver-dns-classes-macos:4.1.100.Final - https://netty.io/netty-resolver-dns-classes-macos/) + (Apache License, Version 2.0) Netty/Resolver/DNS/Native/MacOS (io.netty:netty-resolver-dns-native-macos:4.1.100.Final - https://netty.io/netty-resolver-dns-native-macos/) + (Apache License, Version 2.0) Netty/Transport (io.netty:netty-transport:4.1.100.Final - https://netty.io/netty-transport/) + (Apache License, Version 2.0) Netty/Transport/Classes/Epoll (io.netty:netty-transport-classes-epoll:4.1.100.Final - https://netty.io/netty-transport-classes-epoll/) + (Apache License, Version 2.0) Netty/Transport/Native/Epoll (io.netty:netty-transport-native-epoll:4.1.100.Final - https://netty.io/netty-transport-native-epoll/) + (Apache License, Version 2.0) Netty/Transport/Native/Unix/Common (io.netty:netty-transport-native-unix-common:4.1.100.Final - https://netty.io/netty-transport-native-unix-common/) + (Apache License, Version 2.0) Non-Blocking Reactive Foundation for the JVM (io.projectreactor:reactor-core:3.5.11 - https://github.com/reactor/reactor-core) + (The Apache Software License, Version 2.0) Core functionality for the Reactor Netty library (io.projectreactor.netty:reactor-netty-core:1.1.12 - https://github.com/reactor/reactor-netty) + (The Apache Software License, Version 2.0) HTTP functionality for the Reactor Netty library (io.projectreactor.netty:reactor-netty-http:1.1.12 - https://github.com/reactor/reactor-netty) + (Apache License 2.0) swagger-annotations-jakarta (io.swagger.core.v3:swagger-annotations-jakarta:2.2.15 - https://github.com/swagger-api/swagger-core/modules/swagger-annotations-jakarta) + (Apache License 2.0) swagger-core-jakarta (io.swagger.core.v3:swagger-core-jakarta:2.2.15 - https://github.com/swagger-api/swagger-core/modules/swagger-core-jakarta) + (Apache License 2.0) swagger-models-jakarta (io.swagger.core.v3:swagger-models-jakarta:2.2.15 - https://github.com/swagger-api/swagger-core/modules/swagger-models-jakarta) + (EDL 1.0) Jakarta Activation API (jakarta.activation:jakarta.activation-api:2.1.2 - https://github.com/jakartaee/jaf-api) + (EPL 2.0) (GPL2 w/ CPE) Jakarta Annotations API (jakarta.annotation:jakarta.annotation-api:2.1.1 - https://projects.eclipse.org/projects/ee4j.ca) + (Apache License 2.0) Jakarta Bean Validation API (jakarta.validation:jakarta.validation-api:3.0.2 - https://beanvalidation.org) + (Eclipse Distribution License - v 1.0) Jakarta XML Binding API (jakarta.xml.bind:jakarta.xml.bind-api:4.0.1 - https://github.com/eclipse-ee4j/jaxb-api/jakarta.xml.bind-api) + (Apache License, Version 2.0) Apache Commons Lang (org.apache.commons:commons-lang3:3.12.0 - https://commons.apache.org/proper/commons-lang/) + (Apache License, Version 2.0) Apache Log4j API (org.apache.logging.log4j:log4j-api:2.20.0 - https://logging.apache.org/log4j/2.x/log4j-api/) + (Apache License, Version 2.0) Apache Log4j to SLF4J Adapter (org.apache.logging.log4j:log4j-to-slf4j:2.20.0 - https://logging.apache.org/log4j/2.x/log4j-to-slf4j/) + (Apache License, Version 2.0) tomcat-embed-el (org.apache.tomcat.embed:tomcat-embed-el:10.1.15 - https://tomcat.apache.org/) + (The Apache License, Version 2.0) org.apiguardian:apiguardian-api (org.apiguardian:apiguardian-api:1.1.2 - https://github.com/apiguardian-team/apiguardian) + (Apache License 2.0) Hibernate Validator Engine (org.hibernate.validator:hibernate-validator:8.0.1.Final - http://hibernate.org/validator/hibernate-validator) + (Apache License 2.0) JBoss Logging 3 (org.jboss.logging:jboss-logging:3.5.3.Final - http://www.jboss.org) + (Eclipse Public License v2.0) JUnit Jupiter (Aggregator) (org.junit.jupiter:junit-jupiter:5.9.3 - https://junit.org/junit5/) + (Eclipse Public License v2.0) JUnit Jupiter API (org.junit.jupiter:junit-jupiter-api:5.9.3 - https://junit.org/junit5/) + (Eclipse Public License v2.0) JUnit Jupiter Engine (org.junit.jupiter:junit-jupiter-engine:5.9.3 - https://junit.org/junit5/) + (Eclipse Public License v2.0) JUnit Jupiter Params (org.junit.jupiter:junit-jupiter-params:5.9.3 - https://junit.org/junit5/) + (Eclipse Public License v2.0) JUnit Platform Commons (org.junit.platform:junit-platform-commons:1.9.3 - https://junit.org/junit5/) + (Eclipse Public License v2.0) JUnit Platform Engine API (org.junit.platform:junit-platform-engine:1.9.3 - https://junit.org/junit5/) + (The Apache License, Version 2.0) org.opentest4j:opentest4j (org.opentest4j:opentest4j:1.2.0 - https://github.com/ota4j-team/opentest4j) + (The MIT License) Project Lombok (org.projectlombok:lombok:1.18.30 - https://projectlombok.org) + (MIT-0) reactive-streams (org.reactivestreams:reactive-streams:1.0.4 - http://www.reactive-streams.org/) + (MIT License) JUL to SLF4J bridge (org.slf4j:jul-to-slf4j:2.0.9 - http://www.slf4j.org) + (MIT License) SLF4J API Module (org.slf4j:slf4j-api:2.0.9 - http://www.slf4j.org) + (The Apache License, Version 2.0) springdoc-openapi-starter-common (org.springdoc:springdoc-openapi-starter-common:2.2.0 - https://springdoc.org/springdoc-openapi-starter-common/) + (The Apache License, Version 2.0) springdoc-openapi-starter-webmvc-api (org.springdoc:springdoc-openapi-starter-webmvc-api:2.2.0 - https://springdoc.org/springdoc-openapi-starter-webmvc-api/) + (The Apache License, Version 2.0) springdoc-openapi-starter-webmvc-ui (org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0 - https://springdoc.org/springdoc-openapi-starter-webmvc-ui/) + (Apache License, Version 2.0) Spring AOP (org.springframework:spring-aop:6.0.13 - https://github.com/spring-projects/spring-framework) + (Apache License, Version 2.0) Spring Beans (org.springframework:spring-beans:6.0.13 - https://github.com/spring-projects/spring-framework) + (Apache License, Version 2.0) Spring Context (org.springframework:spring-context:6.0.13 - https://github.com/spring-projects/spring-framework) + (Apache License, Version 2.0) Spring Core (org.springframework:spring-core:6.0.13 - https://github.com/spring-projects/spring-framework) + (Apache License, Version 2.0) Spring Expression Language (SpEL) (org.springframework:spring-expression:6.0.13 - https://github.com/spring-projects/spring-framework) + (Apache License, Version 2.0) Spring Commons Logging Bridge (org.springframework:spring-jcl:6.0.13 - https://github.com/spring-projects/spring-framework) + (Apache License, Version 2.0) Spring Web (org.springframework:spring-web:6.0.13 - https://github.com/spring-projects/spring-framework) + (Apache License, Version 2.0) Spring WebFlux (org.springframework:spring-webflux:6.0.13 - https://github.com/spring-projects/spring-framework) + (Apache License, Version 2.0) Spring Web MVC (org.springframework:spring-webmvc:6.0.13 - https://github.com/spring-projects/spring-framework) + (Apache License, Version 2.0) spring-boot (org.springframework.boot:spring-boot:3.1.5 - https://spring.io/projects/spring-boot) + (Apache License, Version 2.0) spring-boot-autoconfigure (org.springframework.boot:spring-boot-autoconfigure:3.1.5 - https://spring.io/projects/spring-boot) + (Apache License, Version 2.0) spring-boot-starter (org.springframework.boot:spring-boot-starter:3.1.5 - https://spring.io/projects/spring-boot) + (Apache License, Version 2.0) spring-boot-starter-json (org.springframework.boot:spring-boot-starter-json:3.1.5 - https://spring.io/projects/spring-boot) + (Apache License, Version 2.0) spring-boot-starter-logging (org.springframework.boot:spring-boot-starter-logging:3.1.5 - https://spring.io/projects/spring-boot) + (Apache License, Version 2.0) spring-boot-starter-reactor-netty (org.springframework.boot:spring-boot-starter-reactor-netty:3.1.5 - https://spring.io/projects/spring-boot) + (Apache License, Version 2.0) spring-boot-starter-validation (org.springframework.boot:spring-boot-starter-validation:3.1.5 - https://spring.io/projects/spring-boot) + (Apache License, Version 2.0) spring-boot-starter-webflux (org.springframework.boot:spring-boot-starter-webflux:3.1.5 - https://spring.io/projects/spring-boot) + (Apache 2.0) Swagger UI (org.webjars:swagger-ui:5.2.0 - http://webjars.org) + (Apache License, Version 2.0) SnakeYAML (org.yaml:snakeyaml:2.1 - https://bitbucket.org/snakeyaml/snakeyaml) diff --git a/clients/java/ansible.playbook.yml b/clients/java/ansible.playbook.yml index 152bdca253ffaf43b8567705145a03b329e2c01a..ee34ab60ad27b3fbfbbc4f8ad1d45ceb8675a5f3 100644 --- a/clients/java/ansible.playbook.yml +++ b/clients/java/ansible.playbook.yml @@ -1,17 +1,27 @@ - name: Install sdkman ansible.builtin.shell: > curl -o- https://get.sdkman.io | bash + tags: + - java -- name: add sdkman to shell config file +- name: "Add sdkman to shell config file `~/.bashrc`" lineinfile: - dest: "{{ _found_shell_config_file }}" + dest: "~/.bashrc" state: present line: '{{ item }}' with_items: + - "# TRAIN ansible: sdkman config" - "source ~/.sdkman/bin/sdkman-init.sh" + tags: + - java - name: Install concrete version for java - ansible.builtin.shell: | - source ~/.sdkman/bin/sdkman-init.sh - sdk install java 17.0.0-tem - sdk install maven + ansible.builtin.shell: "source ~/.sdkman/bin/sdkman-init.sh && {{ item }}" + with_items: + - sdk version + - sdk install java 21.0.1-librca + - sdk install maven 3.9.5 + - sdk use java 21.0.1-librca + - sdk use maven 3.9.5 + args: + executable: /bin/bash diff --git a/clients/java/pom.xml b/clients/java/pom.xml index d2055a49f520fde20dd2172c12ac7787db546173..13fbd76c7567fff712bb11916e1cf25644035571 100644 --- a/clients/java/pom.xml +++ b/clients/java/pom.xml @@ -26,11 +26,58 @@ <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> + <dependency> + <groupId>io.netty</groupId> + <artifactId>netty-resolver-dns-native-macos</artifactId> + <classifier>osx-aarch_64</classifier> + <scope>runtime</scope> + </dependency> + <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>com.squareup.okhttp3</groupId> + <artifactId>okhttp</artifactId> + </dependency> + <dependency> + <groupId>com.squareup.okhttp3</groupId> + <artifactId>mockwebserver</artifactId> + <scope>test</scope> + </dependency> </dependencies> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-assembly-plugin</artifactId> + <configuration> + <archive> + <manifest> + <addClasspath>true</addClasspath> + <mainClass>eu.xfsc.train.tcr.util.ResolveServiceLauncher</mainClass> + </manifest> + </archive> + <descriptorRefs> + <descriptorRef>jar-with-dependencies</descriptorRef> + </descriptorRefs> + <finalName>${project.artifactId}-${project.version}-full</finalName> + <appendAssemblyId>false</appendAssemblyId> + </configuration> + <executions> + <execution> + <id>assemble-all</id> + <phase>package</phase> + <goals> + <goal>single</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + </project> diff --git a/clients/java/src/main/java/eu/xfsc/train/tcr/client/ResolveServiceClient.java b/clients/java/src/main/java/eu/xfsc/train/tcr/client/ResolveServiceClient.java new file mode 100644 index 0000000000000000000000000000000000000000..7d1848a3eb13d27e81d44710b89b531623ca10cc --- /dev/null +++ b/clients/java/src/main/java/eu/xfsc/train/tcr/client/ResolveServiceClient.java @@ -0,0 +1,84 @@ +package eu.xfsc.train.tcr.client; + +import java.util.List; +import java.util.Map; + +import org.springframework.web.reactive.function.client.WebClient; + +import eu.xfsc.train.tcr.api.generated.model.ResolveRequest; +import eu.xfsc.train.tcr.api.generated.model.ResolveResult; +import eu.xfsc.train.tcr.api.generated.model.ValidateRequest; +import eu.xfsc.train.tcr.api.generated.model.ValidateResponse; + +/** + * Client wrapper hiding communication via REST with TCR server + */ +public class ResolveServiceClient extends ServiceClient { + + /** + * The Client constructor + * + * @param baseUrl: TCR base url + */ + public ResolveServiceClient(String baseUrl) { + super(baseUrl, (String) null); + } + + /** + * The Client constructor + * + * @param baseUrl: TCR base url + * @param client: pre-configured WebClient instance + */ + public ResolveServiceClient(String baseUrl, WebClient client) { + super(baseUrl, client); + } + + /** + * Resolves TrustLists for specified issuer and pointer + * + * @param issuer: TrustList issuer DID/URI + * @param pointer: Trust Framework Pointer + * @param serviceTypes: service types to confider. null allows any service type + * @return list of ResolveResut structures + */ + public List<ResolveResult> resolveTrustList(String issuer, String pointer, List<String> serviceTypes) { //, List<String> listTypes) { + ResolveRequest rrq = new ResolveRequest(); + rrq = rrq.issuer(issuer).addTrustSchemePointersItem(pointer).trustListServiceTypes(serviceTypes); + return resolveTrustList(rrq); + } + + /** + * Resolves TrustList for specified /resolve params + * + * @param rrq: ResolveRequest structure containing bunch of /resolve parameters + * @return list of ResolveResult structures + */ + public List<ResolveResult> resolveTrustList(ResolveRequest rrq) { + return doPost(baseUrl + "/resolve", rrq, Map.of(), List.class); + } + + /** + * Validates resolution for specified issuer and DID + * + * @param issuer: TrustList issuer DID/URI + * @param did: DID resolved for provided Pointer + * @param tlEndpoints: resolved TrustList endpoints + * @return issuer/DID/VC verification status + */ + public ValidateResponse validateTrustList(String issuer, String did, List<String> tlEndpoints) { + ValidateRequest vrq = new ValidateRequest(issuer, did, tlEndpoints); + return validateTrustList(vrq); + } + + /** + * Validates resolution for specified issuer and DID + * + * @param vrq: ValidateRequest structure containing banch of /validate parameters + * @return issuer/DID/VC verification status + */ + public ValidateResponse validateTrustList(ValidateRequest vrq) { + return doPost(baseUrl + "/validate", vrq, Map.of(), ValidateResponse.class); + } + +} diff --git a/clients/java/src/main/java/eu/xfsc/train/tcr/client/ServiceClient.java b/clients/java/src/main/java/eu/xfsc/train/tcr/client/ServiceClient.java index 00d699bf34882847673dae961ce602ee7b21400b..dc8eaf70411e58cde80a6f9ca3599b9e955edd29 100644 --- a/clients/java/src/main/java/eu/xfsc/train/tcr/client/ServiceClient.java +++ b/clients/java/src/main/java/eu/xfsc/train/tcr/client/ServiceClient.java @@ -2,6 +2,7 @@ package eu.xfsc.train.tcr.client; import java.util.Map; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.codec.json.Jackson2JsonDecoder; @@ -9,6 +10,7 @@ import org.springframework.http.codec.json.Jackson2JsonEncoder; import org.springframework.web.reactive.function.client.WebClient; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; @@ -25,17 +27,17 @@ public abstract class ServiceClient { mapper = new ObjectMapper() .findAndRegisterModules() // .registerModule(new ParanamerModule()) .registerModule(new JavaTimeModule()) .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); - this.client = WebClient.builder() - //.apply(oauth2Client.oauth2Configuration()) - //.filter(new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager)) + WebClient.Builder builder = WebClient.builder() .baseUrl(baseUrl) .codecs(configurer -> { configurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(mapper, MediaType.APPLICATION_JSON)); configurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(mapper, MediaType.APPLICATION_JSON)); }) - .defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + jwt) - .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .build(); + .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); + if (jwt != null) { + builder = builder.defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + jwt); + } + this.client = builder.build(); // this.template.setErrorHandler(new ErrorHandler(mapper)); } diff --git a/clients/java/src/main/java/eu/xfsc/train/tcr/client/VerificationClient.java b/clients/java/src/main/java/eu/xfsc/train/tcr/client/VerificationClient.java deleted file mode 100644 index c5ef78db6a382fb023faacca6039a1d7617fe954..0000000000000000000000000000000000000000 --- a/clients/java/src/main/java/eu/xfsc/train/tcr/client/VerificationClient.java +++ /dev/null @@ -1,26 +0,0 @@ -package eu.xfsc.train.tcr.client; - -import java.util.Map; - -import org.springframework.web.reactive.function.client.WebClient; - -import eu.xfsc.train.tcr.api.generated.model.VerificationRequest; -import eu.xfsc.train.tcr.api.generated.model.VerificationResult; - -public class VerificationClient extends ServiceClient { - - public VerificationClient(String baseUrl, String jwt) { - super(baseUrl, jwt); - } - - public VerificationClient(String baseUrl, WebClient client) { - super(baseUrl, client); - } - - public VerificationResult resolveIssuer(String issuer, String pointer) { - VerificationRequest vrq = new VerificationRequest(); - vrq = vrq.issuer(issuer).trustSchemePointer(pointer); - return doPost(baseUrl + "/resolve", vrq, Map.of(), VerificationResult.class); - } - -} diff --git a/clients/java/src/main/java/eu/xfsc/train/tcr/util/ResolveServiceLauncher.java b/clients/java/src/main/java/eu/xfsc/train/tcr/util/ResolveServiceLauncher.java new file mode 100644 index 0000000000000000000000000000000000000000..a7d422619a8356d3245fc097b9030bf5bef36de8 --- /dev/null +++ b/clients/java/src/main/java/eu/xfsc/train/tcr/util/ResolveServiceLauncher.java @@ -0,0 +1,97 @@ +package eu.xfsc.train.tcr.util; + +import java.io.File; +import java.util.Arrays; +import java.util.List; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import eu.xfsc.train.tcr.api.generated.model.ResolveRequest; +import eu.xfsc.train.tcr.api.generated.model.ResolveResult; +import eu.xfsc.train.tcr.api.generated.model.ValidateRequest; +import eu.xfsc.train.tcr.api.generated.model.ValidateResponse; +import eu.xfsc.train.tcr.client.ResolveServiceClient; + +public class ResolveServiceLauncher { + + static final String DEF_URI = "http://localhost:8087"; + static ObjectMapper mapper = new ObjectMapper(); + + + public static void main(String[] args) throws Exception { + + String baseUri = null; + String endpoint = null; + String data = null; + ResolveServiceParams params = null; + //System.out.println("args: " + Arrays.toString(args)); + for (String arg: args) { + String[] parts = arg.split("="); + if ("u".equals(parts[0]) || "uri".equals(parts[0])) { + baseUri = parts[1]; + continue; + } + if ("e".equals(parts[0]) || "endpoint".equals(parts[0])) { + endpoint = parts[1]; + continue; + } + if ("d".equals(parts[0]) || "data".equals(parts[0])) { + data = parts[1]; + continue; + } + if ("f".equals(parts[0]) || "file".equals(parts[0])) { + params = mapper.readValue(new File(parts[1]), ResolveServiceParams.class); + continue; + } + System.out.println("unknown parameter: " + arg); + } + + if (params != null) { + if (baseUri == null) { + baseUri = params.getUri(); + } + if (endpoint == null) { + endpoint = params.getEndpoint(); + } + if (data == null) { + data = params.getData(); + } + } + if (baseUri == null) { + baseUri = DEF_URI; + } + + if (endpoint == null) { + System.out.println("error: no resolution endpoint specified"); + return; + } + + Boolean resolve; + if ("resolve".equals(endpoint)) { + resolve = true; + } else if ("validate".equals(endpoint)) { + resolve = false; + } else { + System.out.println("error: unknown resolution endpoint: " + endpoint); + return; + } + + if (data == null) { + System.out.println("error: no " + (resolve ? "resolution" : "validation") + " data provided"); + return; + } + + ResolveServiceClient client = new ResolveServiceClient(baseUri); + if (resolve) { + ResolveRequest rrq = mapper.readValue(data, ResolveRequest.class); + List<ResolveResult> result = client.resolveTrustList(rrq); + System.out.println("result: " + mapper.writeValueAsString(result)); + } else { + ValidateRequest vrq = mapper.readValue(data, ValidateRequest.class); + ValidateResponse result = client.validateTrustList(vrq); + System.out.println("result: " + mapper.writeValueAsString(result)); + } + // what else? + } + +} diff --git a/clients/java/src/main/java/eu/xfsc/train/tcr/util/ResolveServiceParams.java b/clients/java/src/main/java/eu/xfsc/train/tcr/util/ResolveServiceParams.java new file mode 100644 index 0000000000000000000000000000000000000000..cbe48da9e3189fb934e696be56daf264d42b0f6c --- /dev/null +++ b/clients/java/src/main/java/eu/xfsc/train/tcr/util/ResolveServiceParams.java @@ -0,0 +1,16 @@ +package eu.xfsc.train.tcr.util; + +import com.fasterxml.jackson.annotation.JsonProperty; + +@lombok.Getter +@lombok.Setter +public class ResolveServiceParams { + + @JsonProperty("uri") + private String uri; + @JsonProperty("endpoint") + private String endpoint; + @JsonProperty("data") + private String data; + +} diff --git a/clients/java/src/test/java/eu/xfsc/train/tcr/util/ResolveServiceLauncherTest.java b/clients/java/src/test/java/eu/xfsc/train/tcr/util/ResolveServiceLauncherTest.java new file mode 100644 index 0000000000000000000000000000000000000000..ddb5c67f172d7d7d38ec1a6e839ae2eb81a959f4 --- /dev/null +++ b/clients/java/src/test/java/eu/xfsc/train/tcr/util/ResolveServiceLauncherTest.java @@ -0,0 +1,218 @@ +package eu.xfsc.train.tcr.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import eu.xfsc.train.tcr.api.generated.model.ResolveResult; +import eu.xfsc.train.tcr.api.generated.model.ResolveTrustList; +import eu.xfsc.train.tcr.api.generated.model.ValidateResponse; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.nio.charset.Charset; +import java.util.List; +import java.util.Map; + +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +public class ResolveServiceLauncherTest { + + private static MockWebServer mockBackEnd; + private static final int TCR_PORT = 8087; + private static final ObjectMapper mapper = new ObjectMapper(); + private static final TypeReference<List<ResolveResult>> LIST_TYPE_REF = new TypeReference<List<ResolveResult>>() {}; + + @BeforeAll + static void setUp() throws IOException { + mockBackEnd = new MockWebServer(); + mockBackEnd.noClientAuth(); + mockBackEnd.start(TCR_PORT); + } + + @AfterAll + static void tearDown() throws IOException { + mockBackEnd.shutdown(); + } + + @Test + public void testLauncherFailEmptyRequest() throws Exception { + String[] arguments = new String[] {}; + + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(byteArrayOutputStream); + System.setOut(out); + + ResolveServiceLauncher.main(arguments); + + String output = byteArrayOutputStream.toString(Charset.defaultCharset()); + assertEquals("error: no resolution endpoint specified\n", output); + out.close(); + } + + @Test + public void testLauncherFailOptionsRequest() throws Exception { + String[] arguments = new String[] {"-Dtrain.opt=value", "--flag-on", "-d", "endpoint=validate"}; + + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(byteArrayOutputStream); + System.setOut(out); + + ResolveServiceLauncher.main(arguments); + + String output = byteArrayOutputStream.toString(Charset.defaultCharset()); + assertTrue(output.endsWith("error: no validation data provided\n")); + out.close(); + } + + @Test + public void testLauncherFailNoEndpointRequest() throws Exception { + String[] arguments = new String[] {"d={\"issuer\": \"issuer1\", \"trustSchemePointers\": [\"ptr1\", \"ptr2\"]}"}; + + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(byteArrayOutputStream); + System.setOut(out); + + ResolveServiceLauncher.main(arguments); + + String output = byteArrayOutputStream.toString(Charset.defaultCharset()); + assertEquals("error: no resolution endpoint specified\n", output); + out.close(); + } + + @Test + public void testLauncherFailUnknownEndpointRequest() throws Exception { + String[] arguments = new String[] {"e=verify"}; + + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(byteArrayOutputStream); + System.setOut(out); + + ResolveServiceLauncher.main(arguments); + + String output = byteArrayOutputStream.toString(Charset.defaultCharset()); + assertEquals("error: unknown resolution endpoint: verify\n", output); + out.close(); + } + + @Test + public void testLauncherFailNoDataRequest() throws Exception { + String[] arguments = new String[] {"e=validate"}; + + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(byteArrayOutputStream); + System.setOut(out); + + ResolveServiceLauncher.main(arguments); + + String output = byteArrayOutputStream.toString(Charset.defaultCharset()); + assertEquals("error: no validation data provided\n", output); + out.close(); + } + + @Test + public void testLauncherResolveResponse() throws Exception { + String[] arguments = new String[] {"e=resolve", "d={\"issuer\": \"issuer1\", \"trustSchemePointers\": [\"ptr1\", \"ptr2\"]}"}; + + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(byteArrayOutputStream); + System.setOut(out); + + ResolveResult rrs = new ResolveResult("did:mock:sample-issuer1", List.of("ptr2"), Map.of(), List.of(new ResolveTrustList("https://issuer1.test/trust-list", Map.of()))); + mockBackEnd.enqueue(new MockResponse().setBody(mapper.writeValueAsString(List.of(rrs))).addHeader("Content-Type", "application/json")); + + ResolveServiceLauncher.main(arguments); + + String output = byteArrayOutputStream.toString(Charset.defaultCharset()); + assertTrue(output.startsWith("result: ")); + List<ResolveResult> resp = mapper.readValue(output.substring(8), LIST_TYPE_REF); + assertEquals(1, resp.size()); + assertEquals(rrs.getDid(), resp.get(0).getDid()); + assertEquals(1, rrs.getPointers().size()); + assertEquals("ptr2", rrs.getPointers().get(0)); + + out.close(); + } + + @Test + public void testLauncherValidateResponse() throws Exception { + String[] arguments = new String[] {"e=validate", "d={\"issuer\": \"issuer1\", \"did\": \"did:mock:sample-issuer1\", \"endpoints\": [\"https://issuer1.test/trust-list\"]}"}; + + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(byteArrayOutputStream); + System.setOut(out); + + ValidateResponse vrs = new ValidateResponse(true, true, true, List.of(new ResolveTrustList("https://issuer1.test/trust-list", Map.of()))); + mockBackEnd.enqueue(new MockResponse().setBody(mapper.writeValueAsString(vrs)).addHeader("Content-Type", "application/json")); + + ResolveServiceLauncher.main(arguments); + + String output = byteArrayOutputStream.toString(Charset.defaultCharset()); + assertTrue(output.startsWith("result: ")); + ValidateResponse resp = mapper.readValue(output.substring(8), ValidateResponse.class); + assertEquals(vrs, resp); + + out.close(); + } + + @Test + public void testLauncherResolveFileResponse() throws Exception { + String[] arguments = new String[] {"uri=http://localhost:8087", "f=./src/test/resources/resolve-input.json"}; + + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(byteArrayOutputStream); + System.setOut(out); + + ResolveResult rrs = new ResolveResult("did:mock:sample-issuer1", List.of("ptr2"), Map.of(), List.of(new ResolveTrustList("https://issuer1.test/trust-list", Map.of()))); + mockBackEnd.enqueue(new MockResponse().setBody(mapper.writeValueAsString(List.of(rrs))).addHeader("Content-Type", "application/json")); + + ResolveServiceLauncher.main(arguments); + + String output = byteArrayOutputStream.toString(Charset.defaultCharset()); + assertTrue(output.startsWith("result: ")); + List<ResolveResult> resp = mapper.readValue(output.substring(8), LIST_TYPE_REF); + assertEquals(1, resp.size()); + assertEquals(rrs.getDid(), resp.get(0).getDid()); + assertEquals(1, rrs.getPointers().size()); + assertEquals("ptr2", rrs.getPointers().get(0)); + + out.close(); + } + + @Test + public void testLauncherValidateFileResponse() throws Exception { + String[] arguments = new String[] {"f=src/test/resources/validate-input.json"}; + + mockBackEnd.shutdown(); + mockBackEnd = new MockWebServer(); + mockBackEnd.noClientAuth(); + mockBackEnd.start(8080); + + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(byteArrayOutputStream); + System.setOut(out); + + ValidateResponse vrs = new ValidateResponse(true, true, true, List.of(new ResolveTrustList("https://issuer1.test/trust-list", Map.of()))); + mockBackEnd.enqueue(new MockResponse().setBody(mapper.writeValueAsString(vrs)).addHeader("Content-Type", "application/json")); + + ResolveServiceLauncher.main(arguments); + + String output = byteArrayOutputStream.toString(Charset.defaultCharset()); + assertTrue(output.startsWith("result: ")); + ValidateResponse resp = mapper.readValue(output.substring(8), ValidateResponse.class); + assertEquals(vrs, resp); + + out.close(); + + tearDown(); + setUp(); + } + +} diff --git a/clients/java/src/test/resources/resolve-input.json b/clients/java/src/test/resources/resolve-input.json new file mode 100644 index 0000000000000000000000000000000000000000..7f805f0e7d0aa28ba3598e626e47d744147e0685 --- /dev/null +++ b/clients/java/src/test/resources/resolve-input.json @@ -0,0 +1 @@ +{"uri": "https://external.host", "endpoint": "resolve", "data": "{\"issuer\": \"issuer1\", \"trustSchemePointers\": [\"ptr1\", \"ptr2\"]}"} \ No newline at end of file diff --git a/clients/java/src/test/resources/validate-input.json b/clients/java/src/test/resources/validate-input.json new file mode 100644 index 0000000000000000000000000000000000000000..248b631ebfd073568692082210cf939edf22dd9b --- /dev/null +++ b/clients/java/src/test/resources/validate-input.json @@ -0,0 +1 @@ +{"uri": "http://127.0.0.1:8080", "endpoint": "validate", "data": "{\"issuer\": \"issuer1\", \"did\": \"did:mock:sample-issuer1\", \"endpoints\": [\"https://issuer1.test/trust-list\"]}"} \ No newline at end of file diff --git a/clients/js/ansible.playbook.yml b/clients/js/ansible.playbook.yml index 90e0dabc75fde60bd67a7f76074567342d5d6a60..9fb00aa5f0b6b3720867286828ba79e1162b5e35 100644 --- a/clients/js/ansible.playbook.yml +++ b/clients/js/ansible.playbook.yml @@ -1,23 +1,73 @@ -# see https://github.com/nvm-sh/nvm -- name: Install nvm - ansible.builtin.shell: > - curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash - args: - creates: "{{ ansible_env.HOME }}/.nvm/nvm.sh" +# todo make it work with pyenv +- name: Git checkout + ansible.builtin.git: + repo: 'https://github.com/pyenv/pyenv.git' + dest: ~/.pyenv + when: ansible_distribution == 'Ubuntu' + tags: + - python + +- name: Install Python on MacOSX + homebrew: + name: '{{ item }}' + state: present + with_items: + - python + when: ansible_distribution == 'MacOSX' tags: - - npm + - MacOSX + - python -- name: add nvm to bash config file +- name: Install Python on Ubuntu + apt: + name: + - python3-dev + - build-essential + - libssl-dev + - zlib1g-dev + - libbz2-dev + - libreadline-dev + - libsqlite3-dev + - curl + - libncursesw5-dev + - xz-utils + - tk-dev + - libxml2-dev + - libxmlsec1-dev + - libffi-dev + - liblzma-dev + state: present + when: ansible_distribution == 'Ubuntu' + become: true + become_method: sudo + tags: + - Ubuntu + - python + +- name: "Add Python to shell config file `~/.bashrc`" lineinfile: - dest: "{{ _found_shell_config_file }}" + dest: ~/.bashrc state: present - line: "source {{ ansible_env.HOME }}/.nvm/nvm.sh" + line: "{{ item }}" + with_items: + - "# TRAIN ansible: python config" + - "export PYENV_ROOT=~/.pyenv" + - "export PATH=$PYENV_ROOT/bin:$PATH" + - "eval \"$(pyenv init -)\"" + when: ansible_distribution == 'Ubuntu' tags: - - npm + - python -- name: Install concrete version for Node - ansible.builtin.shell: | - source {{ ansible_env.HOME }}/.nvm/nvm.sh - nvm install v20.8.0 +- name: Install concrete version for python + ansible.builtin.shell: "PS1=emulate-interactive && source ~/.bashrc && {{ item }}" + with_items: + # speedup pyenv commands + - cd ~/.pyenv && src/configure && make -C src + - pyenv --version + - pyenv install 3.12 + - pyenv global 3.12 + args: + executable: /bin/bash + when: ansible_distribution == 'Ubuntu' tags: - - npm \ No newline at end of file + - golang diff --git a/clients/py/Makefile b/clients/py/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..bc803d355190f68cc6e752f881a4b697fba644fe --- /dev/null +++ b/clients/py/Makefile @@ -0,0 +1,64 @@ +# see https://makefiletutorial.com/ + +SHELL := /bin/bash -eu -o pipefail +PYTHON_3 ?= python3 +PYTHON_D ?= /opt/python.d +SOURCE_PATHS := "src/trusted_content_resolver_client" + +VENV_PATH_DEV := $(PYTHON_D)/dev/train/trusted_content_resolver_client +VENV_PATH_PROD := $(PYTHON_D)/prod/train/trusted_content_resolver_client + +setup_dev: $(VENV_PATH_DEV) + +$(VENV_PATH_DEV): + $(PYTHON_3) -m venv $(VENV_PATH_DEV) + "$(VENV_PATH_DEV)/bin/pip" install -U pip wheel + "$(VENV_PATH_DEV)/bin/pip" install -e ".[dev]" + +setup_prod: $(VENV_PATH_PROD) + +$(VENV_PATH_PROD): + $(PYTHON_3) -m venv $(VENV_PATH_PROD) + "$(VENV_PATH_PROD)/bin/pip" install -U pip wheel + "$(VENV_PATH_PROD)/bin/pip" install "." + +isort: + "$(VENV_PATH_DEV)/bin/isort" $(SOURCE_PATHS) tests + +pylint: + "$(VENV_PATH_DEV)/bin/pylint" $(SOURCE_PATHS) tests + +coverage_run: + "$(VENV_PATH_DEV)/bin/coverage" run -m pytest -m "not integration" + +coverage_report: + "$(VENV_PATH_DEV)/bin/coverage" report + +mypy: + "$(VENV_PATH_DEV)/bin/mypy" $(SOURCE_PATHS) + +code_check: \ + setup_dev \ + isort \ + pylint \ + coverage_run coverage_report \ + mypy + +clean_dev: + rm -rfv "$(VENV_PATH_DEV)" + +clean_prod: + rm -rfv "$(VENV_PATH_PROD)" + +activate_env_prod: + @echo "source \"$(VENV_PATH_PROD)/bin/activate\"" + +activate_env_dev: + @echo "source \"$(VENV_PATH_DEV)/bin/activate\"" + +ansible: + cd ../.. && \ + ansible-playbook ansible.playbook.yml --connection=local -i localhost, --tags=python + +third-party-txt-file: + # TODO diff --git a/clients/py/ansible.playbook.yml b/clients/py/ansible.playbook.yml index b7e50b06be6cdfecb692bd9aa42a92b65f0b6e84..164e0f2012194df48482c8af0c9176c56d061098 100644 --- a/clients/py/ansible.playbook.yml +++ b/clients/py/ansible.playbook.yml @@ -1,4 +1,12 @@ # todo make it work with pyenv +- name: Git checkout + ansible.builtin.git: + repo: 'https://github.com/pyenv/pyenv.git' + dest: ~/.pyenv + when: ansible_distribution == 'Ubuntu' + tags: + - python + - name: Install Python on MacOSX homebrew: name: '{{ item }}' @@ -8,13 +16,58 @@ when: ansible_distribution == 'MacOSX' tags: - MacOSX + - python - name: Install Python on Ubuntu apt: - name: '{{ item }}' + name: + - python3-dev + - build-essential + - libssl-dev + - zlib1g-dev + - libbz2-dev + - libreadline-dev + - libsqlite3-dev + - curl + - libncursesw5-dev + - xz-utils + - tk-dev + - libxml2-dev + - libxmlsec1-dev + - libffi-dev + - liblzma-dev state: present - with_items: - - python3-dev when: ansible_distribution == 'Ubuntu' + become: true + become_method: sudo tags: - Ubuntu + - python + +- name: "Add Python to shell config file `~/.bashrc`" + lineinfile: + dest: ~/.bashrc + state: present + line: "{{ item }}" + with_items: + - "# TRAIN ansible: python config" + - "export PYENV_ROOT=~/.pyenv" + - "export PATH=$PYENV_ROOT/bin:$PATH" + - "eval \"$(pyenv init -)\"" + when: ansible_distribution == 'Ubuntu' + tags: + - python + +- name: Install concrete version for python + ansible.builtin.shell: "PS1=emulate-interactive && source ~/.bashrc && {{ item }}" + with_items: + # speedup pyenv commands + - cd ~/.pyenv && src/configure && make -C src + - pyenv --version + - pyenv install 3.11.4 + - pyenv global 3.11.4 + args: + executable: /bin/bash + when: ansible_distribution == 'Ubuntu' + tags: + - golang diff --git a/clients/py/setup.cfg b/clients/py/setup.cfg new file mode 100644 index 0000000000000000000000000000000000000000..ad65c6959b80ad07e3774f339520806533faeaf9 --- /dev/null +++ b/clients/py/setup.cfg @@ -0,0 +1,66 @@ +[metadata] +name = trusted_content_resolver_client +version = 0.0.0 + +[options] +zip_safe = False +include_package_data = True +package_dir= + =src +packages = find: + +install_requires = + requests==2.31.0 + pydantic==2.4.2 + +[options.package_data] +* = *.yaml + +[options.packages.find] +where=src + +[options.extras_require] +dev = + pylint + pytest + mypy + types-requests + coverage + +[isort] +known_typing=typing +known_localfolder=trusted_content_resolver_client +;suppress inspection for section "SpellCheckingInspection" +sections=FUTURE,TYPING,STDLIB,FIRSTPARTY,THIRDPARTY,LOCALFOLDER + +[coverage:run] +data_file=.coverage +branch=True +source=src + +[coverage:report] +fail_under=16 +show_missing=True +exclude_lines = + pragma: no cover + def __repr__ + if self.debug: + if settings.DEBUG + raise AssertionError + raise NotImplementedError + if 0: + if __name__ == .__main__.: + class .*\bProtocol\): + @(abc\.)?abstractmethod + +[tool:pytest] +addopts = --strict-markers -m "not integration" -v +markers = + integration +testpaths = tests +filterwarnings = + error + +[pylint.FORMAT] +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )?<?https?://\S+>?$|See `https://\S+`_ diff --git a/clients/py/setup.py b/clients/py/setup.py new file mode 100644 index 0000000000000000000000000000000000000000..606849326a4002007fd42060b51e69a19c18675c --- /dev/null +++ b/clients/py/setup.py @@ -0,0 +1,3 @@ +from setuptools import setup + +setup() diff --git a/clients/py/src/trusted_content_resolver_client/__init__.py b/clients/py/src/trusted_content_resolver_client/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/clients/py/src/trusted_content_resolver_client/resolve_service_client.py b/clients/py/src/trusted_content_resolver_client/resolve_service_client.py new file mode 100644 index 0000000000000000000000000000000000000000..f7686e80c5fb6b565a91233bcd2dcbd62c3d7a56 --- /dev/null +++ b/clients/py/src/trusted_content_resolver_client/resolve_service_client.py @@ -0,0 +1,10 @@ +""" +Propagation of main java client implementation +""" + + +def resolve_trust_list(*_, **__) -> str: + """ + WIP: To be implemented + """ + return "something" diff --git a/clients/py/test/bdd/bdd.feature b/clients/py/test/bdd/bdd.feature deleted file mode 120000 index f419198b6e2fa11c2e54b5b4bdf841040dfe36c9..0000000000000000000000000000000000000000 --- a/clients/py/test/bdd/bdd.feature +++ /dev/null @@ -1 +0,0 @@ -../../../../features/bdd.feature \ No newline at end of file diff --git a/clients/py/test/bdd/steps/bdd.py b/clients/py/test/bdd/steps/bdd.py deleted file mode 100644 index e9a7bb9bbbba2582fe4d9f22182674d57d132fb7..0000000000000000000000000000000000000000 --- a/clients/py/test/bdd/steps/bdd.py +++ /dev/null @@ -1,98 +0,0 @@ -from dataclasses import dataclass -from behave import * - -import requests - -SESSION = requests.Session() - - -@dataclass -class TrustedContentResolver: - app: str - - -@given('we have XFSC TRAIN Trusted Content Resolver REST API') -def step_impl(context): - # TODO make a heartbeat requests - # ask kubernetis url - # load trusted-content-resolver/service/src/main/resources/application.yml into context - context.ctr = TrustedContentResolver('http://localhost:8087') - pass - - -@given('an actor X to be verified') -def step_impl(context): - # TODO check the data if available - # load input data for actor x into context - context.actor_x = { - 'some': 'info' - } - - -@when('we initiate a verification actor X') -def step_impl(context): - response = SESSION.get(f'https://example.com') - - #response = SESSION.post(f'{context.ctr.app}/tcr/verify', data=context.actor_x['some']) - context.ctr.response = response - - -@then('actor X gets verified') -def step_impl(context): - assert context.ctr.response - - -@when("we initiate a verification actor Y") -def step_impl(context): - """ - :type context: behave.runner.Context - """ - raise NotImplementedError(u'STEP: When we initiate a verification actor Y') - - -# @given( -# "A request update of trust frameworks and DID configuration is successfully reflected in the DNS Zone File (200)") -# def step_impl(context): -# """ -# :type context: behave.runner.Context -# """ -# # GET to DNS which return success DID configuration -# raise NotImplementedError( -# u'STEP: Given A request update of trust frameworks and DID configuration is successfully reflected in the DNS Zone File (200)') -# -# -# @step( -# "An instantiation of a trust list is reflected in the trust list storage with possibility to retrieve via API endpoints") -# def step_impl(context): -# """ -# :type context: behave.runner.Context -# """ -# # qhuery database for -# raise NotImplementedError( -# u'STEP: And An instantiation of a trust list is reflected in the trust list storage with possibility to retrieve via API endpoints') -# -# -# @given("trust framework pointers example.federation1.de and example.federation2.de") -# def step_impl(context): -# """ -# :type context: behave.runner.Context -# """ -# context.trust_framework_pointers = 'example.federation1.de' and 'example.federation2.de'] -# -# -# @when("Navigate to listed trust framework pointers") -# def step_impl(context): -# """ -# :type context: behave.runner.Context -# """ -# data = VerificationRequest(context.trust_framework_pointers) -# response = SESSION.post(f'{context.ctr.app}/tcr/verify', data=data.json()) -# context.ctr.response = response -# -# -# @then("have example.federation1.de and example.federation2.de should be in trust List VC endpoint") -# def step_impl(context): -# """ -# :type context: behave.runner.Context -# """ -# assert sql.query('SELECT WHERE x in [xample.federation1.de and example.federation2.de]') is True diff --git a/clients/py/tests/resolve_service_client_test.py b/clients/py/tests/resolve_service_client_test.py new file mode 100644 index 0000000000000000000000000000000000000000..de00f8b756cbbac123d0938a51e7459b4cf6537b --- /dev/null +++ b/clients/py/tests/resolve_service_client_test.py @@ -0,0 +1,12 @@ +""" +Testing python client +""" + +from trusted_content_resolver_client import resolve_service_client + + +def test_resolve_trust_list(): + """ + WIP + """ + assert resolve_service_client.resolve_trust_list() == "something" diff --git a/docker/.env b/docker/.env index ee100a2bd59192b5ccddd418ed8b75f7481eb800..68879fae30d220a12b4dc4ff2d18fe6b27622e88 100644 --- a/docker/.env +++ b/docker/.env @@ -1,6 +1 @@ COMPOSE_PROJECT_NAME = xfsc-train - -### FC SERVER PROPERTIES ### -CI_REGISTRY=registry.gitlab.com/gaia-x/data-infrastructure-federation-services/cat/fc-service -# federated-catalog client secret, gaia-x realm -FC_CLIENT_SECRET=vZo1equicRl1UdxJDWCNNJWe6vJcm2Cg diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000000000000000000000000000000000000..77f788042a944c2bf7bcd73b786be7871115b601 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,45 @@ +## Build Procedure + +Ensure you have JDK 17, Maven 3.5.4 (or newer) and Git installed + +First clone the TRAIN Trusted Content Resolver (TCR) repository: + +``` sh +git clone git@gitlab.eclipse.org/eclipse/xfsc/train/trusted-content-resolver.git +``` + +Then build project with `maven`: + +``` sh +cd <tcr_root_folder> +mvn clean install +``` + +## Run TCR as local Docker image +Go to `/docker` folder. Use docker compose to start universal-resolver with did:web driver only: + +```sh +docker compose --env-file unires.env -f uni-resolver-web.yml up -d +``` + +or with all available universal-resolver drivers: + +```sh +docker compose --env-file unires.env -f uni-resolver-all.yml up -d +``` + +To test did:web resolution you can try the following CURL commands: + +``` sh +curl -X GET http://localhost:8080/1.0/identifiers/did:web:did.actor:alice +curl -X GET http://localhost:8080/1.0/identifiers/did:web:did.actor:bob +curl -X GET http://localhost:8080/1.0/identifiers/did:web:did.actor:mike +``` + +Then start TCR: + +``` sh +docker compose up -d +``` + +For more information on universal-resolver please see the project on [GitHub](https://github.com/decentralized-identity/universal-resolver) diff --git a/docker/bare_metal_dev_setup/Dockerfile b/docker/bare_metal_dev_setup/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..87fe9aefd05f0f1f200d26efcaf27a79b3734cca --- /dev/null +++ b/docker/bare_metal_dev_setup/Dockerfile @@ -0,0 +1,14 @@ +ARG UBUNTU_BASE_IMAGE_WITH_TAG + +# Specify the parent image from which we build +FROM ${UBUNTU_BASE_IMAGE_WITH_TAG} + +# don't bother with multiple layers since it is local dev purpose container +# Build the application with cmake +RUN apt update -y +RUN DEBIAN_FRONTEND=noninteractive apt install sudo build-essential ansible git zip unzip bash-completion -y + +# do not ask password with sudo for ubuntu user, required for ansible automation +RUN echo 'ubuntu ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers + +#WORKDIR /app diff --git a/docker/bare_metal_dev_setup/Makefile b/docker/bare_metal_dev_setup/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..564154e3779cd18cb4578ff68dd1ac534f8f59ab --- /dev/null +++ b/docker/bare_metal_dev_setup/Makefile @@ -0,0 +1,43 @@ +TRAIN_UBUNTU_VERSION ?= 23.10 +TRAIN_CONTAINER_ENGINE ?= docker +TRAIN_UBUNTU_BASE_IMAGE_WITH_TAG ?= ubuntu:$(TRAIN_UBUNTU_VERSION) + +TRAIN_CONTAINER_NAME ?= trust-content-resolver + +_prepare_container-on-os: _$(TRAIN_CONTAINER_ENGINE)-on-$(shell uname -s) +_podman-on-Darwin: + @podman ps &> /dev/null || podman machine start +_docker-on-Darwin: + # not implemented +_%-on-Linux: + # linux do not require a vm +_%-on-%: + $(error Not supported OS yet) + +build: _prepare_container-on-os + $(TRAIN_CONTAINER_ENGINE) build \ + --build-arg UBUNTU_BASE_IMAGE_WITH_TAG=$(TRAIN_UBUNTU_BASE_IMAGE_WITH_TAG) \ + --tag=trust-content-resolver/$(TRAIN_UBUNTU_BASE_IMAGE_WITH_TAG) \ + --file=./Dockerfile + +ubuntu_bash: _prepare_container-on-os + @set -x && if ! $(TRAIN_CONTAINER_ENGINE) container inspect $(TRAIN_CONTAINER_NAME) &> /dev/null; \ + then $(TRAIN_CONTAINER_ENGINE) run \ + --name $(TRAIN_CONTAINER_NAME) \ + --interactive \ + --tty \ + --volume=../..:/app:rw \ + --workdir=/app \ + --user 0:0 \ + localhost/trust-content-resolver/$(TRAIN_UBUNTU_BASE_IMAGE_WITH_TAG) bash; \ + else $(TRAIN_CONTAINER_ENGINE) start \ + --interactive \ + --attach \ + $(TRAIN_CONTAINER_NAME); \ + fi + +rm: _prepare_container-on-os + $(TRAIN_CONTAINER_ENGINE) rm $(TRAIN_CONTAINER_NAME) + +rmi: rm + $(TRAIN_CONTAINER_ENGINE) rmi trust-content-resolver/$(TRAIN_UBUNTU_BASE_IMAGE_WITH_TAG) diff --git a/docker/bare_metal_dev_setup/README.md b/docker/bare_metal_dev_setup/README.md new file mode 100644 index 0000000000000000000000000000000000000000..da8eaee06853d585267b9ff9560162414986ac26 --- /dev/null +++ b/docker/bare_metal_dev_setup/README.md @@ -0,0 +1,12 @@ +Automation for provisioning OS developer environments is done through Ansible playbooks for Ubuntu and macOS. + +On macOS, playbooks are executed manually from time to time on developers' machines. + +On Linux, it can be integrated into CI/CD with: +``` bahs +$ export TRAIN_CONTAINER_ENGINE=podman # if podman is preferred against docker +$ make rmi # delete previuos image with all containters +$ make build # add to ubuntu image some requorements for make, go, pyhton, java and js +$ make ubuntu_bash # log into ubunut +> make ansible # provison ubuntu with all tools requires for develop components in go, pyhton, java and js +``` \ No newline at end of file diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 5004ca9b4e2a93eb5842b18fc707f6da3f26ec75..947c4df1386483db8422934e053acd0e73045da0 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -1,5 +1,9 @@ version: '3.7' +#include: +# - uni-drivers.yml +# - uni-resolver.yml + services: server: container_name: "tcr-server" @@ -9,7 +13,7 @@ services: dockerfile: Dockerfile # environment: ports: - - "8087:8087" + - "8887:8087" networks: - "gaia-x" # extra_hosts: diff --git a/docker/uni-resolver-all.yml b/docker/uni-resolver-all.yml new file mode 100644 index 0000000000000000000000000000000000000000..71338293a019bb4487e2621aacaeabb2348a4fdd --- /dev/null +++ b/docker/uni-resolver-all.yml @@ -0,0 +1,288 @@ +version: "3.7" + +networks: + default: + name: universal-resolver + +services: + uni-resolver-web: + image: universalresolver/uni-resolver-web:latest + ports: + - "8080:8080" + driver-did-btcr: + image: universalresolver/driver-did-btcr:latest + environment: + uniresolver_driver_did_btcr_bitcoinConnection: ${uniresolver_driver_did_btcr_bitcoinConnection} + uniresolver_driver_did_btcr_rpcUrlMainnet: ${uniresolver_driver_did_btcr_rpcUrlMainnet} + uniresolver_driver_did_btcr_rpcUrlTestnet: ${uniresolver_driver_did_btcr_rpcUrlTestnet} + uniresolver_driver_did_btcr_rpcCertMainnet: ${uniresolver_driver_did_btcr_rpcCertMainnet} + uniresolver_driver_did_btcr_rpcCertTestnet: ${uniresolver_driver_did_btcr_rpcCertTestnet} + ports: + - "8081:8080" + driver-did-sov: + image: universalresolver/driver-did-sov:latest + environment: + uniresolver_driver_did_sov_libIndyPath: ${uniresolver_driver_did_sov_libIndyPath} + uniresolver_driver_did_sov_poolConfigs: ${uniresolver_driver_did_sov_poolConfigs} + uniresolver_driver_did_sov_poolVersions: ${uniresolver_driver_did_sov_poolVersions} + uniresolver_driver_did_sov_walletNames: ${uniresolver_driver_did_sov_walletNames} + uniresolver_driver_did_sov_submitterDidSeeds: ${uniresolver_driver_did_sov_submitterDidSeeds} + ports: + - "8082:8080" + uni-resolver-driver-did-uport: + image: uport/uni-resolver-driver-did-uport:4.1.0 + ports: + - "8083:8081" + driver-did-stack: + image: universalresolver/driver-did-stack:latest + ports: + - "8084:8080" + driver-dns: + image: universalresolver/driver-dns:latest + environment: + uniresolver_driver_dns_dnsServers: ${uniresolver_driver_dns_dnsServers} + ports: + - "8087:8080" + jolocom-did-driver: + image: jolocomgmbh/jolocom-did-driver:latest + ports: + - "8088:8080" + hacera-did-driver: + image: hacera/hacera-did-driver:latest + ports: + - "8089:8080" + driver-did-ccp: + image: universalresolver/driver-did-ccp:latest + ports: + - "8091:8080" + ontid-driver: + image: ontio/ontid-driver:latest + ports: + - "8093:8080" + kilt-did-driver: + image: kiltprotocol/kilt-did-driver:2.4.2 + environment: + KILT_BLOCKCHAIN_NODE: ${uniresolver_driver_kilt_blockchain_node} + ports: + - "8094:8080" + evan-did-driver: + image: evannetwork/evan-did-driver:latest + ports: + - "8095:8080" + uni-resolver-driver-did-factom: + image: sphereon/uni-resolver-driver-did-factom:latest + ports: + - "8097:8080" + uni-resolver-did-v1-driver: + image: veresone/uni-resolver-did-v1-driver:latest + ports: + - "8100:8080" + driver-did-mpg: + image: mpgshankr/driver-did-mpg:latest + ports: + - "8103:8080" + uni-resolver-driver-did-io: + image: iotex/uni-resolver-driver-did-io:latest + ports: + - "8104:8080" + bba-did-driver: + container_name: bba-did-driver + image: blobaa/bba-did-driver:0.2.2 + ports: + - "8107:8080" + schema-registry-did-resolver: + image: 51nodes/schema-registry-did-resolver:0.1.1 + ports: + - "8110:8080" +# runtime error +# driver-did-ion: +# image: identityfoundation/driver-did-ion:v0.8.1 +# ports: +# - "8111:8080" + ace-did-driver: + container_name: ace-did-driver + image: aceblock/ace-did-driver:latest + environment: + UNIRESOLVER_DRIVER_DID_ACE: ${UNIRESOLVER_DRIVER_DID_ACE} + ports: + - "8112:8080" + gataca-did-resolver-driver: + container_name: gataca-did-resolver-driver + image: gatacaid/universal-resolver-driver:2.0.0 + ports: + - "8113:8080" + driver-did-icon: + image: amuyu/driver-did-icon:0.1.3 + environment: + uniresolver_driver_did_icon_node_url: ${uniresolver_driver_did_icon_node_url} + uniresolver_driver_did_icon_score_addr: ${uniresolver_driver_did_icon_score_addr} + uniresolver_driver_did_icon_network_id: ${uniresolver_driver_did_icon_network_id} + ports: + - "8114:8080" + driver-did-vaa: + image: caictdevelop/driver-did-vaa:1.0.0 + ports: + - "8115:8080" + unisot-did-driver: + image: unisot/unisot-did-driver:latest + ports: + - "8116:8080" + driver-did-sol: + image: identitydotcom/driver-did-sol:3.3.0 + ports: + - "8118:8080" + driver-did-lit: + image: ibct/driver-did-lit:0.1.1 + environment: + LEDGIS_LIT_ENDPOINT: ${LEDGIS_LIT_ENDPOINT} + LEDGIS_LIT_CODE: ${LEDGIS_LIT_CODE} + ports: + - "8119:8080" + driver-did-emtrust: + image: halialabsdev/emtrust_did_driver:latest + ports: + - "8120:8080" +# image error +# driver-didkit: +# image: ghcr.io/spruceid/didkit-http:latest +# environment: +# PORT: 3000 +# HOST: 0.0.0.0 +# ports: +# - "8121:3000" + eosio-driver: + container_name: eosio-driver + image: gimlyblockchain/eosio-universal-resolver-driver + ports: + - "8123:8080" +# image error +# orb-did-driver: +# container_name: orb-did-driver +# image: ghcr.io/trustbloc-cicd/orb-did-driver:v1.0.0-rc4-snapshot-7125f6a +# environment: +# ORB_DRIVER_HOST_URL: ${ORB_DRIVER_HOST_URL} +# ORB_DRIVER_TLS_SYSTEMCERTPOOL: ${ORB_DRIVER_TLS_SYSTEMCERTPOOL} +# ORB_DRIVER_VERIFY_RESOLUTION_RESULT_TYPE: ${ORB_DRIVER_VERIFY_RESOLUTION_RESULT_TYPE} +# ports: +# - "8122:8121" +# command: start + driver-did-oyd: + image: oydeu/oydid-resolver:v0.4.5 + ports: + - "8124:3000" + driver-did-moncon: + image: camicasii/didresolver-g + ports: + - "8125:8080" + dock-did-driver: + image: docknetwork/dock-did-driver:latest + ports: + - "8099:8080" + mydata-did-driver: + image: igrantio/uni-resolver-driver-did-mydata:1.3 + ports: + - "8126:8080" + driver-did-dns: + image: universalresolver/driver-did-dns:latest + ports: + - "8127:8080" + driver-did-indy: + image: universalresolver/driver-did-indy:latest + environment: + uniresolver_driver_did_indy_libIndyPath: ${uniresolver_driver_did_indy_libIndyPath} + uniresolver_driver_did_indy_poolConfigs: ${uniresolver_driver_did_indy_poolConfigs} + uniresolver_driver_did_indy_poolVersions: ${uniresolver_driver_did_indy_poolVersions} + uniresolver_driver_did_indy_walletNames: ${uniresolver_driver_did_indy_walletNames} + uniresolver_driver_did_indy_submitterDidSeeds: ${uniresolver_driver_did_indy_submitterDidSeeds} + ports: + - "8128:8080" + everscale-did-driver: + image: radianceteamssi/everscale-did-resolver-driver:latest + ports: + - "8129:8080" + alastria-did-driver-mvp2: + image: alastria/uni-resolver-driver-did-alastria:mvp2 + ports: + - "8130:8080" + cheqd-did-driver: + image: ghcr.io/cheqd/did-resolver:3.2.1 + ports: + - "8131:8080" + environment: + MAINNET_ENDPOINT: "grpc.cheqd.net:443,true,5s" + TESTNET_ENDPOINT: "grpc.cheqd.network:443,true,5s" + LOG_LEVEL: "warn" + RESOLVER_LISTENER: "0.0.0.0:8080" + driver-did-com: + image: ghcr.io/commercionetwork/uni-resolver-driver-did-com:latest + environment: + uniresolver_driver_did_com_network: ${uniresolver_driver_did_com_network} + ports: + - "8132:8080" + did-driver-dyne: + image: dyne/w3c-did-driver:latest + ports: + - "8133:8080" + did-jwk-driver: + image: transmute/restricted-resolver:latest + ports: + - "8134:8080" + did-kscirc-driver: + image: k4security/kschain-resolver:latest + ports: + - "8135:8080" + driver-did-iscc: + image: ghcr.io/iscc/iscc-did-driver:main + ports: + - "8136:8080" +# image error +# driver-did-ev: +# image: ghcr.io/kaytrust/driver-did-ev:latest +# environment: +# NODE_HOST: ${uniresolver_driver_did_ev_node_url} +# ADDRESS_IM: ${uniresolver_driver_did_ev_address_im} +# BASE_BLOCKS: ${uniresolver_driver_did_ev_base_blocks} +# ports: +# - "8137:8000" + driver-did-iid: + image: zoeyian/driver-did-iid:latest + ports: + - "8138:8080" + driver-did-bid: + image: caictdevelop/driver-did-bid:latest + ports: + - "8139:8080" +# image error +# driver-did-algo: +# image: ghcr.io/bryk-io/algoid-resolver:latest +# ports: +# - "8140:9091" + driver-did-polygonid: + image: polygonid/driver-did-polygonid:latest + ports: + - "8141:8080" + driver-did-pdc: + image: w744219971/driver-did-pdc:latest + ports: + - "8142:8080" + driver-did-tys: + image: itpeoplecorp/tys-did-driver:latest + ports: + - "8143:8080" + driver-did-plc: + image: bnewbold/uni-resolver-driver-did-plc:latest + ports: + - "8144:8000" + driver-did-evrc: + image: viitorcloud/uni-resolver-driver-did-evrc:latest + ports: + - "8145:8080" + driver-did-keri: + image: gleif/did-keri-resolver-service:latest + ports: + - "8146:7678" + driver-did-webs: + image: gleif/did-webs-resolver-service:latest + ports: + - "8147:7677" + diff --git a/docker/uni-resolver-web.yml b/docker/uni-resolver-web.yml new file mode 100644 index 0000000000000000000000000000000000000000..56b7d0178755b682aa216257ebba8bcb871a5306 --- /dev/null +++ b/docker/uni-resolver-web.yml @@ -0,0 +1,290 @@ +version: "3.7" + +#include: +# - uni-drivers.yml + +networks: + default: + name: universal-resolver + +services: + uni-resolver-web: + image: universalresolver/uni-resolver-web:latest + ports: + - "8080:8080" +# driver-did-btcr: +# image: universalresolver/driver-did-btcr:latest +# environment: +# uniresolver_driver_did_btcr_bitcoinConnection: ${uniresolver_driver_did_btcr_bitcoinConnection} +# uniresolver_driver_did_btcr_rpcUrlMainnet: ${uniresolver_driver_did_btcr_rpcUrlMainnet} +# uniresolver_driver_did_btcr_rpcUrlTestnet: ${uniresolver_driver_did_btcr_rpcUrlTestnet} +# uniresolver_driver_did_btcr_rpcCertMainnet: ${uniresolver_driver_did_btcr_rpcCertMainnet} +# uniresolver_driver_did_btcr_rpcCertTestnet: ${uniresolver_driver_did_btcr_rpcCertTestnet} +# ports: +# - "8081:8080" +# driver-did-sov: +# image: universalresolver/driver-did-sov:latest +# environment: +# uniresolver_driver_did_sov_libIndyPath: ${uniresolver_driver_did_sov_libIndyPath} +# uniresolver_driver_did_sov_poolConfigs: ${uniresolver_driver_did_sov_poolConfigs} +# uniresolver_driver_did_sov_poolVersions: ${uniresolver_driver_did_sov_poolVersions} +# uniresolver_driver_did_sov_walletNames: ${uniresolver_driver_did_sov_walletNames} +# uniresolver_driver_did_sov_submitterDidSeeds: ${uniresolver_driver_did_sov_submitterDidSeeds} +# ports: +# - "8082:8080" + uni-resolver-driver-did-uport: + image: uport/uni-resolver-driver-did-uport:4.1.0 + ports: + - "8083:8081" +# driver-did-stack: +# image: universalresolver/driver-did-stack:latest +# ports: +# - "8084:8080" +# driver-dns: +# image: universalresolver/driver-dns:latest +# environment: +# uniresolver_driver_dns_dnsServers: ${uniresolver_driver_dns_dnsServers} +# ports: +# - "8087:8080" +# jolocom-did-driver: +# image: jolocomgmbh/jolocom-did-driver:latest +# ports: +# - "8088:8080" +# hacera-did-driver: +# image: hacera/hacera-did-driver:latest +# ports: +# - "8089:8080" +# driver-did-ccp: +# image: universalresolver/driver-did-ccp:latest +# ports: +# - "8091:8080" +# ontid-driver: +# image: ontio/ontid-driver:latest +# ports: +# - "8093:8080" +# kilt-did-driver: +# image: kiltprotocol/kilt-did-driver:2.4.2 +# environment: +# KILT_BLOCKCHAIN_NODE: ${uniresolver_driver_kilt_blockchain_node} +# ports: +# - "8094:8080" +# evan-did-driver: +# image: evannetwork/evan-did-driver:latest +# ports: +# - "8095:8080" +# uni-resolver-driver-did-factom: +# image: sphereon/uni-resolver-driver-did-factom:latest +# ports: +# - "8097:8080" +# uni-resolver-did-v1-driver: +# image: veresone/uni-resolver-did-v1-driver:latest +# ports: +# - "8100:8080" +# driver-did-mpg: +# image: mpgshankr/driver-did-mpg:latest +# ports: +# - "8103:8080" +# uni-resolver-driver-did-io: +# image: iotex/uni-resolver-driver-did-io:latest +# ports: +# - "8104:8080" +# bba-did-driver: +# container_name: bba-did-driver +# image: blobaa/bba-did-driver:0.2.2 +# ports: +# - "8107:8080" + schema-registry-did-resolver: + image: 51nodes/schema-registry-did-resolver:0.1.1 + ports: + - "8110:8080" +# runtime error +# driver-did-ion: +# image: identityfoundation/driver-did-ion:v0.8.1 +# ports: +# - "8111:8080" +# ace-did-driver: +# container_name: ace-did-driver +# image: aceblock/ace-did-driver:latest +# environment: +# UNIRESOLVER_DRIVER_DID_ACE: ${UNIRESOLVER_DRIVER_DID_ACE} +# ports: +# - "8112:8080" +# gataca-did-resolver-driver: +# container_name: gataca-did-resolver-driver +# image: gatacaid/universal-resolver-driver:2.0.0 +# ports: +# - "8113:8080" +# driver-did-icon: +# image: amuyu/driver-did-icon:0.1.3 +# environment: +# uniresolver_driver_did_icon_node_url: ${uniresolver_driver_did_icon_node_url} +# uniresolver_driver_did_icon_score_addr: ${uniresolver_driver_did_icon_score_addr} +# uniresolver_driver_did_icon_network_id: ${uniresolver_driver_did_icon_network_id} +# ports: +# - "8114:8080" +# driver-did-vaa: +# image: caictdevelop/driver-did-vaa:1.0.0 +# ports: +# - "8115:8080" +# unisot-did-driver: +# image: unisot/unisot-did-driver:latest +# ports: +# - "8116:8080" +# driver-did-sol: +# image: identitydotcom/driver-did-sol:3.3.0 +# ports: +# - "8118:8080" +# driver-did-lit: +# image: ibct/driver-did-lit:0.1.1 +# environment: +# LEDGIS_LIT_ENDPOINT: ${LEDGIS_LIT_ENDPOINT} +# LEDGIS_LIT_CODE: ${LEDGIS_LIT_CODE} +# ports: +# - "8119:8080" +# driver-did-emtrust: +# image: halialabsdev/emtrust_did_driver:latest +# ports: +# - "8120:8080" +# image error +# driver-didkit: +# image: ghcr.io/spruceid/didkit-http:latest +# environment: +# PORT: 3000 +# HOST: 0.0.0.0 +# ports: +# - "8121:3000" +# eosio-driver: +# container_name: eosio-driver +# image: gimlyblockchain/eosio-universal-resolver-driver +# ports: +# - "8123:8080" +# image error +# orb-did-driver: +# container_name: orb-did-driver +# image: ghcr.io/trustbloc-cicd/orb-did-driver:v1.0.0-rc4-snapshot-7125f6a +# environment: +# ORB_DRIVER_HOST_URL: ${ORB_DRIVER_HOST_URL} +# ORB_DRIVER_TLS_SYSTEMCERTPOOL: ${ORB_DRIVER_TLS_SYSTEMCERTPOOL} +# ORB_DRIVER_VERIFY_RESOLUTION_RESULT_TYPE: ${ORB_DRIVER_VERIFY_RESOLUTION_RESULT_TYPE} +# ports: +# - "8122:8121" +# command: start +# driver-did-oyd: +# image: oydeu/oydid-resolver:v0.4.5 +# ports: +# - "8124:3000" +# driver-did-moncon: +# image: camicasii/didresolver-g +# ports: +# - "8125:8080" +# dock-did-driver: +# image: docknetwork/dock-did-driver:latest +# ports: +# - "8099:8080" +# mydata-did-driver: +# image: igrantio/uni-resolver-driver-did-mydata:1.3 +# ports: +# - "8126:8080" +# driver-did-dns: +# image: universalresolver/driver-did-dns:latest +# ports: +# - "8127:8080" +# driver-did-indy: +# image: universalresolver/driver-did-indy:latest +# environment: +# uniresolver_driver_did_indy_libIndyPath: ${uniresolver_driver_did_indy_libIndyPath} +# uniresolver_driver_did_indy_poolConfigs: ${uniresolver_driver_did_indy_poolConfigs} +# uniresolver_driver_did_indy_poolVersions: ${uniresolver_driver_did_indy_poolVersions} +# uniresolver_driver_did_indy_walletNames: ${uniresolver_driver_did_indy_walletNames} +# uniresolver_driver_did_indy_submitterDidSeeds: ${uniresolver_driver_did_indy_submitterDidSeeds} +# ports: +# - "8128:8080" +# everscale-did-driver: +# image: radianceteamssi/everscale-did-resolver-driver:latest +# ports: +# - "8129:8080" +# alastria-did-driver-mvp2: +# image: alastria/uni-resolver-driver-did-alastria:mvp2 +# ports: +# - "8130:8080" +# cheqd-did-driver: +# image: ghcr.io/cheqd/did-resolver:3.2.1 +# ports: +# - "8131:8080" +# environment: +# MAINNET_ENDPOINT: "grpc.cheqd.net:443,true,5s" +# TESTNET_ENDPOINT: "grpc.cheqd.network:443,true,5s" +# LOG_LEVEL: "warn" +# RESOLVER_LISTENER: "0.0.0.0:8080" +# driver-did-com: +# image: ghcr.io/commercionetwork/uni-resolver-driver-did-com:latest +# environment: +# uniresolver_driver_did_com_network: ${uniresolver_driver_did_com_network} +# ports: +# - "8132:8080" +# did-driver-dyne: +# image: dyne/w3c-did-driver:latest +# ports: +# - "8133:8080" +# did-jwk-driver: +# image: transmute/restricted-resolver:latest +# ports: +# - "8134:8080" +# did-kscirc-driver: +# image: k4security/kschain-resolver:latest +# ports: +# - "8135:8080" +# driver-did-iscc: +# image: ghcr.io/iscc/iscc-did-driver:main +# ports: +# - "8136:8080" +# image error +# driver-did-ev: +# image: ghcr.io/kaytrust/driver-did-ev:latest +# environment: +# NODE_HOST: ${uniresolver_driver_did_ev_node_url} +# ADDRESS_IM: ${uniresolver_driver_did_ev_address_im} +# BASE_BLOCKS: ${uniresolver_driver_did_ev_base_blocks} +# ports: +# - "8137:8000" +# driver-did-iid: +# image: zoeyian/driver-did-iid:latest +# ports: +# - "8138:8080" +# driver-did-bid: +# image: caictdevelop/driver-did-bid:latest +# ports: +# - "8139:8080" +# image error +# driver-did-algo: +# image: ghcr.io/bryk-io/algoid-resolver:latest +# ports: +# - "8140:9091" +# driver-did-polygonid: +# image: polygonid/driver-did-polygonid:latest +# ports: +# - "8141:8080" +# driver-did-pdc: +# image: w744219971/driver-did-pdc:latest +# ports: +# - "8142:8080" +# driver-did-tys: +# image: itpeoplecorp/tys-did-driver:latest +# ports: +# - "8143:8080" +# driver-did-plc: +# image: bnewbold/uni-resolver-driver-did-plc:latest +# ports: +# - "8144:8000" +# driver-did-evrc: +# image: viitorcloud/uni-resolver-driver-did-evrc:latest +# ports: +# - "8145:8080" +# driver-did-keri: +# image: gleif/did-keri-resolver-service:latest +# ports: +# - "8146:7678" +# driver-did-webs: +# image: gleif/did-webs-resolver-service:latest +# ports: +# - "8147:7677" diff --git a/docker/unires.env b/docker/unires.env new file mode 100644 index 0000000000000000000000000000000000000000..f49aafb184179d21bf9a8d565a924e4a00681bc3 --- /dev/null +++ b/docker/unires.env @@ -0,0 +1,58 @@ +COMPOSE_PROJECT_NAME = universal-resolver + +uniresolver_driver_did_btcr_bitcoinConnection=blockcypherapi +uniresolver_driver_did_btcr_rpcUrlMainnet=http://user:pass@localhost:8332/ +uniresolver_driver_did_btcr_rpcUrlTestnet=http://user:pass@localhost:18332/ +uniresolver_driver_did_btcr_rpcCertMainnet= +uniresolver_driver_did_btcr_rpcCertTestnet= + +uniresolver_driver_did_sov_libIndyPath= +uniresolver_driver_did_sov_poolConfigs=_;./sovrin/_.txn;staging;./sovrin/staging.txn;builder;./sovrin/builder.txn;danube;./sovrin/danube.txn;idunion;./sovrin/idunion.txn;idunion:test;./sovrin/idunion-test.txn;indicio;./sovrin/indicio.txn;indicio:test;./sovrin/indicio-test.txn;indicio:demo;./sovrin/indicio-demo.txn;bbu;./sovrin/bbu.txn +uniresolver_driver_did_sov_poolVersions=_;2;staging;2;builder;2;danube;2;idunion;2;idunion:test;2;indicio;2;indicio:test;2;indicio:demo;2;bbu;2 +uniresolver_driver_did_sov_walletNames=_;w1;staging;w2;builder;w3;danube;w4;idunion;w5;idunion:test;w6;indicio;w7;indicio:test;w8;indicio:demo;w9;bbu;w10 +uniresolver_driver_did_sov_submitterDidSeeds=_;_;staging;_;builder;_;danube;_;idunion;_;idunion:test;_;indicio;_;indicio:test;_;indicio:demo;_;bbu;_ + +uniresolver_driver_did_erc725_ethereumConnections=mainnet;hybrid;ropsten;hybrid;rinkeby;hybrid;kovan;hybrid +uniresolver_driver_did_erc725_rpcUrls=mainnet;https://mainnet.infura.io/v3/fd9e225bc1234f49b48b295c611078eb;ropsten;https://ropsten.infura.io/v3/fd9e225bc1234f49b48b295c611078eb;rinkeby;https://rinkeby.infura.io/v3/fd9e225bc1234f49b48b295c611078eb;kovan;https://kovan.infura.io/v3/fd9e225bc1234f49b48b295c611078eb +uniresolver_driver_did_erc725_etherscanApis=mainnet;http://api.etherscan.io/api;ropsten;http://api-ropsten.etherscan.io/api;rinkeby;http://api-rinkeby.etherscan.io/api;kovan;http://api-kovan.etherscan.io/api + +uniresolver_driver_did_work_apikey=sxVQUoDE015VhAs5ep4b57DFA5vT3zqvf1Dm1sGe +uniresolver_driver_did_work_domain=https://credentials.id.workday.com + +uniresolver_driver_kilt_blockchain_node=wss://spiritnet.kilt.io + +uniresolver_driver_dns_dnsServers= + +uniresolver_driver_did_echo_mainnet_rpc_url=http://127.0.0.1:8090/rpc +uniresolver_driver_did_echo_testnet_rpc_url=http://testnet.echo-dev.io +uniresolver_driver_did_echo_devnet_rpc_url=http://127.0.0.1:8090/rpc + +DID_METHOD_HOST_URL=0.0.0.0:8102 +DID_METHOD_TLS_SYSTEMCERTPOOL=true +DID_METHOD_MODE=resolver + +UNIRESOLVER_DRIVER_DID_ACE=prod + +uniresolver_driver_did_icon_node_url=https://sejong.net.solidwallet.io/api/v3 +uniresolver_driver_did_icon_score_addr=cxc7c8b0bb85eca64aecc8cc38628c4bc3c449f1fd +uniresolver_driver_did_icon_network_id=83 + +LEDGIS_LIT_ENDPOINT=https://lit.ledgis.io +LEDGIS_LIT_CODE=lit + +ORB_DRIVER_HOST_URL=0.0.0.0:8121 +ORB_DRIVER_TLS_SYSTEMCERTPOOL=true +ORB_DRIVER_VERIFY_RESOLUTION_RESULT_TYPE=all + +uniresolver_driver_did_dns_dnsServers= +uniresolver_driver_did_dns_didKeyResolver=https://dev.uniresolver.io/1.0/ + +uniresolver_driver_did_com_network=https://lcd-mainnet.commercio.network + +uniresolver_driver_did_ev_node_url=https://polygon-mumbai.g.alchemy.com/v2/jLMUummm16stzMQjW1OB79IwuDjsJqS7 +uniresolver_driver_did_ev_address_im=0x4E4f55190185f2694D331E5c9Fd70a2B75Eb4Bd2 +uniresolver_driver_did_ev_base_blocks=2700000 + +LOGGING_LEVEL_ROOT: INFO +MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE: "*" + diff --git a/openapi/tcr_openapi.yaml b/openapi/tcr_openapi.yaml index efcda1fe6694e13ded9c46a137f12146f6cfa59c..df41a502cd4621aa2f7d5e39b0c4cfbb4d0ede64 100644 --- a/openapi/tcr_openapi.yaml +++ b/openapi/tcr_openapi.yaml @@ -54,41 +54,130 @@ components: required: - code - message - VerificationRequest: + KnownServiceEndpointType: + type: string + enum: + - gx-trust-list-issuer + - gx-trust-list-schemas + - gx-trust-list-policies + - gx-trust-list-apps + - gx-trust-list-verifier + - gx-trust-list-authorities + ResolveRequest: type: object properties: issuer: description: Issuer details from the VC/VP (e.g., DID/URI) type: string - trustSchemePointer: - description: Trust Framework Pointer (e.g., example.federation1.de) - type: string + trustSchemePointers: + description: Trust Framework Pointers (e.g., example.federation1.de) + type: array + items: + type: string endpointTypes: description: Endpoint types to be consideredduring the resolving type: array items: - type: string - trustListServiceType: + type: string + trustListServiceTypes: description: ServiceType of the trust list (e.g., issuance service, verifier service) - type: string + type: array + items: + type: string required: - - issuer - - trustSchemePointer - VerificationResult: + - trustSchemePointers + ResolveTrustList: + type: object + properties: + endpoint: + description: Resolved Trust List VC endpoint + type: string + trustList: + description: Resolved Trust List + type: object + additionalProperties: + type: object + required: + - endpoint + - trustList + ResolveResult: type: object properties: - resolvedDID: + did: description: Corresponding DID mapped to Trust Framework Pointer - type: string - resolvedDoc: - description: DID Document of the DID (is is a doc URL or the whole doc content?!) type: string - trustListEndpoints: - description: Trust List VC endpoint + pointers: + description: Trust Framework Pointer (e.g., example.federation1.de) type: array items: - type: string + type: string + document: + description: DID Document of the DID + type: object + additionalProperties: + type: object + endpoints: + description: Resolved Trust List VC endpoints + type: array + items: + $ref: '#/components/schemas/ResolveTrustList' + required: + - did + - pointers + - document + - endpoints + ResolveResponse: + type: array + items: + $ref: '#/components/schemas/ResolveResult' + ValidateRequest: + type: object + properties: + issuer: + description: Issuer details from the VC/VP (e.g., DID/URI) + type: string + did: + description: DID resolved from Trust Pointer + type: string +# not clear, why do we need it.. +# document: +# description: DID Document of the DID +# type: object +# additionalProperties: +# type: object + endpoints: + description: Resolved Trust List VC endpoints + type: array + items: + type: string + required: + - issuer + - did +# - document + - endpoints + ValidateResponse: + type: object + properties: + didVerified: + description: DID verified via well-known did-configuration + type: boolean + vcVerified: + description: VC signature verified + type: boolean + issuerVerified: + description: Validation result of Issuer details (present/not present) + type: boolean + endpoints: + description: If Issuer details are found, display meta data information, public keys, schema from the trust list + type: array + items: + $ref: '#/components/schemas/ResolveTrustList' + required: + - didVerified + - vcVerified + - issuerVerified + tags: - name: TrustedContentResolver description: Trusted Content Resolver API @@ -100,25 +189,51 @@ paths: - TrustedContentResolver summary: Verification result based on issuer & trust scheme pointers description: Returns a Verification result from TCR - operationId: resolveIssuer + operationId: resolveTrustList requestBody: description: Verification params content: application/json: schema: - $ref: '#/components/schemas/VerificationRequest' + $ref: '#/components/schemas/ResolveRequest' + required: true + responses: + '200': + description: Resolved + content: + application/json: + schema: + $ref: '#/components/schemas/ResolveResponse' + '400': + $ref: '#/components/responses/ClientError' + '409': + $ref: '#/components/responses/Conflict' + '500': + $ref: '#/components/responses/ServerError' + /validate: + post: + tags: + - TrustedContentResolver + summary: Validation result based on /resolve output + description: Returns a Validation result from TCR + operationId: validateTrustList + requestBody: + description: Validation params + content: + application/json: + schema: + $ref: '#/components/schemas/ValidateRequest' required: true responses: '200': - description: Verified + description: Validated content: application/json: schema: - $ref: '#/components/schemas/VerificationResult' + $ref: '#/components/schemas/ValidateResponse' '400': $ref: '#/components/responses/ClientError' '409': $ref: '#/components/responses/Conflict' '500': $ref: '#/components/responses/ServerError' -# /validate: diff --git a/pom.xml b/pom.xml index 89b850f7b1dd708781ef2b8a7eb7ef7db7d3258e..edccd4879dd9443aea5f19a2bf9f96827ab3924a 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> - <version>3.1.2</version> + <version>3.1.5</version> <relativePath/> </parent> @@ -28,9 +28,9 @@ <url>https://repo.danubetech.com/repository/maven-public/</url> </repository> <repository> - <id>sovrin</id> - <url>https://repo.sovrin.org/repository/maven-public</url> - </repository> + <id>jitpack.io</id> + <url>https://jitpack.io</url> + </repository> </repositories> <modules> @@ -50,17 +50,16 @@ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <!-- dependencies --> - <spring.version>6.0.11</spring.version> - <spring-boot.version>3.1.2</spring-boot.version> - <spring-cloud-contract-wiremock.version>4.0.3</spring-cloud-contract-wiremock.version> - <mockwebserver.version>4.11.0</mockwebserver.version> + <spring.version>6.0.13</spring.version> + <spring-boot.version>3.1.5</spring-boot.version> <springdoc.version>2.2.0</springdoc.version> - <lombok.version>1.18.28</lombok.version> + <lombok.version>1.18.30</lombok.version> <micrometer.version>1.11.3</micrometer.version> - <vc.version>1.2.0</vc.version> - <key-format.version>1.8.0</key-format.version> - <did.version>1.4.0</did.version> - <uni-resolver.version>0.13.0</uni-resolver.version> + <vc.version>1.7.0</vc.version> + <key-format.version>1.12.0</key-format.version> + <did.version>1.11.0</did.version> + <ld-signatures.version>1.6.0</ld-signatures.version> + <uni-resolver.version>0.16.0</uni-resolver.version> <titanium.version>1.3.2</titanium.version> <caffeine.version>3.1.8</caffeine.version> <eclipse-collections.version>11.1.0</eclipse-collections.version> @@ -69,7 +68,15 @@ <dnsjava.version>3.5.2</dnsjava.version> <dnssecjava.version>2.0.0</dnssecjava.version> <okhttp.version>4.11.0</okhttp.version> - <dss-xades.version>5.12.1</dss-xades.version> + <dss.version>5.12.1</dss.version> + <java-multihash.version>v1.3.4</java-multihash.version> + <snakeyml.version>2.1</snakeyml.version> + <xmlsec.version>2.3.4</xmlsec.version> + <okio.version>3.6.0</okio.version> + <jaxb.version>2.3.1</jaxb.version> + <netty-macos.version>4.1.101.Final</netty-macos.version> + <junit-jupiter.version>5.10.0</junit-jupiter.version> + <junit-platform.version>1.10.0</junit-platform.version> <!-- plugins --> <plugin.jib.version>3.2.1</plugin.jib.version> <plugin.openapi-generator.version>6.4.0</plugin.openapi-generator.version> @@ -140,11 +147,11 @@ <artifactId>micrometer-registry-prometheus</artifactId> <version>${micrometer.version}</version> </dependency> - <!--dependency> + <dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>${caffeine.version}</version> - </dependency--> + </dependency> <dependency> <groupId>com.apicatalog</groupId> <artifactId>titanium-json-ld</artifactId> @@ -167,23 +174,14 @@ </dependency> <dependency> <groupId>decentralized-identity</groupId> - <artifactId>uni-resolver-local</artifactId> + <artifactId>uni-resolver-client</artifactId> <version>${uni-resolver.version}</version> </dependency> - <!-- <dependency> - <groupId>decentralized-identity</groupId> - <artifactId>uni-resolver-driver-did-sov</artifactId> - <version>0.7-SNAPSHOT</version> + <groupId>info.weboftrust</groupId> + <artifactId>ld-signatures-java</artifactId> + <version>${ld-signatures.version}</version> </dependency> - --> -<!-- - <dependency> - <groupId>decentralized-identity</groupId> - <artifactId>uni-resolver-driver</artifactId> - <version>${uni-resolver.version}</version> - </dependency> ---> <dependency> <groupId>dnsjava</groupId> <artifactId>dnsjava</artifactId> @@ -201,16 +199,85 @@ </dependency> <dependency> <groupId>eu.europa.ec.joinup.sd-dss</groupId> - <artifactId>dss-xades</artifactId> - <version>${dss-xades.version}</version> - </dependency> + <artifactId>dss-service</artifactId> + <version>${dss.version}</version> + </dependency> + <dependency> + <groupId>eu.europa.ec.joinup.sd-dss</groupId> + <artifactId>dss-tsl-validation</artifactId> + <version>${dss.version}</version> + </dependency> + <dependency> + <groupId>eu.europa.ec.joinup.sd-dss</groupId> + <artifactId>dss-utils-apache-commons</artifactId> + <version>${dss.version}</version> + </dependency> + <dependency> + <groupId>eu.europa.ec.joinup.sd-dss</groupId> + <artifactId>dss-validation-rest</artifactId> + <version>${dss.version}</version> + </dependency> + <dependency> + <groupId>eu.europa.ec.joinup.sd-dss</groupId> + <artifactId>dss-certificate-validation-rest</artifactId> + <version>${dss.version}</version> + </dependency> + <dependency> + <groupId>com.github.multiformats</groupId> + <artifactId>java-multihash</artifactId> + <version>${java-multihash.version}</version> + </dependency> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcpkix-jdk15on</artifactId> <version>${bcpkix.jdk15on.version}</version> </dependency> + <dependency> + <groupId>org.yaml</groupId> + <artifactId>snakeyaml</artifactId> + <version>${snakeyml.version}</version> + </dependency> + <dependency> + <groupId>org.apache.santuario</groupId> + <artifactId>xmlsec</artifactId> + <version>${xmlsec.version}</version> + </dependency> + <dependency> + <groupId>com.squareup.okio</groupId> + <artifactId>okio</artifactId> + <version>${okio.version}</version> + </dependency> + <dependency> + <groupId>javax.xml.bind</groupId> + <artifactId>jaxb-api</artifactId> + <version>${jaxb.version}</version> + </dependency> + <dependency> + <groupId>org.glassfish.jaxb</groupId> + <artifactId>jaxb-runtime</artifactId> + <version>${jaxb.version}</version> + </dependency> + <dependency> + <groupId>io.netty</groupId> + <artifactId>netty-resolver-dns-native-macos</artifactId> + <version>${netty-macos.version}</version> + <classifier>osx-aarch_64</classifier> + <scope>runtime</scope> + </dependency> <!-- Test dependencies --> + <dependency> + <groupId>org.junit.platform</groupId> + <artifactId>junit-platform-launcher</artifactId> + <version>${junit-platform.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter</artifactId> + <version>${junit-jupiter.version}</version> + <scope>test</scope> + </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> @@ -218,9 +285,9 @@ <scope>test</scope> </dependency> <dependency> - <groupId>org.bitbucket.b_c</groupId> - <artifactId>jose4j</artifactId> - <version>${jose4j.version}</version> + <groupId>com.squareup.okhttp3</groupId> + <artifactId>mockwebserver</artifactId> + <version>${okhttp.version}</version> <scope>test</scope> </dependency> </dependencies> diff --git a/service/Dockerfile b/service/Dockerfile index afde11b47d79d30fcff89362a8f4562d5a4688de..ee9c67ace18c5dc25fec93deafa45f4064cc406a 100644 --- a/service/Dockerfile +++ b/service/Dockerfile @@ -1,3 +1,3 @@ -FROM openjdk:17 +FROM bellsoft/liberica-openjdk-alpine:21 COPY /target/trusted-content-resolver-service-*.jar trusted-content-resolver-service.jar ENTRYPOINT ["java", "-jar","/trusted-content-resolver-service.jar"] diff --git a/service/pom.xml b/service/pom.xml index 83c22a091f62cb182ed7df6aa8968b8a6dd5f7b3..00672464f0dc9daced3d8d9414d997df9754486d 100644 --- a/service/pom.xml +++ b/service/pom.xml @@ -17,10 +17,6 @@ <description>Eclipse XFSC TRAIN Trust Content Resolver Server Application</description> <dependencies> - <dependency> - <groupId>ch.qos.logback</groupId> - <artifactId>logback-classic</artifactId> - </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> @@ -33,10 +29,12 @@ <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> - <!--dependency> + <!-- + <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> - </dependency--> + </dependency> + --> <dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> @@ -57,23 +55,27 @@ <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-tracing-bridge-otel</artifactId> - </dependency> + </dependency> + <dependency> + <groupId>com.github.ben-manes.caffeine</groupId> + <artifactId>caffeine</artifactId> + </dependency> + <dependency> + <groupId>info.weboftrust</groupId> + <artifactId>ld-signatures-java</artifactId> + </dependency> <dependency> <groupId>decentralized-identity</groupId> - <artifactId>uni-resolver-local</artifactId> + <artifactId>uni-resolver-client</artifactId> + </dependency> + <dependency> + <groupId>com.danubetech</groupId> + <artifactId>verifiable-credentials-java</artifactId> </dependency> - <!-- - <dependency> - <groupId>decentralized-identity</groupId> - <artifactId>uni-resolver-driver-did-sov</artifactId> - <version>0.4-SNAPSHOT</version> - <scope>compile</scope> - </dependency> - --> <dependency> <groupId>dnsjava</groupId> <artifactId>dnsjava</artifactId> - </dependency> + </dependency> <dependency> <groupId>org.jitsi</groupId> <artifactId>dnssecjava</artifactId> @@ -84,26 +86,58 @@ </dependency> <dependency> <groupId>eu.europa.ec.joinup.sd-dss</groupId> - <artifactId>dss-xades</artifactId> - </dependency> + <artifactId>dss-service</artifactId> + </dependency> + <dependency> + <groupId>eu.europa.ec.joinup.sd-dss</groupId> + <artifactId>dss-tsl-validation</artifactId> + </dependency> + <dependency> + <groupId>eu.europa.ec.joinup.sd-dss</groupId> + <artifactId>dss-utils-apache-commons</artifactId> + </dependency> + <dependency> + <groupId>eu.europa.ec.joinup.sd-dss</groupId> + <artifactId>dss-validation-rest</artifactId> + </dependency> + <dependency> + <groupId>eu.europa.ec.joinup.sd-dss</groupId> + <artifactId>dss-certificate-validation-rest</artifactId> + </dependency> + <dependency> + <groupId>com.github.multiformats</groupId> + <artifactId>java-multihash</artifactId> + </dependency> + <dependency> + <groupId>javax.xml.bind</groupId> + <artifactId>jaxb-api</artifactId> + </dependency> + <dependency> + <groupId>org.glassfish.jaxb</groupId> + <artifactId>jaxb-runtime</artifactId> + </dependency> <dependency> - <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-starter-test</artifactId> + <groupId>org.junit.platform</groupId> + <artifactId>junit-platform-launcher</artifactId> <scope>test</scope> </dependency> - <!-- <dependency> - <groupId>org.bouncycastle</groupId> - <artifactId>bcpkix-jdk15on</artifactId> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter</artifactId> <scope>test</scope> </dependency> <dependency> - <groupId>org.bitbucket.b_c</groupId> - <artifactId>jose4j</artifactId> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> + <exclusions> + <exclusion> + <groupId>com.vaadin.external.google</groupId> + <artifactId>android-json</artifactId> + </exclusion> + </exclusions> </dependency> - --> </dependencies> <build> diff --git a/service/src/main/java/eu/xfsc/train/tcr/server/config/ResolverConfig.java b/service/src/main/java/eu/xfsc/train/tcr/server/config/ResolverConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..6927bdceb7f3e5b3cffb570341846469da3dffc9 --- /dev/null +++ b/service/src/main/java/eu/xfsc/train/tcr/server/config/ResolverConfig.java @@ -0,0 +1,115 @@ +package eu.xfsc.train.tcr.server.config; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.UnknownHostException; +import java.time.Duration; +import java.util.List; + +import org.jitsi.dnssec.validator.ValidatingResolver; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.xbill.DNS.ExtendedResolver; +import org.xbill.DNS.SimpleResolver; + +import com.apicatalog.jsonld.loader.DocumentLoader; + +import org.xbill.DNS.Resolver; + +import eu.xfsc.train.tcr.server.exception.DidException; +import eu.xfsc.train.tcr.server.exception.DnsException; +import foundation.identity.jsonld.ConfigurableDocumentLoader; +import lombok.extern.slf4j.Slf4j; +import uniresolver.UniResolver; +import uniresolver.client.ClientUniResolver; + +@Slf4j +@Configuration +public class ResolverConfig { + + private final static String DNSROOT = ". IN DNSKEY 257 3 8 AwEAAaz/tAm8yTn4Mfeh5eyI96WSVexTBAvkMgJzkKTOiW1vkIbzxeF3+/4RgWOq7HrxRixHlFlExOLAJr5emLvN7SWXgnLh4+B5xQlNVz8Og8kvArMtNROxVQuCaSnIDdD5LKyWbRd2n9WGe2R8PzgCmr3EgVLrjyBxWezF0jLHwVN8efS3rCj/EWgvIWgb9tarpVUDK/b58Da+sqqls3eNbuv7pr+eoZG+SrDK6nWeL3c6H5Apxz7LjVc1uTIdsIXxuOLYA4/ilBmSVIzuDWfdRUfhHdY6+cn8HFRm+2hM8AnXGXws9555KrUB5qihylGa8subX2Nn6UwNR1AkUTV74bU="; + + @Bean + public UniResolver uniResolver(@Value("${tcr.did.base-uri}") String baseUri, + @Value("${tcr.did.timeout}") int timeout) { + log.info("uniResolver.enter; configured base URI: {}, timeout: {}", baseUri, timeout); + UniResolver resolver; + URI uri; + try { + uri = new URI(baseUri); + } catch (URISyntaxException ex) { + log.error("uniResolver.error", ex); + throw new DidException(ex); + } + resolver = ClientUniResolver.create(uri); + log.info("uniResolver.exit; returning resolver: {}", resolver); + return resolver; + } + + @Bean + public Resolver resolver(@Value("${tcr.dns.hosts}") List<String> dnsHosts, + @Value("${tcr.dns.timeout}") int timeout, + @Value("${tcr.dns.dnssec.rootPath}") String dnssecRootPath, + @Value("${tcr.dns.dnssec.enabled}") boolean dnssecEnabled) { + log.info("resolver.enter; configured DNS hosts: {}, timeout: {}, dnssecRootPath: {}, dnssecEnaabled: {}", dnsHosts, timeout, dnssecRootPath, dnssecEnabled); + Resolver resolver; + try { + if (dnsHosts.isEmpty()) { + resolver = new SimpleResolver(); + } else { + SimpleResolver[] resolvers = new SimpleResolver[dnsHosts.size()]; + int idx = 0; + for (String dnsHost : dnsHosts) { + SimpleResolver r = new SimpleResolver(dnsHost); + if (timeout > 0) { + r.setTimeout(Duration.ofMillis(timeout)); + } + resolvers[idx++] = r; + } + resolver = new ExtendedResolver(resolvers); + } + if (timeout > 0) { + resolver.setTimeout(Duration.ofMillis(timeout)); + } + + if (dnssecEnabled) { + InputStream rootInputStream; + File f = new File(dnssecRootPath); + if (f.exists()) { + rootInputStream = new FileInputStream(dnssecRootPath); + } else { + rootInputStream = getClass().getClassLoader().getResourceAsStream(dnssecRootPath); + if (rootInputStream == null) { + log.info("resolve; DNSSEC Root-key not found, using hardcoded backup."); + rootInputStream = new ByteArrayInputStream(DNSROOT.getBytes()); + } + } + + ValidatingResolver validatingResolver = new ValidatingResolver(resolver); + validatingResolver.loadTrustAnchors(rootInputStream); + resolver = validatingResolver; + } + } catch (IOException ex) { + log.error("resolver.error", ex); + throw new DnsException(ex); + } + log.info("resolver.exit; returning resolver: {}", resolver); + return resolver; + } + + @Bean + public DocumentLoader documentLoader() { + ConfigurableDocumentLoader loader = new ConfigurableDocumentLoader(); + // TODO: get settings from app props + loader.setEnableHttp(true); + loader.setEnableHttps(true); + return loader; + } +} + diff --git a/service/src/main/java/eu/xfsc/train/tcr/server/exception/ClientException.java b/service/src/main/java/eu/xfsc/train/tcr/server/exception/ClientException.java new file mode 100644 index 0000000000000000000000000000000000000000..0fe0d2e26dde410322bb4a82f6cf8ab9b50e21dd --- /dev/null +++ b/service/src/main/java/eu/xfsc/train/tcr/server/exception/ClientException.java @@ -0,0 +1,17 @@ +package eu.xfsc.train.tcr.server.exception; + +public class ClientException extends TrainException { + + public ClientException(String message) { + super(message); + } + + public ClientException(Throwable cause) { + super(cause); + } + + public ClientException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/service/src/main/java/eu/xfsc/train/tcr/server/exception/DNSException.java b/service/src/main/java/eu/xfsc/train/tcr/server/exception/DNSException.java deleted file mode 100644 index 67670431bf5abfb9072c6a4cd192020898191030..0000000000000000000000000000000000000000 --- a/service/src/main/java/eu/xfsc/train/tcr/server/exception/DNSException.java +++ /dev/null @@ -1,9 +0,0 @@ -package eu.xfsc.train.tcr.server.exception; - -public class DNSException extends Exception { - - public DNSException(String s) { - super(s); - } -} - diff --git a/service/src/main/java/eu/xfsc/train/tcr/server/exception/DidException.java b/service/src/main/java/eu/xfsc/train/tcr/server/exception/DidException.java new file mode 100644 index 0000000000000000000000000000000000000000..d270184ec575b08abec2ed47970698338304f2a0 --- /dev/null +++ b/service/src/main/java/eu/xfsc/train/tcr/server/exception/DidException.java @@ -0,0 +1,17 @@ +package eu.xfsc.train.tcr.server.exception; + +public class DidException extends TrainException { + + public DidException(String message) { + super(message); + } + + public DidException(Throwable cause) { + super(cause); + } + + public DidException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/service/src/main/java/eu/xfsc/train/tcr/server/exception/DnsException.java b/service/src/main/java/eu/xfsc/train/tcr/server/exception/DnsException.java new file mode 100644 index 0000000000000000000000000000000000000000..954107f8060f240375239ef6b42a4973c41cd817 --- /dev/null +++ b/service/src/main/java/eu/xfsc/train/tcr/server/exception/DnsException.java @@ -0,0 +1,17 @@ +package eu.xfsc.train.tcr.server.exception; + +public class DnsException extends TrainException { + + public DnsException(String message) { + super(message); + } + + public DnsException(Throwable cause) { + super(cause); + } + + public DnsException(String message, Throwable cause) { + super(message, cause); + } +} + diff --git a/service/src/main/java/eu/xfsc/train/tcr/server/exception/RestExceptionHandler.java b/service/src/main/java/eu/xfsc/train/tcr/server/exception/RestExceptionHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..d263ffc1bc34f2c7f49c4a9899c5795ed19a4be8 --- /dev/null +++ b/service/src/main/java/eu/xfsc/train/tcr/server/exception/RestExceptionHandler.java @@ -0,0 +1,98 @@ +package eu.xfsc.train.tcr.server.exception; + +import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.GATEWAY_TIMEOUT; +import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; +import static org.springframework.http.HttpStatus.NOT_IMPLEMENTED; +import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +import eu.xfsc.train.tcr.api.generated.model.Error; +import jakarta.validation.ConstraintViolationException; +import lombok.extern.slf4j.Slf4j; + +/** + * RestExceptionHandler translates RestExceptions to error responses according to the status that is set in + * the application exception. Response content format: {"code" : "ExceptionType", "message" : "some exception message"} + * Implementation of the {@link ResponseEntityExceptionHandler} exception. + */ +@Slf4j +@ControllerAdvice +public class RestExceptionHandler extends ResponseEntityExceptionHandler { + /** + * Method handles the Client Exception. + * + * @param exception Thrown Client Exception. + * @return The custom Federated Catalogue application error with status code 400. + */ + @ExceptionHandler({ClientException.class}) + protected ResponseEntity<Error> handleBadRequestException(ClientException exception) { + log.info("handleBadRequestException; Bad Request error: {}", exception.getMessage()); + return new ResponseEntity<>(new Error("client_error", exception.getMessage()), BAD_REQUEST); + } + + /** + * Method handles the Server Exception. + * + * @param exception Thrown Server Exception. + * @return The custom Federated Catalogue application error with status code 500. + */ + @ExceptionHandler({TrainException.class}) + protected ResponseEntity<Error> handleServerException(TrainException exception) { + log.info("handleServerException; Server error: {}", exception.getMessage()); + return new ResponseEntity<>(new Error("server_error", exception.getMessage()), INTERNAL_SERVER_ERROR); + } + + /** + * Method handles the Verification Exception. + * + * @param exception Thrown Server Exception. + * @return The custom Federated Catalogue application error with status code 422. + */ + @ExceptionHandler({DidException.class}) + protected ResponseEntity<Error> handleVerificationException(DidException exception) { + log.info("handleVerificationException; Verification error: {}", exception.getMessage()); + return new ResponseEntity<>(new Error("verification_error", exception.getMessage()), UNPROCESSABLE_ENTITY); + } + + /** + * Method handles the UnsupportedOperation Exception. + * + * @param exception Thrown Server Exception. + * @return The custom Federated Catalogue application error with status code 501. + */ + @ExceptionHandler({UnsupportedOperationException.class}) + protected ResponseEntity<Error> handleUnsupportedOperationException(UnsupportedOperationException exception) { + log.info("handleUnsupportedOperationException; Unsupported Operation error: {}", exception.getMessage()); + return new ResponseEntity<>(new Error("processing_error", exception.getMessage()), NOT_IMPLEMENTED); + } + + /** + * Method handles the Timeout Exception. + * + * @param exception Thrown Server Exception. + * @return The custom Federated Catalogue application error with status code 504. + */ + @ExceptionHandler({DnsException.class}) + protected ResponseEntity<Error> handleTimeoutException(DnsException exception) { + log.info("handleTimeoutException; Tiomeout error: {}", exception.getMessage()); + return new ResponseEntity<>(new Error("timeout_error", exception.getMessage()), GATEWAY_TIMEOUT); + } + + + /** + * Method handles the constraintViolationException Exception. + * + * @param exception Thrown Server Exception. + * @return The custom Federated Catalogue application error with status code 400. + */ + @ExceptionHandler({ConstraintViolationException.class}) + protected ResponseEntity<Error> constraintViolationException(ConstraintViolationException exception) { + log.info("constraintViolationException; Constraint Violation error: {}", exception.getMessage()); + return new ResponseEntity<>(new Error("constraint_violation_error", exception.getMessage()), BAD_REQUEST); + } +} \ No newline at end of file diff --git a/service/src/main/java/eu/xfsc/train/tcr/server/exception/TrainException.java b/service/src/main/java/eu/xfsc/train/tcr/server/exception/TrainException.java new file mode 100644 index 0000000000000000000000000000000000000000..b8ad53264cd855f63bd099d3794e67321c44f863 --- /dev/null +++ b/service/src/main/java/eu/xfsc/train/tcr/server/exception/TrainException.java @@ -0,0 +1,37 @@ +package eu.xfsc.train.tcr.server.exception; + +/** + * ServiceException is the main exception that can be thrown during the operations of Federated Catalogue + * server application. + * Implementation of the {@link RuntimeException} exception. + */ +public class TrainException extends RuntimeException { + /** + * Constructs a new Train Exception with the specified detail message. + * + * @param message Detailed message about the thrown exception. + */ + public TrainException(String message) { + super(message); + } + + /** + * Constructs a new Train exception with the specified cause. + * + * @param cause Case of the thrown exception. (A null value is permitted, and indicates that the cause is unknown.) + */ + public TrainException(Throwable cause) { + super(cause); + } + + /** + * Constructs a new Train exception with the specified detail message and cause. + * + * @param message Detailed message about the thrown exception. + * @param cause Cause of the thrown exception. (A null value is permitted, and indicates that the cause is unknown.) + */ + public TrainException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/service/src/main/java/eu/xfsc/train/tcr/server/legacy/DANETrustManager.java b/service/src/main/java/eu/xfsc/train/tcr/server/legacy/DANETrustManager.java index 36433e22de61bccda5e314ef47f0ba6930d8cfa9..cb18697f801fa38544b66eea75a90fba7503c53e 100644 --- a/service/src/main/java/eu/xfsc/train/tcr/server/legacy/DANETrustManager.java +++ b/service/src/main/java/eu/xfsc/train/tcr/server/legacy/DANETrustManager.java @@ -3,7 +3,7 @@ package eu.xfsc.train.tcr.server.legacy; import org.xbill.DNS.TLSARecord; import org.xbill.DNS.utils.base16; -import eu.xfsc.train.tcr.server.exception.DNSException; +import eu.xfsc.train.tcr.server.exception.DnsException; import lombok.extern.slf4j.Slf4j; import javax.net.ssl.X509TrustManager; @@ -43,7 +43,7 @@ public class DANETrustManager implements X509TrustManager { try { this.initDANE(); - } catch (IOException | DNSException e) { + } catch (IOException | DnsException e) { throw new CertificateException(e); } @@ -131,7 +131,7 @@ public class DANETrustManager implements X509TrustManager { this.host = host; } - private void initDANE() throws IOException, DNSException { + private void initDANE() throws IOException, DnsException { String host = DANETrustManager.DANE_PREFIX + (this.host.endsWith(".") ? this.host : this.host + "."); log.debug("initDANE; Looking up TLSA record(s) for {}", host); diff --git a/service/src/main/java/eu/xfsc/train/tcr/server/legacy/DNSHelper.java b/service/src/main/java/eu/xfsc/train/tcr/server/legacy/DNSHelper.java index 5a7b18c9524fbd17d8c9fc1f862ce7d066fba335..7402645977fb7dca6d42cd5f48edeb4eb641f53c 100644 --- a/service/src/main/java/eu/xfsc/train/tcr/server/legacy/DNSHelper.java +++ b/service/src/main/java/eu/xfsc/train/tcr/server/legacy/DNSHelper.java @@ -8,7 +8,7 @@ import org.jitsi.dnssec.validator.ValidatingResolver; import org.xbill.DNS.*; import org.xbill.DNS.Record; -import eu.xfsc.train.tcr.server.exception.DNSException; +import eu.xfsc.train.tcr.server.exception.DnsException; import lombok.extern.slf4j.Slf4j; import java.io.*; @@ -97,13 +97,13 @@ public class DNSHelper { } - public Stream<Record> parseMessage(Message response) throws IOException, DNSException { + public Stream<Record> parseMessage(Message response) throws IOException, DnsException { List<RRset> rrSets = response.getSectionRRsets(Section.ANSWER); return rrSets.stream().flatMap(s -> s.rrs().stream()); } - public List<String> queryTXT(String host) throws IOException, DNSException { + public List<String> queryTXT(String host) throws IOException, DnsException { Message response = query(host, Type.TXT); Stream<Record> records = parseMessage(response); @@ -112,7 +112,7 @@ public class DNSHelper { .toList(); } - public List<String> queryPTR(String host) throws IOException, DNSException { + public List<String> queryPTR(String host) throws IOException, DnsException { Message response = query(host, Type.PTR); Stream<Record> records = parseMessage(response); @@ -121,7 +121,7 @@ public class DNSHelper { .toList(); } - public List<String> queryURI(String host) throws IOException, DNSException { + public List<String> queryURI(String host) throws IOException, DnsException { Message response = query(host, Type.URI); Stream<Record> records = parseMessage(response); @@ -130,7 +130,7 @@ public class DNSHelper { .toList(); } - public List<SMIMEAcert> querySMIMEA(String host) throws IOException, DNSException { + public List<SMIMEAcert> querySMIMEA(String host) throws IOException, DnsException { // https://tools.ietf.org/html/rfc6698#section-2 Message response = query(host, Type.SMIMEA); @@ -141,7 +141,7 @@ public class DNSHelper { } - public Message query(String host, int type) throws IOException, DNSException { + public Message query(String host, int type) throws IOException, DnsException { if (!host.endsWith(".")) { host = host + "."; } @@ -164,13 +164,13 @@ public class DNSHelper { log.debug("query; Reason: {}", ((TXTRecord) set.first()).getStrings().get(0)); } } - throw new DNSException("RCode: " + rcode + " (" + Rcode.string(rcode) + ")"); + throw new DnsException("RCode: " + rcode + " (" + Rcode.string(rcode) + ")"); } } return response; } - public <R extends Record> List<R> queryAndParse(String host, Class recordTypeClass, int recordTypeID) throws IOException, DNSException { + public <R extends Record> List<R> queryAndParse(String host, Class recordTypeClass, int recordTypeID) throws IOException, DnsException { Message response = query(host, recordTypeID); List<RRset> rrSets = response.getSectionRRsets(Section.ANSWER); diff --git a/service/src/main/java/eu/xfsc/train/tcr/server/legacy/SMIMEAHelper.java b/service/src/main/java/eu/xfsc/train/tcr/server/legacy/SMIMEAHelper.java index 0997f9cb730547c5385bad89ff304023c88cfbb6..913fe332fb54c4c65b1be1bcc7bef78e0238c525 100644 --- a/service/src/main/java/eu/xfsc/train/tcr/server/legacy/SMIMEAHelper.java +++ b/service/src/main/java/eu/xfsc/train/tcr/server/legacy/SMIMEAHelper.java @@ -6,7 +6,7 @@ import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.List; -import eu.xfsc.train.tcr.server.exception.DNSException; +import eu.xfsc.train.tcr.server.exception.DnsException; import lombok.extern.slf4j.Slf4j; @@ -37,7 +37,7 @@ public class SMIMEAHelper { try { smimeas = dns.querySMIMEA(hostname); log.debug("verifyXMLdocument; Found {} SMIMEA record(s) for this Document", smimeas.size()); - } catch (IOException | DNSException e) { + } catch (IOException | DnsException e) { log.info("verifyXMLdocument.exit; Loading of SMIMEA failed.", e); return false; } diff --git a/service/src/main/java/eu/xfsc/train/tcr/server/legacy/TrustSchemeFactory.java b/service/src/main/java/eu/xfsc/train/tcr/server/legacy/TrustSchemeFactory.java index 425deeadd660d72eaeff9e5d1396597517c89500..8027c12c1c28eaae0a0c71a05130f2279e5636d9 100644 --- a/service/src/main/java/eu/xfsc/train/tcr/server/legacy/TrustSchemeFactory.java +++ b/service/src/main/java/eu/xfsc/train/tcr/server/legacy/TrustSchemeFactory.java @@ -12,10 +12,9 @@ import java.util.List; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; -import eu.xfsc.train.tcr.server.exception.DNSException; +import eu.xfsc.train.tcr.server.exception.DnsException; @Slf4j -@Component public class TrustSchemeFactory { @Value("${tcr.dns.verification.enabled}") @@ -27,7 +26,7 @@ public class TrustSchemeFactory { private SMIMEAHelper smimea = new SMIMEAHelper(dns); - public Collection<TrustScheme> createTrustSchemes(TrustSchemeClaim claim) throws IOException, DNSException { + public Collection<TrustScheme> createTrustSchemes(TrustSchemeClaim claim) throws IOException, DnsException { Collection<TrustScheme> schemes = new ArrayList<>(); List<String> schemeHostNames = discoverTrustSchemes(claim); @@ -73,7 +72,7 @@ public class TrustSchemeFactory { List<String> schemes; try { schemes = dns.queryPTR(hostname); - } catch (IOException | DNSException e) { + } catch (IOException | DnsException e) { log.warn("discoverTrustScheme.error", e); schemes = Collections.emptyList(); } @@ -95,7 +94,7 @@ public class TrustSchemeFactory { List<String> lists; try { lists = dns.queryURI(schemeHostName); - } catch (IOException | DNSException e) { + } catch (IOException | DnsException e) { log.warn("discoverTrustLists.error", e); lists = Collections.emptyList(); } diff --git a/service/src/main/java/eu/xfsc/train/tcr/server/service/DIDResolver.java b/service/src/main/java/eu/xfsc/train/tcr/server/service/DIDResolver.java index 3d7f862a8446436e0b99fae285699f77c02dac4b..0a713ff0af631d6679000f6bb9b2ac4e495d15d8 100644 --- a/service/src/main/java/eu/xfsc/train/tcr/server/service/DIDResolver.java +++ b/service/src/main/java/eu/xfsc/train/tcr/server/service/DIDResolver.java @@ -1,34 +1,417 @@ package eu.xfsc.train.tcr.server.service; -import java.util.HashMap; +import java.net.URI; +import java.net.URISyntaxException; +import java.text.ParseException; +import java.util.List; import java.util.Map; +import java.util.stream.Stream; -//import uniresolver.driver.did.sov.DidSovDriver; -import uniresolver.local.LocalUniResolver; -import uniresolver.result.ResolveResult; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import com.apicatalog.jsonld.JsonLdError; +import com.apicatalog.jsonld.document.JsonDocument; +import com.apicatalog.jsonld.loader.DocumentLoader; +import com.apicatalog.jsonld.loader.DocumentLoaderOptions; +import com.danubetech.keyformats.crypto.PublicKeyVerifier; +import com.danubetech.keyformats.crypto.PublicKeyVerifierFactory; +import com.danubetech.keyformats.jose.JWK; +import com.danubetech.keyformats.jose.JWSAlgorithm; +import com.danubetech.keyformats.jose.KeyTypeName; +import com.danubetech.verifiablecredentials.VerifiableCredential; +import com.nimbusds.jwt.JWT; +import com.nimbusds.jwt.JWTParser; +import eu.xfsc.train.tcr.server.exception.DidException; +import foundation.identity.did.DIDDocument; +import foundation.identity.did.Service; +import foundation.identity.did.VerificationMethod; +import foundation.identity.jsonld.JsonLDObject; +import foundation.identity.jsonld.JsonLDUtils; +import info.weboftrust.ldsignatures.LdProof; +import info.weboftrust.ldsignatures.verifier.LdVerifier; +import info.weboftrust.ldsignatures.verifier.LdVerifierRegistry; +import jakarta.json.JsonArray; +import jakarta.json.JsonObject; +import jakarta.json.JsonValue; +import jakarta.json.JsonValue.ValueType; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; +import uniresolver.ResolutionException; +import uniresolver.UniResolver; +import uniresolver.result.ResolveRepresentationResult; + +@Slf4j +@Component public class DIDResolver { + private static final Map<String, Object> RESOLVE_OPTIONS = Map.of("accept", "application/did+ld+json"); + + @Autowired + private UniResolver resolver; + @Autowired + private DocumentLoader docLoader; - private LocalUniResolver resolver; - public void init() { - resolver = new LocalUniResolver(); - //resolver.getDrivers().add(new DidSovDriver()); - //resolver.getDriver(DidSovDriver.class).setLibIndyPath("./sovrin/lib/libindy.so"); - //resolver.getDriver(DidSovDriver.class).setPoolConfigs("_;./sovrin/mainnet.txn"); - //resolver.getDriver(DidSovDriver.class).setPoolVersions("_;2"); + private DIDDocument resolveDidDocument(String did) { + log.debug("resolveDidDocument.enter; got did to resolve: {}", did); + try { + ResolveRepresentationResult didResult = resolver.resolveRepresentation(did, RESOLVE_OPTIONS); + log.debug("resolveDid; resolved to: {}", didResult.toJson()); + if (didResult.isErrorResult()) { + throw new DidException(didResult.getErrorMessage()); + } - Map<String, Object> resolveOptions = new HashMap<>(); - resolveOptions.put("accept", "application/did+ld+json"); + String docStream = didResult.getDidDocumentStreamAsString(); + log.debug("resolveDidDocument; doc stream is: {}", docStream); + DIDDocument diDoc = DIDDocument.fromJson(docStream); + log.debug("resolveDidDocument.exit; returning doc: {}", diDoc); + return diDoc; + } catch (ResolutionException ex) { + log.warn("resolveDidDocument.error;", ex); + throw new DidException(ex); + } + } + + private Stream<String> resolveEndpoint(Object ep) { + if (ep instanceof String) { + return Stream.of(ep.toString()); + } + if (ep instanceof List) { + Stream<Stream<String>> sss = ((List) ep).stream().map(e -> resolveEndpoint(e)); + return sss.reduce(Stream.of(), (a, b) -> Stream.concat(a, b)); + } + // else ep is a Map + Map<String, Object> eps = (Map<String, Object>) ep; + ep = eps.entrySet().iterator().next().getValue(); + return resolveEndpoint(ep); + } + + public DIDResolveResult resolveDid(String did, List<String> types) { + log.debug("resolveDid.enter; got did: {}, types; {}", did, types); + Stream<Service> services; + DIDDocument diDoc = resolveDidDocument(did); + if (types == null || types.isEmpty()) { + services = diDoc.getServices().stream(); + } else { + services = diDoc.getServices().stream().filter(s -> { + if (s.getTypes() != null && !s.getTypes().isEmpty()) { + return types.stream().anyMatch(t -> s.getTypes().contains(t)); + } + return types.contains(s.getType()); + }); + } + List<String> endpoints = services + .flatMap(s -> resolveEndpoint(s.getServiceEndpoint())).toList(); + log.debug("resolveDid; got endpoints: {}", endpoints); + String origin = null; + if (!endpoints.isEmpty()) { + try { + URI uri = new URI(endpoints.get(0)); + origin = uri.getScheme() + "://" + uri.getHost(); + } catch (URISyntaxException ex) { + log.warn("resolveDid; error constructing origin: {}", ex.getMessage()); + } + } + return new DIDResolveResult(diDoc.getJsonObject(), endpoints, origin); + } + + public boolean resolveDidConfig(String origin) { + log.debug("resolveDidConfig.enter; got origin: {}", origin); + String configUri = origin + "/.well-known/did-configuration"; + String configUrl = configUri + ".json"; + JsonDocument doc; + try { + doc = (JsonDocument) docLoader.loadDocument(JsonLDUtils.stringToUri(configUrl), new DocumentLoaderOptions()); + log.debug("resolveDidConfig; got document: {}", doc); + } catch (JsonLdError ex) { + log.error("resolveDidConfig.error", ex); + throw new DidException(ex); + } + + if (doc.getJsonContent().isEmpty()) { + throw new DidException("empty did-configuration at " + configUrl); + } + JsonObject json = doc.getJsonContent().get().asJsonObject(); + String context = json.getString("@context"); + if (context == null) { + throw new DidException("did-configuration missing @context"); + } + //if (!context.startsWith(configUri)) { + // throw new DidException("wrong @context: " + context); + //} + JsonArray dids = json.getJsonArray("linked_dids"); + if (dids == null) { + throw new DidException("did-configuration missing linked-dids"); + } + if (json.keySet().size() > 2) { + throw new DidException("did-configuration contains unexpected members: " + json.keySet()); + } + + int passed = 0; + String did = null; + String alg = null; + JsonObject dataProof = null; + for (JsonValue linkedDid: dids) { + if (linkedDid.getValueType() == ValueType.OBJECT) { + String local = resolveLinkedData(linkedDid.asJsonObject(), origin); + if (local != null) { + if (did == null) { + did = local; + dataProof = linkedDid.asJsonObject(); + } else { + log.debug("resolveDidConfig; additional did resolved: {}; initial: {}", local, did); + } + passed++; + } + } else if (linkedDid.getValueType() == ValueType.STRING) { + String local = resolveLinkedJWT(linkedDid.toString(), origin); + if (local != null) { + if (alg == null) { + alg = local; + } else { + log.debug("resolveDidConfig; additional alg resolved: {}; initial: {}", local, alg); + } + passed++; + } + } else { + log.debug("resolveDidConfig; unexpected linked-did type: {}; {}", linkedDid.getValueType(), linkedDid); + } + } + log.debug("resolveDidConfig.exit; resolved: {} DIDs, out of: {}", passed, dids.size()); + + boolean resolved = false; + if (did != null) { + if (alg == null) { + alg = getAlgFromProof(dataProof); + } + JsonLDObject payload = JsonLDObject.fromJson(dataProof.toString()); + resolved = verifyVCSignature(payload, did, alg); + } + log.debug("resolveDidConfig.exit; returning: {}; alg: {}, did: {}", resolved, alg, did); + return resolved; + } + + private String resolveLinkedData(JsonObject linkedData, String origin) { + if (linkedData.containsKey("id")) { + log.debug("resolveLinkedData; unexpected id member"); + return null; + } + if (!linkedData.containsKey("issuanceDate")) { + log.debug("resolveLinkedData; absent issuanceDate member"); + return null; + } + if (!linkedData.containsKey("expirationDate")) { + log.debug("resolveLinkedData; absent expirationDate member"); + return null; + } + JsonObject credSubj = linkedData.getJsonObject("credentialSubject"); + if (credSubj == null) { + log.debug("resolveLinkedData; absent credentialSubject member"); + return null; + } + String did = credSubj.getString("id"); + if (did == null) { + log.debug("resolveLinkedData; absent credentialSubject.id member"); + return null; + } + if (!did.startsWith("did:")) { + log.debug("resolveLinkedData; unexpected credentialSubject.id value: {}", did); + return null; + } + String subOrigin = credSubj.getString("origin"); + if (subOrigin == null) { + log.debug("resolveLinkedData; absent credentialSubject.origin member"); + return null; + } + //if (!subOrigin.equals(origin)) { + // log.debug("resolveLinkedData; unexpected credentialSubject.origin value: {}", subOrigin); + // return null; + //} + return did; + } + + private String resolveLinkedJWT(String linkedJWT, String origin) { + JWT jwt; + try { + jwt = JWTParser.parse(linkedJWT); + } catch (ParseException ex) { + log.debug("resolveLinkedJWT; error parsing token: {}", ex.getMessage()); + return null; + } + + if (!jwt.getHeader().getIncludedParams().contains("alg")) { + log.debug("resolveLinkedJWT; absent 'alg' member in header"); + return null; + } + if (!jwt.getHeader().getIncludedParams().contains("kid")) { + log.debug("resolveLinkedJWT; absent 'kid' member in header"); + return null; + } + if (jwt.getHeader().getIncludedParams().size() > 2) { + log.debug("resolveLinkedJWT; unexpected header members: {}", jwt.getHeader().getIncludedParams()); + return null; + } + + try { + Map<String, Object> vc = jwt.getJWTClaimsSet().getJSONObjectClaim("vc"); + Map<String, Object> credSubj = (Map<String, Object>) vc.get("credentialSubject"); + if (credSubj == null) { + log.debug("resolveLinkedJWT; absent credentialSubject member"); + return null; + } + String did = (String) credSubj.get("id"); + if (did == null) { + log.debug("resolveLinkedJWT; absent credentialSubject.id member"); + return null; + } + if (!did.equals(jwt.getJWTClaimsSet().getIssuer())) { + log.debug("resolveLinkedJWT; unexpected iss/did values: {}/{}", jwt.getJWTClaimsSet().getIssuer(), did); + return null; + } + if (!did.equals(jwt.getJWTClaimsSet().getSubject())) { + log.debug("resolveLinkedJWT; unexpected sub/did values: {}/{}", jwt.getJWTClaimsSet().getSubject(), did); + return null; + } + String subOrigin = (String) credSubj.get("origin"); + if (subOrigin == null) { + log.debug("resolveLinkedJWT; absent credentialSubject.origin member"); + return null; + } + if (!origin.endsWith(subOrigin)) { + log.debug("resolveLinkedData; unexpected credentialSubject.origin value: {}", subOrigin); + return null; + } + } catch (ParseException ex) { + log.debug("resolveLinkedJWT; error parsing VC: {}", ex.getMessage()); + return null; + } + return jwt.getHeader().getAlgorithm().getName(); + } + + private boolean verifyVCSignature(JsonLDObject payload, String did, String alg) { + DIDDocument diDoc = resolveDidDocument(did); + List<VerificationMethod> vrMethods = diDoc.getAssertionMethodVerificationMethodsDereferenced(); + payload.setDocumentLoader(docLoader); + LdProof proof = LdProof.fromJsonObject((Map<String, Object>) payload.getJsonObject().get("proof")); + log.debug("verifyVCSignature; payload: {}, proof: {}", payload, proof); + boolean verified = vrMethods.stream().anyMatch(vm -> { + log.debug("verifyVCSignature; veryfying with: {}", vm); + try { + JWK jwkPublic = JWK.fromMap(vm.getPublicKeyJwk()); + LdVerifier<?> verifier = LdVerifierRegistry.getLdVerifierBySignatureSuiteTerm(proof.getType()); + PublicKeyVerifier<?> pkVerifier = PublicKeyVerifierFactory.publicKeyVerifierForJWK(jwkPublic, alg); + verifier.setVerifier(pkVerifier); + if (verifier.verify(payload, proof)) { + return true; + } + log.debug("verifyVCSignature; payload not verified; suite: {}", proof.getType()); + } catch (Exception ex) { + log.warn("verifyVCSignature; error verifying signature", ex); + } + return false; + }); + return verified; + } + + private String getAlgFromProof(JsonObject vc) { + LdProof proof = LdProof.fromJson(vc.getJsonObject("proof").toString()); + if (proof.getType().contains(KeyTypeName.Ed25519.getValue())) { + return JWSAlgorithm.EdDSA; + } + if (proof.getType().startsWith("BbsBls")) { + return JWSAlgorithm.BBSPlus; + } + if (proof.getType().startsWith("RSA")) { + return JWSAlgorithm.RS256; + } + if (proof.getType().startsWith("EcdsaSecp256k")) { + return JWSAlgorithm.ES256K; + } + if (proof.getType().startsWith("EcdsaKoblitz")) { + return JWSAlgorithm.ES256K; + } + if (proof.getType().startsWith("JcsEcdsaSecp256k")) { + return JWSAlgorithm.ES256K; + } + // else we got JsonWebSignature2020 which maps to: + //Map.of(KeyTypeName.RSA, List.of(JWSAlgorithm.PS256, JWSAlgorithm.RS256), + // KeyTypeName.Ed25519, List.of(JWSAlgorithm.EdDSA), + // KeyTypeName.secp256k1, List.of(JWSAlgorithm.ES256K), + // KeyTypeName.P_256, List.of(JWSAlgorithm.ES256), + // KeyTypeName.P_384, List.of(JWSAlgorithm.ES384)), + // so, will need more info on how to choose proper algo.. + if (proof.getJws() != null) { + JWT jwt; + try { + jwt = JWTParser.parse(proof.getJws()); + return jwt.getHeader().getAlgorithm().getName(); + } catch (ParseException ex) { + log.debug("getAlgFromProof; error parsing JWS: {}", ex.getMessage()); + } + } + return JWSAlgorithm.ES256K; + } + + public VCResolveResult resolveVC(String uri) { + log.debug("resolveVC.enter; got uri: {}", uri); + JsonObject jsonVC = loadJsonDocument(uri); + VCResolveResult result = new VCResolveResult(false, null, null); + if (jsonVC != null) { + String json = jsonVC.toString(); + log.debug("resolveVC; got JSON:: {}", json); + VerifiableCredential vc = VerifiableCredential.fromJson(json); + Map<String, Object> claims = vc.getCredentialSubject().getClaims(); + result.setTrustListUri((String) claims.get("trustlistURI")); + result.setHash((String) claims.get("hash")); + JsonObject vcJson = vc.toJsonObject(); + String alg = getAlgFromProof(vcJson); + String did = vc.getLdProof().getVerificationMethod().toString(); + result.setVerified(verifyVCSignature(JsonLDObject.fromJson(json), did, alg)); + } + log.debug("resolveVC.exit; returning: {}", result); + return result; + } - ResolveResult resolveResult; - //resolveResult = resolver.resolve("did:sov:WRfXPg8dantKVubE3HX8pw", resolveOptions); - //System.out.println(resolveResult.toJson()); - //resolveResult = resolver.resolveRepresentation("did:sov:WRfXPg8dantKVubE3HX8pw", resolveOptions); - //System.out.println(resolveResult.toJson()); + private JsonObject loadJsonDocument(String uri) { + log.debug("loadJsonDocument.enter; got uri: {}", uri); + JsonDocument doc; + try { + doc = (JsonDocument) docLoader.loadDocument(JsonLDUtils.stringToUri(uri), new DocumentLoaderOptions()); + } catch (JsonLdError ex) { + log.error("loadDidDocument.error", ex); + throw new DidException(ex); + } + // check for empty.. + return doc.getJsonContent().get().asJsonObject(); + } + + @Getter + @AllArgsConstructor + @ToString + class DIDResolveResult { + + private Map<String, Object> document; + private List<String> endpoints; + private String origin; + + } + + @Getter + @Setter + @AllArgsConstructor + @ToString + class VCResolveResult { + + private boolean verified; + private String trustListUri; + private String hash; + } } diff --git a/service/src/main/java/eu/xfsc/train/tcr/server/service/DNSResolver.java b/service/src/main/java/eu/xfsc/train/tcr/server/service/DNSResolver.java index b571f93545bf919178e2639a95fb685962f2562a..2d2df55f31cbd637a378cc88e22b58048b12149e 100644 --- a/service/src/main/java/eu/xfsc/train/tcr/server/service/DNSResolver.java +++ b/service/src/main/java/eu/xfsc/train/tcr/server/service/DNSResolver.java @@ -1,10 +1,7 @@ package eu.xfsc.train.tcr.server.service; import java.io.IOException; -import java.net.UnknownHostException; -import java.time.Duration; import java.util.Collection; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -12,10 +9,10 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import org.jitsi.dnssec.validator.ValidatingResolver; -import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.xbill.DNS.DClass; -import org.xbill.DNS.ExtendedResolver; +import org.xbill.DNS.Flags; import org.xbill.DNS.Message; import org.xbill.DNS.Name; import org.xbill.DNS.PTRRecord; @@ -24,12 +21,11 @@ import org.xbill.DNS.Rcode; import org.xbill.DNS.Record; import org.xbill.DNS.Resolver; import org.xbill.DNS.Section; -import org.xbill.DNS.SimpleResolver; import org.xbill.DNS.TXTRecord; import org.xbill.DNS.Type; import org.xbill.DNS.URIRecord; -import eu.xfsc.train.tcr.server.exception.DNSException; +import eu.xfsc.train.tcr.server.exception.DnsException; import eu.xfsc.train.tcr.server.legacy.TrustScheme; import eu.xfsc.train.tcr.server.legacy.TrustSchemeClaim; import eu.xfsc.train.tcr.server.legacy.TrustSchemeFactory; @@ -39,40 +35,14 @@ import lombok.extern.slf4j.Slf4j; @Component public class DNSResolver { - - private Resolver resolver; + private static final String PREFIX = "_scheme._trust."; - public DNSResolver(@Value("${tcr.dns.hosts}") List<String> dnsHosts, @Value("${tcr.dns.timeout}") int timeout) { - log.info("<init>.enter; configured DNS hosts: {}, timeout: {}", dnsHosts, timeout); - try { - if (dnsHosts.isEmpty()) { - resolver = new SimpleResolver(); - } else { - SimpleResolver[] resolvers = new SimpleResolver[dnsHosts.size()]; - int idx = 0; - for (String dnsHost: dnsHosts) { - SimpleResolver r = new SimpleResolver(dnsHost); - if (timeout > 0) { - r.setTimeout(Duration.ofMillis(timeout)); - } - resolvers[idx] = r; - idx++; - } - resolver = new ExtendedResolver(resolvers); - } - if (timeout > 0 ) { - resolver.setTimeout(Duration.ofMillis(timeout)); - } - } catch (UnknownHostException ex) { - log.error("<init}.error", ex); - throw new IllegalArgumentException(ex); - } - log.info("<init>.exit; configured resolver: {}", resolver); - } + @Autowired + private Resolver resolver; - public Collection<String> resolveDid(String domain) throws Exception { + public Collection<String> resolveDomain(String domain) throws Exception { Set<String> results = new HashSet<>(); - Set<String> uris = resolvePtr(domain); + Set<String> uris = resolvePtr(domain); // we can get another PTRs here? for (String uri: uris) { Set<String> dids = resolveUri(uri); results.addAll(dids); @@ -80,75 +50,87 @@ public class DNSResolver { return results; } - private static final String PREFIX = "_scheme._trust."; + public Collection<String> resolveDomains(List<String> domains) throws Exception { + Set<String> results = new HashSet<>(); + for (String domain: domains) { + Set<String> uris = resolvePtr(domain); // we can get another PTRs here? + for (String uri: uris) { + Set<String> dids = resolveUri(uri); + results.addAll(dids); + } + } + return results; + } - private String fixHostName(String host) { - if (host.startsWith(PREFIX)) { - return host; - } else { - return PREFIX + host; - } - } - + /** + * resolves DNS PTR record + * + * @param ptr - the PTR record to query + * @return the resolved Set<String> + */ private Set<String> resolvePtr(String ptr) { log.debug("resolvePtr.enter; got PTR: {}", ptr); - String host = fixHostName(ptr); - - Set<String> set; - try { - set = query(host, Type.PTR) - .filter(r -> r instanceof PTRRecord) - .map(r -> ((PTRRecord) r).getTarget().toString()) - .collect(Collectors.toSet()); - } catch (IOException | DNSException e) { - log.warn("resolvePtr.error", e); - set = Collections.emptySet(); - } + Set<String> set = query(ptr, Type.PTR) + .filter(r -> r instanceof PTRRecord) + .map(r -> ((PTRRecord) r).getTarget().toString()) + .collect(Collectors.toSet()); log.debug("resolvePtr.exit; returning uris: {}", set); return set; } + /** + * resolves DNS URI record + * + * @param ptr - the URI record to query + * @return the resolved Set<String> + */ private Set<String> resolveUri(String uri) { log.debug("resolveUri.enter; got URI: {}", uri); - String host = fixHostName(uri); - - Set<String> set; - try { - set = query(host, Type.URI) - .filter(r -> r instanceof URIRecord) - .map(r -> ((URIRecord) r).getTarget()) - .collect(Collectors.toSet()); - } catch (IOException | DNSException e) { - log.warn("resolveUri.error", e); - set = Collections.emptySet(); - } + Set<String> set = query(uri, Type.URI) + .filter(r -> r instanceof URIRecord) + .map(r -> ((URIRecord) r).getTarget()) + .collect(Collectors.toSet()); log.debug("resolveUri.exit; returning dids: {}", set); return set; } - - private Stream<Record> query(String host, int type) throws IOException, DNSException { - if (!host.endsWith(".")) { - host = host + "."; + /** + * + * @param uri - the domain to be queried + * @param type - DNS record type + * @return the stream of DNS records + * @throws IOException + * @throws DnsException + */ + private Stream<Record> query(String uri, int type) { + if (!uri.startsWith(PREFIX)) { + uri = PREFIX + uri; + } + if (!uri.endsWith(".")) { + uri = uri + "."; } - Record query = Record.newRecord(Name.fromConstantString(host), type, DClass.IN); - log.debug("query; DNS query: " + query.toString()); - Message response = resolver.send(Message.newQuery(query)); - //if (!response.getHeader().getFlag(Flags.AD)) { - // log.debug("query; NO AD flag present!"); - //throw new DNSException("No AD flag. (Host not using DNSSec?)"); - //} - + Record query = Record.newRecord(Name.fromConstantString(uri), type, DClass.IN); + log.debug("query; DNS query: {}", query); + Message response; + try { + response = resolver.send(Message.newQuery(query)); + } catch (IOException ex) { + log.warn("query.error ", ex); + return Stream.empty(); + } + int rcode = response.getRcode(); - if (rcode != Rcode.SERVFAIL && rcode != Rcode.NOERROR) { - for (RRset set: response.getSectionRRsets(Section.ADDITIONAL)) { - log.debug("query; Zone: {}", set.getName()); - if (set.getName().equals(Name.root) && set.getType() == Type.TXT && set.getDClass() == ValidatingResolver.VALIDATION_REASON_QCLASS) { - log.info("query; Reason: {}", ((TXTRecord) set.first()).getStrings().get(0)); + log.debug("query; got RCode: {} ({}); AD Flag present: {}", rcode, Rcode.string(rcode), response.getHeader().getFlag(Flags.AD)); + if (rcode != Rcode.NOERROR) { + // the code below is just to log an additional info about error condition + for (RRset rrSet: response.getSectionRRsets(Section.ADDITIONAL)) { + log.debug("query; Zone: {}", rrSet.getName()); + if (rrSet.getName().equals(Name.root) && rrSet.getType() == Type.TXT && rrSet.getDClass() == ValidatingResolver.VALIDATION_REASON_QCLASS) { + log.info("query; Reason: {}", ((TXTRecord) rrSet.first()).getStrings().get(0)); } - throw new DNSException("RCode: " + rcode + " (" + Rcode.string(rcode) + ")"); } + return Stream.empty(); } List<RRset> rrSets = response.getSectionRRsets(Section.ANSWER); @@ -157,7 +139,7 @@ public class DNSResolver { - + // TODO: to be removed, most probably public Collection<String> verifyIdentity(String issuer, String claim) throws Exception { log.debug("verifyIdentity.enter; issuer: {}, claim: {}", issuer, claim); @@ -167,7 +149,7 @@ public class DNSResolver { List<String> resp = null; Collection<TrustScheme> schemes = tsFactory.createTrustSchemes(tsClaim); if (schemes.isEmpty()) { - throw new IOException("Did not find TrustScheme / TrustList"); + throw new DnsException("Did not find TrustScheme / TrustList"); } //resp.VerificationResult.FoundCorrespondingTrustScheme = scheme.getSchemeIdentifierCleaned(); diff --git a/service/src/main/java/eu/xfsc/train/tcr/server/service/ResolutionService.java b/service/src/main/java/eu/xfsc/train/tcr/server/service/ResolutionService.java new file mode 100644 index 0000000000000000000000000000000000000000..e0a07f7e92f9ecd4e2ccf88ffd695c5f6f0d5ecb --- /dev/null +++ b/service/src/main/java/eu/xfsc/train/tcr/server/service/ResolutionService.java @@ -0,0 +1,153 @@ +package eu.xfsc.train.tcr.server.service; + +import java.io.IOException; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +import eu.europa.esig.trustedlist.jaxb.tsl.TrustStatusListType; +import eu.europa.esig.trustedlist.jaxb.tsl.TSPType; +import eu.xfsc.train.tcr.api.generated.model.ResolveRequest; +import eu.xfsc.train.tcr.api.generated.model.ResolveResult; +import eu.xfsc.train.tcr.api.generated.model.ResolveTrustList; +import eu.xfsc.train.tcr.api.generated.model.ValidateRequest; +import eu.xfsc.train.tcr.api.generated.model.ValidateResponse; +import eu.xfsc.train.tcr.server.generated.controller.TrustedContentResolverApiDelegate; +import eu.xfsc.train.tcr.server.service.DIDResolver.DIDResolveResult; +import eu.xfsc.train.tcr.server.service.DIDResolver.VCResolveResult; +import eu.xfsc.train.tcr.server.service.TLResolver.TLResolveResult; +import lombok.extern.slf4j.Slf4j; + +/** + * Implementation of the + * {@link eu.xfsc.train.tcr.server.generated.controller.TrustedContentResolverApiDelegate} + * interface. + */ +@Slf4j +@Service +public class ResolutionService implements TrustedContentResolverApiDelegate { + + private static final TypeReference<Map<String, Object>> MAP_TYPE_REF = new TypeReference<Map<String, Object>>() { + }; + + @Autowired + private DNSResolver reDns; + @Autowired + private DIDResolver reDid; + @Autowired + private TLResolver reTL; + @Autowired + private ObjectMapper jsonMapper; + + @Override + public ResponseEntity<List<ResolveResult>> resolveTrustList(ResolveRequest resolveRequest) { + log.debug("resolveTrustList.enter; got request: {}", resolveRequest); + Map<String, ResolveResult> result = new HashMap<>(); + resolveRequest.getTrustSchemePointers().forEach(ptr -> { + Collection<String> dids = null; + try { + dids = reDns.resolveDomain(ptr); + } catch (Exception ex) { + log.warn("resolveTrustList.error", ex); + // add error block to response? + } + if (dids != null) { + dids.forEach(did -> { + ResolveResult rr = result.computeIfAbsent(did, s -> new ResolveResult().did(s)); + if (rr.getPointers().contains(ptr)) { + log.debug("resolveTrustList; DID {} for PTR {} has been already resolved", did, ptr); + } else { + rr.addPointersItem(ptr); + DIDResolveResult didRes = reDid.resolveDid(did, resolveRequest.getEndpointTypes()); + if (didRes.getOrigin() != null) { + // here we perform well-known did-configuration check.. + if (!reDid.resolveDidConfig(didRes.getOrigin())) { + // should we remove the not-verified entry? + log.info("resolveTrustList; resolved origin not verified: {}", didRes.getOrigin()); + } + } + rr.setDocument(didRes.getDocument()); + didRes.getEndpoints().forEach(endpoint -> { + ResolveTrustList rtl = new ResolveTrustList().endpoint(endpoint); + VCResolveResult vcRes = reDid.resolveVC(endpoint); + //reTL.validateDocument(vcRes.getTrustListUri()); + TLResolveResult tl = reTL.resolveTLHash(vcRes.getTrustListUri(), vcRes.getHash()); + if (tl != null) { + Optional<TSPType> tsPro = findIssuerProvider(tl.getTrustList(), resolveRequest.getIssuer()); + if (tsPro.isPresent()) { + rtl.setTrustList(convertToMap(tsPro.get())); + } + } + rr.addEndpointsItem(rtl); + }); + } + }); + } + }); + log.debug("resolveTrustList.exit; returning result: {}", result); + return ResponseEntity.ok(result.values().stream().toList()); + } + + @Override + public ResponseEntity<ValidateResponse> validateTrustList(ValidateRequest validateRequest) { + log.debug("validateTrustList.enter; got request: {}", validateRequest); + ValidateResponse result = new ValidateResponse(); + DIDResolveResult didRes = reDid.resolveDid(validateRequest.getDid(), null); + if (didRes.getOrigin() == null) { + result.setDidVerified(false); + log.info("validateTrustList; origin not resolved"); + } else { + // here we perform well-known did-configuration check.. + result.setDidVerified(reDid.resolveDidConfig(didRes.getOrigin())); + if (!result.getDidVerified()) { + log.info("validateTrustList; resolved origin not verified: {}", didRes.getOrigin()); + } + } + validateRequest.getEndpoints().forEach(endpoint -> { + ResolveTrustList rtl = new ResolveTrustList().endpoint(endpoint); + VCResolveResult vcRes = reDid.resolveVC(endpoint); + result.setVcVerified(vcRes.isVerified()); + if (vcRes.getTrustListUri() != null) { + TrustStatusListType tl = reTL.resolveTL(vcRes.getTrustListUri(), false); + if (tl != null) { + Optional<TSPType> tsPro = findIssuerProvider(tl, validateRequest.getIssuer()); + if (tsPro.isPresent()) { + rtl.setTrustList(convertToMap(tsPro.get())); + result.setIssuerVerified(true); + result.addEndpointsItem(rtl); + } + } + } + + }); + log.debug("validateTrustList.exit; returning result: {}", result); + return ResponseEntity.ok(result); + } + + private Optional<TSPType> findIssuerProvider(TrustStatusListType trustList, String issuer) { + return trustList.getTrustServiceProviderList().getTrustServiceProvider().stream() + .filter(tsp -> tsp.getTSPInformation().getTSPName().getName().stream() + .anyMatch(n -> issuer.equals(n.getValue()))).findFirst(); + } + + + private Map<String, Object> convertToMap(TSPType tsp) { + try { + byte[] json = jsonMapper.writeValueAsBytes(tsp); + return jsonMapper.readValue(json, MAP_TYPE_REF); + } catch (IOException ex) { + log.warn("convertToMap.error; ", ex); + return null; + } + } + +} diff --git a/service/src/main/java/eu/xfsc/train/tcr/server/service/TLResolver.java b/service/src/main/java/eu/xfsc/train/tcr/server/service/TLResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..06c2e89bc4dee8b196afd4af982b8d3bfc77bfff --- /dev/null +++ b/service/src/main/java/eu/xfsc/train/tcr/server/service/TLResolver.java @@ -0,0 +1,278 @@ +package eu.xfsc.train.tcr.server.service; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.OptionalInt; + +import javax.xml.bind.JAXBException; +import javax.xml.stream.XMLStreamException; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.xml.sax.SAXException; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import eu.europa.esig.dss.enumerations.MimeType; +import eu.europa.esig.dss.enumerations.MimeTypeEnum; +import eu.europa.esig.dss.model.DSSDocument; +import eu.europa.esig.dss.model.DSSException; +import eu.europa.esig.dss.model.FileDocument; +import eu.europa.esig.dss.service.crl.OnlineCRLSource; +import eu.europa.esig.dss.service.ocsp.OnlineOCSPSource; +import eu.europa.esig.dss.service.http.commons.CommonsDataLoader; +import eu.europa.esig.dss.service.http.commons.FileCacheDataLoader; +import eu.europa.esig.dss.spi.client.http.DSSFileLoader; +import eu.europa.esig.dss.spi.client.http.IgnoreDataLoader; +import eu.europa.esig.dss.spi.tsl.TrustedListsCertificateSource; +import eu.europa.esig.dss.spi.x509.CertificateSource; +import eu.europa.esig.dss.spi.x509.KeyStoreCertificateSource; +import eu.europa.esig.dss.spi.x509.aia.DefaultAIASource; +import eu.europa.esig.dss.tsl.cache.CacheCleaner; +import eu.europa.esig.dss.tsl.function.OfficialJournalSchemeInformationURI; +import eu.europa.esig.dss.tsl.job.TLValidationJob; +import eu.europa.esig.dss.tsl.source.LOTLSource; +import eu.europa.esig.dss.tsl.source.TLSource; +import eu.europa.esig.dss.tsl.sync.AcceptAllStrategy; +import eu.europa.esig.dss.validation.CommonCertificateVerifier; +import eu.europa.esig.dss.validation.SignedDocumentValidator; +import eu.europa.esig.dss.validation.reports.Reports; +import eu.europa.esig.trustedlist.TrustedListFacade; +import eu.europa.esig.trustedlist.jaxb.tsl.TrustStatusListType; +import io.ipfs.multihash.Multihash; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Component +public class TLResolver { + + // Should be externalized + private static final String LOTL_URL = "https://ec.europa.eu/tools/lotl/eu-lotl.xml"; + private static final String OJ_URL = "https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=uriserv:OJ.C_.2019.276.01.0001.01.ENG"; + + private final TrustedListFacade facade = TrustedListFacade.newFacade(); + + @Autowired + private ObjectMapper jsonMapper; + + public TrustStatusListType resolveTL(String uri) { + return resolveTL(uri, false); + } + + public TrustStatusListType resolveTL(String uri, boolean validate) { + log.debug("resolveTL.enter; got uri: {}, validate: {}", uri, validate); + + TrustStatusListType trustList = null; + try { + String content = resolveContent(uri); + trustList = parseContent(content, validate); + } catch (IOException | JAXBException | XMLStreamException | SAXException ex) { + log.error("resolveTL.error;", ex); + } + log.debug("resolveTL.exit; returning TL: {}", trustList); + return trustList; + } + + public TLResolveResult resolveTLHash(String uri, String hash) { + log.debug("resolveTLHash.enter; got uri: {}, hash: {}", uri, hash); + + String content = null; + TrustStatusListType trustList = null; + try { + content = resolveContent(uri); + trustList = parseContent(content, false); + } catch (Exception ex) { + log.warn("resolveTLHash.error reading TrustList", ex); + } + if (trustList == null) { + return null; + } + + TLResolveResult tlResult = new TLResolveResult(trustList, null, false); + try { + Multihash vcHash = Multihash.fromBase58(hash); // .decode(hash); + Multihash.Type mType = vcHash.getType(); + + String algo = getDigestType(mType); + log.debug("resolveTLHash; hash algo is: {}", algo); + MessageDigest md = MessageDigest.getInstance(algo); + md.update(content.getBytes()); + Multihash tlHash = new Multihash(mType, md.digest()); + tlResult.setTlHash(tlHash.toString()); + tlResult.setHashVerified(vcHash.equals(tlHash)); + } catch (Exception ex) { + log.warn("resolveTLHash.error verifying Hash: {}", ex.getMessage()); + } + log.debug("resolveTLHash.exit; returning: {}", tlResult); + return tlResult; + } + + private String resolveContent(String uri) throws IOException { + DSSFileLoader dl = onlineLoader(); + DSSDocument tlDoc = dl.getDocument(uri); + log.debug("resolveContent; got TL doc with type: {}", tlDoc.getMimeType()); + InputStream input = tlDoc.openStream(); + return new String(input.readAllBytes(), StandardCharsets.UTF_8); + } + + private TrustStatusListType parseContent(String content, boolean validate) throws JAXBException, XMLStreamException, IOException, SAXException { + MimeType type = getDocType(content); + if (type == MimeTypeEnum.XML) { + return facade.unmarshall(content, validate); + } + if (type == MimeTypeEnum.JSON) { + return jsonMapper.readValue(content, TrustStatusListType.class); + } + log.info("parseContent; got content with unknown type: {}", type); + return null; + } + + private MimeType getDocType(String content) { + OptionalInt opt = content.chars().filter(c -> c == '<' || c == '{').findFirst(); + if (opt.isPresent()) { + if (opt.getAsInt() == '<') { + return MimeTypeEnum.XML; + } else if (opt.getAsInt() == '{') { + return MimeTypeEnum.JSON; + } + } + return MimeTypeEnum.BINARY; + } + + private String getDigestType(Multihash.Type mType) throws NoSuchAlgorithmException { + switch (mType) { + case sha1: return "SHA-1"; + case sha2_256: return "SHA-256"; + case sha2_512: return "SHA-512"; + case sha3_256: return "SHA3-256"; + } + throw new NoSuchAlgorithmException("Unsupported MType: " + mType); + } + + public boolean validateTL(String path) { + log.debug("validateTL.enter; got path: {}", path); + TLSource tlSource = new TLSource(); + tlSource.setUrl(path); + TLValidationJob tlvJob = new TLValidationJob(); + tlvJob.setTrustedListSources(tlSource); + tlvJob.setOfflineDataLoader(offlineLoader()); + tlvJob.offlineRefresh(); + + tlvJob.setOnlineDataLoader(onlineLoader()); + tlvJob.onlineRefresh(); + log.debug("validateTL.exit; summary: {}", tlvJob.getSummary()); + return true; + } + + public void validateDocument(String path) { + log.debug("validateDocument.enter got path: {}", path); + CommonCertificateVerifier commonCertificateVerifier = new CommonCertificateVerifier(); + TLValidationJob job = job(); + TrustedListsCertificateSource trustedListsCertificateSource = new TrustedListsCertificateSource(); + job.setTrustedListCertificateSource(trustedListsCertificateSource); + job.onlineRefresh(); + commonCertificateVerifier.setTrustedCertSources(trustedListsCertificateSource); + commonCertificateVerifier.setCrlSource(new OnlineCRLSource()); + commonCertificateVerifier.setOcspSource(new OnlineOCSPSource()); + commonCertificateVerifier.setAIASource(new DefaultAIASource()); + + SignedDocumentValidator validator = SignedDocumentValidator.fromDocument(new FileDocument(path)); + validator.setCertificateVerifier(commonCertificateVerifier); + Reports repo = validator.validateDocument(); + log.debug("validateDocument.exit; report: {}", repo.getXmlSimpleReport()); + } + + + private TLValidationJob job() { + TLValidationJob job = new TLValidationJob(); + job.setOfflineDataLoader(offlineLoader()); + job.setOnlineDataLoader(onlineLoader()); + job.setTrustedListCertificateSource(trustedCertificateSource()); + job.setSynchronizationStrategy(new AcceptAllStrategy()); + job.setCacheCleaner(cacheCleaner()); + + LOTLSource europeanLOTL = europeanLOTL(); + job.setListOfTrustedListSources(europeanLOTL); + + //job.setLOTLAlerts(Arrays.asList(ojUrlAlert(europeanLOTL), lotlLocationAlert(europeanLOTL))); + //job.setTLAlerts(Arrays.asList(tlSigningAlert(), tlExpirationDetection())); + + return job; + } + + private TrustedListsCertificateSource trustedCertificateSource() { + return new TrustedListsCertificateSource(); + } + + private LOTLSource europeanLOTL() { + LOTLSource lotlSource = new LOTLSource(); + lotlSource.setUrl(LOTL_URL); + //lotlSource.setCertificateSource(officialJournalContentKeyStore()); + lotlSource.setSigningCertificatesAnnouncementPredicate(new OfficialJournalSchemeInformationURI(OJ_URL)); + lotlSource.setPivotSupport(true); + return lotlSource; + } + + private CertificateSource officialJournalContentKeyStore() { + try { + return new KeyStoreCertificateSource(new File("src/main/resources/keystore.p12"), "PKCS12", "dss-password"); + } catch (IOException e) { + throw new DSSException("Unable to load the keystore", e); + } + } + + private DSSFileLoader offlineLoader() { + FileCacheDataLoader offlineFileLoader = new FileCacheDataLoader(); + offlineFileLoader.setCacheExpirationTime(-1); // negative value means cache never expires + offlineFileLoader.setDataLoader(new IgnoreDataLoader()); + offlineFileLoader.setFileCacheDirectory(tlCacheDirectory()); + return offlineFileLoader; + } + + private DSSFileLoader onlineLoader() { + FileCacheDataLoader onlineFileLoader = new FileCacheDataLoader(); + onlineFileLoader.setCacheExpirationTime(0); + onlineFileLoader.setDataLoader(new CommonsDataLoader()); // instance of DataLoader which can access to Internet (proxy,...) + onlineFileLoader.setFileCacheDirectory(tlCacheDirectory()); + return onlineFileLoader; + } + + private File tlCacheDirectory() { + File rootFolder = new File(System.getProperty("java.io.tmpdir")); + File tslCache = new File(rootFolder, "dss-tsl-loader"); + if (tslCache.mkdirs()) { + log.info("TL Cache folder : {}", tslCache.getAbsolutePath()); + } + return tslCache; + } + + private CacheCleaner cacheCleaner() { + CacheCleaner cacheCleaner = new CacheCleaner(); + cacheCleaner.setCleanMemory(true); + cacheCleaner.setCleanFileSystem(true); + cacheCleaner.setDSSFileLoader(offlineLoader()); + return cacheCleaner; + } + + @Getter + @Setter + @AllArgsConstructor + @ToString + class TLResolveResult { + + private TrustStatusListType trustList; + private String tlHash; + private boolean hashVerified; + + } + + +} diff --git a/service/src/main/java/eu/xfsc/train/tcr/server/service/VerificationService.java b/service/src/main/java/eu/xfsc/train/tcr/server/service/VerificationService.java deleted file mode 100644 index 67e2bd8dc8beadc4561e057e6c9e4aa4b6ca6b14..0000000000000000000000000000000000000000 --- a/service/src/main/java/eu/xfsc/train/tcr/server/service/VerificationService.java +++ /dev/null @@ -1,45 +0,0 @@ -package eu.xfsc.train.tcr.server.service; - - -import java.util.ArrayList; -import java.util.Collection; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Service; - -import eu.xfsc.train.tcr.api.generated.model.VerificationRequest; -import eu.xfsc.train.tcr.api.generated.model.VerificationResult; -import eu.xfsc.train.tcr.server.generated.controller.TrustedContentResolverApiDelegate; -import lombok.extern.slf4j.Slf4j; - -/** - * Implementation of the {@link eu.xfsc.fc.server.generated.controller.SessionApiDelegate} interface. - */ -@Slf4j -@Service -public class VerificationService implements TrustedContentResolverApiDelegate { - - @Autowired - private DNSResolver dns; - - - @Override - public ResponseEntity<VerificationResult> resolveIssuer(VerificationRequest verificationRequest) { - log.debug("resolveIssuer.enter; got request: {}", verificationRequest); - try { - Collection<String> addresses = dns.resolveDid(verificationRequest.getTrustSchemePointer()); - //Collection<String> addresses = dns.verifyIdentity(verificationRequest.getIssuer(), verificationRequest.getTrustSchemePointer()); - VerificationResult result = new VerificationResult(); - result.setResolvedDID(verificationRequest.getIssuer()); - result.setTrustListEndpoints(new ArrayList<>(addresses)); - log.debug("resolveIssuer.exit; returning result: {}", result); - return ResponseEntity.ok(result); - } catch (Exception ex) { - log.error("resolveIssuer.error", ex); - ResponseEntity response = ResponseEntity.internalServerError().body(ex.getMessage()); - return response; - } - } - -} diff --git a/service/src/main/resources/application.yml b/service/src/main/resources/application.yml index 0cd04ba532bd8bbe759c895e7e96685d16b59314..5fb5f51666ad462560e6d6494a42657c63d9b517 100644 --- a/service/src/main/resources/application.yml +++ b/service/src/main/resources/application.yml @@ -21,7 +21,7 @@ server: spring: application: - name: federated-catalogue-service + name: trusted-content-resolver mvc: log-request-details: true @@ -40,16 +40,24 @@ logging: eu.xfsc.train.tcr: DEBUG org.springframework.web: INFO org.xbill.DNS: DEBUG + uniresolver: DEBUG tcr: + did: + # base-uri: http://localhost:8080/1.0 + base-uri: https://dev.uniresolver.io/1.0 + timeout: 500 dns: hosts: 1.1.1.1, 8.8.8.8, 8.8.4.4 # - 1.1.1.1 doesn't work this way' # - 8.8.8.8 # - 8.8.4.4 - timeout: 2010 - verification: - enabled: true #DANE.. + timeout: 500 + dnssec: + enabled: false #DANE.. + rootPath: /wrong/path http: timeout: 10 + tl: + verify: true \ No newline at end of file diff --git a/service/src/test/java/eu/xfsc/train/tcr/server/controller/ResolutionControllerTest.java b/service/src/test/java/eu/xfsc/train/tcr/server/controller/ResolutionControllerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..6e6a634f59250a6a9748aa43d6224e55bd4623f1 --- /dev/null +++ b/service/src/test/java/eu/xfsc/train/tcr/server/controller/ResolutionControllerTest.java @@ -0,0 +1,192 @@ +package eu.xfsc.train.tcr.server.controller; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.net.URI; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +import eu.xfsc.train.tcr.api.generated.model.Error; +import eu.xfsc.train.tcr.api.generated.model.ResolveRequest; +import eu.xfsc.train.tcr.api.generated.model.ResolveResult; +import eu.xfsc.train.tcr.api.generated.model.ResolveTrustList; +import eu.xfsc.train.tcr.api.generated.model.ValidateRequest; +import eu.xfsc.train.tcr.api.generated.model.ValidateResponse; +import uniresolver.UniResolver; +import uniresolver.result.ResolveRepresentationResult; + + +@SpringBootTest +@AutoConfigureMockMvc +@ActiveProfiles("test") +@ExtendWith(SpringExtension.class) +public class ResolutionControllerTest { + + @Autowired + private MockMvc mockMvc; + @Autowired + private ObjectMapper jsonMapper; + @MockBean + private UniResolver uniResolver; + + private static final String testDoc = """ + {"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/suites/jws-2020/v1"],"id":"did:web:essif.trust-scheme.de","verificationMethod":[{"id":"did:web:essif.trust-scheme.de#owner","type":"JsonWebKey2020", + "controller":"did:web:essif.trust-scheme.de","publicKeyJwk":{"kty":"OKP","crv":"Ed25519","x":"U8vPLaL0zjnRSjVwaYQNfPVW5k9j_XMF6dTxTOiAojs"}}],"service":[{"id":"did:web:essif.trust-scheme.de#issuer-list", + "type":"issuer-list","serviceEndpoint":"https://essif.iao.fraunhofer.de/files/policy/schuelerausweis.xml"}],"authentication":["did:web:essif.trust-scheme.de#owner"], + "assertionMethod":["did:web:essif.trust-scheme.de#owner"]}"""; + + private static final String essifDoc = """ + {"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/suites/jws-2020/v1"],"id":"did:web:essif.iao.fraunhofer.de","verificationMethod":[{"id":"did:web:essif.iao.fraunhofer.de#owner","type":"JsonWebKey2020", + "controller":"did:web:essif.iao.fraunhofer.de","publicKeyJwk":{"kty":"OKP","crv":"Ed25519","x":"yaHbNw6nj4Pn3nGPHyyTqP-QHXYNJIpkA37PrIOND4c"}}],"service":[{"id":"did:web:essif.iao.fraunhofer.de#issuer-list", + "type":"issuer-list","serviceEndpoint":"https://essif.iao.fraunhofer.de/files/trustlist/federation1.test.train.trust-scheme.de.json"}],"authentication":["did:web:essif.iao.fraunhofer.de#owner"], + "assertionMethod":["did:web:essif.iao.fraunhofer.de#owner"]}"""; + + private static final TypeReference<List<ResolveResult>> LIST_TYPE_REF = new TypeReference<List<ResolveResult>>() { + }; + private static final TypeReference<Map<String, Object>> MAP_TYPE_REF = new TypeReference<Map<String, Object>>() { + }; + + + @Test + public void postResolveRequestShouldReturnFailureResponse() throws Exception { + + String did1 = "did:web:essif.trust-scheme.de"; + ResolveRepresentationResult rrr = ResolveRepresentationResult.build(null, testDoc.getBytes(), null); + when(uniResolver.resolveRepresentation(eq(did1), any())).thenReturn(rrr); + String did2 = "did:web:essif.iao.fraunhofer.de"; + rrr = ResolveRepresentationResult.build(null, essifDoc.getBytes(), null); + when(uniResolver.resolveRepresentation(eq(did2), any())).thenReturn(rrr); + + String ptr = "did-web.test.train.trust-scheme.de"; + ResolveRequest request = new ResolveRequest(); + request.setIssuer("https://test-issuer.sample.org"); + request.addEndpointTypesItem("issuer-list"); + request.addTrustSchemePointersItem(ptr); + + String response = mockMvc + .perform(MockMvcRequestBuilders.post("/resolve") + .content(jsonMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isUnprocessableEntity()) + .andReturn() + .getResponse() + .getContentAsString(); + Error error = jsonMapper.readValue(response, Error.class); + assertEquals("verification_error", error.getCode()); + } + + @Test + public void postResolveRequestShouldReturnEmptyResponse() throws Exception { + + String did = "did:web:essif.trust-scheme.de"; + ResolveRepresentationResult rrr = ResolveRepresentationResult.build(null, testDoc.getBytes(), null); + when(uniResolver.resolveRepresentation(eq(did), any())).thenReturn(rrr); + + String ptr = "did-web.test.train.trust-scheme.de"; + ResolveRequest request = new ResolveRequest(); + request.setIssuer("https://test-issuer.sample.org"); + request.addEndpointTypesItem("wrong-type"); + request.addTrustSchemePointersItem(ptr); + + String response = mockMvc + .perform(MockMvcRequestBuilders.post("/resolve") + .content(jsonMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andReturn() + .getResponse() + .getContentAsString(); + List<ResolveResult> result = jsonMapper.readValue(response, LIST_TYPE_REF); + assertNotNull(result); + assertEquals(1, result.size()); + ResolveResult rr = result.get(0); + assertEquals(did, rr.getDid()); + assertEquals(List.of(ptr), rr.getPointers()); + assertNotNull(rr.getDocument()); + assertTrue(rr.getEndpoints().isEmpty()); + } + + @Test + public void postResolveRequestShouldReturnTrustListResponse() throws Exception { + + String did = "did:web:essif.iao.fraunhofer.de"; + ResolveRepresentationResult rrr = ResolveRepresentationResult.build(null, essifDoc.getBytes(), null); + when(uniResolver.resolveRepresentation(eq(did), any())).thenReturn(rrr); + + String ptr = "gxfs.test.train.trust-scheme.de"; + String issuer = "https://www.federation1.com/"; + ResolveRequest request = new ResolveRequest(issuer, List.of(ptr), null, null); + + String response = mockMvc + .perform(MockMvcRequestBuilders.post("/resolve") + .content(jsonMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andReturn() + .getResponse() + .getContentAsString(); + List<ResolveResult> result = jsonMapper.readValue(response, LIST_TYPE_REF); + assertNotNull(result); + assertEquals(1, result.size()); + ResolveResult rr = result.get(0); + assertEquals(did, rr.getDid()); + assertEquals(List.of(ptr), rr.getPointers()); + assertNotNull(rr.getDocument()); + assertFalse(rr.getEndpoints().isEmpty()); + ResolveTrustList rtl = rr.getEndpoints().get(0); + assertEquals("https://essif.iao.fraunhofer.de/files/trustlist/federation1.test.train.trust-scheme.de.json", rtl.getEndpoint()); + Map<String, Object> tsp = (Map<String, Object>) rtl.getTrustList().get("tspinformation"); + Map<String, Object> tspn = (Map<String, Object>) tsp.get("tspname"); + Map<String, Object> name = ((List<Map<String, Object>>) tspn.get("name")).get(0); + assertEquals(issuer, name.get("value")); + } + + + @Test + public void postValidateRequestShouldReturnValidateResponse() throws Exception { + + String did = "did:web:essif.iao.fraunhofer.de"; + ResolveRepresentationResult rrr = ResolveRepresentationResult.build(null, essifDoc.getBytes(), null); + when(uniResolver.resolveRepresentation(eq(did), any())).thenReturn(rrr); + + String issuer = "https://www.federation1.com/"; + ValidateRequest request = new ValidateRequest(issuer, did, //jsonMapper.readValue(essifDoc, MAP_TYPE_REF), + List.of("https://essif.iao.fraunhofer.de/files/trustlist/federation1.test.train.trust-scheme.de.json")); + + String response = mockMvc + .perform(MockMvcRequestBuilders.post("/validate") + .content(jsonMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andReturn() + .getResponse() + .getContentAsString(); + ValidateResponse result = jsonMapper.readValue(response, ValidateResponse.class); + assertNotNull(result); + } + +} + + diff --git a/service/src/test/java/eu/xfsc/train/tcr/server/controller/VerificationControllerTest.java b/service/src/test/java/eu/xfsc/train/tcr/server/controller/VerificationControllerTest.java deleted file mode 100644 index 57bfa11ab9e48c06bc981a6f0c6b33f54daa8b87..0000000000000000000000000000000000000000 --- a/service/src/test/java/eu/xfsc/train/tcr/server/controller/VerificationControllerTest.java +++ /dev/null @@ -1,57 +0,0 @@ -package eu.xfsc.train.tcr.server.controller; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.MediaType; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; - -import com.fasterxml.jackson.databind.ObjectMapper; - -import eu.xfsc.train.tcr.api.generated.model.VerificationRequest; -import eu.xfsc.train.tcr.api.generated.model.VerificationResult; - - -@SpringBootTest -@AutoConfigureMockMvc -//@ActiveProfiles("test") -@ExtendWith(SpringExtension.class) -public class VerificationControllerTest { - - @Autowired - private MockMvc mockMvc; - @Autowired - private ObjectMapper jsonMapper; - - - @Test - public void postVerifyRequestShouldReturnSuccessResponse() throws Exception { - - VerificationRequest request = new VerificationRequest(); - request.setIssuer("https://test-issuer.sample.org"); - request.setTrustSchemePointer("did-web.test.train.trust-scheme.de"); - - String response = mockMvc - .perform(MockMvcRequestBuilders.post("/resolve") - .content(jsonMapper.writeValueAsString(request)) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andReturn() - .getResponse() - .getContentAsString(); - VerificationResult result = jsonMapper.readValue(response, VerificationResult.class); - assertNotNull(result); - assertTrue(result.getTrustListEndpoints().contains("did:web:essif.trust-scheme.de")); - } - -} diff --git a/service/src/test/java/eu/xfsc/train/tcr/server/service/DIDResolverTest.java b/service/src/test/java/eu/xfsc/train/tcr/server/service/DIDResolverTest.java new file mode 100644 index 0000000000000000000000000000000000000000..3efcfaceeeb9b788155aade99491c2a26b807e58 --- /dev/null +++ b/service/src/test/java/eu/xfsc/train/tcr/server/service/DIDResolverTest.java @@ -0,0 +1,142 @@ +package eu.xfsc.train.tcr.server.service; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import uniresolver.UniResolver; +import uniresolver.result.ResolveRepresentationResult; + +@SpringBootTest +@ActiveProfiles("test") +@ExtendWith(SpringExtension.class) +public class DIDResolverTest { + + @MockBean + //@Autowired + private UniResolver uniResolver; + @Autowired + private DIDResolver didResolver; + + private static final String fhDidEssif = "did:web:essif.iao.fraunhofer.de"; + + private static final String fhDidTrust = "did:web:essif.trust-scheme.de"; + + private static final String didDocSingleEP = """ + {"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/suites/jws-2020/v1"],"id":"did:web:essif.trust-scheme.de","verificationMethod":[{"id":"did:web:essif.trust-scheme.de#owner","type":"JsonWebKey2020", + "controller":"did:web:essif.trust-scheme.de","publicKeyJwk":{"kty":"OKP","crv":"Ed25519","x":"U8vPLaL0zjnRSjVwaYQNfPVW5k9j_XMF6dTxTOiAojs"}}],"service":[{"id":"did:web:essif.trust-scheme.de#issuer-list", + "type":"issuer-list","serviceEndpoint":"https://essif.iao.fraunhofer.de/files/policy/schuelerausweis.xml"}],"authentication":["did:web:essif.trust-scheme.de#owner"], + "assertionMethod":["did:web:essif.trust-scheme.de#owner"]}"""; + + private static final String didDocManyEP = """ + {"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/suites/jws-2020/v1"],"id":"did:web:essif.trust-scheme.de","verificationMethod":[{"id":"did:web:essif.trust-scheme.de#owner","type":"JsonWebKey2020", + "controller":"did:web:essif.trust-scheme.de","publicKeyJwk":{"kty":"OKP","crv":"Ed25519","x":"U8vPLaL0zjnRSjVwaYQNfPVW5k9j_XMF6dTxTOiAojs"}}],"service":[{"id":"did:web:essif.trust-scheme.de#issuer-list", + "type":"issuer-list","serviceEndpoint":["https://essif.iao.fraunhofer.de/firstEP", "https://essif.iao.fraunhofer.de/secondEP"]}],"authentication":["did:web:essif.trust-scheme.de#owner"], + "assertionMethod":["did:web:essif.trust-scheme.de#owner"]}"""; + + private static final String didDocObjectEP = """ + {"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/suites/jws-2020/v1"],"id":"did:web:essif.trust-scheme.de","verificationMethod":[{"id":"did:web:essif.trust-scheme.de#owner","type":"JsonWebKey2020", + "controller":"did:web:essif.trust-scheme.de","publicKeyJwk":{"kty":"OKP","crv":"Ed25519","x":"U8vPLaL0zjnRSjVwaYQNfPVW5k9j_XMF6dTxTOiAojs"}}],"service":[{"id":"did:web:essif.trust-scheme.de#issuer-list", + "type":"issuer-list","serviceEndpoint":{"services":["https://essif.iao.fraunhofer.de/firstEP", "https://essif.iao.fraunhofer.de/secondEP"]}}],"authentication":["did:web:essif.trust-scheme.de#owner"], + "assertionMethod":["did:web:essif.trust-scheme.de#owner"]}"""; + + private static final String didDocKeyVC = """ + {"@context": ["https://www.w3.org/ns/did/v1", {"Ed25519VerificationKey2018": "https://w3id.org/security#Ed25519VerificationKey2018", "publicKeyJwk": {"@id": "https://w3id.org/security#publicKeyJwk", "@type": "@json"}}], + "id": "did:key:z6MkoTHsgNNrby8JzCNQ1iRLyW5QQ6R8Xuu6AA8igGrMVPUM", "verificationMethod": [{"id": "did:key:z6MkoTHsgNNrby8JzCNQ1iRLyW5QQ6R8Xuu6AA8igGrMVPUM#z6MkoTHsgNNrby8JzCNQ1iRLyW5QQ6R8Xuu6AA8igGrMVPUM", "type": "Ed25519VerificationKey2018", + "controller": "did:key:z6MkoTHsgNNrby8JzCNQ1iRLyW5QQ6R8Xuu6AA8igGrMVPUM", "publicKeyJwk": {"kty": "OKP", "crv": "Ed25519", "x": "hbtAIehGcx_wXTFzIYJzrHOwl8IGV8EzRgx__FUEnso"}}], "authentication": [ + "did:key:z6MkoTHsgNNrby8JzCNQ1iRLyW5QQ6R8Xuu6AA8igGrMVPUM#z6MkoTHsgNNrby8JzCNQ1iRLyW5QQ6R8Xuu6AA8igGrMVPUM"], "assertionMethod": ["did:key:z6MkoTHsgNNrby8JzCNQ1iRLyW5QQ6R8Xuu6AA8igGrMVPUM#z6MkoTHsgNNrby8JzCNQ1iRLyW5QQ6R8Xuu6AA8igGrMVPUM"]}"""; + + private static final String didDocEssif = """ + {"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/suites/jws-2020/v1"],"id":"did:web:essif.iao.fraunhofer.de","verificationMethod":[{"id":"did:web:essif.iao.fraunhofer.de#owner","type":"JsonWebKey2020", + "controller":"did:web:essif.iao.fraunhofer.de","publicKeyJwk":{"kty":"OKP","crv":"Ed25519","x":"yaHbNw6nj4Pn3nGPHyyTqP-QHXYNJIpkA37PrIOND4c"}}],"service":[{"id":"did:web:essif.iao.fraunhofer.de#issuer-list", + "type":"issuer-list","serviceEndpoint":"https://essif.iao.fraunhofer.de/files/trustlist/federation1.test.train.trust-scheme.de.json"}],"authentication":["did:web:essif.iao.fraunhofer.de#owner"], + "assertionMethod":["did:web:essif.iao.fraunhofer.de#owner"]}"""; + + @Test + public void testDIDResolutionSingleEP() throws Exception { + ResolveRepresentationResult rrr = ResolveRepresentationResult.build(null, didDocSingleEP.getBytes(), null); + when(uniResolver.resolveRepresentation(eq(fhDidTrust), any())).thenReturn(rrr); + + DIDResolver.DIDResolveResult didRes = didResolver.resolveDid(fhDidTrust, null); + assertNotNull(didRes); + assertNotNull(didRes.getDocument()); + assertTrue(didRes.getEndpoints().contains("https://essif.iao.fraunhofer.de/files/policy/schuelerausweis.xml")); + } + + @Test + public void testDIDResolutionManyEP() throws Exception { + ResolveRepresentationResult rrr = ResolveRepresentationResult.build(null, didDocManyEP.getBytes(), null); + when(uniResolver.resolveRepresentation(eq(fhDidTrust), any())).thenReturn(rrr); + + DIDResolver.DIDResolveResult didRes = didResolver.resolveDid(fhDidTrust, null); + assertNotNull(didRes); + assertNotNull(didRes.getDocument()); + assertEquals(2, didRes.getEndpoints().size()); + assertTrue(didRes.getEndpoints().contains("https://essif.iao.fraunhofer.de/firstEP")); + assertTrue(didRes.getEndpoints().contains("https://essif.iao.fraunhofer.de/secondEP")); + } + + @Test + public void testDIDResolutionObjectEP() throws Exception { + ResolveRepresentationResult rrr = ResolveRepresentationResult.build(null, didDocObjectEP.getBytes(), null); + when(uniResolver.resolveRepresentation(eq(fhDidTrust), any())).thenReturn(rrr); + + DIDResolver.DIDResolveResult didRes = didResolver.resolveDid(fhDidTrust, null); + assertNotNull(didRes); + assertNotNull(didRes.getDocument()); + assertEquals(2, didRes.getEndpoints().size()); + assertTrue(didRes.getEndpoints().contains("https://essif.iao.fraunhofer.de/firstEP")); + assertTrue(didRes.getEndpoints().contains("https://essif.iao.fraunhofer.de/secondEP")); + } + + @Test + public void testDIDConfigResolution() throws Exception { + String did = "did:key:z6MkoTHsgNNrby8JzCNQ1iRLyW5QQ6R8Xuu6AA8igGrMVPUM"; + ResolveRepresentationResult rrr = ResolveRepresentationResult.build(null, didDocKeyVC.getBytes(), null); + when(uniResolver.resolveRepresentation(eq(did), any())).thenReturn(rrr); + + String origin = "https://identity.foundation"; + boolean verified = didResolver.resolveDidConfig(origin); + assertTrue(verified); + } + + @Test + public void testDIDAndConfigResolution() throws Exception { + ResolveRepresentationResult rrr = ResolveRepresentationResult.build(null, didDocEssif.getBytes(), null); + when(uniResolver.resolveRepresentation(eq(fhDidEssif), any())).thenReturn(rrr); + + DIDResolver.DIDResolveResult didRes = didResolver.resolveDid(fhDidEssif, null); + assertNotNull(didRes); + assertNotNull(didRes.getDocument()); + assertEquals("https://essif.iao.fraunhofer.de", didRes.getOrigin()); + assertEquals(List.of("https://essif.iao.fraunhofer.de/files/trustlist/federation1.test.train.trust-scheme.de.json"), didRes.getEndpoints()); + + // now test config.. + boolean verified = didResolver.resolveDidConfig("https://essif.iao.fraunhofer.de"); + assertTrue(verified); + } + + @Test + public void testTLResolution() throws Exception { + ResolveRepresentationResult rrr = ResolveRepresentationResult.build(null, didDocEssif.getBytes(), null); + when(uniResolver.resolveRepresentation(eq(fhDidEssif), any())).thenReturn(rrr); + + DIDResolver.VCResolveResult vcRes = didResolver.resolveVC("https://essif.iao.fraunhofer.de/files/trustlist/federation1.test.train.trust-scheme.de.json"); + assertEquals("https://tspa.trust-scheme.de/tspa_train_domain/api/v1/scheme/federation1.test.train.trust-scheme.de", vcRes.getTrustListUri()); + assertTrue(vcRes.isVerified()); + } + +} diff --git a/service/src/test/java/eu/xfsc/train/tcr/server/service/DNSResolverTest.java b/service/src/test/java/eu/xfsc/train/tcr/server/service/DNSResolverTest.java new file mode 100644 index 0000000000000000000000000000000000000000..459945a030f2c3d896275270844f88724361fcfc --- /dev/null +++ b/service/src/test/java/eu/xfsc/train/tcr/server/service/DNSResolverTest.java @@ -0,0 +1,5 @@ +package eu.xfsc.train.tcr.server.service; + +public class DNSResolverTest { + +} diff --git a/service/src/test/java/eu/xfsc/train/tcr/server/service/TLResolverTest.java b/service/src/test/java/eu/xfsc/train/tcr/server/service/TLResolverTest.java new file mode 100644 index 0000000000000000000000000000000000000000..431e7bac332a33e775cf5d52671a4a3834f3d845 --- /dev/null +++ b/service/src/test/java/eu/xfsc/train/tcr/server/service/TLResolverTest.java @@ -0,0 +1,113 @@ +package eu.xfsc.train.tcr.server.service; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.net.URL; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import eu.europa.esig.trustedlist.jaxb.tsl.TrustStatusListType; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@SpringBootTest +@ActiveProfiles("test") +@ExtendWith(SpringExtension.class) +public class TLResolverTest { + + private static final String fh_TrustList_Uri = "https://tspa.trust-scheme.de/tspa_train_domain/api/v1/scheme/federation1.test.train.trust-scheme.de"; + private static final String fh_TrustList_Hash = "Qmb6gRAKQmVopCVDs1TMspu3crtoQ7JLLWY6rYNb5yVeWY"; + + @Autowired + private TLResolver tlResolver; + + @Test + public void testTLResolveXMLFile() throws Exception { + URL url = this.getClass().getResource("/SampleTrustList.xml"); + File file = new File(url.getFile()); + assertTrue(file.exists()); + TrustStatusListType list = tlResolver.resolveTL(file.getCanonicalFile().toURI().toString()); + assertNotNull(list); + } + + @Test + public void testTLResolveJSONFile() throws Exception { + URL url = this.getClass().getResource("/SampleTrustList.json"); + File file = new File(url.getFile()); + assertTrue(file.exists()); + TrustStatusListType list = tlResolver.resolveTL(file.getCanonicalFile().toURI().toString()); + assertNotNull(list); + } + + @Test + public void testTLResolveXMLUri() throws Exception { + TrustStatusListType list = tlResolver.resolveTL(fh_TrustList_Uri, false); + assertNotNull(list); + } + + @Test + public void testTLResolveWithHash() throws Exception { + TLResolver.TLResolveResult tlRes = tlResolver.resolveTLHash(fh_TrustList_Uri, fh_TrustList_Hash); + assertNotNull(tlRes); + assertTrue(tlRes.isHashVerified()); + } + + @Test + public void testTLValidationXML() throws Exception { + URL url = this.getClass().getResource("/schuelerausweis.xml"); + File file = new File(url.getFile()); + assertTrue(file.exists()); + + boolean valid = tlResolver.validateTL(file.getCanonicalFile().toURI().toString()); + assertTrue(valid); + } + + @Test + public void testTLValidationJSON() throws Exception { + URL url = this.getClass().getResource("/FHTrustList.json"); + File file = new File(url.getFile()); + assertTrue(file.exists()); + boolean valid = tlResolver.validateTL(file.getCanonicalFile().toURI().toString()); + assertTrue(valid); + } + + @Test + public void testValidateSampleTrustList() throws Exception { + // File cache = new File("cache"); + // cache.deleteOnExit(); + + URL url = this.getClass().getResource("/SampleTrustList.xml"); + File file = new File(url.getFile()); + assertTrue(file.exists()); + + tlResolver.validateDocument(file.getPath()); + // assertTrue(valid); + } + + @Test + public void testValidateTL_21() throws Exception { + URL url = this.getClass().getResource("/TL-21.xml"); + File file = new File(url.getFile()); + assertTrue(file.exists()); + + long stamp = System.currentTimeMillis(); + tlResolver.validateDocument(file.getPath()); + stamp = System.currentTimeMillis() - stamp; + log.info("testValidateTL_21; first validation took {} ms", stamp); + + stamp = System.currentTimeMillis(); + tlResolver.validateDocument(file.getPath()); + stamp = System.currentTimeMillis() - stamp; + log.info("testValidateTL_21; second validation took {} ms", stamp); + } + +} \ No newline at end of file diff --git a/service/src/test/resources/FHTrustList.json b/service/src/test/resources/FHTrustList.json new file mode 100644 index 0000000000000000000000000000000000000000..e8ecaeaea7f0b33e64413b4c2b00889e05c67f17 --- /dev/null +++ b/service/src/test/resources/FHTrustList.json @@ -0,0 +1,121 @@ +{ + "TrustServiceStatusList": { + "SchemeInformation": { + "TSLVersionIdentifier": "1", + "TSLSequenceNumber": "1", + "TSLType": "http://TRAIN/TrstSvc/TrustedList/TSLType/federation1-POC", + "SchemeOperatorName": { + "Name": "Federation 1" + }, + "SchemeOperatorAddress": { + "PostalAddresses": { + "PostalAddress": { + "StreetAddress": "Hauptsrasse", + "Locality": "Stuttgart", + "PostalCode": "70563", + "CountryName": "DE" + } + }, + "ElectronicAddress": { + "URI": "mailto:admin@federation1.de" + } + }, + "SchemeName": { + "Name": "federation1.train.trust-scheme.de" + }, + "SchemeInformationURI": { + "URI": "https://TRAIN/interoperability/federation-Directory" + }, + "SchemeTypeCommunityRules": { + "URI": "https://TrustScheme_TRAIN.example.com/en/federation1-dir-rules.html" + }, + "SchemeTerritory": "EU", + "PolicyOrLegalNotice": { + "TSLLegalNotice": "The applicable legal framework for the present trusted list is TBD. Valid legal notice text will be created." + }, + "PointersToOtherTSL": "", + "ListIssueDateTime": "2021-12-15T00:00:00Z", + "LastUpdated": "2021-12-15T00:00:00Z" + } + }, + "TrustServiceProvider": { + "TSPCurrentStatus": "string", + "StatusStartingTime": "string", + "TSPInformation": { + "TSPName": "string", + "TSPLegalName": "string", + "TSPRole": "string", + "TrustSchemeName": "string", + "OtherTSL": "string", + "TSPInformationURI": "string", + "TSPLegalBasis": "string", + "TSPEntityIdentifierList": { + "TSPEntityIdendifier": [ + { + "Type": "string", + "Value": "string" + } + ] + }, + "TSPCertificationList": { + "TSPCertification": [ + { + "Type": "string", + "Value": "string" + } + ] + }, + "TSPKeywords": "string", + "Address": { + "ElectronicAddress": "string", + "PostalAddress": { + "StreetAddress1": "string", + "StreetAddress2": "string", + "City": "string", + "State": "string", + "Country": "string", + "PostalCode": "string" + } + } + }, + "SubmitterInfo": { + "Name": "string", + "Address": { + "ElectronicAddress": "string", + "PostalAddress": { + "StreetAddress1": "string", + "StreetAddress2": "string", + "City": "string", + "State": "string", + "Country": "string", + "PostalCode": "string" + } + } + }, + "TSPServices": { + "TSPService": [ + { + "ServiceCurrentStatus": "string", + "StatusStartingTime": "string", + "ServiceName": "string", + "ServiceTypeIdentifier": "string", + "ServiceSupplyPoint": "string", + "ServiceDefinitionURI": "string", + "ServiceDigitalIdentity": { + "Value": "string", + "KeyType": "string" + }, + "AdditionalServiceInformation": { + "ServiceIssuedCredentialTypes": [ + { + "CredentialType": "string" + } + ], + "ServiceGovernanceURI": "string", + "ServiceBusinessRulesURI": "string" + } + } + ] + } + } +} \ No newline at end of file diff --git a/service/src/test/resources/SampleTrustList.json b/service/src/test/resources/SampleTrustList.json new file mode 100644 index 0000000000000000000000000000000000000000..9801328489268c13ba0f12b12b296d43842a3995 --- /dev/null +++ b/service/src/test/resources/SampleTrustList.json @@ -0,0 +1,320 @@ +{ + "TrustServiceStatusList": { + "SchemeInformation": { + "HistoricalInformationPeriod": "65535", + "ListIssueDateTime": "2022-09-27T00:00:00Z", + "NextUpdate": "2022-12-27T00:00:00Z", + "PolicyOrLegalNotice": { + "TSLLegalNotice": { + "#text": "This is an experimental list for the GXFS Federation Notary.", + "@xml:lang": "en" + } + }, + "SchemeInformationURI": { + "URI": { + "#text": "https://dl.gi.de/handle/20.500.12116/38702", + "@xml:lang": "en" + } + }, + "SchemeName": { + "Name": { + "#text": "DE:TRAIN", + "@xml:lang": "en" + } + }, + "SchemeOperatorAddress": { + "ElectronicAddress": { + "URI": [ + { + "#text": "mailto:mail@federation1.com", + "@xml:lang": "en" + }, + { + "#text": "https://www.federation1.com", + "@xml:lang": "en" + } + ] + }, + "PostalAddresses": { + "PostalAddress": { + "@xml:lang": "en", + "CountryName": "DE", + "Locality": "K\u00f6ln", + "PostalCode": "50825", + "StreetAddress": "Lichtstra\u00dfe 43h" + } + } + }, + "SchemeOperatorName": { + "Name": { + "#text": "Federation 1 Notary", + "@xml:lang": "en" + } + }, + "SchemeTerritory": "GLOBAL", + "SchemeTypeCommunityRules": { + "URI": { + "#text": "https://train.trustscheme.de /schemerules/ngi.train.trustscheme.de", + "@xml:lang": "en" + } + }, + "StatusDeterminationApproach": "http://uri.etsi.org/TrstSvc/TrustedList/StatusDetn/EUappropriate", + "TSLSequenceNumber": "1", + "TSLType": "http://uri.etsi.org/TrstSvc/TrustedList/TSLType/EUgeneric", + "TSLVersionIdentifier": "5" + }, + "TrustServiceProviderList": { + "TrustServiceProvider": { + "StatusStartingTime": { + "dateTime": "2022-11-22T00:00:00Z" + }, + "TSPCurrentStatus": { + "Name": { + "#text": "Active", + "@xml:lang": "en" + } + }, + "TSPInformation": { + "TSPAddress": { + "ElectronicAddress": { + "URI": { + "#text": "mailto:mail.notary1@federation.com", + "@xml:lang": "en" + } + }, + "PostalAddresses": { + "PostalAddress": { + "@xml:lang": "en", + "CountryName": "DE", + "Locality": "K\u00f6ln", + "PostalCode": "50825", + "StreetAddress": "Lichtstra\u00dfe 43h" + } + } + }, + "TSPCertificationList": { + "TSPCertification": [ + { + "@xml:lang": "en", + "Scope": null, + "Type": "LEI", + "Value": "1234567" + }, + { + "@xml:lang": "en", + "Scope": null, + "Type": "Gaia-X Compliance", + "Value": "1234567" + }, + { + "@xml:lang": "en", + "Scope": null, + "Type": "eidas", + "Value": "1234567" + } + ] + }, + "TSPInformationURI": { + "URI": { + "#text": "https://notary1.info/TRAIN/info", + "@xml:lang": "en" + } + }, + "TSPName": { + "Name": { + "#text": "Notary 1", + "@xml:lang": "en" + } + }, + "TSPTradeName": { + "Name": [ + { + "#text": "NTRUK-SC090312", + "@xml:lang": "en" + }, + { + "#text": "Notary Federation 1", + "@xml:lang": "en" + } + ] + } + }, + "TSPServices": { + "TSPService": [ + { + "ServiceInformation": { + "AdditionalServiceInformation": { + "ServiceBusinessRules": "https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX%3A32021R0953", + "ServiceCredentialTypes": { + "CredentialType": [ + "X.509", + "did:web" + ] + }, + "ServiceGovernanceURI": "https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX%3A32021R0953" + }, + "ServiceDigitalIdentity": { + "did": "did:web:notary.federation1.com", + "x509": "242364735r634785634857348957349587395473957395739573932458743rz3ufgf3hrfv3hfv3hfv3hfv3hf" + }, + "ServiceName": { + "Name": { + "#text": "Federation Participant Membership Credential", + "@xml:lang": "en" + } + }, + "ServiceStatus": "http://uri.etsi.org/TrstSvc/TrustedList/Svcstatus/granted", + "ServiceSupplyPoints": { + "ServiceSupplyPoint": "https://participant.membership.notary1.federation.com" + }, + "ServiceTypeIdentifier": "https://participant.membership.notary1.federation.com", + "StatusStartingTime": "2022-09-29T22:00:00Z", + "TSPServiceDefinitionURI": { + "URI": "https://notary1.info/schema/V-2022-1/participant_membership.json" + } + } + }, + { + "ServiceInformation": { + "AdditionalServiceInformation": { + "ServiceBusinessRules": "https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX%3A32021R0953", + "ServiceCredentialTypes": { + "CredentialType": [ + "X.509", + "did:web" + ] + }, + "ServiceGovernanceURI": "https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX%3A32021R0953" + }, + "ServiceDigitalIdentity": { + "did": "did:web:notary.federation1.com", + "x509": "242364735r634785634857348957349587395473957395739573932458743rz3ufgf3hrfv3hfv3hfv3hfv3hf" + }, + "ServiceName": { + "Name": { + "#text": "Federation Principal Credential", + "@xml:lang": "en" + } + }, + "ServiceStatus": "http://uri.etsi.org/TrstSvc/TrustedList/Svcstatus/granted", + "ServiceSupplyPoints": { + "ServiceSupplyPoint": "https://verifier.research.identiproof.io/" + }, + "ServiceTypeIdentifier": "https://principal.membership.notary1.federation.com", + "StatusStartingTime": "2022-09-29T22:00:00Z", + "TSPServiceDefinitionURI": { + "URI": "https://notary1.info/schema/V-2022-1/participant_membership.json" + } + } + }, + { + "ServiceInformation": { + "AdditionalServiceInformation": { + "ServiceBusinessRules": "https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX%3A32021R0953", + "ServiceCredentialTypes": { + "CredentialType": [ + "X.509", + "did:web" + ] + }, + "ServiceGovernanceURI": "https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX%3A32021R0953" + }, + "ServiceDigitalIdentity": { + "did": "did:web:notary.federation1.com", + "x509": "242364735r634785634857348957349587395473957395739573932458743rz3ufgf3hrfv3hfv3hfv3hfv3hf" + }, + "ServiceName": { + "Name": { + "#text": "Federation Consumer Credential", + "@xml:lang": "en" + } + }, + "ServiceStatus": "http://uri.etsi.org/TrstSvc/TrustedList/Svcstatus/granted", + "ServiceSupplyPoints": { + "ServiceSupplyPoint": "https://verifier.research.identiproof.io/" + }, + "ServiceTypeIdentifier": "https://consumer.membership.notary1.federation.com", + "StatusStartingTime": "2022-09-29T22:00:00Z", + "TSPServiceDefinitionURI": { + "URI": "https://notary1.info/schema/V-2022-1/participant_membership.json" + } + } + }, + { + "ServiceInformation": { + "AdditionalServiceInformation": { + "ServiceBusinessRules": "https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX%3A32021R0953", + "ServiceCredentialTypes": { + "CredentialType": [ + "X.509", + "did:web" + ] + }, + "ServiceGovernanceURI": "https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX%3A32021R0953" + }, + "ServiceDigitalIdentity": { + "did": "did:web:notary.federation1.com", + "x509": "242364735r634785634857348957349587395473957395739573932458743rz3ufgf3hrfv3hfv3hfv3hfv3hf" + }, + "ServiceName": { + "Name": { + "#text": "Federation Resource Credential", + "@xml:lang": "en" + } + }, + "ServiceStatus": "http://uri.etsi.org/TrstSvc/TrustedList/Svcstatus/granted", + "ServiceSupplyPoints": { + "ServiceSupplyPoint": "https://resource.membership.notary1.federation.com" + }, + "ServiceTypeIdentifier": "https://resource.membership.notary1.federation.com", + "StatusStartingTime": "2022-09-29T22:00:00Z", + "TSPServiceDefinitionURI": { + "URI": "https://notary1.info/schema/V-2022-1/participant_membership.json" + } + } + }, + { + "ServiceInformation": { + "AdditionalServiceInformation": { + "ServiceBusinessRules": "https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX%3A32021R0953", + "ServiceCredentialTypes": { + "CredentialType": [ + "X.509", + "did:web" + ] + }, + "ServiceGovernanceURI": "https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX%3A32021R0953" + }, + "ServiceDigitalIdentity": { + "did": "did:web:notary.federation1.com", + "x509": "242364735r634785634857348957349587395473957395739573932458743rz3ufgf3hrfv3hfv3hfv3hfv3hf" + }, + "ServiceName": { + "Name": { + "#text": "Federation Onboarding Credential", + "@xml:lang": "en" + } + }, + "ServiceStatus": "http://uri.etsi.org/TrstSvc/TrustedList/Svcstatus/granted", + "ServiceSupplyPoints": { + "ServiceSupplyPoint": "https://onboarding.membership.notary1.federation.com" + }, + "ServiceTypeIdentifier": "https://onboarding.membership.notary1.federation.com", + "StatusStartingTime": "2022-09-29T22:00:00Z", + "TSPServiceDefinitionURI": { + "URI": "https://notary1.info/schema/V-2022-1/participant_membership.json" + } + } + } + ] + }, + "UID": { + "Name": { + "#text": "2325", + "@xml:lang": "en" + } + } + } + } + } +} \ No newline at end of file diff --git a/service/src/test/resources/SampleTrustList.xml b/service/src/test/resources/SampleTrustList.xml new file mode 100644 index 0000000000000000000000000000000000000000..dc9e273c0f5cf90ad84ea17a2975c6c4f9f58457 --- /dev/null +++ b/service/src/test/resources/SampleTrustList.xml @@ -0,0 +1,281 @@ +<TrustServiceStatusList + xmlns="http://uri.etsi.org/02231/v2#" + xmlns:ns2="http://www.w3.org/2000/09/xmldsig#" + xmlns:ns3="http://uri.etsi.org/01903/v1.3.2#" + xmlns:ns4="http://uri.etsi.org/02231/v2/additionaltypes#" + xmlns:ns5="http://uri.etsi.org/TrstSvc/SvcInfoExt/eSigDir-1999-93-EC-TrustedList/#" + xmlns:ns6="http://uri.etsi.org/01903/v1.4.1#" + TSLTag="http://uri.etsi.org/19612/TSLTag"> + <SchemeInformation> + <TSLVersionIdentifier>5</TSLVersionIdentifier> + <TSLSequenceNumber>1</TSLSequenceNumber> + <TSLType>http://uri.etsi.org/TrstSvc/TrustedList/TSLType/EUgeneric</TSLType> + <SchemeOperatorName> + <Name xml:lang="en">Federation 1 Notary</Name> + </SchemeOperatorName> + <SchemeOperatorAddress> + <PostalAddresses> + <PostalAddress xml:lang="en"> + <StreetAddress>Lichtstraße 43h</StreetAddress> + <Locality>Köln</Locality> + <PostalCode>50825</PostalCode> + <CountryName>DE</CountryName> + </PostalAddress> + </PostalAddresses> + <ElectronicAddress> + <URI xml:lang="en">mailto:mail@federation1.com</URI> + <URI xml:lang="en">https://www.federation1.com</URI> + </ElectronicAddress> + </SchemeOperatorAddress> + <SchemeName> + <Name xml:lang="en">DE:TRAIN</Name> + </SchemeName> + <SchemeInformationURI> + <URI xml:lang="en">https://dl.gi.de/handle/20.500.12116/38702</URI> + </SchemeInformationURI> + <StatusDeterminationApproach>http://uri.etsi.org/TrstSvc/TrustedList/StatusDetn/EUappropriate</StatusDeterminationApproach> + <SchemeTypeCommunityRules> + <URI xml:lang="en">https://train.trustscheme.de/schemerules/ngi.train.trustscheme.de</URI> + </SchemeTypeCommunityRules> + <SchemeTerritory>GLOBAL</SchemeTerritory> + <PolicyOrLegalNotice> + <TSLLegalNotice xml:lang="en">This is an experimental list for the GXFS Federation Notary.</TSLLegalNotice> + </PolicyOrLegalNotice> + <HistoricalInformationPeriod>65535</HistoricalInformationPeriod> + <ListIssueDateTime>2022-09-27T00:00:00Z</ListIssueDateTime> + <NextUpdate> + <dateTime>2023-12-27T00:00:00Z</dateTime> + </NextUpdate> + </SchemeInformation> + <TrustServiceProviderList> + <TrustServiceProvider> + <!-- + <UID> + <Name xml:lang="en">2325</Name> + </UID> + --> + <TSPInformation> + <TSPName> + <Name xml:lang="en">Notary 1</Name> + </TSPName> + <TSPTradeName> + <Name xml:lang="en">NTRUK-SC090312</Name> + <Name xml:lang="en">Notary Federation 1 </Name> + </TSPTradeName> + <TSPAddress> + <PostalAddresses> + <PostalAddress xml:lang="en"> + <StreetAddress>Lichtstraße 43h</StreetAddress> + <Locality>Köln</Locality> + <PostalCode>50825</PostalCode> + <CountryName>DE</CountryName> + </PostalAddress> + </PostalAddresses> + <ElectronicAddress> + <URI xml:lang="en">mailto:mail.notary1@federation.com</URI> + </ElectronicAddress> + </TSPAddress> + <TSPInformationURI> + <URI xml:lang="en">https://notary1.info/TRAIN/info</URI> + </TSPInformationURI> + <!-- + <TSPCertificationList> + <TSPCertification xml:lang="en"> + <Type>LEI</Type> + <Value>1234567</Value> + <Scope></Scope> + </TSPCertification> + <TSPCertification xml:lang="en"> + <Type>Gaia-X Compliance</Type> + <Value>1234567</Value> + <Scope></Scope> + </TSPCertification> + <TSPCertification xml:lang="en"> + <Type>eidas</Type> + <Value>1234567</Value> + <Scope></Scope> + </TSPCertification> + </TSPCertificationList> + --> + </TSPInformation> + <TSPServices> + <TSPService> + <ServiceInformation> + <ServiceTypeIdentifier>https://participant.membership.notary1.federation.com</ServiceTypeIdentifier> + <ServiceName> + <Name xml:lang="en">Federation Participant Membership Credential</Name> + </ServiceName> + <ServiceDigitalIdentity> + <DigitalId><Other>242364735r634785634857348957349587395473957395739573932458743rz3ufgf3hrfv3hfv3hfv3hfv3hf</Other></DigitalId> + <!-- + <x509>242364735r634785634857348957349587395473957395739573932458743rz3ufgf3hrfv3hfv3hfv3hfv3hf</x509> + <did>did:web:notary.federation1.com</did> + --> + </ServiceDigitalIdentity> + <ServiceStatus>http://uri.etsi.org/TrstSvc/TrustedList/Svcstatus/granted</ServiceStatus> + <StatusStartingTime>2022-09-29T22:00:00Z</StatusStartingTime> + <ServiceSupplyPoints> + <ServiceSupplyPoint>https://participant.membership.notary1.federation.com</ServiceSupplyPoint> + </ServiceSupplyPoints> + <TSPServiceDefinitionURI> + <URI xml:lang="en">https://notary1.info/schema/V-2022-1/participant_membership.json</URI> + </TSPServiceDefinitionURI> + <!-- + <AdditionalServiceInformation> + <ServiceCredentialTypes> + <CredentialType>X.509</CredentialType> + <CredentialType>did:web</CredentialType> + </ServiceCredentialTypes> + <ServiceGovernanceURI>https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX%3A32021R0953</ServiceGovernanceURI> + <ServiceBusinessRules>https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX%3A32021R0953</ServiceBusinessRules> + </AdditionalServiceInformation> + --> + </ServiceInformation> + </TSPService> + <TSPService> + <ServiceInformation> + <ServiceTypeIdentifier>https://principal.membership.notary1.federation.com</ServiceTypeIdentifier> + <ServiceName> + <Name xml:lang="en">Federation Principal Credential</Name> + </ServiceName> + <ServiceDigitalIdentity> + <DigitalId><Other>242364735r634785634857348957349587395473957395739573932458743rz3ufgf3hrfv3hfv3hfv3hfv3hf</Other></DigitalId> + <!-- + <x509>242364735r634785634857348957349587395473957395739573932458743rz3ufgf3hrfv3hfv3hfv3hfv3hf</x509> + <did>did:web:notary.federation1.com</did> + --> + </ServiceDigitalIdentity> + <ServiceStatus>http://uri.etsi.org/TrstSvc/TrustedList/Svcstatus/granted</ServiceStatus> + <StatusStartingTime>2022-09-29T22:00:00Z</StatusStartingTime> + <ServiceSupplyPoints> + <ServiceSupplyPoint>https://verifier.research.identiproof.io/ + </ServiceSupplyPoint> + </ServiceSupplyPoints> + <TSPServiceDefinitionURI> + <URI xml:lang="en">https://notary1.info/schema/V-2022-1/participant_membership.json</URI> + </TSPServiceDefinitionURI> + <!-- + <AdditionalServiceInformation> + <ServiceCredentialTypes> + <CredentialType>X.509</CredentialType> + <CredentialType>did:web</CredentialType> + </ServiceCredentialTypes> + <ServiceGovernanceURI>https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX%3A32021R0953</ServiceGovernanceURI> + <ServiceBusinessRules>https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX%3A32021R0953</ServiceBusinessRules> + </AdditionalServiceInformation> + --> + </ServiceInformation> + </TSPService> + <TSPService> + <ServiceInformation> + <ServiceTypeIdentifier>https://consumer.membership.notary1.federation.com</ServiceTypeIdentifier> + <ServiceName> + <Name xml:lang="en">Federation Consumer Credential</Name> + </ServiceName> + <ServiceDigitalIdentity> + <DigitalId><Other>242364735r634785634857348957349587395473957395739573932458743rz3ufgf3hrfv3hfv3hfv3hfv3hf</Other></DigitalId> + <!-- + <x509>242364735r634785634857348957349587395473957395739573932458743rz3ufgf3hrfv3hfv3hfv3hfv3hf</x509> + <did>did:web:notary.federation1.com</did> + --> + </ServiceDigitalIdentity> + <ServiceStatus>http://uri.etsi.org/TrstSvc/TrustedList/Svcstatus/granted</ServiceStatus> + <StatusStartingTime>2022-09-29T22:00:00Z</StatusStartingTime> + <ServiceSupplyPoints> + <ServiceSupplyPoint>https://verifier.research.identiproof.io/</ServiceSupplyPoint> + </ServiceSupplyPoints> + <TSPServiceDefinitionURI> + <URI xml:lang="en">https://notary1.info/schema/V-2022-1/participant_membership.json</URI> + </TSPServiceDefinitionURI> + <!-- + <AdditionalServiceInformation> + <ServiceCredentialTypes> + <CredentialType>X.509</CredentialType> + <CredentialType>did:web</CredentialType> + </ServiceCredentialTypes> + <ServiceGovernanceURI>https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX%3A32021R0953</ServiceGovernanceURI> + <ServiceBusinessRules>https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX%3A32021R0953</ServiceBusinessRules> + </AdditionalServiceInformation> + --> + </ServiceInformation> + </TSPService> + <TSPService> + <ServiceInformation> + <ServiceTypeIdentifier>https://resource.membership.notary1.federation.com</ServiceTypeIdentifier> + <ServiceName> + <Name xml:lang="en">Federation Resource Credential</Name> + </ServiceName> + <ServiceDigitalIdentity> + <DigitalId><Other>242364735r634785634857348957349587395473957395739573932458743rz3ufgf3hrfv3hfv3hfv3hfv3hf</Other></DigitalId> + <!-- + <x509>242364735r634785634857348957349587395473957395739573932458743rz3ufgf3hrfv3hfv3hfv3hfv3hf</x509> + <did>did:web:notary.federation1.com</did> + --> + </ServiceDigitalIdentity> + <ServiceStatus>http://uri.etsi.org/TrstSvc/TrustedList/Svcstatus/granted</ServiceStatus> + <StatusStartingTime>2022-09-29T22:00:00Z</StatusStartingTime> + <ServiceSupplyPoints> + <ServiceSupplyPoint>https://resource.membership.notary1.federation.com</ServiceSupplyPoint> + </ServiceSupplyPoints> + <TSPServiceDefinitionURI> + <URI xml:lang="en">https://notary1.info/schema/V-2022-1/participant_membership.json</URI> + </TSPServiceDefinitionURI> + <!-- + <AdditionalServiceInformation> + <ServiceCredentialTypes> + <CredentialType>X.509</CredentialType> + <CredentialType>did:web</CredentialType> + </ServiceCredentialTypes> + <ServiceGovernanceURI>https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX%3A32021R0953</ServiceGovernanceURI> + <ServiceBusinessRules>https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX%3A32021R0953</ServiceBusinessRules> + </AdditionalServiceInformation> + --> + </ServiceInformation> + </TSPService> + <TSPService> + <ServiceInformation> + <ServiceTypeIdentifier>https://onboarding.membership.notary1.federation.com</ServiceTypeIdentifier> + <ServiceName> + <Name xml:lang="en">Federation Onboarding Credential</Name> + </ServiceName> + <ServiceDigitalIdentity> + <DigitalId><Other>242364735r634785634857348957349587395473957395739573932458743rz3ufgf3hrfv3hfv3hfv3hfv3hf</Other></DigitalId> + <!-- + <x509>242364735r634785634857348957349587395473957395739573932458743rz3ufgf3hrfv3hfv3hfv3hfv3hf</x509> + <did>did:web:notary.federation1.com</did> + --> + </ServiceDigitalIdentity> + <ServiceStatus>http://uri.etsi.org/TrstSvc/TrustedList/Svcstatus/granted</ServiceStatus> + <StatusStartingTime>2022-09-29T22:00:00Z</StatusStartingTime> + <ServiceSupplyPoints> + <ServiceSupplyPoint>https://onboarding.membership.notary1.federation.com</ServiceSupplyPoint> + </ServiceSupplyPoints> + <TSPServiceDefinitionURI> + <URI xml:lang="en">https://notary1.info/schema/V-2022-1/participant_membership.json</URI> + </TSPServiceDefinitionURI> + <!-- + <AdditionalServiceInformation> + <ServiceCredentialTypes> + <CredentialType>X.509</CredentialType> + <CredentialType>did:web</CredentialType> + </ServiceCredentialTypes> + <ServiceGovernanceURI>https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX%3A32021R0953</ServiceGovernanceURI> + <ServiceBusinessRules>https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX%3A32021R0953</ServiceBusinessRules> + </AdditionalServiceInformation> + --> + </ServiceInformation> + </TSPService> + </TSPServices> + <!-- + <TSPCurrentStatus> + <Name xml:lang="en">Active</Name> + </TSPCurrentStatus> + --> + <!-- + <StatusStartingTime> + <dateTime>2022-11-22T00:00:00Z</dateTime> + </StatusStartingTime> + --> + </TrustServiceProvider> + </TrustServiceProviderList> +</TrustServiceStatusList> \ No newline at end of file diff --git a/service/src/test/resources/TL-21.xml b/service/src/test/resources/TL-21.xml new file mode 100644 index 0000000000000000000000000000000000000000..fed7309a67ce2487b167c5fe1c6e3604ec14ed6a --- /dev/null +++ b/service/src/test/resources/TL-21.xml @@ -0,0 +1,127 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?><TrustServiceStatusList xmlns="http://uri.etsi.org/02231/v2#" xmlns:ns2="http://www.w3.org/2000/09/xmldsig#" xmlns:ns3="http://uri.etsi.org/02231/v2/additionaltypes#" xmlns:ns4="http://uri.etsi.org/01903/v1.3.2#" xmlns:ns5="http://uri.etsi.org/TrstSvc/SvcInfoExt/eSigDir-1999-93-EC-TrustedList/#" xmlns:ns6="http://uri.etsi.org/01903/v1.4.1#" Id="TL12345" TSLTag="http://uri.etsi.org/19612/TSLTag"> + <SchemeInformation> + <TSLVersionIdentifier>5</TSLVersionIdentifier> + <TSLSequenceNumber>1</TSLSequenceNumber> + <TSLType>http://uri.etsi.org/TrstSvc/TrustedList/TSLType/EUgeneric</TSLType> + <SchemeOperatorName> + <Name xml:lang="en">LU Operator name</Name> + </SchemeOperatorName> + <SchemeOperatorAddress> + <PostalAddresses> + <PostalAddress xml:lang="en"> + <StreetAddress>TEST</StreetAddress> + <Locality>TEST</Locality> + <PostalCode>TEST</PostalCode> + <CountryName>EU</CountryName> + </PostalAddress> + </PostalAddresses> + <ElectronicAddress> + <URI xml:lang="en">mailto:test@test.test</URI> + </ElectronicAddress> + </SchemeOperatorAddress> + <SchemeName> + <Name xml:lang="en">LU: TEST</Name> + </SchemeName> + <SchemeInformationURI> + <URI xml:lang="en">https://eidas.ec.europa.eu/efda/api/v2/validation-tests/testcase/test-disclaimer/</URI> + </SchemeInformationURI> + <StatusDeterminationApproach>https://eidas.ec.europa.eu/efda/api/v2/validation-tests/testcase/test-disclaimer/</StatusDeterminationApproach> + <SchemeTypeCommunityRules> + <URI xml:lang="en">http://uri.etsi.org/TrstSvc/TrustedList/schemerules/EUcommon</URI> + <URI xml:lang="en">http://uri.etsi.org/TrstSvc/TrustedList/schemerules/LU</URI> + </SchemeTypeCommunityRules> + <SchemeTerritory>LU</SchemeTerritory> + <PolicyOrLegalNotice> + <TSLLegalNotice xml:lang="en">TEST</TSLLegalNotice> + </PolicyOrLegalNotice> + <HistoricalInformationPeriod>65535</HistoricalInformationPeriod> + <PointersToOtherTSL> + <OtherTSLPointer> + <ServiceDigitalIdentities> + <ServiceDigitalIdentity> + <DigitalId> + <X509Certificate>MIIDRjCCAi6gAwIBAgIBATANBgkqhkiG9w0BAQ0FADBVMRQwEgYDVQQDDAtDRVJULUxPVEwtMjEYMBYGA1UECgwPRVUgT3JnYW5pemF0aW9uMRYwFAYDVQQLDA1DRVJUIEZPUiBURVNUMQswCQYDVQQGEwJMVTAeFw0yMjExMTIwMDAwMDhaFw0yNDExMTIwMDAwMDhaMFUxFDASBgNVBAMMC0NFUlQtTE9UTC0yMRgwFgYDVQQKDA9FVSBPcmdhbml6YXRpb24xFjAUBgNVBAsMDUNFUlQgRk9SIFRFU1QxCzAJBgNVBAYTAkxVMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzy2BuWbXWS5L/xSdiz51KKncA04f8drUqjzAJeie2C7nsHdDJchmy+q5jwK6leCn1rNHpSaN9RZlHDrQb6TkDJ3QksCpQNZcVvxAafF//0MkJu9aSHOH5C7hLdM0TEVUz8DSbBQFqO81dh1zR148/r+wJ7E1ecth5N3f6xtkst1BiAfvGfLdfpc0T8cx+sKBBxRnfVBBTUR09XjJ8NSnsBA7ioGla6KlD/hBRkiGgJd4mOC4m5hrKvyxv4UXdciv2AF7FpO76bdwAdvXKUihye8KEj1kzRCOc30EVzxjTDYnWvaHIXsiwmCRvnLE3spgK+f6jWiQRWSfSgoY4W/pGQIDAQABoyEwHzAdBgNVHQ4EFgQUVR/75/0qY5gTPFwN3SA2OJepSegwDQYJKoZIhvcNAQENBQADggEBABB5ohWXYSeQscfdN1M4OJlnYxGNjrHwJrKwwRusTxr8m6n2aNErw0s6nNBt74gesLUmsQSF2sVa6+RqSZ5+ce9A/lQZgN1UsU4EQGZuyE+KaNHc3pmLkiCgdvpfWMh+bXGNqI9AzSoyx+qqGDc+Ylwj5Py2sJui+8qnNzcnfvYyIsSmRZVvILcnxoFHpnd8U7grhZxdodjAF1WqwWhZrzMJK6uaTKmVi0lHSrWpanj+zFzU1c5XNDGFH6U5JLF4zk9M4a+nv77vZJ46btbvoAQmtSayGm/reNVHhKyUkm8dgN36EuDCcl7c53MGtqquPUXQF9BhDy7JU5sdiMr6TWE=</X509Certificate> + </DigitalId> + </ServiceDigitalIdentity> + </ServiceDigitalIdentities> + <TSLLocation>https://eidas.ec.europa.eu/efda/api/v2/validation-tests/testcase/tl/LOTL-2.xml</TSLLocation> + <AdditionalInformation> + <OtherInformation> + <SchemeTerritory>EU</SchemeTerritory> + </OtherInformation> + <OtherInformation> + <TSLType>http://uri.etsi.org/TrstSvc/TrustedList/TSLType/EUlistofthelists</TSLType> + </OtherInformation> + <OtherInformation> + <SchemeOperatorName> + <Name xml:lang="en">EU Operator name</Name> + </SchemeOperatorName> + </OtherInformation> + <OtherInformation> + <ns3:MimeType>application/vnd.etsi.tsl+xml</ns3:MimeType> + </OtherInformation> + </AdditionalInformation> + </OtherTSLPointer> + </PointersToOtherTSL> + <ListIssueDateTime>2023-11-11T00:00:00Z</ListIssueDateTime> + <NextUpdate> + <dateTime>2023-12-13T00:00:00Z</dateTime> + </NextUpdate> + </SchemeInformation> + <TrustServiceProviderList> + <TrustServiceProvider> + <TSPInformation> + <TSPName> + <Name xml:lang="en">TSP-21.1 Name</Name> + </TSPName> + <TSPTradeName> + <Name xml:lang="en">TSP-21.1 Trade name</Name> + </TSPTradeName> + <TSPAddress> + <PostalAddresses> + <PostalAddress xml:lang="en"> + <StreetAddress>Street address</StreetAddress> + <Locality>Locality</Locality> + <StateOrProvince>Province</StateOrProvince> + <PostalCode>123</PostalCode> + <CountryName>LU</CountryName> + </PostalAddress> + </PostalAddresses> + <ElectronicAddress> + <URI xml:lang="en">mailto:test@test.test</URI> + </ElectronicAddress> + </TSPAddress> + <TSPInformationURI> + <URI xml:lang="en">http://tsp.information.uri/test</URI> + </TSPInformationURI> + </TSPInformation> + <TSPServices> + <TSPService> + <ServiceInformation> + <ServiceTypeIdentifier>http://uri.etsi.org/TrstSvc/Svctype/CA/QC</ServiceTypeIdentifier> + <ServiceName> + <Name xml:lang="en">TS-21.1.1 Name</Name> + </ServiceName> + <ServiceDigitalIdentity> + <DigitalId> +<X509Certificate>MIIDYTCCAkmgAwIBAgIBAzANBgkqhkiG9w0BAQsFADBSMRMwEQYDVQQDDApTREktMjEuMS4xMRYwFAYDVQQKDA1UU1AtMjEuMSBOYW1lMRYwFAYDVQQLDA1DRVJUIEZPUiBURVNUMQswCQYDVQQGEwJMVTAeFw0yMjExMTIwMDAwMDlaFw0yNDExMTIwMDAwMDlaMFIxEzARBgNVBAMMClNESS0yMS4xLjExFjAUBgNVBAoMDVRTUC0yMS4xIE5hbWUxFjAUBgNVBAsMDUNFUlQgRk9SIFRFU1QxCzAJBgNVBAYTAkxVMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhtsCMi4dNZ/1SlwL0F2S6LG5FKtviLxCIx6KsmQP7zFrcmEEG7wnr5QYN2uuTXuUtYaS6HoDJlC1RNuHPquBhhC1VQD2IqxnYW4TsyjkQml5Bw8R679nMlUgU8r+wloPq/5mExOz3pG29n/W1eJnm7yyCEnOMkLwxEzuBcVThJhNQd6XwImrYqZSjD85c9wtVrNemdIAgwZV14ICTs8jX6VgazPHl+NUSMBM2JS6aKlm1BkykOQe7SwHJymXSUKqaPprUugrFZDXF3QkkXfDLQlkRZXGd+Ig5UE9HGFfbBxkXVJp58v65AGIsURsxU4W00oznDzoAyVwCjLokNqcEQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFEHXjq4Qe9q4ukmHl2znE9NQjdc1MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAEUa4NvQpRB2CD0qAOkrx3WwVVKREwx4LON4AjOxcfzmq3yI+/dtFHRY3Yy6EMErmuhpcnzG8Tzf48pE0gOGVXdu9FSk6/Zps4w2+bnvIQQfobRzPfjX6ntp1PQaT48lzw2NnNPjcW7ny4oHoCoERDAez6OfPzMFQwk+/nN5DQJXK6rQdmxLJr6AHjOvHGutV3gApN7etKJaK9lTmPcwogaQ69y71zwqHliXoJyfEqqu7L+lzCLEC8PLCkktR4YmqYrWTGEn4eJf6aNt/5EuY7Om6sCJZHl7LsfDXEWOy5w4hvATDIMubPjnnRnTEgr8qGs8Ph6f4/t016o4AkinFSc=</X509Certificate> + </DigitalId> + <DigitalId> +<X509SubjectName>C=LU,OU=CERT FOR TEST,O=TSP-21.1 Name,CN=SDI-21.1.1</X509SubjectName> + </DigitalId> + </ServiceDigitalIdentity> + <ServiceStatus>http://uri.etsi.org/TrstSvc/TrustedList/Svcstatus/granted</ServiceStatus> + <StatusStartingTime>2016-06-30T22:00:00Z</StatusStartingTime> + <ServiceInformationExtensions> + <Extension Critical="true"> +<AdditionalServiceInformation> + <URI xml:lang="en">http://uri.etsi.org/TrstSvc/TrustedList/SvcInfoExt/ForeSignatures</URI> +</AdditionalServiceInformation> + </Extension> + </ServiceInformationExtensions> + </ServiceInformation> + </TSPService> + </TSPServices> + </TrustServiceProvider> + </TrustServiceProviderList> +<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="id-aedb1ef81dba548eb80100d1c5849106"><ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/><ds:Reference Id="xml_ref_id" URI=""><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue>pDpTS8XpPc8n3sCRt3Wpuv9+8PfgOxM35omUzVtPULU=</ds:DigestValue></ds:Reference><ds:Reference Type="http://uri.etsi.org/01903#SignedProperties" URI="#xades-id-aedb1ef81dba548eb80100d1c5849106"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue>cgJODlMb6hcakJaLY5zjS0sl8NXhqr+Q5/5w0lIS+Io=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue Id="value-id-aedb1ef81dba548eb80100d1c5849106">F4Pb2f8swSwL9p9E1tARyhor+15s7rc6PKRSDG6fEbwNHAEtrR5uCm9TDRQb0Gfk9rrMUrc+gK9QAmqz1VWOsz5IfAFOFvpUQHoQybp/EtmoUnfiZlr3E8OsJjhYOnqu/EqibSPegBtiKzdRnKI45VH409wrv7mciNZ3bhvPkOgKcXgdchIuRkjYFXz+y1K17vwyyx7FOF9Iy+AtWEacxp3figLktc92d37xW171e1ZZcVuCiH1NCaiNdmEjb6qiUb1VTpAjKdJaZbLudxAfZVYvZI2dGPinxva2fegGwTPgldqKI6vhLkXRnL2OePIPyRM48Q4qr+FiVeTZzGimXg==</ds:SignatureValue><ds:KeyInfo><ds:X509Data><ds:X509Certificate>MIIDVDCCAjygAwIBAgIBAjANBgkqhkiG9w0BAQ0FADBcMRkwFwYDVQQDDBBDRVJULVRMLTIxLVdST05HMRowGAYDVQQKDBFUTDIxIE9yZ2FuaXphdGlvbjEWMBQGA1UECwwNQ0VSVCBGT1IgVEVTVDELMAkGA1UEBhMCTFUwHhcNMjIxMTEyMDAwMDA4WhcNMjQxMTEyMDAwMDA4WjBcMRkwFwYDVQQDDBBDRVJULVRMLTIxLVdST05HMRowGAYDVQQKDBFUTDIxIE9yZ2FuaXphdGlvbjEWMBQGA1UECwwNQ0VSVCBGT1IgVEVTVDELMAkGA1UEBhMCTFUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDezi/F/qxz3atjE7o3fJl9+WXIGBVt4/j+HoBh8a4Hd1KQxy/PmFctfW/xtLmjHzY6RPz8s/vGat+yC2LOnxrQ6vrjFJfe2Z1SuBLcqB57icTLjwNjEpCOw7IaB8WElDtAxpyPauQ8iFFdTeSJNY/hhvTKwaI4YTXaeO2mxoQ5/QMniYcxpvcStSlejXqXVMaN+JLLAvNJaxVlDuaOPvN6CsDlPKVdNOmlIrWthJjw0x1VwmzLfhHJkFsE2CIM68sJ4jj38tyfdpjXFXBpCNkLkvkMO/wKRHMszHBpcJQCkoQ8Mjb5WQ7WPEwsvo4mQEbFMM67GVi+13Xxf1TrwkVnAgMBAAGjITAfMB0GA1UdDgQWBBQnDCTRgW2Hs9c08sNDMTLU4vKpmjANBgkqhkiG9w0BAQ0FAAOCAQEALFKFfec2uElYiTCwoH/otOoeJTh1O/hg2oLbmNX4mRjGx9l3ancMBV+0zl6YJ3EhudXOfB5SBlLwg9gYmyyx2dpVLXSHWKG2Vku9bW9G4ZpgYXFHfvDia+G/ktrTVzUHIGADt6IZ+LoBI7K/t9dX26PaNPglXha0Ct/ygpDLtEnO9UhPLAqKy48R5ik/QQJ1y6dTjbw/JzfgRuUHx7/VqWMfmZqSHzofm6AN4wkSKyUbR9uC/RbT3Lc0hslbJEXiKwGsh5skgu0Bg1x2Fumt1H3rdbKfpYStghGPJeJWRaAtGVkpLEp114zukFxCsN4a6IuqvlrYM3lDFP7meBXcRQ==</ds:X509Certificate></ds:X509Data></ds:KeyInfo><ds:Object><xades:QualifyingProperties xmlns:xades="http://uri.etsi.org/01903/v1.3.2#" Target="#id-aedb1ef81dba548eb80100d1c5849106"><xades:SignedProperties Id="xades-id-aedb1ef81dba548eb80100d1c5849106"><xades:SignedSignatureProperties><xades:SigningTime>2023-11-12T00:01:00Z</xades:SigningTime><xades:SigningCertificate><xades:Cert><xades:CertDigest><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512"/><ds:DigestValue>3rdRffkS4baRCjG5xLatj5+wKjasfamCnyTKyZ8g14rDkZgUG52j71TsRiqBMgdRfTZGw2eACHNXQUDV4322zw==</ds:DigestValue></xades:CertDigest><xades:IssuerSerial><ds:X509IssuerName>C=LU,OU=CERT FOR TEST,O=TL21 Organization,CN=CERT-TL-21-WRONG</ds:X509IssuerName><ds:X509SerialNumber>2</ds:X509SerialNumber></xades:IssuerSerial></xades:Cert></xades:SigningCertificate></xades:SignedSignatureProperties><xades:SignedDataObjectProperties><xades:DataObjectFormat ObjectReference="#xml_ref_id"><xades:MimeType>application/octet-stream</xades:MimeType></xades:DataObjectFormat></xades:SignedDataObjectProperties></xades:SignedProperties></xades:QualifyingProperties></ds:Object></ds:Signature></TrustServiceStatusList> \ No newline at end of file diff --git a/service/src/test/resources/application.yml b/service/src/test/resources/application-test.yml similarity index 82% rename from service/src/test/resources/application.yml rename to service/src/test/resources/application-test.yml index 379b9d2abcbaa5314741fe0f18d1f4a1fb78da12..169958cbff002904c7889d638a32179b363df4c0 100644 --- a/service/src/test/resources/application.yml +++ b/service/src/test/resources/application-test.yml @@ -9,8 +9,9 @@ tcr: dns: hosts: timeout: 2000 - verification: - enabled: false + dnssec: + enabled: true + http: timeout: 10 \ No newline at end of file diff --git a/service/src/test/resources/schuelerausweis.xml b/service/src/test/resources/schuelerausweis.xml new file mode 100644 index 0000000000000000000000000000000000000000..9931ed119c42c60265ff1a2d565bda8e1c516636 --- /dev/null +++ b/service/src/test/resources/schuelerausweis.xml @@ -0,0 +1,323 @@ +<TrustServiceStatusList + xmlns="http://uri.etsi.org/02231/v2#" + xmlns:ns2="http://www.w3.org/2000/09/xmldsig#" + xmlns:ns3="http://uri.etsi.org/01903/v1.3.2#" + xmlns:ns4="http://uri.etsi.org/02231/v2/additionaltypes#" + xmlns:ns5="http://uri.etsi.org/TrstSvc/SvcInfoExt/eSigDir-1999-93-EC-TrustedList/#" + xmlns:ns6="http://uri.etsi.org/01903/v1.4.1#" + TSLTag="http://uri.etsi.org/19612/TSLTag"> + <SchemeInformation> + <TSLVersionIdentifier>5</TSLVersionIdentifier> + <TSLSequenceNumber>1</TSLSequenceNumber> + <TSLType>http://uri.etsi.org/TrstSvc/TrustedList/TSLType/EUgeneric</TSLType> + <SchemeOperatorName> + <Name xml:lang="en">ONCE</Name> + </SchemeOperatorName> + <SchemeOperatorAddress> + <PostalAddresses> + <PostalAddress xml:lang="de"> + <StreetAddress>somewhere in Deutschland</StreetAddress> + <Locality>Deutschland</Locality> + <PostalCode>111111</PostalCode> + <CountryName>DE</CountryName> + </PostalAddress> + </PostalAddresses> + <ElectronicAddress> + <URI xml:lang="en">mailto:alice@TrustSchemeESSIF_TRAIN.example.com</URI> + <URI xml:lang="en">https://TrustSchemeESSIF_TRAIN.example.com/Servicemenu/English</URI> + </ElectronicAddress> + </SchemeOperatorAddress> + <SchemeName> + <Name xml:lang="en">Trust Scheme ONCE Example: Trusted list including information related to the qualified schools under ONCE ecosystem</Name> + </SchemeName> + <SchemeInformationURI> + <URI xml:lang="en">https://TrustSchemeESSIF_TRAIN.example.com/en/tsl_e.htm</URI> + </SchemeInformationURI> + <StatusDeterminationApproach>test</StatusDeterminationApproach> + <SchemeTypeCommunityRules> + <URI xml:lang="en">https://TrustSchemeESSIF_TRAIN.example.com/en/schemerules.xml</URI> + </SchemeTypeCommunityRules> + <SchemeTerritory>Deutschland</SchemeTerritory> + <PolicyOrLegalNotice> + <TSLLegalNotice xml:lang="en">The applicable legal framework for the present trusted list is ONCE Regulation No ??.</TSLLegalNotice> + </PolicyOrLegalNotice> + <HistoricalInformationPeriod>65535</HistoricalInformationPeriod> + <ListIssueDateTime>2019-01-01T00:00:00Z</ListIssueDateTime> + <NextUpdate> + <dateTime>2021-01-01T00:00:00Z</dateTime> + </NextUpdate> + <DistributionPoints> + <URI>https://TrustSchemeESSIF_TRAIN.example.com/trust-list.xml</URI> + </DistributionPoints> + </SchemeInformation> + <TrustServiceProviderList> + <TrustServiceProvider> + <TSPInformation> + <TSPName> + <Name xml:lang="en">MusterMann Schule</Name> + </TSPName> + <TSPTradeName> + <Name xml:lang="en">VAT-11111111</Name> + </TSPTradeName> + <TSPAddress> + <PostalAddresses> + <PostalAddress xml:lang="en"> + <StreetAddress>Hauptstr 7</StreetAddress> + <Locality>Deutschland</Locality> + <PostalCode>70569</PostalCode> + <CountryName>DE</CountryName> + </PostalAddress> + </PostalAddresses> + <ElectronicAddress> + <URI xml:lang="en">https://www.schule.example.com/en/</URI> + <URI xml:lang="en">mailto:info@schule.example.com</URI> + </ElectronicAddress> + </TSPAddress> + <TSPInformationURI> + <URI xml:lang="en">https://www.schule.example.com/en/info</URI> + </TSPInformationURI> + <!-- + <IssuerName> + <Name xml:lang="en">did:jolo:3eda3790b040704ba8b8b30534874d692de25deeffb7ef46f0cd99c6543afdc3</Name> + </IssuerName> + --> + </TSPInformation> + <TSPServices> + <TSPService> + <ServiceInformation> + <ServiceTypeIdentifier>http://TrustSchemeschule.example.com/ServiceTypes/thirdparties/ + </ServiceTypeIdentifier> + <ServiceName> + <Name xml:lang="en">schuelersusweis</Name> + </ServiceName> + <ServiceDigitalIdentity> + <DigitalId> + <X509Certificate>MIIEETCCAvmgAwIBAgIEAVTL9zANBgkqhkiG9w0BAQ0FADBkMQwwCgYDVQQDEwNtZWExMjAwBgNVBAoTKVVuaXRlZCBO</X509Certificate> + </DigitalId> + </ServiceDigitalIdentity> + <ServiceStatus>http://TrustSchemeESSIF_TRAIN.example.com/ServiceTypes/Servicestatus/granted/</ServiceStatus> + <StatusStartingTime>2019-01-01T00:00:00Z</StatusStartingTime> + </ServiceInformation> + </TSPService> + </TSPServices> + </TrustServiceProvider> + <TrustServiceProvider> + <TSPInformation> + <TSPName> + <Name xml:lang="de">regio iT Demo-1: Gymnasium an der Gartenstraße (MG)</Name> + </TSPName> + <!-- + <IssuerName> + <Name xml:lang="en">M5kPLHELwtS4Lxrmw2mAKG</Name> + </IssuerName> + --> + <TSPTradeName> + <Name xml:lang="en">VAT-11111111</Name> + </TSPTradeName> + <TSPAddress> + <PostalAddresses> + <PostalAddress xml:lang="en"> + <StreetAddress>Hauptstr 7</StreetAddress> + <Locality>Deutschland</Locality> + <PostalCode>70569</PostalCode> + <CountryName>DE</CountryName> + </PostalAddress> + </PostalAddresses> + <ElectronicAddress> + <URI xml:lang="en">https://www.schule.example.com/en/</URI> + <URI xml:lang="en">mailto:info@schule.example.com</URI> + </ElectronicAddress> + </TSPAddress> + <TSPInformationURI> + <URI xml:lang="en">https://www.schule.example.com/en/info</URI> + </TSPInformationURI> + </TSPInformation> + <TSPServices> + <TSPService> + <ServiceInformation> + <ServiceTypeIdentifier>http://TrustSchemeschule.example.com/ServiceTypes/thirdparties/</ServiceTypeIdentifier> + <ServiceName> + <Name xml:lang="en">Demo-1</Name> + </ServiceName> + <ServiceDigitalIdentity> + <DigitalId> + <X509Certificate>MIIEETCCAvmgAwIBAgIEAVTL9zANBgkqhkiG9w0BAQ0FADBkMQwwCgYDVQQDEwNtZWExMjAwBgNVBAoTKVVuaXRlZCBO</X509Certificate> + </DigitalId> + </ServiceDigitalIdentity> + <ServiceStatus>http://TrustSchemeESSIF_TRAIN.example.com/ServiceTypes/Servicestatus/granted/</ServiceStatus> + <StatusStartingTime>2019-01-01T00:00:00Z</StatusStartingTime> + </ServiceInformation> + </TSPService> + </TSPServices> + </TrustServiceProvider> + <TrustServiceProvider> + <TSPInformation> + <TSPName> + <Name xml:lang="en">regio iT DEV</Name> + </TSPName> + <!-- + <IssuerName> + <Name xml:lang="en">Przc8rbKzWrteKvPH4pGvT</Name> + </IssuerName> + --> + <TSPTradeName> + <Name xml:lang="en">VAT-11111111</Name> + </TSPTradeName> + <TSPAddress> + <PostalAddresses> + <PostalAddress xml:lang="en"> + <StreetAddress>Hauptstr 7</StreetAddress> + <Locality>Deutschland</Locality> + <PostalCode>70569</PostalCode> + <CountryName>DE</CountryName> + </PostalAddress> + </PostalAddresses> + <ElectronicAddress> + <URI xml:lang="en">https://www.schule.example.com/en/</URI> + <URI xml:lang="en">mailto:info@schule.example.com</URI> + </ElectronicAddress> + </TSPAddress> + <TSPInformationURI> + <URI xml:lang="en">https://www.schule.example.com/en/info</URI> + </TSPInformationURI> + </TSPInformation> + <TSPServices> + <TSPService> + <ServiceInformation> + <ServiceTypeIdentifier>http://TrustSchemeschule.example.com/ServiceTypes/thirdparties/ + </ServiceTypeIdentifier> + <ServiceName> + <Name xml:lang="en">DEV</Name> + </ServiceName> + <ServiceDigitalIdentity> + <DigitalId> + <X509Certificate>MIIEETCCAvmgAwIBAgIEAVTL9zANBgkqhkiG9w0BAQ0FADBkMQwwCgYDVQQDEwNtZWExMjAwBgNVBAoTKVVuaXRlZCBO</X509Certificate> + </DigitalId> + </ServiceDigitalIdentity> + <ServiceStatus>http://TrustSchemeESSIF_TRAIN.example.com/ServiceTypes/Servicestatus/granted/ + </ServiceStatus> + <StatusStartingTime>2019-01-01T00:00:00Z</StatusStartingTime> + </ServiceInformation> + </TSPService> + </TSPServices> + </TrustServiceProvider> + <TrustServiceProvider> + <TSPInformation> + <TSPName> + <Name xml:lang="en">regio iT Demo-2: Mönchengladbach-Ausweis</Name> + </TSPName> + <!-- + <IssuerName> + <Name xml:lang="en">2MiHNdshRpfShqxk3g8gGd</Name> + </IssuerName> + --> + <TSPTradeName> + <Name xml:lang="en">VAT-11111111</Name> + </TSPTradeName> + <TSPAddress> + <PostalAddresses> + <PostalAddress xml:lang="en"> + <StreetAddress>Hauptstr 7</StreetAddress> + <Locality>Deutschland</Locality> + <PostalCode>70569</PostalCode> + <CountryName>DE</CountryName> + </PostalAddress> + </PostalAddresses> + <ElectronicAddress> + <URI xml:lang="en">https://www.schule.example.com/en/</URI> + <URI xml:lang="en">mailto:info@schule.example.com</URI> + </ElectronicAddress> + </TSPAddress> + <TSPInformationURI> + <URI xml:lang="en">https://www.schule.example.com/en/info</URI> + </TSPInformationURI> + </TSPInformation> + <TSPServices> + <TSPService> + <ServiceInformation> + <ServiceTypeIdentifier>http://TrustSchemeschule.example.com/ServiceTypes/thirdparties/ + </ServiceTypeIdentifier> + <ServiceName> + <Name xml:lang="en">Demo-2</Name> + </ServiceName> + <ServiceDigitalIdentity> + <DigitalId> + <X509Certificate>MIIEETCCAvmgAwIBAgIEAVTL9zANBgkqhkiG9w0BAQ0FADBkMQwwCgYDVQQDEwNt + ZWExMjAwBgNVBAoTKVVuaXRlZCBO </X509Certificate> + </DigitalId> + </ServiceDigitalIdentity> + <ServiceStatus>http://TrustSchemeESSIF_TRAIN.example.com/ServiceTypes/Servicestatus/granted/ + </ServiceStatus> + <StatusStartingTime>2019-01-01T00:00:00Z</StatusStartingTime> + </ServiceInformation> + </TSPService> + </TSPServices> + </TrustServiceProvider> + </TrustServiceProviderList> + <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="id-f77bf9f6605f3e0a6f15785801da6e08"> + <ds:SignedInfo> + <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /> + <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" /> + <ds:Reference Id="r-id-f77bf9f6605f3e0a6f15785801da6e08-1" URI=""> + <ds:Transforms> + <ds:Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116"> + <ds:XPath>not(ancestor-or-self::ds:Signature)</ds:XPath> + </ds:Transform> + <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /> + </ds:Transforms> + <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" /> + <ds:DigestValue>sG//6RNhH+mjxPuE4sFgjjsU5rQvoh8kKOiOVmPlhBg=</ds:DigestValue> + </ds:Reference> + <ds:Reference Type="http://uri.etsi.org/01903#SignedProperties" URI="#xades-id-f77bf9f6605f3e0a6f15785801da6e08"> + <ds:Transforms> + <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /> + </ds:Transforms> + <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" /> + <ds:DigestValue>qFuTow4cFrzFCuPugWRMrJiofYMPkolxeaRFSb6Ip5c=</ds:DigestValue> + </ds:Reference> + </ds:SignedInfo> + <ds:SignatureValue + Id="value-id-f77bf9f6605f3e0a6f15785801da6e08">mCAOTSzddi5e8sQzHEvyo2mwmTfGTmT1Kd/0I3+rz4aM/BkBEAJ0AE1x6Nk+mVU2/y6+iQElOm2TNwfcyMRuG+kSBPLsgCjXWzi0Uhikh5t3gvtrvguOAsM/Xih5VjMSthIoAL2CZMLi+vPsdmYixfvHeVl96udO/P/wYgTrFR5jn8lkQRsYs8+m810fozOm+zLm+j92jcBp1xJhwNqyNHIgiLWOnWhrUYSD9tY5be+moqZQYbi6EWxCH1Em9soa+2eUrFxZ1nQZosUqhzsTV2jV9xqIFECyxOfCNwACtOU4ATH0dbfSwIOqzvnLlWCjbPF8VKdZz4uOpRnZXDkMAw== + </ds:SignatureValue> + <ds:KeyInfo> + <ds:X509Data> + <ds:X509Certificate>MIIEETCCAvmgAwIBAgIEAVTL9zANBgkqhkiG9w0BAQ0FADBkMQwwCgYDVQQDEwNtZWExMjAwBgNVBAoTKVVuaXRlZCBOYXRpb25hbCBPcmdhbml6YXRpb24gZm9yIHJlZnVnZWVzMRMwEQYDVQQHEwpDb3BlbmhhZ2VuMQswCQYDVQQGEwJETjAeFw0yMDAxMDkwOTA5MDFaFw0yNjExMTMwOTA5MDFaMGcxDzANBgNVBAMTBmpvcmRhbjEyMDAGA1UEChMpVW5pdGVkIE5hdGlvbmFsIE9yZ2FuaXphdGlvbiBmb3IgcmVmdWdlZXMxEzARBgNVBAcTCkNvcGVuaGFnZW4xCzAJBgNVBAYTAkROMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArxj4uGQWD1Wl9d0FuraCTi7iZ50GSk9vyPUdQZGCqGOYeXGNF8GqNthXAQJVB7vs7J5GTa/bSezGOevvS3wYTHMMWYXvT3FdhmyD+2bpnrHy+UkRNh69d5KBb23Ih+cK7ReTqGCTMZ50Q/Dg7qMru1Z7nAM0qHqewhZuT/OCcRI0ycoDeJz4620Jd19db8D0FCAQntWkOtIAIZKn92B8IyjjQi7ddk9Z4w5lz4M3SSjzOi4DQ/SBzstf0VbB9S+HabGA3byqLLNX5o5z/qfNNWXSim8tZsNyj6W19zobKsVwmYSTmw3gJPooQ696vIMyz5Q3DiQcOJyf8kQy8XVVgwIDAQABo4HHMIHEMB8GA1UdIwQYMBaAFFbgaDVQP34mJ9fXiPJS27XmuDQnMA8GA1UdEwQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgK0MDEGA1UdEQQqMCiCJmpvcmRhbi5kYWZpLWRlbW8ubGlnaHRlc3QubmxuZXRsYWJzLm5sMC4GA1UdEgQnMCWCI21lYS5kYWZpLWRlbW8ubGlnaHRlc3QubmxuZXRsYWJzLm5sMB0GA1UdDgQWBBTmGg8mcxog22WBsNhFKHqkAGlL6TANBgkqhkiG9w0BAQ0FAAOCAQEAOEaGtiZ8axLodER8m3FpCC6y83Q8DQVFdhmmslULf6MW9C18Fdk6MH5On3Hkm+qmLR6a55cENuDAGgIoczGlyeYFBndypQJfp2SZK1SKXR5hb0HspCo5FOW8bdqJivVjLbaW/aF4MOzhHJzlRkeJbP2clq7LD1M16bASEnB35kwksbWQRN6WQqKVzot8zd11xiWMFUpbSVRlX8Jb62jCMwC/nt0p4JCJ+8ETVtnKf88IGAVShZ+4tt/J42SjuNT8CB3St7iRGZmE1iuE2AeVfD8V1N17GItlOXy9FQ6RgqlU01SORu2uV3xqzTTxIcsEY/DO99PfEtexrW4hFSNarA== + </ds:X509Certificate> + <ds:X509Certificate>MIIEDzCCAvegAwIBAgIEBTl/sTANBgkqhkiG9w0BAQ0FADBmMQ4wDAYDVQQDEwV1bmhjcjEyMDAGA1UEChMpVW5pdGVkIE5hdGlvbmFsIE9yZ2FuaXphdGlvbiBmb3IgcmVmdWdlZXMxEzARBgNVBAcTCkNvcGVuaGFnZW4xCzAJBgNVBAYTAkROMB4XDTIwMDEwOTA5MDkwMVoXDTI2MTExMzA5MDkwMVowZDEMMAoGA1UEAxMDbWVhMTIwMAYDVQQKEylVbml0ZWQgTmF0aW9uYWwgT3JnYW5pemF0aW9uIGZvciByZWZ1Z2VlczETMBEGA1UEBxMKQ29wZW5oYWdlbjELMAkGA1UEBhMCRE4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCIB+VYoqe9r2aFxQMoyuONIXZgO2xnprEnn/RPLikvnAulX/xYMFpcU0SSbopfsdK/XMQiFIFmAO5GjcEqIV+WQ84NlQsTBd+I/VnGwLje4JH9Hc3ppj4xuaBzaX8wiXqoBQtV8Y9LlicRenvaBM29li3CvqLdONOeIUgAH8VF5d3YNmXGWoyIuZv9sruebnrGajjgU2xP0fUdexBx1LbpaafhoSy1KcER0B0PTyofdSR5CV3exwiRUuS48sob/I0IdrJVAPqKmWrosXha+Xj6ejZU473XnPBii6KZlZhOMv0ht29kOixM+5sttswGxP9poraiE1r5yYBC4hV1RcrPAgMBAAGjgcYwgcMwHwYDVR0jBBgwFoAUoI1m4qYD7gRYZQ+sDjyHGqmfRS0wDwYDVR0TBAgwBgEB/wIBAzAOBgNVHQ8BAf8EBAMCArQwLgYDVR0RBCcwJYIjbWVhLmRhZmktZGVtby5saWdodGVzdC5ubG5ldGxhYnMubmwwMAYDVR0SBCkwJ4IldW5oY3IuZGFmaS1kZW1vLmxpZ2h0ZXN0Lm5sbmV0bGFicy5ubDAdBgNVHQ4EFgQUVuBoNVA/fiYn19eI8lLbtea4NCcwDQYJKoZIhvcNAQENBQADggEBAGGLS7+TZGI0pJKg6TOzu5YjDvlaobwKbjpgMWTFvaC1wJJkPZpBXBUmcvef/d4sJjI+6o/4y13LxEeMZGt+7ORJ+D+gvwdZS3KUIRegn77UzIBef/eplwIUBnzNIRV0p2nuMMDQPyzPg+DogvYzoFVZmfXyv5zWsfPwetMTARILKd42QAprGe/3NwhMOTHghE7ejlpfeUFbTCemmxZMZJku4tw1Qzoqd70G64GXahyawigrGK8Up/HXjK3L1Xy8+ED71YGnHIgosoeLGioemzT5lFTKgudefZ41TSaY2sT/CEuc3cCTutVPIiVjDHIaFeI5pa/zsnOJ3a71+T2iXKY= + </ds:X509Certificate> + <ds:X509Certificate>MIID7DCCAtSgAwIBAgIEALxhTjANBgkqhkiG9w0BAQ0FADBmMQ4wDAYDVQQDEwV1bmhjcjEyMDAGA1UEChMpVW5pdGVkIE5hdGlvbmFsIE9yZ2FuaXphdGlvbiBmb3IgcmVmdWdlZXMxEzARBgNVBAcTCkNvcGVuaGFnZW4xCzAJBgNVBAYTAkROMB4XDTIwMDEwOTA5MDkwMVoXDTI2MTExMzA5MDkwMVowZjEOMAwGA1UEAxMFdW5oY3IxMjAwBgNVBAoTKVVuaXRlZCBOYXRpb25hbCBPcmdhbml6YXRpb24gZm9yIHJlZnVnZWVzMRMwEQYDVQQHEwpDb3BlbmhhZ2VuMQswCQYDVQQGEwJETjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJLCCpTbuaZtpf/CDwxK70ijUnzaJARx2Oyw0KAS0FXlnF614PO1m2mgCvWYLoOoTAzA4wNPUrkvtRHmblKs5n6dyRDp8d8Yl27gocjPHtTjjUoTe+EwpMWLUSa41n/8AMZ3mLRRQnWk8mZSntQzmiox1fIxV4f8aJqwwXdly1uCfBjbBLotWZ2fFyb/VocUDf6n4DJK2W9gwuxcCd6n8koFwgfH6D2Z54f5LUfkzvJwAZxAzSsb8VuzGVcNbmXvfKuKL94dZ/R7wJG+J28bVWyHLxvYK4q911s+Q88vBw0zOhIIO3hF8tL2n0qrEL+nxRMjM4nMScHWDDy7pwqwcX8CAwEAAaOBoTCBnjAfBgNVHSMEGDAWgBSgjWbipgPuBFhlD6wOPIcaqZ9FLTAPBgNVHRMECDAGAQH/AgEDMA4GA1UdDwEB/wQEAwICtDAwBgNVHREEKTAngiV1bmhjci5kYWZpLWRlbW8ubGlnaHRlc3QubmxuZXRsYWJzLm5sMAkGA1UdEgQCMAAwHQYDVR0OBBYEFKCNZuKmA+4EWGUPrA48hxqpn0UtMA0GCSqGSIb3DQEBDQUAA4IBAQAm0i8U88wODD0rThZVCFVtLs8oGwXvulrhpIPeduVTI0Z+7/vblBYrAJp8IsGTk/VmLfi31oY+o1Q+qJq1giuQlyRwuCkGoQgDdzSzbcUpm4l3GRIzDDHsK54OqMxXHuZyvT+3eJwrx+tN7TDn+Rf94J8d0bs4Hy30GoVmNe4nEdPUuMtVPL54//X/Mkr56Ul2+qtv9D8nPSD+chHEAUizlt0yfGsSq+f3hc63DXZIqh2E9YgZqKIrmRfQXF6Nb4D9QrK0pmz0NJ6umcvfBIbEYz1VSNdUfHw7C9zfc5VUan9+PKXxft3kQXylN8ZeInmhwu4sMTr/zRQL0C6BtNMx + </ds:X509Certificate> + </ds:X509Data> + </ds:KeyInfo> + <ds:Object> + <xades:QualifyingProperties xmlns:xades="http://uri.etsi.org/01903/v1.3.2#" Target="#id-f77bf9f6605f3e0a6f15785801da6e08"> + <xades:SignedProperties Id="xades-id-f77bf9f6605f3e0a6f15785801da6e08"> + <xades:SignedSignatureProperties> + <xades:SigningTime>2020-01-09T17:04:34Z</xades:SigningTime> + <xades:SigningCertificate> + <xades:Cert> + <xades:CertDigest> + <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512" /> + <ds:DigestValue>rgs+nklEAZB0nfaXoxixYEbMe8gU7ElHQaNuWKsW8UUXJgVtJM6gXd/DKSOxdPhcTEEhS8/d3o92qSUNyS6C0A==</ds:DigestValue> + </xades:CertDigest> + <xades:IssuerSerial> + <ds:X509IssuerName>MHAwaKRmMGQxDDAKBgNVBAMTA21lYTEyMDAGA1UEChMpVW5pdGVkIE5hdGlvbmFsIE9yZ2FuaXphdGlvbiBmb3IgcmVmdWdlZXMxEzARBgNVBAcTCkNvcGVuaGFnZW4xCzAJBgNVBAYTAkROAgQBVMv3</ds:X509IssuerName> + <ds:X509SerialNumber>123</ds:X509SerialNumber> + </xades:IssuerSerial> + </xades:Cert> + </xades:SigningCertificate> + <xades:SignaturePolicyIdentifier> + <xades:SignaturePolicyImplied /> + </xades:SignaturePolicyIdentifier> + <xades:SignatureProductionPlace /> + </xades:SignedSignatureProperties> + <xades:SignedDataObjectProperties> + <xades:DataObjectFormat ObjectReference="#r-id-f77bf9f6605f3e0a6f15785801da6e08-1"> + <xades:MimeType>text/xml</xades:MimeType> + </xades:DataObjectFormat> + </xades:SignedDataObjectProperties> + </xades:SignedProperties> + </xades:QualifyingProperties> + </ds:Object> + </ds:Signature> +</TrustServiceStatusList>