oops x.x
This commit is contained in:
parent
8d97e0efa4
commit
16968427b3
3
Example-Java/README.md
Normal file
3
Example-Java/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Example
|
||||
|
||||
This is the example of how to interact with the license server from within Java.
|
79
Example-Java/pom.xml
Normal file
79
Example-Java/pom.xml
Normal file
@ -0,0 +1,79 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>me.braydon</groupId>
|
||||
<artifactId>Example</artifactId>
|
||||
<version>1.0.0</version>
|
||||
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>${java.version}</source>
|
||||
<target>${java.version}</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<!-- Lombok -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.30</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Gson -->
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.10.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Oshi -->
|
||||
<dependency>
|
||||
<groupId>com.github.oshi</groupId>
|
||||
<artifactId>oshi-core</artifactId>
|
||||
<version>6.4.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- OkHttp -->
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
<version>4.11.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
337
Example-Java/src/main/java/me/braydon/example/LicenseClient.java
Normal file
337
Example-Java/src/main/java/me/braydon/example/LicenseClient.java
Normal file
@ -0,0 +1,337 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Braydon (Rainnny). All rights reserved.
|
||||
*
|
||||
* For inquiries, please contact braydonrainnny@gmail.com
|
||||
*/
|
||||
package me.braydon.example;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import lombok.*;
|
||||
import okhttp3.*;
|
||||
import oshi.SystemInfo;
|
||||
import oshi.hardware.CentralProcessor;
|
||||
import oshi.hardware.ComputerSystem;
|
||||
import oshi.hardware.HardwareAbstractionLayer;
|
||||
import oshi.software.os.OperatingSystem;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An example of how to interact
|
||||
* with the license server. This
|
||||
* can be conveniently used in
|
||||
* any project by simply copying
|
||||
* the class into your project.
|
||||
*
|
||||
* @author Braydon
|
||||
* @see <a href="https://git.rainnny.club/Rainnny/LicenseServer">License Server</a>
|
||||
*/
|
||||
public final class LicenseClient {
|
||||
private static final String ALGORITHM = "RSA"; // The crypto algorithm to use
|
||||
|
||||
/**
|
||||
* The endpoint to use for downloading the {@link PublicKey}.
|
||||
*/
|
||||
private static final String PUBLIC_KEY_ENDPOINT = "/crypto/pub";
|
||||
|
||||
/**
|
||||
* The endpoint to check licenses at.
|
||||
*/
|
||||
private static final String CHECK_ENDPOINT = "/check";
|
||||
|
||||
/**
|
||||
* The {@link Gson} instance to use.
|
||||
*/
|
||||
private static final Gson GSON = new GsonBuilder().serializeNulls().create();
|
||||
|
||||
/**
|
||||
* The URL of the license server to make requests to.
|
||||
*/
|
||||
@NonNull private final String appUrl;
|
||||
|
||||
/**
|
||||
* The product to use for client.
|
||||
*/
|
||||
@NonNull private final String product;
|
||||
|
||||
/**
|
||||
* The {@link OkHttpClient} to use for requests.
|
||||
*/
|
||||
@NonNull private final OkHttpClient httpClient;
|
||||
|
||||
/**
|
||||
* The {@link PublicKey} to use for encryption.
|
||||
*/
|
||||
@NonNull private final PublicKey publicKey;
|
||||
|
||||
public LicenseClient(@NonNull String appUrl, @NonNull String product, @NonNull File publicKeyFile) {
|
||||
this.appUrl = appUrl;
|
||||
this.product = product;
|
||||
httpClient = new OkHttpClient(); // Create a new http client
|
||||
publicKey = fetchPublicKey(publicKeyFile); // Fetch our public key
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the public key from the given bytes.
|
||||
*
|
||||
* @param bytes the bytes of the public key
|
||||
* @return the public key
|
||||
* @see PrivateKey for public key
|
||||
*/
|
||||
@SneakyThrows
|
||||
private static PublicKey readPublicKey(byte[] bytes) {
|
||||
return KeyFactory.getInstance(ALGORITHM).generatePublic(new X509EncodedKeySpec(bytes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the license with the given
|
||||
* key for the given product.
|
||||
*
|
||||
* @param key the key to check
|
||||
* @return the license response
|
||||
* @see LicenseResponse for response
|
||||
*/
|
||||
@NonNull
|
||||
public LicenseResponse check(@NonNull String key) {
|
||||
String hardwareId = getHardwareId(); // Get the hardware id of the machine
|
||||
|
||||
// Build the json body
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
body.put("key", encrypt(key));
|
||||
body.put("product", product);
|
||||
body.put("hwid", encrypt(hardwareId));
|
||||
String bodyJson = GSON.toJson(body); // The json body
|
||||
|
||||
MediaType mediaType = MediaType.parse("application/json"); // Ensure the media type is json
|
||||
RequestBody requestBody = RequestBody.create(mediaType, bodyJson); // Build the request body
|
||||
Request request = new Request.Builder()
|
||||
.url(appUrl + CHECK_ENDPOINT)
|
||||
.post(requestBody)
|
||||
.build(); // Build the POST request
|
||||
|
||||
Response response = null; // The response of the request
|
||||
int responseCode = -1; // The response code of the request
|
||||
try { // Attempt to execute the request
|
||||
response = httpClient.newCall(request).execute();
|
||||
responseCode = response.code();
|
||||
|
||||
// If the response is successful, we can parse the response
|
||||
if (response.isSuccessful()) {
|
||||
ResponseBody responseBody = response.body();
|
||||
assert responseBody != null; // We don't want the response body being null
|
||||
|
||||
JsonObject json = GSON.fromJson(responseBody.string(), JsonObject.class); // Parse the json
|
||||
JsonElement description = json.get("description");
|
||||
JsonElement ownerSnowflake = json.get("ownerSnowflake");
|
||||
JsonElement ownerName = json.get("ownerName");
|
||||
JsonElement plan = json.get("plan");
|
||||
JsonElement latestVersion = json.get("latestVersion");
|
||||
|
||||
// Parsing the expiration date if we have one
|
||||
JsonElement expires = json.get("expires");
|
||||
Date expiresDate = null;
|
||||
if (!expires.isJsonNull()) {
|
||||
OffsetDateTime offsetDateTime = OffsetDateTime.parse(expires.getAsString(), DateTimeFormatter.ISO_OFFSET_DATE_TIME);
|
||||
expiresDate = Date.from(offsetDateTime.toInstant());
|
||||
}
|
||||
|
||||
// Return the license response
|
||||
return new LicenseResponse(200, null,
|
||||
description.isJsonNull() ? null : description.getAsString(),
|
||||
ownerSnowflake.isJsonNull() ? -1 : ownerSnowflake.getAsLong(),
|
||||
ownerName.isJsonNull() ? null : ownerName.getAsString(),
|
||||
plan.getAsString(),
|
||||
latestVersion.getAsString(),
|
||||
expires.isJsonNull() ? null : expiresDate
|
||||
);
|
||||
} else {
|
||||
ResponseBody errorBody = response.body(); // Get the error body
|
||||
if (errorBody != null) { // If we have an error body, we can parse it
|
||||
String errorResponse = errorBody.string();
|
||||
JsonObject jsonError = GSON.fromJson(errorResponse, JsonObject.class);
|
||||
JsonElement errorMessage = jsonError.get("error");
|
||||
if (!errorMessage.isJsonNull()) { // We have an error message, return it
|
||||
return new LicenseResponse(responseCode, errorMessage.getAsString());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
} finally {
|
||||
// Close the response if it's open
|
||||
if (response != null) {
|
||||
response.close();
|
||||
}
|
||||
}
|
||||
// Return an unknown error
|
||||
return new LicenseResponse(responseCode, "An unknown error occurred");
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the public key.
|
||||
* <p>
|
||||
* If the public key is not already present, we
|
||||
* fetch it from the server. Otherwise, the public
|
||||
* key is loaded from the file.
|
||||
* </p>
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
}
|
35
Example-Java/src/main/java/me/braydon/example/Main.java
Normal file
35
Example-Java/src/main/java/me/braydon/example/Main.java
Normal file
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user