编写一个基于OpenSSL的SSL/TLS服务端(HTTPS)可运行的完整示例

初级代码游戏的专栏介绍与文章目录-CSDN博客

我的github:codetoys,所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。

这些代码大部分以Linux为目标但部分代码是纯C++的,可以在任何平台上使用。

源码指引:github源码指引_初级代码游戏的博客-CSDN博客

本文代码位于httpd目录


        现在不支持SSL/TLS已经不好意思了,原则上旧的代码仅仅需要增加安全连接建立过程然后将原来的接收发送的代码改成openssl的对应函数就可以了。但旧的代码如果写的太分散,处理连接和收发的代码到处都是,那就比较头疼了。

        下面介绍使用openssl的代码示例以及如何把我的socket和http代码如何修改成https。

目录

一、基本功能的分解和包装

1.1 OpenSSL包装类

1.2 初始化SSL_CTX

1.3 建立SSL连接SSL_accept

1.4 发送和接收SSL_write SSL_read

二、服务器的改造

2.1 服务器初始化

2.2 建立SSL连接

2.3 释放

三、测试代码

3.1 测试代码

3.2 配置、运行和结果


一、基本功能的分解和包装

1.1 OpenSSL包装类

        包装类很简单(mySSLTLS.h):

class CmySSLTLS
{
private:
    SSL_CTX* ctx;
    
    int _test_SSLTLS();
public:
    //初始化
    bool Init_SSL_CTX();

    //处理一个连接,以socket为参数
    SSL* getSSL(int sd);
    //释放SSL
    void freeSSL(SSL* ssl);

    //结束
    void free_SSL_CTX();
 
    //测试
    static int test_SSLTLS();
};

        SSL_CTX是上下文(类中唯一的成员变量),设置各种参数和所需的证书文件,SSL代表一个连接,参数是个文件描述符(socket也是文件描述符),SSL实际上与一个连接相关的SSL状态信息。SSL_CTX和SSL用完都要释放。

        最后有个测试函数用来验证。

1.2 初始化SSL_CTX

/*所有需要的参数信息都在此处以#define的形式提供*/
#define CERTF   "server.cer" /*服务端的证书(需经CA签名)*/
#define KEYF   "server.key"  /*服务端的私钥(建议加密存储)*/
#define CACERT "ca.cer" /*CA 的证书*/
#define PORT   60000   /*准备绑定的端口,注意只是测试程序test_SSLTLS使用*/

#define CHK_ERR(err,s) if ((err)==-1) { perror(s); exit(1); }

bool CmySSLTLS::Init_SSL_CTX()
{
	SSL_METHOD const* meth;

	SSL_library_init();
	SSL_load_error_strings();            /*为打印调试信息作准备*/
	OpenSSL_add_ssl_algorithms();        /*初始化*/
	meth = SSLv23_server_method();  /*采用什么协议(SSLv2/SSLv3/TLSv1)在此指定*/

	ctx = SSL_CTX_new(meth);
	if (ctx == NULL)
	{
		thelog << "SSL_CTX_new 失败" << ende;
		return false;
	}

	int mode = SSL_VERIFY_NONE;/*验证与否 SSL_VERIFY_NONE SSL_VERIFY_PEER */
	SSL_CTX_set_verify(ctx, mode, NULL);
	//加载证书
	{
		if (1 != SSL_CTX_load_verify_locations(ctx, CACERT, NULL)) /*若验证,则放置CA证书*/
		{
			ERR_print_errors_fp(stderr);
			thelog << "SSL_CTX_load_verify_locations 失败" << ende;
			return false;
		}

		if (1 != SSL_CTX_use_certificate_file(ctx, CERTF, SSL_FILETYPE_PEM))
		{
			ERR_print_errors_fp(stderr);
			thelog << "SSL_CTX_use_certificate_file 失败" << ende;
			return false;
		}
		if (1 != SSL_CTX_use_PrivateKey_file(ctx, KEYF, SSL_FILETYPE_PEM))
		{
			ERR_print_errors_fp(stderr);
			thelog << "SSL_CTX_use_PrivateKey_file 失败" << ende;
			return false;
		}

		if (1 != SSL_CTX_check_private_key(ctx))
		{
			printf("Private key does not match the certificate public key\n");
			thelog << "SSL_CTX_check_private_key 失败" << ende;
			return false;
		}
	}

	SSL_CTX_set_cipher_list(ctx, "");//"RC4-MD5"
	return true;
}

        初始化做了几件事:

  • 启动库SSL_library_init
  • 加载错误信息字符串SSL_load_error_strings,后面有错就能输出字符串信息
  • 初始化算法库OpenSSL_add_ssl_algorithms
  • 创建上下文对象同时指定协议版本SSL_CTX_new,代码中用的是SSLv23_server_method
  • 设置验证模式,单向还是双向,SSL_VERIFY_NONE就是单向,服务端不需要验证客户端(客户端验证服务端是必须的,否则就不是安全通信了),对web服务一般不用SSL_VERIFY_PEER,因为客户方会太麻烦,对于公开服务也没必要
  • 设置证书文件和私钥文件并验证私钥和证书匹配
  • 设置允许的加密算法SSL_CTX_set_cipher_list,有默认值

        初始化过程和服务端socket操作是没有关系的,仍然需要原来的建立服务socket、监听端口、接受连接的过程,接受连接之后在连接之上通过发送复杂的交互数据来建立SSL连接。

