SSL协议简介
SSL(Secure Socket Layer)是netscape公司设计的主要用于web的安全传输协议。这种协议在WEB上获得了广泛的应用。SL协议分为两部分:Handshake Protocol和Record Protocol,。其中Handshake Protocol用来协商密钥,协议的大部分内容就是通信双方如何利用它来安全的协商出一份密钥;Record Protocol则定义了传输的格式。SSL协议可以理解为工作在传输层之上、应用层之下。
SSL握手过程
SSL协议的握手过程完成身份认证、密码协商两个功能,单向认证场景下不需要验证客户端身份,交互过程如下:
1) SSL客户端(也是TCP的客户端)在TCP链接建立之后,发出一个“Client Hello”来发起握手,这个消息里面包含了自己可实现的算法列表和其它一些需要的消息;
2) SSL的服务端会回应一个“Server Hello”,这里面确定了这次通信所需要的算法;服务端发过去证书certificate(里面包含了身份和自己的公钥);
3) Client在收到这个消息后会生成一个通信密码,用SSL服务端的公钥加密后发到服务端;
4) SSL服务器端用自己的私钥解密后,会话密钥协商成功,双方可以用同一份会话密钥来通信。
SSL双向认证过程要复杂得多,分为互相验证证书、协商加密方案、协商密钥三大步骤:
1) 客户端发送一个连接请求给SSL服务端。
2) 服务端将自己的证书,以及同证书相关的信息发送给客户端。
3) 客户端验证服务端证书,验证通过则发送客户端证书到服务端,此时客户端获得服务端公钥。
4) 服务器验证客户的证书,如果没有通过验证,拒绝连接;如果通过验证,服务端获得用户的公钥。
5) 客户端生成一个通信密码方案列表,用服务端的公钥加密后传过去。
6) 服务器从客户发送过来的密码方案中,选择一种加密程度最高的密码方案,用客户的公钥加过密后通知客户端。
8) 客户端针对这个密码方案,选择一个通话密钥,接着用服务端的公钥加过密后发送给服务器。
9) 服务器接收到端送过来的消息,用自己的私钥解密,获得通话密钥。
10) 服务器、端接下来的通讯都是用对称密码方案,对称密钥是加过密的。
证书文件说明
1) 证书文件类型
.cer/.crt是用于存放证书,它是2进制形式存放的,不含私钥。
.pem跟crt/cer的区别是它以Ascii(BASE64)来表示。
pfx/p12用于存放个人证书/私钥,通常包含保护密码,2进制方式。
p10是证书请求。
p7r是CA对证书请求的回复,只用于导入。
p7b以树状展示证书链(certificate chain),同时也支持单个证书,不含私钥。
2) X509证书链
X509证书是最通用一种证书个数,用到三类文件:key、csr、crt。
Key文件是私钥文件,openssl格式,通常使用rsa算法,密钥长度要求2048,1024已经被破解。
csr文件是证书请求文件,用于申请证书,可以设定读取该文件的密码。
crt文件是经过根证书CA认证过的证书文件,(windows下csr就是crt文件),csr文件使用自己的key来签署得到crt文件。
3) 证书转换:
X509到P12的转换:
openssl pkcs12 -export -clcerts -in client.crt -inkey client.key -out client.p12
PKCS#12到 PEM的转换:
openssl pkcs12 [-nocerts] -nodes -in cert.p12 –out cert.pem
x509到pfx:
pkcs12 -export –in keys/client1.crt -inkey keys/client1.key -out keys/client1.pfx
从PFX格式文件中提取私钥格式文件(.key):
openssl pkcs12 -in mycert.pfx -nocerts -nodes -out mycert.key
证书制作过程
以SUSE11环境的OPENSSL工具为例制作证书,先创建一个专门生成SSL证书的目录,下面所有操作均在该目录下执行。
1) 制作根证书私钥
openssl genrsa –des3 –out ca.key 2048(有密码)
openssl genrsa -out ca.key 2048 (无密码)
2) 制作自签名根证书
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt
其中要提示输入一些证书的基本信息,如红框部分。包括国家代码、省份、城市、公司名称、项目/部门名称、证书名称等信息。这些内容,最好根据项目的实际情况进行填写。当然,也可以不设置,直接回车即可。
3) 制作服务端私钥
openssl genrsa -out zte.key 2048
4) 制作服务端证书
openssl req -new -key zte.key -out srv.csr
其中的需要填写部分可以对比第二步中的方法。
5) 签发证书
执行openss命令可以制作制作简单的证书:
openssl x509 -req -in srv.csr -out srv.crt -signkey zte.key -CA ca.crt -CAkey ca.key -CAcreateserial -days 3650
6) 制作客户端私钥证书
openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr
openssl x509 -req -in clt.csr -out clt.crt -signkey zte.key -CA ca.crt -CAkey ca.key -CAcreateserial -days 3650
如果要产生一个包含私钥、证书的p12文件需要执行:
openssl pkcs12 -export -clcerts -in client.crt -inkey client.key -out client.p12
说明:
SSL单向认证时,不需要制作客户端证书,服务端需要部署根证书ca.crt、服务端私钥srv.key、服务端证书srv.crt三个文件,客户端可以部署根证书ca.crt,客户端也可以不使用根证书。SSL双向认证时,则需要制作客户端证书、私钥,客户端需要部署ca.crt、client.key、client.key三个文件。
SSL编程总结
网上很多文章推荐SSL握手过程使用阻塞方式,握手过程结束后再设置非阻塞方式,这种方法有个非常危险的场景,向一个SSL监听端口建TCP链接后不发起SSL握手请求,这个SSL监听进程会一直阻塞住。(不知道是否有超时函数可用)下面提到的SSL操作都是非阻塞的。
SSL_connect():一次调用发送client hello,返回值-1,errno=2,需要继续读;此时SSL_pending()返回长度为0,继续读、写均返回-1,但抓包确认SSL握手可以完成。
SSL_accept():一次调用立即发送服务端证书,返回值-1,errno=2,需要继续读;继续SSL_read()仍然返回-1,errno=2,抓包确认SSL握手可以正常完成。
根据函数说明SSL_write/SSL_read可以完成ssl握手过程,应用只需要根据select()结果执行后续读写操作,就可以完成握手过程;测试验证确认这样可以完成握手过程,但是握手过程中SSL_read、SSL_write都返回-1,errno=2,无法得知何时完成握手过程。
非阻塞SSL客户端握手过程编码:
(1) SSL_connect()(返回值-1),发送client hello;
(2) select检测句柄可读,即为收到服务端发送的server hello,执行SSL_connect()(返回值-1),则发送经过公钥加密的密码;
(3) 再次select检测句柄可读,即为收到服务端发送的密码确认,执行SSL_connect()(返回值1),握手成功;
(4) 修改链路为正常establish状态,执行后续操作。
非阻塞SSL服务端握手过程编码:
(1) tcp层完成3次握手后,select检测到句柄可读,即为收到客户端的client hello,执行SSL_accept()(返回值-1),发送server hello;
(2) select再次检测句柄可读,收到客户端发送的密文密码,执行SSL_accept()(返回值1),发送经过密码确认,握手成功结束;
(3) 修改链路为正常establish状态,执行后续操作。
- 客户端校验服务端证书:
默认为SSL_VERIFY_NONE模式,客户端可以不加载ca.crt信任根证书;
如果初始化为SSL_VERIFY_PEER,则客户端会加载信任根证书并认证服务端发过来的证书,认证失败则会执行回调函数;
握手失败时,应用可以调用SSL_get_verify_result()接口获取失败原因。
附:SSL会话初始化时,执行SSL_CTX_set_verify()设置证书认证模式,第二个参数为SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT|SSL_VERIFY_CLIENT_ONCE,第三个参数指定校验回调函数,NULL表示使用SSL自带的校验函数。
- 服务端校验客户端端证书:
单向验证时,服务端不要求客户端发送证书、公钥,也就不对客户端证书进行认证;
服务端初始化为SSL_VERIFY_PEER,则服务端要求双向认证,客户端必须发送证书、公钥,服务端进行认证校验;
还有一种,双向不认证,两层都配置SSL_VERIFY_NONE,客户端验证服务端证书不通过,但仍然可以继续传输数据。
小结:
证书是用来验证对端身份是否合法的,根证书又是用来验证对端证书合法性的,如果不验证对端证书,本地也就不需要根证书了。
服务端不验证客户端证书:服务端不需要CA证书、客户端不需要配置证书私钥;
服务端验证客户端证书:服务端需要CA证书、客户端需要配置证书私钥;
客户端验证服务端证书:客户端需要CA证书、服务端需要证书私钥;
客户端不验证服务端证书:客户端不需要CA证书、服务端仍然需要证书私钥。
参考:http://blog.csdn.net/dog250/article/details/5303388