diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..8f2b711 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.compile.nullAnalysis.mode": "disabled" +} \ No newline at end of file diff --git a/app/src/main/java/com/example/app/LicenseOneShot.java b/app/src/main/java/com/example/app/LicenseOneShot.java new file mode 100644 index 0000000..eeb7269 --- /dev/null +++ b/app/src/main/java/com/example/app/LicenseOneShot.java @@ -0,0 +1,135 @@ +package com.example.app; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.time.Instant; +import java.util.Base64; +import java.util.Properties; + +import com.example.app.utils.DeterministicHexSequenceWithTimestamp; +import com.example.app.utils.KeyUtils; +import com.example.app.utils.LicenseUtils; + +public class LicenseOneShot { + + private final String myId; + private final String myPrivateKey; + private final String licenseServerPublicKey; + private final String licenseServerEndpoint; + private final InputStream trustStoreIO; + private final String trustStorePassword; + private final String filePath; + private final Properties properties = new Properties(); + + public LicenseOneShot(String myFolder, String myId, String myPrivateKey, + String licenseServerPublicKey, String licenseServerEndpoint, InputStream trustStoreIO, + String trustStorePassword) { + this.myId = myId; + this.myPrivateKey = myPrivateKey; + this.licenseServerPublicKey = licenseServerPublicKey; + this.licenseServerEndpoint = licenseServerEndpoint; + this.trustStoreIO = trustStoreIO; + this.trustStorePassword = trustStorePassword; + this.filePath = myFolder + File.separator + myId; + checkProperties(this.properties, filePath); + } + + public void check() { + + // TODO: check expiration... + final var requestTimestamp = Instant.now() + .toEpochMilli(); + final var message = properties.getProperty("index") + ":" + requestTimestamp; + final var encryptedData = KeyUtils.encryptDataWithAESGCM(message, KeyUtils.generateSharedSecret( + KeyUtils.stringToPrivateKey(myPrivateKey), + KeyUtils.stringToPublicKey(licenseServerPublicKey))); + + final var encryptedRequest = Base64.getUrlEncoder() + .encodeToString(encryptedData); + final var signatureRequest = KeyUtils.signMessage(message, + KeyUtils.stringToPrivateKey(myPrivateKey)); + + final var responseBody = LicenseUtils + .request(licenseServerEndpoint, trustStoreIO, + trustStorePassword, + Base64.getUrlEncoder() + .encodeToString(myId.getBytes()) + "." + encryptedRequest + "." + + signatureRequest); + + // decrypt response + final var splitResponse = responseBody.split("\\."); + final var encryptedResponse = splitResponse[0]; + final var signatureResponse = splitResponse[1]; + + final var decryptedResponse = KeyUtils + .decryptDataWithAESGCM(Base64.getUrlDecoder().decode(encryptedResponse), + KeyUtils.generateSharedSecret( + KeyUtils.stringToPrivateKey(myPrivateKey), + KeyUtils.stringToPublicKey(licenseServerPublicKey))); + + // verify signature of response + final var isVerified = KeyUtils.verifySignature(decryptedResponse, signatureResponse, + KeyUtils.stringToPublicKey(licenseServerPublicKey)); + System.out.println("Is verified? " + isVerified); + + if (!isVerified) { + throw new IllegalStateException("License not valid! Invalid signature."); + } + + // parse response + final var splitDecryptedResponse = decryptedResponse.split("\\:"); + + // check values and throw if license is bad + System.out.println("License server index: " + splitDecryptedResponse[0]); + final var nextLocalIndex = DeterministicHexSequenceWithTimestamp + .nextValueString(properties.getProperty("index"), requestTimestamp); + System.out.println("Calculated local index: " + nextLocalIndex); + if (!splitDecryptedResponse[0].equals(nextLocalIndex)) { + throw new IllegalStateException("License not valid!"); + } + + // persist license on property file. + properties.setProperty("index", splitDecryptedResponse[0]); + properties.setProperty("until", splitDecryptedResponse[1]); + saveProperties(properties, this.filePath); + } + + private void checkProperties(Properties properties, + String filePath) { + try { + File file = new File(filePath); + if (!file.exists()) { + createDefaultProperties(properties, filePath); + } else { + loadProperties(properties, filePath); + } + } catch (IOException e) { + throw new RuntimeException("IO Exception!", e); + } + } + + private void createDefaultProperties(Properties properties, + String filePath) throws IOException { + properties.setProperty("index", "1A3F"); + saveProperties(properties, filePath); + } + + private void saveProperties(Properties properties, + String filePath) { + try (FileOutputStream fos = new FileOutputStream(filePath)) { + properties.store(fos, "Updated properties file"); + } catch (IOException e) { + throw new RuntimeException("IO Exception!", e); + } + } + + private void loadProperties(Properties properties, + String filePath) throws IOException { + try (FileInputStream fis = new FileInputStream(filePath)) { + properties.load(fis); + } + } +} diff --git a/app/src/main/java/com/example/app/StartupValidator.java b/app/src/main/java/com/example/app/StartupValidator.java index 49234e2..7d3f900 100644 --- a/app/src/main/java/com/example/app/StartupValidator.java +++ b/app/src/main/java/com/example/app/StartupValidator.java @@ -32,73 +32,20 @@ public class StartupValidator implements ApplicationContextInitializer= 400) + ? connection.getErrorStream() // Handle errors + : connection.getInputStream(); + + if (inputStream == null) { + return "No response"; + } + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { + StringBuilder response = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + response.append(line).append("\n"); + } + return response.toString().trim(); + } + } + } diff --git a/license/src/main/java/com/example/license/rest/LicenseRest.java b/license/src/main/java/com/example/license/rest/LicenseRest.java index 4b0412a..b3805fd 100644 --- a/license/src/main/java/com/example/license/rest/LicenseRest.java +++ b/license/src/main/java/com/example/license/rest/LicenseRest.java @@ -5,6 +5,12 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; import java.util.Base64; import java.util.Properties; @@ -34,7 +40,7 @@ public class LicenseRest { String[] split = payload.split("\\."); File directory = new File(licenseDirectory); - // TODO: extract ID, encrypted message and signature + // extract ID, encrypted message and signature final var id = new String(Base64.getUrlDecoder().decode(split[0]), StandardCharsets.UTF_8); final var encrypted = split[1]; final var signature = split[2]; @@ -58,33 +64,55 @@ public class LicenseRest { KeyUtils.stringToPublicKey(props.getProperty("sender-public-key")))); System.out.println("Decrypted: " + decrypted); - // TODO: verify signature + // verify signature boolean isVerified = KeyUtils.verifySignature(decrypted, signature, KeyUtils.stringToPublicKey(props.getProperty("sender-public-key"))); System.out.println("Is verified? " + isVerified); - // TODO: parse data + if (!isVerified) { + throw new RuntimeException("Invalid signature!"); + } + // parse data final var decryptedSplit = decrypted.split("\\:"); final var currentSenderIndex = decryptedSplit[0]; final var timestamp = Long.valueOf(decryptedSplit[1]); final var currentLocalIndex = props.getProperty("index"); - // TODO: check current local index if it matches with current sender index + + // check current local index if it matches with current sender index final var nextLocalIndex = DeterministicHexSequenceWithTimestamp .nextValueString(props.getProperty("index"), timestamp); System.out.println("Current sender index: " + currentSenderIndex + " timestamp = " + timestamp + " current local index = " + currentLocalIndex + " next index = " + nextLocalIndex); - // TODO: veryfy current index match, increment index using timestamp then send - // new index in response. + setExpiration(Instant.ofEpochMilli(timestamp)); + + // veryfy current index match, increment index using timestamp then send new + // index in response. + if (!currentSenderIndex.equals(currentLocalIndex)) { + throw new RuntimeException("Invalid current index!"); + } System.out.println("Props: " + props); - System.out.println(props.getProperty("index")); if (!StringUtils.hasText(props.getProperty("index"))) { props.setProperty("index", "1A3F"); saveProperties(filePath, props); } else { - props.setProperty("index", DeterministicHexSequenceWithTimestamp - .nextValueString(props.getProperty("index"), 0)); + props.setProperty("index", nextLocalIndex); + saveProperties(filePath, props); + + final var messageResponse = nextLocalIndex + ":" + + setExpiration(Instant.ofEpochMilli(timestamp)).toEpochMilli(); + final var encryptedDataResponse = KeyUtils.encryptDataWithAESGCM(messageResponse, + KeyUtils.generateSharedSecret( + KeyUtils.stringToPrivateKey(props.getProperty("receiver-private-key")), + KeyUtils.stringToPublicKey(props.getProperty("sender-public-key")))); + + final var encryptedResponse = Base64.getUrlEncoder() + .encodeToString(encryptedDataResponse); + final var signatureResponse = KeyUtils.signMessage(messageResponse, + KeyUtils.stringToPrivateKey(props.getProperty("receiver-private-key"))); + return encryptedResponse + "." + signatureResponse; } + } } } @@ -108,4 +136,21 @@ public class LicenseRest { } } + public Instant setExpiration(Instant now) { + + // Convert Instant to LocalDate in a specific time zone + ZoneId zoneId = ZoneId.systemDefault(); // Change if needed + LocalDate localDate = now.atZone(zoneId).toLocalDate(); + + // Set the time to 23:59:59.999999999 + LocalTime endOfDay = LocalTime.MAX; // Equivalent to 23:59:59.999999999 + ZonedDateTime endOfDayZoned = ZonedDateTime.of(localDate, endOfDay, ZoneOffset.UTC); + + System.out.println("Now: " + now); + System.out.println("End of Day: " + endOfDayZoned); + + // Convert back to Instant + return endOfDayZoned.toInstant(); + } + } diff --git a/license/src/main/resources/application.properties b/license/src/main/resources/application.properties index 93efb91..44d284e 100644 --- a/license/src/main/resources/application.properties +++ b/license/src/main/resources/application.properties @@ -4,5 +4,6 @@ server.ssl.key-store=classpath:server.jks server.ssl.key-store-password=test server.ssl.key-store-type=JKS server.ssl.key-alias=torsim-license-server +#server.ssl.key-alias=torsim-license-server-dummy license-folder=${user.home}/test/license/server \ No newline at end of file diff --git a/license/src/main/resources/server.jks b/license/src/main/resources/server.jks index e9ef8c4..a273e1a 100644 Binary files a/license/src/main/resources/server.jks and b/license/src/main/resources/server.jks differ