登录
  • 人们都希望被别人需要 却往往事与愿违
  • C语言诡异离奇, 缺陷重重, 但却获得了巨大的成功@Dennis Ritchie (C语言之父 Unix之父)

JetBrains License Server原理及40行Python代码的实现

不懂编程 Benny小土豆 7275次浏览 5707字 52个评论
文章目录[显示]
这篇文章在 2018年08月31日11:08:55 更新了哦~

Repository:
开源地址

警告:
本文仅供学习参考,请勿用于其他用途。如果你喜欢JetBrains的产品,请购买正版授权。

近期license server升级,可以阅读官方的这篇文章,在2018.2.1(2018年第三季度左右发布)之后旧版服务器将无法使用,所以先别升级到2018.2.1,留在2018.2吧。关于新的激活服务器,似乎有点难度,欢迎各位提供任何可能会有帮助的资料和工具(包括但不限于本地激活器,license server,以及相关介绍文章和抓包记录等),更多详情请参考文末记录

估计是个程序员都应该使用过JetBrains的IDE(尤其是Python程序员吧)。JetBrains的产品线非常丰富,PHP的PHPStorm,前端的Webstorm,Python的Pycharm,Java的IntelliJ,Ruby的RubyMine,Go的Goland,数据库的DataGrip……

几个月之前,我曾经尝试着把Java版本的License Server 移植到Python,但是失败了。但是今天我就成功了……好了不废话了,咱来一步一步的学习下怎么用40行代码写一个JetBrains License Server

JetBrains License Server激活流程

打开IDE,依次点击Help-Register,输入服务器地址,点击Activate

JetBrains License Server原理及40行Python代码的实现

这时就会从 License Server取得一个Ticket了。

友情提示:

如果你有edu邮箱的话,那快去申请免费正版授权吧,哪怕你没有edu邮箱,也可以试试什么注册edu邮箱的,比如说这个

抓包与分析

网上已经有人写好了几个版本的授权服务器,我参考了Java版的、golang版的还有JavaScript版的。

于是我找了那个能用的Java版本的,自己搭建,然后尝试激活、抓包并过滤http,进行分析。

通过追踪HTTP流我们能发现,IDE向指定服务器发起了一个GET请求,请求中包含很多参数,之后服务器返回了一个像是散列值,还有一小串XML

JetBrains License Server原理及40行Python代码的实现

IDE请求:

GET /rpc/obtainTicket.action?buildDate=20170719&buildNumber=2017.2.2+Build+CL-172.3968.17&clientVersion=4&hostName=xxxxxxm&machineId=51xxxxx20a-14d259f1fc94&productCode=cfc7xxxx978-a2a2-46fxxxx405&productFamilyId=cfxxxx-ae43-4978-a2a2-46feb1679405&salt=1506491409302&secure=false&userName=xxxx&version=2017200&versionNumber=2017200

服务器返回:

<!-- 61b3a2f53ffxxxxxxxxx6b132e0270275d12434f217ba3231b5 -->

<ObtainTicketResponse><message></message><prolongationPeriod>607875500</prolongationPeriod><responseCode>OK</responseCode><salt>1506491409302</salt><ticketId>1</ticketId><ticketProperties>licensee=xxxxx licenseType=0 </ticketProperties></ObtainTicketResponse>

由此我们可以很清楚的了解到激活的步骤:请求+相应+验证,那么大体猜测一下应该是服务端持有一个私钥,对请求字符串进行加密,然后返回,客户端使用公钥验证。

验证猜想

为了验证我们的猜想,咱其实是要去读其他版本的源代码的……鉴于这个流程挺复杂,要求你会多门语言,所以咱就不演示了……总之,经过我的研读,我发现的细节是这样的:

服务端构造如下的字符串:

<ObtainTicketResponse><message></message><prolongationPeriod>607875500</prolongationPeriod><responseCode>OK</responseCode><salt>1506491409302</salt><ticketId>1</ticketId><ticketProperties>licensee=xxxxx licenseType=0 </ticketProperties></ObtainTicketResponse>

其中salt是发起请求时的时间戳(单位毫秒),licensee是发起请求的用户名(其实这个用户名是随便的,在代码里写死也是可以的)。

然后服务器使用RSA with MD5 and PKCS1v15进行加密,把加密后的内容转换为十六进制,加上HTML注释<!-- hex -->(注意前后的两个空格),把十六进制HTML注释和构造的字符串一起返回,客户端使用hex和返回的字符串(以及公钥)进行验证。

