diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/config/SubmissionServiceConfig.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/config/SubmissionServiceConfig.java index 3efa794d08..324e72515e 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/config/SubmissionServiceConfig.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/config/SubmissionServiceConfig.java @@ -21,11 +21,15 @@ package app.coronawarn.server.services.submission.config; import java.io.File; +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; +import org.springframework.validation.annotation.Validated; @Component @ConfigurationProperties(prefix = "services.submission") +@Validated public class SubmissionServiceConfig { // Exponential moving average of the last N real request durations (in ms), where @@ -33,6 +37,9 @@ public class SubmissionServiceConfig { private Long initialFakeDelayMilliseconds; private Long fakeDelayMovingAverageSamples; private Integer retentionDays; + @Min(1) + @Max(100) + private Integer randomKeyPaddingMultiplier; private Integer connectionPoolSize; private Payload payload; private Verification verification; @@ -63,6 +70,14 @@ public void setRetentionDays(Integer retentionDays) { this.retentionDays = retentionDays; } + public Integer getRandomKeyPaddingMultiplier() { + return randomKeyPaddingMultiplier; + } + + public void setRandomKeyPaddingMultiplier(Integer randomKeyPaddingMultiplier) { + this.randomKeyPaddingMultiplier = randomKeyPaddingMultiplier; + } + public Integer getConnectionPoolSize() { return connectionPoolSize; } diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java index d035a3d743..ef67ffaa36 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java @@ -29,8 +29,10 @@ import app.coronawarn.server.services.submission.validation.ValidSubmissionPayload; import app.coronawarn.server.services.submission.verification.TanVerifier; import io.micrometer.core.annotation.Timed; +import java.security.SecureRandom; import java.util.ArrayList; import java.util.List; +import java.util.stream.IntStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; @@ -59,6 +61,7 @@ public class SubmissionController { private final DiagnosisKeyService diagnosisKeyService; private final TanVerifier tanVerifier; private final Integer retentionDays; + private final Integer randomKeyPaddingMultiplier; private final FakeDelayManager fakeDelayManager; SubmissionController( @@ -69,6 +72,7 @@ public class SubmissionController { this.submissionMonitor = submissionMonitor; this.fakeDelayManager = fakeDelayManager; retentionDays = submissionServiceConfig.getRetentionDays(); + randomKeyPaddingMultiplier = submissionServiceConfig.getRandomKeyPaddingMultiplier(); } /** @@ -130,6 +134,26 @@ public void persistDiagnosisKeysPayload(SubmissionPayload protoBufDiagnosisKeys) } } - diagnosisKeyService.saveDiagnosisKeys(diagnosisKeys); + diagnosisKeyService.saveDiagnosisKeys(padDiagnosisKeys(diagnosisKeys)); + } + + private List padDiagnosisKeys(List diagnosisKeys) { + List paddedDiagnosisKeys = new ArrayList<>(); + diagnosisKeys.forEach(diagnosisKey -> { + paddedDiagnosisKeys.add(diagnosisKey); + IntStream.range(1, randomKeyPaddingMultiplier) + .mapToObj(index -> { + byte[] randomKeyData = new byte[16]; + new SecureRandom().nextBytes(randomKeyData); + return DiagnosisKey.builder() + .withKeyData(randomKeyData) + .withRollingStartIntervalNumber(diagnosisKey.getRollingStartIntervalNumber()) + .withTransmissionRiskLevel(diagnosisKey.getTransmissionRiskLevel()) + .withRollingPeriod(diagnosisKey.getRollingPeriod()) + .build(); + }) + .forEach(paddedDiagnosisKeys::add); + }); + return paddedDiagnosisKeys; } } diff --git a/services/submission/src/main/resources/application.yaml b/services/submission/src/main/resources/application.yaml index 43074e079e..8d908f30f4 100644 --- a/services/submission/src/main/resources/application.yaml +++ b/services/submission/src/main/resources/application.yaml @@ -15,6 +15,12 @@ services: fake-delay-moving-average-samples: 10 # The retention threshold for acceptable diagnosis keys during submission. retention-days: 14 + # The number of keys to save to the DB for every real submitted key. + # Example: If the 'random-key-padding-multiplier' is set to 10, and 5 keys are being submitted, + # then the 5 real submitted keys will be saved to the DB, plus an additional 45 keys with + # random 'key_data'. All properties, besides the 'key_data', of the additional keys will be + # identical to the real key. + random-key-padding-multiplier: ${RANDOM_KEY_PADDING_MULTIPLIER:1} # The ApacheHttpClient's connection pool size. connection-pool-size: 200 payload: diff --git a/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/SubmissionControllerTest.java b/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/SubmissionControllerTest.java index 1be6c17bfc..dffb71ca8b 100644 --- a/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/SubmissionControllerTest.java +++ b/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/SubmissionControllerTest.java @@ -127,28 +127,28 @@ void singleKeyWithOutdatedRollingStartIntervalNumberDoesNotGetSaved() { @Test void keysWithOutdatedRollingStartIntervalNumberDoNotGetSaved() { - Collection keys = buildPayloadWithMultipleKeys(); + Collection submittedKeys = buildPayloadWithMultipleKeys(); TemporaryExposureKey outdatedKey = createOutdatedKey(); - keys.add(outdatedKey); + submittedKeys.add(outdatedKey); ArgumentCaptor> argument = ArgumentCaptor.forClass(Collection.class); - executor.executePost(keys, buildOkHeaders()); + executor.executePost(submittedKeys, buildOkHeaders()); verify(diagnosisKeyService, atLeastOnce()).saveDiagnosisKeys(argument.capture()); - keys.remove(outdatedKey); - assertElementsCorrespondToEachOther(keys, argument.getValue()); + submittedKeys.remove(outdatedKey); + assertElementsCorrespondToEachOther(submittedKeys, argument.getValue()); } @Test void checkSaveOperationCallAndFakeDelayUpdateForValidParameters() { - Collection keys = buildPayloadWithMultipleKeys(); + Collection submittedKeys = buildPayloadWithMultipleKeys(); ArgumentCaptor> argument = ArgumentCaptor.forClass(Collection.class); - executor.executePost(keys, buildOkHeaders()); + executor.executePost(submittedKeys, buildOkHeaders()); verify(diagnosisKeyService, atLeastOnce()).saveDiagnosisKeys(argument.capture()); verify(fakeDelayManager, times(1)).updateFakeRequestDelay(anyLong()); - assertElementsCorrespondToEachOther(keys, argument.getValue()); + assertElementsCorrespondToEachOther(submittedKeys, argument.getValue()); } @ParameterizedTest @@ -186,7 +186,7 @@ private static Stream createDeniedHttpMethods() { return Arrays.stream(HttpMethod.values()) .filter(method -> method != HttpMethod.POST) .filter(method -> method != HttpMethod.PATCH) /* not supported by Rest Template */ - .map(elem -> Arguments.of(elem)); + .map(Arguments::of); } @Test @@ -252,18 +252,36 @@ private static Collection buildPayloadWithInvalidKey() { .collect(Collectors.toCollection(ArrayList::new)); } - private void assertElementsCorrespondToEachOther - (Collection submittedKeys, Collection keyEntities) { - Set expKeys = submittedKeys.stream() - .map(aSubmittedKey -> DiagnosisKey.builder().fromProtoBuf(aSubmittedKey).build()) + private void assertElementsCorrespondToEachOther(Collection submittedTemporaryExposureKeys, + Collection savedDiagnosisKeys) { + + Set submittedDiagnosisKeys = submittedTemporaryExposureKeys.stream() + .map(submittedDiagnosisKey -> DiagnosisKey.builder().fromProtoBuf(submittedDiagnosisKey).build()) .collect(Collectors.toSet()); - assertThat(keyEntities) - .withFailMessage("Number of submitted keys and generated key entities don't match.") - .hasSameSizeAs(expKeys); - keyEntities.forEach(anActKey -> assertThat(expKeys) - .withFailMessage("Key entity does not correspond to a submitted key.") - .contains(anActKey) - ); + assertThat(savedDiagnosisKeys).hasSize(submittedDiagnosisKeys.size() * 10); + assertThat(savedDiagnosisKeys).containsAll(submittedDiagnosisKeys); + + submittedDiagnosisKeys.forEach(submittedDiagnosisKey -> { + List savedKeysForSingleSubmittedKey = savedDiagnosisKeys.stream() + .filter(savedDiagnosisKey -> savedDiagnosisKey.getRollingPeriod() == + submittedDiagnosisKey.getRollingPeriod()) + .filter(savedDiagnosisKey -> savedDiagnosisKey.getTransmissionRiskLevel() == + submittedDiagnosisKey.getTransmissionRiskLevel()) + .filter(savedDiagnosisKey -> savedDiagnosisKey.getRollingStartIntervalNumber() == + submittedDiagnosisKey.getRollingStartIntervalNumber()) + .collect(Collectors.toList()); + + assertThat(savedKeysForSingleSubmittedKey).hasSize(10); + assertThat(savedKeysForSingleSubmittedKey.stream().filter(savedKey -> + Arrays.equals(savedKey.getKeyData(), submittedDiagnosisKey.getKeyData()))).hasSize(1); + assertThat(savedKeysForSingleSubmittedKey).allMatch( + savedKey -> savedKey.getRollingPeriod() == submittedDiagnosisKey.getRollingPeriod()); + assertThat(savedKeysForSingleSubmittedKey).allMatch( + savedKey -> savedKey.getRollingStartIntervalNumber() == submittedDiagnosisKey + .getRollingStartIntervalNumber()); + assertThat(savedKeysForSingleSubmittedKey).allMatch( + savedKey -> savedKey.getTransmissionRiskLevel() == submittedDiagnosisKey.getTransmissionRiskLevel()); + }); } } diff --git a/services/submission/src/test/resources/application.yaml b/services/submission/src/test/resources/application.yaml index 0f7d87385a..c4b8409067 100644 --- a/services/submission/src/test/resources/application.yaml +++ b/services/submission/src/test/resources/application.yaml @@ -25,6 +25,7 @@ services: initial-fake-delay-milliseconds: 1 fake-delay-moving-average-samples: 1 retention-days: 14 + random-key-padding-multiplier: 10 connection-pool-size: 200 payload: max-number-of-keys: 14