基于openssl实现TCP双向认证

文章参考

深入探索 OpenSSL:概念、原理、开发步骤、使用方法、使用场景及代码示例
c++使用OpenSSL基于socket实现tcp双向认证ssl(使用TSL协议)代码实现
SSL握手通信详解及linux下c/c++ SSL Socket代码举例(另附SSL双向认证客户端代码)
SSL/CA 证书及其相关证书文件(pem、crt、cer、key、csr)

TCP实现OpenSSL原理(TCP3次握手+OPENSLL四次握手

SSL全称安全套接字协议层,为了通信安全,使用RSA非对称加密交换密钥,密钥交换完成后使用对称密钥进行通信。(为什么将非对称加密切换称对称加密是为了提供通信效率。非对称加密效率低)
TCP openssl双向认证:即在TCP三次握手的基础上增加一次openssl的4次握手。

实现OPENSSL四次握手
  1. 服务端,在已建立的TCP链接基础上调用SSL_accept
  2. 客户端,在已建立的TCP链接基础上调用SSL_connect
TCP SSL 服务端步骤
  1. 初始化ssl并加载证书和密钥
  2. TCP服务收到客户端连接之后调用SSL_accept进行四次握手
  3. SSL握手建立完成之后调用SSL_readSSL_write收发数据
  4. 使用完成之后关闭并释放SSL资源SSL_shutdownSSL_free
#include <iostream>
#include <tchar.h>
#include <winsock.h>
#include "openssl/ssl.h"

#pragma comment(lib,"ws2_32.lib")
#pragma comment(lib,"libcrypto_static.lib")
#pragma comment(lib,"libssl_static.lib")

using namespace std;

int main()
{
    WSADATA wsdata;
    int errcode = WSAStartup(MAKEWORD(2, 2), &wsdata);
    if (errcode != 0)
    {
        std::cout << "WSAStartup failed,errcode:%d" << errcode<<std::endl;
        return 0;
    }

    //初始化ssl
    SSL_library_init();
    //加载ssl算法库
    OpenSSL_add_all_algorithms();
    //加载ssl 错误信息
    SSL_load_error_strings();
    //以 SSL V2 和 V3 标准兼容方式产生一个 SSL_CTX ,即 SSL Context
    SSL_CTX* ssl_ctx = SSL_CTX_new(SSLv23_server_method());
    if (ssl_ctx == nullptr)
    {
		std::cout << "SSL_CTX_new failed"<<std::endl;
		return 0;
    }

    //加载数字证书
   if (SSL_CTX_use_certificate_chain_file(ssl_ctx, "ca.crt") <= 0)
    {
		printf("SSL_CTX_use_certificate_chain_file failed\r\n");
		return 0;
    }

    //加载私钥
    if (SSL_CTX_use_PrivateKey_file(ssl_ctx, "ca.key", SSL_FILETYPE_PEM) <= 0)
    {
		printf("SSL_CTX_use_PrivateKey_file failed\r\n");
		return 0;
    }
    // 检查用户私钥是否正确 
    if (!SSL_CTX_check_private_key(ssl_ctx))
    {
        printf("SSL_CTX_check_private_key failed\r\n");
        return 0;
    }
    
    SSL_CTX_set_timeout(ssl_ctx, 100);
    //创建socket->bind->list->accept
    SOCKET listen_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_sock == INVALID_SOCKET)
    {
		printf("socket create failed\r\n");
		return 0;
    }

    //setsocketopt resuse port
    unsigned short port = 32100;
    sockaddr_in sin;
    memset(&sin, 0, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_port = htons(port);
    sin.sin_addr.S_un.S_addr = INADDR_ANY;
    if (SOCKET_ERROR == bind(listen_sock, (sockaddr*)&sin, sizeof(sin)))
    {
		printf("socket bind failed\r\n");
		return 0;
    }

    if (SOCKET_ERROR == listen(listen_sock, 5))
    {
		printf("socket listen failed\r\n");
		return 0;
    }

    printf("tcp ssl server:%d\r\n", port);
    while (true)
	{
		char szbuf[1024] = "";
        sockaddr_in peer_addr;
        int naddr_len = sizeof(peer_addr);
        printf("wait client...\r\n");
        SOCKET soct_peer = accept(listen_sock, (sockaddr*)&peer_addr, &naddr_len);
        printf("accept client ,socket:%d\r\n", soct_peer);
        //将socket和ssl绑定(ssl握手)
        SSL* ssl_peer = SSL_new(ssl_ctx);
        if (ssl_peer == nullptr)
        {
            printf("socket:%d,SSL_new failed\r\n", soct_peer);
            goto freessl;
        }
        SSL_set_fd(ssl_peer, soct_peer);
        
        //这里会无限阻塞,为了安全应该异步增加一个超时值,如果超时仍然未连接上则应该close
        if (SSL_accept(ssl_peer) < 0)
        {
            printf("socket:%d,SSL_accept failed\r\n", soct_peer);
            goto freessl;
        }

        //开始ssl读写
        SSL_read(ssl_peer, szbuf, 1024);
        printf("socket[%d] read:%s\r\n", soct_peer, szbuf);
        SSL_write(ssl_peer, szbuf, lstrlenA(szbuf));
    freessl:
		shutdown(soct_peer, 0);
		closesocket(soct_peer);
		
        //释放ssl
        if(ssl_peer)
            SSL_free(ssl_peer);

        continue;
    }
    return 0;
}
TCP SSL 客户端端步骤
  1. TCP客户端连接成功之后调用SSL_connect进行四次握手
  2. SSL握手建立完成之后调用SSL_readSSL_write收发数据
  3. 使用完成之后关闭并释放SSL资源SSL_shutdownSSL_free
#include <iostream>
#include <tchar.h>
#include <winsock.h>
#include "openssl/ssl.h"

#pragma comment(lib,"ws2_32.lib")
#pragma comment(lib,"libcrypto_static.lib")
#pragma comment(lib,"libssl_static.lib")

using namespace std;

int main()
{
	WSADATA wsdata;
	int errcode = WSAStartup(MAKEWORD(2, 2), &wsdata);
	if (errcode != 0)
	{
		std::cout << "WSAStartup failed,errcode:%d" << errcode << std::endl;
		return 0;
	}

	//初始化ssl
	SSL_library_init();
	//加载ssl算法库
	OpenSSL_add_all_algorithms();
	//加载ssl 错误信息
	SSL_load_error_strings();
	//以 SSL V2 和 V3 标准兼容方式产生一个 SSL_CTX ,即 SSL Context
	SSL_CTX* ssl_ctx = SSL_CTX_new(SSLv23_client_method());
	if (ssl_ctx == nullptr)
	{
		std::cout << "SSL_CTX_new failed" << std::endl;
		return 0;
	}
	//SSL_CTX_set_timeout(ssl_ctx, 100);
	//创建socket->connect
	SOCKET cli_sock = socket(AF_INET, SOCK_STREAM, 0);
	if (cli_sock == INVALID_SOCKET)
	{
		printf("socket create failed\r\n");
		return 0;
	}

	//setsocketopt resuse port
	sockaddr_in sin;
	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_port = htons(32100);
	sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	if (SOCKET_ERROR == connect(cli_sock, (sockaddr*)&sin, sizeof(sin)))
	{
		printf("socket connect failed\r\n");
		return 0;
	}
	printf("connect client ,socket:%d\r\n", cli_sock);
	//将socket和ssl绑定(ssl握手)
	char szbuf[1024] = "Hellow word!!!";
	SSL* ssl_peer = SSL_new(ssl_ctx);
	if (ssl_peer == nullptr)
	{
		printf("socket:%d,SSL_new failed\r\n", cli_sock);
		goto freessl;
	}
	SSL_set_fd(ssl_peer, cli_sock);

	//这里会无限阻塞,为了安全应该异步增加一个超时值,如果超时仍然未连接上则应该close
	if (SSL_connect(ssl_peer) < 0)
	{
		printf("socket:%d,SSL_accept failed\r\n", cli_sock);
		goto freessl;
	}
	printf("ssl connectd ok\r\n");
	SSL_write(ssl_peer, szbuf, lstrlenA(szbuf));
	//开始ssl读写
	memset(szbuf, 0, 1024);
	SSL_read(ssl_peer, szbuf, 1024);
	printf("socket[%d] read:%s\r\n", cli_sock, szbuf);
freessl:
	shutdown(cli_sock, 0);
	closesocket(cli_sock);

	//释放ssl
	if (ssl_peer)
		SSL_free(ssl_peer);

	return 0;
}
NOTE

1. 服务端和客户端初始化SSL_CTX参数不同

//以 SSL V2 和 V3 标准兼容方式产生一个 SSL_CTX ,即 SSL Context

//客户端调用openssl客户端的方法
SSL_CTX* ssl_ctx = SSL_CTX_new(SSLv23_client_method());

//服务端调用openssl服务端的方法
SSL_CTX* ssl_ctx = SSL_CTX_new(SSLv23_server_method());

2. SSL_acceptSSL_connect阻塞函数
在同步调用 SSL_acceptSSL_connect的时候会进行阻塞,需要定时检测是否超时,如果超时则关闭当前socket,防止恶意链接。例如拿非openssl的客户端连接openssl的服务端,此时不会存在四次握手,在未发送数据前会一直阻塞下去,等待握手的完成。

3. 认证自签证书
SSL_CTX_set_verify 指定认证类型

  • SSL_VERIFY_NONE:完全忽略验证证书的结果
  • SSL_VERIFY_PEER:希望验证对方的证书。对 CLIENT 来说,如果设置了这样的模式,验证SERVER的证书出了任何错误,SSL 握手失败。对 SERVER 来说,如果设置了这样的模式,CLIENT 倒不一定要把自己的证书交出去。如果 CLIENT 没有交出证书,SERVER 自己决定下一步怎么做
  • SSL_VERIFY_FAIL_IF_NO_PEER_CERT:这是 SERVER 使用的一种模式,在这种模式下, SERVER 会向 CLIENT 要证书。如果 CLIENT 不给,SSL 握手失败。
  • SSL_VERIFY_CLIENT_ONCE:仅能使用在 SSL SESSION RENEGOTIATION 阶段的一种方式。如果不是用这个模式的话,那么在 RENEGOTIATION 的时候,CLIENT 都要把自己的证书送给 SERVER,然后做一番分析。这个过程很消耗 CPU 时间的,而这个模式则不需要 CLIENT 在 RENEGOTIATION 的时候重复送自己的证书了

SSL_CTX_load_verify_locations 指定需要验证的证书位置或者目录(验证对方的证书)

client.cpp 片段

SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, nullptr);
nret = SSL_CTX_load_verify_locations(ssl_ctx, "F:\\project\\testopenssl\\ca\\ca.crt",nullptr);

SSL_CTX_load_verify_locations 如果指定了目录,则在该目录寻找有效证书。但是证书名称是证书的hashcode.0 或者hashcode.1等格式,例如hashcode为cc13a244,则在指定目录寻找cc13a244.0。如果上述指定寻找目录为F:\project\testopenssl\ca\,则查找文件:F:\project\testopenssl\ca\cc13a244.0。下面可通过Process Monitor工具查看。
设置Path->contains->\ca\ (因为我们路径包含\ca)
在这里插入图片描述

4. 常用openssl命令

#form格式 PEM DER
#inform 输入文件的证书格式
#outform 输出文件的证书格式
# 检查 PEM 格式的证书
openssl x509 -text -inform PEM -in certificate.pem
 
# 检查 DER 格式的证书
openssl x509 -text -inform DER -in certificate.der

# 证书PEM,crt转换
openssl x509 -inform PEM -outform DER -in ca.pem -out ca.crt

#计算hash值
openssl x509 -subject_hash_old -in ca0.crt -inform der
  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值