×

【安全课堂】openssl系列篇之Openssl以及证书认证
  

深信服安全产品研发 7144

{{ttag.title}}
Openssl以及证书认证

                                     上一篇:openssl简介
                                             下一篇: Openssl 常用命令

简介
https协议是一种安全性较高的交互通道,其安全基础就是SSL协议。

SSL协议,它已被广泛地用于Web浏览器与服务器之间的身份认证和加密数据传输.它位于TCP/IP协议与各种应用层协议之间,为数据通讯提供安全支持。

SSL协议可分为两层:

SSL记录协议(SSLRecord Protocol):它建立在可靠的传输协议(如TCP)之上,为高层协议提供数据封装、压缩、加密等基本功能的支持。

SSL握手协议(SSL Handshake Protocol):它建立在SSL记录协议之上,用于在实际的数据传输开始前,通讯双方进行身份认证、协商加密算法、交换加密密钥等。

服务端是通过openssl来实现对ssl的支持。而证书认证,实际上主要就是一个建立ssl连接的过程,以及一些认证检测,比如crl的检测之类的。

Openssl里面的实现比较复杂,有些地方也还不甚明了,欢迎大家一起学习。本篇主要会介绍一些,关于SSL连接建立的大致流程,证书认证的一些步骤以及crl的检测。

说明:本文档分析所涉及到的代码均是以svpn中所使用的为标准,如openssl使用的是0.9.8o版本。以下分析,主要是站在服务器端的角度进行分析。


一、建立ssl连接
通常,我们手动创建一个ssl连接,需要通过以下几步:

1、ssl的初始化


2、建立ssl新连接


服务器的SSL_accept()函数和客户端的SSL_connect()函数共同完成SSL的握手协商过程。

3、ssl通信

和普通的read()和write()函数调用一样,用下面的函数完成数据的SSL接收和发送,函数输入的是明文,函数自动将数据封装进SSL中:

接收:SSL_read()

发送:SSL_write()

4、ssl的释放

SSL的释放也很简单:

SSL_free(ssl)


以上,只是一个简单的使用样例,介绍下SSL的基本使用,其内部实现是比较复杂的。SSL建立的主要过程,都在SSL_accept和SSL_connect中,这两步完成的工作也比较复杂。

二、ssl连接分析方法集及初始化
下面简单介绍下SSL的连接。SSL连接的数据流图大致描述如下:


第一步:客户端发送client_hello请求SSL连接;

         服务器端回应server_hello。

这一步主要协商,包括协议版本,session ID,加密算法、以及初始化的随机数。

第二步:服务端发送证书,进行证书用来计算连接共享密钥。

         服务端发送密钥,数据加密密钥的交换操作,RSA密钥交换。
         服务端发送证书请求,要求获取客户端证书的请求。

第三步:服务端接收客户端发送的证书、以及客户端密钥、进行认证。

第四步:发送修改信息,修改加密算法信息。

在介绍SSL连接的具体实现之前,先来介绍下openssl的几个至关重要的方法集,所谓的方法集,也就是一个结构体,里面全是函数指针,设置的一些回调函数,这些回调函数,贯穿整个连接以及认证。

在介绍这个函数集之前,又需要先来介绍结构体和一个宏,用来描述这个方法集(参考文档:include/openssl/ssl<结构体>和ssl/ssl_locl.h<宏>):



这个结构体就是描述了这个方法集,描述的是这个方法集的数据类型。

而接下来要介绍的这个宏,则是创建一个SSL_METHOD的变量,并附上一定的初始化步骤。


从以上代码可以看出,这个宏里面是定义了一个函数,这个函数返回的是一个SSL_METHOD类型的结构体指针。这个函数的工作是就是创建一个static的


SSL_METHOD类型的结构体。定义的函数的名字是宏的参数func_name所传入的值。

举个例子:参考mod_ssl中的modules/ssl/ssl_engine_init.c中的ssl_init_ctx_protocol(359行)初始化函数。


文中是对modssl_ctx_t中的ssl_ctx的初始化部分。modthod就是这个函数集的结构体。赋的值是函数SSLV23_server_method的返回值,而函数SSLV23_server_method定义的地方是在openssl中ssl/S23_srvr.c中的135行处。


文中用上面介绍的宏IMPLEMENT_ssl23_meth_func定义了一个函数SSLv23_server_method。该函数将返回SSL_METHOD的结构体。

三、ssl连接分析ssl_accept
关于证书认证,调用的几个主要函数是:

