diff --git a/jetbra-dist/package.xml b/jetbra-dist/package.xml
index a37e7ff..8dd7eef 100644
--- a/jetbra-dist/package.xml
+++ b/jetbra-dist/package.xml
@@ -41,9 +41,5 @@
${project.parent.basedir}/jetbra-agent/target/jetbra-agent.jar
jetbra-agent.jar
-
- ${project.parent.basedir}/readme.md
- readme.md
-
diff --git a/jetbra-server/pom.xml b/jetbra-server/pom.xml
index a72ac5f..dd2f83d 100644
--- a/jetbra-server/pom.xml
+++ b/jetbra-server/pom.xml
@@ -27,12 +27,31 @@
org.springframework.boot
spring-boot-starter-web
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
org.springframework.boot
spring-boot-starter-test
test
+
+ org.bouncycastle
+ bcpkix-jdk18on
+ 1.72
+
+
+ org.bouncycastle
+ bcprov-jdk18on
+ 1.72
+
+
+ org.projectlombok
+ lombok
+ true
+
@@ -40,8 +59,17 @@
org.springframework.boot
spring-boot-maven-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
\ No newline at end of file
diff --git a/jetbra-server/src/main/java/win/novice/li/controller/LicenseController.java b/jetbra-server/src/main/java/win/novice/li/controller/LicenseController.java
new file mode 100644
index 0000000..3c637be
--- /dev/null
+++ b/jetbra-server/src/main/java/win/novice/li/controller/LicenseController.java
@@ -0,0 +1,92 @@
+package win.novice.li.controller;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.SneakyThrows;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openssl.PEMKeyPair;
+import org.bouncycastle.openssl.PEMParser;
+import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+import win.novice.li.model.License;
+
+import java.io.FileInputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.security.*;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+
+@RestController
+public class LicenseController {
+ private static final PrivateKey PRIVATE_KEY = getPrivateKey();
+ private static final X509Certificate CRT = getCertificate();
+
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+
+ @PostMapping("/generateLicense")
+ @SneakyThrows
+ public Map generateLicense(@RequestBody @Validated License license) {
+ Map ans = new HashMap<>();
+
+ String licenseId = generateLicenseId();
+ license.setLicenseId(licenseId);
+
+ String licensePart = MAPPER.writeValueAsString(license);
+ byte[] licensePartBytes = licensePart.getBytes(StandardCharsets.UTF_8);
+ String licensePartBase64 = Base64.getEncoder().encodeToString(licensePartBytes);
+
+
+ Signature signature = Signature.getInstance("SHA1withRSA");
+ signature.initSign(PRIVATE_KEY);
+ signature.update(licensePartBytes);
+ byte[] signatureBytes = signature.sign();
+ String sigResultsBase64 = Base64.getEncoder().encodeToString(signatureBytes);
+
+ String result = licenseId + "-" + licensePartBase64 + "-" + sigResultsBase64 + "-" + Base64.getEncoder().encodeToString(CRT.getEncoded());
+
+ ans.put("license",result);
+ return ans;
+ }
+
+
+ private static final String ALLOWED_CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+ private static final SecureRandom random = new SecureRandom();
+
+ public static String generateLicenseId() {
+ int licenseLength = 10;
+ StringBuilder sb = new StringBuilder(licenseLength);
+ for (int i = 0; i < licenseLength; i++) {
+ int randomIndex = random.nextInt(ALLOWED_CHARACTERS.length());
+ char randomChar = ALLOWED_CHARACTERS.charAt(randomIndex);
+ sb.append(randomChar);
+ }
+ return sb.toString();
+ }
+
+
+ @SneakyThrows
+ static PrivateKey getPrivateKey() {
+ ClassPathResource licenseKeyResource = new ClassPathResource("jetbra.key");
+ Security.addProvider(new BouncyCastleProvider());
+ PEMParser pemParser = new PEMParser(new InputStreamReader(licenseKeyResource.getInputStream()));
+ JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
+ Object object = pemParser.readObject();
+ KeyPair kp = converter.getKeyPair((PEMKeyPair) object);
+ return kp.getPrivate();
+ }
+
+ @SneakyThrows
+ static X509Certificate getCertificate() {
+ CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
+ ClassPathResource crtResource = new ClassPathResource("jetbra.crt");
+ return (X509Certificate) certificateFactory.generateCertificate(crtResource.getInputStream());
+ }
+}
+
diff --git a/jetbra-server/src/main/java/win/novice/li/model/License.java b/jetbra-server/src/main/java/win/novice/li/model/License.java
new file mode 100644
index 0000000..5cf6224
--- /dev/null
+++ b/jetbra-server/src/main/java/win/novice/li/model/License.java
@@ -0,0 +1,40 @@
+package win.novice.li.model;
+
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class License {
+ private String licenseId;
+ @NotBlank
+ private String licenseeName = "Test";
+ @NotBlank
+ private String assigneeName = "novice.li";
+ @NotNull
+ private String assigneeEmail = "";
+ @NotNull
+ private String licenseRestriction = "";
+ @NotNull
+ private Boolean checkConcurrentUse = false;
+
+ @NotEmpty
+ private List<@Valid Product> products;
+ @NotBlank
+ private String metadata = "0120230102PPAA013009";
+ @NotBlank
+ private String hash = "41472961/0:1563609451";
+
+ @NotNull
+ @Min(1)
+ private Integer gracePeriodDays = 7;
+ @NotNull
+ private Boolean autoProlongated = true;
+ @NotNull
+ private Boolean isAutoProlongated = true;
+}
diff --git a/jetbra-server/src/main/java/win/novice/li/model/Product.java b/jetbra-server/src/main/java/win/novice/li/model/Product.java
new file mode 100644
index 0000000..417cdc8
--- /dev/null
+++ b/jetbra-server/src/main/java/win/novice/li/model/Product.java
@@ -0,0 +1,19 @@
+
+package win.novice.li.model;
+
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Data
+public class Product {
+ @NotBlank
+ private String code;
+ @NotBlank
+ private String fallbackDate = "2025-12-31";
+ @NotBlank
+ private String paidUpTo = "2025-12-31";
+ @NotNull
+ private Boolean extended = false;
+}
\ No newline at end of file
diff --git a/jetbra-server/src/main/resources/jetbra.crt b/jetbra-server/src/main/resources/jetbra.crt
new file mode 100644
index 0000000..bd113b7
--- /dev/null
+++ b/jetbra-server/src/main/resources/jetbra.crt
@@ -0,0 +1,28 @@
+-----BEGIN CERTIFICATE-----
+MIIEtTCCAp2gAwIBAgIUDyuccmylba71lZQAQic5TJiAhwwwDQYJKoZIhvcNAQEL
+BQAwGDEWMBQGA1UEAwwNSmV0UHJvZmlsZSBDQTAeFw0yMzA5MjkxNDA2MTJaFw0z
+MzA5MjcxNDA2MTJaMBExDzANBgNVBAMMBk5vdmljZTCCAiIwDQYJKoZIhvcNAQEB
+BQADggIPADCCAgoCggIBALenqcGP2ZxGkYqmKA9c4Hzf8+YD1smvmOxKjd+bmTLr
+utM/hXv1cj1rW3/lqyDtdDk7K6W8/TDq1CRrEt+Do6l30DxhAiC34aH8DmGwgq77
+xEoLimvH5LpePxflF+tbB1RZtFgFDOIYLdSQaKFH2JDgVKxhLiV3S6jniPhkCtWW
+rTs+E6vq4N15Bm3NnM5AJILqjtUbOjNfaxVq6RrOoTc0R3Fqqo6yvxo/+JYa2UnH
+IC+r2dbKuDLMUrtgnydEUdJNX0zH9FtcdELvr48uc9mY038TWUsZUK1pnQbxA2bP
+yA4qnYJ9IvUgO6LtLXvGFm137YQMS1N41AHDBOrwoNI8UoDX+qI3rM96biFOFvn7
+Edky7rByzybt3H+zxdojfjvpL1E0NO98BT9zfufHAaAxZtlmDOu5LDJe3CGurnyR
+MRExbtc+Qjl1mUh6tG4lakAwdsoxry0GdG72yaYyb9it53kaFks/T/s7Z7bRJzVF
+zQDV1Y4bzUtk43vKm2vztBVlQkBkZY5f2Jbe5Ig3b8swQzBnOT0mrL5SPUhwmQ6I
+xkEWztj55OEujBMmRr92oESuq9ZYMaeLidKWVR3/++HA8BRZaRGEKtSHZCbFEFdi
+hDxxJv9Xh6NuT/ewJ6HYp+0NQpFnUnJ72n8wV+tudpam7aKcdzVmz7cNwOhG2Ls7
+AgMBAAEwDQYJKoZIhvcNAQELBQADggIBAIdeaQfKni7tXtcywC3zJvGzaaj242pS
+WB1y40HW8jub0uHjTLsBPX27iA/5rb+rNXtUWX/f2K+DU4IgaIiiHhkDrMsw7piv
+azqwA9h7/uA0A5nepmTYf/HY4W6P2stbeqInNsFRZXS7Jg4Q5LgEtHKo/H8USjtV
+w9apmE3BCElkXRuelXMsSllpR/JEVv/8NPLmnHSY02q4KMVW2ozXtaAxSYQmZswy
+P1YnBcnRukoI4igobpcKQXwGoQCIUlec8LbFXYM9V2eNCwgABqd4r67m7QJq31Y/
+1TJysQdMH+hoPFy9rqNCxSq3ptpuzcYAk6qVf58PrrYH/6bHwiYPAayvvdzNPOhM
+9OCwomfcazhK3y7HyS8aBLntTQYFf7vYzZxPMDybYTvJM+ClCNnVD7Q9fttIJ6eM
+XFsXb8YK1uGNjQW8Y4WHk1MCHuD9ZumWu/CtAhBn6tllTQWwNMaPOQvKf1kr1Kt5
+etrONY+B6O+Oi75SZbDuGz7PIF9nMPy4WB/8XgKdVFtKJ7/zLIPHgY8IKgbx/VTz
+6uBhYo8wOf3xzzweMnn06UcfV3JGNvtMuV4vlkZNNxXeifsgzHugCvJX0nybhfBh
+fIqVyfK6t0eKJqrvp54XFEtJGR+lf3pBfTdcOI6QFEPKGZKoQz8Ck+BC/WBDtbjc
+/uYKczZ8DKZu
+-----END CERTIFICATE-----
diff --git a/jetbra-server/src/main/resources/public/index.html b/jetbra-server/src/main/resources/public/index.html
deleted file mode 100644
index 2fe743e..0000000
--- a/jetbra-server/src/main/resources/public/index.html
+++ /dev/null
@@ -1,3 +0,0 @@
-
-hello world
-
\ No newline at end of file
diff --git a/readme.md b/readme.md
index 09903f3..e29fc35 100644
--- a/readme.md
+++ b/readme.md
@@ -1 +1,2 @@
# Jetbra
+适用于jetbrains家族产品的一款工具,参照[热老的项目](https://jetbra.in/s)自己写了点代码 ,使用方式与之一至
\ No newline at end of file