其实验证猜想的这一步才是最复杂的,需要参考很多源代码做出适当的实验。

既然猜想已经得到了证实,那么剩下来就开始写了。基础的结构很简单,用flask搭建一个简易的服务器,用cryptography完成密码学相关的操作。

cryptography完成RSA签名与十六进制转换

Python中比较安全好用的密码学库是cryptography(唉 好吧)、pycrypto(这货很久不更新了,不敢用),顺便说一句,进行安全随机盐散列函数的有passlib

首先需要引入对应的库

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding

然后载入key(文件形式)

with open('jbls_private_key.pem') as f:
    private_key = serialization.load_pem_private_key(f.read(), password=None, backend=default_backend())

执行加密

i = bytes(content)
signature = private_key.sign(i, padding.PKCS1v15(), hashes.MD5())

转换二进制为十六进制表达

binascii.hexlify(signature)

大概这么五行,我们就完成了加密与转换操作,剩下我们只要在Flask端做一下逻辑处理就好了。

Flask进行逻辑处理

这一步也很简单,用@app.route()装饰器确定路由,构造字符串与返回。真的是很简单,大概就是如下这么十几行:

@app.route("/rpc/obtainTicket.action")
def obtain():
    salt = request.args.get('salt')
    user_name = request.args.get('userName')
    content = "<ObtainTicketResponse><message></message>" + \
        "<prolongationPeriod>607875500</prolongationPeriod><responseCode>OK</responseCode>" + \
        "<salt>" + salt + "</salt><ticketId>1</ticketId>" \
        "<ticketProperties>licensee=" + user_name + "\tlicenseType=0\t</ticketProperties>" \
        "</ObtainTicketResponse>"
    verification_hex = hex_signature(content)
    return "<!-- " + verification_hex + " -->\n" + content

通过Flask的requests拿到salt和username,构造XML,调用函数,返回hex以及XML。

所以加起来,整体代码真的只有不到40行。就算把KEY嵌入到源代码中,也只是43行而已……

最终结果

服务端的请求日志

JetBrains License Server原理及40行Python代码的实现

客户端显示激活成功

JetBrains License Server原理及40行Python代码的实现

附录与FAQ

监听0.0.0.0

如果想要把这个服务部署在服务器上,那么最好就监听0.0.0.0,只需要把app.run()写成 app.run(host='0.0.0.0')即可。

持久运行

当然我们希望这个Flask程序能够长期运行而不退出,此时我们最好使用supervisor或者systemd啦,详细参考此篇《Linux 怎么让程序持续运行:简单说说几种好玩的办法》

示例源代码

本示例源代码可以到下面的地址查看:
开源地址

为啥不放到GitHub呢?被DMCA Takedown啦ε=ε=ε=┏(゜ロ゜;)┛

需要注意的是,需要使用pip安装cryptography、flask,并且jbls.py同时支持Python 2和Python 3.

可否提供一个有效的私钥

不好意思,不提供……至于我这个测试的私钥是在哪找的……你们猜呀。

如何生成exe文件

使用pyinstaller就可以了。

pyinstaller -F demo.py

travis-ci

我们用travis-ci做持续集成测试,但是travis-ci并不是为了运行程序而准备的。但是我们这个Flask应用还必须得运行才能测试。那咋办呢?subprocess.Popen()?反正各种奇技淫巧都试过了,不好用的。劝大家还是试试app.test_client().get(url).data吧,这样就能获取到响应然后assert了。

 

新版JetBrains License Server备注

以下内容全文引用自lanyus评论

贵站居然没有过滤html的特殊字符?重新发一下吧,以下是原文:

