Proof of concept, now to figure out how it works!
This commit is contained in:
32
SentinelAuth/build.gradle
Normal file
32
SentinelAuth/build.gradle
Normal 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
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package me.trouper.sentinel.auth.data;
|
||||
|
||||
public record AuthRequest(String authCode, String clientHash) {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package me.trouper.sentinel.auth.data;
|
||||
|
||||
public record AuthResponse(byte[] jarData, String decryptionKey) {
|
||||
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
1
SentinelAuth/src/main/resources/application.properties
Normal file
1
SentinelAuth/src/main/resources/application.properties
Normal file
@@ -0,0 +1 @@
|
||||
spring.application.name=SentinelAuth
|
||||
Reference in New Issue
Block a user