Skip to content
This repository was archived by the owner on Sep 15, 2023. It is now read-only.

Commit f8d67ac

Browse files
authored
Merge pull request #39 from admin-ch/feature/renew
add verification endpoint for cert renewal
2 parents 6d1062e + fab3307 commit f8d67ac

File tree

4 files changed

+123
-19
lines changed

4 files changed

+123
-19
lines changed

ch-covidcertificate-backend-verification-check/ch-covidcertificate-backend-verification-check-ws/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@
7474
<dependency>
7575
<groupId>ch.admin.bag.covidcertificate</groupId>
7676
<artifactId>sdk-core</artifactId>
77-
<version>3.1.0</version>
77+
<version>3.2.0</version>
7878
</dependency>
7979

8080
<dependency>

ch-covidcertificate-backend-verification-check/ch-covidcertificate-backend-verification-check-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verification/check/ws/controller/VerificationController.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,31 @@ public VerificationController(VerificationService verificationService) {
8484
return verificationResponse;
8585
}
8686

87+
@Documentation(
88+
description = "Verify whether a certificate is eligible for renewal"
89+
)
90+
@PostMapping(path = {"/verifyRenewal"})
91+
public @ResponseBody VerificationResponse verifyRenewal(
92+
@RequestBody HCertPayload hCertPayload){
93+
// Decode hcert
94+
final var certificateHolder = verificationService.decodeHCert(hCertPayload);
95+
96+
// Verify hcert
97+
final var verificationState = verificationService.verifyDccForRenewal(certificateHolder);
98+
99+
// Build response
100+
final var verificationResponse = new VerificationResponse();
101+
verificationResponse.setHcertDecoded(certificateHolder);
102+
if (verificationState instanceof VerificationState.SUCCESS) {
103+
verificationResponse.setSuccessState((VerificationState.SUCCESS) verificationState);
104+
} else if (verificationState instanceof ERROR) {
105+
verificationResponse.setErrorState((ERROR) verificationState);
106+
} else {
107+
verificationResponse.setInvalidState((INVALID) verificationState);
108+
}
109+
return verificationResponse;
110+
}
111+
87112
@ExceptionHandler(DecodingException.class)
88113
@ResponseStatus(HttpStatus.BAD_REQUEST)
89114
public ResponseEntity<String> invalidHCert(DecodingException e) {

ch-covidcertificate-backend-verification-check/ch-covidcertificate-backend-verification-check-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verification/check/ws/model/TrustListConfig.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ public class TrustListConfig {
1010

1111
private RevokedCertificatesRepository revokedCertificatesRepository;
1212

13+
private TrustList renewalTrustList;
14+
1315
public RevokedCertificatesRepository getRevokedCertificatesRepository() {
1416
return revokedCertificatesRepository;
1517
}
@@ -42,4 +44,13 @@ public boolean isOutdated() {
4244
|| this.lastSync.isBefore(
4345
now.minusMillis(trustList.getRuleSet().getValidDuration()));
4446
}
47+
48+
public TrustList getRenewalTrustList() {
49+
return renewalTrustList;
50+
}
51+
52+
public void setRenewalTrustList(
53+
TrustList renewalTrustList) {
54+
this.renewalTrustList = renewalTrustList;
55+
}
4556
}

ch-covidcertificate-backend-verification-check/ch-covidcertificate-backend-verification-check-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verification/check/ws/verification/VerificationService.java

Lines changed: 86 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,18 @@
88
import ch.admin.bag.covidcertificate.backend.verification.check.ws.model.RevokedCertificatesRepository;
99
import ch.admin.bag.covidcertificate.backend.verification.check.ws.model.RevokedCertificatesRepository.RevokedCertificates;
1010
import ch.admin.bag.covidcertificate.backend.verification.check.ws.model.TrustListConfig;
11+
import ch.admin.bag.covidcertificate.sdk.core.data.ErrorCodes;
1112
import ch.admin.bag.covidcertificate.sdk.core.decoder.CertificateDecoder;
1213
import ch.admin.bag.covidcertificate.sdk.core.models.healthcert.CertificateHolder;
1314
import ch.admin.bag.covidcertificate.sdk.core.models.state.CheckNationalRulesState;
1415
import ch.admin.bag.covidcertificate.sdk.core.models.state.CheckRevocationState;
1516
import ch.admin.bag.covidcertificate.sdk.core.models.state.CheckSignatureState;
1617
import ch.admin.bag.covidcertificate.sdk.core.models.state.DecodeState;
18+
import ch.admin.bag.covidcertificate.sdk.core.models.state.SuccessState.WalletSuccessState;
1719
import ch.admin.bag.covidcertificate.sdk.core.models.state.VerificationState;
20+
import ch.admin.bag.covidcertificate.sdk.core.models.state.VerificationState.ERROR;
1821
import ch.admin.bag.covidcertificate.sdk.core.models.state.VerificationState.INVALID;
22+
import ch.admin.bag.covidcertificate.sdk.core.models.state.VerificationState.LOADING;
1923
import ch.admin.bag.covidcertificate.sdk.core.models.state.VerificationState.SUCCESS;
2024
import ch.admin.bag.covidcertificate.sdk.core.models.trustlist.ActiveModes;
2125
import ch.admin.bag.covidcertificate.sdk.core.models.trustlist.DisplayRule;
@@ -66,6 +70,10 @@ public class VerificationService {
6670

6771
private final RestTemplate rt;
6872

73+
private static final VerificationState DUMMY_SUCCESS_STATE =
74+
new VerificationState.SUCCESS(
75+
new WalletSuccessState(false, null, new ArrayList<>(), null, null), false);
76+
6977
@Value("${verifier.baseurl}")
7078
private String verifierBaseUrl;
7179

@@ -81,6 +89,8 @@ public class VerificationService {
8189
@Value("${verifier.api-key:}")
8290
private String apiKey;
8391

92+
private static final String SHOW_RENEW_BANNER = "showRenewBanner";
93+
8494
public VerificationService(RestTemplate rt) {
8595
this.rt = rt;
8696
}
@@ -93,12 +103,15 @@ public void init() {
93103
public void updateTrustListConfig() {
94104
try {
95105
logger.info("updating trust list config");
96-
Jwks jwks = getDSCs();
106+
Jwks jwks = getDSCs(null);
107+
Jwks jwksCh = getDSCs("CH");
97108
RevokedCertificatesRepository revokedCerts = getRevokedCerts();
98109
RuleSet nationalRules = getNationalRules();
99110
this.trustListConfig.setTrustList(new TrustList(jwks, revokedCerts, nationalRules));
100111
this.trustListConfig.setRevokedCertificatesRepository(revokedCerts);
101112
this.trustListConfig.setLastSync(Instant.now());
113+
this.trustListConfig.setRenewalTrustList(
114+
new TrustList(jwksCh, revokedCerts, nationalRules));
102115
logger.info("done updating trust list config");
103116
} catch (Exception e) {
104117
logger.error("failed to update trust list config", e);
@@ -111,17 +124,24 @@ public void updateTrustListConfig() {
111124
*
112125
* @return a JWKs object as required by the SDK-core mapped from a list of ClientCerts
113126
*/
114-
private Jwks getDSCs() throws URISyntaxException {
115-
logger.info("Updating list of DSCs");
127+
private Jwks getDSCs(String country) throws URISyntaxException {
128+
if (country == null) {
129+
logger.info("Updating list of DSCs for all countries");
130+
} else {
131+
logger.info("Updating list of DSCs for {}", country);
132+
}
116133
Map<String, String> params = getKeyUpdatesParams();
134+
if (country != null) {
135+
params.put("country", country);
136+
}
117137
List<Jwk> jwkList = new ArrayList<>();
118138
boolean done = false;
119139
int it = 0;
120140
do {
121141
final ResponseEntity<Jwks> response =
122142
rt.exchange(getRequestEntity(dscEndpoint, params), Jwks.class);
123143
var body = response.getBody();
124-
if(body != null){
144+
if (body != null) {
125145
jwkList.addAll(body.getCerts());
126146
}
127147

@@ -172,7 +192,7 @@ private RevokedCertificatesRepository getRevokedCerts() throws URISyntaxExceptio
172192
rt.exchange(
173193
getRequestEntity(revocationEndpoint, params), RevokedCertificates.class);
174194
RevokedCertificates revokedCerts = response.getBody();
175-
if(revokedCerts == null){
195+
if (revokedCerts == null) {
176196
logger.error("Failed to get revoked certificates");
177197
throw new NullPointerException("Failed to get revoked certificates");
178198
}
@@ -188,11 +208,11 @@ private RevokedCertificatesRepository getRevokedCerts() throws URISyntaxExceptio
188208
rt.exchange(
189209
getRequestEntity(revocationEndpoint, params),
190210
RevokedCertificates.class);
191-
if(response.getBody() != null) {
211+
if (response.getBody() != null) {
192212
var body = response.getBody();
193-
if(body != null){
213+
if (body != null) {
194214
repo.addCertificates(body.getRevokedCerts());
195-
}else{
215+
} else {
196216
logger.error("Failed to fetch some of the revoked certificates");
197217
}
198218
}
@@ -203,7 +223,7 @@ private RevokedCertificatesRepository getRevokedCerts() throws URISyntaxExceptio
203223

204224
it++;
205225
}
206-
logger.info("downloaded {} revoked certificates", revokedCerts.getRevokedCerts().size());
226+
logger.info("downloaded {} revoked certificates", revokedCerts.getRevokedCerts().size());
207227

208228
return repo;
209229
}
@@ -220,7 +240,7 @@ private RuleSet getNationalRules() throws URISyntaxException {
220240
getRequestEntity(rulesEndpoint, new HashMap<>()),
221241
IntermediateRuleSet.class)
222242
.getBody();
223-
if(intermediateRuleSet == null){
243+
if (intermediateRuleSet == null) {
224244
logger.error("Failed to fetch national rules");
225245
throw new NullPointerException("intermediateRuleSet is null");
226246
}
@@ -248,7 +268,12 @@ private RuleSet getNationalRules() throws URISyntaxException {
248268
.map(rule -> new DisplayRule(rule.getId(), rule.getLogic()))
249269
.collect(Collectors.toList());
250270
ModeRules intermediateModeRules = intermediateRuleSet.getModeRules();
251-
ch.admin.bag.covidcertificate.sdk.core.models.trustlist.ModeRules sdkModeRules = new ch.admin.bag.covidcertificate.sdk.core.models.trustlist.ModeRules(intermediateModeRules.getActiveModes(), intermediateModeRules.getWalletActiveModes(), intermediateModeRules.getVerifierActiveModes(), intermediateModeRules.getLogic());
271+
ch.admin.bag.covidcertificate.sdk.core.models.trustlist.ModeRules sdkModeRules =
272+
new ch.admin.bag.covidcertificate.sdk.core.models.trustlist.ModeRules(
273+
intermediateModeRules.getActiveModes(),
274+
intermediateModeRules.getWalletActiveModes(),
275+
intermediateModeRules.getVerifierActiveModes(),
276+
intermediateModeRules.getLogic());
252277
logger.info("downloaded {} rules", rules.size());
253278

254279
return new RuleSet(
@@ -303,10 +328,53 @@ public VerificationState verifyDccAllModes(CertificateHolder certificateHolder)
303328
: getOutdatedTrustListState(state);
304329
}
305330

306-
public VerificationState verifyDccSingleMode(CertificateHolder certificateHolder, String mode){
331+
public VerificationState verifyDccSingleMode(CertificateHolder certificateHolder, String mode) {
307332
TrustList trustList = trustListConfig.getTrustList();
308333
VerificationState state =
309-
VerifyWrapper.verifyVerifier(certificateVerifier, certificateHolder, trustList, mode);
334+
VerifyWrapper.verifyVerifier(
335+
certificateVerifier, certificateHolder, trustList, mode);
336+
return !trustListConfig.isOutdated() || state instanceof VerificationState.ERROR
337+
? state
338+
: getOutdatedTrustListState(state);
339+
}
340+
341+
public VerificationState verifyDccForRenewal(CertificateHolder certificateHolder) {
342+
TrustList trustList = trustListConfig.getRenewalTrustList();
343+
VerificationState state =
344+
VerifyWrapper.verifyWallet(certificateVerifier, certificateHolder, trustList);
345+
String renewBanner = null;
346+
if (state instanceof ERROR || state instanceof LOADING) {
347+
return state;
348+
} else if (state instanceof SUCCESS) {
349+
renewBanner =
350+
((WalletSuccessState) ((SUCCESS) state).getSuccessState()).getShowRenewBanner();
351+
} else if (state instanceof INVALID) {
352+
renewBanner = ((INVALID) state).getShowRenewBanner();
353+
}
354+
if (!SHOW_RENEW_BANNER.equals(renewBanner)) {
355+
return new INVALID(null, null, null, null, null);
356+
}
357+
// Certain verification failures are accepted in the renewal case as long as it's not
358+
// revoked
359+
if (state instanceof INVALID
360+
&& (((INVALID) state).getRevocationState()
361+
instanceof CheckRevocationState.SUCCESS)) {
362+
var signatureState = ((INVALID) state).getSignatureState();
363+
364+
// As long as the signature is ok, we can ignore rule failures
365+
if (signatureState instanceof CheckSignatureState.SUCCESS) {
366+
state = DUMMY_SUCCESS_STATE;
367+
// If the signature has expired but is otherwise ok, the cert is also approved for
368+
// renewal
369+
} else if (signatureState instanceof CheckSignatureState.INVALID) {
370+
var invalidState = (CheckSignatureState.INVALID) signatureState;
371+
if (invalidState
372+
.getSignatureErrorCode()
373+
.equals(ErrorCodes.SIGNATURE_TIMESTAMP_EXPIRED)) {
374+
state = DUMMY_SUCCESS_STATE;
375+
}
376+
}
377+
}
310378
return !trustListConfig.isOutdated() || state instanceof VerificationState.ERROR
311379
? state
312380
: getOutdatedTrustListState(state);
@@ -343,16 +411,16 @@ private VerificationState getOutdatedTrustListState(VerificationState originalSt
343411
signatureState,
344412
new CheckRevocationState.INVALID(TRUST_LIST_OUTDATED),
345413
new CheckNationalRulesState.INVALID(
346-
NationalRulesError.UNKNOWN_RULE_FAILED, false, TRUST_LIST_OUTDATED),
347-
null);
414+
NationalRulesError.UNKNOWN_RULE_FAILED, false, TRUST_LIST_OUTDATED, ""),
415+
null,
416+
"");
348417
}
349418

350-
public List<ActiveModes> getWalletVerificationModes(){
419+
public List<ActiveModes> getWalletVerificationModes() {
351420
return trustListConfig.getTrustList().getRuleSet().getModeRules().getActiveModes();
352421
}
353422

354-
public List<ActiveModes> getVerifierVerificationModes(){
423+
public List<ActiveModes> getVerifierVerificationModes() {
355424
return trustListConfig.getTrustList().getRuleSet().getModeRules().getVerifierActiveModes();
356425
}
357-
358426
}

0 commit comments

Comments
 (0)