Skip to content

Java SDK

kr.signox:signox-sdk — the official SDK for Java (Spring servers, desktop apps). Works on Java 8+.

  • Every server response is trusted only after its Ed25519 signature is verified with the product public key (tampering/replay defense).
  • The hardware ID (hwid) is generated internally by the SDK — callers never deal with it.
  • Verdicts are delivered as LicenseResult codes, not exceptions. Network failure is distinguished from an invalid verdict by code.
<dependency>
<groupId>kr.signox</groupId>
<artifactId>signox-sdk</artifactId>
<version>0.1.0</version>
</dependency>

The only runtime dependency is BouncyCastle bcprov-jdk18on (MIT, zero transitive dependencies). It is required on Java 8–14, which lack Ed25519 in the JDK, and the same code path is used on JDK 15+ as well.

import kr.signox.sdk.*;
// Embed the product public key (SPKI PEM) in your code (see "Embedding the public key" below)
String productPublicKey =
"-----BEGIN PUBLIC KEY-----\n....\n-----END PUBLIC KEY-----\n";
SignoxClient client = new SignoxClient(
SignoxClient.builder(productPublicKey)
.baseUrl("https://api.signox.kr") // default
.timeoutMs(10_000) // default
.build());
// 1) Activate the device (once) — idempotent
LicenseResult activated = client.activate("S4K2MB-7IWQ3F-...",
ActivateOptions.builder().name("John's laptop").build());
if (!activated.isValid()) {
System.out.println("Activation failed: " + activated.getCode());
}
// 2) Validate the license (on app start / periodically)
LicenseResult r = client.validate("S4K2MB-7IWQ3F-...");
if (r.isValid()) {
boolean pro = r.getFeatureBool("pro_export", false);
long seats = r.getFeatureInt("max_seats", 1);
// ... enable features
} else if (r.getCode() == ValidationCode.NETWORK_ERROR) {
// Network problem — not an invalid verdict. Retry or apply grace handling
} else {
// EXPIRED / REVOKED / DEVICE_NOT_ACTIVATED ...
}

A SignoxClient instance is immutable and thread-safe — create one and share it app-wide.

Method Purpose
validate(licenseKey) Validate license + device (includes cache/offline fallback)
activate(licenseKey[, opts]) Activate this device (hwid/anomalies attached automatically)
deactivate(licenseKey) Deactivate (returns the slot)
heartbeat(licenseKey) Liveness signal + lightweight re-validation
reportUsage(licenseKey, feature[, value]) Report metered usage
requestOfflineFile(licenseKey) Issue a .lic file (content via getFileContent())
validateOffline(licContent) Fully local .lic verification (no network)
getHwid() Diagnostic — this device’s hwid
  • State: VALID, IN_GRACE_PERIOD, EXPIRED, SUSPENDED, REVOKED
  • Request/device: NOT_FOUND, PRODUCT_INACTIVE, HWID_REQUIRED, DEVICE_NOT_ACTIVATED, DEVICE_LIMIT_REACHED, VM_NOT_ALLOWED, FEATURE_NOT_FOUND, USAGE_LIMIT_REACHED
  • SDK-local: SIGNATURE_INVALID (signature verification failed), NONCE_MISMATCH (suspected replay), OFFLINE_FILE_INVALID (malformed .lic), HWID_MISMATCH (.lic bound to another device), NETWORK_ERROR (network failure + no valid cache — distinct from an invalid verdict)

Only VALID and IN_GRACE_PERIOD make isValid() return true. See the online activation guide for the recommended UX per code.

  • A successful validate response is trusted for the server-provided cache.ttl, skipping re-validation (naturally converging under the rate limit).
  • The cache stores only the signed raw response and re-verifies the signature on every read (tamper defense).
  • On network failure, a valid cache within the offline grace window (offlineGraceDays, default 7 days counted from when the cache was stored, 0 = no fallback) is used as the verdict, with isStale() returning true.
  • With cacheDir set, the cache is also written to disk and survives process restarts. In-memory only otherwise.
SignoxClient.builder(productPublicKey)
.cacheDir(new File(System.getProperty("user.home"), ".myapp/signox-cache"))
.offlineGraceDays(14) // 14-day offline grace
.build();

Machines without internet access use a .lic file issued from the portal/dashboard or from an active device, verified fully locally. See offline activation for the full flow.

// Issue (online, once)
LicenseResult issued = client.requestOfflineFile("S4K2MB-7IWQ3F-...");
if (issued.getFileContent() != null) {
Files.write(Paths.get("license.lic"),
issued.getFileContent().getBytes(StandardCharsets.UTF_8));
}
// Verify (offline, repeatedly)
String lic = new String(Files.readAllBytes(Paths.get("license.lic")), StandardCharsets.UTF_8);
LicenseResult r = client.validateOffline(lic);
// VALID / IN_GRACE_PERIOD / EXPIRED / HWID_MISMATCH / SIGNATURE_INVALID / OFFLINE_FILE_INVALID

Always embed the product public key in your application. The SDK deliberately provides no public-key lookup endpoint — fetching the key at runtime would open a forgery vector where a man-in-the-middle swaps in their own key. Never put the private key in a client (signing is server-only).

BouncyCastle classpath conflicts (bcprov-jdk15on)

Section titled “BouncyCastle classpath conflicts (bcprov-jdk15on)”

If your app uses the legacy bcprov-jdk15on, classes may conflict. Either unify on the current bcprov-jdk18on, or as a last resort exclude the SDK’s transitive dependency and provide your own BC (1.70+ with Ed25519 support).

<dependency>
<groupId>kr.signox</groupId>
<artifactId>signox-sdk</artifactId>
<version>0.1.0</version>
<exclusions>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
</exclusion>
</exclusions>
</dependency>