Skip to content

Commit

Permalink
Merge pull request #78 from WorldHealthOrganization/feature/did-per-c…
Browse files Browse the repository at this point in the history
…ountry

feat(did): did per country
  • Loading branch information
ascheibal committed Oct 27, 2023
2 parents f676617 + b6d132d commit 49b3240
Show file tree
Hide file tree
Showing 9 changed files with 139 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ public class DgcConfigProperties {

private CloudmersiveConfig cloudmersive = new CloudmersiveConfig();

private CountryCodeMap countryCodeMap = new CountryCodeMap();

@Getter
@Setter
public static class DidConfig {
Expand Down Expand Up @@ -170,4 +172,10 @@ public static class Federation {
public static class SignerInformation {
private int deleteThreshold = 14;
}

@Getter
@Setter
public static class CountryCodeMap {
private Map<String, String> virtualCountries = new HashMap<>();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import com.azure.storage.blob.models.BlobHttpHeaders;
import eu.europa.ec.dgc.gateway.config.DgcConfigProperties;
import java.net.InetSocketAddress;
import java.text.MessageFormat;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.http.MediaType;
Expand All @@ -42,7 +43,7 @@
@Slf4j
public class AzureDidUploader implements DidUploader {

private final BlobClient blobClient;
private final BlobServiceClient blobServiceClient;

private final DgcConfigProperties dgcConfigProperties;

Expand All @@ -64,7 +65,7 @@ public AzureDidUploader(DgcConfigProperties dgcConfigProperties) {

HttpClient httpClient = HttpClient.createDefault(httpClientOptions);

BlobServiceClient blobServiceClient = new BlobServiceClientBuilder()
blobServiceClient = new BlobServiceClientBuilder()
.httpClient(httpClient)
.endpoint(dgcConfigProperties.getDid().getAzure().getBlobEndpoint())
.credential(new ClientSecretCredentialBuilder()
Expand All @@ -75,10 +76,24 @@ public AzureDidUploader(DgcConfigProperties dgcConfigProperties) {
.build())
.buildClient();

BlobContainerClient blobContainerClient = blobServiceClient.getBlobContainerClient(
dgcConfigProperties.getDid().getAzure().getBlobContainer());
}

blobClient = blobContainerClient.getBlobClient(dgcConfigProperties.getDid().getAzure().getBlobName());
/**
* Setup container specific BlobClient.
* @param subContainer the name of the container (will be used in lowercase)
*/
public BlobClient getBlobContainerClientForBlobContainer(String subContainer) {
BlobContainerClient blobContainerClient;
if (subContainer == null) {
blobContainerClient = blobServiceClient.getBlobContainerClient(
dgcConfigProperties.getDid().getAzure().getBlobContainer());
} else {
blobContainerClient = blobServiceClient.getBlobContainerClient(
MessageFormat.format("{0}/{1}",
dgcConfigProperties.getDid().getAzure().getBlobContainer(),
subContainer.toLowerCase()));
}
return blobContainerClient.getBlobClient(dgcConfigProperties.getDid().getAzure().getBlobName());
}

@Override
Expand All @@ -88,6 +103,20 @@ public void uploadDid(byte[] content) {
dgcConfigProperties.getDid().getAzure().getBlobContainer(),
dgcConfigProperties.getDid().getAzure().getBlobName());

BlobClient blobClient = getBlobContainerClientForBlobContainer(null);
blobClient.upload(BinaryData.fromBytes(content), true);
blobClient.setHttpHeaders(new BlobHttpHeaders().setContentType(MediaType.APPLICATION_JSON_VALUE));
log.info("Upload successful");
}

@Override
public void uploadDid(String subContainer, byte[] content) {
log.info("Uploading {} bytes as {}{}/{} to Azure BLOB Storage", content.length,
dgcConfigProperties.getDid().getAzure().getBlobEndpoint(),
dgcConfigProperties.getDid().getAzure().getBlobContainer(),
dgcConfigProperties.getDid().getAzure().getBlobName());

BlobClient blobClient = getBlobContainerClientForBlobContainer(subContainer);
blobClient.upload(BinaryData.fromBytes(content), true);
blobClient.setHttpHeaders(new BlobHttpHeaders().setContentType(MediaType.APPLICATION_JSON_VALUE));
log.info("Upload successful");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import eu.europa.ec.dgc.gateway.restapi.dto.did.DidTrustListEntryDto;
import eu.europa.ec.dgc.gateway.service.TrustListService;
import eu.europa.ec.dgc.gateway.service.TrustedIssuerService;
import eu.europa.ec.dgc.gateway.service.TrustedPartyService;
import foundation.identity.jsonld.ConfigurableDocumentLoader;
import foundation.identity.jsonld.JsonLDObject;
import info.weboftrust.ldsignatures.jsonld.LDSecurityKeywords;
Expand All @@ -50,7 +51,9 @@
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.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -60,6 +63,7 @@
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;


@Slf4j
@Service
@RequiredArgsConstructor
Expand All @@ -86,15 +90,17 @@ public class DidTrustListService {

private final ObjectMapper objectMapper;

private final TrustedPartyService trustedPartyService;

/**
* Create and upload DID Document holding Uploaded DSC and Trusted Issuer.
*/
@Scheduled(cron = "0 0 * * * *")
@Scheduled(cron = "${dgc.trustlist.cron}")
@SchedulerLock(name = "didTrustListGenerator")
public void job() {
String trustList;
try {
trustList = generateTrustList();
trustList = generateTrustList(null);
} catch (Exception e) {
log.error("Failed to generate DID-TrustList: {}", e.getMessage());
return;
Expand All @@ -106,21 +112,75 @@ public void job() {
log.error("Failed to Upload DID-TrustList: {}", e.getMessage());
return;
}

List<String> countries = trustedPartyService.getCountryList();

for (String country : countries) {
String countryTrustList = null;
List<String> countryAsList = List.of(country);
String countryAsSubcontainer = getCountryAsLowerCaseAlpha3(country);
if (countryAsSubcontainer != null) {
try {
countryTrustList = generateTrustList(countryAsList);
} 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");
}

private String generateTrustList() throws Exception {
private String getCountryAsLowerCaseAlpha3(String country) {
if (country == null || country.length() != 2 && country.length() != 3) {
return null;
} else if (country.length() == 3) {
return country.toLowerCase();
}

return configProperties.getCountryCodeMap().getVirtualCountries()
.compute(country, (alpha2, alpha3) -> {
if (alpha3 != null) {
return alpha3.toLowerCase();
}

try {
return new Locale("en", alpha2).getISO3Country().toLowerCase();
} catch (MissingResourceException e) {
log.error("Country Code to alpha 3 conversion issue for country {} : {}",
country, e.getMessage());
return null;
}
});
}

private String generateTrustList(List<String> countries) throws Exception {
DidTrustListDto trustList = new DidTrustListDto();
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)));
trustList.setController(configProperties.getDid().getDidController()
+ SEPARATOR_COLON
+ getCountryAsLowerCaseAlpha3(countries.get(0)));
}

// Add DSC
List<TrustedCertificateTrustList> certs = trustListService.getTrustedCertificateTrustList(
SignerInformationEntity.CertificateType.stringValues(),
null,
countries,
null,
configProperties.getDid().getIncludeFederated()
);
Expand All @@ -146,7 +206,7 @@ private String generateTrustList() throws Exception {

// Add TrustedIssuer
trustedIssuerService.search(
null, null, configProperties.getDid().getIncludeFederated()).stream()
null, countries, configProperties.getDid().getIncludeFederated()).stream()
.filter(trustedIssuer -> trustedIssuer.getUrlType() == TrustedIssuerEntity.UrlType.DID)
.forEach(trustedIssuer -> trustList.getVerificationMethod().add(trustedIssuer.getUrl()));

Expand Down Expand Up @@ -200,11 +260,11 @@ private void addTrustListEntry(DidTrustListDto trustList,
trustListEntry.setType("JsonWebKey2020");
trustListEntry.setId(configProperties.getDid().getTrustListIdPrefix()
+ SEPARATOR_COLON
+ cert.getCountry()
+ getCountryAsLowerCaseAlpha3(cert.getCountry())
+ SEPARATOR_FRAGMENT
+ URLEncoder.encode(cert.getKid(), StandardCharsets.UTF_8));
trustListEntry.setController(configProperties.getDid().getTrustListControllerPrefix()
+ SEPARATOR_COLON + cert.getCountry());
+ SEPARATOR_COLON + getCountryAsLowerCaseAlpha3(cert.getCountry()));
trustListEntry.setPublicKeyJwk(publicKeyJwk);

trustList.getVerificationMethod().add(trustListEntry);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,6 @@ public interface DidUploader {

void uploadDid(byte[] content);

void uploadDid(String subContainer, byte[] content);

}
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,9 @@ public void uploadDid(byte[] content) {
log.info("Uploaded {} bytes", content.length);
}

@Override
public void uploadDid(String subContainer, byte[] content) {
log.info("Uploaded {} bytes to subContainer {}", content.length, subContainer);
}

}
8 changes: 8 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,19 @@ dgc:
- SHC
- CRED
- RACSEL-DDVC
trustlist:
cron: "* */5 * * * *"
did:
enableDidGeneration: false
contextMapping:
"[https://www.w3.org/ns/did/v1]": did_v1.json
"[https://w3id.org/security/suites/jws-2020/v1]": jws-2020_v1.json
countryCodeMap:
virtualCountries:
XA: XXA
XB: XXB
XO: XXO
XL: XCL
cloudmersive:
apiKey: 0a0a0a0a-0a0a-0a0a-0a0a-0a0a0a0a0a0a
url: https://api.cloudmersive.com
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -603,7 +603,7 @@ void testValidationTimestamps3() throws Exception {
validationRule.setValidFrom(ZonedDateTime.now().plus(3, ChronoUnit.DAYS));
validationRule.setValidTo(ZonedDateTime.now()
.plus(6, ChronoUnit.DAYS)
.minus(1, ChronoUnit.SECONDS));
.minus(2, ChronoUnit.HOURS));

String payload = new SignedStringMessageBuilder()
.withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,12 +220,12 @@ void testTrustList(boolean isEcAlgorithm) throws Exception {
Assertions.assertEquals("b", parsed.getController());
Assertions.assertEquals(6, parsed.getVerificationMethod().size());

assertVerificationMethod(getVerificationMethodByKid(parsed.getVerificationMethod(), "c" + ":DE" + "#" + URLEncoder.encode(certDscDeKid, StandardCharsets.UTF_8)),
certDscDeKid, certDscDe, certCscaDe, "DE");
assertVerificationMethod(getVerificationMethodByKid(parsed.getVerificationMethod(), "c:EU#kid2"),
"kid2", certDscEu, certCscaEu, "EU");
assertVerificationMethod(getVerificationMethodByKid(parsed.getVerificationMethod(), "c:EX#kid3"),
"kid3", federatedCertDscEx, null, "EX");
assertVerificationMethod(getVerificationMethodByKid(parsed.getVerificationMethod(), "c" + ":deu" + "#" + URLEncoder.encode(certDscDeKid, StandardCharsets.UTF_8)),
certDscDeKid, certDscDe, certCscaDe, "deu");
assertVerificationMethod(getVerificationMethodByKid(parsed.getVerificationMethod(), "c:xeu#kid2"),
"kid2", certDscEu, certCscaEu, "xeu");
assertVerificationMethod(getVerificationMethodByKid(parsed.getVerificationMethod(), "c:xex#kid3"),
"kid3", federatedCertDscEx, null, "xex");

Assertions.assertTrue(parsed.getVerificationMethod().contains("did:trusted:DE:issuer"));
Assertions.assertTrue(parsed.getVerificationMethod().contains("did:trusted:EU:issuer"));
Expand Down
8 changes: 8 additions & 0 deletions src/test/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,11 @@ dgc:
apiKey: 0a0a0a0a-0a0a-0a0a-0a0a-0a0a0a0a0a0a
enabled: true
url: https://api.cloudmersive.com
countryCodeMap:
virtualCountries:
XA: XXA
XB: XXB
XO: XXO
XL: XCL
EU: XEU
EX: XEX

0 comments on commit 49b3240

Please sign in to comment.