Jetbrains家产品线激活服务器认证从2018.2开始使用两个handler来处理,第一个handler会对老的签名方式进行认证(<!-- abcdef1234567890 --><ObtainTicketResponse></ObtainTicketResponse >类样式),该handler会判断签名是否包含hex之外的字符。如果没有,按老版本方式来验证签名,如果存在hex之外字符则抛异常由自己接住后进入第二个handler来验证。
从版本2018.2.1开始便去掉了第一个handler,改为只由第二个handler来验证,由此老的验证服务器全面失效。
来说说第二个handler的验证逻辑吧:
新的报文返回还是形同:<!-- sign_body --><xxxResponse></xxxResponse>,其中<xxxResponse></xxxResponse>这样的响应正文中相比较老版本添加<serveruid>xyz</serveruid>配置,这个xyz后文中有用到,不可缺少。
sign_body格式较老版本的hex字符串变动很大,其示例格式如下:SHA1withRSA-xxxxxxxxxxxxxx-yyyyyyyyyyyyyyy,以-符号分隔,SHA1withRSA对应java中签名所使用的算法(可换为MD5withRSA等),xxxxxxxxxxxxxx部分是使用该算法以私钥签名的bin -> base64字符串,yyyyyyyyyyyyyyy部分则是pem格式的证书字符串(可直接放证书正文,不换行)。
其中yyyyyyyyyyyyyyy证书必须由Jetbrains产品内置的一个CN为License Server CA的CA证书签发才有效,而且yyyyyyyyyyyyyyy证书中必须包含CN=xyz.lsrv.jetbrains.com(其中xyz对应前文<serveruid>xyz</serveruid>部分所填)。
证书验证通过后才会取出证书中所包含的公钥来验证签名<xxxResponse></xxxResponse>响应正文(和base64Decode(xxxxxxxxxxxxxx)比对),验证通过后继续完成服务器认证(解析正文内容,逻辑同旧版)。
啰啰嗦嗦这么多,总结一下:如果我们想要自己造验证服务器,则需要拿到一张License Server CA签发的证书(包括私钥),而且这个证书被Jetbrains发现可能会被吊销!所以这个恐怕有些痴人说梦了。
对了License Server CA证书的内容由另一张Jetprofile签发的叫prod3y的证书公钥定时校验(数据格式同上文所述,不过正文部分是CA证书字符串),应该是防止被替换吧。
还有类似idea.lanyus.com这样的认证服务器域名黑名单也由这张prod3y证书公钥定时校验,也是怕被篡改吧。如果要替换CA要从替换prod3y证书开始,但那是破解的内容了,不如注册服务器优雅。
就这么多。

 

所以估计License Server这条路可能走不通了,只能通过javaagent这种方法修改客户端(IDE)代码然后达到目的了。


文章版权归原作者所有丨本站默认采用CC-BY-NC-SA 4.0协议进行授权|
转载必须包含本声明,并以超链接形式注明原作者和本文原始地址:
https://www.bennythink.com/jbls.html
喜欢 (5)
分享:-)
Benny小土豆
关于作者:
If you have any further questions, feel free to contact me in English or Chinese.
发表我的评论(代码和日志请使用Pastebin或Gist)
取消评论

                     

去你妹的实名制!

  • 昵称 (必填)
  • 邮箱 (必填,不要邮件提醒可以随便写)
  • 网址 (选填)
