- 浏览: 6221827 次
文章分类
最新评论
-
vb2005xu:
这样你跑一个1000试试,卡不死你
PHP实现斐波那契数列 -
ykbj117:
你们知道刘绍华么?就是北邮的一个教授,专门研究WebRTC的资 ...
WebRTC体系结构 -
huangbye:
其实这也没什么卵用!
thinkphp自定义标签,view直接标签连接数据 -
cofftech:
opencv源码:http://www.eyesourceco ...
opencv资料和文档 -
langke93:
wangzhengyi_nopass.key这个怎么生成没写
nginx搭建https服务器
返璞归真实现OpenVPN第二阶段协商
1.背景介绍
听着《梦中的额吉》,《天堂》...女儿在睡觉...外面细雨...中秋小长假,完成自己的OpenVPN patch编码中充满了快乐!前提是你知道自己在做什么!
OpenVPN不给力,虽然它给出了N多的Renegotiate选项,然则其实现却不尽人意。难道设计者以为我们众人就这么好忽悠吗?
OpenVPN实现重新密钥协商是极其重量级的,需要做以下几件事:
1.断开当前的SSL连接;
2.重新开始一次SSL连接,建立一次SSL session;
3.在重新生成的SSL加密信道上重新开始OpenVPN的密钥协商;
记住,不能单纯的理解这3个事件,是因为如果从底层看,底层的UDP连接(不考虑TCP的情况)并没有断开,需要理解的是,SSL协商和数据通道复用一个信道。这个过程之所以说是重量级的是因为1和2并不总是必要的,要知道SSL的目的有两点,第一是相互认证,确保客户端连接的是自己要连接的服务器,确保客户端有权接入VPN;第二是在协商出的SSL加密信道上协商OpenVPN的对称密钥。如果我们只是要保护VPN通信的内容,那么大可不必重复认证,也就是没有必要重新进行SSL握手。要知道,如果SSL加密信道可以被攻破,那么后续的通信将是不再安全的,幸运的是,SSL协议本身的完备性保证了SSL最终协商出的信道是安全的,否则SSL协议也不会这么流行,因此除非SSL本身的规范要求,在OpenVPN中,如果仅仅为了保证数据的机密性,SSL重协商是没有必要的。
这只是其一。任何伟大的事情,都有无数的先行者,或者说它本身就需要借鉴前辈的成果。我们可以考虑IKE,ISAKMP的规范,它明确规定了两个阶段的协商,为了效率,一般情况下,通信双方只需要进行第二阶段的重新协商,在IKE的规范中,第一阶段的协商所完成的工作和SSL握手完成的工作很类似(也许正好说反了,...我女儿长得和我很像,而不是我长得和我女儿很像!)。对称密钥由于密钥长度不够,算法相对简单,被暴破的可能性很大,因此应该不断重新协商对称密钥。在使用SSL协议的情况下有两种方式重新协商出对称密钥,第一种方式就是重新进行SSL握手,使用SSL最终协商出的那个对称密钥,第二就是在直接在SSL加密信道上协商新的对称密钥,这就使对称密钥的结果和SSL协议分离了,无疑这是一种效率更高且配置更灵活的方式,也许这也是对IKE/ISAKMP的一种呼应吧。
然而很可惜,OpenVPN并没有使用这种优雅的方式,每当OpenVPN需要重新协商,都要从新建SSL session开始!key_state_soft_reset这个函数并没有做到其名称所描述的那样soft,而实则是一种very hard的恶毒方式,因此需要改变这种方式!我对OpenVPN的已经关注超过两年了,由于工作需要,加之自己的兴趣,着手对之做个外科手术,手术很成功,以数据为证,效率有很大的提升。在学习的过程中,Internet对我帮助很大,Internet本身就是共享的,因此将patch献出,也希望共勉者完善之。本patch也以委托的方式提交到了OpenVPN的maillist,希望能在下个版本中见到它的身影,如果有朋友觉得有改进的地方,那就大胆的改进它,然后提交...这对于每一个开发人员都是一笔财富!
2.尝试修改
前文《让OpenVPN实现IKE似的两阶段密钥协商》,代码已然完成,然则不便公开,同时代码丑陋之极,无颜!总的来讲,那只是一次初期的预研,逻辑如下:2.1.在S_ACTIVE之后定义新的状态码;
2.2.当需要进行第二阶段协商的时候,像client push一个字符串,将本地状态设置一个中间状态;
2.3.client端收到server端push下来的对应字符串之后,将本地状态设置为一个新定义的预协商状态,并且写入新的key协商消息,同时reply一个字符串;
2.4.server端收到client端reply的字符串之后,将本地状态设置为预协商的状态,读取key协商消息;
2.5.双方开始在以前的SSL加密信道上重新协商新的对称密钥;
2.6.最终,二者到达一个新的S_ACTIVE状态;
2.7.协商完成。
这个手术开始非常让我自豪,因为它有效的降低了传输延迟,然而经过后续的仔细测试,发现它引起了大量的丢包,使用ping进行的简单的测试就会发现丢包,因为echo reply的序号会有断续...这个结果极大的打击了我。但是,我的选择只能是继续尝试其他的方法,否则我的努力将是前功尽弃...(腾格尔的《天堂》,从时间4:45开始的一段音乐很不错[哦...Belala...],我很喜欢)。我的push/pull/reply的想法并没有错啊,server端总是需要一种方式通知client需要重新协商了,这本身很合理,然而哪里错了?后来看代码,发现就算是硬协商,也就是重新开始SSL握手,标准的OpenVPN也是这么做的,所不同的是,标准的OpenVPN并没有使用push,而是发送了一个很特别的P_CONTROL_SOFT_RESET_V1消息,然后再在client端的tls_pre_decrypt函数中对其进行特殊的处理,从而进行正常的伪造的“SOFT”reset。
既然如果,我又何必出力不讨好,直接使用标准的实现不就可以了吗!
还好OpenVPN协议的定义很灵活,其op码为5位,最大可以有31个,现在才使用了8个,我扩展一个是很简单的事情啊。想到这里,再也忍不住了,先吸一根烟再说!(《梦中的额吉》在1:54时间开始一段音乐很不错,引发了更多的想法)
3.确定如何修改
当初的尝试之所以出现了丢包,那是因为我忽略了OpenVPN对session和key的管理。在OpenVPN中,为了实现在重新协商以及clilent断开中不丢包,业务数据传输不受影响的正常进行,OpenVPN设计了3个session和2个key state,在client断开重连时,server管理session,以至于正常连接成为可能,在重新协商密钥时,server端管理key state,从而平滑过渡到新的密钥。因此,如果不吃透OpenVPN复杂的协议处理,实现第二阶段的协商是没戏的。我的做法是,先动手再说,先修改掉代码再慢慢测试排错,我觉得程序员就应该这样,毕竟我们不是研究院的专家有那么多的时间和经费。具体实现见第4节。
实现了之后,测试,效果良好,先给出数据,先看一个ping的结果,测试中为了比较重新协商SSL和仅仅重新协商对称密钥,我使用了常数4秒,测试原生OpenVPN时,使用reneg-sec 4参数,而测试我修改过的OpenVPN时,使用reneg-second参数,首先看一下没有修改的原生的OpenVPN的ping结果:
可以看到,抖动虽然还有,但是平缓了很多。仅仅通过ping还不足以看出个究竟,那么我们看看tcp传输的丢包重传统计值:
结果几乎是肯定的,当然TCP协议是复杂的,不能单纯从丢包或者重传来判断,正好比,有一条比较超豪华的高速公路,其事故率并不比国道少的原因一样(不得不说的插曲:TCP重传可能是由于其拥塞避免阀值设置得过高所导致,或者由于其拥塞控制算法过于自私所导致,然而可悲的是,很多人都认为重传越多,网络状况越差!这样的人不在少数,还总是用什么业务逻辑之类词汇将真理打发)
4.最终的补丁
起初我想直接提交这个补丁,但是由于Unix邮箱环境坏了,web邮箱提交patch有存在编码问题,因此采用了委托的方式,希望社区的家伙能帮我这个忙,至于作者是谁是无所谓的,Internet上的作者大多是虚拟的,像dog250之类的,呵呵~~。最终的补丁其实很简单,之所以称为返璞归真,是因为OpenVPN本身做的就很好,其协议的操作码预留了5位的空间,而它仅仅用了不到8个,这就是值得欣慰的事情,这意味着我们只需要增加一个操作码就能实现额外的功能,对于第二阶段协商来讲,用这个方法实现再好不过了,补丁如下,如果有谁索要常规编码的补丁,请发邮件(不要为补丁所迷惑,后面有解释):
TO everyone.As we know,IKE takes only phase II to consult the final KEY IF WE DO NOT CARE ABOUT THE RESULT ABOUT RE-AUTJENTICATION in the special condition.This idea is very true especially if we use SSL protocol because of the secure test in very bitter surrounding. The SSL protocol can ensure the security of the whole vitrual line after the entire handshack.
OpenVPN use the protocol of itself not only SSL.Its channel is build on its protocol of itself.Thus if somebody crack the SSL key, (S)HE must take sometime that may be long to crack the OpenVPN channel key. AS THE RESULT,WE CAN ONLY TO Renegotiate ITS CHANNEL KEY NOT THE WHOLE SSL SESSION.
Unfortunately,as we show,OpenVPN takes a pool way to renegotiate the new key, not only the channel key but also the SSL record protocol key.
This patch rework the whole thing.Its only take the phase II to renegotiate the channel key.
*****************************************************************************
diff -uNr openvpn-2.2.1/options.c openvpn-2.2.1_my/options.c --- openvpn-2.2.1/options.c 2011-09-09 16:25:49.000000000 +0800 +++ openvpn-2.2.1_my/options.c 2011-09-09 17:12:03.000000000 +0800 @@ -668,6 +668,7 @@ "--show-pkcs11-ids provider [cert_private] : Show PKCS#11 available ids.\n" " --verb option can be added *BEFORE* this.\n" #endif /* ENABLE_PKCS11 */ + "--reneg-second sec :xxx" ; #endif /* !ENABLE_SMALL */ @@ -3544,6 +3545,7 @@ int msglevel_fc = msglevel_forward_compatible (options, msglevel); ASSERT (MAX_PARMS >= 5); + reneg_sec_time = 0; if (!file) { file = "[CMD-LINE]"; @@ -5781,6 +5783,10 @@ VERIFY_PERMISSION (OPT_P_GENERAL); options->crl_file = p[1]; } + else if (streq (p[0], "reneg-second") && p[1]) { + reneg_sec_time = atoi(p[1]); + } + else if (streq (p[0], "tls-verify") && p[1]) { VERIFY_PERMISSION (OPT_P_SCRIPT); diff -uNr openvpn-2.2.1/ssl.c openvpn-2.2.1_my/ssl.c --- openvpn-2.2.1/ssl.c 2011-09-09 16:25:49.000000000 +0800 +++ openvpn-2.2.1_my/ssl.c 2011-09-09 17:16:28.000000000 +0800 @@ -2329,6 +2329,7 @@ * to/from memory BIOs. */ CLEAR (*ks); + session->ks_size = KS_SIZE; ks->ssl = SSL_new (session->opt->ssl_ctx); if (!ks->ssl) @@ -2469,6 +2470,7 @@ dmsg (D_TLS_DEBUG, "TLS: tls_session_init: entry"); CLEAR (*session); + multi->tm_size = TM_SIZE; /* Set options data to point to parent's option structure */ session->opt = &multi->opt; @@ -2520,7 +2522,7 @@ if (session->tls_auth.packet_id) packet_id_free (session->tls_auth.packet_id); - for (i = 0; i < KS_SIZE; ++i) + for (i = 0; i < session->ks_size; ++i) key_state_free (&session->key[i], false); if (session->common_name) @@ -2725,8 +2727,8 @@ cert_hash_free (multi->locked_cert_hash_set); - for (i = 0; i < TM_SIZE; ++i) - tls_session_free (&multi->session[i], false); + for (i = 0; i < multi->tm_size; ++i) + tls_session_free (&multi->session[i], false); if (clear) CLEAR (*multi); @@ -3256,6 +3258,26 @@ ks->remote_addr = ks_lame->remote_addr; } +void +key_state_soft_reset2(struct tls_session *session, struct tls_multi *multi) +{ + update_time (); + ks->must_die = now + session->opt->transition_window; /* remaining lifetime of old key */ + *ks_lame = *ks; + session->ks_size = 1; + multi->tm_size = 1; + + session->initial_opcode = P_CONTROL_SOFT_RESET_SECOND; + ks->initial_opcode = session->initial_opcode; + ks->key_id = session->key_id; + ++session->key_id; + session->key_id &= P_KEY_ID_MASK; + if (!session->key_id) + session->key_id = 1; + ks->state = S_K; + ks->session_id_remote = ks_lame->session_id_remote; + ks->remote_addr = ks_lame->remote_addr; +} /* * Read/write strings from/to a struct buffer with a u16 length prefix. */ @@ -4039,6 +4061,16 @@ ks->n_packets, session->opt->renegotiate_packets); key_state_soft_reset (session); } + + if (((ks->state == S_ACTIVE) || (ks->state == S_NORMAL_OP)) && + reneg_sec_time && + (now >= ks->reneg_base + reneg_sec_time) && + session->opt->server) { + ks->reneg_base = now; + key_state_soft_reset2 (session, multi); + + } + /* Kill lame duck key transition_window seconds after primary key negotiation */ if (lame_duck_must_die (session, wakeup)) { @@ -4069,7 +4101,7 @@ if (true) { /* Initial handshake */ - if (ks->state == S_INITIAL) + if (ks->state == S_INITIAL || ks->state == S_K) { buf = reliable_get_buf_output_sequenced (ks->send_reliable); if (buf) @@ -4082,6 +4114,8 @@ INCR_GENERATED; ks->state = S_PRE_START; + if (ks->state == S_K) + ks->state = S_START; state_change = true; dmsg (D_TLS_DEBUG, "TLS: Initial Handshake, sid=%s", session_id_print (&session->session_id, &gc)); @@ -4118,7 +4152,7 @@ } /* Wait for Initial Handshake ACK */ - if (ks->state == S_PRE_START && FULL_SYNC) + if ((ks->state == S_PRE_START || ks->state == S_K) && FULL_SYNC) { ks->state = S_START; state_change = true; @@ -4131,7 +4165,9 @@ { if (FULL_SYNC) { + ks->established = now; + ks->reneg_base = now; dmsg (D_TLS_DEBUG_MED, "STATE S_ACTIVE"); if (check_debug_level (D_HANDSHAKE)) print_details (ks->ssl, "Control Channel:"); @@ -4366,6 +4402,9 @@ if (ks->established && session->opt->renegotiate_seconds) compute_earliest_wakeup (wakeup, ks->established + session->opt->renegotiate_seconds - now); + if (ks->reneg_base && reneg_sec_time) + compute_earliest_wakeup (wakeup, + ks->reneg_base + reneg_sec_time - now); /* prevent event-loop spinning by setting minimum wakeup of 1 second */ if (*wakeup <= 0) @@ -4894,13 +4933,25 @@ dmsg (D_TLS_DEBUG, "TLS: received P_CONTROL_SOFT_RESET_V1 s=%d sid=%s", i, session_id_print (&sid, &gc)); + } else + if (op == P_CONTROL_SOFT_RESET_SECOND + && DECRYPT_KEY_ENABLED (multi, ks)) + { + if (!read_control_auth (buf, &session->tls_auth, from)) { + goto error; + } + key_state_soft_reset2 (session, multi); + + dmsg (D_TLS_DEBUG, + "TLS: received P_CONTROL_SOFT_RESET_V1 s=%d sid=%s", + i, session_id_print (&sid, &gc)); } else { /* * Remote responding to our key renegotiation request? */ - if (op == P_CONTROL_SOFT_RESET_V1) + if (op == P_CONTROL_SOFT_RESET_V1 || op == P_CONTROL_SOFT_RESET_SECOND) do_burst = true; if (!read_control_auth (buf, &session->tls_auth, from)) diff -uNr openvpn-2.2.1/ssl.h openvpn-2.2.1_my/ssl.h --- openvpn-2.2.1/ssl.h 2011-09-09 16:25:49.000000000 +0800 +++ openvpn-2.2.1_my/ssl.h 2011-09-09 17:19:26.000000000 +0800 @@ -210,6 +210,7 @@ #define P_CONTROL_SOFT_RESET_V1 3 /* new key, graceful transition from old to new key */ #define P_CONTROL_V1 4 /* control channel packet (usually TLS ciphertext) */ #define P_ACK_V1 5 /* acknowledgement for packets received */ +#define P_CONTROL_SOFT_RESET_SECOND 9 /* new key, graceful transition from old to new key */ #define P_DATA_V1 6 /* data channel packet */ /* indicates key_method >= 2 */ @@ -218,18 +219,25 @@ /* define the range of legal opcodes */ #define P_FIRST_OPCODE 1 -#define P_LAST_OPCODE 8 +#define P_LAST_OPCODE 10 /* key negotiation states */ #define S_ERROR -1 #define S_UNDEF 0 #define S_INITIAL 1 /* tls_init() was called */ #define S_PRE_START 2 /* waiting for initial reset & acknowledgement */ -#define S_START 3 /* ready to exchange keys */ -#define S_SENT_KEY 4 /* client does S_SENT_KEY -> S_GOT_KEY */ -#define S_GOT_KEY 5 /* server does S_GOT_KEY -> S_SENT_KEY */ -#define S_ACTIVE 6 /* ready to exchange data channel packets */ -#define S_NORMAL_OP 7 /* normal operations */ + +#define S_K 3 /* ready to exchange keys */ +#define S_START 4 /* ready to exchange keys */ +#define S_SENT_KEY 5 /* client does S_SENT_KEY -> S_GOT_KEY */ +#define S_GOT_KEY 6 /* server does S_GOT_KEY -> S_SENT_KEY */ + +unsigned int reneg_sec_time; +unsigned int reneg_base; + +#define S_ACTIVE 7 /* ready to exchange data channel packets */ +#define S_NORMAL_OP 8 /* normal operations */ + /* * Are we ready to receive data channel packets? @@ -358,6 +366,7 @@ BIO *ct_out; /* read ciphertext from here */ time_t established; /* when our state went S_ACTIVE */ + time_t reneg_base; time_t must_negotiate; /* key negotiation times out if not finished before this time */ time_t must_die; /* this object is destroyed at this time */ @@ -540,6 +549,7 @@ int verify_maxlevel; char *common_name; + int ks_size; struct cert_hash_set *cert_hash_set; @@ -637,6 +647,7 @@ * Our session objects. */ struct tls_session session[TM_SIZE]; + int tm_size; }; /* @@ -843,6 +854,7 @@ /*#define EXTRACT_X509_FIELD_TEST*/ void extract_x509_field_test (void); +void key_state_soft_reset2 (struct tls_session *session, struct tls_multi *multi); #endif /* USE_CRYPTO && USE_SSL */
相关推荐
网络系统管理赛项软件包(服务模块软件包、普通PC软件包、无线地勘系统等)
与原版open-build-master相比,我做了稍稍修改,已经包含Open虚拟专网2.5源代码和依赖项源代码,需要VS2019、ActivePerl、WDK10,可以直接按照我写的教程进行编译,100%可编译。
一个基于DELPHI的远程屏幕传输(差异截图)
Angular 2 introduces an entirely new paradigm of applications. It wholly embraces all the newest concepts that are built into the next generation of browsers, and it cuts away all the fat and bloat ...
交叉编译器3.4.5,下载解压,按照交叉编译器安装过程安装。
win10mi版+Linux CentOS-7+苹果系统驱动加满的无敌合集
如果你连接不上,这是一个可以帮助你连接服务器并进行数据加密的工具。 本地加密后连接到服务器。Linux下面一行命令即可搞定,批量管理。Hiveos进入命令,执行即可。100%安全的加密软件.数据加密后,乱码提交到... ...
Angular 2 Cookbook Angular 2 Cookbook Angular 2 Cookbook
ARM9平台上的嵌入式Linux系统移植研究
根据本人运维经验,结合openVPN社区相关案例,针对TAP-Windows-adapter安装失败“an error occured installing the TAP device driver”错误提示,提出五种解决方案
安装OpenVPN 保护OpenVPN 在局域网中安装Orthanc ? 先决条件 Ansible> 2.5 Ansible> 2.9(用于SSH-sec角色) 在客户端计算机和服务器计算机之间已正确配置SSH身份验证。 安装 克隆项目并安装角色: git clone...
信息安全-中间人攻击的技术构成,以及Kali Linux的双工具.
嵌入式Linux在ARM9(TQ)上的移植.pdf
Atmel-SmartConnect APK for android!
linux企业实战运维入门到高级系列 ubuntu Centos ...自动化运维 安装kickstart文件(半自动化) ANSIBLE部署 企业级OpenVPN 安装OpenVPN 该笔记由刘森飚整理,版权归原作者所有 仅用于学习交流分享,如有争议请联系下架
OpenConnect GUI 这是openconnect VPN的GUI客户端。 该客户端处于beta测试阶段。 不能假定它提供了所需的安全性。 查看项目网页以获取详细描述,屏幕截图和其他相关项目。支持平台Microsoft Windows 7及更高版本...
网上收集来的,别问干什么用,我什么都不知道,给OPENWRT的GUI界面添加什么功能,配置某些功能方便点而已
个人实战积累的成果,基于国密算法的总结,希望可以帮到您 亲们下载我任何一个付费资源后,即可私信联系我免费下载其他相关资源哦~ 个人实战积累的成果,基于国密算法的总结,希望可以帮到您 亲们下载我任何一个...
Over 100 practical recipes to learn PrimeFaces 5.x – the most popular JSF component library on the planet
应用程序和 API 支持 Java、ASP、PHP、Ruby、OpenVPN、TACACS+ 等。阅读我们的电子指南,了解如何使用双因素身份验证设置您的网络:...