From 1dc68f37faaa4de6fab03bb97b52337785335835 Mon Sep 17 00:00:00 2001 From: Torsten Egenolf Date: Fri, 10 May 2024 17:06:58 +0200 Subject: [PATCH] feat(trustlist): added trustlist v2 generation (WIP) --- .../SignerInformationRepository.java | 10 + .../service/SignerInformationService.java | 15 + .../service/did/DidTrustListService.java | 618 +++++++++--------- .../service/did/DidTrustListServiceV2.java | 364 +++++++++++ 4 files changed, 698 insertions(+), 309 deletions(-) create mode 100644 src/main/java/tng/trustnetwork/keydistribution/service/did/DidTrustListServiceV2.java diff --git a/src/main/java/tng/trustnetwork/keydistribution/repository/SignerInformationRepository.java b/src/main/java/tng/trustnetwork/keydistribution/repository/SignerInformationRepository.java index b4cb4be..e082776 100644 --- a/src/main/java/tng/trustnetwork/keydistribution/repository/SignerInformationRepository.java +++ b/src/main/java/tng/trustnetwork/keydistribution/repository/SignerInformationRepository.java @@ -66,4 +66,14 @@ public interface SignerInformationRepository extends JpaRepository getAllByDeletedIs(boolean deleted); List getAllByDeletedIsAndCountryIsIn(boolean deleted, List countries); + + @Query("SELECT DISTINCT s.domain FROM SignerInformationEntity s WHERE s.deleted = false") + List getDomainsList(); + + + List getAllByDeletedIsAndDomainIs(boolean deleted, String domain); + + List getAllByDeletedIsAndDomainIsAndCountryIs(boolean deleted, String domain, String country); + + } diff --git a/src/main/java/tng/trustnetwork/keydistribution/service/SignerInformationService.java b/src/main/java/tng/trustnetwork/keydistribution/service/SignerInformationService.java index 626227a..eeb1c20 100644 --- a/src/main/java/tng/trustnetwork/keydistribution/service/SignerInformationService.java +++ b/src/main/java/tng/trustnetwork/keydistribution/service/SignerInformationService.java @@ -204,4 +204,19 @@ private CertificatesLookupResponseItemDto map(SignerInformationEntity entity) { return new CertificatesLookupResponseItemDto(entity.getKid(), entity.getRawData()); } + + + public List getActiveCertificatesForFilter(String domain, String participant){ + if (domain != null && participant != null){ + return signerInformationRepository.getAllByDeletedIsAndDomainIsAndCountryIs(false, domain, participant); + }else if (domain != null){ + return signerInformationRepository.getAllByDeletedIsAndDomainIs(false, domain); + }else{ + return getActiveCertificates(); + } + } + + public List getDomainsList() { + return signerInformationRepository.getDomainsList(); + } } diff --git a/src/main/java/tng/trustnetwork/keydistribution/service/did/DidTrustListService.java b/src/main/java/tng/trustnetwork/keydistribution/service/did/DidTrustListService.java index cf6ce8d..1ddcf6e 100644 --- a/src/main/java/tng/trustnetwork/keydistribution/service/did/DidTrustListService.java +++ b/src/main/java/tng/trustnetwork/keydistribution/service/did/DidTrustListService.java @@ -1,309 +1,309 @@ -/*- - * ---license-start - * WorldHealthOrganization / tng-key-distribution - * --- - * Copyright (C) 2021 - 2024 T-Systems International GmbH and all other contributors - * --- - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ---license-end - */ - -package tng.trustnetwork.keydistribution.service.did; - -import com.apicatalog.jsonld.document.JsonDocument; -import com.danubetech.keyformats.crypto.ByteSigner; -import com.fasterxml.jackson.databind.ObjectMapper; -import eu.europa.ec.dgc.utils.CertificateUtils; -import foundation.identity.jsonld.ConfigurableDocumentLoader; -import foundation.identity.jsonld.JsonLDObject; -import info.weboftrust.ldsignatures.jsonld.LDSecurityKeywords; -import info.weboftrust.ldsignatures.signer.JsonWebSignature2020LdSigner; -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.security.PublicKey; -import java.security.cert.CertificateEncodingException; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.security.interfaces.ECPublicKey; -import java.security.interfaces.RSAPublicKey; -import java.util.ArrayList; -import java.util.Base64; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.MissingResourceException; -import java.util.Objects; -import java.util.Optional; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; -import org.bouncycastle.cert.X509CertificateHolder; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Service; -import tng.trustnetwork.keydistribution.config.KdsConfigProperties; -import tng.trustnetwork.keydistribution.entity.SignerInformationEntity; -import tng.trustnetwork.keydistribution.service.SignerInformationService; -import tng.trustnetwork.keydistribution.service.TrustedIssuerService; -import tng.trustnetwork.keydistribution.service.TrustedPartyService; -import tng.trustnetwork.keydistribution.service.did.entity.DidTrustList; -import tng.trustnetwork.keydistribution.service.did.entity.DidTrustListEntry; - -@Slf4j -@Service -@RequiredArgsConstructor -@ConditionalOnProperty("dgc.did.enableDidGeneration") -public class DidTrustListService { - - private static final String SEPARATOR_COLON = ":"; - - private static final String SEPARATOR_FRAGMENT = "#"; - - private static final List DID_CONTEXTS = List.of( - "https://www.w3.org/ns/did/v1", - "https://w3id.org/security/suites/jws-2020/v1"); - - private final TrustedPartyService trustedPartyService; - - private final SignerInformationService signerInformationService; - - private final KdsConfigProperties configProperties; - - private final ByteSigner byteSigner; - - private final DidUploader didUploader; - - private final ObjectMapper objectMapper; - - private final CertificateUtils certificateUtils; - - private final TrustedIssuerService trustedIssuerService; - - private final GitProvider gitProvider; - - /** - * Create and upload DID Document holding Uploaded DSC and Trusted Issuer. - */ - @Scheduled(cron = "${dgc.did.cron}") - @SchedulerLock(name = "didTrustListGenerator") - public void job() { - - String trustList; - - try { - trustList = generateTrustList(null); - } catch (Exception e) { - log.error("Failed to generate DID-TrustList: {}", e.getMessage()); - return; - } - - try { - didUploader.uploadDid(trustList.getBytes(StandardCharsets.UTF_8)); - } catch (Exception e) { - log.error("Failed to Upload DID-TrustList: {}", e.getMessage()); - return; - } - - List countries = signerInformationService.getCountryList(); - - for (String country : countries) { - String countryTrustList; - - String countryAsSubcontainer = getCountryAsLowerCaseAlpha3(country); - if (countryAsSubcontainer != null) { - try { - countryTrustList = generateTrustList(List.of(country)); - } catch (Exception e) { - log.error("Failed to generate DID-TrustList for country {} : {}", country, e.getMessage()); - continue; - } - - try { - didUploader.uploadDid(countryAsSubcontainer, countryTrustList.getBytes(StandardCharsets.UTF_8)); - } catch (Exception e) { - log.error("Failed to Upload DID-TrustList for country {} : {}", country, e.getMessage()); - } - } - } - - log.info("Finished DID Export Process"); - - gitProvider.upload(configProperties.getDid().getLocalFile().getDirectory()); - } - - private String getCountryAsLowerCaseAlpha3(String country) { - - if (country == null || country.length() != 2 && country.length() != 3) { - return null; - } else if (country.length() == 3) { - return country; - } - - return configProperties.getDid().getVirtualCountries().computeIfAbsent(country, (c) -> { - try { - return new Locale("en", c).getISO3Country().toLowerCase(); - } catch (MissingResourceException e) { - log.error("Country Code to alpha 3 conversion issue for country {} : {}", - c, e.getMessage()); - return c; - } - }); - } - - private String generateTrustList(List countries) throws Exception { - - DidTrustList trustList = new DidTrustList(); - trustList.setContext(DID_CONTEXTS); - trustList.setId(configProperties.getDid().getDidId()); - trustList.setController(configProperties.getDid().getDidController()); - trustList.setVerificationMethod(new ArrayList<>()); - - if (countries != null && !countries.isEmpty()) { - trustList.setId(configProperties.getDid().getDidId() - + SEPARATOR_COLON - + getCountryAsLowerCaseAlpha3(countries.get(0))); - } - - // Add DSC - List signerInformationEntities = countries == null - ? signerInformationService.getActiveCertificates() - : signerInformationService.getActiveCertificatesForCountries(countries); - - for (SignerInformationEntity signerInformationEntity : signerInformationEntities) { - - X509Certificate parsedCertificate = parseCertificate(signerInformationEntity.getRawData()); - PublicKey publicKey = parsedCertificate.getPublicKey(); - - if (publicKey instanceof RSAPublicKey rsaPublicKey) { - addTrustListEntry(trustList, signerInformationEntity, - new DidTrustListEntry.RsaPublicKeyJwk( - rsaPublicKey, List.of(signerInformationEntity.getRawData())), parsedCertificate); - - } else if (publicKey instanceof ECPublicKey ecPublicKey) { - addTrustListEntry(trustList, signerInformationEntity, - new DidTrustListEntry.EcPublicKeyJwk( - ecPublicKey, List.of(signerInformationEntity.getRawData())), parsedCertificate); - - } else { - log.error("Public Key is not RSA or EC Public Key for cert {} of country {}", - signerInformationEntity.getKid(), - signerInformationEntity.getCountry()); - } - } - - // Add DID References - trustedIssuerService.getAllDid() - .forEach(did -> trustList.getVerificationMethod().add(did.getUrl())); - - // Create LD-Proof Document - JsonWebSignature2020LdSigner signer = new JsonWebSignature2020LdSigner(byteSigner); - signer.setCreated(new Date()); - signer.setProofPurpose(LDSecurityKeywords.JSONLD_TERM_ASSERTIONMETHOD); - signer.setVerificationMethod(URI.create(configProperties.getDid().getLdProofVerificationMethod())); - signer.setDomain(configProperties.getDid().getLdProofDomain()); - signer.setNonce(configProperties.getDid().getLdProofNonce()); - - // Load DID-Contexts - Map contextMap = new HashMap<>(); - for (String didContext : DID_CONTEXTS) { - String didContextFile = configProperties.getDid().getContextMapping().get(didContext); - - if (didContextFile == null) { - log.error("Failed to load DID-Context Document for {}: No Mapping to local JSON-File.", didContext); - } - - try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream( - "did_contexts/" + didContextFile)) { - if (inputStream != null) { - contextMap.put(URI.create(didContext), JsonDocument.of(inputStream)); - } - } catch (Exception e) { - log.error("Failed to load DID-Context Document {}: {}", didContextFile, e.getMessage()); - throw e; - } - } - JsonLDObject jsonLdObject = JsonLDObject.fromJson(objectMapper.writeValueAsString(trustList)); - jsonLdObject.setDocumentLoader(new ConfigurableDocumentLoader(contextMap)); - - signer.sign(jsonLdObject); - - return jsonLdObject.toJson(); - } - - private X509Certificate parseCertificate(String raw) { - - try { - byte[] rawDataBytes = Base64.getDecoder().decode(raw); - X509CertificateHolder certificateHolder = new X509CertificateHolder(rawDataBytes); - return certificateUtils.convertCertificate(certificateHolder); - } catch (CertificateException | IOException e) { - return null; - } - } - - private void addTrustListEntry(DidTrustList trustList, - SignerInformationEntity signerInformationEntity, - DidTrustListEntry.PublicKeyJwk publicKeyJwk, - X509Certificate dsc) { - - Optional csca = searchCsca(dsc, signerInformationEntity.getCountry()); - - if (csca.isPresent()) { - - try { - String encodedCsca = Base64.getEncoder().encodeToString(csca.get().getEncoded()); - publicKeyJwk.getEncodedX509Certificates() - .add(encodedCsca); - } catch (CertificateEncodingException e) { - throw new RuntimeException(e); - } - } - - DidTrustListEntry trustListEntry = new DidTrustListEntry(); - trustListEntry.setType("JsonWebKey2020"); - trustListEntry.setId(configProperties.getDid().getTrustListIdPrefix() - + SEPARATOR_COLON - + getCountryAsLowerCaseAlpha3(signerInformationEntity.getCountry()) - + SEPARATOR_FRAGMENT - + URLEncoder.encode(signerInformationEntity.getKid(), StandardCharsets.UTF_8)); - trustListEntry.setController(configProperties.getDid().getTrustListControllerPrefix() - + SEPARATOR_COLON - + getCountryAsLowerCaseAlpha3(signerInformationEntity.getCountry())); - trustListEntry.setPublicKeyJwk(publicKeyJwk); - - trustList.getVerificationMethod().add(trustListEntry); - } - - /** - * Search for CSCA for DSC. - * - * @param dsc DSC to search CSCA for. - * @return Optional holding the CSCA if found. - */ - private Optional searchCsca(X509Certificate dsc, String country) { - - return trustedPartyService.getCscaByCountry(country) - .stream() - .map(csca -> parseCertificate(csca.getRawData())) - .filter(Objects::nonNull) - .filter(csca -> csca.getSubjectX500Principal() - .equals(dsc.getIssuerX500Principal())) - .findFirst(); - } - -} +///*- +// * ---license-start +// * WorldHealthOrganization / tng-key-distribution +// * --- +// * Copyright (C) 2021 - 2024 T-Systems International GmbH and all other contributors +// * --- +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// * ---license-end +// */ +// +//package tng.trustnetwork.keydistribution.service.did; +// +//import com.apicatalog.jsonld.document.JsonDocument; +//import com.danubetech.keyformats.crypto.ByteSigner; +//import com.fasterxml.jackson.databind.ObjectMapper; +//import eu.europa.ec.dgc.utils.CertificateUtils; +//import foundation.identity.jsonld.ConfigurableDocumentLoader; +//import foundation.identity.jsonld.JsonLDObject; +//import info.weboftrust.ldsignatures.jsonld.LDSecurityKeywords; +//import info.weboftrust.ldsignatures.signer.JsonWebSignature2020LdSigner; +//import java.io.IOException; +//import java.io.InputStream; +//import java.net.URI; +//import java.net.URLEncoder; +//import java.nio.charset.StandardCharsets; +//import java.security.PublicKey; +//import java.security.cert.CertificateEncodingException; +//import java.security.cert.CertificateException; +//import java.security.cert.X509Certificate; +//import java.security.interfaces.ECPublicKey; +//import java.security.interfaces.RSAPublicKey; +//import java.util.ArrayList; +//import java.util.Base64; +//import java.util.Date; +//import java.util.HashMap; +//import java.util.List; +//import java.util.Locale; +//import java.util.Map; +//import java.util.MissingResourceException; +//import java.util.Objects; +//import java.util.Optional; +//import lombok.RequiredArgsConstructor; +//import lombok.extern.slf4j.Slf4j; +//import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; +//import org.bouncycastle.cert.X509CertificateHolder; +//import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +//import org.springframework.scheduling.annotation.Scheduled; +//import org.springframework.stereotype.Service; +//import tng.trustnetwork.keydistribution.config.KdsConfigProperties; +//import tng.trustnetwork.keydistribution.entity.SignerInformationEntity; +//import tng.trustnetwork.keydistribution.service.SignerInformationService; +//import tng.trustnetwork.keydistribution.service.TrustedIssuerService; +//import tng.trustnetwork.keydistribution.service.TrustedPartyService; +//import tng.trustnetwork.keydistribution.service.did.entity.DidTrustList; +//import tng.trustnetwork.keydistribution.service.did.entity.DidTrustListEntry; +// +//@Slf4j +//@Service +//@RequiredArgsConstructor +//@ConditionalOnProperty("dgc.did.enableDidGeneration") +//public class DidTrustListService { +// +// private static final String SEPARATOR_COLON = ":"; +// +// private static final String SEPARATOR_FRAGMENT = "#"; +// +// private static final List DID_CONTEXTS = List.of( +// "https://www.w3.org/ns/did/v1", +// "https://w3id.org/security/suites/jws-2020/v1"); +// +// private final TrustedPartyService trustedPartyService; +// +// private final SignerInformationService signerInformationService; +// +// private final KdsConfigProperties configProperties; +// +// private final ByteSigner byteSigner; +// +// private final DidUploader didUploader; +// +// private final ObjectMapper objectMapper; +// +// private final CertificateUtils certificateUtils; +// +// private final TrustedIssuerService trustedIssuerService; +// +// private final GitProvider gitProvider; +// +// /** +// * Create and upload DID Document holding Uploaded DSC and Trusted Issuer. +// */ +// @Scheduled(cron = "${dgc.did.cron}") +// @SchedulerLock(name = "didTrustListGenerator") +// public void job() { +// +// String trustList; +// +// try { +// trustList = generateTrustList(null); +// } catch (Exception e) { +// log.error("Failed to generate DID-TrustList: {}", e.getMessage()); +// return; +// } +// +// try { +// didUploader.uploadDid(trustList.getBytes(StandardCharsets.UTF_8)); +// } catch (Exception e) { +// log.error("Failed to Upload DID-TrustList: {}", e.getMessage()); +// return; +// } +// +// List countries = signerInformationService.getCountryList(); +// +// for (String country : countries) { +// String countryTrustList; +// +// String countryAsSubcontainer = getCountryAsLowerCaseAlpha3(country); +// if (countryAsSubcontainer != null) { +// try { +// countryTrustList = generateTrustList(List.of(country)); +// } catch (Exception e) { +// log.error("Failed to generate DID-TrustList for country {} : {}", country, e.getMessage()); +// continue; +// } +// +// try { +// didUploader.uploadDid(countryAsSubcontainer, countryTrustList.getBytes(StandardCharsets.UTF_8)); +// } catch (Exception e) { +// log.error("Failed to Upload DID-TrustList for country {} : {}", country, e.getMessage()); +// } +// } +// } +// +// log.info("Finished DID Export Process"); +// +// gitProvider.upload(configProperties.getDid().getLocalFile().getDirectory()); +// } +// +// private String getCountryAsLowerCaseAlpha3(String country) { +// +// if (country == null || country.length() != 2 && country.length() != 3) { +// return null; +// } else if (country.length() == 3) { +// return country; +// } +// +// return configProperties.getDid().getVirtualCountries().computeIfAbsent(country, (c) -> { +// try { +// return new Locale("en", c).getISO3Country().toLowerCase(); +// } catch (MissingResourceException e) { +// log.error("Country Code to alpha 3 conversion issue for country {} : {}", +// c, e.getMessage()); +// return c; +// } +// }); +// } +// +// private String generateTrustList(List countries) throws Exception { +// +// DidTrustList trustList = new DidTrustList(); +// trustList.setContext(DID_CONTEXTS); +// trustList.setId(configProperties.getDid().getDidId()); +// trustList.setController(configProperties.getDid().getDidController()); +// trustList.setVerificationMethod(new ArrayList<>()); +// +// if (countries != null && !countries.isEmpty()) { +// trustList.setId(configProperties.getDid().getDidId() +// + SEPARATOR_COLON +// + getCountryAsLowerCaseAlpha3(countries.get(0))); +// } +// +// // Add DSC +// List signerInformationEntities = countries == null +// ? signerInformationService.getActiveCertificates() +// : signerInformationService.getActiveCertificatesForCountries(countries); +// +// for (SignerInformationEntity signerInformationEntity : signerInformationEntities) { +// +// X509Certificate parsedCertificate = parseCertificate(signerInformationEntity.getRawData()); +// PublicKey publicKey = parsedCertificate.getPublicKey(); +// +// if (publicKey instanceof RSAPublicKey rsaPublicKey) { +// addTrustListEntry(trustList, signerInformationEntity, +// new DidTrustListEntry.RsaPublicKeyJwk( +// rsaPublicKey, List.of(signerInformationEntity.getRawData())), parsedCertificate); +// +// } else if (publicKey instanceof ECPublicKey ecPublicKey) { +// addTrustListEntry(trustList, signerInformationEntity, +// new DidTrustListEntry.EcPublicKeyJwk( +// ecPublicKey, List.of(signerInformationEntity.getRawData())), parsedCertificate); +// +// } else { +// log.error("Public Key is not RSA or EC Public Key for cert {} of country {}", +// signerInformationEntity.getKid(), +// signerInformationEntity.getCountry()); +// } +// } +// +// // Add DID References +// trustedIssuerService.getAllDid() +// .forEach(did -> trustList.getVerificationMethod().add(did.getUrl())); +// +// // Create LD-Proof Document +// JsonWebSignature2020LdSigner signer = new JsonWebSignature2020LdSigner(byteSigner); +// signer.setCreated(new Date()); +// signer.setProofPurpose(LDSecurityKeywords.JSONLD_TERM_ASSERTIONMETHOD); +// signer.setVerificationMethod(URI.create(configProperties.getDid().getLdProofVerificationMethod())); +// signer.setDomain(configProperties.getDid().getLdProofDomain()); +// signer.setNonce(configProperties.getDid().getLdProofNonce()); +// +// // Load DID-Contexts +// Map contextMap = new HashMap<>(); +// for (String didContext : DID_CONTEXTS) { +// String didContextFile = configProperties.getDid().getContextMapping().get(didContext); +// +// if (didContextFile == null) { +// log.error("Failed to load DID-Context Document for {}: No Mapping to local JSON-File.", didContext); +// } +// +// try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream( +// "did_contexts/" + didContextFile)) { +// if (inputStream != null) { +// contextMap.put(URI.create(didContext), JsonDocument.of(inputStream)); +// } +// } catch (Exception e) { +// log.error("Failed to load DID-Context Document {}: {}", didContextFile, e.getMessage()); +// throw e; +// } +// } +// JsonLDObject jsonLdObject = JsonLDObject.fromJson(objectMapper.writeValueAsString(trustList)); +// jsonLdObject.setDocumentLoader(new ConfigurableDocumentLoader(contextMap)); +// +// signer.sign(jsonLdObject); +// +// return jsonLdObject.toJson(); +// } +// +// private X509Certificate parseCertificate(String raw) { +// +// try { +// byte[] rawDataBytes = Base64.getDecoder().decode(raw); +// X509CertificateHolder certificateHolder = new X509CertificateHolder(rawDataBytes); +// return certificateUtils.convertCertificate(certificateHolder); +// } catch (CertificateException | IOException e) { +// return null; +// } +// } +// +// private void addTrustListEntry(DidTrustList trustList, +// SignerInformationEntity signerInformationEntity, +// DidTrustListEntry.PublicKeyJwk publicKeyJwk, +// X509Certificate dsc) { +// +// Optional csca = searchCsca(dsc, signerInformationEntity.getCountry()); +// +// if (csca.isPresent()) { +// +// try { +// String encodedCsca = Base64.getEncoder().encodeToString(csca.get().getEncoded()); +// publicKeyJwk.getEncodedX509Certificates() +// .add(encodedCsca); +// } catch (CertificateEncodingException e) { +// throw new RuntimeException(e); +// } +// } +// +// DidTrustListEntry trustListEntry = new DidTrustListEntry(); +// trustListEntry.setType("JsonWebKey2020"); +// trustListEntry.setId(configProperties.getDid().getTrustListIdPrefix() +// + SEPARATOR_COLON +// + getCountryAsLowerCaseAlpha3(signerInformationEntity.getCountry()) +// + SEPARATOR_FRAGMENT +// + URLEncoder.encode(signerInformationEntity.getKid(), StandardCharsets.UTF_8)); +// trustListEntry.setController(configProperties.getDid().getTrustListControllerPrefix() +// + SEPARATOR_COLON +// + getCountryAsLowerCaseAlpha3(signerInformationEntity.getCountry())); +// trustListEntry.setPublicKeyJwk(publicKeyJwk); +// +// trustList.getVerificationMethod().add(trustListEntry); +// } +// +// /** +// * Search for CSCA for DSC. +// * +// * @param dsc DSC to search CSCA for. +// * @return Optional holding the CSCA if found. +// */ +// private Optional searchCsca(X509Certificate dsc, String country) { +// +// return trustedPartyService.getCscaByCountry(country) +// .stream() +// .map(csca -> parseCertificate(csca.getRawData())) +// .filter(Objects::nonNull) +// .filter(csca -> csca.getSubjectX500Principal() +// .equals(dsc.getIssuerX500Principal())) +// .findFirst(); +// } +// +//} diff --git a/src/main/java/tng/trustnetwork/keydistribution/service/did/DidTrustListServiceV2.java b/src/main/java/tng/trustnetwork/keydistribution/service/did/DidTrustListServiceV2.java new file mode 100644 index 0000000..97fc5cf --- /dev/null +++ b/src/main/java/tng/trustnetwork/keydistribution/service/did/DidTrustListServiceV2.java @@ -0,0 +1,364 @@ +/*- + * ---license-start + * WorldHealthOrganization / tng-key-distribution + * --- + * Copyright (C) 2021 - 2024 T-Systems International GmbH and all other contributors + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + +package tng.trustnetwork.keydistribution.service.did; + +import com.apicatalog.jsonld.document.JsonDocument; +import com.danubetech.keyformats.crypto.ByteSigner; +import com.fasterxml.jackson.databind.ObjectMapper; +import eu.europa.ec.dgc.utils.CertificateUtils; +import foundation.identity.jsonld.ConfigurableDocumentLoader; +import foundation.identity.jsonld.JsonLDObject; +import info.weboftrust.ldsignatures.jsonld.LDSecurityKeywords; +import info.weboftrust.ldsignatures.signer.JsonWebSignature2020LdSigner; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; +import org.bouncycastle.cert.X509CertificateHolder; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import tng.trustnetwork.keydistribution.config.KdsConfigProperties; +import tng.trustnetwork.keydistribution.entity.SignerInformationEntity; +import tng.trustnetwork.keydistribution.service.SignerInformationService; +import tng.trustnetwork.keydistribution.service.TrustedIssuerService; +import tng.trustnetwork.keydistribution.service.TrustedPartyService; +import tng.trustnetwork.keydistribution.service.did.entity.DidTrustList; +import tng.trustnetwork.keydistribution.service.did.entity.DidTrustListEntry; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.security.PublicKey; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPublicKey; +import java.util.*; + +@Slf4j +@Service +@RequiredArgsConstructor +@ConditionalOnProperty("dgc.did.enableDidGeneration") +public class DidTrustListServiceV2 { + + private static final String SEPARATOR_COLON = ":"; + + private static final String SEPARATOR_FRAGMENT = "#"; + + private static final List DID_CONTEXTS = List.of( + "https://www.w3.org/ns/did/v1", + "https://w3id.org/security/suites/jws-2020/v1"); + private static final String TYPE_DOMAIN = "D"; //domain + private static final String TYPE_PARTICIPANT = "P"; //participatn (aka country code) + private static final String TYPE_CERTIFICATE = "C"; //certificate type (DSC; SCA) + + private final TrustedPartyService trustedPartyService; + + private final SignerInformationService signerInformationService; + + private final KdsConfigProperties configProperties; + + private final ByteSigner byteSigner; + + private final DidUploader didUploader; + + private final ObjectMapper objectMapper; + + private final CertificateUtils certificateUtils; + + private final TrustedIssuerService trustedIssuerService; + + private final GitProvider gitProvider; + + /** + * Create and upload DID Document holding Uploaded DSC and Trusted Issuer. + */ + @Scheduled(cron = "${dgc.did.cron}") + @SchedulerLock(name = "didTrustListGeneratorV2") + public void job() { + + + // for all domains + for (String domain : signerInformationService.getDomainsList()) { + + try { + //generate + save DID for domain + saveDid(generateContainerPathForDid(domain, null, null), generateTrustList(domain, null, null)); + } catch (Exception e) { + log.error("Failed to process DID-TrustList for domain {} : {}", domain, e.getMessage()); + } + + //TODO: implement get all participants with given domain (see signerInformationService.getCountryList()) +// for (String participant : signerInformationService.getParticipantsByDomain(domain)) { +// String didDocument = null; +// try { +// saveDid(generateContainerPathForDid(domain, participant, null), generateTrustList(domain, participant, null)); +// saveDid(generateContainerPathForDid(domain, participant, DSC), generateTrustList(domain, participant, DSC)); +// saveDid(generateContainerPathForDid(domain, participant, CSCA), generateTrustList(domain, participant, CSCA)); +// } catch (Exception e) { +// log.error("Failed to process DID-TrustList for domain {} : {}", domain, e.getMessage()); +// } +// } + + } + + log.info("Finished DID Export Process"); + + //gitProvider.upload(configProperties.getDid().getLocalFile().getDirectory()); + + } + + + + private void saveDid(String containerPath, String didDocument){ + try { + didUploader.uploadDid(containerPath, didDocument.getBytes(StandardCharsets.UTF_8)); + } catch (Exception e) { + log.error("Failed to Upload DID-TrustList: {}", e.getMessage()); + } + } + + private String generateTrustList(String domain, String participant, String certificateType) throws Exception { + + DidTrustList trustList = new DidTrustList(); + trustList.setContext(DID_CONTEXTS); + trustList.setId(generateDidId(domain, participant, certificateType, null)); + trustList.setController(generateDidId(domain, participant, certificateType, null)); + trustList.setVerificationMethod(new ArrayList<>()); + + + // Add DSC + List signerInformationEntities = getSignerInformationEntities(domain, participant); + + for (SignerInformationEntity signerInformationEntity : signerInformationEntities) { + + X509Certificate parsedCertificate = parseCertificate(signerInformationEntity.getRawData()); + PublicKey publicKey = parsedCertificate.getPublicKey(); + + if (publicKey instanceof RSAPublicKey rsaPublicKey) { + //TODO: refactor to set TrustListEntry id based on parameters and kid + addTrustListEntry(trustList, signerInformationEntity, + new DidTrustListEntry.RsaPublicKeyJwk( + rsaPublicKey, List.of(signerInformationEntity.getRawData())), parsedCertificate); + + } else if (publicKey instanceof ECPublicKey ecPublicKey) { + //TODO: refactor to set TrustListEntry id based on parameters and kid + addTrustListEntry(trustList, signerInformationEntity, + new DidTrustListEntry.EcPublicKeyJwk( + ecPublicKey, List.of(signerInformationEntity.getRawData())), parsedCertificate); + + } else { + log.error("Public Key is not RSA or EC Public Key for cert {} of country {}", + signerInformationEntity.getKid(), + signerInformationEntity.getCountry()); + } + } + + // Add DID References + trustedIssuerService.getAllDid() + .forEach(did -> trustList.getVerificationMethod().add(did.getUrl())); + + // Create LD-Proof Document + JsonWebSignature2020LdSigner signer = new JsonWebSignature2020LdSigner(byteSigner); + signer.setCreated(new Date()); + signer.setProofPurpose(LDSecurityKeywords.JSONLD_TERM_ASSERTIONMETHOD); + signer.setVerificationMethod(URI.create(configProperties.getDid().getLdProofVerificationMethod())); + signer.setDomain(configProperties.getDid().getLdProofDomain()); + signer.setNonce(configProperties.getDid().getLdProofNonce()); + + // Load DID-Contexts + Map contextMap = new HashMap<>(); + for (String didContext : DID_CONTEXTS) { + String didContextFile = configProperties.getDid().getContextMapping().get(didContext); + + if (didContextFile == null) { + log.error("Failed to load DID-Context Document for {}: No Mapping to local JSON-File.", didContext); + } + + try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream( + "did_contexts/" + didContextFile)) { + if (inputStream != null) { + contextMap.put(URI.create(didContext), JsonDocument.of(inputStream)); + } + } catch (Exception e) { + log.error("Failed to load DID-Context Document {}: {}", didContextFile, e.getMessage()); + throw e; + } + } + JsonLDObject jsonLdObject = JsonLDObject.fromJson(objectMapper.writeValueAsString(trustList)); + jsonLdObject.setDocumentLoader(new ConfigurableDocumentLoader(contextMap)); + + signer.sign(jsonLdObject); + + return jsonLdObject.toJson(); + } + + + + + + private List getSignerInformationEntities(String domain, String participant) { + return signerInformationService.getActiveCertificatesForFilter(domain, participant); + } + + + + private String generateDidId(String domain, String participant, String certificateType, String kid) { + + //Example: did:web:tng-cdn-dev.who.int:trustlist:v.2.0.0:DDCC:XXA:DSC + + StringBuilder idBuilder = new StringBuilder(configProperties.getDid().getDidId()); + + if (domain != null && !domain.isEmpty()) { + idBuilder.append(SEPARATOR_COLON); + idBuilder.append(domain); + if (participant != null && !participant.isEmpty()) { + if (idBuilder.length() > configProperties.getDid().getDidId().length()) { + idBuilder.append(SEPARATOR_COLON); + } + idBuilder.append(participant); + if (certificateType != null && !certificateType.isEmpty()) { + if (idBuilder.length() > configProperties.getDid().getDidId().length()) { + idBuilder.append(SEPARATOR_COLON); + } + idBuilder.append(certificateType); + if (kid != null && !kid.isEmpty()) { + if (idBuilder.length() > configProperties.getDid().getDidId().length()) { + idBuilder.append(SEPARATOR_COLON); + } + idBuilder.append(kid); + } + } + } + } + //Note: the generated ID should match the path the resuting file is served at (check: getContainerPathForDid) + return idBuilder.toString(); + } + + private String generateContainerPathForDid(String domain, String participant, String certificateType) { + StringBuilder path = new StringBuilder(); + if (domain != null) { + path.append(domain); + } + if (participant != null) { + if (path.length() > 0) { + path.append("/"); + } + path.append(participant); + } + if (certificateType != null) { + if (path.length() > 0) { + path.append("/"); + } + path.append(certificateType); + } + return path.toString(); + } + + //writeDidLocal(Path, DidDoc) + + + + private String getCountryAsLowerCaseAlpha3(String country) { + + if (country == null || country.length() != 2 && country.length() != 3) { + return null; + } else if (country.length() == 3) { + return country; + } + + return configProperties.getDid().getVirtualCountries().computeIfAbsent(country, (c) -> { + try { + return new Locale("en", c).getISO3Country().toLowerCase(); + } catch (MissingResourceException e) { + log.error("Country Code to alpha 3 conversion issue for country {} : {}", + c, e.getMessage()); + return c; + } + }); + } + + + + private X509Certificate parseCertificate(String raw) { + + try { + byte[] rawDataBytes = Base64.getDecoder().decode(raw); + X509CertificateHolder certificateHolder = new X509CertificateHolder(rawDataBytes); + return certificateUtils.convertCertificate(certificateHolder); + } catch (CertificateException | IOException e) { + return null; + } + } + + private void addTrustListEntry(DidTrustList trustList, + SignerInformationEntity signerInformationEntity, + DidTrustListEntry.PublicKeyJwk publicKeyJwk, + X509Certificate dsc) { + + Optional csca = searchCsca(dsc, signerInformationEntity.getCountry()); + + if (csca.isPresent()) { + + try { + String encodedCsca = Base64.getEncoder().encodeToString(csca.get().getEncoded()); + publicKeyJwk.getEncodedX509Certificates() + .add(encodedCsca); + } catch (CertificateEncodingException e) { + throw new RuntimeException(e); + } + } + + DidTrustListEntry trustListEntry = new DidTrustListEntry(); + trustListEntry.setType("JsonWebKey2020"); + trustListEntry.setId(configProperties.getDid().getTrustListIdPrefix() + + SEPARATOR_COLON + + getCountryAsLowerCaseAlpha3(signerInformationEntity.getCountry()) + + SEPARATOR_FRAGMENT + + URLEncoder.encode(signerInformationEntity.getKid(), StandardCharsets.UTF_8)); + trustListEntry.setController(configProperties.getDid().getTrustListControllerPrefix() + + SEPARATOR_COLON + + getCountryAsLowerCaseAlpha3(signerInformationEntity.getCountry())); + trustListEntry.setPublicKeyJwk(publicKeyJwk); + + trustList.getVerificationMethod().add(trustListEntry); + } + + /** + * Search for CSCA for DSC. + * + * @param dsc DSC to search CSCA for. + * @return Optional holding the CSCA if found. + */ + private Optional searchCsca(X509Certificate dsc, String country) { + + return trustedPartyService.getCscaByCountry(country) + .stream() + .map(csca -> parseCertificate(csca.getRawData()))//TODO: CSCA for filter: domain, participant + .filter(Objects::nonNull) + .filter(csca -> csca.getSubjectX500Principal() + .equals(dsc.getIssuerX500Principal())) + .findFirst(); + } + +}