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 @@ ...@@ -49,6 +49,12 @@
<artifactId>spring-boot-starter-data-jpa</artifactId> <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency> </dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>robert-server-common</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
......
package fr.gouv.stopc.robert.crypto.grpc.server.storage.cryptographic.service; package fr.gouv.stopc.robert.crypto.grpc.server.storage.cryptographic.service;
import java.security.Key;
import java.security.KeyPair; import java.security.KeyPair;
import java.security.Provider; import java.security.Provider;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
public interface ICryptographicStorageService { public interface ICryptographicStorageService {
...@@ -15,6 +15,10 @@ public interface ICryptographicStorageService { ...@@ -15,6 +15,10 @@ public interface ICryptographicStorageService {
void store(String alias, String secretKey); 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); byte[] getEntry(String alias);
void addKeys(String serverPublicKey, String serverPrivateKey); void addKeys(String serverPublicKey, String serverPrivateKey);
......
...@@ -10,7 +10,6 @@ import java.security.KeyFactory; ...@@ -10,7 +10,6 @@ import java.security.KeyFactory;
import java.security.KeyPair; import java.security.KeyPair;
import java.security.KeyPairGenerator; import java.security.KeyPairGenerator;
import java.security.KeyStore; import java.security.KeyStore;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.KeyStoreException; import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey; import java.security.PrivateKey;
...@@ -18,7 +17,6 @@ import java.security.Provider; ...@@ -18,7 +17,6 @@ import java.security.Provider;
import java.security.ProviderException; import java.security.ProviderException;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.Security; import java.security.Security;
import java.security.UnrecoverableEntryException;
import java.security.UnrecoverableKeyException; import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory; import java.security.cert.CertificateFactory;
...@@ -28,12 +26,17 @@ import java.security.spec.EncodedKeySpec; ...@@ -28,12 +26,17 @@ import java.security.spec.EncodedKeySpec;
import java.security.spec.InvalidKeySpecException; import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec; import java.security.spec.X509EncodedKeySpec;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Stream; import java.util.stream.Stream;
import javax.crypto.KeyAgreement; import javax.crypto.KeyAgreement;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
...@@ -45,6 +48,7 @@ import org.springframework.stereotype.Service; ...@@ -45,6 +48,7 @@ import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import fr.gouv.stopc.robert.crypto.grpc.server.storage.cryptographic.service.ICryptographicStorageService; 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 lombok.extern.slf4j.Slf4j;
import sun.security.pkcs11.SunPKCS11; import sun.security.pkcs11.SunPKCS11;
...@@ -120,12 +124,22 @@ public class CryptographicStorageServiceImpl implements ICryptographicStorageSer ...@@ -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 @Override
public void addKeys(String serverPublicKey, String serverPrivateKey) { public void addKeys(String serverPublicKey, String serverPrivateKey) {
try { try {
log.info("PUBLIC : {}", serverPublicKey);
log.info("PRIVATE : {}", serverPrivateKey);
EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(Base64.decode(serverPrivateKey)); EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(Base64.decode(serverPrivateKey));
KeyFactory generator = KeyFactory.getInstance("EC"); KeyFactory generator = KeyFactory.getInstance("EC");
PrivateKey privateKey = generator.generatePrivate(privateKeySpec); PrivateKey privateKey = generator.generatePrivate(privateKeySpec);
...@@ -213,7 +227,7 @@ public class CryptographicStorageServiceImpl implements ICryptographicStorageSer ...@@ -213,7 +227,7 @@ public class CryptographicStorageServiceImpl implements ICryptographicStorageSer
@Override @Override
public Provider getProvider() { public Provider getProvider() {
// TODO Auto-generated method stub
return this.provider; return this.provider;
} }
...@@ -241,11 +255,98 @@ public class CryptographicStorageServiceImpl implements ICryptographicStorageSer ...@@ -241,11 +255,98 @@ public class CryptographicStorageServiceImpl implements ICryptographicStorageSer
ecdh.doPhase(ephemeralKeys.getPublic(), true); ecdh.doPhase(ephemeralKeys.getPublic(), true);
return ecdh.generateSecret(); return ecdh.generateSecret();
} catch (KeyStoreException | NoSuchAlgorithmException | InvalidAlgorithmParameterException | UnrecoverableKeyException | InvalidKeyException | IllegalStateException e) { } 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; 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"?> <?xml version="1.0" encoding="UTF-8"?>
<configuration> <configuration>
<property name="LOG_DIR" value="${ROBERT_CRYPTO_SERVER_LOG_FILE_PATH:-/logs}" /> <property name="LOG_DIR"
<property name="LOG_FILENAME" value="${ROBERT_CRYPTO_SERVER_LOG_FILE_NAME:-robert-crypto-grpc-server}" /> value="${ROBERT_CRYPTO_SERVER_LOG_FILE_PATH:-/logs}" />
<property name="ERROR_LOG_FILENAME" value="${ROBERT_CRYPTO_SERVER_ERROR_LOG_FILE_NAME:-robert-crypto-grpc-server}.error" /> <property name="LOG_FILENAME"
value="${ROBERT_CRYPTO_SERVER_LOG_FILE_NAME:-robert-crypto-grpc-server}" />
<appender name="RollingFile" <property name="ERROR_LOG_FILENAME"
class="ch.qos.logback.core.rolling.RollingFileAppender"> value="${ROBERT_CRYPTO_SERVER_ERROR_LOG_FILE_NAME:-robert-crypto-grpc-server}.error" />
<file>${LOG_DIR}/${LOG_FILENAME}.log</file>
<encoder <appender name="RollingFile"
class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> class="ch.qos.logback.core.rolling.RollingFileAppender">
<Pattern>%d %p %C{1.} [%t] %m%n</Pattern> <file>${LOG_DIR}/${LOG_FILENAME}.log</file>
</encoder> <encoder
class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<rollingPolicy <Pattern>%d %p %C{1.} [%t] %m%n</Pattern>
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> </encoder>
<!-- rollover daily and when the file reaches 10 MegaBytes -->
<fileNamePattern>${LOG_DIR}/${LOG_FILENAME}.%d{yyyy-MM-dd}.%i.log.gz <rollingPolicy
</fileNamePattern> class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<maxFileSize>10MB</maxFileSize> <!-- rollover daily and when the file reaches 10 MegaBytes -->
</rollingPolicy> <fileNamePattern>${LOG_DIR}/${LOG_FILENAME}.%d{yyyy-MM-dd}.%i.log.gz
</appender> </fileNamePattern>
<maxFileSize>10MB</maxFileSize>
</rollingPolicy>
</appender>
<appender name="RollingErrorFile" <appender name="RollingErrorFile"
class="ch.qos.logback.core.rolling.RollingFileAppender"> class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_DIR}/${ERROR_LOG_FILENAME}.log</file> <file>${LOG_DIR}/${ERROR_LOG_FILENAME}.log</file>
<encoder <encoder
class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<Pattern>%d %p %C{1.} [%t] %m%n</Pattern> <Pattern>%d %p %C{1.} [%t] %m%n</Pattern>
</encoder> </encoder>
<rollingPolicy <rollingPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- rollover daily and when the file reaches 10 MegaBytes --> <!-- rollover daily and when the file reaches 10 MegaBytes -->
<fileNamePattern>${LOG_DIR}/${ERROR_LOG_FILENAME}.%d{yyyy-MM-dd}.%i.log.gz <fileNamePattern>${LOG_DIR}/${ERROR_LOG_FILENAME}.%d{yyyy-MM-dd}.%i.log.gz
</fileNamePattern> </fileNamePattern>
<maxFileSize>10MB</maxFileSize> <maxFileSize>10MB</maxFileSize>
</rollingPolicy> </rollingPolicy>
</appender> </appender>
<!-- LOG everything at INFO level --> <!-- LOG everything at INFO level -->
<root level="info"> <root level="info">
<appender-ref ref="RollingFile" /> <appender-ref ref="RollingFile" />
</root> </root>
<!-- at TRACE level --> <!-- at TRACE level -->
<logger name="trace" level="trace" additivity="false"> <logger name="trace" level="trace" additivity="false">
<appender-ref ref="RollingFile" /> <appender-ref ref="RollingFile" />
</logger> </logger>
<!-- at ERROR level --> <!-- at WARN level -->
<logger name="error" level="error" additivity="false"> <logger name="warn" level="warn" additivity="false">
<appender-ref ref="RollingErrorFile" /> <appender-ref ref="RollingFile" />
</logger> </logger>
<!-- at ERROR level -->
<logger name="error" level="error" additivity="true">
<appender-ref ref="RollingErrorFile" />
</logger>
</configuration> </configuration>
package fr.gouv.stopc.robert.server.common.utils; package fr.gouv.stopc.robert.server.common.utils;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
public final class TimeUtils { public final class TimeUtils {
// Number of seconds to fill the gap between UNIX timestamp (1/1/1970) and NTP timestamp (1/1/1900) // 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; private final static long SECONDS_FROM_01_01_1900 = 2208988800L;
...@@ -39,4 +43,11 @@ public final class TimeUtils { ...@@ -39,4 +43,11 @@ public final class TimeUtils {
public static int getCurrentEpochFrom(final long timeStart) { public static int getCurrentEpochFrom(final long timeStart) {
return getNumberOfEpochsBetween(timeStart, convertUnixMillistoNtpSeconds(System.currentTimeMillis())); 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