Skip to content

Commit eb9a7de

Browse files
authored
Merge pull request #83 from eu-digital-green-certificates/feat/revocation_list_download
Feat/revocation list download
2 parents 174c63d + 073da3a commit eb9a7de

10 files changed

Lines changed: 819 additions & 0 deletions
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package eu.europa.ec.dgc.gateway.connector;
2+
3+
4+
import com.fasterxml.jackson.core.JsonProcessingException;
5+
import com.fasterxml.jackson.databind.DeserializationFeature;
6+
import com.fasterxml.jackson.databind.ObjectMapper;
7+
import eu.europa.ec.dgc.gateway.connector.client.DgcGatewayConnectorRestClient;
8+
import eu.europa.ec.dgc.gateway.connector.dto.RevocationBatchDto;
9+
import eu.europa.ec.dgc.gateway.connector.exception.RevocationBatchDownloadException;
10+
import eu.europa.ec.dgc.gateway.connector.exception.RevocationBatchGoneException;
11+
import eu.europa.ec.dgc.gateway.connector.exception.RevocationBatchParseException;
12+
import eu.europa.ec.dgc.gateway.connector.iterator.DgcGatewayRevocationListDownloadIterator;
13+
import eu.europa.ec.dgc.signing.SignedMessageParser;
14+
import eu.europa.ec.dgc.signing.SignedStringMessageParser;
15+
import feign.FeignException;
16+
import java.time.ZonedDateTime;
17+
import lombok.RequiredArgsConstructor;
18+
import lombok.extern.slf4j.Slf4j;
19+
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
20+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
21+
import org.springframework.context.annotation.Scope;
22+
import org.springframework.http.HttpStatus;
23+
import org.springframework.http.ResponseEntity;
24+
import org.springframework.stereotype.Service;
25+
26+
@ConditionalOnProperty("dgc.gateway.connector.enabled")
27+
@Service
28+
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
29+
@RequiredArgsConstructor
30+
@Slf4j
31+
public class DgcGatewayRevocationListDownloadConnector {
32+
33+
private final DgcGatewayConnectorRestClient dgcGatewayConnectorRestClient;
34+
private final ObjectMapper objectMapper;
35+
36+
/**
37+
* Gets a revocation list iterator, for partly downloading the revocation list.
38+
* The if-modified-since header is set to the default value to start at the beginning of the list.
39+
* @return revocation list iterator
40+
*/
41+
public DgcGatewayRevocationListDownloadIterator getRevocationListDownloadIterator() {
42+
return new DgcGatewayRevocationListDownloadIterator(dgcGatewayConnectorRestClient);
43+
}
44+
45+
/**
46+
* Gets a revocation list iterator, for partly downloading the revocation list.
47+
* The if-modified-since header is set to the value of the parameter. Only newer part of the list are downloaded.
48+
* @param ifModifiedSinceDate The value for the if-modified-since header
49+
* @return revocation list iterator
50+
*/
51+
public DgcGatewayRevocationListDownloadIterator getRevocationListDownloadIterator(
52+
ZonedDateTime ifModifiedSinceDate) {
53+
54+
return new DgcGatewayRevocationListDownloadIterator(dgcGatewayConnectorRestClient, ifModifiedSinceDate);
55+
}
56+
57+
/**
58+
* Gets the revocation list batch data for a given batchId.
59+
* @param batchId the id of the batch to download.
60+
* @return the batch data.
61+
*/
62+
public RevocationBatchDto getRevocationListBatchById(String batchId) throws RevocationBatchDownloadException,
63+
RevocationBatchGoneException, RevocationBatchParseException {
64+
65+
ResponseEntity<String> responseEntity;
66+
67+
try {
68+
responseEntity = dgcGatewayConnectorRestClient.downloadBatch(batchId);
69+
} catch (FeignException e) {
70+
log.error("Download of revocation list batch failed. DGCG responded with status code: {}", e.status());
71+
72+
if (e.status() == HttpStatus.GONE.value()) {
73+
throw new RevocationBatchGoneException(String.format("Batch already gone: %s", batchId),batchId);
74+
}
75+
76+
throw new RevocationBatchDownloadException("Batch download failed with exception.", e);
77+
}
78+
79+
if (responseEntity.getStatusCode() != HttpStatus.OK) {
80+
int statusCode = responseEntity.getStatusCode().value();
81+
log.error("Download of revocation list batch failed. DGCG responded with status code: {}", statusCode);
82+
83+
throw new RevocationBatchDownloadException(
84+
String.format("Batch download failed with unexpected response. Response status code: %d", statusCode),
85+
statusCode);
86+
}
87+
88+
String cms = responseEntity.getBody();
89+
90+
if (!checkCmsSignature(cms)) {
91+
log.error("CMS check failed for revocation batch: {}", batchId);
92+
throw new RevocationBatchParseException(
93+
String.format("CMS check failed for revocation batch: %s", batchId), batchId);
94+
}
95+
96+
return map(cms, batchId);
97+
}
98+
99+
private boolean checkCmsSignature(String cms) {
100+
SignedStringMessageParser parser =
101+
new SignedStringMessageParser(cms);
102+
103+
if (parser.getParserState() != SignedMessageParser.ParserState.SUCCESS) {
104+
log.error("Invalid CMS for Revocation List Batch.");
105+
return false;
106+
}
107+
108+
if (!parser.isSignatureVerified()) {
109+
log.error("Invalid CMS Signature for Revocation List Batch");
110+
return false;
111+
}
112+
113+
return true;
114+
}
115+
116+
private RevocationBatchDto map(String cms, String batchId) {
117+
SignedStringMessageParser parser =
118+
new SignedStringMessageParser(cms);
119+
120+
try {
121+
objectMapper.configure(DeserializationFeature.FAIL_ON_TRAILING_TOKENS, true);
122+
return objectMapper.readValue(parser.getPayload(), RevocationBatchDto.class);
123+
} catch (JsonProcessingException e) {
124+
log.error("Failed to parse revocation batch JSON: {}", e.getMessage());
125+
126+
throw new RevocationBatchParseException(
127+
String.format("Failed to parse revocation batch JSON: %s", e.getMessage()), batchId);
128+
}
129+
130+
}
131+
132+
}

src/main/java/eu/europa/ec/dgc/gateway/connector/client/DgcGatewayConnectorRestClient.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,22 @@
2121
package eu.europa.ec.dgc.gateway.connector.client;
2222

2323
import eu.europa.ec.dgc.gateway.connector.dto.CertificateTypeDto;
24+
import eu.europa.ec.dgc.gateway.connector.dto.RevocationBatchListDto;
2425
import eu.europa.ec.dgc.gateway.connector.dto.TrustListItemDto;
2526
import eu.europa.ec.dgc.gateway.connector.dto.ValidationRuleDto;
2627
import java.util.List;
2728
import java.util.Map;
2829
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
2930
import org.springframework.cloud.openfeign.FeignClient;
31+
import org.springframework.http.HttpHeaders;
3032
import org.springframework.http.MediaType;
3133
import org.springframework.http.ResponseEntity;
3234
import org.springframework.web.bind.annotation.DeleteMapping;
3335
import org.springframework.web.bind.annotation.GetMapping;
3436
import org.springframework.web.bind.annotation.PathVariable;
3537
import org.springframework.web.bind.annotation.PostMapping;
3638
import org.springframework.web.bind.annotation.RequestBody;
39+
import org.springframework.web.bind.annotation.RequestHeader;
3740

3841
@ConditionalOnProperty("dgc.gateway.connector.enabled")
3942
@FeignClient(
@@ -119,4 +122,22 @@ public interface DgcGatewayConnectorRestClient {
119122
@GetMapping(value = "/rules/{cc}", produces = MediaType.APPLICATION_JSON_VALUE)
120123
ResponseEntity<Map<String, List<ValidationRuleDto>>> downloadValidationRule(@PathVariable("cc") String countryCode);
121124

125+
126+
/**
127+
* Downloads a batch list from the revocation list.
128+
*
129+
*/
130+
@GetMapping(value = "/revocation-list", produces = MediaType.APPLICATION_JSON_VALUE)
131+
ResponseEntity<RevocationBatchListDto> downloadRevocationList(
132+
@RequestHeader(HttpHeaders.IF_MODIFIED_SINCE) String lastUpdate);
133+
134+
/**
135+
* Downloads a batch of the revocation list.
136+
*
137+
* @param batchId ID of the batch to download
138+
* @return batch as cms massage
139+
*/
140+
@GetMapping(value = "/revocation-list/{batchId}", produces = {"application/cms-text"})
141+
ResponseEntity<String> downloadBatch(@PathVariable("batchId") String batchId);
142+
122143
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*-
2+
* ---license-start
3+
* eu-digital-green-certificates / dgc-lib
4+
* ---
5+
* Copyright (C) 2022 T-Systems International GmbH and all other contributors
6+
* ---
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
* ---license-end
19+
*/
20+
21+
package eu.europa.ec.dgc.gateway.connector.dto;
22+
23+
import java.time.ZonedDateTime;
24+
import java.util.List;
25+
import lombok.AllArgsConstructor;
26+
import lombok.Data;
27+
import lombok.NoArgsConstructor;
28+
29+
30+
@Data
31+
@NoArgsConstructor
32+
@AllArgsConstructor
33+
public class RevocationBatchDto {
34+
35+
private String country;
36+
37+
private ZonedDateTime expires;
38+
39+
private String kid;
40+
41+
private RevocationHashTypeDto hashType;
42+
43+
private List<BatchEntryDto> entries;
44+
45+
@Data
46+
@NoArgsConstructor
47+
@AllArgsConstructor
48+
public static class BatchEntryDto {
49+
50+
private String hash;
51+
52+
}
53+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*-
2+
* ---license-start
3+
* eu-digital-green-certificates / dgc-lib
4+
* ---
5+
* Copyright (C) 2022 T-Systems International GmbH and all other contributors
6+
* ---
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
* ---license-end
19+
*/
20+
21+
package eu.europa.ec.dgc.gateway.connector.dto;
22+
23+
import java.time.ZonedDateTime;
24+
import java.util.List;
25+
import lombok.AllArgsConstructor;
26+
import lombok.Data;
27+
import lombok.NoArgsConstructor;
28+
29+
30+
@Data
31+
public class RevocationBatchListDto {
32+
33+
34+
private Boolean more;
35+
36+
private List<RevocationBatchListItemDto> batches;
37+
38+
@Data
39+
@AllArgsConstructor
40+
@NoArgsConstructor
41+
public static class RevocationBatchListItemDto {
42+
43+
private String batchId;
44+
45+
private String country;
46+
47+
private ZonedDateTime date;
48+
49+
private Boolean deleted;
50+
}
51+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*-
2+
* ---license-start
3+
* eu-digital-green-certificates / dgc-lib
4+
* ---
5+
* Copyright (C) 2022 T-Systems International GmbH and all other contributors
6+
* ---
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
* ---license-end
19+
*/
20+
21+
package eu.europa.ec.dgc.gateway.connector.dto;
22+
23+
// Type of hash for revocation lists
24+
public enum RevocationHashTypeDto {
25+
26+
// The hash is calculated over the UCI string encoded in UTF-8 and converted to a byte array.
27+
UCI,
28+
29+
// The hash is calculated over the bytes of the COSE_SIGN1 signature from the CWT
30+
SIGNATURE,
31+
32+
// The CountryCode encoded as a UTF-8 string concatenated with the UCI encoded with a
33+
// UTF-8 string. This is then converted to a byte array and used as input to the hash function.")
34+
COUNTRYCODEUCI
35+
36+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package eu.europa.ec.dgc.gateway.connector.exception;
2+
3+
import lombok.Getter;
4+
5+
@Getter
6+
public class RevocationBatchDownloadException extends RuntimeException {
7+
8+
9+
private final int status;
10+
11+
public RevocationBatchDownloadException(String message, Throwable inner) {
12+
super(message, inner);
13+
this.status = 500;
14+
}
15+
16+
public RevocationBatchDownloadException(String message) {
17+
super(message);
18+
this.status = 500;
19+
}
20+
21+
public RevocationBatchDownloadException(String message, Throwable inner, int status) {
22+
super(message, inner);
23+
this.status = status;
24+
}
25+
26+
public RevocationBatchDownloadException(String message, int status) {
27+
super(message);
28+
this.status = status;
29+
}
30+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package eu.europa.ec.dgc.gateway.connector.exception;
2+
3+
import lombok.Getter;
4+
5+
@Getter
6+
public class RevocationBatchGoneException extends RuntimeException {
7+
8+
private final String batchId;
9+
10+
public RevocationBatchGoneException(String message, String batchId) {
11+
super(message);
12+
this.batchId = batchId;
13+
}
14+
15+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package eu.europa.ec.dgc.gateway.connector.exception;
2+
3+
import lombok.Getter;
4+
5+
@Getter
6+
public class RevocationBatchParseException extends RuntimeException {
7+
8+
private final String batchId;
9+
10+
public RevocationBatchParseException(String message, String batchId) {
11+
super(message);
12+
this.batchId = batchId;
13+
}
14+
15+
}
16+

0 commit comments

Comments
 (0)