Update KeygenLicenseVerifier.java

This commit is contained in:
Anthony Stirling 2025-05-18 13:56:40 +01:00 committed by GitHub
parent a6b1389246
commit 1a4d18eb28
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -48,34 +48,46 @@ public class KeygenLicenseVerifier {
private static final ObjectMapper objectMapper = new ObjectMapper(); private static final ObjectMapper objectMapper = new ObjectMapper();
private final ApplicationProperties applicationProperties; private final ApplicationProperties applicationProperties;
// Floating license configuration // Shared HTTP client for connection pooling
private static final HttpClient httpClient = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.connectTimeout(java.time.Duration.ofSeconds(10))
.build();
// License metadata context class to avoid shared mutable state
private static class LicenseContext {
private boolean isFloatingLicense = false; private boolean isFloatingLicense = false;
private int maxMachines = 1; // Default to 1 if not specified private int maxMachines = 1; // Default to 1 if not specified
private boolean isEnterpriseLicense = false;
public LicenseContext() {}
}
public License verifyLicense(String licenseKeyOrCert) { public License verifyLicense(String licenseKeyOrCert) {
License license; License license;
LicenseContext context = new LicenseContext();
if (isCertificateLicense(licenseKeyOrCert)) { if (isCertificateLicense(licenseKeyOrCert)) {
log.info("Detected certificate-based license. Processing..."); log.info("Detected certificate-based license. Processing...");
boolean isValid = verifyCertificateLicense(licenseKeyOrCert); boolean isValid = verifyCertificateLicense(licenseKeyOrCert, context);
if (isValid) { if (isValid) {
license = isEnterpriseLicense ? License.ENTERPRISE : License.PRO; license = context.isEnterpriseLicense ? License.ENTERPRISE : License.PRO;
} else { } else {
license = License.NORMAL; license = License.NORMAL;
} }
} else if (isJWTLicense(licenseKeyOrCert)) { } else if (isJWTLicense(licenseKeyOrCert)) {
log.info("Detected JWT-style license key. Processing..."); log.info("Detected JWT-style license key. Processing...");
boolean isValid = verifyJWTLicense(licenseKeyOrCert); boolean isValid = verifyJWTLicense(licenseKeyOrCert, context);
if (isValid) { if (isValid) {
license = isEnterpriseLicense ? License.ENTERPRISE : License.PRO; license = context.isEnterpriseLicense ? License.ENTERPRISE : License.PRO;
} else { } else {
license = License.NORMAL; license = License.NORMAL;
} }
} else { } else {
log.info("Detected standard license key. Processing..."); log.info("Detected standard license key. Processing...");
boolean isValid = verifyStandardLicense(licenseKeyOrCert); boolean isValid = verifyStandardLicense(licenseKeyOrCert, context);
if (isValid) { if (isValid) {
license = isEnterpriseLicense ? License.ENTERPRISE : License.PRO; license = context.isEnterpriseLicense ? License.ENTERPRISE : License.PRO;
} else { } else {
license = License.NORMAL; license = License.NORMAL;
} }
@ -83,7 +95,7 @@ public class KeygenLicenseVerifier {
return license; return license;
} }
private boolean isEnterpriseLicense = false; // Removed instance field for isEnterpriseLicense, now using LicenseContext
private boolean isCertificateLicense(String license) { private boolean isCertificateLicense(String license) {
return license != null && license.trim().startsWith(CERT_PREFIX); return license != null && license.trim().startsWith(CERT_PREFIX);
@ -93,7 +105,7 @@ public class KeygenLicenseVerifier {
return license != null && license.trim().startsWith(JWT_PREFIX); return license != null && license.trim().startsWith(JWT_PREFIX);
} }
private boolean verifyCertificateLicense(String licenseFile) { private boolean verifyCertificateLicense(String licenseFile, LicenseContext context) {
try { try {
String encodedPayload = licenseFile; String encodedPayload = licenseFile;
// Remove the header // Remove the header
@ -148,7 +160,7 @@ public class KeygenLicenseVerifier {
} }
// Process the certificate data // Process the certificate data
boolean isValid = processCertificateData(decodedData); boolean isValid = processCertificateData(decodedData, context);
return isValid; return isValid;
} catch (Exception e) { } catch (Exception e) {
@ -191,7 +203,7 @@ public class KeygenLicenseVerifier {
} }
} }
private boolean processCertificateData(String certData) { private boolean processCertificateData(String certData, LicenseContext context) {
try { try {
JSONObject licenseData = new JSONObject(certData); JSONObject licenseData = new JSONObject(certData);
JSONObject metaObj = licenseData.optJSONObject("meta"); JSONObject metaObj = licenseData.optJSONObject("meta");
@ -233,15 +245,17 @@ public class KeygenLicenseVerifier {
if (attributesObj != null) { if (attributesObj != null) {
log.info("Found attributes in certificate data"); log.info("Found attributes in certificate data");
// Check for floating license
context.isFloatingLicense = attributesObj.optBoolean("floating", false);
context.maxMachines = attributesObj.optInt("maxMachines", 1);
// Extract metadata // Extract metadata
JSONObject metadataObj = attributesObj.optJSONObject("metadata"); JSONObject metadataObj = attributesObj.optJSONObject("metadata");
if (metadataObj != null) { if (metadataObj != null) {
int users = metadataObj.optInt("users", 0); int users = metadataObj.optInt("users", 1);
if (users > 0) {
applicationProperties.getPremium().setMaxUsers(users); applicationProperties.getPremium().setMaxUsers(users);
log.info("License allows for {} users", users); log.info("License allows for {} users", users);
} context.isEnterpriseLicense = metadataObj.optBoolean("isEnterprise", false);
isEnterpriseLicense = metadataObj.optBoolean("isEnterprise", false);
} }
// Check license status if available // Check license status if available
@ -261,7 +275,7 @@ public class KeygenLicenseVerifier {
} }
} }
private boolean verifyJWTLicense(String licenseKey) { private boolean verifyJWTLicense(String licenseKey, LicenseContext context) {
try { try {
log.info("Verifying ED25519_SIGN format license key"); log.info("Verifying ED25519_SIGN format license key");
@ -295,7 +309,7 @@ public class KeygenLicenseVerifier {
String payload = new String(payloadBytes); String payload = new String(payloadBytes);
// Process the license payload // Process the license payload
boolean isValid = processJWTLicensePayload(payload); boolean isValid = processJWTLicensePayload(payload, context);
return isValid; return isValid;
} catch (Exception e) { } catch (Exception e) {
@ -331,7 +345,7 @@ public class KeygenLicenseVerifier {
} }
} }
private boolean processJWTLicensePayload(String payload) { private boolean processJWTLicensePayload(String payload, LicenseContext context) {
try { try {
log.info("Processing license payload: {}", payload); log.info("Processing license payload: {}", payload);
@ -352,6 +366,13 @@ public class KeygenLicenseVerifier {
String licenseId = licenseObj.optString("id", "unknown"); String licenseId = licenseObj.optString("id", "unknown");
log.info("Processing license with ID: {}", licenseId); log.info("Processing license with ID: {}", licenseId);
// Check for floating license in license object
context.isFloatingLicense = licenseObj.optBoolean("floating", false);
context.maxMachines = licenseObj.optInt("maxMachines", 1);
if (context.isFloatingLicense) {
log.info("Detected floating license with max machines: {}", context.maxMachines);
}
// Check expiry date // Check expiry date
String expiryStr = licenseObj.optString("expiry", null); String expiryStr = licenseObj.optString("expiry", null);
if (expiryStr != null && !"null".equals(expiryStr)) { if (expiryStr != null && !"null".equals(expiryStr)) {
@ -387,9 +408,20 @@ public class KeygenLicenseVerifier {
String policyId = policyObj.optString("id", "unknown"); String policyId = policyObj.optString("id", "unknown");
log.info("License uses policy: {}", policyId); log.info("License uses policy: {}", policyId);
// Check for floating license in policy
boolean policyFloating = policyObj.optBoolean("floating", false);
int policyMaxMachines = policyObj.optInt("maxMachines", 1);
// Policy settings take precedence
if (policyFloating) {
context.isFloatingLicense = true;
context.maxMachines = policyMaxMachines;
log.info("Policy defines floating license with max machines: {}", context.maxMachines);
}
// Extract max users and isEnterprise from policy or metadata // Extract max users and isEnterprise from policy or metadata
int users = policyObj.optInt("users", 0); int users = policyObj.optInt("users", 1);
isEnterpriseLicense = policyObj.optBoolean("isEnterprise", false); context.isEnterpriseLicense = policyObj.optBoolean("isEnterprise", false);
if (users > 0) { if (users > 0) {
applicationProperties.getPremium().setMaxUsers(users); applicationProperties.getPremium().setMaxUsers(users);
@ -403,7 +435,7 @@ public class KeygenLicenseVerifier {
log.info("License allows for {} users (from metadata)", users); log.info("License allows for {} users (from metadata)", users);
// Check for isEnterprise flag in metadata // Check for isEnterprise flag in metadata
isEnterpriseLicense = metadata.optBoolean("isEnterprise", false); context.isEnterpriseLicense = metadata.optBoolean("isEnterprise", false);
} else { } else {
// Default value // Default value
applicationProperties.getPremium().setMaxUsers(1); applicationProperties.getPremium().setMaxUsers(1);
@ -419,13 +451,13 @@ public class KeygenLicenseVerifier {
} }
} }
private boolean verifyStandardLicense(String licenseKey) { private boolean verifyStandardLicense(String licenseKey, LicenseContext context) {
try { try {
log.info("Checking standard license key"); log.info("Checking standard license key");
String machineFingerprint = generateMachineFingerprint(); String machineFingerprint = generateMachineFingerprint();
// First, try to validate the license // First, try to validate the license
JsonNode validationResponse = validateLicense(licenseKey, machineFingerprint); JsonNode validationResponse = validateLicense(licenseKey, machineFingerprint, context);
if (validationResponse != null) { if (validationResponse != null) {
boolean isValid = validationResponse.path("meta").path("valid").asBoolean(); boolean isValid = validationResponse.path("meta").path("valid").asBoolean();
String licenseId = validationResponse.path("data").path("id").asText(); String licenseId = validationResponse.path("data").path("id").asText();
@ -439,10 +471,10 @@ public class KeygenLicenseVerifier {
"License not activated for this machine. Attempting to" "License not activated for this machine. Attempting to"
+ " activate..."); + " activate...");
boolean activated = boolean activated =
activateMachine(licenseKey, licenseId, machineFingerprint); activateMachine(licenseKey, licenseId, machineFingerprint, context);
if (activated) { if (activated) {
// Revalidate after activation // Revalidate after activation
validationResponse = validateLicense(licenseKey, machineFingerprint); validationResponse = validateLicense(licenseKey, machineFingerprint, context);
isValid = isValid =
validationResponse != null validationResponse != null
&& validationResponse && validationResponse
@ -462,9 +494,8 @@ public class KeygenLicenseVerifier {
} }
} }
private JsonNode validateLicense(String licenseKey, String machineFingerprint) private JsonNode validateLicense(String licenseKey, String machineFingerprint, LicenseContext context)
throws Exception { throws Exception {
HttpClient client = HttpClient.newHttpClient();
String requestBody = String requestBody =
String.format( String.format(
"{\"meta\":{\"key\":\"%s\",\"scope\":{\"fingerprint\":\"%s\"}}}", "{\"meta\":{\"key\":\"%s\",\"scope\":{\"fingerprint\":\"%s\"}}}",
@ -483,7 +514,7 @@ public class KeygenLicenseVerifier {
.POST(HttpRequest.BodyPublishers.ofString(requestBody)) .POST(HttpRequest.BodyPublishers.ofString(requestBody))
.build(); .build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
log.info("ValidateLicenseResponse body: {}", response.body()); log.info("ValidateLicenseResponse body: {}", response.body());
JsonNode jsonResponse = objectMapper.readTree(response.body()); JsonNode jsonResponse = objectMapper.readTree(response.body());
if (response.statusCode() == 200) { if (response.statusCode() == 200) {
@ -500,10 +531,11 @@ public class KeygenLicenseVerifier {
// Check if the license itself has floating attribute // Check if the license itself has floating attribute
JsonNode licenseAttrs = jsonResponse.path("data").path("attributes"); JsonNode licenseAttrs = jsonResponse.path("data").path("attributes");
if (!licenseAttrs.isMissingNode()) { if (!licenseAttrs.isMissingNode()) {
isFloatingLicense = licenseAttrs.path("floating").asBoolean(false); context.isFloatingLicense = licenseAttrs.path("floating").asBoolean(false);
maxMachines = licenseAttrs.path("maxMachines").asInt(1); context.maxMachines = licenseAttrs.path("maxMachines").asInt(1);
log.info("License floating (from license): {}, maxMachines: {}", isFloatingLicense, maxMachines); log.info("License floating (from license): {}, maxMachines: {}",
context.isFloatingLicense, context.maxMachines);
} }
// Also check the policy for floating license support if included // Also check the policy for floating license support if included
@ -526,25 +558,26 @@ public class KeygenLicenseVerifier {
// Policy takes precedence over license attributes // Policy takes precedence over license attributes
if (policyFloating) { if (policyFloating) {
isFloatingLicense = true; context.isFloatingLicense = true;
maxMachines = policyMaxMachines; context.maxMachines = policyMaxMachines;
} }
log.info("License floating (from policy): {}, maxMachines: {}", isFloatingLicense, maxMachines); log.info("License floating (from policy): {}, maxMachines: {}",
context.isFloatingLicense, context.maxMachines);
} }
// Extract user count // Extract user count, default to 1 if not specified
int users = int users =
jsonResponse jsonResponse
.path("data") .path("data")
.path("attributes") .path("attributes")
.path("metadata") .path("metadata")
.path("users") .path("users")
.asInt(0); .asInt(1);
applicationProperties.getPremium().setMaxUsers(users); applicationProperties.getPremium().setMaxUsers(users);
// Extract isEnterprise flag // Extract isEnterprise flag
isEnterpriseLicense = context.isEnterpriseLicense =
jsonResponse jsonResponse
.path("data") .path("data")
.path("attributes") .path("attributes")
@ -560,11 +593,11 @@ public class KeygenLicenseVerifier {
return jsonResponse; return jsonResponse;
} }
private boolean activateMachine(String licenseKey, String licenseId, String machineFingerprint) private boolean activateMachine(String licenseKey, String licenseId, String machineFingerprint,
throws Exception { LicenseContext context) throws Exception {
// For floating licenses, we first need to check if we need to deregister any machines // For floating licenses, we first need to check if we need to deregister any machines
if (isFloatingLicense) { if (context.isFloatingLicense) {
log.info("Processing floating license activation. Max machines allowed: {}", maxMachines); log.info("Processing floating license activation. Max machines allowed: {}", context.maxMachines);
// Get the current machines for this license // Get the current machines for this license
JsonNode machinesResponse = fetchMachinesForLicense(licenseKey, licenseId); JsonNode machinesResponse = fetchMachinesForLicense(licenseKey, licenseId);
@ -572,7 +605,7 @@ public class KeygenLicenseVerifier {
JsonNode machines = machinesResponse.path("data"); JsonNode machines = machinesResponse.path("data");
int currentMachines = machines.size(); int currentMachines = machines.size();
log.info("Current machine count: {}, Max allowed: {}", currentMachines, maxMachines); log.info("Current machine count: {}, Max allowed: {}", currentMachines, context.maxMachines);
// Check if the current fingerprint is already activated // Check if the current fingerprint is already activated
boolean isCurrentMachineActivated = false; boolean isCurrentMachineActivated = false;
@ -594,7 +627,7 @@ public class KeygenLicenseVerifier {
} }
// If we've reached the max machines limit, we need to deregister the oldest machine // If we've reached the max machines limit, we need to deregister the oldest machine
if (currentMachines >= maxMachines) { if (currentMachines >= context.maxMachines) {
log.info("Max machines reached. Deregistering oldest machine to make room for the new machine."); log.info("Max machines reached. Deregistering oldest machine to make room for the new machine.");
// Find the oldest machine based on creation timestamp // Find the oldest machine based on creation timestamp
@ -641,8 +674,6 @@ public class KeygenLicenseVerifier {
} }
// Proceed with machine activation // Proceed with machine activation
HttpClient client = HttpClient.newHttpClient();
String hostname; String hostname;
try { try {
hostname = java.net.InetAddress.getLocalHost().getHostName(); hostname = java.net.InetAddress.getLocalHost().getHostName();
@ -689,7 +720,7 @@ public class KeygenLicenseVerifier {
.POST(HttpRequest.BodyPublishers.ofString(body.toString())) .POST(HttpRequest.BodyPublishers.ofString(body.toString()))
.build(); .build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
log.info("activateMachine Response body: " + response.body()); log.info("activateMachine Response body: " + response.body());
if (response.statusCode() == 201) { if (response.statusCode() == 201) {
log.info("Machine activated successfully"); log.info("Machine activated successfully");
@ -705,7 +736,7 @@ public class KeygenLicenseVerifier {
} }
private String generateMachineFingerprint() { private String generateMachineFingerprint() {
return GeneralUtils.generateMachineFingerprint() + "2"; return GeneralUtils.generateMachineFingerprint();
} }
/** /**
@ -717,8 +748,6 @@ public class KeygenLicenseVerifier {
* @throws Exception if an error occurs during the HTTP request * @throws Exception if an error occurs during the HTTP request
*/ */
private JsonNode fetchMachinesForLicense(String licenseKey, String licenseId) throws Exception { private JsonNode fetchMachinesForLicense(String licenseKey, String licenseId) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder() HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + "/" + ACCOUNT_ID + "/licenses/" + licenseId + "/machines")) .uri(URI.create(BASE_URL + "/" + ACCOUNT_ID + "/licenses/" + licenseId + "/machines"))
.header("Content-Type", "application/vnd.api+json") .header("Content-Type", "application/vnd.api+json")
@ -727,7 +756,7 @@ public class KeygenLicenseVerifier {
.GET() .GET()
.build(); .build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
log.info("fetchMachinesForLicense Response body: {}", response.body()); log.info("fetchMachinesForLicense Response body: {}", response.body());
if (response.statusCode() == 200) { if (response.statusCode() == 200) {
@ -748,8 +777,6 @@ public class KeygenLicenseVerifier {
*/ */
private boolean deregisterMachine(String licenseKey, String machineId) { private boolean deregisterMachine(String licenseKey, String machineId) {
try { try {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder() HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + "/" + ACCOUNT_ID + "/machines/" + machineId)) .uri(URI.create(BASE_URL + "/" + ACCOUNT_ID + "/machines/" + machineId))
.header("Content-Type", "application/vnd.api+json") .header("Content-Type", "application/vnd.api+json")
@ -758,7 +785,7 @@ public class KeygenLicenseVerifier {
.DELETE() .DELETE()
.build(); .build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 204) { if (response.statusCode() == 204) {
log.info("Machine {} successfully deregistered", machineId); log.info("Machine {} successfully deregistered", machineId);