Commit 20109f41 authored by Deniro StopCovid's avatar Deniro StopCovid Committed by Redford StopCovid

feat: Use the property var robert.server.request-time-delta-tolerance mapped...

feat: Use the property var robert.server.request-time-delta-tolerance mapped on the env var ROBERT_SERVER_REQUEST_TIME_DELTA_TOLERANCE
parent 4a62ce28
......@@ -19,7 +19,7 @@
<groupId>fr.gouv.stopc</groupId>
<artifactId>robert-server</artifactId>
<version>1.0.1-SNAPSHOT</version>
<version>1.1.1-SNAPSHOT</version>
<name>robert-server</name>
<packaging>pom</packaging>
<description>Projet principal</description>
......
......@@ -13,7 +13,7 @@
<parent>
<groupId>fr.gouv.stopc</groupId>
<artifactId>robert-server</artifactId>
<version>1.0.1-SNAPSHOT</version>
<version>1.1.1-SNAPSHOT</version>
</parent>
<artifactId>robert-crypto-grpc-server-messaging</artifactId>
......
......@@ -13,7 +13,7 @@
<parent>
<groupId>fr.gouv.stopc</groupId>
<artifactId>robert-server</artifactId>
<version>1.0.1-SNAPSHOT</version>
<version>1.1.1-SNAPSHOT</version>
</parent>
......
......@@ -13,7 +13,7 @@
<parent>
<groupId>fr.gouv.stopc</groupId>
<artifactId>robert-server</artifactId>
<version>1.0.1-SNAPSHOT</version>
<version>1.1.1-SNAPSHOT</version>
</parent>
<artifactId>robert-crypto-grpc-server</artifactId>
......
......@@ -2,6 +2,7 @@ package fr.gouv.stopc.robert.crypto.grpc.server.service;
public interface ICryptoServerConfigurationService {
/**
* TpStart in NTP seconds
*
......
......@@ -4,11 +4,7 @@ import java.security.Key;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Objects;
import java.util.Optional;
import java.util.*;
import java.util.stream.Collectors;
import javax.inject.Inject;
......@@ -378,6 +374,7 @@ public class CryptoGrpcServiceBaseImpl extends CryptoGrpcServiceImplImplBase {
* Decrypt the provided ebid and check the authRequestEpoch it contains the provided one or the next/previous
* @param ebid
* @param authRequestEpoch
* @param enableEpochOverlapping authorize the epoch overlapping (ie too close epochs => (Math.abs(epoch1 - epoch2) == 1))
* @param adjacentEpochMatchEnum
* @return
* @throws RobertServerCryptoException
......@@ -386,6 +383,7 @@ public class CryptoGrpcServiceBaseImpl extends CryptoGrpcServiceImplImplBase {
int authRequestEpoch,
boolean mustCheckWithPreviousDayKey,
boolean ksAdjustment,
boolean enableEpochOverlapping,
AdjacentEpochMatchEnum adjacentEpochMatchEnum)
throws RobertServerCryptoException {
......@@ -406,13 +404,17 @@ public class CryptoGrpcServiceBaseImpl extends CryptoGrpcServiceImplImplBase {
if (authRequestEpoch != ebidEpochId) {
log.warn("Epoch from EBID and accompanying authRequestEpoch do not match: ebid epoch = {} vs auth request epoch = {}", ebidEpochId, authRequestEpoch);
if (ksAdjustment && !mustCheckWithPreviousDayKey) {
if(enableEpochOverlapping && (Math.abs(authRequestEpoch - ebidEpochId) == 1)) {
return EbidContent.builder().epochId(ebidEpochId).idA(idA).build();
} else if (ksAdjustment && !mustCheckWithPreviousDayKey) {
return decryptEBIDAndCheckEpoch(
ebid,
authRequestEpoch,
true,
false,
adjacentEpochMatchEnum);
enableEpochOverlapping, adjacentEpochMatchEnum);
} else {
return manageEBIDDecryptRetry(ebid,
authRequestEpoch,
......@@ -442,7 +444,7 @@ public class CryptoGrpcServiceBaseImpl extends CryptoGrpcServiceImplImplBase {
epoch,
false,
isEBIDWithinRange(epoch),
AdjacentEpochMatchEnum.NONE);
false, AdjacentEpochMatchEnum.NONE);
}
private EbidContent manageEBIDDecryptRetry(byte[] ebid, int authRequestEpoch, AdjacentEpochMatchEnum adjacentEpochMatchEnum)
......@@ -450,10 +452,11 @@ public class CryptoGrpcServiceBaseImpl extends CryptoGrpcServiceImplImplBase {
switch (adjacentEpochMatchEnum) {
case PREVIOUS:
log.warn("Retrying ebid decrypt with previous epoch");
return decryptEBIDAndCheckEpoch(ebid, authRequestEpoch - 1, false, false, AdjacentEpochMatchEnum.NONE);
return decryptEBIDAndCheckEpoch(ebid, authRequestEpoch - 1, false, false, false, AdjacentEpochMatchEnum.NONE);
case NEXT:
log.warn("Retrying ebid decrypt with next epoch");
return decryptEBIDAndCheckEpoch(ebid, authRequestEpoch + 1, false, false, AdjacentEpochMatchEnum.NONE);
return decryptEBIDAndCheckEpoch(ebid, authRequestEpoch + 1, false, false, false, AdjacentEpochMatchEnum.NONE);
case NONE:
default:
return null;
......@@ -471,7 +474,7 @@ public class CryptoGrpcServiceBaseImpl extends CryptoGrpcServiceImplImplBase {
epoch,
false,
isEBIDWithinRange(epoch),
atStartOrEndOfDay(timeReceived));
true, atStartOrEndOfDay(timeReceived));
// AdjacentEpochMatchEnum adjacentEpochMatch = AdjacentEpochMatchEnum.NONE;
// // TODO: replace local EPOCH_DURATION with common epoch duration constant
......@@ -487,6 +490,7 @@ public class CryptoGrpcServiceBaseImpl extends CryptoGrpcServiceImplImplBase {
ZonedDateTime zonedDateTime = Instant
.ofEpochMilli(TimeUtils.convertNTPSecondsToUnixMillis(timeReceived))
.atZone(ZoneOffset.UTC);
int tolerance = this.propertyLoader.getHelloMessageTimeStampTolerance();
if (zonedDateTime.getHour() == 0
......
......@@ -19,6 +19,7 @@ import fr.gouv.stopc.robert.server.common.utils.TimeUtils;
@Service
public class CryptoServerConfigurationServiceImpl implements ICryptoServerConfigurationService {
private long timeStartNtp;
private final PropertyLoader propertyLoader;
......
package test.fr.gouv.stopc.robert.crypto.grpc.server;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.Mockito.doReturn;
......@@ -36,7 +36,6 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import com.fasterxml.jackson.core.type.TypeReference;
......@@ -44,9 +43,19 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.protobuf.ByteString;
import fr.gouv.stopc.robert.crypto.grpc.server.CryptoServiceGrpcServer;
import fr.gouv.stopc.robert.crypto.grpc.server.messaging.*;
import fr.gouv.stopc.robert.crypto.grpc.server.messaging.CreateRegistrationRequest;
import fr.gouv.stopc.robert.crypto.grpc.server.messaging.CreateRegistrationResponse;
import fr.gouv.stopc.robert.crypto.grpc.server.messaging.CryptoGrpcServiceImplGrpc;
import fr.gouv.stopc.robert.crypto.grpc.server.messaging.CryptoGrpcServiceImplGrpc.CryptoGrpcServiceImplImplBase;
import fr.gouv.stopc.robert.crypto.grpc.server.messaging.CryptoGrpcServiceImplGrpc.CryptoGrpcServiceImplStub;
import fr.gouv.stopc.robert.crypto.grpc.server.messaging.DeleteIdRequest;
import fr.gouv.stopc.robert.crypto.grpc.server.messaging.DeleteIdResponse;
import fr.gouv.stopc.robert.crypto.grpc.server.messaging.GetIdFromAuthRequest;
import fr.gouv.stopc.robert.crypto.grpc.server.messaging.GetIdFromAuthResponse;
import fr.gouv.stopc.robert.crypto.grpc.server.messaging.GetIdFromStatusRequest;
import fr.gouv.stopc.robert.crypto.grpc.server.messaging.GetIdFromStatusResponse;
import fr.gouv.stopc.robert.crypto.grpc.server.messaging.GetInfoFromHelloMessageRequest;
import fr.gouv.stopc.robert.crypto.grpc.server.messaging.GetInfoFromHelloMessageResponse;
import fr.gouv.stopc.robert.crypto.grpc.server.service.ICryptoServerConfigurationService;
import fr.gouv.stopc.robert.crypto.grpc.server.service.impl.CryptoGrpcServiceBaseImpl;
import fr.gouv.stopc.robert.crypto.grpc.server.service.impl.ECDHKeyServiceImpl;
......@@ -410,11 +419,10 @@ class CryptoServiceGrpcServerTest {
}
private AuthRequestBundle generateAuthRequestBundleWithTimeDeltaAndOtherKS(byte[] id,
byte[] keyForMac,
DigestSaltEnum digestSalt,
long timeDelta,
OtherKSEnum otherKs) {
byte[] keyForMac,
DigestSaltEnum digestSalt,
long timeDelta,
OtherKSEnum otherKs) {
long time = getCurrentTimeNTPSeconds();
int epochId = TimeUtils.getNumberOfEpochsBetween(
this.serverConfigurationService.getServiceTimeStart(),
......@@ -423,7 +431,6 @@ class CryptoServiceGrpcServerTest {
// Mock K_S
byte[] ks = new byte[24];
new SecureRandom().nextBytes(ks);
byte[] ksPrevious = new byte[24];
new SecureRandom().nextBytes(ksPrevious);
byte[] ksNext = new byte[24];
......
......@@ -13,7 +13,7 @@
<parent>
<groupId>fr.gouv.stopc</groupId>
<artifactId>robert-server</artifactId>
<version>1.0.1-SNAPSHOT</version>
<version>1.1.1-SNAPSHOT</version>
</parent>
<artifactId>robert-server-batch</artifactId>
......@@ -80,6 +80,13 @@
<artifactId>javax.inject</artifactId>
</dependency>
<dependency>
<groupId>com.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>5.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
......@@ -98,6 +105,11 @@
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
......@@ -107,30 +119,11 @@
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- Disabled because fails when not executed from each module's directory -->
<!--
<plugin>
<groupId>org.complykit</groupId>
<artifactId>license-check-maven-plugin</artifactId>
<version>0.5.3</version>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>os-check</goal>
</goals>
</execution>
</executions>
<configuration>
<blacklist>
<param>agpl-3.0</param>
<param>gpl-2.0</param>
<param>gpl-3.0</param>
</blacklist>
<excludes>
</excludes>
</configuration>
</plugin>
-->
<!-- <plugin> <groupId>org.complykit</groupId> <artifactId>license-check-maven-plugin</artifactId>
<version>0.5.3</version> <executions> <execution> <phase>verify</phase> <goals>
<goal>os-check</goal> </goals> </execution> </executions> <configuration>
<blacklist> <param>agpl-3.0</param> <param>gpl-2.0</param> <param>gpl-3.0</param>
</blacklist> <excludes> </excludes> </configuration> </plugin> -->
</plugins>
</build>
......
package fr.gouv.stopc.robert.server.batch.configuration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import lombok.Getter;
import lombok.Setter;
/**
* Configuration class of the scoring algorithm
*
*
*/
@Getter
@Setter
@Component
@ConfigurationProperties(prefix = "robert.scoring")
public class ScoringAlgorithmConfiguration {
// Max Rssi cutting peak
private int rssiMax;
// Weighting vector for the # of packets received per window values
private double[] deltas;
// limit power in Db below which the collected value is assumed to be zero
private double p0;
// Constant for RSSI averaging = 10 log(10)
private double softMaxA;
// Constant for risk averaging
private double softMaxB;
}
package fr.gouv.stopc.robert.server.batch.model;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class ScoringResult {
private Integer duration;
private Integer nbContacts;
private Double rssiScore;
}
......@@ -8,6 +8,7 @@ import java.util.Optional;
import java.util.stream.Collectors;
import fr.gouv.stopc.robert.crypto.grpc.server.messaging.*;
import fr.gouv.stopc.robert.server.batch.service.impl.ScoringStrategyV2ServiceImpl;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.util.CollectionUtils;
......@@ -17,6 +18,7 @@ import fr.gouv.stopc.robert.crypto.grpc.server.client.service.ICryptoServerGrpcC
import fr.gouv.stopc.robert.server.batch.exception.RobertScoringException;
import fr.gouv.stopc.robert.server.batch.service.ScoringStrategyService;
import fr.gouv.stopc.robert.server.batch.utils.PropertyLoader;
import fr.gouv.stopc.robert.server.batch.model.ScoringResult;
import fr.gouv.stopc.robert.server.common.service.IServerConfigurationService;
import fr.gouv.stopc.robert.server.common.utils.TimeUtils;
import fr.gouv.stopc.robert.server.crypto.exception.RobertServerCryptoException;
......@@ -137,12 +139,21 @@ public class ContactProcessor implements ItemProcessor<Contact, Contact> {
.filter(ep -> ep.getEpochId() > latestRiskEpoch)
.collect(Collectors.toList());
Double totalRisk = scoresSinceLastNotif.stream()
.map(EpochExposition::getExpositionScores)
.map(item -> item.stream().mapToDouble(Double::doubleValue).sum())
.reduce(0.0, (a,b) -> a + b);
// TODO: delay to end of batch for all registrations and epochs that have been affected
// If at risk detection is delayed to end of batch, no aggregate scoring here
// If not, scoring must be done here. If at risk trigger on single exposed epoch,
// then remove loop and get epochExposition[epoch] and launch aggregate but protect setAtRisk if set to true
int numberOfAtRiskExposedEpochs = 0;
for (EpochExposition epochExposition : scoresSinceLastNotif) {
double finalRiskForEpoch = this.scoringStrategy.aggregate(epochExposition.getExpositionScores());
if (finalRiskForEpoch > this.propertyLoader.getRiskThreshold()) {
log.info("Risk detected. Scored aggregate risk for epoch {}: {}", epochExposition.getEpochId(), finalRiskForEpoch);
numberOfAtRiskExposedEpochs++;
break;
}
}
registration.setAtRisk(totalRisk > this.propertyLoader.getRiskThreshold());
registration.setAtRisk(numberOfAtRiskExposedEpochs >= this.scoringStrategy.getNbEpochsScoredAtRiskThreshold());
this.registrationService.saveRegistration(registration);
this.contactService.delete(contact);
......@@ -159,17 +170,19 @@ public class ContactProcessor implements ItemProcessor<Contact, Contact> {
final long timeFromDeviceAs16bits = castLong(helloMessageDetail.getTimeCollectedOnDevice(), 2);
final int timeDiffTolerance = this.propertyLoader.getHelloMessageTimeStampTolerance();
// TODO: fix this as overflow of 16bits may cause rejection of valid messages
if (Math.abs(timeFromHelloNTPsecAs16bits - timeFromDeviceAs16bits) > timeDiffTolerance) {
log.warn("Time tolerance was exceeded: |{} (HELLO) vs {} (receiving device)| > {}; discarding HELLO message",
timeFromHelloNTPsecAs16bits,
timeFromDeviceAs16bits,
timeDiffTolerance);
return false;
if (TimeUtils.toleranceCheckWithWrap(timeFromHelloNTPsecAs16bits, timeFromDeviceAs16bits, timeDiffTolerance)) {
return true;
}
return true;
log.warn("Time tolerance was exceeded: |{} (HELLO) vs {} (receiving device)| > {}; discarding HELLO message",
timeFromHelloNTPsecAs16bits,
timeFromDeviceAs16bits,
timeDiffTolerance);
return false;
}
/**
* Robert Spec Step #6
*/
......@@ -203,13 +216,13 @@ public class ContactProcessor implements ItemProcessor<Contact, Contact> {
.filter(item -> item.getEpochId() == epochIdFromEBID)
.findFirst();
Double scoredRisk = this.scoringStrategy.execute(contact);
ScoringResult scoredRisk = this.scoringStrategy.execute(contact);
if (epochToAddTo.isPresent()) {
List<Double> epochScores = epochToAddTo.get().getExpositionScores();
epochScores.add(scoredRisk);
epochScores.add(scoredRisk.getRssiScore());
} else {
exposedEpochs.add(EpochExposition.builder()
.expositionScores(Arrays.asList(scoredRisk))
.expositionScores(Arrays.asList(scoredRisk.getRssiScore()))
.epochId(epochIdFromEBID)
.build());
}
......
package fr.gouv.stopc.robert.server.batch.service;
import fr.gouv.stopc.robert.server.batch.exception.RobertScoringException;
import fr.gouv.stopc.robert.server.batch.model.ScoringResult;
import fr.gouv.stopc.robertserver.database.model.Contact;
import java.util.List;
public interface ScoringStrategyService {
/**
* Compute a risk score based on the nature of the contact
*
* @param contact
*/
Double execute(Contact contact) throws RobertScoringException;
ScoringResult execute(Contact contact) throws RobertScoringException;
int getScoringStrategyVersion();
double aggregate(List<Double> scores);
int getNbEpochsScoredAtRiskThreshold();
}
......@@ -4,11 +4,13 @@ import java.util.List;
import javax.inject.Inject;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import fr.gouv.stopc.robert.server.batch.exception.RobertScoringException;
import fr.gouv.stopc.robert.server.batch.service.ScoringStrategyService;
import fr.gouv.stopc.robert.server.batch.utils.PropertyLoader;
import fr.gouv.stopc.robert.server.batch.model.ScoringResult;
import fr.gouv.stopc.robert.server.common.service.IServerConfigurationService;
import fr.gouv.stopc.robertserver.database.model.Contact;
import fr.gouv.stopc.robertserver.database.model.HelloMessageDetail;
......@@ -22,6 +24,7 @@ import lombok.extern.slf4j.Slf4j;
*/
@Service
@Slf4j
@ConditionalOnProperty(name = "robert.scoring.algo-version", havingValue = "0")
public class ScoringStrategyServiceImpl implements ScoringStrategyService {
private final IServerConfigurationService serverConfigurationService;
......@@ -36,7 +39,23 @@ public class ScoringStrategyServiceImpl implements ScoringStrategyService {
}
@Override
public Double execute(Contact contact) throws RobertScoringException {
public int getScoringStrategyVersion() {
return 1;
}
@Override
public double aggregate(List<Double> scores) {
return scores.stream().mapToDouble(Double::doubleValue).sum();
}
@Override
public int getNbEpochsScoredAtRiskThreshold() {
return 1;
}
@Override
public ScoringResult execute(Contact contact) throws RobertScoringException {
List<HelloMessageDetail> messageDetails = contact.getMessageDetails();
final int alpha = initAlpha();
......@@ -66,7 +85,7 @@ public class ScoringStrategyServiceImpl implements ScoringStrategyService {
throw new RobertScoringException(errorMessage);
}
return 0 - acc;
return ScoringResult.builder().rssiScore(0 - acc).build();
}
private int initAlpha() {
......
package fr.gouv.stopc.robert.server.batch.service.impl;
import java.util.ArrayList;
import java.util.List;
import fr.gouv.stopc.robert.server.batch.utils.PropertyLoader;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import fr.gouv.stopc.robert.server.batch.configuration.ScoringAlgorithmConfiguration;
import fr.gouv.stopc.robert.server.batch.exception.RobertScoringException;