+ * If the public key is not already present, we + * fetch it from the server. Otherwise, the public + * key is loaded from the file. + *
+ * + * @param publicKeyFile the public key file + * @return the public key + * @see PublicKey for public key + */ + @SneakyThrows + private PublicKey fetchPublicKey(@NonNull File publicKeyFile) { + byte[] publicKey; + if (publicKeyFile.exists()) { // Public key exists, use it + publicKey = Files.readAllBytes(publicKeyFile.toPath()); + } else { + Request request = new Request.Builder() + .url(appUrl + PUBLIC_KEY_ENDPOINT) + .build(); // Build the GET request + @Cleanup Response response = httpClient.newCall(request).execute(); // Make the request + if (!response.isSuccessful()) { // Response wasn't successful + throw new IOException("Failed to download the public key, got response " + response.code()); + } + ResponseBody body = response.body(); // Get the response body + assert body != null; // We need a response body + publicKey = body.bytes(); // Read our public key + + // Write the response to the public key file + try (FileOutputStream outputStream = new FileOutputStream(publicKeyFile)) { + outputStream.write(publicKey); + } + } + return readPublicKey(publicKey); + } + + /** + * Get the unique hardware + * identifier of this machine. + * + * @return the hardware id + */ + @NonNull + private String getHardwareId() { + SystemInfo systemInfo = new SystemInfo(); + OperatingSystem operatingSystem = systemInfo.getOperatingSystem(); + HardwareAbstractionLayer hardwareAbstractionLayer = systemInfo.getHardware(); + CentralProcessor centralProcessor = hardwareAbstractionLayer.getProcessor(); + ComputerSystem computerSystem = hardwareAbstractionLayer.getComputerSystem(); + + // Retrieve necessary hardware information + String vendor = operatingSystem.getManufacturer(); + String processorSerialNumber = computerSystem.getSerialNumber(); + String uuid = computerSystem.getHardwareUUID(); + String processorIdentifier = centralProcessor.getProcessorIdentifier().getIdentifier(); + int processors = centralProcessor.getLogicalProcessorCount(); + + // Generate a unique hardware id using the retrieved information + return String.format("%08x", vendor.hashCode()) + "-" + + String.format("%08x", processorSerialNumber.hashCode()) + "-" + + String.format("%08x", uuid.hashCode()) + "-" + + String.format("%08x", processorIdentifier.hashCode()) + "-" + processors; + } + + /** + * Encrypt the given input. + * + * @param input the encrypted input + * @return the encrypted result + */ + @SneakyThrows @NonNull + private String encrypt(@NonNull String input) { + Cipher cipher = Cipher.getInstance(ALGORITHM); // Create our cipher + cipher.init(Cipher.ENCRYPT_MODE, publicKey); // Set our mode and public key + return Base64.getEncoder().encodeToString(cipher.doFinal(input.getBytes())); // Return our encrypted result + } + + /** + * The response of a license check. + * + * @see #check(String) + */ + @AllArgsConstructor @Getter @ToString + public static class LicenseResponse { + /** + * The status code of the response. + */ + private final long status; + + /** + * The error in the response, null if none. + */ + private String error; + + /** + * The description of the license, present if valid. + */ + private String description; + + /** + * The Discord snowflake of the license owner, present + * if valid and there is an owner. + */ + private long ownerSnowflake; + + /** + * The Discord name of the license owner, present + * if valid and there is an owner. + */ + private String ownerName; + + /** + * The plan for this license. + */ + @NonNull private String plan; + + /** + * The latest version of the product this license is for. + */ + @NonNull private String latestVersion; + + /** + * The optional expiration {@link Date} of the license. + */ + private Date expires; + + public LicenseResponse(long status, @NonNull String error) { + this.status = status; + this.error = error; + } + + /** + * Check if the license is valid. + * + * @return true if valid, otherwise false + */ + public boolean isValid() { + return status == 200; + } + + /** + * Check if the license is permanent. + * + * @return true if permanent, otherwise false + */ + public boolean isPermanent() { + return expires == null; + } + } +} \ No newline at end of file diff --git a/Example-Java/src/main/java/me/braydon/example/Main.java b/Example-Java/src/main/java/me/braydon/example/Main.java new file mode 100644 index 0000000..4f11dab --- /dev/null +++ b/Example-Java/src/main/java/me/braydon/example/Main.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023 Braydon (Rainnny). All rights reserved. + * + * For inquiries, please contact braydonrainnny@gmail.com + */ +package me.braydon.example; + +import java.io.File; + +/** + * @author Braydon + */ +public final class Main { + public static void main(String[] args) { + LicenseClient client = new LicenseClient("http://localhost:7500", "Example", new File("public.key")); // Create the client + LicenseClient.LicenseResponse response = client.check("XXXX-XXXX-XXXX-XXXX"); // Check our license + if (!response.isValid()) { // License isn't valid + System.err.println("Invalid license: " + response.getError()); + return; + } + // License is valid + System.out.println("License is valid!"); + if (response.getOwnerName() != null) { + System.out.println("Welcome " + response.getOwnerName() + "! Your plan is " + response.getPlan() + " and the latest version is " + response.getLatestVersion()); + } + if (response.getDescription() != null) { + System.out.println("Description: " + response.getDescription()); // License description + } + if (response.isPermanent()) { // License is permanent + System.out.println("Your license is permanent"); + } else { // License has an expiration date + System.out.printf("Your license will expire at: %s%n", response.getExpires().toInstant()); + } + } +} \ No newline at end of file