一. 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<s_server>可以用於調試SSL客戶端。爲了從一個web瀏覽器接受連接,命令如下:
openssl s_server -accept 443 -www
可以這樣使用。
大多數的web(Netscape 和 MSIE除外)僅僅支持RSA算法套件。因此當使用非RSA格式的證書時,他們不能夠連接服務器。
即使指定一個空的CA列表,當請求一個客戶端證書時敬愛那個嚴重的違反協議標準,一些SSL客戶端解釋說任何CA都可以接受。這個對調試證書用途非常有效。
可以使用B<sess_id>命令來打印出使用的session參數。
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<R>,被設置則session有可能會被重啓。如果設置的是命令行B<Q>或到達了文件的結尾,連接將會被斷開。
注意:
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/emailAddress=[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/emailAddress=[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/emailAddress=[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/emailAddress=[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 <openssl/ssl.h>
#include <openssl/err.h>
基於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 <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <resolv.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#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 <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/socket.h>
#include <resolv.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#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;
}