diff --git a/robert-crypto-grpc-server-storage/pom.xml b/robert-crypto-grpc-server-storage/pom.xml index 9d103fc5749a416b23889e35c3bb6e9eca8bb221..68337b144a36dda4276a632ed75bd70ac3ffb713 100644 --- a/robert-crypto-grpc-server-storage/pom.xml +++ b/robert-crypto-grpc-server-storage/pom.xml @@ -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> diff --git a/robert-crypto-grpc-server-storage/src/main/java/fr/gouv/stopc/robert/crypto/grpc/server/storage/cryptographic/service/ICryptographicStorageService.java b/robert-crypto-grpc-server-storage/src/main/java/fr/gouv/stopc/robert/crypto/grpc/server/storage/cryptographic/service/ICryptographicStorageService.java index 74f47c6cab72f822bcd2ac30d20a86393464e63d..e3757ea01de0ff7c31b73d3044419adbb4751d3f 100644 --- a/robert-crypto-grpc-server-storage/src/main/java/fr/gouv/stopc/robert/crypto/grpc/server/storage/cryptographic/service/ICryptographicStorageService.java +++ b/robert-crypto-grpc-server-storage/src/main/java/fr/gouv/stopc/robert/crypto/grpc/server/storage/cryptographic/service/ICryptographicStorageService.java @@ -1,8 +1,8 @@ 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); diff --git a/robert-crypto-grpc-server-storage/src/main/java/fr/gouv/stopc/robert/crypto/grpc/server/storage/cryptographic/service/impl/CryptographicStorageServiceImpl.java b/robert-crypto-grpc-server-storage/src/main/java/fr/gouv/stopc/robert/crypto/grpc/server/storage/cryptographic/service/impl/CryptographicStorageServiceImpl.java index bfdb7db3e636e4c12fb7e325cba151d4b3b287d4..b98e3ff006594dee164df63a3b92b39f69097ee8 100644 --- a/robert-crypto-grpc-server-storage/src/main/java/fr/gouv/stopc/robert/crypto/grpc/server/storage/cryptographic/service/impl/CryptographicStorageServiceImpl.java +++ b/robert-crypto-grpc-server-storage/src/main/java/fr/gouv/stopc/robert/crypto/grpc/server/storage/cryptographic/service/impl/CryptographicStorageServiceImpl.java @@ -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; + + } + } diff --git a/robert-crypto-grpc-server/src/main/resources/logback.xml b/robert-crypto-grpc-server/src/main/resources/logback.xml index ef824b371e4a1f0d9ac82cb7bcd0665e11bdabba..577544b8c5ba777ef9768c514d955d451c1fab81 100644 --- a/robert-crypto-grpc-server/src/main/resources/logback.xml +++ b/robert-crypto-grpc-server/src/main/resources/logback.xml @@ -1,57 +1,65 @@ <?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> diff --git a/robert-server-common/src/main/java/fr/gouv/stopc/robert/server/common/utils/TimeUtils.java b/robert-server-common/src/main/java/fr/gouv/stopc/robert/server/common/utils/TimeUtils.java index a8ac9549175c0541c91951ac3d0b34a653f4604e..b056a6dae6964f8eb8e381868abb27301124431d 100644 --- a/robert-server-common/src/main/java/fr/gouv/stopc/robert/server/common/utils/TimeUtils.java +++ b/robert-server-common/src/main/java/fr/gouv/stopc/robert/server/common/utils/TimeUtils.java @@ -1,5 +1,9 @@ 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(); + } }