Mentions légales du service

Skip to content
Snippets Groups Projects
Commit 7bb4fda3 authored by Deniro StopCovid's avatar Deniro StopCovid
Browse files

feat: Enable KS rotation and storage in secure keystore

parent b61a8f9f
No related branches found
No related tags found
No related merge requests found
......@@ -49,6 +49,12 @@
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>robert-server-common</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
......
package fr.gouv.stopc.robert.crypto.grpc.server.storage.cryptographic.service;
import java.security.Key;
import java.security.KeyPair;
import java.security.Provider;
import java.util.Map;
import java.util.Optional;
public interface ICryptographicStorageService {
......@@ -15,6 +15,10 @@ public interface ICryptographicStorageService {
void store(String alias, String secretKey);
byte[] getServerKey(int epochId, long serviceTimeStart);
Map<Integer, byte[]> getServerKeys(int epochId, long timeStart, int nbDays);
byte[] getEntry(String alias);
void addKeys(String serverPublicKey, String serverPrivateKey);
......
......@@ -10,7 +10,6 @@ import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
......@@ -18,7 +17,6 @@ import java.security.Provider;
import java.security.ProviderException;
import java.security.PublicKey;
import java.security.Security;
import java.security.UnrecoverableEntryException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
......@@ -28,12 +26,17 @@ import java.security.spec.EncodedKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;
import javax.crypto.KeyAgreement;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
......@@ -45,6 +48,7 @@ import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import fr.gouv.stopc.robert.crypto.grpc.server.storage.cryptographic.service.ICryptographicStorageService;
import fr.gouv.stopc.robert.server.common.utils.TimeUtils;
import lombok.extern.slf4j.Slf4j;
import sun.security.pkcs11.SunPKCS11;
......@@ -120,12 +124,22 @@ public class CryptographicStorageServiceImpl implements ICryptographicStorageSer
}
private void storeKey(String alias, Key secretKey) {
try {
if (!isServerKeyAlias(alias) && this.contains(alias)) {
this.delete(alias);
this.keyStore.setKeyEntry(alias, secretKey, null, null);
}
} catch (KeyStoreException e) {
log.error("An expected error occured when trying to store the alias {} due to {}", alias, e.getMessage());
}
}
@Override
public void addKeys(String serverPublicKey, String serverPrivateKey) {
try {
log.info("PUBLIC : {}", serverPublicKey);
log.info("PRIVATE : {}", serverPrivateKey);
EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(Base64.decode(serverPrivateKey));
KeyFactory generator = KeyFactory.getInstance("EC");
PrivateKey privateKey = generator.generatePrivate(privateKeySpec);
......@@ -213,7 +227,7 @@ public class CryptographicStorageServiceImpl implements ICryptographicStorageSer
@Override
public Provider getProvider() {
// TODO Auto-generated method stub
return this.provider;
}
......@@ -241,11 +255,98 @@ public class CryptographicStorageServiceImpl implements ICryptographicStorageSer
ecdh.doPhase(ephemeralKeys.getPublic(), true);
return ecdh.generateSecret();
} catch (KeyStoreException | NoSuchAlgorithmException | InvalidAlgorithmParameterException | UnrecoverableKeyException | InvalidKeyException | IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
log.error("Unable to perform ECDH key agreement from the server public/private keys", e.getClass(), e.getMessage());
}
return null;
}
public void storeServerKey(String alias, byte[] secret) {
if(Objects.isNull(secret)) {
log.error("Unable to store a null secret");
}
if (this.contains(alias)) {
log.warn("A Server key for this alias {} already exists, could not replace it", alias);
return ;
}
SecretKeySpec secretKey = new SecretKeySpec(secret, "AES");
}
@Override
public byte[] getServerKey(int epochId, long serviceTimeStart) {
LocalDate dateFromEpoch = TimeUtils.getDateFromEpoch(epochId, serviceTimeStart);
if(Objects.isNull(dateFromEpoch) ) {
log.error("The date from epoch {} and the time start {} is null", epochId, serviceTimeStart);
return null;
}
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd-MM-yyyy");
String alias = dateFromEpoch.format(dateFormatter);
if (this.contains(alias)) {
log.warn("A Server key for this alias {} already exists, could not replace it", alias);
return this.getEntry(alias) ;
}
byte[] serverKey = generateKey(192);
SecretKeySpec secretKey = new SecretKeySpec(serverKey, "AES");
this.storeKey(alias, secretKey);
return serverKey;
}
private byte[] getServerKey(LocalDate dateFromEpoch) {
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd-MM-yyyy");
String alias = dateFromEpoch.format(dateFormatter);
if (this.contains(alias)) {
log.warn("A Server key for this alias {} already exists, could not replace it", alias);
return this.getEntry(alias) ;
}
byte[] serverKey = generateKey(192);
SecretKeySpec secretKey = new SecretKeySpec(serverKey, "AES");
this.storeKey(alias, secretKey);
return serverKey;
}
/**
* Generation of bit array.
* @param size in bits
* @return
*/
private byte[] generateKey(int size) {
size /= 8;
byte[] data = new byte[size];
for (int i = 0; i < size; i++) {
data[i] = new Long(i).byteValue();
}
return data;
}
@Override
public Map<Integer, byte[]> getServerKeys(int epochId, long timeStart, int nbDays) {
LocalDate dateFromEpoch = TimeUtils.getDateFromEpoch(epochId, timeStart);
if(Objects.isNull(dateFromEpoch) ) {
log.error("The date from epoch {} and the time start {} is null", epochId, timeStart);
return null;
}
Map<Integer, byte[]> keyMap = new HashMap<Integer, byte[]>();
keyMap.put(0, getServerKey(dateFromEpoch));
for(int i = 1; i< nbDays; i++) {
keyMap.put(i, this.getServerKey(dateFromEpoch.plusDays(i)));
}
return keyMap;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="LOG_DIR" value="${ROBERT_CRYPTO_SERVER_LOG_FILE_PATH:-/logs}" />
<property name="LOG_FILENAME" value="${ROBERT_CRYPTO_SERVER_LOG_FILE_NAME:-robert-crypto-grpc-server}" />
<property name="ERROR_LOG_FILENAME" value="${ROBERT_CRYPTO_SERVER_ERROR_LOG_FILE_NAME:-robert-crypto-grpc-server}.error" />
<appender name="RollingFile"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_DIR}/${LOG_FILENAME}.log</file>
<encoder
class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<Pattern>%d %p %C{1.} [%t] %m%n</Pattern>
</encoder>
<rollingPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- rollover daily and when the file reaches 10 MegaBytes -->
<fileNamePattern>${LOG_DIR}/${LOG_FILENAME}.%d{yyyy-MM-dd}.%i.log.gz
</fileNamePattern>
<maxFileSize>10MB</maxFileSize>
</rollingPolicy>
</appender>
<property name="LOG_DIR"
value="${ROBERT_CRYPTO_SERVER_LOG_FILE_PATH:-/logs}" />
<property name="LOG_FILENAME"
value="${ROBERT_CRYPTO_SERVER_LOG_FILE_NAME:-robert-crypto-grpc-server}" />
<property name="ERROR_LOG_FILENAME"
value="${ROBERT_CRYPTO_SERVER_ERROR_LOG_FILE_NAME:-robert-crypto-grpc-server}.error" />
<appender name="RollingFile"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_DIR}/${LOG_FILENAME}.log</file>
<encoder
class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<Pattern>%d %p %C{1.} [%t] %m%n</Pattern>
</encoder>
<rollingPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- rollover daily and when the file reaches 10 MegaBytes -->
<fileNamePattern>${LOG_DIR}/${LOG_FILENAME}.%d{yyyy-MM-dd}.%i.log.gz
</fileNamePattern>
<maxFileSize>10MB</maxFileSize>
</rollingPolicy>
</appender>
<appender name="RollingErrorFile"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_DIR}/${ERROR_LOG_FILENAME}.log</file>
<encoder
class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<Pattern>%d %p %C{1.} [%t] %m%n</Pattern>
</encoder>
<rollingPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- rollover daily and when the file reaches 10 MegaBytes -->
<fileNamePattern>${LOG_DIR}/${ERROR_LOG_FILENAME}.%d{yyyy-MM-dd}.%i.log.gz
</fileNamePattern>
<maxFileSize>10MB</maxFileSize>
</rollingPolicy>
</appender>
<!-- LOG everything at INFO level -->
<root level="info">
<appender-ref ref="RollingFile" />
</root>
<!-- at TRACE level -->
<logger name="trace" level="trace" additivity="false">
<appender-ref ref="RollingFile" />
</logger>
<!-- at ERROR level -->
<logger name="error" level="error" additivity="false">
<appender-ref ref="RollingErrorFile" />
</logger>
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_DIR}/${ERROR_LOG_FILENAME}.log</file>
<encoder
class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<Pattern>%d %p %C{1.} [%t] %m%n</Pattern>
</encoder>
<rollingPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- rollover daily and when the file reaches 10 MegaBytes -->
<fileNamePattern>${LOG_DIR}/${ERROR_LOG_FILENAME}.%d{yyyy-MM-dd}.%i.log.gz
</fileNamePattern>
<maxFileSize>10MB</maxFileSize>
</rollingPolicy>
</appender>
<!-- LOG everything at INFO level -->
<root level="info">
<appender-ref ref="RollingFile" />
</root>
<!-- at TRACE level -->
<logger name="trace" level="trace" additivity="false">
<appender-ref ref="RollingFile" />
</logger>
<!-- at WARN level -->
<logger name="warn" level="warn" additivity="false">
<appender-ref ref="RollingFile" />
</logger>
<!-- at ERROR level -->
<logger name="error" level="error" additivity="true">
<appender-ref ref="RollingErrorFile" />
</logger>
</configuration>
package fr.gouv.stopc.robert.server.common.utils;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
public final class TimeUtils {
// Number of seconds to fill the gap between UNIX timestamp (1/1/1970) and NTP timestamp (1/1/1900)
private final static long SECONDS_FROM_01_01_1900 = 2208988800L;
......@@ -39,4 +43,11 @@ public final class TimeUtils {
public static int getCurrentEpochFrom(final long timeStart) {
return getNumberOfEpochsBetween(timeStart, convertUnixMillistoNtpSeconds(System.currentTimeMillis()));
}
public static LocalDate getDateFromEpoch(int epoch, long timeStart) {
long fromInNtpSecs = timeStart - (EPOCH_DURATION_SECS * epoch);
long fromUnixMillis = (fromInNtpSecs - SECONDS_FROM_01_01_1900) * 1000;
return Instant.ofEpochMilli(fromUnixMillis).atZone(ZoneId.of("Europe/Paris")).toLocalDate();
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment