commit b1c4ebb87c6074d93e32b9a7c038d567c4f052e7 Author: HuangQing Date: Fri Oct 4 17:10:46 2024 +0800 init diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..b2a15e4 Binary files /dev/null and b/.DS_Store differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a1c2a23 --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..35410ca --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..37f1f40 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..dca7df3 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..712ab9d --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/jrebel-license-server-for-java.iml b/.idea/jrebel-license-server-for-java.iml new file mode 100644 index 0000000..d6ebd48 --- /dev/null +++ b/.idea/jrebel-license-server-for-java.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..5142f5d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8bd064a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,7 @@ +FROM java:8-jre-alpine + +ENV PORT 7001 + +ADD target/Jrebel-License-Server.jar /JrebelBrains.jar +CMD java -jar /JrebelBrains.jar -p $PORT + diff --git a/README.md b/README.md new file mode 100644 index 0000000..1c8e75b --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# 本地激活JRebel + +## 介绍 +本地激活JRebel + +## 使用说明 +1. 启动项目 +2. 激活地址 http://127.0.0.1:7001/{guid} +> 注意:需要把 {guid} 换成生成的 guid + +> 如 http://127.0.0.1:7001/d060463a-2720-45a3-977a-7d004349ec92 + +## Docker 方式启动 + +```shell +# 编译 +mvn package +# 打镜像 +docker build -t jrebel-active +# 启动容器 +docker run -d --name jrebel-active --restart always -e PORT=7001 -p 7001:7001 jrebel-active +``` + diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..a04eea9 --- /dev/null +++ b/build.gradle @@ -0,0 +1,38 @@ +plugins { + id "com.github.johnrengelman.shadow" version "4.0.1" +} + +apply plugin: 'java' +apply plugin: 'maven' +apply plugin: 'application' + +group = 'com.vvvtimes.com' +version = '1.0-SNAPSHOT' + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +tasks.withType(JavaCompile) { + options.encoding = 'UTF-8' +} + +mainClassName = 'com.vvvtimes.server.MainServer' + +jar { + manifest { + attributes 'Main-Class': mainClassName + } +} + +repositories { + jcenter() +} + +dependencies { + compile group: 'org.eclipse.jetty.aggregate', name: 'jetty-all', version: '8.1.22.v20160922' + compile group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.60' + compile group: 'commons-codec', name: 'commons-codec', version: '1.11' + compile group: 'net.sf.json-lib', name: 'json-lib', version: '2.4', classifier: 'jdk15' + compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.1' + compileOnly group: 'javax.servlet', name: 'javax.servlet-api', version: '3.0.1' +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..e0b3fb8 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..cccdd3d --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..f955316 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..140c8f9 --- /dev/null +++ b/pom.xml @@ -0,0 +1,110 @@ + + + 4.0.0 + + com.witty.com + Jrebel-License-Server + 1.0.0 + + + UTF-8 + + + + ${artifactId} + + + org.apache.maven.plugins + maven-compiler-plugin + + 3.13.0 + + 17 + 17 + utf8 + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.6.0 + + + false + + + + package + + shade + + + + + com.witty.server.MainServer + + + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + + + + + + + + + org.eclipse.jetty.aggregate + jetty-all + 9.4.56.v20240826 + pom + + + + javax.servlet + javax.servlet-api + 4.0.1 + + + + org.bouncycastle + bcprov-jdk15on + 1.70 + + + + commons-codec + commons-codec + 1.17.1 + + + + com.alibaba.fastjson2 + fastjson2 + 2.0.53 + + + + org.apache.commons + commons-lang3 + 3.17.0 + + + + + \ No newline at end of file diff --git a/src/main/java/com/witty/handler/IndexHandler.java b/src/main/java/com/witty/handler/IndexHandler.java new file mode 100644 index 0000000..6293751 --- /dev/null +++ b/src/main/java/com/witty/handler/IndexHandler.java @@ -0,0 +1,51 @@ +package com.witty.handler; + +import org.eclipse.jetty.server.Request; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.UUID; + +public class IndexHandler { + + public static void indexHandler(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException { + response.setContentType("text/html; charset=utf-8"); + response.setStatus(HttpServletResponse.SC_OK); + baseRequest.setHandled(true); + + // 拼接服务器地址 + String licenseUrl = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort(); + + StringBuffer html = new StringBuffer("

使用说明(Instructions for use)

"); + + html.append("
"); + + html.append("

Hello,This is a Jrebel & JetBrains License Server!

"); + html.append("

License Server started at ").append(licenseUrl); + html.append("

JetBrains Activation address was: ").append(licenseUrl).append("/"); + html.append("

JRebel 7.1 and earlier version Activation address was: ") + .append(licenseUrl).append("/{tokenname}") + .append(", with any email."); + html.append("

JRebel 2018.1 and later version Activation address was: ") + .append(licenseUrl).append("/{guid}") + .append("(eg:") + .append(licenseUrl).append("/").append(UUID.randomUUID()) + .append("), with any email."); + + html.append("


"); + html.append("

Hello,此地址是 Jrebel & JetBrains License Server!

"); + html.append("

JetBrains许可服务器激活地址 ").append(licenseUrl); + html.append("

JetBrains激活地址是: ").append(licenseUrl).append("/"); + html.append("

JRebel 7.1 及旧版本激活地址: ") + .append(licenseUrl).append("/{tokenname}") + .append(", 以及任意邮箱地址。"); + html.append("

JRebel 2018.1+ 版本激活地址: ") + .append(licenseUrl).append("/{guid}") + .append("(例如:") + .append(licenseUrl).append("/").append(UUID.randomUUID()) + .append("), 以及任意邮箱地址。"); + + response.getWriter().println(html); + } +} diff --git a/src/main/java/com/witty/handler/JetBrainsHandler.java b/src/main/java/com/witty/handler/JetBrainsHandler.java new file mode 100644 index 0000000..4664c90 --- /dev/null +++ b/src/main/java/com/witty/handler/JetBrainsHandler.java @@ -0,0 +1,66 @@ +package com.witty.handler; + +import com.witty.util.RsaSign; +import org.eclipse.jetty.server.Request; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +public class JetBrainsHandler { + public static void releaseTicketHandler(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException { + response.setContentType("text/html; charset=utf-8"); + response.setStatus(HttpServletResponse.SC_OK); + String salt = request.getParameter("salt"); + baseRequest.setHandled(true); + if (salt == null) { + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + } else { + String xmlContent = "OK" + salt + ""; + String xmlSignature = RsaSign.Sign(xmlContent); + String body = "\n" + xmlContent; + response.getWriter().print(body); + } + } + + public static void pingHandler(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException { + response.setContentType("text/html; charset=utf-8"); + response.setStatus(HttpServletResponse.SC_OK); + String salt = request.getParameter("salt"); + baseRequest.setHandled(true); + if (salt == null) { + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + } else { + String xmlContent = "OK" + salt + ""; + String xmlSignature = RsaSign.Sign(xmlContent); + String body = "\n" + xmlContent; + response.getWriter().print(body); + } + + } + + public static void obtainTicketHandler(String target, Request baseRequest, HttpServletRequest request, + HttpServletResponse response) throws IOException { + response.setContentType("text/html; charset=utf-8"); + response.setStatus(HttpServletResponse.SC_OK); + SimpleDateFormat fm = new SimpleDateFormat("EEE,d MMM yyyy hh:mm:ss Z", Locale.ENGLISH); + String date = fm.format(new Date()) + " GMT"; + //response.setHeader("Date", date); + //response.setHeader("Server", "fasthttp"); + String salt = request.getParameter("salt"); + String username = request.getParameter("userName"); + String prolongationPeriod = "607875500"; + baseRequest.setHandled(true); + if (salt == null || username == null) { + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + } else { + String xmlContent = "" + prolongationPeriod + "OK" + salt + "1licensee=" + username + "\tlicenseType=0\t"; + String xmlSignature = RsaSign.Sign(xmlContent); + String body = "\n" + xmlContent; + response.getWriter().print(body); + } + } +} diff --git a/src/main/java/com/witty/handler/JrebelHandler.java b/src/main/java/com/witty/handler/JrebelHandler.java new file mode 100644 index 0000000..69b7700 --- /dev/null +++ b/src/main/java/com/witty/handler/JrebelHandler.java @@ -0,0 +1,120 @@ +package com.witty.handler; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.witty.util.JrebelUtil.JrebelSign; +import org.eclipse.jetty.server.Request; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public class JrebelHandler { + public static void jrebelLeasesHandler(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException { + response.setContentType("application/json; charset=utf-8"); + response.setStatus(HttpServletResponse.SC_OK); + String clientRandomness = request.getParameter("randomness"); + String username = request.getParameter("username"); + String guid = request.getParameter("guid"); + System.out.println(request.getParameterMap()); + boolean offline = Boolean.parseBoolean(request.getParameter("offline")); + String validFrom = "null"; + String validUntil = "null"; + if (offline) { + String clientTime = request.getParameter("clientTime"); + String offlineDays = request.getParameter("offlineDays"); + //long clientTimeUntil = Long.parseLong(clientTime) + Long.parseLong(offlineDays) * 24 * 60 * 60 * 1000; + long clientTimeUtil = Long.parseLong(clientTime) + 180L * 24 * 60 * 60 * 1000; + validFrom = clientTime; + validUntil = String.valueOf(clientTimeUtil); + } + baseRequest.setHandled(true); + + String jsonStr = """ + { + "serverVersion": "3.2.4", + "serverProtocolVersion": "1.1", + "serverGuid": "a1b4aea8-b031-4302-b602-670a990272cb", + "groupType": "managed", + "id": 1, + "licenseType": 1, + "evaluationLicense": false, + "signature": "OJE9wGg2xncSb+VgnYT+9HGCFaLOk28tneMFhCbpVMKoC/Iq4LuaDKPirBjG4o394/UjCDGgTBpIrzcXNPdVxVr8PnQzpy7ZSToGO8wv/KIWZT9/ba7bDbA8/RZ4B37YkCeXhjaixpmoyz/CIZMnei4q7oWR7DYUOlOcEWDQhiY=", + "serverRandomness": "H2ulzLlh7E0=", + "seatPoolType": "standalone", + "statusCode": "SUCCESS", + "offline": %s, + "validFrom": %s, + "validUntil": %s, + "company": "Administrator", + "orderId": "", + "zeroIds": [ + \s + ], + "licenseValidFrom": 1490544001000, + "licenseValidUntil": 1691839999000 + } + """; + jsonStr = String.format(jsonStr, offline, validFrom, validUntil); + JSONObject jsonObject = JSON.parseObject(jsonStr); + if (clientRandomness == null || username == null || guid == null) { + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + } else { + JrebelSign jrebelSign = new JrebelSign(); + jrebelSign.toLeaseCreateJson(clientRandomness, guid, offline, validFrom, validUntil); + String signature = jrebelSign.getSignature(); + jsonObject.put("signature", signature); + jsonObject.put("company", username); + String body = jsonObject.toString(); + response.getWriter().print(body); + } + } + + public static void jrebelValidateHandler(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException { + response.setContentType("application/json; charset=utf-8"); + response.setStatus(HttpServletResponse.SC_OK); + baseRequest.setHandled(true); + String jsonStr = """ + { + "serverVersion": "3.2.4", + "serverProtocolVersion": "1.1", + "serverGuid": "a1b4aea8-b031-4302-b602-670a990272cb", + "groupType": "managed", + "statusCode": "SUCCESS", + "company": "Administrator", + "canGetLease": true, + "licenseType": 1, + "evaluationLicense": false, + "seatPoolType": "standalone" + } + """; + JSONObject jsonObject = JSON.parseObject(jsonStr); + String body = jsonObject.toString(); + response.getWriter().print(body); + } + + public static void jrebelLeases1Handler(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException { + response.setContentType("application/json; charset=utf-8"); + response.setStatus(HttpServletResponse.SC_OK); + String username = request.getParameter("username"); + baseRequest.setHandled(true); + String jsonStr = """ + { + "serverVersion": "3.2.4", + "serverProtocolVersion": "1.1", + "serverGuid": "a1b4aea8-b031-4302-b602-670a990272cb", + "groupType": "managed", + "statusCode": "SUCCESS", + "msg": null, + "statusMessage": null + } + """; + JSONObject jsonObject = JSON.parseObject(jsonStr); + if (username != null) { + jsonObject.put("company", username); + } + String body = jsonObject.toString(); + response.getWriter().print(body); + + } +} diff --git a/src/main/java/com/witty/handler/ToolHandler.java b/src/main/java/com/witty/handler/ToolHandler.java new file mode 100644 index 0000000..fbf1095 --- /dev/null +++ b/src/main/java/com/witty/handler/ToolHandler.java @@ -0,0 +1,19 @@ +package com.witty.handler; + +import org.eclipse.jetty.server.Request; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.UUID; + +public class ToolHandler { + public static void guidHandler(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException { + response.setContentType("text/html; charset=utf-8"); + response.setStatus(HttpServletResponse.SC_OK); + baseRequest.setHandled(true); + String body = UUID.randomUUID().toString(); + response.getWriter().print(body); + + } +} diff --git a/src/main/java/com/witty/server/MainServer.java b/src/main/java/com/witty/server/MainServer.java new file mode 100644 index 0000000..fd1221a --- /dev/null +++ b/src/main/java/com/witty/server/MainServer.java @@ -0,0 +1,91 @@ +package com.witty.server; + +import com.witty.handler.IndexHandler; +import com.witty.handler.JetBrainsHandler; +import com.witty.handler.JrebelHandler; +import com.witty.handler.ToolHandler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.AbstractHandler; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class MainServer extends AbstractHandler { + + private static Map parseArguments(String[] args) { + if (args.length % 2 != 0) { + throw new IllegalArgumentException("Error in argument's length "); + } + + Map params = new HashMap<>(); + + for (int i = 0, len = args.length; i < len; ) { + String argName = args[i++]; + + if (argName.charAt(0) == '-') { + if (argName.length() < 2) { + throw new IllegalArgumentException("Error at argument " + argName); + } + + argName = argName.substring(1); + } + + params.put(argName, args[i++]); + } + + return params; + } + + public static void main(String[] args) throws Exception { + Map arguments = parseArguments(args); + String port = arguments.get("p"); + + if (port == null || !port.matches("\\d+")) { + port = "7001"; + } + + Server server = new Server(Integer.parseInt(port)); + server.setHandler(new MainServer()); + server.start(); + + String serverStr = "http://localhost"; + if (!"80".equals(port)) { + serverStr = serverStr + ":" + port; + } + + System.out.println("License Server started at " + serverStr); + System.out.println("JetBrains Activation address was: " + serverStr + "/"); + System.out.println("JRebel 7.1 and earlier version Activation address was: " + serverStr + "/{tokenname}, with any email."); + System.out.println("JRebel 2018.1 and later version Activation address was: " + serverStr + "/{guid}(eg:http://localhost:" + port + "/" + UUID.randomUUID().toString() + "), with any email."); + + server.join(); + } + + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) + throws IOException { + System.out.println(target); + switch (target) { + case "/" -> IndexHandler.indexHandler(target, baseRequest, request, response); + case "/jrebel/leases", "/agent/leases" -> + JrebelHandler.jrebelLeasesHandler(target, baseRequest, request, response); + case "/jrebel/leases/1", "/agent/leases/1" -> + JrebelHandler.jrebelLeases1Handler(target, baseRequest, request, response); + case "/jrebel/validate-connection" -> + JrebelHandler.jrebelValidateHandler(target, baseRequest, request, response); + case "/rpc/ping.action" -> JetBrainsHandler.pingHandler(target, baseRequest, request, response); + case "/rpc/obtainTicket.action" -> + JetBrainsHandler.obtainTicketHandler(target, baseRequest, request, response); + case "/rpc/releaseTicket.action" -> + JetBrainsHandler.releaseTicketHandler(target, baseRequest, request, response); + case "/guid" -> ToolHandler.guidHandler(target, baseRequest, request, response); + default -> response.setStatus(HttpServletResponse.SC_FORBIDDEN); + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/witty/util/Base64.java b/src/main/java/com/witty/util/Base64.java new file mode 100644 index 0000000..e715275 --- /dev/null +++ b/src/main/java/com/witty/util/Base64.java @@ -0,0 +1,25 @@ +package com.witty.util; + + +public class Base64 { + /** + * 编码 + * + * @param bstr + * @return String + */ + public static String encode(byte[] bstr) { + return org.apache.commons.codec.binary.Base64.encodeBase64String(bstr); + } + + /** + * 解码 + * + * @param str + * @return string + */ + public static byte[] decode(String str) { + return org.apache.commons.codec.binary.Base64.decodeBase64(str); + } + +} diff --git a/src/main/java/com/witty/util/Hex.java b/src/main/java/com/witty/util/Hex.java new file mode 100644 index 0000000..94e05dc --- /dev/null +++ b/src/main/java/com/witty/util/Hex.java @@ -0,0 +1,42 @@ +package com.witty.util; + +public class Hex { + public static String bytesToHexString(byte[] src){ + StringBuilder stringBuilder = new StringBuilder(""); + if (src == null || src.length <= 0) { + return null; + } + for (int i = 0; i < src.length; i++) { + int v = src[i] & 0xFF; + String hv = Integer.toHexString(v); + if (hv.length() < 2) { + stringBuilder.append(0); + } + stringBuilder.append(hv); + } + return stringBuilder.toString(); + } + /** + * Convert hex string to byte[] + * @param hexString the hex string + * @return byte[] + */ + public static byte[] hexStringToBytes(String hexString) { + if (hexString == null || hexString.equals("")) { + return null; + } + hexString = hexString.toUpperCase(); + int length = hexString.length() / 2; + char[] hexChars = hexString.toCharArray(); + byte[] d = new byte[length]; + for (int i = 0; i < length; i++) { + int pos = i * 2; + d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1])); + } + return d; + } + private static byte charToByte(char c) { + return (byte) "0123456789ABCDEF".indexOf(c); + } + +} diff --git a/src/main/java/com/witty/util/JrebelUtil/ByteUtil.java b/src/main/java/com/witty/util/JrebelUtil/ByteUtil.java new file mode 100644 index 0000000..88506a8 --- /dev/null +++ b/src/main/java/com/witty/util/JrebelUtil/ByteUtil.java @@ -0,0 +1,34 @@ +package com.witty.util.JrebelUtil; + +import org.apache.commons.codec.binary.Base64; + +import java.nio.charset.StandardCharsets; +import java.util.Random; + +public class ByteUtil { + private static final Random a; + + public static String a(final byte[] binaryData) { + if (binaryData == null) { + return null; + } + return new String(Base64.encodeBase64(binaryData), StandardCharsets.UTF_8); + } + + public static byte[] a(final String s) { + if (s == null) { + return null; + } + return Base64.decodeBase64(s.getBytes(StandardCharsets.UTF_8)); + } + + public static byte[] a(final int n) { + final byte[] array = new byte[n]; + ByteUtil.a.nextBytes(array); + return array; + } + + static { + a = new Random(); + } +} diff --git a/src/main/java/com/witty/util/JrebelUtil/JrebelSign.java b/src/main/java/com/witty/util/JrebelUtil/JrebelSign.java new file mode 100644 index 0000000..c259752 --- /dev/null +++ b/src/main/java/com/witty/util/JrebelUtil/JrebelSign.java @@ -0,0 +1,27 @@ +package com.witty.util.JrebelUtil; + +import org.apache.commons.lang3.StringUtils; + +public class JrebelSign { + private String signature; + + public void toLeaseCreateJson(String clientRandomness, String guid, boolean offline, String validFrom, String validUntil) { + //String serverRandomness = ByteUtil.a(ByteUtil.a(8)); + String serverRandomness = "H2ulzLlh7E0="; //服务端随机数,如果要自己生成,务必将其写到json的serverRandomness中 + //String value = String.valueOf("false"); + String s2= ""; + if(offline){ + s2 = StringUtils.join(new String[]{clientRandomness, serverRandomness, guid, String.valueOf(offline), validFrom, validUntil}, ';'); + }else{ + s2 = StringUtils.join(new String[]{clientRandomness, serverRandomness, guid, String.valueOf(offline)}, ';'); + } + System.out.println(s2); + final byte[] a2 =LicenseServer2ToJRebelPrivateKey.a(s2.getBytes()); + this.signature = ByteUtil.a(a2); + } + + public String getSignature() { + return signature; + } + +} diff --git a/src/main/java/com/witty/util/JrebelUtil/LicenseServer2ToJRebelPrivateKey.java b/src/main/java/com/witty/util/JrebelUtil/LicenseServer2ToJRebelPrivateKey.java new file mode 100644 index 0000000..7663513 --- /dev/null +++ b/src/main/java/com/witty/util/JrebelUtil/LicenseServer2ToJRebelPrivateKey.java @@ -0,0 +1,41 @@ +package com.witty.util.JrebelUtil; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.Signature; +import java.security.spec.PKCS8EncodedKeySpec; + +public class LicenseServer2ToJRebelPrivateKey { + private static final String b = "MIICXAIBAAKBgQDQ93CP6SjEneDizCF1P/MaBGf582voNNFcu8oMhgdTZ/N6qa6O7XJDr1FSCyaDdKSsPCdxPK7Y4Usq/fOPas2kCgYcRS/iebrtPEFZ/7TLfk39HLuTEjzo0/CNvjVsgWeh9BYznFaxFDLx7fLKqCQ6w1OKScnsdqwjpaXwXqiulwIDAQABAoGATOQvvBSMVsTNQkbgrNcqKdGjPNrwQtJkk13aO/95ZJxkgCc9vwPqPrOdFbZappZeHa5IyScOI2nLEfe+DnC7V80K2dBtaIQjOeZQt5HoTRG4EHQaWoDh27BWuJoip5WMrOd+1qfkOtZoRjNcHl86LIAh/+3vxYyebkug4UHNGPkCQQD+N4ZUkhKNQW7mpxX6eecitmOdN7Yt0YH9UmxPiW1LyCEbLwduMR2tfyGfrbZALiGzlKJize38shGC1qYSMvZFAkEA0m6psWWiTUWtaOKMxkTkcUdigalZ9xFSEl6jXFB94AD+dlPS3J5gNzTEmbPLc14VIWJFkO+UOrpl77w5uF2dKwJAaMpslhnsicvKMkv31FtBut5iK6GWeEafhdPfD94/bnidpP362yJl8Gmya4cI1GXvwH3pfj8S9hJVA5EFvgTB3QJBAJP1O1uAGp46X7Nfl5vQ1M7RYnHIoXkWtJ417Kb78YWPLVwFlD2LHhuy/okT4fk8LZ9LeZ5u1cp1RTdLIUqAiAECQC46OwOm87L35yaVfpUIjqg/1gsNwNsj8HvtXdF/9d30JIM3GwdytCvNRLqP35Ciogb9AO8ke8L6zY83nxPbClM="; + private static final byte[] c; + private static final BouncyCastleProvider d; + + private static PrivateKey a() { + final PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(LicenseServer2ToJRebelPrivateKey.c); + try { + return KeyFactory.getInstance("RSA", LicenseServer2ToJRebelPrivateKey.d).generatePrivate(pkcs8EncodedKeySpec); + } catch (Exception ex) { + ex.printStackTrace(); + return null; + } + } + + public static byte[] a(final byte[] array) { + try { + final Signature instance = Signature.getInstance("SHA1withRSA", LicenseServer2ToJRebelPrivateKey.d); + instance.initSign(a()); + instance.update(array); + return instance.sign(); + } catch (GeneralSecurityException ex) { + throw new RuntimeException("License Server installation error 0000000F2", ex); + } + } + + static { + c = ByteUtil.a("MIICXAIBAAKBgQDQ93CP6SjEneDizCF1P/MaBGf582voNNFcu8oMhgdTZ/N6qa6O7XJDr1FSCyaDdKSsPCdxPK7Y4Usq/fOPas2kCgYcRS/iebrtPEFZ/7TLfk39HLuTEjzo0/CNvjVsgWeh9BYznFaxFDLx7fLKqCQ6w1OKScnsdqwjpaXwXqiulwIDAQABAoGATOQvvBSMVsTNQkbgrNcqKdGjPNrwQtJkk13aO/95ZJxkgCc9vwPqPrOdFbZappZeHa5IyScOI2nLEfe+DnC7V80K2dBtaIQjOeZQt5HoTRG4EHQaWoDh27BWuJoip5WMrOd+1qfkOtZoRjNcHl86LIAh/+3vxYyebkug4UHNGPkCQQD+N4ZUkhKNQW7mpxX6eecitmOdN7Yt0YH9UmxPiW1LyCEbLwduMR2tfyGfrbZALiGzlKJize38shGC1qYSMvZFAkEA0m6psWWiTUWtaOKMxkTkcUdigalZ9xFSEl6jXFB94AD+dlPS3J5gNzTEmbPLc14VIWJFkO+UOrpl77w5uF2dKwJAaMpslhnsicvKMkv31FtBut5iK6GWeEafhdPfD94/bnidpP362yJl8Gmya4cI1GXvwH3pfj8S9hJVA5EFvgTB3QJBAJP1O1uAGp46X7Nfl5vQ1M7RYnHIoXkWtJ417Kb78YWPLVwFlD2LHhuy/okT4fk8LZ9LeZ5u1cp1RTdLIUqAiAECQC46OwOm87L35yaVfpUIjqg/1gsNwNsj8HvtXdF/9d30JIM3GwdytCvNRLqP35Ciogb9AO8ke8L6zY83nxPbClM="); + d = new BouncyCastleProvider(); + } +} diff --git a/src/main/java/com/witty/util/RsaSign.java b/src/main/java/com/witty/util/RsaSign.java new file mode 100644 index 0000000..cb8303d --- /dev/null +++ b/src/main/java/com/witty/util/RsaSign.java @@ -0,0 +1,98 @@ +package com.witty.util; + +import org.bouncycastle.asn1.ASN1InputStream; +import org.bouncycastle.asn1.ASN1Primitive; +import org.bouncycastle.asn1.pkcs.RSAPrivateKeyStructure; + +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.RSAPrivateKeySpec; + + +public class RsaSign { + static String header = "607875500OK15084842582741licensee=Administrator licenseType=0 "; + static String content = "607875500OK15084842582741licensee=Administrator licenseType=0 "; + String ASNKEY = "-----BEGIN RSA PRIVATE KEY-----\r\n" + + "MIIBOgIBAAJBALecq3BwAI4YJZwhJ+snnDFj3lF3DMqNPorV6y5ZKXCiCMqj8OeOmxk4YZW9aaV9\r\n" + + "ckl/zlAOI0mpB3pDT+Xlj2sCAwEAAQJAW6/aVD05qbsZHMvZuS2Aa5FpNNj0BDlf38hOtkhDzz/h\r\n" + + "kYb+EBYLLvldhgsD0OvRNy8yhz7EjaUqLCB0juIN4QIhAOeCQp+NXxfBmfdG/S+XbRUAdv8iHBl+\r\n" + + "F6O2wr5fA2jzAiEAywlDfGIl6acnakPrmJE0IL8qvuO3FtsHBrpkUuOnXakCIQCqdr+XvADI/UTh\r\n" + + "TuQepuErFayJMBSAsNe3NFsw0cUxAQIgGA5n7ZPfdBi3BdM4VeJWb87WrLlkVxPqeDSbcGrCyMkC\r\n" + + "IFSs5JyXvFTreWt7IQjDssrKDRIPmALdNjvfETwlNJyY\r\n" + + "-----END RSA PRIVATE KEY-----"; + String PCKS8KEY = "-----BEGIN PRIVATE KEY-----\r\n" + + "MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAt5yrcHAAjhglnCEn\r\n" + + "6yecMWPeUXcMyo0+itXrLlkpcKIIyqPw546bGThhlb1ppX1ySX/OUA4jSakHekNP\r\n" + + "5eWPawIDAQABAkBbr9pUPTmpuxkcy9m5LYBrkWk02PQEOV/fyE62SEPPP+GRhv4Q\r\n" + + "Fgsu+V2GCwPQ69E3LzKHPsSNpSosIHSO4g3hAiEA54JCn41fF8GZ90b9L5dtFQB2\r\n" + + "/yIcGX4Xo7bCvl8DaPMCIQDLCUN8YiXppydqQ+uYkTQgvyq+47cW2wcGumRS46dd\r\n" + + "qQIhAKp2v5e8AMj9ROFO5B6m4SsVrIkwFICw17c0WzDRxTEBAiAYDmftk990GLcF\r\n" + + "0zhV4lZvztasuWRXE+p4NJtwasLIyQIgVKzknJe8VOt5a3shCMOyysoNEg+YAt02\r\n" + + "O98RPCU0nJg=\r\n" + "-----END PRIVATE KEY-----"; + + static String key22 = "MIIBOgIBAAJBALecq3BwAI4YJZwhJ+snnDFj3lF3DMqNPorV6y5ZKXCiCMqj8OeOmxk4YZW9aaV9" + + "ckl/zlAOI0mpB3pDT+Xlj2sCAwEAAQJAW6/aVD05qbsZHMvZuS2Aa5FpNNj0BDlf38hOtkhDzz/h" + + "kYb+EBYLLvldhgsD0OvRNy8yhz7EjaUqLCB0juIN4QIhAOeCQp+NXxfBmfdG/S+XbRUAdv8iHBl+" + + "F6O2wr5fA2jzAiEAywlDfGIl6acnakPrmJE0IL8qvuO3FtsHBrpkUuOnXakCIQCqdr+XvADI/UTh" + + "TuQepuErFayJMBSAsNe3NFsw0cUxAQIgGA5n7ZPfdBi3BdM4VeJWb87WrLlkVxPqeDSbcGrCyMkC" + + "IFSs5JyXvFTreWt7IQjDssrKDRIPmALdNjvfETwlNJyY"; + + static String key33 = "MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAt5yrcHAAjhglnCEn" + + "6yecMWPeUXcMyo0+itXrLlkpcKIIyqPw546bGThhlb1ppX1ySX/OUA4jSakHekNP" + + "5eWPawIDAQABAkBbr9pUPTmpuxkcy9m5LYBrkWk02PQEOV/fyE62SEPPP+GRhv4Q" + + "Fgsu+V2GCwPQ69E3LzKHPsSNpSosIHSO4g3hAiEA54JCn41fF8GZ90b9L5dtFQB2" + + "/yIcGX4Xo7bCvl8DaPMCIQDLCUN8YiXppydqQ+uYkTQgvyq+47cW2wcGumRS46dd" + + "qQIhAKp2v5e8AMj9ROFO5B6m4SsVrIkwFICw17c0WzDRxTEBAiAYDmftk990GLcF" + + "0zhV4lZvztasuWRXE+p4NJtwasLIyQIgVKzknJe8VOt5a3shCMOyysoNEg+YAt02" + + "O98RPCU0nJg="; + + public static String Sign(String content){ + return RsaSign.Sign(content.getBytes(), key22); + } + + public static String Sign2(String content){ + return RsaSign.Sign2(content.getBytes(), key33); + } + + //传入秘钥为ASN格式 + //私钥签名程序,privateKey是私钥base64编码字符串,即私钥文件数据中,中间的主体部分 + public static String Sign(byte[] content, String privateKey) { + try { + byte[] keybyte = Base64.decode(privateKey.toString()); + ASN1InputStream in = new ASN1InputStream(keybyte); + ASN1Primitive obj = in.readObject(); + RSAPrivateKeyStructure pStruct = RSAPrivateKeyStructure.getInstance(obj); + RSAPrivateKeySpec spec = new RSAPrivateKeySpec(pStruct.getModulus(), pStruct.getPrivateExponent()); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PrivateKey priKey = keyFactory.generatePrivate(spec); + java.security.Signature signature = java.security.Signature.getInstance("MD5WithRSA"); + signature.initSign(priKey); + signature.update(content); + byte[] signed = signature.sign(); + return Hex.bytesToHexString(signed); + } + catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + //传入秘钥为PKCS#8私钥非加密格式 + //私钥签名程序,privateKey是私钥base64编码字符串,即私钥文件数据中,中间的主体部分 + public static String Sign2(byte[] content, String privateKey) { + try { + PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(Base64.decode(privateKey)); + KeyFactory keyf = KeyFactory.getInstance("RSA"); + PrivateKey priKey = keyf.generatePrivate(priPKCS8); + java.security.Signature signature = java.security.Signature.getInstance("MD5WithRSA"); + signature.initSign(priKey); + signature.update(content); + byte[] signed = signature.sign(); + return Hex.bytesToHexString(signed); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/target/.DS_Store b/target/.DS_Store new file mode 100644 index 0000000..99659ad Binary files /dev/null and b/target/.DS_Store differ diff --git a/target/maven-archiver/pom.properties b/target/maven-archiver/pom.properties new file mode 100644 index 0000000..b9efbdf --- /dev/null +++ b/target/maven-archiver/pom.properties @@ -0,0 +1,5 @@ +#Generated by Maven +#Thu Jul 06 00:38:37 CST 2023 +version=1.0.0 +groupId=com.witty.com +artifactId=Jrebel-License-Server diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 0000000..e69de29 diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..feadebe --- /dev/null +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1,11 @@ +/Users/huangqing/code/jrebel-license-server-for-java/src/main/java/com/witty/handler/ToolHandler.java +/Users/huangqing/code/jrebel-license-server-for-java/src/main/java/com/witty/handler/JrebelHandler.java +/Users/huangqing/code/jrebel-license-server-for-java/src/main/java/com/witty/util/Base64.java +/Users/huangqing/code/jrebel-license-server-for-java/src/main/java/com/witty/util/JrebelUtil/ByteUtil.java +/Users/huangqing/code/jrebel-license-server-for-java/src/main/java/com/witty/util/Hex.java +/Users/huangqing/code/jrebel-license-server-for-java/src/main/java/com/witty/util/RsaSign.java +/Users/huangqing/code/jrebel-license-server-for-java/src/main/java/com/witty/handler/IndexHandler.java +/Users/huangqing/code/jrebel-license-server-for-java/src/main/java/com/witty/util/JrebelUtil/JrebelSign.java +/Users/huangqing/code/jrebel-license-server-for-java/src/main/java/com/witty/server/MainServer.java +/Users/huangqing/code/jrebel-license-server-for-java/src/main/java/com/witty/handler/JetBrainsHandler.java +/Users/huangqing/code/jrebel-license-server-for-java/src/main/java/com/witty/util/JrebelUtil/LicenseServer2ToJRebelPrivateKey.java