SSL双向认证和SSL单向认证区别
参考:整理
双向认证 SSL 协议要求服务器和用户双方都有证书。单向认证 SSL 协议不需要客户拥有CA证书
单向认证:
流程图:
①客户端–>服务器
客户端SSL协议的版本号,加密算法的种类,产生的随机数,以及其他服务器和客户端之间通讯所需要的各种信息
②服务器–>客户端
SSL协议的版本号,加密算法的种类,随机数以及其他相关信息,同时服务器还将向客户端传送自己的证书。
③客户利用服务器传过来的信息验证服务器的合法性
- 服务器的合法性包括:
- 证书是否过期,发行服务器证书的CA是否可靠
- 发行者证书的公钥能否正确解开服务器证书的"发行者的数字签名"
- 服务器证书上的域名是否和服务器的实际域名相匹配
如果合法性验证没有通过,通讯将断开;如果合法性验证通过,将继续进行④
④客户端随机产生一个用于后面通讯的"对称密码",然后用服务器的公钥(服务器的公钥从步骤②中的服务器的证书中获得)对其加密,然后将加密后的"预主密码"传给服务器。(客户端–>服务器)
⑤如果服务器要求客户的身份认证(在握手过程中为可选)
客户端可以建立一个随机数然后对其进行数据签名,将这个含有签名的随机数和客户自己的证书以及加密过的"预主密码"一起传给服务器。(客户端–>服务器)
⑥如果服务器要求客户的身份认证
- 服务器必须检验客户证书和签名随机数的合法性
- 具体的合法性验证过程包括:
- 客户的证书使用日期是否有效
- 为客户提供证书的CA是否可靠
- 发行CA 的公钥能否正确解开客户证书的发行CA的数字签名
- 检查客户的证书是否在证书废止列表(CRL)中
检验如果没有通过,通讯立刻中断;如果验证通过,服务器将用自己的私钥解开加密的"预主密码 ",然后执行一系列步骤来产生主通讯密码(客户端也将通过同样的方法产生相同的主通讯密码)。(服务器–>客户端)
⑦服务器和客户端用相同的主密码即"通话密码",一个对称密钥用于SSL协议的安全数据通讯的加解密通讯。同时在SSL通讯过程中还要完成数据通讯的完整性,防止数据通讯中的任何变化。(客户端–>服务器)
⑧客户端向服务器端发出信息
指明后面的数据通讯将使用的步骤⑦中的主密码为对称密钥,同时通知服务器客户端的握手过程结束。
⑨服务器向客户端发出信息
指明后面的数据通讯将使用的步骤⑦中的主密码为对称密钥,同时通知客户端服务器端的握手过程结束。
⑩-SSL的握手部分结束
SSL安全通道的数据通讯开始,客户和服务器开始使用相同的对称密钥进行数据通讯,同时进行通讯完整性的检验。
SSL单向认证只要求站点部署了ssl证书就行,任何用户都可以去访问(IP被限制除外等),只是服务端提供了身份认证。
双向认证:
流程图:
① 客户端—连接请求–>服务端
② 服务端–自己的证书以及同证书相关的信息–>客户客户端
③ 客户端检查
服务器送过来的证书是否是由自己信赖的CA中心(如沃通CA)所签发的
如果是,就继续执行协议;如果不是,客户浏览器就给客户一个警告消息:警告客户这个证书不是可以信赖的,询问客户是否需要继续
④ 客户端比较证书里的消息(区别于单向)
例如域名和公钥,与服务端刚刚发送的相关消息是否一致,如果是一致的,客户端认可这个服务端的合法身份。
⑤ 服务端要求客户发送客户自己的证书(区别于单向)
收到后,服务端验证客户的证书,如果没有通过验证,拒绝连接;如果通过验证,服务端获得用户的公钥。
⑥ 客户端告诉服务端自己所能够支持的通讯对称密码方案。
⑦ 服务端从客户发送过来的密码方案中,选择一种加密程度最高的密码方案,用客户的公钥加过密后通知客户端
⑧ 客户端针对这个密码方案,选择一个通话密钥,接着用服务端的公钥加过密后发送给服务端。
⑨ 服务端接收到客户端送过来的消息,用自己的私钥解密,获得通话密钥。
⑩ 服务端、客户端接下来的通讯都是用对称密码方案,对称密钥是加过密的。
双向认证则是需要服务端与客户端提供身份认证,只能是服务端允许的客户能去访问,安全性相对于要高一些。
SSL/TLS 工作流:
- SSL Server 自己生成一个 私钥/公钥对。server.key/server.pub // 私钥加密,公钥解密!
- server.pub 生成一个请求文件 server.req. 请求文件中包含有 server 的一些信息,如域名/申请者/公钥等。
- server 将请求文件 server.req 递交给 CA,CA验明正身后,将用 ca.key和请求文件加密生成 server.crt
- 由于 ca.key 和 ca.crt 是一对, 于是 ca.crt 可以解密 server.crt.
实际应用:
如果 SSL Client 想要校验 SSL server.那么 SSL server 必须要将他的证书 server.crt 传给 client
然后 client 用 ca.crt 去校验 server.crt 的合法性
如果是一个钓鱼网站,那么CA是不会给他颁发合法server.crt证书的,这样client 用ca.crt去校验,就会失败。
比如浏览器作为一个 client,你想访问合法的淘宝网站https://www.taobao.com, 结果不慎访问到 https://wwww.jiataobao.com ,那么浏览器将会检验到这个假淘宝钓鱼网站的非法性,提醒用户不要继续访问!这样就可以保证了client的所有https访问都是安全的。
单向认证与双向认证证书区别:
单向认证:
只有一个对象校验对端的证书合法性。
通常都是client来校验服务器的合法性。那么client需要一个ca.crt,服务器需要server.crt,server.key
双向认证:
相互校验,服务器需要校验每个client,client也需要校验服务器。
server 需要 server.key 、server.crt 、ca.crt
client 需要 client.key 、client.crt 、ca.crt
单向/双向认证流程:
(1)client_hello
客户端发起请求,以明文传输请求信息
包含版本信息,加密套件候选列表,压缩算法候选列表,随机数,扩展字段等信息
相关信息如下:
- 支持的最高TSL协议版本version,从低到高依次 SSLv2 SSLv3 TLSv1 TLSv1.1 TLSv1.2,当前基本不再使用低于 TLSv1 的版本;
- 客户端支持的加密套件 cipher suites 列表, 每个加密套件对应前面 TLS 原理中的四个功能的组合:认证算法 Au (身份验证)、密钥交换算法 KeyExchange(密钥协商)、对称加密算法 Enc (信息加密)和信息摘要 Mac(完整性校验);
- 支持的压缩算法 compression methods 列表,用于后续的信息压缩传输;
- 随机数 random_C,用于后续的密钥的生成;
- 扩展字段 extensions,支持协议与算法的相关参数以及其它辅助信息等,常见的 SNI 就属于扩展字段,后续单独讨论该字段作用。
(2).server_hello+server_certificate+sever_hello_done
- server_hello:
服务端返回协商的信息结果,包括选择使用的协议版本 version,选择的加密套件 cipher suite,选择的压缩算法 compression method、随机数 random_S 等,其中随机数用于后续的密钥协商;
- server_certificates:
服务器端配置对应的证书链,用于身份验证与密钥交换;
- server_hello_done:
通知客户端 server_hello 信息发送结束;
(3).证书校验
[证书链]的可信性 trusted certificate path,方法如前文所述;
- 证书是否吊销 revocation,有两类方式离线 CRL 与在线 OCSP,不同的客户端行为会不同;
- 有效期 expiry date,证书是否在有效时间范围;
- 域名 domain,核查证书域名是否与当前的访问域名匹配,匹配规则后续分析;
(4).client_key_exchange+change_cipher_spec+encrypted_handshake_message(+certificate双向)
- client_key_exchange:
合法性验证通过之后,客户端计算产生随机数字 Pre-master,并用证书公钥加密,发送给服务器;
此时客户端已经获取全部的计算协商密钥需要的信息:两个明文随机数 random_C 和 random_S 与自己计算产生的 Pre-master,计算得到协商密钥;
enc_key=Fuc(random_C, random_S, Pre-Master)
- change_cipher_spec:
客户端通知服务器后续的通信都采用协商的通信密钥和加密算法进行加密通信;
- encrypted_handshake_message:
结合之前所有通信参数的 hash 值与其它相关信息生成一段数据,采用协商密钥 session secret 与算法进行加密,然后发送给服务器用于数据与握手验证;
- certificate:
在client认证完服务器证书后,client会将自己的证书client.crt传给服务器。服务器验证通过后,开始秘钥协商
(5).change_cipher_spec+encrypted_handshake_message
服务器用私钥解密加密的 Pre-master 数据,基于之前交换的两个明文随机数 random_C 和 random_S,计算得到协商密钥:enc_key=Fuc(random_C, random_S, Pre-Master);
计算之前所有接收信息的 hash 值,然后解密客户端发送的 encrypted_handshake_message,验证数据和密钥正确性;
- change_cipher_spec:
验证通过之后,服务器同样发送 change_cipher_spec 以告知客户端后续的通信都采用协商的密钥与算法进行加密通信;
- encrypted_handshake_message:
服务器也结合所有当前的通信参数信息生成一段数据并采用协商密钥 session secret 与算法加密并发送到客户端;
(6).握手结束
客户端计算所有接收信息的 hash 值,并采用协商密钥解密 encrypted_handshake_message,验证服务器发送的数据和密钥,验证通过则握手完成;
(7).加密通信
开始使用协商密钥与算法进行加密通信。
实际wireshark分析:
单向:
我们搭建的SSL/TLS服务器是192.168.111.100,client是192.168.111.101. client 需要认证 server的合法性。
我们只看 TLSv1.1 的数据包:
第一包(No. 25) Client Hello 包,即SSL/TLS单向认证流程的(1)
第二包(No. 27) Server Hello 包,包含服务器证书等。即SSL/TLS单向认证流程的(2)
第三包(No. 28) 服务器证书验证完成,同时发送client key exchange+change cipher spec + encrypted handshake message.即SSL/TLS单向认证流程的(4)
第四包(No. 29)秘钥协商,change cipher spec + encrypted hanshake message.即SSL/TLS单向认证流程的(5)
第五包(No. 30)握手完成。开始上层数据传输。SSL/TLS单向认证流程的(7)
双向:
我们搭建的SSL/TLS服务器是192.168.111.100,client是192.168.111.101. client 需要认证 server的合法性,server也需要认证client的合法性!
我们只看 TLSv1.1 的数据包:
第一包(No. 55) Client Hello 包,即SSL/TLS单向认证流程的(1)
第二包(No. 57) Server Hello 包,包含服务器证书等。即SSL/TLS单向认证流程的(2)
第三包(No. 60) 服务器证书验证完成,同时发送客户端的证书client.crt ,同时包含client key exchange+change cipher spec + encrypted handshake message.即SSL/TLS单向认证流程的(4)
第四包(No. 61)服务器验证客户端证书的合法性。通过后进行秘钥协商,change cipher spec + encrypted hanshake message.即SSL/TLS单向认证流程的(5)
重传包(No. 62)由于网络原因,TCP重传第No. 60包。
第五包(No. 64)握手完成。开始上层数据传输。SSL/TLS单向认证流程的(7)
证书详细工作流
1)申请认证:
服务器需自己生成公钥私钥对pub_svr & pri_svr,同时根据 pri_svr 生成请求文件 csr,提交给CA
csr中含有公钥、组织信息、个人信息(域名)等信息。
2)审核信息:
CA通过线上、线下等多种手段验证申请者提供信息的真实性,如组织是否存在、企业是否合法,是否拥有域名的所有权等。
3)签发证书:
如信息审核通过,CA会向申请者签发认证文件-证书。
证书包含以下信息:
- 申请者公钥
- 申请者的组织信息和个人信息
- 签发机构 CA的信息、有效时间、证书序列号等信息的明文,同时包含一个签名。
- 签名的产生算法:首先使用散列函数计算公开的明文信息的信息摘要,然后,采用 CA的私钥对信息摘要进行加密,密文即签名。(图一中生成server.crt)
4)返回证书:
client如果请求验证服务器,服务器需返回证书文件
5)client验证证书:
client读取证书中的相关的明文信息,采用相同的散列函数计算得到信息摘要,然后,利用对应 CA的公钥解密签名数据,对比证书的信息摘要
如果一致,则可以确认证书的合法性,即公钥合法。客户端然后验证证书相关的域名信息、有效时间是否吊销等信息。
客户端会内置信任CA的证书信息(包含公钥),如果CA不被信任,则找不到对应 CA的证书,证书也会被判定非法。(check可选,我们可以选择不验证服务器证书的有效性)
6)秘钥协商:
验证通过后,Server和Client将进行秘钥协商。接下来Server和Client会采用对称秘钥加密。(对称加密时间性能优)
7)数据传输:
Server和Client采用对称秘钥加密解密数据。
证书等格式说明
- .crt 表示证书, .key表示私钥, .req 表示请求文件,.csr也表示请求文件, .pem表示pem格式,.der表示der格式。
文件拓展名你可以随便命名。只是为了理解需要而命名不同的拓展名。但文件中的信息是有格式的
证书有两种格式:
pem格式和der格式。所有证书,私钥等都可以是pem,也可以是der格式,取决于应用需要。
pem和der格式可以互转:
openssl x509 -in ca.crt -outform DER -out ca.der //pem -> der
openssl x509 -inform der -in ca.der -out ca.pem // der -> pem
- pem格式:经过加密的文本文件,一般有下面几种开头结尾:
-----BEGIN RSA PRIVATE KEY-----
-----END RSA PRIVATE KEY-----
or:
-----BEGIN CERTIFICATE REQUEST-----
-----END CERTIFICATE REQUEST-----
or:
----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
- der格式: 经过加密的二进制文件。
- 证书中含有 申请者公钥、申请者的组织信息和个人信息、签发机构 CA的信息、有效时间、证书序列号等信息的明文,同时包含一个签名。如查看百度证书详细信息。
命令查看证书详细信息
openssl x509 -noout -text -in baiducom.crt
详细信息中,有一个字段: X509v3 Basic Constraints: CA: FALSE
该字段指出该证书是否是 CA证书,还是一般性的非 CA 证书。
- 私钥加密,公钥解密
本地生成SSL相关文件
双向证书生成脚本
makefile.sh
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the axTLS project nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
#
# Generate the certificates and keys for testing.
#
PROJECT_NAME="TLS Project"
# Generate the openssl configuration files.
cat > ca_cert.conf << EOF
[ req ]
distinguished_name = req_distinguished_name
prompt = no
[ req_distinguished_name ]
O = $PROJECT_NAME Dodgy Certificate Authority
EOF
cat > server_cert.conf << EOF
[ req ]
distinguished_name = req_distinguished_name
prompt = no
[ req_distinguished_name ]
O = $PROJECT_NAME
CN = 192.16.108.93
EOF
cat > client_cert.conf << EOF
[ req ]
distinguished_name = req_distinguished_name
prompt = no
[ req_distinguished_name ]
O = $PROJECT_NAME Device Certificate
CN = 192.16.108.93
EOF
mkdir ca
mkdir server
mkdir client
mkdir certDER
# private key generation
openssl genrsa -out ca.key 2048
openssl genrsa -out server.key 2048
openssl genrsa -out client.key 2048
# cert requests
openssl req -out ca.req -key ca.key -new \
-config ./ca_cert.conf
openssl req -out server.req -key server.key -new \
-config ./server_cert.conf
openssl req -out client.req -key client.key -new \
-config ./client_cert.conf
# generate the actual certs.
openssl x509 -req -in ca.req -out ca.crt \
-sha1 -days 3650 -signkey ca.key
openssl x509 -req -in server.req -out server.crt \
-sha1 -CAcreateserial -days 3650 \
-CA ca.crt -CAkey ca.key
openssl x509 -req -in client.req -out client.crt \
-sha1 -CAcreateserial -days 3650 \
-CA ca.crt -CAkey ca.key
openssl x509 -in ca.crt -outform DER -out ca.der
openssl x509 -in server.crt -outform DER -out server.der
openssl x509 -in client.crt -outform DER -out client.der
mv ca.crt ca.key ca/
mv server.crt server.key server/
mv client.crt client.key client/
mv ca.der server.der client.der certDER/
rm *.conf
rm *.req
rm *.srl
修改:
- 修改 CN 域中 IP 地址为你主机/设备的 IP 地址
- [可选]加密位数 1024 修改为你需要的加密位数
终端执行$./makefile.sh
对应文件目录:
ca目录:保存ca的私钥ca.key和证书ca.crt
certDER目录:将证书保存为二进制文件 ca.der client.der server.der
client目录: client.crt client.key
server目录:server.crt server.key
删除脚本
rmfile.sh
rm ca/ -rf
rm certDER/ -rf
rm client/ -rf
rm server/ -rf
将上述代码保存为rmfile.sh,终端执行
将会删除产生过的目录和文件:$./rmfile.sh
CA校验证书测试
我们可在本地使用 CA证书来分别校验由自己颁发的服务器证书 server.crt 和客户端证书 client.crt .
执行命令:
openssl verify -CAfile ca/ca.crt server/server.crt
openssl verify -CAfile ca/ca.crt client/client.crt
单向证书生成脚本:
gencsssl.sh
#!/bin/sh
#用openssl生成带SubjectAltName的自签名证书
#openssl生成CA的时候出错:TXT_DB error number 2
#修改demoCA下 index.txt.attr,将unique_subject = yes 改成 unique_subject = no
rm -rf demoCA
rm -f *.pem
mkdir demoCA
cd demoCA/
mkdir private crl certs newcerts csr
touch index.txt
echo '01' > serial
PREFIXDN="/C=CN/ST=Tianjin/L=Tianjin/O=TLS_Project"
CADN="$PREFIXDN/OU=emdg/CN=gwemdg"
USERDN="$PREFIXDN/OU=emdg/CN=gwemdg"
OPENSSL_CMD="openssl"
echo "create ca rsa ..."
$OPENSSL_CMD genrsa -out private/cakey.pem 2048
echo "create ca req ..."
$OPENSSL_CMD req -new -config ../openssl.cnf -x509 -subj "$CADN" -days 3650 -key private/cakey.pem -out cacert.pem
#$OPENSSL_CMD req -new -config ../openssl.cnf -sha256 -subj "$CADN" -days 3650 -key private/cakey.pem -out cacert.pem
echo "print ..."
$OPENSSL_CMD req -text -noout -in cacert.pem
echo "create server rsa ..."
$OPENSSL_CMD genrsa -out private/server.key 2048
echo "create server req ..."
#$OPENSSL_CMD req -new -config ../openssl.cnf -x509 -subj "$USERDN" -key private/server.key -out csr/server.csr
$OPENSSL_CMD req -new -config ../openssl.cnf -subj "$USERDN" -key private/server.key -out csr/server.csr
cd ..
#$OPENSSL_CMD ca -config ./openssl.cnf -batch -subj "$USERDN" -days 365 -outdir demoCA/certs -infiles demoCA/csr/server.csr -extensions server_cert
$OPENSSL_CMD ca -config ./openssl.cnf -batch -subj "$USERDN" -days 365 -out demoCA/certs/server.crt -infiles demoCA/csr/server.csr
cp demoCA/certs/server.crt demoCA/private/server.pem
cat demoCA/private/server.key >> demoCA/private/server.pem
cd demoCA/
echo "create client rsa ..."
$OPENSSL_CMD genrsa -out private/client.key 2048
echo "create client req ..."
#$OPENSSL_CMD req -new -config ../openssl.cnf -x509 -subj "$USERDN" -key private/client.key -out csr/client.csr
$OPENSSL_CMD req -new -config ../openssl.cnf -subj "$USERDN" -key private/client.key -out csr/client.csr
cd ..
#$OPENSSL_CMD ca -config ./openssl.cnf -batch -subj "$USERDN" -days 365 -outdir demoCA/certs -infiles demoCA/csr/client.csr -extensions client_cert
$OPENSSL_CMD ca -config ./openssl.cnf -batch -subj "$USERDN" -days 365 -out demoCA/certs/client.crt -infiles demoCA/csr/client.csr
cp demoCA/certs/client.crt demoCA/private/client.pem
cat demoCA/private/client.key >> demoCA/private/client.pem
运行目录文件:
gencsssl.sh openssl.cnf
执行:./gencsssl.sh
对应文件目录:
demonCA目录下
cacert.pem
certs目录:client.crt server.crt
csr目录:client.csr server.csr
newcerts目录:01.pem 02.pem
private目录:cakey.pem client.key client.pem server.key server.pem
SSL单向认证代码实例:
服务端:
#include <errno.h>
#include <unistd.h>
#include <malloc.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <resolv.h>
#include "openssl/ssl.h"
#include "openssl/err.h"
#define FAIL -1
int OpenListener(int port)
{
int sd;
struct sockaddr_in addr;
sd = socket(PF_INET, SOCK_STREAM, 0);
bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
if ( bind(sd, (struct sockaddr*)&addr, sizeof(addr)) != 0 )
{
perror("can't bind port");
abort();
}
if ( listen(sd, 10) != 0 )
{
perror("Can't configure listening port");
abort();
}
return sd;
}
SSL_CTX* InitServerCTX(void)
{
SSL_CTX *ctx = NULL;
#if OPENSSL_VERSION_NUMBER >= 0x10000000L
const SSL_METHOD *method;
#else
SSL_METHOD *method;
#endif
SSL_library_init();
OpenSSL_add_all_algorithms(); /* load & register all cryptos, etc. */
SSL_load_error_strings(); /* load all error messages */
method = SSLv23_server_method(); /* create new server-method instance */
ctx = SSL_CTX_new(method); /* create new context from method */
if ( ctx == NULL )
{
ERR_print_errors_fp(stderr);
abort();
}
return ctx;
}
void LoadCertificates(SSL_CTX* ctx, char* CertFile, char* KeyFile)
{
//New lines
if (SSL_CTX_load_verify_locations(ctx, CertFile, KeyFile) != 1)
ERR_print_errors_fp(stderr);
if (SSL_CTX_set_default_verify_paths(ctx) != 1)
ERR_print_errors_fp(stderr);
//End new lines
/* set the local certificate from CertFile */
if ( SSL_CTX_use_certificate_file(ctx, CertFile, SSL_FILETYPE_PEM) <= 0 )
{
ERR_print_errors_fp(stderr);
abort();
}
/* set the private key from KeyFile (may be the same as CertFile) */
if ( SSL_CTX_use_PrivateKey_file(ctx, KeyFile, SSL_FILETYPE_PEM) <= 0 )
{
ERR_print_errors_fp(stderr);
abort();
}
/* verify private key */
if ( !SSL_CTX_check_private_key(ctx) )
{
fprintf(stderr, "Private key does not match the public certificate\n");
abort();
}
printf("LoadCertificates Compleate Successfully.....\n");
}
// void ShowCerts(SSL* ssl)
// {
// X509 *cert;
// char *line;
// cert = SSL_get_peer_certificate(ssl); /* Get certificates (if available) */
// if ( cert != NULL )
// {
// printf("Server certificates:\n");
// line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
// printf("Subject: %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 certificates.\n");
// }
void Servlet(SSL* ssl) /* Serve the connection -- threadable */
{
char buf[1024];
char reply[1024];
int sd, bytes;
const char* HTMLecho="LOVE %s";
if ( SSL_accept(ssl) == FAIL ) /* do SSL-protocol accept */
ERR_print_errors_fp(stderr);
else{
// ShowCerts(ssl); /* get any certificates */
bytes = SSL_read(ssl, buf, sizeof(buf)); /* get request */
if ( bytes > 0 )
{
buf[bytes] = 0;
printf("recv Client msg: \"%s\"\n", buf);
sprintf(reply, HTMLecho, buf); /* construct reply */
if(SSL_write(ssl, reply, strlen(reply))>0)/* send reply */
printf("send: \"%s\"\n", reply);
}
else
ERR_print_errors_fp(stderr);
}
sd = SSL_get_fd(ssl); /* get socket connection */
SSL_free(ssl); /* release SSL state */
close(sd); /* close connection */
}
int main(int count, char *strings[])
{
SSL_CTX *ctx;
int server;
char *portnum;
if ( count != 2 )
{
printf("Usage: %s <portnum>\n", strings[0]);
exit(0);
}
else
{
printf("Usage: %s <portnum>\n", strings[1]);
}
SSL_library_init();
portnum = strings[1];
ctx = InitServerCTX(); /* initialize SSL */
LoadCertificates(ctx, "server.pem", "server.pem"); /* load certs */
server = OpenListener(atoi(portnum)); /* create server socket */
while (1)
{ struct sockaddr_in addr;
socklen_t len = sizeof(addr);
SSL *ssl;
int client = accept(server, (struct sockaddr*)&addr, &len); /* accept connection as usual */
printf("Connection: %s:%d\n",inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
ssl = SSL_new(ctx); /* get new SSL state with context */
SSL_set_fd(ssl, client); /* set connection socket to SSL state */
Servlet(ssl); /* service connection */
}
close(server); /* close server socket */
SSL_CTX_free(ctx); /* release context */
}
客户端:
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <malloc.h>
#include <string.h>
#include <sys/socket.h>
#include <resolv.h>
#include <netdb.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#define FAIL -1
int OpenConnection(const char *hostname, int port)
{
int sd;
struct hostent *host;
struct sockaddr_in addr;
if ((host = gethostbyname(hostname)) == NULL)
{
printf('Eroor: %s\n', hostname);
perror(hostname);
abort();
}
sd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = *(long *)(host->h_addr);
if (connect(sd, (struct sockaddr *)&addr, sizeof(addr)) != 0)
{
close(sd);
perror(hostname);
abort();
}
return sd;
}
SSL_CTX *InitCTX(void)
{
SSL_METHOD *method;
SSL_CTX *ctx;
OpenSSL_add_all_algorithms(); /* Load cryptos, et.al. */
SSL_load_error_strings(); /* Bring in and register error messages */
method = SSLv23_client_method(); /* Create new client-method instance */
printf("line:%d method: %p\n",__LINE__, method);
ctx = SSL_CTX_new(method); /* Create new context */
printf("line:%d ctx: %p\n",__LINE__, ctx);
if (ctx == NULL)
{
ERR_print_errors_fp(stderr);
printf('Eroor: %s\n', stderr);
abort();
}
return ctx;
}
void ShowCerts(SSL *ssl)
{
X509 *cert;
char *line;
cert = SSL_get_peer_certificate(ssl); /* get the server's certificate */
if (cert != NULL)
{
printf("Server certificates:\n");
line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
printf("Subject: %s\n", line);
free(line); /* free the malloc'ed string */
line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
printf("Issuer: %s\n", line);
free(line); /* free the malloc'ed string */
X509_free(cert); /* free the malloc'ed certificate copy */
}
else
printf("No certificates.\n");
}
int main(int count, char *strings[])
{
SSL_CTX *ctx;
int server;
SSL *ssl;
char buf[1024];
int bytes;
char *hostname, *portnum;
if (count != 3)
{
printf("usage: %s <hostname> <portnum>\n", strings[0]);
exit(0);
}
SSL_library_init();
hostname = strings[1];
portnum = strings[2];
ctx = InitCTX();
server = OpenConnection(hostname, atoi(portnum));
ssl = SSL_new(ctx); /* create new SSL connection state */
SSL_set_fd(ssl, server); /* attach the socket descriptor */
if (SSL_connect(ssl) == FAIL) /* perform the connection */
{
printf('Eroor: %s\n', stderr);
ERR_print_errors_fp(stderr);
}
else
{
char *msg = "HelloWorld";
printf("Connected with %s encryption\n", SSL_get_cipher(ssl));
ShowCerts(ssl); /* get any certs */
if(SSL_write(ssl, msg, strlen(msg))>0) /* encrypt & send message */
printf("send: \"%s\"\n", msg);
bytes = SSL_read(ssl, buf, sizeof(buf)); /* get reply & decrypt */
buf[bytes] = 0;
printf("Received: \"%s\"\n", buf);
SSL_free(ssl); /* release connection state */
}
close(server); /* close socket */
SSL_CTX_free(ctx); /* release context */
return 0;
}
运行目录文件:
client client.c server server.c server.pem
运行命令及结果:
./server 7788
Usage: 7788 <portnum>
LoadCertificates Compleate Successfully.....
Connection: 127.0.0.1:33942
recv Client msg: "HelloWorld"
send: "LOVE HelloWorld"
./client 127.0.0.1 7788
line:43 method: 0x7f3965b64a20
line:45 ctx: 0x1a52700
Connected with AES256-GCM-SHA384 encryption
Server certificates:
Subject: /C=CN/ST=Beijing/O=eyou/OU=emdg/CN=dc/emailAddress=support@eyou.net
Issuer: /C=CN/ST=Beijing/L=Beijing/O=eyou/OU=emdg/CN=dc
send: "HelloWorld"
Received: "LOVE HelloWorld"
SSL双向认证代码实例:
服务端:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#define MAXBUF 1024
void ShowCerts(SSL * ssl)
{
X509 *cert;
char *line;
cert = SSL_get_peer_certificate(ssl);
// SSL_get_verify_result()是重点,SSL_CTX_set_verify()只是配置启不启用并没有执行认证,调用该函数才会真证进行证书认证
// 如果验证不通过,那么程序抛出异常中止连接
if(SSL_get_verify_result(ssl) == X509_V_OK){
printf("证书验证通过\n");
}
if (cert != NULL) {
printf("数字证书信息:\n");
line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
printf("证书: %s\n", line);
free(line);
line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
printf("颁发者: %s\n", line);
free(line);
X509_free(cert);
} else
printf("无证书信息!\n");
}
int main(int argc, char **argv) {
int sockfd, new_fd;
socklen_t len;
struct sockaddr_in my_addr, their_addr;
unsigned int myport, lisnum;
char buf[MAXBUF + 1];
SSL_CTX *ctx;
if (argv[1])
myport = atoi(argv[1]);
else
myport = 7838;
if (argv[2])
lisnum = atoi(argv[2]);
else
lisnum = 2;
/* SSL 库初始化 */
SSL_library_init();
/* 载入所有 SSL 算法 */
OpenSSL_add_all_algorithms();
/* 载入所有 SSL 错误消息 */
SSL_load_error_strings();
/* 以 SSL V2 和 V3 标准兼容方式产生一个 SSL_CTX ,即 SSL Content Text */
ctx = SSL_CTX_new(SSLv23_server_method());
/* 也可以用 SSLv2_server_method() 或 SSLv3_server_method() 单独表示 V2 或 V3标准 */
if (ctx == NULL) {
ERR_print_errors_fp(stdout);
exit(1);
}
// 双向验证
// SSL_VERIFY_PEER---要求对证书进行认证,没有证书也会放行
// SSL_VERIFY_FAIL_IF_NO_PEER_CERT---要求客户端需要提供证书,但验证发现单独使用没有证书也会放行
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
// 设置信任根证书
if (SSL_CTX_load_verify_locations(ctx, "ca.crt",NULL)<=0){
ERR_print_errors_fp(stdout);
exit(1);
}
/* 载入用户的数字证书, 此证书用来发送给客户端。 证书里包含有公钥 */
if (SSL_CTX_use_certificate_file(ctx, argv[3], SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stdout);
exit(1);
}
/* 载入用户私钥 */
if (SSL_CTX_use_PrivateKey_file(ctx, argv[4], SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stdout);
exit(1);
}
/* 检查用户私钥是否正确 */
if (!SSL_CTX_check_private_key(ctx)) {
ERR_print_errors_fp(stdout);
exit(1);
}
/* 开启一个 socket 监听 */
if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(1);
} else
printf("socket created\n");
bzero(&my_addr, sizeof(my_addr));
my_addr.sin_family = PF_INET;
my_addr.sin_port = htons(myport);
my_addr.sin_addr.s_addr = INADDR_ANY;
if (bind(sockfd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr))
== -1) {
perror("bind");
exit(1);
} else
printf("binded\n");
if (listen(sockfd, lisnum) == -1) {
perror("listen");
exit(1);
} else
printf("begin listen\n");
while (1) {
SSL *ssl;
len = sizeof(struct sockaddr);
/* 等待客户端连上来 */
if ((new_fd = accept(sockfd, (struct sockaddr *) &their_addr, &len))
== -1) {
perror("accept");
exit(errno);
} else
printf("server: got connection from %s, port %d, socket %d\n",
inet_ntoa(their_addr.sin_addr), ntohs(their_addr.sin_port),
new_fd);
/* 基于 ctx 产生一个新的 SSL */
ssl = SSL_new(ctx);
/* 将连接用户的 socket 加入到 SSL */
SSL_set_fd(ssl, new_fd);
/* 建立 SSL 连接 */
if (SSL_accept(ssl) == -1) {
perror("accept");
close(new_fd);
break;
}
ShowCerts(ssl);
/* 开始处理每个新连接上的数据收发 */
bzero(buf, MAXBUF + 1);
strcpy(buf, "server->client");
/* 发消息给客户端 */
len = SSL_write(ssl, buf, strlen(buf));
if (len <= 0) {
printf("消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n", buf, errno,
strerror(errno));
goto finish;
} else
printf("消息'%s'发送成功,共发送了%d个字节!\n", buf, len);
bzero(buf, MAXBUF + 1);
/* 接收客户端的消息 */
len = SSL_read(ssl, buf, MAXBUF);
if (len > 0)
printf("接收消息成功:'%s',共%d个字节的数据\n", buf, len);
else
printf("消息接收失败!错误代码是%d,错误信息是'%s'\n",
errno, strerror(errno));
/* 处理每个新连接上的数据收发结束 */
finish:
/* 关闭 SSL 连接 */
SSL_shutdown(ssl);
/* 释放 SSL */
SSL_free(ssl);
/* 关闭 socket */
close(new_fd);
}
/* 关闭监听的 socket */
close(sockfd);
/* 释放 CTX */
SSL_CTX_free(ctx);
return 0;
}
客户端:
#include <stdio.h>
#include <string.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 MAXBUF 1024
void ShowCerts(SSL * ssl)
{
X509 *cert;
char *line;
cert = SSL_get_peer_certificate(ssl);
// SSL_get_verify_result()是重点,SSL_CTX_set_verify()只是配置启不启用并没有执行认证,调用该函数才会真证进行证书认证
// 如果验证不通过,那么程序抛出异常中止连接
if(SSL_get_verify_result(ssl) == X509_V_OK){
printf("证书验证通过\n");
}
if (cert != NULL) {
printf("数字证书信息:\n");
line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
printf("证书: %s\n", line);
free(line);
line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
printf("颁发者: %s\n", line);
free(line);
X509_free(cert);
} else
printf("无证书信息!\n");
}
int main(int argc, char **argv)
{
int sockfd, len;
struct sockaddr_in dest;
char buffer[MAXBUF + 1];
SSL_CTX *ctx;
SSL *ssl;
if (argc != 5) {
printf("参数格式错误!正确用法如下:\n\t\t%s IP地址 端口\n\t比如:\t%s 127.0.0.1 80\n此程序用来从某个"
"IP 地址的服务器某个端口接收最多 MAXBUF 个字节的消息",
argv[0], argv[0]);
exit(0);
}
/* SSL 库初始化,参看 ssl-server.c 代码 */
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);
}
// 双向验证
// SSL_VERIFY_PEER---要求对证书进行认证,没有证书也会放行
// SSL_VERIFY_FAIL_IF_NO_PEER_CERT---要求客户端需要提供证书,但验证发现单独使用没有证书也会放行
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
// 设置信任根证书
if (SSL_CTX_load_verify_locations(ctx, "ca.crt",NULL)<=0){
ERR_print_errors_fp(stdout);
exit(1);
}
/* 载入用户的数字证书, 此证书用来发送给服务端。 证书里包含有公钥 */
if (SSL_CTX_use_certificate_file(ctx, argv[3], SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stdout);
exit(1);
}
/* 载入用户私钥 */
if (SSL_CTX_use_PrivateKey_file(ctx, argv[4], SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stdout);
exit(1);
}
/* 检查用户私钥是否正确 */
if (!SSL_CTX_check_private_key(ctx)) {
ERR_print_errors_fp(stdout);
exit(1);
}
/* 创建一个 socket 用于 tcp 通信 */
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Socket");
exit(errno);
}
printf("socket created\n");
/* 初始化服务器端(对方)的地址和端口信息 */
bzero(&dest, sizeof(dest));
dest.sin_family = AF_INET;
dest.sin_port = htons(atoi(argv[2]));
if (inet_aton(argv[1], (struct in_addr *) &dest.sin_addr.s_addr) == 0) {
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");
/* 基于 ctx 产生一个新的 SSL */
ssl = SSL_new(ctx);
SSL_set_fd(ssl, sockfd);
/* 建立 SSL 连接 */
if (SSL_connect(ssl) == -1)
ERR_print_errors_fp(stderr);
else {
printf("Connected with %s encryption\n", SSL_get_cipher(ssl));
ShowCerts(ssl);
}
/* 接收对方发过来的消息,最多接收 MAXBUF 个字节 */
bzero(buffer, MAXBUF + 1);
/* 接收服务器来的消息 */
len = SSL_read(ssl, buffer, MAXBUF);
if (len > 0)
printf("接收消息成功:'%s',共%d个字节的数据\n",
buffer, len);
else {
printf
("消息接收失败!错误代码是%d,错误信息是'%s'\n",
errno, strerror(errno));
goto finish;
}
bzero(buffer, MAXBUF + 1);
strcpy(buffer, "from client->server");
/* 发消息给服务器 */
len = SSL_write(ssl, buffer, strlen(buffer));
if (len < 0)
printf
("消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n",
buffer, errno, strerror(errno));
else
printf("消息'%s'发送成功,共发送了%d个字节!\n",
buffer, len);
finish:
/* 关闭连接 */
SSL_shutdown(ssl);
SSL_free(ssl);
close(sockfd);
SSL_CTX_free(ctx);
return 0;
}
运行目录文件:
将前面生成的证书文件放入当前目录
ca.crt client.c client.crt client.key myclient1 myserver1 server.c server.crt server.key
运行命令及结果:
./myserver1 7838 1 server.crt server.key
socket created
binded
begin listen
server: got connection from 127.0.0.1, port 56566, socket 4
证书验证通过
数字证书信息:
证书: /O=TLS Project Device Certificate/CN=192.16.108.93
颁发者: /O=TLS Project Dodgy Certificate Authority
消息'server->client'发送成功,共发送了14个字节!
接收消息成功:'from client->server',共19个字节的数据
./myclient1 127.0.0.1 7838 client.crt client.key
socket created
address created
server connected
Connected with AES256-GCM-SHA384 encryption
证书验证通过
数字证书信息:
证书: /O=TLS Project/CN=192.168.111.100
颁发者: /O=TLS Project Dodgy Certificate Authority
接收消息成功:'server->client',共14个字节的数据
消息'from client->server'发送成功,共发送了19个字节!