You've already forked Arcturus-Morningstar-Extended
mirror of
https://github.com/duckietm/Arcturus-Morningstar-Extended.git
synced 2026-06-19 15:06:19 +00:00
Merge pull request #216 from simoleo89/fix/nitro-secure-api-safety
fix(auth): bound secure api payloads
This commit is contained in:
+19
-1
@@ -24,7 +24,9 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
public class NitroSecureApiHandler extends ChannelDuplexHandler {
|
public class NitroSecureApiHandler extends ChannelDuplexHandler {
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(NitroSecureApiHandler.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(NitroSecureApiHandler.class);
|
||||||
private static final String ENABLED_CONFIG = "nitro.secure.api.enabled";
|
private static final String ENABLED_CONFIG = "nitro.secure.api.enabled";
|
||||||
|
private static final String MAX_PAYLOAD_CONFIG = "nitro.secure.api.max_payload_bytes";
|
||||||
private static final String API_PREFIX = "/api/";
|
private static final String API_PREFIX = "/api/";
|
||||||
|
private static final int DEFAULT_MAX_PAYLOAD_BYTES = 64 * 1024;
|
||||||
private static final AttributeKey<Deque<SecureApiContext>> SECURE_CONTEXTS =
|
private static final AttributeKey<Deque<SecureApiContext>> SECURE_CONTEXTS =
|
||||||
AttributeKey.valueOf("nitroSecureApiContexts");
|
AttributeKey.valueOf("nitroSecureApiContexts");
|
||||||
private static final ConcurrentHashMap<String, Long> NONCE_CACHE = new ConcurrentHashMap<>();
|
private static final ConcurrentHashMap<String, Long> NONCE_CACHE = new ConcurrentHashMap<>();
|
||||||
@@ -81,7 +83,14 @@ public class NitroSecureApiHandler extends ChannelDuplexHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] encrypted = new byte[req.content().readableBytes()];
|
int readableBytes = req.content().readableBytes();
|
||||||
|
int maxPayloadBytes = maxPayloadBytes();
|
||||||
|
if (readableBytes > maxPayloadBytes) {
|
||||||
|
sendText(ctx, req, HttpResponseStatus.REQUEST_ENTITY_TOO_LARGE, "Secure payload too large.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] encrypted = new byte[readableBytes];
|
||||||
req.content().getBytes(req.content().readerIndex(), encrypted);
|
req.content().getBytes(req.content().readerIndex(), encrypted);
|
||||||
byte[] clear = NitroSecureAssetHandler.decrypt(sessionKey, NitroSecureAssetHandler.fromHex(new String(encrypted, StandardCharsets.UTF_8)));
|
byte[] clear = NitroSecureAssetHandler.decrypt(sessionKey, NitroSecureAssetHandler.fromHex(new String(encrypted, StandardCharsets.UTF_8)));
|
||||||
clear = unwrapEnvelope(clear, req, secureContext);
|
clear = unwrapEnvelope(clear, req, secureContext);
|
||||||
@@ -173,6 +182,15 @@ public class NitroSecureApiHandler extends ChannelDuplexHandler {
|
|||||||
return com.eu.habbo.Emulator.getConfig().getBoolean(ENABLED_CONFIG, true);
|
return com.eu.habbo.Emulator.getConfig().getBoolean(ENABLED_CONFIG, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int maxPayloadBytes() {
|
||||||
|
if (com.eu.habbo.Emulator.getConfig() == null) {
|
||||||
|
return DEFAULT_MAX_PAYLOAD_BYTES;
|
||||||
|
}
|
||||||
|
|
||||||
|
int configured = com.eu.habbo.Emulator.getConfig().getInt(MAX_PAYLOAD_CONFIG, DEFAULT_MAX_PAYLOAD_BYTES);
|
||||||
|
return configured > 0 ? configured : DEFAULT_MAX_PAYLOAD_BYTES;
|
||||||
|
}
|
||||||
|
|
||||||
private static byte[] unwrapEnvelope(byte[] clear, FullHttpRequest req, SecureApiContext secureContext) {
|
private static byte[] unwrapEnvelope(byte[] clear, FullHttpRequest req, SecureApiContext secureContext) {
|
||||||
if (!requiresReplayEnvelope(req.method())) return clear;
|
if (!requiresReplayEnvelope(req.method())) return clear;
|
||||||
|
|
||||||
|
|||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
package com.eu.habbo.networking.gameserver.auth;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class NitroSecureApiHandlerContractTest {
|
||||||
|
private static String handlerSource() throws Exception {
|
||||||
|
return Files.readString(Path.of("src/main/java/com/eu/habbo/networking/gameserver/auth/NitroSecureApiHandler.java"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String emulatorSource() throws Exception {
|
||||||
|
return Files.readString(Path.of("src/main/java/com/eu/habbo/Emulator.java"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void encryptedApiPayloadSizeIsBoundedBeforeCopyAndDecrypt() throws Exception {
|
||||||
|
String handler = handlerSource();
|
||||||
|
String emulator = emulatorSource();
|
||||||
|
|
||||||
|
int readableBytes = handler.indexOf("int readableBytes = req.content().readableBytes()");
|
||||||
|
int maxPayload = handler.indexOf("int maxPayloadBytes = maxPayloadBytes()", readableBytes);
|
||||||
|
int oversizedGuard = handler.indexOf("readableBytes > maxPayloadBytes", maxPayload);
|
||||||
|
int byteArray = handler.indexOf("new byte[readableBytes]", readableBytes);
|
||||||
|
int decrypt = handler.indexOf("NitroSecureAssetHandler.decrypt", byteArray);
|
||||||
|
|
||||||
|
assertTrue(handler.contains("DEFAULT_MAX_PAYLOAD_BYTES = 64 * 1024"),
|
||||||
|
"Secure API handler should have a conservative default payload cap");
|
||||||
|
assertTrue(handler.contains("nitro.secure.api.max_payload_bytes"),
|
||||||
|
"Secure API max payload should be configurable");
|
||||||
|
assertTrue(readableBytes > -1, "Secure API handler must read content size before allocation");
|
||||||
|
assertTrue(maxPayload > readableBytes, "Secure API handler must resolve max payload before allocation");
|
||||||
|
assertTrue(oversizedGuard > maxPayload, "Secure API handler must reject oversized encrypted payloads");
|
||||||
|
assertTrue(oversizedGuard < byteArray, "Oversized encrypted payloads must be rejected before byte array allocation");
|
||||||
|
assertTrue(byteArray < decrypt, "Secure API payload must be bounded before decrypting");
|
||||||
|
assertTrue(handler.contains("REQUEST_ENTITY_TOO_LARGE"),
|
||||||
|
"Secure API callers need a deterministic status for oversized encrypted payloads");
|
||||||
|
assertTrue(emulator.contains("register(\"nitro.secure.api.max_payload_bytes\", \"65536\")"),
|
||||||
|
"Secure API max payload default must be registered before startup");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user