(52)个小伙伴在吐槽
  1. 不错,key怎么搞?提示一下,然后最近在学py,go和Java都算会吧,另外两个的源码地址能提供一下吗?学习学习! BTW,不支持2018.2.1很蛋疼
    木头2018-08-17 10:00 回复
    • 2018.2.1以及之后的版本官方换了激活方式采用证书签名。这个东西不修改客户端的话基本无解。至少我没办法搞定。
      chenq2018-08-17 10:13 回复
    • Benny小土豆
      Key的话,最简单的办法自然就是去上面提的能用的Java版提取,或者去官方License Server里也能提取出来;
      另外两个go和JavaScript版本的嘛,被DMCA Takedown了(其实我也是被Takedown的之一),所以源代码我也么有/(ㄒoㄒ)/~~
      Benny小土豆2018-08-17 10:13 回复
      • 我找了几个key,复制过来,要不是 500内部错误,就是无效,蛋疼
        木头2018-08-17 15:06 回复
        • Benny小土豆
          最新版本的话,要修改util.py哦,而且不要改错了,比如少了上下的-----。
          500的话应该是你改错了Key
          Benny小土豆2018-08-17 15:41
      • 算逑了,不折腾了,2018.2.1也不用了
        木头2018-08-17 15:45 回复
        • Benny小土豆
          噗 2018.2.1当然是不可以的,暂时用回2018.2吧
          Benny小土豆2018-08-17 15:52
  2. 哈呀,java作者表示受宠若惊
    chenq2018-08-17 09:38 回复
    • Benny小土豆
      啊呀,俺才是受宠若惊,竟然把原作者吸引过来了(✿◡‿◡)
      Benny小土豆2018-08-17 10:10 回复
    • 还支持激活JRebel!良心作者,支持下!
      移影残风2018-09-14 21:10 回复
      • Benny小土豆
        还是要感谢Java版的作者呐
        Benny小土豆2018-09-15 12:30 回复
  3. 嗨,2018.2.1 已经推出了,当我按注册时,会显示出: "License Server response has not passed data integrity check: Invalid signature format". 请问,有没有办法解决呢?
    wayne92018-08-10 16:48 回复
    • Benny小土豆
      我暂时也没有解决方案。不知道2018.2.1改了什么。需要一个官方新版license server的抓包样本。
      Benny小土豆2018-08-10 16:51 回复
      • 2018.2.1以及之后的版本官方换了激活方式采用证书签名。这个东西不修改客户端的话基本无解。至少我没办法搞定。
        chenq2018-08-17 10:14 回复
        • Benny小土豆
          lanyus的评论区找到这么一篇原理分析,至今只拿到了两个证书……对没有私钥/(ㄒoㄒ)/~~
          Benny小土豆2018-08-17 10:20
  4. 报错如下: cannot import name certificate_transparency
    echoops2018-08-06 13:13 回复
    • Benny小土豆
      确定正确安装了cryptography吗? sudo pip install -U cryptography
      推荐使用Python 3
      Benny小土豆2018-08-06 13:21 回复
    • 这个问题已经解决了,看日志貌似也返回200了,idea还是激活失败。 Licence Server response has not passed data integrity check: Invalid signature format
      echoops2018-08-06 13:23 回复
      • Benny小土豆
        你需要替换一个能够激活的key哦,默认的那个是随机生成的假的key
        Benny小土豆2018-08-06 13:25 回复
        • 嗯 谢谢回复
          echoops2018-08-06 13:28
  5. 你好,改了private key后报错 ERROR in app: Exception on /rpc/ping.action [GET] Traceback (most recent call last): File "/usr/lib64/python2.7/site-packages/flask/app.py", line 2292, in wsgi_app response = self.full_dispatch_request() File "/usr/lib64/python2.7/site-packages/flask/app.py", line 1815, in full_dispatch_request rv = self.handle_user_exception(e) File "/usr/lib64/python2.7/site-packages/flask/app.py", line 1718, in handle_user_exception reraise(exc_type, exc_value, tb) File "/usr/lib64/python2.7/site-packages/flask/app.py", line 1813, in full_dispatch_request rv = self.dispatch_request() File "/usr/lib64/python2.7/site-packages/flask/app.py", line 1799, in dispatch_request return self.view_functionsrule.endpoint File "licenseserver/jbls.py", line 82, in ping verification_hex = hex_signature(content) File "licenseserver/jbls.py", line 114, in hex_signature private_key = serialization.load_pem_private_key(KEY, password=None, backend=default_backend()) File "/usr/lib64/python2.7/site-packages/cryptography/hazmat/primitives/serialization.py", line 20, in load_pem_private_key return backend.load_pem_private_key(data, password) File "/usr/lib64/python2.7/site-packages/cryptography/hazmat/backends/openssl/backend.py", line 1014, in load_pem_private_key password, File "/usr/lib64/python2.7/site-packages/cryptography/hazmat/backends/openssl/backend.py", line 1198, in _load_key mem_bio = self._bytes_to_bio(data) File "/usr/lib64/python2.7/site-packages/cryptography/hazmat/backends/openssl/backend.py", line 436, in _bytes_to_bio data_char_p = self._ffi.new(&quot;char[]&quot;, data) TypeError: initializer for ctype 'char[]' must be a str or list or tuple, not unicode
    vampillia2018-08-06 00:08 回复
    • Benny小土豆
      可能是你的key贴错了,有图吗
      Benny小土豆2018-08-06 09:03 回复
      • key确实错了,我改了下。报如下错误 "GET //rpc/obtainTicket.action?buildDate=20180327&buildNumber=2018.1.5+Build+IU-181.5281.24&clientVersion=5&hostName=vampillia&machineId=18a3f2fa-3a93-42e1-b6dc-137f9a3b431e&productCode=49c202d4-ac56-452b-bb84-735056242fb3&productFamilyId=49c202d4-ac56-452b-bb84-735056242fb3&salt=1533518810640&secure=false&userName=jinxin&version=2018100&versionNumber=2018100 HTTP/1.1" 404 -
        vampillia2018-08-06 09:30 回复
        • Benny小土豆
          GET //r
          前面两个//
          Benny小土豆2018-08-06 09:31
      • //是怎么出来的,我没改过代码,就改了下key
        vampillia2018-08-06 09:37 回复
        • Benny小土豆
          因为你请求的时候是http://yourhost.com:5000//rpc...这样的呗
          Benny小土豆2018-08-06 09:39
        • 谢谢了
          vampillia2018-08-06 09:44
  6. 嗨,2018.2 出版了,license server 不能使用了,请问有什么解决方式呢?
    wayne92018-07-26 09:38 回复
    • Benny小土豆
      Starting with the 2018.2.1 release
      官方跳票了,2018.2还是可以正常激活并且没有任何报错的啊是有警告的,估计要等下一个小版本更新吧……
      Benny小土豆2018-07-26 09:52 回复
      • 哈哈,对不起没发现, 太心急了,多谢楼主的回复。 :)
        wayne92018-07-26 10:05 回复
        • Benny小土豆
          没关系没关系,发现更新2018.2的时候我也很激动,然后发现官方大概是跳票了……
          Benny小土豆2018-07-26 10:37
      • 官方当时说的就是2018.2升级server但是没指定哪个版本.顺便问一下老板,这个打字冒星星是什么插件做的?
        auv2018-07-29 13:28 回复
        • Benny小土豆
          官方最开始是2018.2,后来改到2018.2.1。打字查看这个commit
          Benny小土豆2018-07-29 14:16
  7. 我现在用的也是 Java版,可是不会Java~嘤嘤嘤 个人也是更喜欢Python,没想到 在这看到了
    nels0n2018-07-07 10:09 回复
    • Benny小土豆
      Python版写起来更简洁~
      Benny小土豆2018-07-07 14:32 回复
  8. 您好,现在 PhpStorm 2018.1.6 已经出现 warning message: Outdated License Server Detected Your license ticket is obtained from an outdated version of the license server. To keep your product licensed, the license server requires an immediate update to the latest version. Read full details, and pass this information to the server administrator on your side. 请问有没有办法解决这个问题呢?
    wayne92018-06-20 11:57 回复
    • Benny小土豆
      2018.1.6未能复现。
      Benny小土豆2018-06-20 20:58 回复
      • 官方说明 Read full details: https://www.jetbrains.com/license-server/outdated-ls/
        zhuisui2018-06-21 14:33 回复
        • Benny小土豆
          Starting with the 2018.2 major release
          ,还要稍微等等,想要获得最新更新就watch下吧
          Benny小土豆2018-06-21 20:35
  9. phpstorm和goland用的挺爽的
    冯小贤2018-05-24 18:33 回复
    • Benny小土豆
      ??不会go
      Benny小土豆2018-05-24 21:42 回复
  10. clion使用时占用内存 1G+,VS2017 500M,不算轻量吧。。 (也不算什么大不了的事情啦)
    billow2018-03-31 23:08 回复
    • Benny小土豆
      JetBrains的东西是有点占用内存啦,不过如果你想想VS2017安装完要多大空间,你再参考下Clion…………
      不过,你是不是评论错博文了??
      Benny小土豆2018-03-31 23:39 回复
  11. pycrypto确实旧了点,在安卓上termux编译时各种报错,不装的话ykdl又用不了,pip都解决不了
    cok3302018-02-23 10:22 回复
    • Benny小土豆
      pycrypto上次更新于2014年6月,还是尽量不要用的啦。
      Benny小土豆2018-02-23 15:25 回复
  12. Jetbrain是真的良心...话说如果学C/c++, 你推荐vs还是clion?
    每次评论换一个2018-02-22 01:09 回复
    • Benny小土豆
      如果是标准的C/C++的话,这俩都可以。但是CLion更轻量级。
      Benny小土豆2018-02-22 08:24 回复
  13. JetBrains 还有Visual Studio插件(比如ReSharper),以及团队协作工具(TeamCity, YouTrack, Upsource, Hub)呢?
    还有Toolbox管理你说的IDE们的安装? (日常万能?)
    布偶君2018-02-07 08:04 回复
    • Benny小土豆
      是的的是日常万能?
      Benny小土豆2018-02-07 09:09 回复
  14. 7小时前发布!
    布偶君2018-02-06 17:45 回复
    • Benny小土豆
      代码发布于24小时之前!
      Benny小土豆2018-02-06 17:48 回复