一. openssl中的s_server命令与s_client命令
1.1 s_server的man函数
NAME
s_server - SSL/TLS server program
SYNOPSIS
openssl s_server [-accept port] [-context id] [-verify depth] [-Verify
depth] [-crl_check] [-crl_check_all] [-cert filename] [-certform DER|PEM]
[-key keyfile] [-keyform DER|PEM] [-pass arg] [-dcert filename] [-dcertform
DER|PEM] [-dkey keyfile] [-dkeyform DER|PEM] [-dpass arg] [-dhparam
filename] [-nbio] [-nbio_test] [-crlf] [-debug] [-msg] [-state] [-CApath
directory] [-CAfile filename] [-trusted_first] [-krb5svc service] [-keytab
filename] [-nocert] [-cipher cipherlist] [-quiet] [-no_tmp_rsa] [-ssl2]
[-ssl3] [-tls1] [-tls1_1] [-tls1_2] [-dtls1] [-no_ssl2] [-no_ssl3]
[-no_tls1] [-no_tls1_1] [-no_tls1_2] [-no_dhe] [-no_ecdhe] [-bugs] [-hack]
[-www] [-WWW] [-HTTP] [-engine id] [-tlsextdebug] [-no_ticket] [-id_prefix
arg] [-rand file(s)] [-nextprotoneg protocols]
DESCRIPTION
The s_server command implements a generic SSL/TLS server which listens for
connections on a given port using SSL/TLS.
OPTIONS
-accept port
the TCP port to listen on for connections. If not specified 4433 is
used.
-context id
sets the SSL context id. It can be given any string value. If this
option is not present a default value will be used.
**** ***** ******
选项说明:
-accept port:监听的TCP端口。缺省为4433。
-context id:设置SSL context的id, 可以设置为任何值。SSL context是会话ID的上下文。也可以不设置这个选项,,有缺省的给你用的。
-verify depth、-Verify depth:意义和s_client的这个选项一样,但同时表示必须验证client的证书。不记得server对client的证书验证是可以选的吗?-verify表示向client要求证书,但client还是可以选择不发送证书,-Verify表示一定要client的证书验证,否则握手失败。
-crl_check、-crl_check_all:检查客户端的证书是否在CA的废除列表中。CRL(s)在证书文件中。crl_check_all表示要检查所有的CA证书中的废除列表。
-cert filename:使用的证书文件名。大多数服务器算法套件需要一个证书,还有一些需要证书的公钥类型,例如DSS算法组件需要证书(包含一个DSS(DSA)密钥)。缺省使用 ./server.pem。
-certform DER|PEM:证书的格式,一般为DER或PEM。缺省为PEM。
-key filename:使用的私有密钥文件。如果没有指定,那么证书文件会被使用(使用的是证书公钥值)。
-keyform DER|PEM:私有密钥文件的格式,一般为DER或PEM。缺省为PEM。
-pass arg:私钥保护口令来源。
-dcert filename、-dkey keyfile:指定一个附加的证书文件和私有密钥文件。不同的cipher需要不同的证书和私有密钥文件。这个不同的cipher主要指cipher里面的不对称加密算法不同。比如基于RSA的cipher需要的是RSA的私有密钥文件和证书,而基于DSA的算法则需要的是DSA的私有密钥文件和证书。这个option可以让这样我们的server同时支持俩种算法的cipher成为可能。
-dcertform DER|PEM:附加证书的格式,一般为DER或PEM。缺省为PEM。
-dkeyform DER|PEM:附加的私有密钥文件的格式,一般为DER或PEM。缺省为PEM。
-dpass arg:附加私钥保护口令来源。
-dhparam filename:使用的DH参数文件名。如果没有设置,那么server会试图去从证书文件里面获得这些参数。如果证书里面没有这么参数,一些hard code的参数就被调用。
-name_curve arg:椭圆曲线算法的选择类型。
-nbio_test:检查非阻塞socket的I/O运行情况。
-nbio:使用非阻塞socket。
-crlf:把在终端输入的换行回车转化成/r/n送出去。
-debug:打印所有的调试信息。
-msg:用16进制显示所有的协议数据。
-state:打印SSL session的状态, ssl也是一个协议,当然有状态。
-CApath directory:设置信任CA文件所在路径,此路径中的ca文件名采用特殊的形式:xxx.0,其中xxx为CA证书持有者的哈希值,它通过x509 -hash命令获得。
-CAfile filename:某文件,里面是所有你信任的CA的证书的内容。当你要建立client的证书链的时候也需要用到这个文件。
-nocert:如果server不想使用任何证书,就设置这个选项。目前只有anonymous DH算法有需要这么做。
-cipher cipherlist:由我们自己来决定选用什么加密算法,尽管是由server来决定使用什么算法列表,但它一般都会采用我们送过去的cipher列表里的第一个cipher。
-quiet:禁止打印sessionhe 和证书信息值。
-no_tmp_rsa:现在的接口cipher有时会使用临时RSA密钥。那就是说每次对话的时候临时生成密钥对。本选项就是用来禁止这种情况的。
-ssl2、-ssl3、-tls1_1、-tls1_2、-tls1、-dtls1、-no_ssl2、-no_ssl3、-no_tls1、-no_tls1_1、-no_tls1_2:使用的协议状态值。
-no_dhe:如果这个选项被设置,则没有DH参数提供,即不能够使用关于DH相关的ciphers。
-no_ecdhe:能够使用关于ECDH相关的ciphers。
-bugs:打印所有的调试信息。
-hack:这个选项对更早的Netscape SSL代码提供一个可行的解决方案。
-www:当client连接上来的时候,发回一个网页,内容就是SSL握手的一些内容。
-HTTP、-WWW:用来把具体某个文件当网页发回给client的请求。比如client的URL请求是 https://myhost/page.html ,就把 ./page.html发回给client。-engine id:硬件引擎。
-tlsextdebug:打印TLS协议中服务器端接收到的额外信息值。
-no_ticket:不支持RFC4507bis会话类型。
-id_prefix arg:根据arg的值来产生SSL/TLS中的session IDs的前缀。当有多个服务器的时候,大多数用于测试SSL/TLS代码。
-rand file(s):指定随机数种子文件,多个文件间用分隔符分开,windows用“;”,OpenVMS用“,“,其他系统用“:”。
连接的命令:
没有设置 –www、 -WWW这俩个选项,当一个ssl client连接上来的话它所发过来的任何东西都会显示出来,你在终端输入的任何东西都会发回给client。你可以通过在终端输入的行的第一个字母控制一些行为。
q:
中断当前连接,但不关闭server.
Q
中断当前连接,退出程序。
r
进行renegotiate行为。
R
进行renegotiate行为, 并且要求client的证书。
P
在TCP层直接送一些明文。这会使client认为我们没有按协议的游戏规则进行通信而断开连接。
S
打印出session-cache的状态信息。session-cache在编程章节会详细介绍。
注意:
B
openssl s_server -accept 443 -www
可以这样使用。
大多数的web(Netscape 和 MSIE除外)仅仅支持RSA算法套件。因此当使用非RSA格式的证书时,他们不能够连接服务器。
即使指定一个空的CA列表,当请求一个客户端证书时敬爱那个严重的违反协议标准,一些SSL客户端解释说任何CA都可以接受。这个对调试证书用途非常有效。
可以使用B
BUGs:
因为该项目有很多选项,好多用的是老的技术,c代码的s_client很难去读取为什么会被关闭。一个典型的SSL客户端项目将会更加简单的。
普通的算法输出是个错误:它仅仅给出了OpenSSL确认的客户端支持的算法套件列表。
这是一个方法:打印客户端支持的不知名的算法套件的详细信息值。
1.2 s_client的man函数
NAME
s_client - SSL/TLS client program
SYNOPSIS
openssl s_client [-connect host:port] [-verify depth] [-cert filename]
[-certform DER|PEM] [-key filename] [-keyform DER|PEM] [-pass arg] [-CApath
directory] [-CAfile filename] [-trusted_first] [-krb5svc service] [-keytab
filename] [-reconnect] [-pause] [-showcerts] [-debug] [-msg] [-nbio_test]
[-state] [-nbio] [-crlf] [-ign_eof] [-no_ign_eof] [-quiet] [-ssl2] [-ssl3]
[-tls1] [-tls1_1] [-tls1_2] [-dtls1] [-no_ssl2] [-no_ssl3] [-no_tls1]
[-no_tls1_1] [-no_tls1_2] [-fallback_scsv] [-bugs] [-cipher cipherlist]
[-starttls protocol] [-engine id] [-tlsextdebug] [-no_ticket] [-sess_out
filename] [-sess_in filename] [-rand file(s)] [-nextprotoneg protocols]
DESCRIPTION
The s_client command implements a generic SSL/TLS client which connects to
a remote host using SSL/TLS. It is a very useful diagnostic tool for SSL
servers.
OPTIONS
-connect host:port
This specifies the host and optional port to connect to. If not
specified then an attempt is made to connect to the local host on port
4433.
-cert certname
The certificate to use, if one is requested by the server. The default
is not to use a certificate.
选项说明:
-host host:设置服务地址。
-port port:设置服务端口,默认为4433。
-connect host:port:设置服务器地址和端口号。如果没有设置,则默认为本地主机以及端口号4433。
-verify depth:设置证书的验证深度。记得CA也是分层次的吧?如果对方的证书的签名CA不是Root CA,那么你可以再去验证给该CA的证书签名的CA,一直到Root CA. 目前的验证操作即使这条CA链上的某一个证书验证有问题也不会影响对更深层的CA的身份的验证。所以整个CA链上的问题都可以检查出来。当然CA的验证出问题并不会直接造成连接马上断开,好的应用程序可以让你根据验证结果决定下一步怎么走。
-cert filename:使用的证书文件。如果server不要求要证书,这个可以省略。
-certform DER|PEM:证书的格式,一般为DER和PEM。默认为PEM格式。
-key filename:使用的证书私钥文件。
-keyform DER|PEM:证书私钥文件的格式,一般为DER和PEM。默认为PEM格式。
-pass arg:私钥保护口令来源,比如:-pass file:pwd.txt,将私钥保护口令存放在一个文件中,通过此选项来指定,不需要用户来输入口令。
-CApath directory:设置信任CA文件所在路径,此路径中的ca文件名采用特殊的形式:xxx.0,其中xxx为CA证书持有者的哈希值,它通过x509 -hash命令获得。
-CAfile filename:某文件,里面是所有你信任的CA的证书的内容。当你要建立client的证书链的时候也需要用到这个文件。
-reconnect:使用同样的session-id连接同一个server五次,用来测试server的session缓冲功能是否有问题。
-pause:每当读写数据时,sleep 1秒。
-showcerts:显示整条server的证书的CA的证书链。否则只显示server的证书。
-debug:打印所有的调试信息。
-msg:用16进制显示所有的协议数据。
-state:打印SSL session的状态, ssl也是一个协议,当然有状态。
-nbio_test:检查非阻塞socket的I/O运行情况。
-nbio:使用非阻塞socket。
-crlf:把在终端输入的换行回车转化成/r/n送出去。
-ign_eof:当输入文件到达文件尾的时候并不断开连接。
-no_ign_eof:当输入文件到达文件尾的时候断开连接。
-quiet:不打印出session和证书的信息。同时会打开-ign_eof这个选项。
-ssl2、-ssl3、-tls1_1、-tls1_2、-tls1、-dtls1、-no_ssl2、-no_ssl3、-no_tls1、-no_tls1_1、-no_tls1_2:使用的协议状态值。
-bugs:兼容老版本服务端的中的bug。
-cipher cipherlist:由我们自己来决定选用什么加密算法,尽管是由server来决定使用什么算法列表,但它一般都会采用我们送过去的cipher列表里的第一个cipher。
-starttls protocol:protocol可以为smtp或pop3,用于邮件安全传输。
-engine id:硬件引擎。
-tlsextdebug:打印TLS协议中服务器端接收到的额外信息值。
-no_ticket:不支持RFC4507bis会话类型。
-sess_out filename:输出SSL会话信息值到filename中。
-sess_in filename:从filename中获取SSL Session值。
-rand file(s):指定随机数种子文件,多个文件间用分隔符分开,windows用“;”,OpenVMS用“,“,其他系统用“:”。
连接选项:
如果一个确认的连接到SSL服务器,并显示了从服务器端接收到了的数据,任何操作都被发送到服务器。当交互(这意味着没有给出B<-quiet> 、B<-ign_eof>这两个选项)的时候,如果命令行B或到达了文件的结尾,连接将会被断开。
注意:
S_client可用于调试SSL服务器端。为了连接一个SSL HTTP服务器,命令如下:
openssl s_client -connect servername:443
一旦和某个SSL server建立连接之后,所有从server得到的数据都会被打印出来,所有你在终端上输入的东西也会被送给server. 这是人机交互式的。这时候不能设置-quiet和 -ign_eof这俩个选项。如果输入的某行开头字母是R,那么在这里session会重启, 如果输入的某行开头是Q,那么连接会被断开。你完成整个输入之后连接也会被断开。
如果连接成功,你可以用HTTP的指令,比如"GET /"什么的去获得网页了。
如果握手失败,原因可能有以下几种:
1. server需要验证你的证书,但你没有证书。
2. 如果肯定不是原因1,那么就慢慢一个一个set以下几个选项:-bugs, -ssl2, -ssl3, -tls1,-no_ssl2,-no_ssl3, -no_dtls。
3. 这可能是因为对方的server处理SSL有bug。
有的时候,client会报错:没有证书可以使用,或者供选择的证书列表是空的。这一般是因为Server没有把给你签名的CA的名字列进它自己认为可以信任的CA列表,你可以用检查一下server的信任CA列表。有的http server只在 client给出了一个URL之后才验证client的证书,这中情况下要设置 -prexit这个选项,并且送给server一个页面请求。
即使使用-cert指明使用的证书,如果server不要求验证client的证书,那么该证书也不会被验证。所以不要以为在命令行里加了-cert 的参数又连接成功就代表你的证书没有问题。
如果验证server的证书有问题,就可以设置-showcerts来看看server的证书的CA链了。
自从SSLv23客户端hello不能够包含压缩方法或扩展仅仅会被支持。
BUGs:
因为该项目有很多选项,好多用的是老的技术,c代码的s_client很难去读取为什么会被关闭。一个典型的SSL客户端项目将会更加简单的。
如果服务器验证失败,B<-verify>将会退出。
B<-prexit>选项是一个很小的空间。当一个session重启后,我们必须报告。
参考--https://blog.csdn.net/as3luyuan123/article/details/16812071
二.分别制作服务器与客户端的密钥与证书
2.1服务器——
[a4729821@JYstd socket_key]$ openssl req -x509 -days 365 -newkey rsa:2048 -keyout server.pem -out server.pem
Generating a 2048 bit RSA private key
.........................................................+++
..+++
writing new private key to 'server.pem'
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:CN
State or Province Name (full name) []:Hubei
Locality Name (eg, city) [Default City]:Wuhan
Organization Name (eg, company) [Default Company Ltd]:lingyun
Organizational Unit Name (eg, section) []:IT
Common Name (eg, your name or your server's hostname) []:www.10086.com
Email Address []:[email protected]
[a4729821@JYstd socket_key]$ ls
server.pem
打印信息
[a4729821@JYstd socket_key]$ openssl x509 -in server.pem -noout -text
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 14208309324865622410 (0xc52e0e0d577c518a)
Signature Algorithm: sha1WithRSAEncryption
Issuer: C=CN, ST=Hubei, L=Wuhan, O=lingyun, OU=IT, CN=www.10086.com/[email protected]
Validity
Not Before: Jul 31 06:32:50 2018 GMT
Not After : Jul 31 06:32:50 2019 GMT
Subject: C=CN, ST=Hubei, L=Wuhan, O=lingyun, OU=IT, CN=www.10086.com/[email protected]
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:dd:b3:08:d5:be:2f:94:ae:9c:b7:9e:0b:22:1e:
d4:f6:06:55:bf:55:06:fe:6b:e5:33:cd:c7:ed:e8:
75:46:81:c1:51:d9:90:f8:ec:28:50:81:4b:5a:4d:
df:7e:59:97:48:43:4d:59:a6:6b:f0:54:60:51:16:
2b:7a:8f:77:cc:09:ca:1e:7d:a5:ac:bf:c4:65:5a:
1b:e8:f9:61:1b:fa:8a:00:7a:e1:99:2e:ad:f5:6b:
74:49:c8:f1:fa:bb:d7:8f:d8:89:c4:cc:81:de:1a:
61:94:df:fc:ff:94:74:f2:2c:cf:83:60:2a:12:7d:
0f:bd:e8:10:12:eb:12:52:26:08:1d:88:5b:e8:24:
ae:e2:35:3b:3f:3a:45:90:b2:e6:d3:ae:15:7a:6d:
9c:6e:84:fa:4e:00:4d:90:bc:2f:e4:26:a0:ce:42:
61:f6:c1:4f:54:32:06:ed:21:28:2b:65:b3:c0:d3:
df:fe:5a:f6:25:8d:15:15:06:97:4d:17:df:25:ef:
3d:2c:a9:7e:88:15:f5:cb:86:ac:7d:79:6e:c4:d3:
ce:17:08:67:4a:03:68:bd:6d:11:cd:d3:f9:58:99:
2c:c3:f9:25:56:83:2c:6a:9b:9a:5a:0b:48:ce:4b:
f4:24:71:27:36:df:3c:70:24:4e:9e:5f:41:ee:c9:
7a:d7
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Subject Key Identifier:
08:1C:FE:EA:93:CC:43:67:BC:A1:34:24:75:4F:F3:A9:7B:53:89:BB
X509v3 Authority Key Identifier:
keyid:08:1C:FE:EA:93:CC:43:67:BC:A1:34:24:75:4F:F3:A9:7B:53:89:BB
X509v3 Basic Constraints:
CA:TRUE
Signature Algorithm: sha1WithRSAEncryption
d8:29:4e:a7:64:97:0f:3f:fd:1f:d8:c2:c2:54:09:2f:ff:2c:
be:8e:c0:20:64:20:a5:4c:b0:6d:9d:45:62:c1:07:39:2b:7d:
3b:e0:1f:e9:c5:a6:a8:c6:ca:f3:51:4a:6c:07:be:11:7c:e9:
9e:c6:ec:12:38:68:91:67:c8:f1:cb:29:db:1f:10:b5:9e:8a:
be:f0:07:f4:41:c9:9f:f4:9d:12:17:53:46:f9:5f:0c:fc:32:
39:11:cf:1c:4c:df:da:54:6f:93:2f:73:0f:97:5a:fe:69:88:
b1:22:22:e4:53:41:5e:b9:2d:ef:4c:03:ed:eb:c4:89:46:85:
87:8b:f4:7a:3c:9c:e4:ac:c3:4d:81:2e:40:20:4e:8c:39:31:
e0:5f:da:cb:50:b2:0a:e2:4b:04:c6:a1:08:92:74:ad:63:fd:
c4:99:3b:42:1e:b2:6d:fd:01:4c:f8:89:a2:3c:e3:43:5f:6d:
5f:02:8f:4f:fd:f6:74:97:b6:8d:c0:bb:68:6c:c3:61:24:5a:
dd:ed:03:96:6a:0b:63:65:31:46:01:69:92:99:c2:03:fb:db:
e0:f6:76:79:dc:a1:3e:64:dd:4c:3f:37:80:00:ef:8c:3c:fc:
6f:e7:82:f5:81:81:54:45:34:6a:8c:6e:5a:fa:2b:49:1e:fd:
98:16:16:7f
2.2客户端——
[a4729821@JYstd socket_key]$ openssl req -x509 -days 365 -newkey rsa:2048 -keyout client.pem -out client.pem
Generating a 2048 bit RSA private key
............+++
..................+++
writing new private key to 'client.pem'
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:CN
State or Province Name (full name) []:guangxi
Locality Name (eg, city) [Default City]:nanning
Organization Name (eg, company) [Default Company Ltd]:lanxiang
Organizational Unit Name (eg, section) []:muzhu
Common Name (eg, your name or your server's hostname) []:www.10000.com
Email Address []:[email protected]
打印密钥信息
[a4729821@JYstd socket_key]$ openssl x509 -in client.pem -noout -text
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 13484683701559912667 (0xbb233861b3fa68db)
Signature Algorithm: sha1WithRSAEncryption
Issuer: C=CN, ST=guangxi, L=nanning, O=lanxiang, OU=muzhu, CN=www.10000.com/[email protected]
Validity
Not Before: Jul 31 06:37:16 2018 GMT
Not After : Jul 31 06:37:16 2019 GMT
Subject: C=CN, ST=guangxi, L=nanning, O=lanxiang, OU=muzhu, CN=www.10000.com/[email protected]
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:d9:8b:04:41:27:bc:a1:e3:cc:6e:96:81:ee:50:
82:9e:93:bb:ee:8c:3c:6b:55:8b:f7:50:95:d2:d6:
cd:36:b6:d4:b2:9b:85:d9:87:2e:c7:42:74:b3:53:
64:54:46:86:2e:df:57:f9:6a:a8:47:ac:da:ac:fa:
38:08:94:34:d2:b9:b0:b9:fa:a6:31:ba:79:a2:15:
3f:2d:65:12:32:05:59:ba:d6:f6:df:73:2b:d6:76:
eb:d4:92:d1:93:49:f3:4e:22:f1:7b:4c:0d:df:9f:
1b:63:a6:53:43:11:f9:e6:ca:39:6e:cc:62:cb:d2:
96:c1:3c:c3:4d:bc:49:b6:24:1f:8d:72:34:66:0a:
21:86:94:d2:e0:94:18:57:8d:63:0e:a6:51:69:91:
d4:7c:95:d1:2f:37:07:b5:20:29:16:20:cd:61:b0:
50:84:4b:a7:d7:c5:bd:3f:67:c6:4d:49:f6:57:f9:
48:f8:8c:27:4b:8c:ff:ae:0c:b4:cf:8b:63:c8:fc:
0b:47:86:3c:85:ee:f9:f8:d4:a7:5f:4b:3f:f9:d5:
46:a7:b7:ec:b5:0c:f9:04:49:a0:eb:66:f7:ea:03:
23:0c:fe:b7:4b:ed:36:0d:7e:3a:69:87:57:7b:0b:
0d:05:99:ab:29:f0:0b:fd:56:df:5d:f3:1e:bc:82:
1c:0f
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Subject Key Identifier:
5F:EA:A5:0C:C5:BD:4A:6D:39:DB:E0:AA:29:BB:69:BC:78:14:9D:5A
X509v3 Authority Key Identifier:
keyid:5F:EA:A5:0C:C5:BD:4A:6D:39:DB:E0:AA:29:BB:69:BC:78:14:9D:5A
X509v3 Basic Constraints:
CA:TRUE
Signature Algorithm: sha1WithRSAEncryption
bf:54:86:2b:2d:ba:d5:0c:4e:5b:e4:05:c1:a1:8f:1e:b4:07:
3b:02:11:3a:d9:fa:c4:ac:8e:13:77:90:bd:49:3a:60:6b:0d:
ad:07:72:07:4a:9c:ad:2e:22:91:a5:cb:d0:8c:0e:22:c4:2a:
fd:ea:fd:32:5e:63:51:c6:c6:9b:fb:af:0a:26:79:85:e7:b3:
7b:3a:45:60:c4:fd:43:82:d5:b9:e5:e6:e7:b1:74:c3:36:46:
41:b9:7c:a9:5d:9e:a8:53:16:3f:29:33:b4:b2:67:c3:c8:13:
61:89:42:db:46:26:3c:a3:80:ef:95:11:93:73:8f:f6:6f:38:
73:7c:dc:5f:97:4c:2a:53:05:cd:45:6b:71:80:29:51:a0:25:
6d:04:23:06:9b:6f:15:18:97:ba:c9:11:7f:2b:18:38:8b:b3:
c8:51:e9:26:6d:e7:20:94:ab:ed:e7:e8:e6:6c:79:34:75:1f:
26:d1:0c:cf:28:75:26:f0:fb:c5:6a:1c:4b:16:51:6d:0e:96:
6f:db:d5:a5:0b:20:b9:25:33:c4:fd:70:d8:45:fc:55:f2:6e:
8a:a5:a9:c2:cb:4b:70:29:71:cf:7e:1a:4e:2b:67:3b:7b:73:
72:e1:54:7b:6e:44:e7:85:ef:34:07:70:30:d0:fa:87:6e:53:
5b:36:95:fc
三.
OpenSSL是一个开放源代码的SSL协议的产品实现,它采用C语言作为开发语言,具备了跨系统的性能。调用OpenSSL的函数就可以实现一个SSL加密的安全数据传输通道,从而保护客户端和服务器之间数据的安全。
头文件:
#include
#include
基于OpenSSL的程序都要遵循以下几个步骤:
(1 ) OpenSSL初始化
在使用OpenSSL之前,必须进行相应的协议初始化工作,这可以通过下面的函数实现:
int SSL_library_init(void);
SSL_load_error_strings();
OpenSSL_add_ssl_algorithms();
(2 ) 选择会话协议
在利用OpenSSL开始SSL会话之前,需要为客户端和服务器制定本次会话采用的协议,目前能够使用的协议包括TLSv1.0、SSLv2、SSLv3、SSLv2/v3。
需要注意的是,客户端和服务器必须使用相互兼容的协议,否则SSL会话将无法正常进行。
ctx = SSL_CTX_new(method); /* Create new context */
SSLv3_client_method 是指定ssl要使用的协议。SSL协议由美国 NetScape公司开发的,V1.0版本从没有公开发表过;V2.0版本于1995年2月发布。但是,由于V2.0版本有许多安全漏洞,所以,1996年紧接着就发布了V3.0版本。微软从IE 7开始就已经把浏览器的缺省设置不支持SSL 2.0,但可能是考虑到有些网站还只支持SSL 2.0,所以IE浏览器留了一个可以由用户设置支持SSL 2.0的选项,以便能正常访问只支持SSL 2.0的网站。IE7/IE8支持SSL 3.0和TLS1.0,而IE9还支持TLS1.1和1.2。
在openssl里指定协议很简单,每个协议都有对应的函数,一行代码就可以搞定。
SSL_METHOD* TLSv1_client_method(void); TLSv1.0 协议
SSL_METHOD* SSLv2_client_method(void); SSLv2 协议
SSL_METHOD* SSLv3_client_method(void); SSLv3 协议
SSL_METHOD* SSLv23_client_method(void); SSLv2/v3 协议
SSL_CTX_new创建ssl上下文,这里面很多全局变量要被各个阶段共享。
(3 ) 创建会话环境
在OpenSSL中创建的SSL会话环境称为CTX,使用不同的协议会话,其环境也不一样的。
申请SSL会话环境的OpenSSL函数是:
SSL_CTX *SSL_CTX_new(SSL_METHOD * method);
当SSL会话环境申请成功后,还要根据实际的需要设置CTX的属性,通常的设置是指定SSL握手阶段证书的验证方式和加载自己的证书。
制定证书验证方式的函数是:
int SSL_CTX_set_verify(SSL_CTX *ctx,int mode,int(*verify_callback),int(X509_STORE_CTX *));
设置证书验证的方式。第一个参数是当前的CTX指针,第二个是验证方式,如果是要验证对方的话,就使用SSL_VERIFY_PEER。不需要的话,使用 SSL_VERIFY_NONE.一般情况下,客户端需要验证对方,而服务器不需要。第三个参数是处理验证的回调函数,如果没有特殊的需要,使用空指针就可以了。
为SSL会话环境加载CA证书的函数是:
SSL_CTX_load_verify_location(SSL_CTX *ctx,const char *Cafile,const char *Capath);
SSL_CTX_load_verify_locations用于加载受信任的CA证书,CAfile如果不为NULL,则他指向的文件包含PEM编码格式的一个或多个证书,可以用e.g.来简要介绍证书内容
CApath如果不为NULL,则它指向一个包含PEM格式的CA证书的目录,目录中每个文件包含一份CA证书,文件名是证书中CA名的HASH值
证书的编码类型一般分为两种:PEM格式和ASN1格式,即Base64编码格式和DER编码文件,它们分别传入SSL_FILETYPE_PEM、SSL_FILETYPE_ASN1
缺省mode是SSL_VERIFY_NONE,如果想要验证对方的话,便要将此项变成SSL_VERIFY_PEER.SSL/TLS中缺省只验证server,如果没有设置 SSL_VERIFY_PEER的话,客户端连证书都不会发过来
//验证方式
SSL_VERIFY_NONE
Server mode: the server will not send a client certificate
request to the client, so the client will not send a
certificate.
Client mode: if not using an anonymous cipher (by default
disabled), the server will send a certificate which will be
checked. The result of the certificate verification process can
be checked after the TLS/SSL handshake using the
SSL_get_verify_result(3) function. The handshake will be
continued regardless of the verification result.
SSL_VERIFY_PEER
Server mode: the server sends a client certificate request to
the client. The certificate returned (if any) is checked. If
the verification process fails, the TLS/SSL handshake is
immediately terminated with an alert message containing the
reason for the verification failure. The behaviour can be
controlled by the additional SSL_VERIFY_FAIL_IF_NO_PEER_CERT and
SSL_VERIFY_CLIENT_ONCE flags.
Client mode: the server certificate is verified. If the
verification process fails, the TLS/SSL handshake is immediately
terminated with an alert message containing the reason for the
verification failure. If no server certificate is sent, because
an anonymous cipher is used, SSL_VERIFY_PEER is ignored.
SSL_VERIFY_FAIL_IF_NO_PEER_CERT
Server mode: if the client did not return a certificate, the
TLS/SSL handshake is immediately terminated with a "handshake
failure" alert. This flag must be used together with
SSL_VERIFY_PEER.
Client mode: ignored
为SSL会话加载用户证书的函数是:
SSL_CTX_use_certificate_file(SSL_CTX *ctx, const char *file,int type);
为SSL会话加载用户私钥的函数是:
SSL_CTX_use_PrivateKey_file(SSL_CTX *ctx,const char* file,int type);
在将证书和私钥加载到SSL会话环境之后,就可以调用下面的函数来验证私钥和证书是否相符:
int SSL_CTX_check_private_key(SSL_CTX *ctx);
(4) 建立SSL套接字
SSL套接字是建立在普通的TCP套接字基础之上,在建立SSL套接字时可以使用下面的一些函数:
SSL *SSl_new(SSL_CTX *ctx);
//申请一个SSL套接字
int SSL_set_fd(SSL *ssl,int fd);)
//绑定读写套接字
int SSL_set_rfd(SSL *ssl,int fd);
//绑定只读套接字
int SSL_set_wfd(SSL *ssl,int fd);
//绑定只写套接字
(5) 完成SSL握手
在成功创建SSL套接字后,客户端应使用函数SSL_connect( )替代传统的函数connect( )来完成握手过程:
int SSL_connect(SSL *ssl);
而对服务器来讲,则应使用函数SSL_ accept ( )替代传统的函数accept ( )来完成握手过程:
int SSL_accept(SSL *ssl);
握手过程完成之后,通常需要询问通信双方的证书信息,以便进行相应的验证,这可以借助于下面的函数来实现:
X509 *SSL_get_peer_certificate(SSL *ssl);
该函数可以从SSL套接字中提取对方的证书信息,这些信息已经被SSL验证过了。
X509_NAME *X509_get_subject_name(X509 *a);
该函数得到证书所用者的名字。
(6) 进行数据传输
当SSL握手完成之后,就可以进行安全的数据传输了,在数据传输阶段,需要使用SSL_read( )和SSL_write( )来替代传统的read( )和write( )函数,来完成对套接字的读写操作:
int SSL_read(SSL *ssl,void *buf,int num);
int SSL_write(SSL *ssl,const void *buf,int num);
(7 ) 结束SSL通信
当客户端和服务器之间的数据通信完成之后,调用下面的函数来释放已经申请的SSL资源:
int SSL_shutdown(SSL *ssl);
//关闭SSL套接字
void SSl_free(SSL *ssl);
//释放SSL套接字
void SSL_CTX_free(SSL_CTX *ctx);
//释放SSL会话环境
服务器代码:
/*********************************************************************************
* Copyright: (C) 2018 lingyun
* All rights reserved.
*
* Filename: serve.c
* Description: This file
*
* Version: 1.0.0(08/07/2018)
* Author: huangjy <[email protected]>
* ChangeLog: 1, Release initial version on "08/07/2018 02:31:27 PM"
*
********************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAXBUF 1024
#define CACERT "./ca/ca.crt"
#define MYKEY "./serve_key/serve.key"
#define MYCERT "./serve_key/serve.crt"
void ShowCerts(SSL *ssl)
{
X509 *cert;
char *line;
cert = SSL_get_peer_certificate(ssl);
if(cert != NULL)
{
line = X509_NAME_oneline(X509_get_subject_name(cert),0,0);
printf("cert: %s\n",line);
free(line);
//获取证书拥有者
line = X509_NAME_oneline(X509_get_issuer_name(cert),0,0);
printf("Issuer: %s\n",line);
free(line);
//获取证书发布者
X509_free(cert);
}
else
{
printf("no cert message \n");
}
}
int main(int argc,char **argv)
{
int sock_fd,conn_fd;
char buf[MAXBUF];
unsigned int port;
unsigned int recv_length;
struct sockaddr_in peeraddr;
struct sockaddr_in serveraddr;
socklen_t peer_len = sizeof(peeraddr) ;
SSL *ssl;
SSL_CTX *ctx; //会话环境
SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
ctx = SSL_CTX_new(SSLv23_server_method());
memset(&serveraddr,0,sizeof(serveraddr));
if(ctx == NULL)
{
ERR_print_errors_fp(stdout);
exit(1);
}
if(argv[1])
{
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
}
else
{
serveraddr.sin_addr.s_addr = htons(INADDR_ANY);
}
printf("serveraddr = %d\n",serveraddr.sin_addr.s_addr);
if(argv[2])
{
port = atoi(argv[2]);
}
else
{
port = 8888;
}
printf("port = %d\n",port);
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(port);
if((sock_fd = socket(AF_INET,SOCK_STREAM,0))<0)
{
perror("Socket");
exit(errno);
}
printf("Socket created! \n");
if(bind(sock_fd,(const struct sockaddr *)&serveraddr,sizeof(serveraddr))<0)
{
perror("blind");
exit(1);
}
if(listen(sock_fd,10)<0)
{
perror("listen");
exit(1);
}
SSL_CTX_set_verify(ctx,SSL_VERIFY_PEER,NULL); //设置验证方式
SSL_CTX_load_verify_locations(ctx,CACERT,NULL); //加载CA证书
if( SSL_CTX_use_certificate_file(ctx,MYCERT,SSL_FILETYPE_PEM)<0)//加载服务器证书
{
ERR_print_errors_fp(stdout);
exit(1);
}
printf("加载服务器证书成功!\n");
if( SSL_CTX_use_PrivateKey_file(ctx,MYKEY,SSL_FILETYPE_PEM)<0)//加载服务器私钥
{
ERR_print_errors_fp(stdout);
exit(1);
}
printf("加载服务器私钥成功!\n");
if(SSL_CTX_check_private_key(ctx)<0) //验证密钥与证书是否匹配
{
ERR_print_errors_fp(stdout);
exit(1);
}
printf("证书与密钥是匹配的\n");
printf("blind and listen success 等待客户端\n");
while(1)
{
if((conn_fd = accept(sock_fd,(struct sockaddr *)&peeraddr,&peer_len))<0)
{
perror("accept");
exit(errno);
}
else
{
printf("\n======客户端连接成功=====\n");
printf(" IP = %s ,PORT = %d \n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));
}
ssl = SSL_new(ctx); //申请套接字
SSL_set_fd(ssl,conn_fd); //绑定读写套接字
if(SSL_accept(ssl)<0)
{
ERR_print_errors_fp(stdout);
close(conn_fd);
break;
}
else
{
printf("Accepted with %s encryption\n",SSL_get_cipher(ssl));
ShowCerts(ssl);
}
while(1)
{
memset(buf,0,sizeof(buf));
printf("start read from client:");
if((recv_length = SSL_read(ssl,buf,sizeof(buf)))<0)
{
printf("client has closed!\n");
break;
}
else
{
printf("client say [%d] bytes:%s\n",recv_length,buf);
}
memset(buf,0,sizeof(buf));
fgets(buf,sizeof(buf),stdin);
SSL_write(ssl,buf,strlen(buf));
}
SSL_shutdown(ssl);
SSL_free(ssl);
close(conn_fd);
}
SSL_CTX_free(ctx);
close(sock_fd);
return 0;
}
客户端代码:
/*********************************************************************************
Copyright: (C) 2018 lingyun
* All rights reserved.
*
* Filename: client.c
* Description: This file
*
* Version: 1.0.0(08/05/2018)
* Author: huangjy <[email protected]>
* ChangeLog: 1, Release initial version on "08/05/2018 02:55:12 PM"
*
********************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define CACERT "./ca/ca.crt"
#define MYKEY "./client_key/client.key"
#define MYCERT "./client_key/client.crt"
#define MAXBUF 1024
void ShowCerts(SSL * ssl)
{
X509 *cert;
char *line;
cert = SSL_get_peer_certificate(ssl); //从SSL套接字提取证书信息
if (cert != NULL)
{
printf("num cert messsage:\n");
line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
printf("cert: %s\n", line); //获取证书拥有者名字
free(line);
line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
printf("Issuer: %s\n", line);
free(line); //获取证书颁布者名字
X509_free(cert);
}
else
{
printf("no cert message \n");
}
}
int main(int argc, char **argv)
{
int sockfd, len;
struct sockaddr_in dest;
char buffer[MAXBUF];
const char *server_ip = argv[1];
SSL_CTX *ctx;
SSL *ssl;
if (argc !=3)
{
printf("err param :\n\t%s IPaddress port:\nexample:\t%s 127.0.0.1 80\n",argv[0], argv[0]);
exit(0);
}
SSL_library_init(); // 初始化算法库
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
ctx = SSL_CTX_new(SSLv23_client_method());
if (ctx == NULL)
{
ERR_print_errors_fp(stdout);
exit(1);
}
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("Socket");
exit(errno);
}
printf("socket created\n");
SSL_CTX_set_verify(ctx,SSL_VERIFY_PEER,NULL);//设置通信验证方式
SSL_CTX_load_verify_locations(ctx,CACERT,NULL); //加载CA证书
if(SSL_CTX_use_certificate_file(ctx,MYCERT,SSL_FILETYPE_PEM)<=0)
{
ERR_print_errors_fp(stdout);
exit(1);
}
printf("加载用户证书成功!\n");
if(SSL_CTX_use_PrivateKey_file(ctx,MYKEY,SSL_FILETYPE_PEM)<=0)
{
ERR_print_errors_fp(stdout);
exit(1);
}
printf("加载用户密钥成功!\n");
if(SSL_CTX_check_private_key(ctx)<0)
{
ERR_print_errors_fp(stdout);
exit(1);
}
printf("证书密钥是匹配的!\n");
bzero(&dest, sizeof(dest));
dest.sin_family = AF_INET;
dest.sin_port = htons(atoi(argv[2]));
if((inet_pton(AF_INET,server_ip,(struct in_addr *)&dest.sin_addr.s_addr))<0) //十进制的ip地址转化为用于网络传输的数值格式
{
perror(argv[1]);
exit(errno);
}
printf("address created\n");
if (connect(sockfd, (struct sockaddr *) &dest, sizeof(dest)) != 0)
{
perror("Connect ");
exit(errno);
}
printf("server connected\n");
ssl = SSL_new(ctx);
SSL_set_fd(ssl, sockfd);
if (SSL_connect(ssl) == -1)
{
ERR_print_errors_fp(stderr);
}
else
{
printf("Connected with %s encryption\n", SSL_get_cipher(ssl));
ShowCerts(ssl);
}
bzero(buffer, MAXBUF);
printf("Read string from stin: \n");
while( fgets(buffer,sizeof(buffer),stdin)!=NULL && strncmp(buffer,"quit", 4) )
{
printf("strcmp: %s strlen: %lu\n", buffer, strlen(buffer));
SSL_write(ssl,buffer,strlen(buffer));
printf("send success!\n");
memset(buffer,0,sizeof(buffer));
printf("star to read data from serve:\n");
len = SSL_read(ssl,buffer,sizeof(buffer));
printf("serve say %d byte:%s\n",len,buffer);
memset(buffer,0,sizeof(buffer));
printf("Read string from: \n");
}
printf("client will be closed.see you next time.\n");
SSL_shutdown(ssl);
close(sockfd);
SSL_free(ssl);
SSL_CTX_free(ctx);
return 0;
}