From 629af0bc966c0c3d554909d5cb30b73620f2ab94 Mon Sep 17 00:00:00 2001 From: kotharikaushal <168159346+kotharikaushal@users.noreply.github.com> Date: Tue, 20 Aug 2024 14:25:55 +0530 Subject: [PATCH 1/2] fix:Base64URL encoding for kid (#90) Signed-off-by: Kothari --- .../service/did/DidTrustListService.java | 7 ++++++- .../service/DidTrustListServiceTest.java | 17 +++++++++++------ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/main/java/eu/europa/ec/dgc/gateway/service/did/DidTrustListService.java b/src/main/java/eu/europa/ec/dgc/gateway/service/did/DidTrustListService.java index ef793993..b62f113a 100644 --- a/src/main/java/eu/europa/ec/dgc/gateway/service/did/DidTrustListService.java +++ b/src/main/java/eu/europa/ec/dgc/gateway/service/did/DidTrustListService.java @@ -23,6 +23,7 @@ import com.apicatalog.jsonld.document.JsonDocument; import com.danubetech.keyformats.crypto.ByteSigner; import com.fasterxml.jackson.databind.ObjectMapper; +import com.nimbusds.jose.util.Base64URL; import eu.europa.ec.dgc.gateway.config.DgcConfigProperties; import eu.europa.ec.dgc.gateway.entity.SignerInformationEntity; import eu.europa.ec.dgc.gateway.entity.TrustedIssuerEntity; @@ -262,7 +263,7 @@ private void addTrustListEntry(DidTrustListDto trustList, + SEPARATOR_COLON + getCountryAsLowerCaseAlpha3(cert.getCountry()) + SEPARATOR_FRAGMENT - + URLEncoder.encode(cert.getKid(), StandardCharsets.UTF_8)); + + getEncodedKid(cert.getKid()));; trustListEntry.setController(configProperties.getDid().getTrustListControllerPrefix() + SEPARATOR_COLON + getCountryAsLowerCaseAlpha3(cert.getCountry())); trustListEntry.setPublicKeyJwk(publicKeyJwk); @@ -282,4 +283,8 @@ private Optional searchForIssuer(TrustedCertificate .equals(cert.getParsedCertificate().getIssuerX500Principal())) .findFirst(); } + + private String getEncodedKid(String kid) { + return Base64URL.encode(kid).toString(); + } } diff --git a/src/test/java/eu/europa/ec/dgc/gateway/service/DidTrustListServiceTest.java b/src/test/java/eu/europa/ec/dgc/gateway/service/DidTrustListServiceTest.java index 0e5b8462..2685f4e2 100644 --- a/src/test/java/eu/europa/ec/dgc/gateway/service/DidTrustListServiceTest.java +++ b/src/test/java/eu/europa/ec/dgc/gateway/service/DidTrustListServiceTest.java @@ -23,6 +23,7 @@ import static org.mockito.Mockito.doNothing; import com.fasterxml.jackson.databind.ObjectMapper; +import com.nimbusds.jose.util.Base64URL; import eu.europa.ec.dgc.gateway.entity.FederationGatewayEntity; import eu.europa.ec.dgc.gateway.entity.SignerInformationEntity; import eu.europa.ec.dgc.gateway.entity.TrustedPartyEntity; @@ -220,12 +221,12 @@ void testTrustList(boolean isEcAlgorithm) throws Exception { Assertions.assertEquals("b", parsed.getController()); Assertions.assertEquals(6, parsed.getVerificationMethod().size()); - assertVerificationMethod(getVerificationMethodByKid(parsed.getVerificationMethod(), "c" + ":deu" + "#" + URLEncoder.encode(certDscDeKid, StandardCharsets.UTF_8)), + assertVerificationMethod(getVerificationMethodByKid(parsed.getVerificationMethod(), "c" + ":deu" + "#" + getEncodedKid(certDscDeKid)), 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"); + assertVerificationMethod(getVerificationMethodByKid(parsed.getVerificationMethod(), "c" + ":xeu" + "#" + getEncodedKid("kid2")), + "kid2", certDscEu, certCscaEu, "xeu"); + assertVerificationMethod(getVerificationMethodByKid(parsed.getVerificationMethod(), "c" + ":xex" + "#" + getEncodedKid("kid3")), + "kid3", federatedCertDscEx, null, "xex"); Assertions.assertTrue(parsed.getVerificationMethod().contains("did:trusted:DE:issuer")); Assertions.assertTrue(parsed.getVerificationMethod().contains("did:trusted:EU:issuer")); @@ -261,7 +262,7 @@ private void assertVerificationMethod(Object in, String kid, X509Certificate dsc LinkedHashMap jsonNode = (LinkedHashMap) in; Assertions.assertEquals("JsonWebKey2020", jsonNode.get("type")); Assertions.assertEquals("d" + ":" + country, jsonNode.get("controller")); - Assertions.assertEquals("c" + ":" + country + "#" + URLEncoder.encode(kid, StandardCharsets.UTF_8), jsonNode.get("id")); + Assertions.assertEquals("c" + ":" + country + "#" + getEncodedKid(kid), jsonNode.get("id")); LinkedHashMap publicKeyJwk = (LinkedHashMap) jsonNode.get("publicKeyJwk"); @@ -313,4 +314,8 @@ private static class LDProof { } } + + private String getEncodedKid(String kid) { + return Base64URL.encode(kid).toString(); + } } From 4045fa7e91585e0db40113e2e49e4fe54cd96420 Mon Sep 17 00:00:00 2001 From: kotharikaushal <168159346+kotharikaushal@users.noreply.github.com> Date: Thu, 22 Aug 2024 10:02:52 +0530 Subject: [PATCH 2/2] Feat: revocation endpoint should be shown again for compatibility reasons (#91) * feat: revocation endpoint should be shown again for compatibility reasons Signed-off-by: Kothari * Add application.yml file changes Signed-off-by: Kothari * Fix indentation changes Signed-off-by: Kothari --------- Signed-off-by: Kothari --- .../CertificateRevocationListController.java | 61 +-------- ...ficateRevocationListDefaultController.java | 121 ++++++++++++++++++ src/main/resources/application.yml | 4 +- 3 files changed, 128 insertions(+), 58 deletions(-) create mode 100644 src/main/java/eu/europa/ec/dgc/gateway/restapi/controller/CertificateRevocationListDefaultController.java diff --git a/src/main/java/eu/europa/ec/dgc/gateway/restapi/controller/CertificateRevocationListController.java b/src/main/java/eu/europa/ec/dgc/gateway/restapi/controller/CertificateRevocationListController.java index bbb97245..fc4dc5af 100644 --- a/src/main/java/eu/europa/ec/dgc/gateway/restapi/controller/CertificateRevocationListController.java +++ b/src/main/java/eu/europa/ec/dgc/gateway/restapi/controller/CertificateRevocationListController.java @@ -28,7 +28,6 @@ import eu.europa.ec.dgc.gateway.restapi.dto.SignedStringDto; import eu.europa.ec.dgc.gateway.restapi.dto.revocation.RevocationBatchDeleteRequestDto; import eu.europa.ec.dgc.gateway.restapi.dto.revocation.RevocationBatchDto; -import eu.europa.ec.dgc.gateway.restapi.dto.revocation.RevocationBatchListDto; import eu.europa.ec.dgc.gateway.restapi.filter.CertificateAuthenticationFilter; import eu.europa.ec.dgc.gateway.restapi.filter.CertificateAuthenticationRequired; import eu.europa.ec.dgc.gateway.restapi.filter.CertificateAuthenticationRole; @@ -45,14 +44,11 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement; import jakarta.validation.Valid; import jakarta.validation.constraints.Pattern; -import java.time.ZonedDateTime; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.DeleteMapping; @@ -61,7 +57,6 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestAttribute; import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -70,7 +65,9 @@ @RequiredArgsConstructor @Validated @Slf4j -@ConditionalOnProperty(name = "dgc.revocation.enabled", havingValue = "true") +@ConditionalOnExpression( + "'${dgc.revocation.enabled}' == 'true' and '${dgc.revocation.hidden.endpoints}' == 'true'" +) public class CertificateRevocationListController { private final RevocationListService revocationListService; @@ -84,56 +81,6 @@ public class CertificateRevocationListController { private static final String MDC_DOWNLOADED_COUNTRY = "downloadedCountry"; private static final String MDC_DOWNLOADED_BATCH_ID = "downloadedBatchId"; - /** - * Endpoint to download Revocation Batch List. - */ - @CertificateAuthenticationRequired(requiredRoles = CertificateAuthenticationRole.RevocationListReader) - @GetMapping(path = "", produces = MediaType.APPLICATION_JSON_VALUE) - @Operation( - security = { - @SecurityRequirement(name = OpenApiConfig.SECURITY_SCHEMA_HASH), - @SecurityRequirement(name = OpenApiConfig.SECURITY_SCHEMA_DISTINGUISH_NAME) - }, - tags = {"Revocation"}, - summary = "Download Batch List", - description = "Returning a list of batches with a small wrapper providing metadata." - + " The batches are sorted by date in ascending (chronological) order.", - parameters = { - @Parameter( - in = ParameterIn.HEADER, - name = HttpHeaders.IF_MODIFIED_SINCE, - description = "This header contains the last downloaded date to get just the latest results. " - + "On the initial call the header should be the set to ā€˜2021-06-01T00:00:00Zā€™", - required = true) - }, - responses = { - @ApiResponse( - responseCode = "200", - description = "Response contains the batch list.", - content = @Content(schema = @Schema(implementation = RevocationBatchListDto.class))), - @ApiResponse( - responseCode = "204", - description = "No Content if no data is available later than provided If-Modified-Since header.") - } - ) - public ResponseEntity downloadBatchList( - @Valid @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) - @RequestHeader(HttpHeaders.IF_MODIFIED_SINCE) ZonedDateTime ifModifiedSince) { - - if (ifModifiedSince.isAfter(ZonedDateTime.now())) { - throw new DgcgResponseException(HttpStatus.BAD_REQUEST, "", "IfModifiedSince must be in past", "", ""); - } - - RevocationBatchListDto revocationBatchListDto = - revocationBatchMapper.toDto(revocationListService.getRevocationBatchList(ifModifiedSince)); - - if (revocationBatchListDto.getBatches().isEmpty()) { - return ResponseEntity.noContent().build(); - } else { - return ResponseEntity.ok(revocationBatchListDto); - } - } - /** * Endpoint to download Revocation Batch. */ diff --git a/src/main/java/eu/europa/ec/dgc/gateway/restapi/controller/CertificateRevocationListDefaultController.java b/src/main/java/eu/europa/ec/dgc/gateway/restapi/controller/CertificateRevocationListDefaultController.java new file mode 100644 index 00000000..a8116c6a --- /dev/null +++ b/src/main/java/eu/europa/ec/dgc/gateway/restapi/controller/CertificateRevocationListDefaultController.java @@ -0,0 +1,121 @@ +/*- + * ---license-start + * WHO Digital Documentation Covid Certificate Gateway Service / ddcc-gateway + * --- + * Copyright (C) 2022 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 eu.europa.ec.dgc.gateway.restapi.controller; + +import eu.europa.ec.dgc.gateway.config.OpenApiConfig; +import eu.europa.ec.dgc.gateway.exception.DgcgResponseException; +import eu.europa.ec.dgc.gateway.restapi.dto.revocation.RevocationBatchListDto; +import eu.europa.ec.dgc.gateway.restapi.filter.CertificateAuthenticationRequired; +import eu.europa.ec.dgc.gateway.restapi.filter.CertificateAuthenticationRole; +import eu.europa.ec.dgc.gateway.restapi.mapper.RevocationBatchMapper; +import eu.europa.ec.dgc.gateway.service.RevocationListService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import jakarta.validation.Valid; +import java.time.ZonedDateTime; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/revocation-list") +@RequiredArgsConstructor +@Validated +@Slf4j +@ConditionalOnProperty(name = "dgc.revocation.enabled", havingValue = "true") +public class CertificateRevocationListDefaultController { + + private final RevocationListService revocationListService; + + private final RevocationBatchMapper revocationBatchMapper; + + public static final String UUID_REGEX = + "^[0-9a-f]{8}\\b-[0-9a-f]{4}\\b-[0-9a-f]{4}\\b-[0-9a-f]{4}\\b-[0-9a-f]{12}$"; + + private static final String MDC_DOWNLOADER_COUNTRY = "downloaderCountry"; + private static final String MDC_DOWNLOADED_COUNTRY = "downloadedCountry"; + private static final String MDC_DOWNLOADED_BATCH_ID = "downloadedBatchId"; + + /** + * Endpoint to download Revocation Batch List. + */ + @CertificateAuthenticationRequired(requiredRoles = CertificateAuthenticationRole.RevocationListReader) + @GetMapping(path = "", produces = MediaType.APPLICATION_JSON_VALUE) + @Operation( + security = { + @SecurityRequirement(name = OpenApiConfig.SECURITY_SCHEMA_HASH), + @SecurityRequirement(name = OpenApiConfig.SECURITY_SCHEMA_DISTINGUISH_NAME) + }, + tags = {"Revocation"}, + summary = "Download Batch List", + description = "Returning a list of batches with a small wrapper providing metadata." + + " The batches are sorted by date in ascending (chronological) order.", + parameters = { + @Parameter( + in = ParameterIn.HEADER, + name = HttpHeaders.IF_MODIFIED_SINCE, + description = "This header contains the last downloaded date to get just the latest results. " + + "On the initial call the header should be the set to ā€˜2021-06-01T00:00:00Zā€™", + required = true) + }, + responses = { + @ApiResponse( + responseCode = "200", + description = "Response contains the batch list.", + content = @Content(schema = @Schema(implementation = RevocationBatchListDto.class))), + @ApiResponse( + responseCode = "204", + description = "No Content if no data is available later than provided If-Modified-Since header.") + } + ) + public ResponseEntity downloadBatchList( + @Valid @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + @RequestHeader(HttpHeaders.IF_MODIFIED_SINCE) ZonedDateTime ifModifiedSince) { + + if (ifModifiedSince.isAfter(ZonedDateTime.now())) { + throw new DgcgResponseException(HttpStatus.BAD_REQUEST, "", "IfModifiedSince must be in past", "", ""); + } + + RevocationBatchListDto revocationBatchListDto = + revocationBatchMapper.toDto(revocationListService.getRevocationBatchList(ifModifiedSince)); + + if (revocationBatchListDto.getBatches().isEmpty()) { + return ResponseEntity.noContent().build(); + } else { + return ResponseEntity.ok(revocationBatchListDto); + } + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 751ba886..55bb3297 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -67,7 +67,9 @@ dgc: pem: X-SSL-Client-Cert revocation: delete-threshold: 14 - enabled: false + enabled: true + hidden: + endpoints: false signer-information: delete-threshold: 14 trustedCertificates: