diff --git a/dpppt-backend-sdk/dpppt-backend-sdk-data/src/main/java/org/dpppt/backend/sdk/data/gaen/FakeKeyService.java b/dpppt-backend-sdk/dpppt-backend-sdk-data/src/main/java/org/dpppt/backend/sdk/data/gaen/FakeKeyService.java index c6f1e7d6..d96b81cd 100644 --- a/dpppt-backend-sdk/dpppt-backend-sdk-data/src/main/java/org/dpppt/backend/sdk/data/gaen/FakeKeyService.java +++ b/dpppt-backend-sdk/dpppt-backend-sdk-data/src/main/java/org/dpppt/backend/sdk/data/gaen/FakeKeyService.java @@ -3,6 +3,7 @@ import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.time.Duration; +import java.time.Instant; import java.time.LocalDate; import java.time.LocalTime; import java.time.ZoneOffset; @@ -16,7 +17,7 @@ import org.slf4j.LoggerFactory; public class FakeKeyService { - + private final GAENDataService dataService; private final Integer minNumOfKeys; private final SecureRandom random; @@ -49,12 +50,13 @@ public void updateFakeKeys() { random.nextBytes(keyData); var keyGAENTime = (int) Duration.ofSeconds(tmpDate.toEpochSecond(LocalTime.MIDNIGHT, ZoneOffset.UTC)) .dividedBy(GaenUnit.TenMinutes.getDuration()); - var key = new GaenKey(Base64.getEncoder().encodeToString(keyData), keyGAENTime, 144, 0); + + var key = new GaenKey(Base64.getEncoder().encodeToString(keyData), keyGAENTime, GaenKey.GaenKeyDefaultRollingPeriod, 0); keys.add(key); } this.dataService.upsertExposees(keys); tmpDate = tmpDate.plusDays(1); - } while (tmpDate.isBefore(currentKeyDate.plusDays(1))); + } while (tmpDate.isBefore(currentKeyDate)); } private void deleteAllKeys() { @@ -66,8 +68,13 @@ public List fillUpKeys(List keys, Long publishedafter, Long ke if (!isEnabled) { return keys; } + var today = LocalDate.now(ZoneOffset.UTC); + var keyLocalDate = LocalDate.ofInstant(Instant.ofEpochMilli(keyDate), ZoneOffset.UTC); + if (today.isEqual(keyLocalDate)) { + return keys; + } var fakeKeys = this.dataService.getSortedExposedForKeyDate(keyDate, publishedafter, - LocalDate.now().plusDays(1).atStartOfDay(ZoneOffset.UTC).toInstant().toEpochMilli()); + today.plusDays(1).atStartOfDay(ZoneOffset.UTC).toInstant().toEpochMilli()); keys.addAll(fakeKeys); return keys; diff --git a/dpppt-backend-sdk/dpppt-backend-sdk-data/src/main/java/org/dpppt/backend/sdk/data/gaen/GAENDataService.java b/dpppt-backend-sdk/dpppt-backend-sdk-data/src/main/java/org/dpppt/backend/sdk/data/gaen/GAENDataService.java index ab6cf6b8..951c019c 100644 --- a/dpppt-backend-sdk/dpppt-backend-sdk-data/src/main/java/org/dpppt/backend/sdk/data/gaen/GAENDataService.java +++ b/dpppt-backend-sdk/dpppt-backend-sdk-data/src/main/java/org/dpppt/backend/sdk/data/gaen/GAENDataService.java @@ -11,7 +11,6 @@ package org.dpppt.backend.sdk.data.gaen; import java.time.Duration; -import java.time.OffsetDateTime; import java.util.List; import org.dpppt.backend.sdk.model.gaen.GaenKey; @@ -25,14 +24,6 @@ public interface GAENDataService { */ void upsertExposees(List keys); - /** - * Upserts (Update or Inserts) the given list of exposed keys, with delayed release of same day TEKs - * - * @param keys the list of exposed keys to upsert - * @param delayedReceivedAt the timestamp to use for the delayed release (if null use now rounded to next bucket) - */ - void upsertExposeesDelayed(List keys, OffsetDateTime delayedReceivedAt); - /** * Returns the maximum id of the stored exposed entries for the given batch. * diff --git a/dpppt-backend-sdk/dpppt-backend-sdk-data/src/main/java/org/dpppt/backend/sdk/data/gaen/JDBCGAENDataServiceImpl.java b/dpppt-backend-sdk/dpppt-backend-sdk-data/src/main/java/org/dpppt/backend/sdk/data/gaen/JDBCGAENDataServiceImpl.java index 84223bbe..e8e55603 100644 --- a/dpppt-backend-sdk/dpppt-backend-sdk-data/src/main/java/org/dpppt/backend/sdk/data/gaen/JDBCGAENDataServiceImpl.java +++ b/dpppt-backend-sdk/dpppt-backend-sdk-data/src/main/java/org/dpppt/backend/sdk/data/gaen/JDBCGAENDataServiceImpl.java @@ -46,12 +46,6 @@ public JDBCGAENDataServiceImpl(String dbType, DataSource dataSource, Duration re @Override @Transactional(readOnly = false) public void upsertExposees(List gaenKeys) { - upsertExposeesDelayed(gaenKeys, null); - } - - @Override - public void upsertExposeesDelayed(List gaenKeys, OffsetDateTime delayedReceivedAt) { - String sql = null; if (dbType.equals(PGSQL)) { sql = "insert into t_gaen_exposed (key, rolling_start_number, rolling_period, transmission_risk_level, received_at) values (:key, :rolling_start_number, :rolling_period, :transmission_risk_level, :received_at)" @@ -63,8 +57,7 @@ public void upsertExposeesDelayed(List gaenKeys, OffsetDateTime delayed } var parameterList = new ArrayList(); var nowMillis = System.currentTimeMillis(); - //if delayedReceivedAt is supplied use it - var receivedAt = delayedReceivedAt == null? (nowMillis/releaseBucketDuration.toMillis() + 1) * releaseBucketDuration.toMillis() - 1 : delayedReceivedAt.toInstant().toEpochMilli(); + var receivedAt = (nowMillis/releaseBucketDuration.toMillis() + 1) * releaseBucketDuration.toMillis() - 1; for (var gaenKey : gaenKeys) { MapSqlParameterSource params = new MapSqlParameterSource(); params.addValue("key", gaenKey.getKeyData()); @@ -78,7 +71,6 @@ public void upsertExposeesDelayed(List gaenKeys, OffsetDateTime delayed jt.batchUpdate(sql, parameterList.toArray(new MapSqlParameterSource[0])); } - @Override @Transactional(readOnly = true) public int getMaxExposedIdForKeyDate(Long keyDate, Long publishedAfter, Long publishedUntil) { @@ -143,4 +135,5 @@ public void cleanDB(Duration retentionPeriod) { String sqlExposed = "delete from t_gaen_exposed where received_at < :retention_time"; jt.update(sqlExposed, params); } + } diff --git a/dpppt-backend-sdk/dpppt-backend-sdk-ws/src/main/java/org/dpppt/backend/sdk/ws/config/WSBaseConfig.java b/dpppt-backend-sdk/dpppt-backend-sdk-ws/src/main/java/org/dpppt/backend/sdk/ws/config/WSBaseConfig.java index 406af148..f823dc66 100644 --- a/dpppt-backend-sdk/dpppt-backend-sdk-ws/src/main/java/org/dpppt/backend/sdk/ws/config/WSBaseConfig.java +++ b/dpppt-backend-sdk/dpppt-backend-sdk-ws/src/main/java/org/dpppt/backend/sdk/ws/config/WSBaseConfig.java @@ -128,8 +128,6 @@ public abstract class WSBaseConfig implements SchedulingConfigurer, WebMvcConfig String keyIdentifier; @Value("${ws.app.gaen.algorithm:1.2.840.10045.4.3.2}") String gaenAlgorithm; - @Value("${ws.app.gaen.delayTodaysKeys: false}") - boolean delayTodaysKeys; @Autowired(required = false) ValidateRequest requestValidator; @@ -213,7 +211,7 @@ public GaenController gaenController() { } return new GaenController(gaenDataService(), fakeKeyService(), theValidator, gaenSigner(), gaenValidationUtils(), Duration.ofMillis(releaseBucketDuration), Duration.ofMillis(requestTime), - Duration.ofMillis(exposedListCacheControl), keyVault.get("nextDayJWT").getPrivate(), delayTodaysKeys); + Duration.ofMillis(exposedListCacheControl), keyVault.get("nextDayJWT").getPrivate()); } @Bean diff --git a/dpppt-backend-sdk/dpppt-backend-sdk-ws/src/main/java/org/dpppt/backend/sdk/ws/controller/GaenController.java b/dpppt-backend-sdk/dpppt-backend-sdk-ws/src/main/java/org/dpppt/backend/sdk/ws/controller/GaenController.java index 5b1749b8..d3f6e5af 100644 --- a/dpppt-backend-sdk/dpppt-backend-sdk-ws/src/main/java/org/dpppt/backend/sdk/ws/controller/GaenController.java +++ b/dpppt-backend-sdk/dpppt-backend-sdk-ws/src/main/java/org/dpppt/backend/sdk/ws/controller/GaenController.java @@ -88,11 +88,9 @@ public class GaenController { private final PrivateKey secondDayKey; private final ProtoSignature gaenSigner; - private final boolean delayTodaysKeys; - public GaenController(GAENDataService dataService, FakeKeyService fakeKeyService, ValidateRequest validateRequest, ProtoSignature gaenSigner, ValidationUtils validationUtils, Duration releaseBucketDuration, Duration requestTime, - Duration exposedListCacheControl, PrivateKey secondDayKey, boolean delayTodaysKeys) { + Duration exposedListCacheControl, PrivateKey secondDayKey) { this.dataService = dataService; this.fakeKeyService = fakeKeyService; this.releaseBucketDuration = releaseBucketDuration; @@ -102,7 +100,6 @@ public GaenController(GAENDataService dataService, FakeKeyService fakeKeyService this.exposedListCacheControl = exposedListCacheControl; this.secondDayKey = secondDayKey; this.gaenSigner = gaenSigner; - this.delayTodaysKeys = delayTodaysKeys; } @PostMapping(value = "/exposed") @@ -132,7 +129,6 @@ public GaenController(GAENDataService dataService, FakeKeyService fakeKeyService } List nonFakeKeys = new ArrayList<>(); - List nonFakeKeysDelayed = new ArrayList<>(); for (var key : gaenRequest.getGaenKeys()) { if (!validationUtils.isValidBase64Key(key.getKeyData())) { return () -> new ResponseEntity<>("No valid base64 key", HttpStatus.BAD_REQUEST); @@ -144,27 +140,15 @@ public GaenController(GAENDataService dataService, FakeKeyService fakeKeyService } if (key.getRollingPeriod().equals(0)) { - logger.error("RollingPeriod should NOT be 0, fixing it and using 144"); - key.setRollingPeriod(GaenKey.GaenKeyDefaultRollingPeriod); - } - - if(delayTodaysKeys) { - // Additionally to delaying keys this feature also make sure rolling period is always set to 144 - // to make sure iOS 13.5.x does not ignore the TEK. + //currently only android seems to send 0 which can never be valid, since a non used key should not be submitted + //default value according to EN is 144, so just set it to that. If we ever get 0 from iOS we should log it, since + //this should not happen key.setRollingPeriod(GaenKey.GaenKeyDefaultRollingPeriod); - - var rollingStartNumberDuration = Duration.of(key.getRollingStartNumber(), GaenUnit.TenMinutes).toMillis(); - var rollingStartNumberInstant = Instant.ofEpochMilli(rollingStartNumberDuration); - var rollingStartDate = LocalDate.ofInstant(rollingStartNumberInstant, ZoneOffset.UTC); - // If this is a same day TEK we are delaying its release - if(LocalDate.now(ZoneOffset.UTC).isEqual(rollingStartDate)) { - nonFakeKeysDelayed.add(key); - } else { - nonFakeKeys.add(key); + if (userAgent.toLowerCase().contains("ios")) { + logger.error("Received a rolling period of 0 for an iOS User-Agent"); } - } else { - nonFakeKeys.add(key); } + nonFakeKeys.add(key); } if (principal instanceof Jwt && ((Jwt) principal).containsClaim("fake") @@ -174,15 +158,6 @@ public GaenController(GAENDataService dataService, FakeKeyService fakeKeyService if (!nonFakeKeys.isEmpty()) { dataService.upsertExposees(nonFakeKeys); } - if (!nonFakeKeysDelayed.isEmpty()) { - // Hold back same day TEKs until 02:00 UTC of the next day (as RPIs are accepted by EN up to 2h after rolling period) - var tomorrowAt2AM = LocalDate.now(ZoneOffset.UTC) - .plusDays(1) - .atStartOfDay(ZoneOffset.UTC) - .plusHours(2) - .toOffsetDateTime(); - dataService.upsertExposeesDelayed(nonFakeKeysDelayed, tomorrowAt2AM); - } var delayedKeyDateDuration = Duration.of(gaenRequest.getDelayedKeyDate(), GaenUnit.TenMinutes); var delayedKeyDate = LocalDate.ofInstant(Instant.ofEpochMilli(delayedKeyDateDuration.toMillis()), diff --git a/dpppt-backend-sdk/dpppt-backend-sdk-ws/src/test/java/org/dpppt/backend/sdk/ws/controller/GaenControllerTest.java b/dpppt-backend-sdk/dpppt-backend-sdk-ws/src/test/java/org/dpppt/backend/sdk/ws/controller/GaenControllerTest.java index 374897fd..a2c20de0 100644 --- a/dpppt-backend-sdk/dpppt-backend-sdk-ws/src/test/java/org/dpppt/backend/sdk/ws/controller/GaenControllerTest.java +++ b/dpppt-backend-sdk/dpppt-backend-sdk-ws/src/test/java/org/dpppt/backend/sdk/ws/controller/GaenControllerTest.java @@ -49,6 +49,8 @@ import org.dpppt.backend.sdk.model.gaen.GaenSecondDay; import org.dpppt.backend.sdk.model.gaen.GaenUnit; import org.dpppt.backend.sdk.model.gaen.proto.TemporaryExposureKeyFormat; +import org.dpppt.backend.sdk.model.gaen.proto.TemporaryExposureKeyFormat.TEKSignatureList; +import org.dpppt.backend.sdk.model.gaen.proto.TemporaryExposureKeyFormat.TemporaryExposureKeyExport; import org.dpppt.backend.sdk.ws.security.KeyVault; import org.dpppt.backend.sdk.ws.security.signature.ProtoSignature; import org.junit.Test; @@ -68,7 +70,6 @@ @ActiveProfiles({"actuator-security"}) @SpringBootTest(properties = { "ws.app.jwt.publickey=classpath://generated_pub.pem", "logging.level.org.springframework.security=DEBUG", "ws.exposedlist.releaseBucketDuration=7200000", "ws.gaen.randomkeysenabled=true", - "ws.app.gaen.delayTodaysKeys=true", "ws.monitor.prometheus.user=prometheus", "ws.monitor.prometheus.password=prometheus", "management.endpoints.enabled-by-default=true", @@ -125,19 +126,10 @@ private void testNKeys(int n, boolean shouldSucceed) throws Exception{ gaenKey2.setRollingPeriod(0); gaenKey2.setFake(0); gaenKey2.setTransmissionRiskLevel(0); - //third key should be delayed - var gaenKey3 = new GaenKey(); - gaenKey3.setRollingStartNumber((int) Duration.ofMillis(LocalDate.now(ZoneOffset.UTC).atStartOfDay(ZoneOffset.UTC).toInstant().toEpochMilli()) - .dividedBy(Duration.ofMinutes(10))); - gaenKey3.setKeyData(Base64.getEncoder().encodeToString("testKey32Bytes03".getBytes("UTF-8"))); - gaenKey3.setRollingPeriod(120); - gaenKey3.setFake(0); - gaenKey3.setTransmissionRiskLevel(0); List exposedKeys = new ArrayList<>(); exposedKeys.add(gaenKey1); exposedKeys.add(gaenKey2); - exposedKeys.add(gaenKey3); - for (int i = 0; i < n-3; i++) { + for (int i = 0; i < n-2; i++) { var tmpKey = new GaenKey(); tmpKey.setRollingStartNumber( (int) Duration.ofMillis(now).dividedBy(Duration.ofMinutes(10))); @@ -182,11 +174,6 @@ private void testNKeys(int n, boolean shouldSucceed) throws Exception{ result = gaenDataService.getSortedExposedForKeyDate(LocalDate.now(ZoneOffset.UTC).minusDays(1).atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli(),null, (now / releaseBucketDuration)*releaseBucketDuration); assertEquals(0, result.size()); - - //third key should be released tomorrow - var tomorrow2AM = LocalDate.now(ZoneOffset.UTC).plusDays(1).atStartOfDay().plusHours(2).plusSeconds(1); - result = gaenDataService.getSortedExposedForKeyDate(LocalDate.now(ZoneOffset.UTC).atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli(),null, tomorrow2AM.toInstant(ZoneOffset.UTC).toEpochMilli()); - assertEquals(1, result.size()); } @Test @@ -958,7 +945,7 @@ public void zipContainsFiles() throws Exception { Long publishedUntil = Long.parseLong(response.getHeader("X-PUBLISHED-UNTIL")); assertTrue(publishedUntil < System.currentTimeMillis(), "Published until must be in the past"); - verifyZipResponse(response, 20); + verifyZipResponse(response, 20, 144); // request again the keys with date date 1 day ago. with publish until, so that // we only get the second batch. @@ -971,31 +958,28 @@ public void zipContainsFiles() throws Exception { .andExpect(status().is2xxSuccessful()).andReturn().getResponse(); //we always have 10 - verifyZipResponse(responseWithPublishedAfter, 15); + verifyZipResponse(responseWithPublishedAfter, 15, 144); } @Test @Transactional(transactionManager = "testTransactionManager") - public void testNonEmptyResponseAnd304() throws Exception { + public void testNonEmptyResponse() throws Exception { MockHttpServletResponse response = mockMvc .perform(get("/v1/gaen/exposed/" + LocalDate.now(ZoneOffset.UTC).minusDays(8).atStartOfDay(ZoneOffset.UTC).toInstant().toEpochMilli()) .header("User-Agent", "MockMVC")) .andExpect(status().isOk()).andReturn().getResponse(); - verifyZipInZipResponse(response, 10); - // var etag = response.getHeader("ETag"); - // var firstPublishUntil = response.getHeader("X-PUBLISHED-UNTIL"); - // var signature = response.getHeader("Signature"); - // assertNotNull(signature); - - // response = mockMvc - // .perform(get("/v1/gaen/exposed/" - // + LocalDate.now(ZoneOffset.UTC).minusDays(8).atStartOfDay(ZoneOffset.UTC).toInstant().toEpochMilli()) - // .header("User-Agent", "MockMVC") - // .header("If-None-Match", etag)) - // .andExpect(status().is(304)).andReturn().getResponse(); - // signature = response.getHeader("Signature"); - // assertNull(signature); + verifyZipResponse(response, 10, 144); + } + + @Test + @Transactional(transactionManager = "testTransactionManager") + public void testTodayWeDontHaveKeys() throws Exception { + MockHttpServletResponse response = mockMvc + .perform(get("/v1/gaen/exposed/" + + LocalDate.now(ZoneOffset.UTC).atStartOfDay(ZoneOffset.UTC).toInstant().toEpochMilli()) + .header("User-Agent", "MockMVC")) + .andExpect(status().is(204)).andReturn().getResponse(); } // @Test @@ -1053,7 +1037,7 @@ private void verifyZipInZipResponse(MockHttpServletResponse response, int expect } } - private void verifyZipResponse(MockHttpServletResponse response, int expectKeyCount) + private void verifyZipResponse(MockHttpServletResponse response, int expectKeyCount, int expectedRollingPeriod) throws IOException, NoSuchAlgorithmException, InvalidKeyException, SignatureException { ByteArrayInputStream baisOuter = new ByteArrayInputStream(response.getContentAsByteArray()); ZipInputStream zipOuter = new ZipInputStream(baisOuter); @@ -1082,10 +1066,10 @@ private void verifyZipResponse(MockHttpServletResponse response, int expectKeyCo assertTrue(foundData, "export.bin not found in zip"); assertTrue(foundSignature, "export.sig not found in zip"); - var list = TemporaryExposureKeyFormat.TEKSignatureList.parseFrom(signatureProto); - var export = TemporaryExposureKeyFormat.TemporaryExposureKeyExport.parseFrom(keyProto); + TEKSignatureList list = TemporaryExposureKeyFormat.TEKSignatureList.parseFrom(signatureProto); + TemporaryExposureKeyExport export = TemporaryExposureKeyFormat.TemporaryExposureKeyExport.parseFrom(keyProto); for(var key : export.getKeysList()) { - assertNotEquals(0, key.getRollingPeriod()); + assertEquals(expectedRollingPeriod, key.getRollingPeriod()); } var sig = list.getSignatures(0); java.security.Signature signatureVerifier = java.security.Signature