1.3 建立SSL连接SSL_accept

SSL* CmySSLTLS::getSSL(int sd)
{
	int err;
	SSL* ssl = SSL_new(ctx);
	if (ssl == NULL) exit(1);
	SSL_set_fd(ssl, sd);
	err = SSL_accept(ssl);
	printf("SSL_accept finished %d\n", err);
	if ((err) == -1)
	{
		ERR_print_errors_fp(stderr);
		SSL_free(ssl);
		return NULL;
	}

	/*打印所有加密算法的信息(可选)*/
	printf("SSL connection using %s\n", SSL_get_cipher(ssl));

	/*得到服务端的证书并打印些信息(可选) */
	X509* client_cert;
	client_cert = SSL_get_peer_certificate(ssl);
	if (client_cert != NULL)
	{
		printf("Client certificate:\n");

		char* str = X509_NAME_oneline(X509_get_subject_name(client_cert), 0, 0);
		if (str == NULL) exit(1);
		printf("\t subject: %s\n", str);
		free(str);

		str = X509_NAME_oneline(X509_get_issuer_name(client_cert), 0, 0);
		if (str == NULL) exit(1);
		printf("\t issuer: %s\n", str);
		free(str);

		X509_free(client_cert);/*如不再需要,需将证书释放 */
	}
	else
		printf("Client does not have certificate.\n");
	return ssl;
}

        大部分代码都是输出调试信息,最关键只是用SSL_accept来实现握手过程(先通过SSL_set_fd来把SSL对象和原始的socket连接绑定在一起)。

        握手过程很复杂,但是对我们只是一个调用。

1.4 发送和接收SSL_write SSL_read

        此时再用原始的发送和接收函数只能得到加密后的数据,需要替换为openssl的发送接收函数:

#ifdef ENABLE_HTTPS //用ssl发送
				if (ssl)n = SSL_write(ssl, buf + i, count - i);
				else n = send(s, buf + i, count - i, 0);
#else
				n = send(s, buf + i, count - i, 0);
#endif

#ifdef ENABLE_HTTPS //用ssl接收
			if (ssl)*pReadCount = SSL_read(ssl, buf, buflen);
			else *pReadCount = recv(s, buf, buflen, 0);
#else
			*pReadCount = recv(s, buf, buflen, 0);
#endif

        这个代码位于function/mysocket.h,相关修改都由ENABLE_HTTPS宏控制。

        ssl是SSL*,如果不为空就执行SSL的发送和接收,否则就是原始的发送和接收,这样就兼顾了SSL和普通socket。

二、服务器的改造

        相关代码位于myhttpserver.h。使用了前面介绍的包装类。

2.1 服务器初始化

	public://ISocketServerProcess
#ifdef ENABLE_HTTPS //定义ssl的ctx
		CmySSLTLS ctx;
