From 34cb8a67303af7de9f58088bbbf3ffadeed62122 Mon Sep 17 00:00:00 2001 From: QrackyDev <217113176+QrackyDev@users.noreply.github.com> Date: Wed, 10 Jun 2026 12:43:40 +0200 Subject: [PATCH 1/2] refactor: Removed Ktor as a dependency and used the java.net one, and some other general improvements. --- build.gradle.kts | 1 - gradle/libs.versions.toml | 14 ------ src/main/kotlin/com/nmcrate/key/NMKey.kt | 58 ++++++++++++------------ src/test/resources/paper-plugin.yml | 2 +- 4 files changed, 31 insertions(+), 44 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index d19443e..2a2f466 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -29,7 +29,6 @@ repositories { @Suppress("VulnerableLibrariesLocal") dependencies { - implementation(libs.bundles.ktor) compileOnly(libs.paper.api) testImplementation(libs.kotlin.test) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4c52eed..84d08f9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,7 +11,6 @@ minecraft = "1.21.8" gdPublish = "1.0.0-SNAPSHOT" -ktor = "3.5.0" junit-jupiter = "6.0.1" mockbukkit = "1.13.0" maven-resolver-provider = "3.8.1" @@ -24,11 +23,6 @@ run-task = "3.0.2" [libraries] kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } -ktor-client = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } -ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" } -ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" } -ktor-serialization = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" } - paper-api = { module = "io.papermc.paper:paper-api", version.ref = "paper-api" } junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" } mockbukkit = { module = "com.github.seeseemelk:MockBukkit-v1.17", version.ref = "mockbukkit" } @@ -42,11 +36,3 @@ kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", vers shadow = { id = "com.gradleup.shadow", version.ref = "shadow" } run-paper = { id = "xyz.jpenilla.run-paper", version.ref = "run-task" } gdPublish = { id = "com.guavadealer.plugins.GDPublish", version.ref = "gdPublish" } - -[bundles] -ktor = [ - "ktor-client", - "ktor-client-cio", - "ktor-client-content-negotiation", - "ktor-serialization", -] diff --git a/src/main/kotlin/com/nmcrate/key/NMKey.kt b/src/main/kotlin/com/nmcrate/key/NMKey.kt index e6fc232..75c3211 100644 --- a/src/main/kotlin/com/nmcrate/key/NMKey.kt +++ b/src/main/kotlin/com/nmcrate/key/NMKey.kt @@ -1,19 +1,13 @@ package com.nmcrate.key -import io.ktor.client.* -import io.ktor.client.call.* -import io.ktor.client.engine.cio.* -import io.ktor.client.plugins.* -import io.ktor.client.plugins.contentnegotiation.* -import io.ktor.client.request.* -import io.ktor.http.* -import io.ktor.serialization.kotlinx.json.* +import kotlinx.coroutines.future.await import kotlinx.coroutines.runBlocking import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import org.bukkit.plugin.java.JavaPlugin import java.net.InetAddress import java.net.NetworkInterface +import java.net.URI import java.nio.charset.StandardCharsets import java.security.KeyFactory import java.security.MessageDigest @@ -21,6 +15,8 @@ import java.security.Signature import java.security.spec.X509EncodedKeySpec import java.util.* import java.util.concurrent.ConcurrentHashMap +import java.net.http.* +import java.time.Duration /** * Singleton utility for managing license key validation and hardware fingerprinting for NMCrate plugins. @@ -29,7 +25,7 @@ import java.util.concurrent.ConcurrentHashMap * sessions, and ensure the integrity of responses using Ed25519 digital signatures. It maintains an * internal cache for public keys and license strings to minimize network overhead and I/O operations. * - * @author Idan Nehama (GuavaDealer) + * @author Idan Nehama (GuavaDealer) and QrackyDev (Qracky) * @since 1.0.0 */ object NMKey { @@ -41,18 +37,15 @@ object NMKey { */ const val DEFAULT_API_URL = "https://www.nmcrate.com/api/nmkey/v1" + val requestTimeout = Duration.ofMillis(7500) + private val publicKeys = ConcurrentHashMap() private val cachedKeys = ConcurrentHashMap() private val json = Json { ignoreUnknownKeys = true } private val client by lazy { - HttpClient(CIO) { - install(ContentNegotiation) { json(json) } - install(HttpTimeout) { - connectTimeoutMillis = 3_000 - requestTimeoutMillis = 7_500 - socketTimeoutMillis = 7_500 - } - } + HttpClient.newBuilder() + .connectTimeout(Duration.ofMillis(3000)) + .build() } private val keyFactory = KeyFactory.getInstance("Ed25519") private val base64Decoder: Base64.Decoder = Base64.getDecoder() @@ -126,10 +119,14 @@ object NMKey { val nonce = UUID.randomUUID().toString() pl.logger.info("NMKey: sending validation request to $DEFAULT_API_URL/validate.") - val res = client.post("$DEFAULT_API_URL/validate") { - contentType(ContentType.Application.Json) - setBody(KeyRequest(pluginId, key, fp, nonce)) - }.body() + val body = json.encodeToString(KeyRequest(pluginId, key, fp, nonce)) + val request = HttpRequest.newBuilder() + .uri(URI("$DEFAULT_API_URL/validate")) + .POST(HttpRequest.BodyPublishers.ofString(body)) + .header("Content-Type", "application/json") + .build() + val responseBody = client.sendAsync(request, HttpResponse.BodyHandlers.ofString()).get().body() + val res = json.decodeFromString(responseBody) val canonical = "v1|$pluginId|$key|$fp|${res.status}|$nonce|${res.issuedAt}" val valid = res.status.equals("valid", ignoreCase = true) && @@ -169,10 +166,12 @@ object NMKey { runCatching { runBlocking { val key = readKey(pl) ?: return@runBlocking - client.post("$DEFAULT_API_URL/release") { - contentType(ContentType.Application.Json) - setBody(KeyRequest(pluginId, key, fingerprint(pl))) - } + val body = json.encodeToString(KeyRequest(pluginId, key, fingerprint(pl))) + val request = HttpRequest.newBuilder() + .uri(URI("$DEFAULT_API_URL/releases")) + .POST(HttpRequest.BodyPublishers.ofString(body)) + .build() + client.sendAsync(request, HttpResponse.BodyHandlers.discarding()) } } } @@ -232,9 +231,12 @@ object NMKey { } pl.logger.info("NMKey: fetching public key from $DEFAULT_API_URL/public-key.") - val fetched = client.get("$DEFAULT_API_URL/public-key") { parameter("pluginId", pluginId) } - .body() - .removePemPublicKeyHeaders() + val request = HttpRequest.newBuilder() + .uri(URI("$DEFAULT_API_URL/public-key?pluginId=\$encodedPluginId")) + .timeout(requestTimeout) + .GET() + .build() + val fetched = client.sendAsync(request, HttpResponse.BodyHandlers.ofString()).await().body().removePemPublicKeyHeaders() publicKeys[pluginId] = fetched pl.logger.info("NMKey: public key fetched and cached.") diff --git a/src/test/resources/paper-plugin.yml b/src/test/resources/paper-plugin.yml index 944b85f..35632a4 100644 --- a/src/test/resources/paper-plugin.yml +++ b/src/test/resources/paper-plugin.yml @@ -3,4 +3,4 @@ version: '${version}' main: com.nmcrate.key.tests.NMKeyTestPlugin api-version: '1.21.8' -load: POSTWORLD +load: STARTUP From 228bef5eeccbea681aac5f42eac4bd0123e737b8 Mon Sep 17 00:00:00 2001 From: QrackyDev <217113176+QrackyDev@users.noreply.github.com> Date: Thu, 11 Jun 2026 09:18:41 +0200 Subject: [PATCH 2/2] fix: Fixed library imports; fixed ensurePublicKey to use the URLEncoder and some general improvements. --- build.gradle.kts | 1 + gradle/libs.versions.toml | 6 ++++++ src/main/kotlin/com/nmcrate/key/NMKey.kt | 11 +++++++---- src/main/kotlin/com/nmcrate/key/NMKeyExtensions.kt | 1 + src/test/resources/nmkey.txt | 2 +- 5 files changed, 16 insertions(+), 5 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 2a2f466..4e2852f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -30,6 +30,7 @@ repositories { @Suppress("VulnerableLibrariesLocal") dependencies { compileOnly(libs.paper.api) + implementation(libs.bundles.kotlinx) testImplementation(libs.kotlin.test) testImplementation(libs.junit.jupiter) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 84d08f9..dc72e65 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,6 @@ [versions] kotlin = "2.4.0" +kotlinx = "1.11.0" paper-api = "1.17-R0.1-SNAPSHOT" paper-api-compat-v1-v17 = "1.17-R0.1-SNAPSHOT" paper-api-compat-v1_v18v2 = "1.18.2-R0.1-SNAPSHOT" @@ -22,6 +23,8 @@ run-task = "3.0.2" [libraries] kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } +kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx" } +kotlinx-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx" } paper-api = { module = "io.papermc.paper:paper-api", version.ref = "paper-api" } junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" } @@ -30,6 +33,9 @@ maven-resolver-provider = { module = "org.apache.maven:maven-resolver-provider", maven-resolver-connector-basic = { module = "org.apache.maven.resolver:maven-resolver-connector-basic", version.ref = "maven-resolver" } maven-resolver-transport-http = { module = "org.apache.maven.resolver:maven-resolver-transport-http", version.ref = "maven-resolver" } +[bundles] +kotlinx = ["kotlinx-coroutines", "kotlinx-serialization"] + [plugins] kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } diff --git a/src/main/kotlin/com/nmcrate/key/NMKey.kt b/src/main/kotlin/com/nmcrate/key/NMKey.kt index 75c3211..96c2587 100644 --- a/src/main/kotlin/com/nmcrate/key/NMKey.kt +++ b/src/main/kotlin/com/nmcrate/key/NMKey.kt @@ -8,6 +8,7 @@ import org.bukkit.plugin.java.JavaPlugin import java.net.InetAddress import java.net.NetworkInterface import java.net.URI +import java.net.URLEncoder import java.nio.charset.StandardCharsets import java.security.KeyFactory import java.security.MessageDigest @@ -25,7 +26,8 @@ import java.time.Duration * sessions, and ensure the integrity of responses using Ed25519 digital signatures. It maintains an * internal cache for public keys and license strings to minimize network overhead and I/O operations. * - * @author Idan Nehama (GuavaDealer) and QrackyDev (Qracky) + * @author Idan Nehama (GuavaDealer) + * @author QrackyDev (Qracky) * @since 1.0.0 */ object NMKey { @@ -37,7 +39,7 @@ object NMKey { */ const val DEFAULT_API_URL = "https://www.nmcrate.com/api/nmkey/v1" - val requestTimeout = Duration.ofMillis(7500) + val requestTimeout: Duration = Duration.ofMillis(7500) private val publicKeys = ConcurrentHashMap() private val cachedKeys = ConcurrentHashMap() @@ -231,8 +233,9 @@ object NMKey { } pl.logger.info("NMKey: fetching public key from $DEFAULT_API_URL/public-key.") + val encodedPluginId = URLEncoder.encode(pluginId, StandardCharsets.UTF_8) val request = HttpRequest.newBuilder() - .uri(URI("$DEFAULT_API_URL/public-key?pluginId=\$encodedPluginId")) + .uri(URI("$DEFAULT_API_URL/public-key?pluginId=$encodedPluginId")) .timeout(requestTimeout) .GET() .build() @@ -269,7 +272,7 @@ object NMKey { .digest(raw.toByteArray(StandardCharsets.UTF_8)) .joinToString("") { byte -> "%02x".format(byte) } .take(32) - } catch (e: Exception) { + } catch (_: Exception) { return "0" } } diff --git a/src/main/kotlin/com/nmcrate/key/NMKeyExtensions.kt b/src/main/kotlin/com/nmcrate/key/NMKeyExtensions.kt index 916bf3a..2967661 100644 --- a/src/main/kotlin/com/nmcrate/key/NMKeyExtensions.kt +++ b/src/main/kotlin/com/nmcrate/key/NMKeyExtensions.kt @@ -13,6 +13,7 @@ import org.bukkit.plugin.java.JavaPlugin * @param valid Whether the key validation was successful. * * @author Idan Nehama (GuavaDealer) + * @author QrackyDev (Qracky) * @since 1.0.0 */ class NMKeySession internal constructor( diff --git a/src/test/resources/nmkey.txt b/src/test/resources/nmkey.txt index dbfa212..bbcefb8 100644 --- a/src/test/resources/nmkey.txt +++ b/src/test/resources/nmkey.txt @@ -1 +1 @@ -NMK-LXROK2W3-2LUCI374-BVLKLO75 +NMK-LXROK2W3-2LUCI374-BVLKLO75 \ No newline at end of file