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
LicenseResultcodes, not exceptions. Network failure is distinguished from an invalid verdict by code.
Installation (Maven)
Section titled “Installation (Maven)”<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.
Quickstart
Section titled “Quickstart”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) — idempotentLicenseResult 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.
Public API
Section titled “Public API”| 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 |
Verdict codes (ValidationCode)
Section titled “Verdict codes (ValidationCode)”- 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(.licbound 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.
Caching and offline fallback
Section titled “Caching and offline fallback”- A successful
validateresponse is trusted for the server-providedcache.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, withisStale()returningtrue. - With
cacheDirset, 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();Offline verification (.lic)
Section titled “Offline verification (.lic)”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_INVALIDEmbedding the public key (important)
Section titled “Embedding the public key (important)”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>