#endif
		//服务开始时调用(主进程)
		virtual bool OnStartServer()
		{
#ifdef ENABLE_HTTPS //初始化ssl的ctx
			if (!ctx.Init_SSL_CTX())
			{
				return false;
			}
#endif

        这个其实放哪里都行。

2.2 建立SSL连接

		//处理一个已经建立的连接
		virtual bool SocketProcess(bool _isMgrPort, CMySocket & _s, long * pRet, long i_child)
		{
            。。。。。。

			m_s=_s;
			。。。。。

#ifdef ENABLE_HTTPS //获得SSL
			m_s.ssl = this->ctx.getSSL(m_s.GetFD());
#endif

        这里传入的_s就是原始连接,还没有收发任何数据的。在这里执行握手并把SSL放在CMySocket对象里,后续收发就会自动调用SSL函数了。

2.3 释放

		//服务结束时调用(主进程)
		virtual bool OnStopServer()
		{
#ifdef ENABLE_HTTPS //释放ssl的ctx
			ctx.free_SSL_CTX();
#endif



		//处理一个已经建立的连接
		virtual bool SocketProcess(bool _isMgrPort, CMySocket & _s, long * pRet, long i_child)
		{
。。。。。。

#ifdef ENABLE_HTTPS //释放SSL
			this->ctx.freeSSL(m_s.ssl);
			m_s.ssl = NULL;
#endif
			return true;
		}

三、测试代码

3.1 测试代码

int CmySSLTLS::_test_SSLTLS()
{
	int err;
	int listen_sd;
	struct sockaddr_in sa_serv;
	struct sockaddr_in sa_cli;
	socklen_t client_len;
	char     buf[4096];

	if (!Init_SSL_CTX())return __LINE__;

	/*开始正常的TCP socket过程.................................*/
	thelog << "Begin TCP socket..." << endi;

	listen_sd = socket(AF_INET, SOCK_STREAM, 0);
	CHK_ERR(listen_sd, "socket");

	memset(&sa_serv, '\0', sizeof(sa_serv));
	sa_serv.sin_family = AF_INET;
	sa_serv.sin_addr.s_addr = INADDR_ANY;
	sa_serv.sin_port = htons(PORT);

	err = bind(listen_sd, (struct sockaddr*)&sa_serv,

		sizeof(sa_serv));

	CHK_ERR(err, "bind");

	/*接受TCP链接*/
	err = listen(listen_sd, 5);
	CHK_ERR(err, "listen");

	thelog << "访问方式: https://www.test.com:" << PORT << "/" << endi;
	while (true)
	{
		int sd;
		
		client_len = sizeof(sa_cli);
		sd = accept(listen_sd, (struct sockaddr*)(void*)&sa_cli, &client_len);
		CHK_ERR(sd, "accept");

		thelog << "Connection from " << inet_ntoa(sa_cli.sin_addr) << ", port " << sa_cli.sin_port << endi;

		SSL* ssl= getSSL(sd);
		if (NULL == ssl)continue;

		/* 数据交换开始,用SSL_write,SSL_read代替write,read */
		err = SSL_read(ssl, buf, sizeof(buf) - 1);
		if ((err) == -1) { ERR_print_errors_fp(stderr); continue; }
		buf[err] = '\0';
		printf("Got %d chars:\n%s\n", err, buf);

		char outbuf[10240];
		time_t t1 = time(NULL);
		sprintf(outbuf, "HTTP/1.0 200 ok\r\nContent-type: text/plain\r\n\r\n%s %d %s request:\r\n%s"
			, __FILE__, __LINE__, ctime(&t1), buf);
		err = SSL_write(ssl, outbuf, strlen(outbuf));
		if ((err) == -1) { ERR_print_errors_fp(stderr); continue; }

		shutdown(sd, 2);
		freeSSL(ssl);
	}

	/* 收尾工作*/
	close(listen_sd);
	free_SSL_CTX();

	return 0;
}

int CmySSLTLS::test_SSLTLS()
{
	CmySSLTLS me;
	return me._test_SSLTLS();
}

        测试代码是完全独立的(不是前面的完整服务器),只会将收到的全部信息作为文本返回。

3.2 配置、运行和结果

        测试代码入口在myhttpd_t.cpp的main函数里,被注释掉了。打开即可执行测试程序。

        rebuild.sh编译,run.sh运行:

        注意必须用域名访问才能被浏览器认为证书正确,因为证书是给这个域名的(将httpd目录下的ca.cer安装到受信任的根证书存储区)。通过修改hosts文件来解决DNS问题(参见修改hosts文件,修改安全属性,建立自己的DNS-CSDN博客)。

        看到了吧,大功告成,没有安全警告。(直接看httpd的正式服务也可以,不过相关代码分散了,不便于学习)


(这里是文档结束) 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/981415.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MyBatis的关联映射

前言 在实际开发中&#xff0c;对数据库的操作通常会涉及多张表&#xff0c;MyBatis提供了关联映射&#xff0c;这些关联映射可以很好地处理表与表&#xff0c;对象与对象之间的的关联关系。 一对一查询 步骤&#xff1a; 先确定表的一对一关系确定好实体类&#xff0c;添加关…

利用Git和wget批量下载网页数据

一、Git的下载&#xff08;参考文章&#xff09; 二. wget下载&#xff08;网上很多链接&#xff09; 三、git和wget结合使用 1.先建立一个文本&#xff0c;将代码写入文本&#xff08;代码如下&#xff09;&#xff0c;将txt后缀改为sh&#xff08;download_ssebop.sh&#xf…

deepseek助力运维和监控自动化

将DeepSeek与Agent、工作流及Agent编排技术结合&#xff0c;可实现IT运维与监控的智能化闭环管理。以下是具体应用框架和场景示例&#xff1a; 一、智能Agent体系设计 多模态感知Agent 日志解析Agent&#xff1a;基于DeepSeek的NLP能力&#xff0c;实时解析系统日志中的语义&a…

从零开始实现机器臂仿真(UR5)

1. UR5软件配置 # 安装 MoveIt! 依赖 sudo apt install ros-humble-moveit ros-humble-tf2-ros ros-humble-moveit-setup-assistant ros-humble-gazebo-ros-pkgs # 安装 UR 官方 ROS2 驱动 sudo apt update sudo apt install ros-humble-ur-robot-driver ros-humble-ur-descri…

h5 IOS端渐变的兼容问题 渐变实现弧形效果

IOS端使用渐变的时候有兼容问题 以下是问题效果&#xff0c;图中黑色部分期望的效果应该是白色的。但是ios端是下面的样子…… 安卓pc 支持&#xff1a; background-image: radial-gradient(circle 40rpx at 100% 0, #f3630c 40rpx, rgb(255, 255, 255) 50%);安卓pc ios支持…

文件上传漏洞与phpcms漏洞安全分析

目录 1. 文件上传漏洞简介 2. 文件上传漏洞的危害 3. 文件上传漏洞的触发条件 1. 文件必须能被服务器解析执行 2. 上传目录必须支持代码执行 3. 需要能访问上传的文件 4. 例外情况&#xff1a;非脚本文件也可能被执行 4. 常见的攻击手法 4.1 直接上传恶意文件 4.2 文件…

DeepSeek 助力 Vue3 开发:打造丝滑的时间选择器(Time Picker)

前言:哈喽,大家好,今天给大家分享一篇文章!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕 目录 DeepSeek 助力 Vue3 开发:打造丝滑的时间选择器(Time Picker)📚前言📚页面效果📚指令输入…

「多开浏览器」颜值升级之「消灭堆叠的窗口」(二)

01 传统指纹浏览器的架构 传统指纹浏览器&#xff08;也称为多用户浏览器或反检测浏览器&#xff09;是一种用于模拟多个独立用户环境的技术工具&#xff0c;主要用于网络爬虫、广告验证、社交媒体管理等场景。其核心目标是通过模拟不同的浏览器指纹&#xff08;Browser Finge…

IndexError: index 0 is out of bounds for axis 1 with size 0

IndexError: index 0 is out of bounds for axis 1 with size 0 欢迎来到英杰社区&#xff0c;这里是博主英杰https://bbs.csdn.net/topics/617804998 报错原因 数组或数据结构为空 如果数组或 DataFrame 在指定的维度上没有任何元素&#xff08;例如&#xff0c;没有列&#x…

本地部署阿里万象2.1文生视频模型(Wan2.1-T2V)完全指南

在生成式AI技术爆发式发展的今天,阿里云开源的万象2.1(Wan2.1)视频生成模型,为创作者提供了从文字/图像到高清视频的一站式解决方案。本文针对消费级显卡用户,以RTX 4060 Ti 16G为例,详解本地部署全流程与性能调优方案,涵盖环境配置、多模型选择策略、显存优化技巧及实战…

[Python学习日记-85] 并发编程之多进程 —— Process 类、join 方法、僵尸进程与孤儿进程

[Python学习日记-85] 并发编程之多进程 —— Process 类、join 方法、僵尸进程与孤儿进程 简介 multiprocessing 模块 Process 类 僵尸进程与孤儿进程 简介 在前面的进程理论的介绍当中我们已经介绍了进程的概念、并发与并行的区别以及进程并发的实现理论&#xff0c;这些都…

飞书考勤Excel导入到自己系统

此篇主要用于记录Excel一行中&#xff0c;单条数据的日期拿取&#xff0c;并判断上下班打卡情况。代码可能满足不了大部分需求&#xff0c;目前只够本公司用&#xff0c;如果需要&#xff0c;可以参考。 需要把飞书月度汇总的考勤表导入系统中可以参考下。 下图为需要获取的年…

Python项目】基于Python的图像去雾算法研究和系统实现

Python项目】基于Python的图像去雾算法研究和系统实现 技术简介&#xff1a;采用Python技术、MYSQL数据库等实现。 系统简介&#xff1a;图像去雾系统主要是基于暗通道先验和逆深度估计技术的去雾算法&#xff0c;系统功能模块分为&#xff08;1&#xff09;图像上传模块&…

游戏引擎学习第135天

仓库:https://gitee.com/mrxiao_com/2d_game_3 回顾 game_asset.cpp 的创建 在开发过程中&#xff0c;不使用任何现成的游戏引擎或第三方库&#xff0c;而是直接基于 Windows 进行开发&#xff0c;因为 Windows 目前仍然是游戏的标准平台&#xff0c;因此首先在这个环境中进行…

【Linux】冯诺依曼体系结构-操作系统

一.冯诺依曼体系结构 我们所使用的计算机&#xff0c;如笔记本等都是按照冯诺依曼来设计的&#xff1a; 截止目前&#xff0c;我们所知道的计算机都是由一个一个的硬件组装起来的&#xff0c;这些硬件又由于功能的不同被分为了输入设备&#xff0c;输出设备&#xff0c;存储器…

[liorf_localization_imuPreintegration-2] process has died

使用liorf&#xff0c;编译没报错&#xff0c;但是roslaunch报错如下&#xff1a; 解决方法&#xff1a; step1: 如果你之前没有安装 GTSAM&#xff0c;可以尝试安装它 step2: 检查是否缺少依赖库 ldd /home/zz/1210/devel/lib/liorf_localization/liorf_localization_imuPr…

模块11_面向对象

文章目录 模块11_面向对象模块十回顾&&模块十一重点 第一章.接口1.接口的介绍2.接口的定义以及使用3.接口中的成员3.1抽象方法3.2默认方法3.3静态方法3.4成员变量3.4成员变量 4.接口的特点5.接口和抽象类的区别 第二章.多态1.多态的介绍2.多态的基本使用3.多态的条件下…

常见webshell工具的流量特征

1、蚁剑 1.1、蚁剑webshell静态特征 蚁剑中php使用assert、eval执行&#xff1b;asp只有eval执行&#xff1b;在jsp使用的是Java类加载&#xff08;ClassLoader&#xff09;&#xff0c;同时会带有base64编码解码等字符特征。 1.2、蚁剑webshell动态特征 查看流量分析会发现…

03标准IO接口

一、系统与标准IO的区别 相同点:系统IO与标准IO都可以操作linux系统下的文件。 ⭐不同点: 系统IO&#xff1a;打开文件得到的是一个整数&#xff0c;称为文件描述符。 标准IO&#xff1a;打开文件得到的是一个指针&#xff0c;称为文件指针。系统IO&#xff1a;可以访问linux…

Axure高保真Element框架元件库

点击下载《Axure高保真Element框架元件库》 原型效果&#xff1a;https://axhub.im/ax9/9da2109b9c68749a/#g1 摘要 本文详细阐述了在 Axure 环境下打造的一套高度还原 Element 框架的组件元件集。通过对 Element 框架组件的深入剖析&#xff0c;结合 Axure 的强大功能&#…