Proof of concept, now to figure out how it works!

This commit is contained in:
thetrouper
2025-03-25 06:15:10 -05:00
parent 8a755bd642
commit 96c758edd0
23 changed files with 608 additions and 18 deletions

32
SentinelAuth/build.gradle Normal file
View File

@@ -0,0 +1,32 @@
plugins {
id 'java'
id 'org.springframework.boot' version '3.4.4'
id 'io.spring.dependency-management' version '1.1.7'
}
group = 'me.trouper.sentinel'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
tasks.named('test') {
useJUnitPlatform()
}
bootJar {
mainClass = 'me.trouper.sentinel.auth.SentinelAuth' // Replace with your actual main class
}

View File

@@ -0,0 +1,44 @@
package me.trouper.sentinel.auth;
import me.trouper.sentinel.auth.data.AuthRequest;
import me.trouper.sentinel.auth.data.AuthResponse;
import me.trouper.sentinel.auth.services.JarService;
import me.trouper.sentinel.auth.services.LicenseService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
@RestController
public class AuthController {
private final LicenseService licenseService;
private final JarService jarService;
public AuthController(LicenseService ls, JarService js) {
this.licenseService = ls;
this.jarService = js;
}
@PostMapping("/authenticate")
public ResponseEntity<Map<String, String>> authenticate(@RequestBody AuthRequest request) {
for (String key : licenseService.getLicenseKeys()) {
String serverCode = LicenseService.generateAuthCode(key);
if (serverCode.equals(request.authCode())) {
Map<String, String> response = new HashMap<>();
response.put("decryptionKey", jarService.getDecryptionKey());
if (!jarService.getJarHash().equals(request.clientHash())) {
response.put("jarData",
Base64.getEncoder().encodeToString(jarService.getEncryptedJar()));
}
return ResponseEntity.ok(response);
}
}
return ResponseEntity.status(401).build();
}
}

View File

@@ -0,0 +1,13 @@
package me.trouper.sentinel.auth;
import me.trouper.sentinel.auth.services.JarService;
import me.trouper.sentinel.auth.services.LicenseService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SentinelAuth {
public static void main(String[] args) {
SpringApplication.run(SentinelAuth.class, args);
}
}

View File

@@ -0,0 +1,5 @@
package me.trouper.sentinel.auth.data;
public record AuthRequest(String authCode, String clientHash) {
}

View File

@@ -0,0 +1,5 @@
package me.trouper.sentinel.auth.data;
public record AuthResponse(byte[] jarData, String decryptionKey) {
}

View File

@@ -0,0 +1,59 @@
package me.trouper.sentinel.auth.services;
import jakarta.annotation.PostConstruct;
import me.trouper.sentinel.auth.utils.DigestUtils;
import org.springframework.stereotype.Service;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.util.Base64;
@Service
public class JarService {
private byte[] unencryptedJar;
private byte[] encryptedJar; // Store encrypted JAR
private String jarHash;
private String decryptionKey; // Will be initialized
private final Path unencryptedJarPath = Path.of("storage/SentinelPlugin-1.0-SNAPSHOT.jar");
@PostConstruct
public void init() throws IOException, GeneralSecurityException {
if (Files.exists(unencryptedJarPath)) {
this.unencryptedJar = Files.readAllBytes(unencryptedJarPath);
// Set encryption key and decrypt key
String encryptionKey = "AES_KEY_12345678";
this.decryptionKey = encryptionKey; // Initialize decryptionKey
// Encrypt once during startup
this.encryptedJar = encryptJar(unencryptedJar, encryptionKey);
this.jarHash = DigestUtils.getSHA256(encryptedJar);
} else {
throw new FileNotFoundException("Unencrypted plugin JAR not found at: " + unencryptedJarPath);
}
}
public byte[] getEncryptedJar() {
return encryptedJar;
}
private byte[] encryptJar(byte[] input, String encryptionKey) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKeySpec keySpec = new SecretKeySpec(encryptionKey.getBytes(StandardCharsets.UTF_8), "AES");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
return cipher.doFinal(input);
}
public String getJarHash() { return jarHash; }
public String getDecryptionKey() { return decryptionKey; }
}

View File

@@ -0,0 +1,41 @@
package me.trouper.sentinel.auth.services;
import org.springframework.stereotype.Service;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.List;
@Service
public class LicenseService {
private final List<String> licenseKeys = List.of("SECRET_KEY_12345"); // In real use, load from secure config
public List<String> getLicenseKeys() {
return licenseKeys;
}
public static String generateAuthCode(String licenseKey) {
try {
byte[] key = licenseKey.getBytes(StandardCharsets.UTF_8);
long counter = Instant.now().getEpochSecond() / 10;
byte[] counterBytes = ByteBuffer.allocate(8).putLong(counter).array();
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(new SecretKeySpec(key, "HmacSHA1"));
byte[] hmac = mac.doFinal(counterBytes);
int offset = hmac[hmac.length - 1] & 0xF;
int binary = ((hmac[offset] & 0x7F) << 24)
| ((hmac[offset + 1] & 0xFF) << 16)
| ((hmac[offset + 2] & 0xFF) << 8)
| (hmac[offset + 3] & 0xFF);
return String.format("%06d", binary % 1_000_000);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,29 @@
package me.trouper.sentinel.auth.utils;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class DigestUtils {
public static String getSHA256(byte[] bytes) {
MessageDigest md = null;
try {
md = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
byte[] digestBytes = md.digest(bytes);
return bytesToHex(digestBytes);
}
private static String bytesToHex(byte[] bytes) {
StringBuilder hexString = new StringBuilder();
for (byte b : bytes) {
String hex = String.format("%02x", b);
hexString.append(hex);
}
return hexString.toString();
}
}

View File

@@ -0,0 +1 @@
spring.application.name=SentinelAuth