Mentions légales du service

Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • x-ctac/robert-server
  • stopcovid19/robert-server
2 results
Show changes
Commits on Source (12)
Showing
with 336 additions and 111 deletions
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**
!**/src/test/**
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
### VS Code ###
.vscode/
......@@ -138,10 +138,6 @@ public class ContactProcessor implements ItemProcessor<Contact, Contact> {
* Robert Spec Step #5: check that the delta between tA (16 bits) & timeA (32 bits) [truncated to 16bits] is below threshold.
*/
private boolean step5CheckDeltaTaAndTimeABelowThreshold(HelloMessageDetail helloMessageDetail) {
// Cast values from int that is unsigned into a signed long
final long timeFromHelloNTPsec = Integer.toUnsignedLong(helloMessageDetail.getTimeFromHelloMessage());
final long timeFromDevice = helloMessageDetail.getTimeCollectedOnDevice();
// Process 16-bit values for sanity check
final long timeFromHelloNTPsecAs16bits = castIntegerToLong(helloMessageDetail.getTimeFromHelloMessage(), 2);
final long timeFromDeviceAs16bits = castLong(helloMessageDetail.getTimeCollectedOnDevice(), 2);
......@@ -160,10 +156,10 @@ public class ContactProcessor implements ItemProcessor<Contact, Contact> {
/**
* Robert Spec Step #6
*/
private boolean step6CheckTimeACorrespondsToEpochiA(byte[] epochId, int timeFromDevice) {
private boolean step6CheckTimeACorrespondsToEpochiA(byte[] epochId, long timeFromDevice) {
final int epochIdFromEBID = ByteUtils.convertEpoch24bitsToInt(epochId);
final long tpstStartNTPsec = this.serverConfigurationService.getServiceTimeStart();
long epochIdFromMessage = TimeUtils.getNumberOfEpochsBetween(tpstStartNTPsec, Integer.toUnsignedLong(timeFromDevice));
long epochIdFromMessage = TimeUtils.getNumberOfEpochsBetween(tpstStartNTPsec, timeFromDevice);
// Check if epochs match with a limited tolerance
if (Math.abs(epochIdFromMessage - epochIdFromEBID) > 1) {
......@@ -258,7 +254,7 @@ public class ContactProcessor implements ItemProcessor<Contact, Contact> {
byte[] epochId) {
final long timeFromDevice = helloMessageDetail.getTimeCollectedOnDevice();
if (!step5CheckDeltaTaAndTimeABelowThreshold(helloMessageDetail)
|| !step6CheckTimeACorrespondsToEpochiA(epochId, (int) timeFromDevice)
|| !step6CheckTimeACorrespondsToEpochiA(epochId, timeFromDevice)
// Step #7: retrieve from IDTable, KA, the key associated with idA
|| !step8VerifyMACIsValid(ebid, encryptedCodeCountry, helloMessageDetail, registration)) {
return false;
......
......@@ -214,7 +214,7 @@ public class ContactProcessorTest {
return HelloMessageDetail.builder()
.timeFromHelloMessage(timeHello)
.timeCollectedOnDevice((long)timeReceived)
.timeCollectedOnDevice(Integer.toUnsignedLong(timeReceived))
.rssiCalibrated(rssi)
.mac(mac)
.build();
......@@ -253,7 +253,8 @@ public class ContactProcessorTest {
// Setup id with an existing score below threshold
Registration registrationWithEE = this.registration.get();
registrationWithEE.setExposedEpochs(Arrays.asList(EpochExposition.builder()
registrationWithEE.setExposedEpochs(Arrays.asList(
EpochExposition.builder()
.epochId(previousEpoch)
.expositionScores(Arrays.asList(3.0))
.build(),
......
......@@ -243,7 +243,7 @@ public class RSSICalibratedScoringStrategyTest {
.build());
messages.add(HelloMessageDetail.builder()
.timeCollectedOnDevice(this.randomReferenceEpochStartTime + (900 - 5))
.timeCollectedOnDevice(this.randomReferenceEpochStartTime + (900L - 5L))
.rssiCalibrated(-78)
.build());
......@@ -289,7 +289,7 @@ public class RSSICalibratedScoringStrategyTest {
public void testScoreRiskOneMessageLateNotAtRisk() {
List<HelloMessageDetail> messages = new ArrayList<>();
messages.add(HelloMessageDetail.builder()
.timeCollectedOnDevice(this.randomReferenceEpochStartTime + (900 - 5))
.timeCollectedOnDevice(this.randomReferenceEpochStartTime + (900L - 5L))
.rssiCalibrated(-78)
.build());
......
......@@ -40,6 +40,11 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>bson</artifactId>
</dependency>
</dependencies>
<build>
......
......@@ -13,7 +13,7 @@ public interface IServerConfigurationService {
* @return The federation key (256 bits, 32 bytes)
*/
byte[] getFederationKey();
/**
* TpStart in NTP seconds
* @return the time the ROBERT service was started (permanent, never changes, not tied to an instance)
......@@ -25,7 +25,7 @@ public interface IServerConfigurationService {
* @return
*/
byte getServerCountryCode();
/**
*
* @return the time tolerance for the validation of helloMessage timestamp
......@@ -55,26 +55,14 @@ public interface IServerConfigurationService {
int getStatusRequestMinimumEpochGap();
/**
*
* @return The secret to be sent to the captcha server along with challenge response
*/
String getCaptchaSecret();
/**
* The FQ package name of the Android app to check against the response from the captcha server
* @return
*/
String getCaptchaAppPackageName();
/**
*
* @return the accepted time delay between the solving of a captcha and the verification
*/
int getCaptchaChallengeTimestampTolerance();
*/
int getCaptchaChallengeTimestampTolerance();
/**
*
* @return the risk threshold (theta) determining whether someone is at risk
*/
double getRiskThreshold();
double getRiskThreshold();
}
......@@ -17,6 +17,7 @@ import fr.gouv.stopc.robert.server.common.utils.TimeUtils;
public class ServerConfigurationServiceImpl implements IServerConfigurationService {
private final byte[] serverKey;
private final byte[] federationKey;
/**
......@@ -60,33 +61,43 @@ public class ServerConfigurationServiceImpl implements IServerConfigurationServi
}
@Override
public byte getServerCountryCode() { return (byte) 0x33; }
@Override
public int getHelloMessageTimeStampTolerance() { return 3; }
@Override
public int getContagiousPeriod() { return 14; }
public byte getServerCountryCode() {
return (byte) 0x33;
}
@Override
public int getEpochDurationSecs() { return TimeUtils.EPOCH_DURATION_SECS; }
public int getHelloMessageTimeStampTolerance() {
return 3;
}
@Override
public int getRequestTimeDeltaTolerance() { return 60; }
public int getContagiousPeriod() {
return 14;
}
@Override
public int getStatusRequestMinimumEpochGap() { return 2; }
public int getEpochDurationSecs() {
return TimeUtils.EPOCH_DURATION_SECS;
}
@Override
public String getCaptchaSecret() { return ""; }
public int getRequestTimeDeltaTolerance() {
return 60;
}
@Override
public String getCaptchaAppPackageName() { return "fr.gouv.stopc"; }
public int getStatusRequestMinimumEpochGap() {
return 2;
}
@Override
public int getCaptchaChallengeTimestampTolerance() { return 60; }
public int getCaptchaChallengeTimestampTolerance() {
return 60;
}
// Issue #TODO: store all values of this risk threshold to track any configuration change over time
@Override
public double getRiskThreshold() { return 5.0; }
public double getRiskThreshold() {
return 5.0;
}
}
package test.fr.gouv.stopc.robert.server.common.service;
import org.junit.jupiter.api.Test;
import fr.gouv.stopc.robert.server.common.service.impl.ServerConfigurationServiceImpl;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.Base64;
import java.util.HashMap;
import static org.junit.jupiter.api.Assertions.*;
import org.bson.internal.Base64;
import org.junit.jupiter.api.Test;
import fr.gouv.stopc.robert.server.common.service.impl.ServerConfigurationServiceImpl;
class ServerConfigurationServiceImplTest {
......@@ -22,14 +22,14 @@ class ServerConfigurationServiceImplTest {
// Federation key should be 256-bits long
final byte[] fakeFederationKey = keyService.getFederationKey();
String federationKeyAsBase64 = Base64.getEncoder().encodeToString(fakeFederationKey);
String federationKeyAsBase64 = Base64.encode(fakeFederationKey);
System.out.println(federationKeyAsBase64);
federationMap.put(federationKeyAsBase64, i);
assertEquals(fakeFederationKey.length, 256/8);
// Server key should be 192-bits long
final byte[] fakeServerKey = keyService.getServerKey();
String serverKeyAsBase64 = Base64.getEncoder().encodeToString(fakeServerKey);
String serverKeyAsBase64 = Base64.encode(fakeServerKey);
System.out.println(serverKeyAsBase64);
serverMap.put(serverKeyAsBase64, i);
assertEquals(fakeServerKey.length, 192/8);
......
......@@ -8,7 +8,9 @@ import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import fr.gouv.stopc.robert.server.crypto.exception.RobertServerCryptoException;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public abstract class CryptoCipherStructureAbstract implements ICryptoStructure, ICipherStructure {
/**
......@@ -22,6 +24,7 @@ public abstract class CryptoCipherStructureAbstract implements ICryptoStructure,
return this.getCipher().doFinal(payloadToEncrypt);
} catch (InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
log.error(e.getMessage(), e);
throw new RobertServerCryptoException(e.getMessage());
}
}
......@@ -37,6 +40,7 @@ public abstract class CryptoCipherStructureAbstract implements ICryptoStructure,
return this.getCipher().doFinal(payloadToEncrypt);
} catch (InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
log.error(e.getMessage(), e);
throw new RobertServerCryptoException(e.getMessage());
}
}
......
......@@ -3,7 +3,9 @@ package fr.gouv.stopc.robert.server.crypto.structure;
import java.security.InvalidKeyException;
import fr.gouv.stopc.robert.server.crypto.exception.RobertServerCryptoException;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public abstract class CryptoMacStructureAbstract implements ICryptoStructure, IMacStructure {
/**
......@@ -18,6 +20,7 @@ public abstract class CryptoMacStructureAbstract implements ICryptoStructure, IM
return this.getMac().doFinal(payloadToEncrypt);
} catch (InvalidKeyException | IllegalStateException e) {
log.error(e.getMessage(), e);
throw new RobertServerCryptoException(e.getMessage());
}
}
......
......@@ -14,10 +14,18 @@ import lombok.ToString;
@Builder
public class HelloMessageDetail {
/**
* WARNING: Since Java does not support unsigned int, we are obligated to store the value coming from the JSON representation
* as a Long even though it should be used as an unsigned int.
*/
@NotNull
@ToString.Exclude
private Long timeCollectedOnDevice;
/**
* WARNING: Since Java does not support unsigned short, we are obligated to store the value coming from the JSON
* representation as an Integer even though it should be used as an unsigned short.
*/
@NotNull
@ToString.Exclude
private Integer timeFromHelloMessage;
......
package fr.gouv.stopc.robertserver.ws.controller;
import javax.validation.Valid;
import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import fr.gouv.stopc.robertserver.ws.dto.DeleteHistoryResponseDto;
import fr.gouv.stopc.robertserver.ws.exception.RobertServerException;
import fr.gouv.stopc.robertserver.ws.utils.UriConstants;
import fr.gouv.stopc.robertserver.ws.vo.DeleteHistoryRequestVo;
@RestController
@RequestMapping(value = "${controller.path.prefix}")
@Consumes(MediaType.APPLICATION_JSON_VALUE)
@Produces(MediaType.APPLICATION_JSON_VALUE)
public interface IDeleteHistoryController {
@PostMapping(value = UriConstants.DELETE_HISTORY)
ResponseEntity<DeleteHistoryResponseDto> deleteHistory(
@Valid @RequestBody(required = true) DeleteHistoryRequestVo deleteExposureRequestVo)
throws RobertServerException;
}
package fr.gouv.stopc.robertserver.ws.controller.impl;
import java.util.Objects;
import java.util.Optional;
import javax.inject.Inject;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import com.google.protobuf.ByteString;
import fr.gouv.stopc.robert.crypto.grpc.server.client.service.ICryptoServerGrpcClient;
import fr.gouv.stopc.robert.crypto.grpc.server.request.MacValidationForTypeRequest;
import fr.gouv.stopc.robert.server.common.DigestSaltEnum;
import fr.gouv.stopc.robertserver.database.model.Registration;
import fr.gouv.stopc.robertserver.database.service.IRegistrationService;
import fr.gouv.stopc.robertserver.ws.controller.IDeleteHistoryController;
import fr.gouv.stopc.robertserver.ws.dto.DeleteHistoryResponseDto;
import fr.gouv.stopc.robertserver.ws.exception.RobertServerException;
import fr.gouv.stopc.robertserver.ws.service.AuthRequestValidationService;
import fr.gouv.stopc.robertserver.ws.vo.DeleteHistoryRequestVo;
@Service
public class DeleteHistoryControllerImpl implements IDeleteHistoryController {
private final IRegistrationService registrationService;
private final AuthRequestValidationService authRequestValidationService;
private final ICryptoServerGrpcClient cryptoServerClient;
@Inject
public DeleteHistoryControllerImpl(final ICryptoServerGrpcClient cryptoServerClient,
final IRegistrationService registrationService,
final AuthRequestValidationService authRequestValidationService) {
this.cryptoServerClient = cryptoServerClient;
this.registrationService = registrationService;
this.authRequestValidationService = authRequestValidationService;
}
@Override
public ResponseEntity<DeleteHistoryResponseDto> deleteHistory(DeleteHistoryRequestVo deleteHistoryRequestVo)
throws RobertServerException {
Optional<ResponseEntity> entity = authRequestValidationService.validateRequestForAuth(deleteHistoryRequestVo,
new DeleteHistoryMacValidator(this.cryptoServerClient), new DeleteHistoryAuthenticatedRequestHandler());
if (entity.isPresent()) {
return entity.get();
} else {
return ResponseEntity.badRequest().build();
}
}
private class DeleteHistoryMacValidator implements AuthRequestValidationService.IMacValidator {
private final ICryptoServerGrpcClient cryptoServerClient;
public DeleteHistoryMacValidator(final ICryptoServerGrpcClient cryptoServerClient) {
this.cryptoServerClient = cryptoServerClient;
}
@Override
public boolean validate(byte[] key, byte[] toCheck, byte[] mac) {
boolean res;
try {
MacValidationForTypeRequest request = MacValidationForTypeRequest.newBuilder()
.setKa(ByteString.copyFrom(key)).setDataToValidate(ByteString.copyFrom(toCheck))
.setMacToMatchWith(ByteString.copyFrom(mac))
.setPrefixe(ByteString.copyFrom(new byte[] { DigestSaltEnum.DELETE_HISTORY.getValue() }))
.build();
res = this.cryptoServerClient.validateMacForType(request);
} catch (Exception e) {
res = false;
}
return res;
}
}
private class DeleteHistoryAuthenticatedRequestHandler
implements AuthRequestValidationService.IAuthenticatedRequestHandler {
@Override
public Optional<ResponseEntity> validate(Registration record, int epoch) {
if (Objects.isNull(record)) {
return Optional.of(ResponseEntity.notFound().build());
}
// Clear ExposedEpoch list then save the updated registration
if (Objects.nonNull(record.getExposedEpochs())) {
record.getExposedEpochs().clear();
registrationService.saveRegistration(record);
}
DeleteHistoryResponseDto statusResponse = DeleteHistoryResponseDto.builder().success(true).build();
return Optional.of(ResponseEntity.ok(statusResponse));
}
}
}
package fr.gouv.stopc.robertserver.ws.controller.impl;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
......@@ -8,6 +7,7 @@ import java.util.stream.Collectors;
import javax.inject.Inject;
import org.bson.internal.Base64;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
......@@ -71,11 +71,12 @@ public class RegisterControllerImpl implements IRegisterController {
@Override
public ResponseEntity<RegisterResponseDto> register(RegisterVo registerVo) throws RobertServerException {
if (StringUtils.isEmpty(registerVo.getCaptcha())) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
// TODO: Enable this when the capcha becomes
// if (StringUtils.isEmpty(registerVo.getCaptcha())) {
// return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
// }
if (!captchaService.verifyCaptcha(registerVo)) {
if (!this.captchaService.verifyCaptcha(registerVo)) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
......@@ -124,9 +125,9 @@ public class RegisterControllerImpl implements IRegisterController {
}
registerResponseDto.setIdsForEpochs(
epochKeyBundleDtoMapper.convert(ephTuples));
this.epochKeyBundleDtoMapper.convert(ephTuples));
registerResponseDto.setKey(Base64.getEncoder().encodeToString(registration.getSharedKey()));
registerResponseDto.setKey(Base64.encode(registration.getSharedKey()));
return registerResponseDto;
}
......
......@@ -26,9 +26,8 @@ public class CaptchaDto {
@JsonProperty("challenge_ts")
private Date challengeTimestamp;
@JsonProperty("apk_package_name")
@NotNull
private String appPackageName;
private String hostname;
@JsonProperty("error-codes")
private List<String> errorCodes;
......
package fr.gouv.stopc.robertserver.ws.dto;
import javax.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
@Data
@Builder
public class DeleteHistoryResponseDto {
@NotNull
private Boolean success;
private String message;
}
package fr.gouv.stopc.robertserver.ws.dto.mapper;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.bson.internal.Base64;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import com.google.protobuf.ByteString;
import fr.gouv.stopc.robert.crypto.grpc.server.response.EphemeralTupleResponse;
import fr.gouv.stopc.robertserver.ws.dto.EpochKeyBundleDto;
import fr.gouv.stopc.robertserver.ws.dto.EpochKeyDto;
@Component
public class EpochKeyBundleDtoMapper {
public EpochKeyBundleDtoMapper () {}
public Optional<EpochKeyBundleDto> convert(EphemeralTupleResponse ephemeralTupleResponse) {
public Optional<EpochKeyBundleDto> convert(EphemeralTupleResponse ephemeralTupleResponse){
return Optional.ofNullable(ephemeralTupleResponse)
.map(response -> {
return EpochKeyBundleDto.builder()
.epochId(ephemeralTupleResponse.getEpochId())
.key(EpochKeyDto.builder()
.ebid(Base64.getEncoder().encodeToString(
ephemeralTupleResponse.getEbid().toByteArray()))
.ecc(Base64.getEncoder().encodeToString(
ephemeralTupleResponse.getEcc().toByteArray()))
.build())
.build();
.epochId(ephemeralTupleResponse.getEpochId())
.key(EpochKeyDto.builder()
.ebid(encode(ephemeralTupleResponse.getEbid()))
.ecc(encode(ephemeralTupleResponse.getEcc()))
.build())
.build();
});
}
public List<EpochKeyBundleDto> convert(List<EphemeralTupleResponse> ephemeralTupleResponses){
if (CollectionUtils.isEmpty(ephemeralTupleResponses)) {
return Collections.emptyList();
}
......@@ -49,4 +46,13 @@ public class EpochKeyBundleDtoMapper {
.map(Optional::get)
.collect(Collectors.toList());
}
private String encode(ByteString byteString) {
return Optional.ofNullable(byteString)
.map(ByteString::toByteArray)
.map(Base64::encode)
.orElse(null);
}
}
package fr.gouv.stopc.robertserver.ws.service.impl;
import java.nio.ByteBuffer;
import java.util.Base64;
import java.util.Date;
import java.util.Objects;
import java.util.Optional;
import javax.inject.Inject;
import org.bson.internal.Base64;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
......@@ -29,7 +29,9 @@ import lombok.extern.slf4j.Slf4j;
public class AuthRequestValidationServiceImpl implements AuthRequestValidationService {
private final IServerConfigurationService serverConfigurationService;
private final RegistrationService registrationService;
private final ICryptoServerGrpcClient cryptoServerClient;
......@@ -44,29 +46,27 @@ public class AuthRequestValidationServiceImpl implements AuthRequestValidationSe
}
@Override
public Optional<ResponseEntity> validateRequestForAuth(AuthRequestVo authRequestVo,
IMacValidator macValidator,
IAuthenticatedRequestHandler otherValidator) {
public Optional<ResponseEntity> validateRequestForAuth(AuthRequestVo authRequestVo, IMacValidator macValidator, IAuthenticatedRequestHandler otherValidator) {
// Step #1: Parameter check
if(Objects.isNull(authRequestVo)) {
if (Objects.isNull(authRequestVo)) {
log.info("Discarding authenticated request because of empty request body");
return Optional.of(ResponseEntity.badRequest().build());
}
byte[] ebid = Base64.getDecoder().decode(authRequestVo.getEbid());
if(ByteUtils.isEmpty(ebid) || ebid.length != 8) {
byte[] ebid = Base64.decode(authRequestVo.getEbid());
if (ByteUtils.isEmpty(ebid) || ebid.length != 8) {
log.info("Discarding authenticated request because of invalid EBID field size");
return Optional.of(ResponseEntity.badRequest().build());
}
byte[] time = Base64.getDecoder().decode(authRequestVo.getTime());
if(ByteUtils.isEmpty(time) || time.length != 4) {
byte[] time = Base64.decode(authRequestVo.getTime());
if (ByteUtils.isEmpty(time) || time.length != 4) {
log.info("Discarding authenticated request because of invalid Time field size");
return Optional.of(ResponseEntity.badRequest().build());
}
byte[] mac = Base64.getDecoder().decode(authRequestVo.getMac());
if(ByteUtils.isEmpty(mac) || mac.length != 32) {
byte[] mac = Base64.decode(authRequestVo.getMac());
if (ByteUtils.isEmpty(mac) || mac.length != 32) {
log.info("Discarding authenticated request because of invalid MAC field size");
return Optional.of(ResponseEntity.badRequest().build());
}
......@@ -74,17 +74,14 @@ public class AuthRequestValidationServiceImpl implements AuthRequestValidationSe
final long currentTime = TimeUtils.convertUnixMillistoNtpSeconds(new Date().getTime());
// Step #2: check if time is close to current time
if(!checkTime(time, currentTime)) {
if (!checkTime(time, currentTime)) {
log.info("Discarding authenticated request because provided time is too far from current server time");
return Optional.of(ResponseEntity.badRequest().build());
}
try {
// Step #3: retrieve id_A and epoch from EBID
DecryptEBIDRequest request = DecryptEBIDRequest
.newBuilder()
.setEbid(ByteString.copyFrom(ebid))
.build();
DecryptEBIDRequest request = DecryptEBIDRequest.newBuilder().setEbid(ByteString.copyFrom(ebid)).build();
byte[] decrytedEbid = this.cryptoServerClient.decryptEBID(request);
byte[] epochId = new byte[4];
......@@ -92,22 +89,22 @@ public class AuthRequestValidationServiceImpl implements AuthRequestValidationSe
System.arraycopy(decrytedEbid, 0, epochId, 1, epochId.length - 1);
System.arraycopy(decrytedEbid, epochId.length - 1, idA, 0, idA.length);
ByteBuffer wrapped = ByteBuffer.wrap(epochId);
int epoch = wrapped.getInt();
int epoch = wrapped.getInt();
// Step #4: Get record from database
Optional<Registration> record = this.registrationService.findById(idA);
if(record.isPresent()) {
if (record.isPresent()) {
byte[] ka = record.get().getSharedKey();
// Step #5: Verify MAC
byte [] toCheck = new byte[12];
byte[] toCheck = new byte[12];
System.arraycopy(ebid, 0, toCheck, 0, 8);
System.arraycopy(time, 0, toCheck, 8, 4);
boolean isMacValid = macValidator.validate(ka, toCheck, mac);
if(!isMacValid) {
if (!isMacValid) {
log.info("Discarding authenticated request because MAC is invalid");
return Optional.of(ResponseEntity.badRequest().build());
}
......
......@@ -11,11 +11,13 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import fr.gouv.stopc.robert.server.common.service.IServerConfigurationService;
import fr.gouv.stopc.robertserver.ws.dto.CaptchaDto;
import fr.gouv.stopc.robertserver.ws.service.CaptchaService;
import fr.gouv.stopc.robertserver.ws.utils.PropertyLoader;
import fr.gouv.stopc.robertserver.ws.vo.RegisterVo;
import lombok.AllArgsConstructor;
import lombok.Data;
......@@ -26,17 +28,18 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
public class CaptchaServiceImpl implements CaptchaService {
final static String URL_VERIFICATION = "https://www.google.com/recaptcha/api/siteverify";
private RestTemplate restTemplate;
private IServerConfigurationService serverConfigurationService;
private PropertyLoader propertyLoader;
@Inject
public CaptchaServiceImpl(RestTemplate restTemplate, IServerConfigurationService serverConfigurationRepository) {
public CaptchaServiceImpl(RestTemplate restTemplate, IServerConfigurationService serverConfigurationService, PropertyLoader propertyLoader) {
this.restTemplate = restTemplate;
this.serverConfigurationService = serverConfigurationRepository;
this.serverConfigurationService = serverConfigurationService;
this.propertyLoader = propertyLoader;
}
@Override
......@@ -47,12 +50,18 @@ public class CaptchaServiceImpl implements CaptchaService {
// must be called for test
return true || Optional.ofNullable(registerVo).map(item -> {
HttpEntity<RegisterVo> request = new HttpEntity(new CaptchaVo(item.getCaptcha(), this.serverConfigurationService.getCaptchaSecret()).toString(), initHttpHeaders());
HttpEntity<RegisterVo> request = new HttpEntity(new CaptchaVo(item.getCaptcha(), this.propertyLoader.getCaptchaSecret()).toString(), initHttpHeaders());
Date sendingDate = new Date();
ResponseEntity<CaptchaDto> response = this.restTemplate.postForEntity(URL_VERIFICATION, request, CaptchaDto.class);
ResponseEntity<CaptchaDto> response = null;
try {
response = this.restTemplate.postForEntity(this.propertyLoader.getCaptchaVerificationUrl(), request, CaptchaDto.class);
} catch(RestClientException e) {
log.error(e.getMessage());
return false;
}
return Optional.ofNullable(response).map(ResponseEntity::getBody).filter(captchaDto -> !Objects.isNull(captchaDto.getChallengeTimestamp())).map(captchaDto -> {
return Optional.ofNullable(response).map(ResponseEntity::getBody).filter(captchaDto -> Objects.nonNull(captchaDto.getChallengeTimestamp())).map(captchaDto -> {
log.info("Result of CAPTCHA verification: {}", captchaDto);
return isSuccess(captchaDto, sendingDate);
......@@ -71,9 +80,8 @@ public class CaptchaServiceImpl implements CaptchaService {
private boolean isSuccess(CaptchaDto captchaDto, Date sendingDate) {
return this.serverConfigurationService.getCaptchaAppPackageName().equals(captchaDto.getAppPackageName()) && Math.abs(
sendingDate.getTime() - captchaDto.getChallengeTimestamp().getTime()) <= this.serverConfigurationService
.getCaptchaChallengeTimestampTolerance() * 1000L;
return this.propertyLoader.getCaptchaHostname().equals(captchaDto.getHostname()) && Math
.abs(sendingDate.getTime() - captchaDto.getChallengeTimestamp().getTime()) <= this.serverConfigurationService.getCaptchaChallengeTimestampTolerance() * 1000L;
}
@NoArgsConstructor
......
......@@ -5,16 +5,35 @@ import org.springframework.stereotype.Component;
import lombok.Getter;
@Getter
@Component
public class PropertyLoader {
@Value("${robert.crypto.server.host}")
private String cryptoServerHost;
@Value("${robert.crypto.server.port}")
private String cryptoServerPort;
@Value("${robert.crypto.server.host}")
private String cryptoServerHost;
@Value("${robert.crypto.server.port}")
private String cryptoServerPort;
/**
*
* @return the verification URL for the captcha
*/
@Value("${captcha.verify.url}")
private String captchaVerificationUrl;
/**
*
* @return the secret to be sent to the captcha server along with challenge response
*/
@Value("${captcha.secret}")
private String captchaSecret;
/**
*
* @return the hostname of the site to check against the response from the captcha server
*/
@Value("${captcha.hostname}")
private String captchaHostname;
}