ssl_hook_Access函数 ->
SSL_do_handshake函数 ->
ssl23_accept函数.

其实整个证书认证的过程,就是一个重新握手的过程。在ssl23_accept这个函数中,主要描述了整个握手过程,以及这个握手过程中所经历的状态变化和每一步所做的工作。

关于证书认证,在mod_ssl调用的关键步骤如下(modules/ssl/Ssl_engine_kernel.c中的ssl_hook_Access函数,大致位置614行):


文中调用了两遍SSL_do_handshake函数,第一遍调用ssl_do_handshake仅是向客户端发送hello的答复信息;第二遍SSL_do_handshake是接收处理,以及整个认证过程,核心工作在第二个SSL_do_handshake。

mod_ssl通过向客户端发送一个hello req的回复请求信息来强制SSL进行重新协商。Mod_ssl做了这么一个处理:在服务端发送hello req回复请求之后,openssl会立即相应,虽然说在SSL/TLS协议中并没有指定客户端必须响应服务器端的hello req回复请求,但是服务器端为了容错,会强制要求回复,之后服务器端会改成ACCEPT状态。在这里不能使用SSL_set_accept_state()函数,因为SSL_set_accept_state()会重设连接的很多信息,所以只能自己设置状态进行手动的握手。

对上面代码的分析:

1、首先调用SSL_renegotiate函数,会设置一个标志位renegotiate(ssl->s3->renegotiate),一个表示重新协商的标志位,SSL_do_handshake的函数中,会调用一个回调函数ssl_renegotiate_check来检测这个标志位,如果这个标志位被设成1,则会将ssl的状态设置SSL_ST_RENEGOTIATE,并将renegotiate标记位设置成0。这个状态位贯穿以及连接整个ssl_accept的过程,以及控制其握手流程。

   2、第二行的SSL_do_handshake函数,也就是调用的第一遍SSL_do_handshake,会完成上述的检测renegotiate状态之后,调用另外一个回调函数handshake_func,这个函数实际上就是SSL_accept或者是SSL_connect函数(对应的是服务器端或者客户端)。这一步在SSL_accept中,所经历的状态变化是:

状态值                     对应的宏
0x3004               SSL_ST_RENEGOTIATE
0x2120                     SSL3_ST_SW_HELLO_REQ_A
0x2121                     SSL3_ST_SW_HELLO_REQ_B
0x2100                     SSL3_ST_SW_FLUSH
0x2122                     SSL3_ST_SW_HELLO_REQ_C
0x3                              SSL_ST_OK

先对这些宏的字面解释做个简单介绍,openssl的这几个宏,从字面上看就能看出大概所做的处理:以SSL_ST_SW_HELLO_REQ_A为例,SSL、SSL3是指协议版本,ST是指状态类型,SW中的S是指Server,W是指写Write(相应的就有对应的Client和Read),HELLO_REQ是指工作的内容,最后面的后缀A、B、C是指这步活动的各个小步骤,SSL很多步骤都分A、B两种,通常,A状态表示刚进入该状态,还没有收发数据,B状态表示进行的收发数据处理后,但还没有完成善后工作。初步可以看出,这一步完成的工作是服务端写入hello回复请求。

在这步中所经历的其他几个状态,在下一次SSL_do_handshake中将还会出现,将在下一次SSL_do_handshake中做统一介绍。

在这次SSL_do_handshake调用中最核心的一步就是处于SSL3_ST_SW_HELLO_REQ_A状态中调用的ssl3_send_hello_request函数,向客户端发送一个hello的回复请求。

   3、中间的几行是容错处理,比较核心的最后两步,倒数第二步比较简单,SSL_set_state(ssl, SSL_ST_ACCEPT);就是设置一个状态,将状态设置成SSL_ST_ACCEPT。

   4、最后一步,也就是第二次调用SSL_do_handshake,也是所有的核心处理。
下面将粗略下分析第四步每一步所做的工作:

这一步经历的所有状态如下:
步骤                 状态数值                对应的宏
1                    0x2000                        SSL_ST_ACCEPT
2                    0x2110                        SSL3_ST_SR_CLNT_HELLO_A
3                    0x2130                        SSL3_ST_SW_SRVR_HELLO_A
4                    0x2131                        SSL3_ST_SW_SRVR_HELLO_B
5                    0x2140                        SSL3_ST_SW_CERT_A
6                    0x2141                        SSL3_ST_SW_CERT_B
7                    0x2150                        SSL3_ST_SW_KEY_EXCH_A
8                    0x2160                        SSL3_ST_SW_CERT_REQ_A
9                    0x2161                        SSL3_ST_SW_CERT_REQ_B
10                   0x2100                        SSL3_ST_SW_FLUSH
11                   0x2180                        SSL3_ST_SR_CERT_A
12                   0x2181                        SSL3_ST_SR_CERT_B
13                   0x2190                        SSL3_ST_SR_KEY_EXCH_A
14                   0x21a0                        SSL3_ST_SR_CERT_VRFY_A
15                   0x21c0                        SSL3_ST_SR_FINISHED_A
16                   0x21c1                        SSL3_ST_SR_FINISHED_B
17                   0x21d0                        SSL3_ST_SW_CHANGE_A
18                   0x21d1                        SSL3_ST_SW_CHANGE_B
19                   0x21e0                        SSL3_ST_SW_FINISHED_A
20                   0x21e1                        SSL3_ST_SW_FINISHED_B
21                   0x2100                        SSL3_ST_SW_FLUSH
22                   0x3                           SSL_ST_OK

      由于这个函数比较庞大,在这里将不附上源码(代码penssl中的ssl/S3_srve.c中的ssl3_accept函数,164行),仅粗略描述下状态变化,以及每步的主要功能和所调用的主要函数。

      第1步是之前在ssk_hook_Access(mod_ssl中的)中设置的状态位,也是这次的入口状态,

      第2步读取客户端的数据,函数自己构造的,而不是实际收到的,主要调用的函数

ssl3_get_client_hello。

      第3、4步是写服务器端的回应的HELLO请求信息,主要调用2个函数:ssl3_send_hello_request,写服务端的回应请求信息;ssl3_init_finished_mac,初始化认证码MAC。

在函数ssl3_send_hello_request里面会将状态改为SSL3_ST_SW_SRVR_HELLO_B。其中有个比较重要的判断,s->hit用来标记该ssl会话是否是重用的,在ssl3_get_client()函数中检查客户端的hello信息后设置的,如果会话是重用的,将状态改为SSL3_ST_SW_CHANGE_A,否则为新的SSL会话,进入证书处理A状态,将状态改为SSL_ST_SW_CERT_A。

      第5、6步SSL3_ST_SW_CERT_A,进行证书交换,用来计算连接共享密钥。主要调用ssl3_send_server_certificate函数,在非NULL加密的话调用,发送服务器端的证书。

      第7步SSL3_ST_SW_KEY_EXCH_A,数据加密的密钥交换操作。算法类型由一个常数表述l=s->s3->tmp.new_cipher->algorithms。主要调用的函数ssl3_send_server_key_exchange,进行RSA密钥交换。

      第8、9步SSL3_ST_SW_CERT_REQ_A,进入证书请求阶段。

大多数情况下是不需要客户端的证书的,如果要验证对方,只要以下条件之一不满足就可以认证对方:
          file://localhost/Users/ZJ/Library/Caches/TemporaryItems/msoclip/0/clip_image003.gif


   其中CTX的verify_mode通过SSL_CTX_set_verify()来修改,s->verify_mode可通过函数SSL_get_verify()来修改,s->verify_mode的初始值是ctx->verify_mode赋予的。

   这一步主要调用的函数ssl3_send_certificate_request,发送要获取对方证书的请求。其中s->s3->tmp.next_state=SSL3_ST_SR_CERT_A会在清除写缓冲状态的处理中会用到这个数据,记录清除写缓冲之后下一步的操作。

   第10步SSL3_ST_SW_FLUSH。清除写缓冲。

   第11、12步SSL3_ST_SR_CERT_A,接收客户端证书。这步主要调用了2个函数ssl3_check_client_hello,检测客户端的hello信息;ssl3_get_client_certificate,获取客户端证书。整个证书认证、以及crl检测就是在ssl3_get_client_certificate这个函数中完成的。

   第13步SSL3_ST_SR_KEY_EXCH_A,处理密钥交换。主要调用了2个函数,ssl3_get_client_key_exchange获取客户端密钥;以及回调函数method->ssl3_enc->cert_verify_mac,用来认证证书MAC码。

   第14步SSL3_ST_SR_CERT_VRFY_A,验证证书。主要调用函数ssl3_get_cert_verify,这个函数的主要目的是验证证书的签名。

   第15、16步SSL3_ST_SR_FINISHED_A,服务器端接收结束状态。主要调用函数ssl3_get_finished,获取结束信息。这里里面,也有判断重用标记,s->hit,如果是重用的,连接已建立,改状态为SSL_ST_OK,否则,改状态为SSL3_ST_SW_CHANGE_A。

   第17、18步SSL3_ST_SW_CHANGE_A,服务器发送修改信息状态。这步主要有s->session->cipher=s->s3->tmp.new_cipher设置SSL加密算法;以及函数调用ssl3_send_change_cipher_spec,发送修改加密算法信息。

   第19、20步SSL3_ST_SW_FINISHED_A,服务端发送结束,握手完成。调用函数ssl3_send_finished。这一步会将状态转成SSL3_ST_SW_FLUSH,同时指定下下步的状态。在指定下下步状态的时候,也会判断s->hit标记位,如过为1,将下下步的状态改成SSL3_ST_SR_FINISHED_A,否则将下下步状态改成SSL_ST_OK。

   第21步SSL3_ST_SW_FLUSH,清除SSL写缓冲。并指定下一步状态。

   第22步SSL_ST_OK,连接成功完成,清除连接过程中所申请的资源。
整个认证的流程以及执行的顺序如上述描述。

补充说明:openssl对于ssl_accept这一系列的函数,总共有3套,分别如ssl23_accept、ssl2_accept、ssl3_accept以23、2、3来区分(表示的版本信息的区别)。实际上第一连接会走ssl23_accept()函数,这个函数里面会有协商过程,协商客户端、服务器端的ssl版本,协商的过程是在ssl23_get_client_hello()函数中完成的,这也是个比较复杂的函数(这个函数主要是要分析ssl的协议)。在协商玩完了之后会转到SSL_accept函数,实际上就是转到ssl2_accept或者ssl3_accept函数。在ssl2_accept和ssl3_accept函数中就没有协商的过程了。

四、总结
以上描述的是openssl中ssl的建立和连接的过程。

证书认证其实就是一个建立ssl连接的过程,但是证书认证的细节、以及crl的存储,都涉及到openssl中大量的数据结构,而且openssl中的数据结构存储的这些信息比较复杂,openssl的数据结构中关于证书和crl的存储都是一层堆栈嵌套一层堆栈,总共是嵌套了4层堆栈,而且每层堆栈都还有几个分支,存储结构有点错综复杂,在这里仅做一个概要的流程处理描述:

证书认证的函数流程描述如下:
1、ssl3_accept
2、ssl3_get_client_certificate
3、ssl_verify_cert_chain
4、X509_verify_cert
5、internal_verify
6、internal_verify中1027行调用的回调函数cb,也就是mod_ssl中指定的函数ssl_callback_SSLVerify_CRL。

其中前五步是为了验证证书,最后的回调函数,是为了验证查找crl。

打赏鼓励作者,期待更多好文!

打赏
暂无人打赏

Sangfor_闪电回_朱丽 发表于 2016-4-14 16:33
  
涨知识了,感谢研发哥的分享~
中国是我家 发表于 2019-5-7 10:35
  
吃瓜群众来围观一下
发表新帖
热门标签
全部标签>
技术盲盒
西北区每日一问
安全效果
每日一问
干货满满
新版本体验
【 社区to talk】
技术笔记
功能体验
技术咨询
产品连连看
标准化排查
GIF动图学习
2023技术争霸赛专题
每周精选
信服课堂视频
通用技术
自助服务平台操作指引
秒懂零信任
技术晨报
安装部署配置
答题自测
原创分享
升级&主动服务
社区新周刊
POC测试案例
畅聊IT
专家问答
技术圆桌
在线直播
MVP
网络基础知识
升级
安全攻防
上网策略
测试报告
日志审计
问题分析处理
流量管理
每日一记
运维工具
云计算知识
用户认证
解决方案
sangfor周刊
VPN 对接
项目案例
SANGFOR资讯
专家分享
技术顾问
信服故事
SDP百科
功能咨询
终端接入
授权
设备维护
资源访问
地址转换
虚拟机
存储
迁移
加速技术
排障笔记本
产品预警公告
玩转零信任
信服圈儿
S豆商城资讯
技术争霸赛
「智能机器人」
追光者计划
深信服技术支持平台
社区帮助指南
答题榜单公布
纪元平台
卧龙计划
华北区拉练
天逸直播
以战代练
山东区技术晨报
文档捉虫活动
齐鲁TV
华北区交付直播
2024年技术争霸赛
北京区每日一练
场景专题
故障笔记
排障那些事
高手请过招
高频问题集锦
全能先锋系列
云化安全能力

本版达人

新手68983...

本周分享达人

零和一网络

本周提问达人