diff --git a/src/main/java/eu/europa/ec/dgc/issuance/restapi/dto/DgciIdentifier.java b/src/main/java/eu/europa/ec/dgc/issuance/restapi/dto/DgciIdentifier.java index 08ff1bf..4a7c174 100644 --- a/src/main/java/eu/europa/ec/dgc/issuance/restapi/dto/DgciIdentifier.java +++ b/src/main/java/eu/europa/ec/dgc/issuance/restapi/dto/DgciIdentifier.java @@ -32,4 +32,5 @@ public class DgciIdentifier { private int algId; private String countryCode; private long expired; + private long expiredDuration; } diff --git a/src/main/java/eu/europa/ec/dgc/issuance/service/ConfigurableCborService.java b/src/main/java/eu/europa/ec/dgc/issuance/service/ConfigurableCborService.java index bdbb877..ffc30e2 100644 --- a/src/main/java/eu/europa/ec/dgc/issuance/service/ConfigurableCborService.java +++ b/src/main/java/eu/europa/ec/dgc/issuance/service/ConfigurableCborService.java @@ -6,9 +6,6 @@ import ehn.techiop.hcert.data.Eudgc; import ehn.techiop.hcert.kotlin.chain.impl.DefaultCborService; import eu.europa.ec.dgc.issuance.config.IssuanceConfigProperties; -import eu.europa.ec.dgc.issuance.entity.GreenCertificateType; -import java.time.Instant; -import java.time.temporal.ChronoUnit; import lombok.RequiredArgsConstructor; import org.jetbrains.annotations.NotNull; import org.springframework.stereotype.Service; @@ -38,22 +35,12 @@ public byte[] encode(@NotNull Eudgc input) { } catch (JsonProcessingException e) { throw new IllegalArgumentException(e); } - GreenCertificateType greenCertificateType; - if (input.getT() != null && !input.getT().isEmpty()) { - greenCertificateType = GreenCertificateType.Test; - } else if (input.getR() != null && !input.getR().isEmpty()) { - greenCertificateType = GreenCertificateType.Recovery; - } else { - greenCertificateType = GreenCertificateType.Vaccination; - } - long issueTime = Instant.now().getEpochSecond(); - long expirationTime = issueTime - + expirationService.expirationForType(greenCertificateType).get(ChronoUnit.SECONDS); + ExpirationService.CwtTimeFields cwtTimes = expirationService.calculateCwtExpiration(input); CBORObject coseContainer = CBORObject.NewMap(); coseContainer.set(CBORObject.FromObject(ISSUER), CBORObject.FromObject(issuanceConfigProperties.getCountryCode())); - coseContainer.set(CBORObject.FromObject(ISSUED_AT),CBORObject.FromObject(issueTime)); - coseContainer.set(CBORObject.FromObject(EXPIRATION),CBORObject.FromObject(expirationTime)); + coseContainer.set(CBORObject.FromObject(ISSUED_AT),CBORObject.FromObject(cwtTimes.getIssuedAt())); + coseContainer.set(CBORObject.FromObject(EXPIRATION),CBORObject.FromObject(cwtTimes.getExpiration())); CBORObject hcert = CBORObject.NewMap(); hcert.set(CBORObject.FromObject(HCERT_VERSION),CBORObject.DecodeFromBytes(cbor)); coseContainer.set(CBORObject.FromObject(HCERT),hcert); diff --git a/src/main/java/eu/europa/ec/dgc/issuance/service/DgciService.java b/src/main/java/eu/europa/ec/dgc/issuance/service/DgciService.java index 3f58b1f..2db57d9 100644 --- a/src/main/java/eu/europa/ec/dgc/issuance/service/DgciService.java +++ b/src/main/java/eu/europa/ec/dgc/issuance/service/DgciService.java @@ -63,8 +63,10 @@ import java.security.interfaces.RSAPublicKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; +import java.time.Duration; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Base64; import java.util.List; @@ -113,7 +115,8 @@ public DgciIdentifier initDgci(DgciInit dgciInit) { dgciEntity.setGreenCertificateType(dgciInit.getGreenCertificateType()); ZonedDateTime now = ZonedDateTime.now(); - ZonedDateTime expiration = now.plus(expirationService.expirationForType(dgciInit.getGreenCertificateType())); + Duration expirationDuration = expirationService.expirationForType(dgciInit.getGreenCertificateType()); + ZonedDateTime expiration = now.plus(expirationDuration); dgciEntity.setExpiresAt(expiration); dgciRepository.saveAndFlush(dgciEntity); @@ -131,7 +134,8 @@ public DgciIdentifier initDgci(DgciInit dgciInit) { certificateService.getKidAsBase64(), certificateService.getAlgorithmIdentifier(), issuanceConfigProperties.getCountryCode(), - expirationSec + expirationSec, + expirationDuration.get(ChronoUnit.SECONDS) ); } diff --git a/src/main/java/eu/europa/ec/dgc/issuance/service/ExpirationService.java b/src/main/java/eu/europa/ec/dgc/issuance/service/ExpirationService.java index 9264569..72cb468 100644 --- a/src/main/java/eu/europa/ec/dgc/issuance/service/ExpirationService.java +++ b/src/main/java/eu/europa/ec/dgc/issuance/service/ExpirationService.java @@ -1,9 +1,15 @@ package eu.europa.ec.dgc.issuance.service; +import ehn.techiop.hcert.data.Eudgc; import eu.europa.ec.dgc.issuance.config.IssuanceConfigProperties; import eu.europa.ec.dgc.issuance.entity.GreenCertificateType; import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; +import java.util.Date; +import lombok.Data; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; @@ -42,4 +48,66 @@ public Duration expirationForType(GreenCertificateType greenCertificateType) { } return duration; } + + /** + * calulate cbor web token expiration fields. + * It depends partly on configuration and for test and recovery also from DGC Json data + * @param eudgc json data of dgc + * @return the times + */ + public CwtTimeFields calculateCwtExpiration(Eudgc eudgc) { + CwtTimeFields result = new CwtTimeFields(); + GreenCertificateType greenCertificateType; + long expirationTime; + long issueTime = Instant.now().getEpochSecond(); + long expirationStartTime = issueTime; + + if (eudgc.getT() != null && !eudgc.getT().isEmpty()) { + greenCertificateType = GreenCertificateType.Test; + expirationStartTime = extractTimesSec(eudgc.getT().get(0).getSc(),expirationStartTime); + expirationTime = expirationStartTime + expirationForType(greenCertificateType).get(ChronoUnit.SECONDS); + } else if (eudgc.getR() != null && !eudgc.getR().isEmpty()) { + greenCertificateType = GreenCertificateType.Recovery; + expirationTime = expirationStartTime + expirationForType(greenCertificateType).get(ChronoUnit.SECONDS); + expirationTime = extractTimesSec(eudgc.getR().get(0).getDu(),expirationTime); + } else if (eudgc.getV() != null && !eudgc.getV().isEmpty()) { + greenCertificateType = GreenCertificateType.Vaccination; + expirationStartTime = extractTimesSec(eudgc.getV().get(0).getDt(),expirationStartTime); + expirationTime = expirationStartTime + expirationForType(greenCertificateType).get(ChronoUnit.SECONDS); + } else { + // fallback + greenCertificateType = GreenCertificateType.Vaccination; + expirationTime = expirationStartTime + expirationForType(greenCertificateType).get(ChronoUnit.SECONDS); + } + result.setIssuedAt(issueTime); + result.setExpiration(expirationTime); + return result; + } + + private long extractTimesSec(Date date, long defaultTimeSec) { + long timeSec; + if (date != null) { + timeSec = date.toInstant().getEpochSecond(); + } else { + timeSec = defaultTimeSec; + } + return timeSec; + } + + private long extractTimesSec(String dateAsString, long defaultTimeSec) { + long timeSec; + if (dateAsString != null && dateAsString.length() > 0) { + timeSec = LocalDate.parse(dateAsString).atStartOfDay().toInstant(ZoneOffset.UTC).getEpochSecond(); + } else { + timeSec = defaultTimeSec; + } + return timeSec; + } + + + @Data + public static class CwtTimeFields { + long issuedAt; + long expiration; + } } diff --git a/src/test/java/eu/europa/ec/dgc/issuance/service/ExpirationServiceTest.java b/src/test/java/eu/europa/ec/dgc/issuance/service/ExpirationServiceTest.java new file mode 100644 index 0000000..9c09809 --- /dev/null +++ b/src/test/java/eu/europa/ec/dgc/issuance/service/ExpirationServiceTest.java @@ -0,0 +1,46 @@ +package eu.europa.ec.dgc.issuance.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import ehn.techiop.hcert.data.Eudgc; +import ehn.techiop.hcert.kotlin.chain.SampleData; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@SpringBootTest +class ExpirationServiceTest { + @Autowired + ExpirationService expirationService; + ObjectMapper objectMapper = new ObjectMapper(); + + @Test + void testExpriationCalculation() throws Exception { + String vacDataJson = SampleData.Companion.getVaccination(); + Eudgc eudgc = testCalculation("vactination",vacDataJson); + System.out.println(eudgc.getV().get(0).getDt()); + String recoveryDataJson = SampleData.Companion.getRecovery(); + eudgc = testCalculation("recovery",recoveryDataJson); + System.out.println(eudgc.getR().get(0).getDu()); + String testDataJson = SampleData.Companion.getTestNaa(); + eudgc = testCalculation("test",testDataJson); + System.out.println(eudgc.getT().get(0).getSc().toInstant().atOffset(ZoneOffset.UTC)); + assertNotNull(eudgc); + + } + + private Eudgc testCalculation(String description, String vacDataJson) throws com.fasterxml.jackson.core.JsonProcessingException { + System.out.println("testing: "+description); + Eudgc eudgc = objectMapper.readValue(vacDataJson,Eudgc.class); + ExpirationService.CwtTimeFields expTime = expirationService.calculateCwtExpiration(eudgc); + LocalDateTime issuedAt = LocalDateTime.ofEpochSecond(expTime.getIssuedAt(), 0, ZoneOffset.UTC); + assertNotNull(issuedAt); + System.out.println(issuedAt); + LocalDateTime expired = LocalDateTime.ofEpochSecond(expTime.getExpiration(), 0, ZoneOffset.UTC); + System.out.println(expired); + return eudgc; + } +} diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index e9f9132..e28e09a 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -18,6 +18,10 @@ issuance: privateKeyPassword: dgca countryCode: DE tanExpirationHours: 24 + expiration: + vaccination: 365 + recovery: 365 + test: 3 endpoints: frontendIssuing: true backendIssuing: true