基于SSL的select编程Linux系统

在这个功能上我参考了一下的文章:
https://www.openssl.org
(这是openssl的官方网站,可以查询到很多函数的使用,英文网站)
https://www.cnblogs.com/hjbf/p/10248388.html
(关于SSL协议有相当详细的介绍 如果有足够的时间可以了解一下)
https://blog.csdn.net/nyyjs/article/details/72832896
(关于SSL中涉及到的各种证书的介绍)
https://www.cnblogs.com/LiuYanYGZ/p/10438861.html
(在这篇文章中使用了多个证书和密钥是因为使用的双向认证,本程序只是一个demo比较简单,使用的是单向认证方式)
https://www.jianshu.com/p/fb5fe0165ef2
(关于SSL的单向认证和双向认证的介绍)
https://www.cnblogs.com/AloneSword/p/3809002.html
(使用OpenSSL生成证书,基本都是命令,可以大致了解一下流程,在下面我会给出明确指令生成证书)
https://blog.csdn.net/xs574924427/article/details/17240793
(这篇文章是我代码的原型,这篇文章的开始对SSL编程的流程介绍的很仔细,值得一看,但是实现代码部分稍显凌乱)

Server端程序
1.
一定要有CA证书和密钥,做SSL协议是不可以绕过的,我尝试省略这一步,因为这里的校验过程相当复杂琐碎,我会用一个好理解的方式总结一下【https://www.cnblogs.com/y-c-y/p/12124685.html】,但是CS两端不能成功通信,因为在SSL握手的过程中会对结构体SSL进行一些赋值,如果没有证书和密钥,有些流程是走不下去的。
2.
用下面这两个命令产生上述cacert.pem和privkey.pem文件:
可以cd到你的代码目录生成就可以了

openssl genrsa -out privkey.pem 2048
openssl req -new -x509 -key privkey.pem -out cacert.pem -days 1095

cacert.pem生成的时候会让你输入一些东西,类似省份,公司名称之类的,可以随便输入

#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>
#include "utils.h"
#define MAXBUF 1024
#define PORT 7838
#define CAR "cacert.pem"
#define PRIVA_KEY "privkey.pem"

int main()
{
	int sockfd, new_fd;
	FILE *fp
	socklen_t len;
	struct sockaddr_in my_addr, their_addr;
	char buf[MAXBUF + 1];
	SSL_METHOD *meth;
	SSL_CTX *ctx;
	fd_set readFdSet;

    /* 初始化OpenSSL */
	SSL_library_init();
	/*加载算法库 */
	OpenSSL_add_all_algorithms();
	/*加载错误处理信息 */
	SSL_load_error_strings();
	/* 选择会话协议  有多种版本可以选择*/
	meth = (SSL_METHOD *)SSLv23_server_method();
	/* 创建会话环境 */
	ctx = SSL_CTX_new(meth);
	/* 制定证书验证方式 */
	SSL_CTX_set_security_level(ctx, 0);
	
	if (ctx == NULL) 
	{
		ERR_print_errors_fp(stdout);
		exit(1);
	}
    /* 为SSL会话加载CA证书 */
	if (SSL_CTX_use_certificate_file(ctx, CAR, SSL_FILETYPE_PEM) <= 0) 
	{
		ERR_print_errors_fp(stdout);
		exit(1);
	}
	/* 为SSL会话加载私钥 */
	if (SSL_CTX_use_PrivateKey_file(ctx, PRIVA_KEY, 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);
	}
	
    /* 建立TCP的socket */
	if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) 
	{
		perror("socket");
		exit(1);
	} 
	else
	    printf("socket created
");
	
	bzero(&my_addr, sizeof(my_addr));
	my_addr.sin_family = PF_INET;
	my_addr.sin_port = htons(PORT);
	my_addr.sin_addr.s_addr = INADDR_ANY;
	/* TCP绑定socket */
	if (bind(sockfd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr))== -1) 
	{
		perror("bind");
		exit(1);
	} 
	else
	    printf("binded
");
	/* TCP监听socket */
	if (listen(sockfd, 5) == -1) 
	{
		perror("listen");
		exit(1);
	} 
	else
	    printf("begin listen
");
	 
	FD_ZERO(&readFdSet);
	FD_SET(fileno(fp),&readFdSet);
	FD_SET(sockfd,&readFdSet);
	while (1) 
	{
		FD_SET(fileno(fp),&readFdSet);
	    FD_SET(sockfd,&readFdSet);
		/* select函数调用 */
		if(select(sockfd+1,&readFdSet,NULL,NULL,NULL)>0)
		{
			if(FD_ISSET(sockfd,&readFdSet))
			{
				SSL *ssl;
				ssl = SSL_new(ctx);
				/* 将socket号转换为ssl结构体相关 */
				SSL_set_fd(ssl, sockfd);
				/* SSL接收socket 并且得到最终的接收消息的socket号 */
				if (SSL_accept(ssl) == -1) 
				{
					perror("accept");
					close(new_fd);
					break;
				}
				
				bzero(buf, MAXBUF + 1);
				scanf("%s",buf);
				len = SSL_write(ssl, buf, strlen(buf));
				if (len <= 0) 
				{
					printf("消息'%s'发送失败!错误代码是%d,错误信息是'%s'
",buf, errno, strerror(errno));
					SSL_shutdown(ssl);
					SSL_free(ssl);
					close(new_fd);
					SSL_CTX_free(ctx);
					return 0;
				} 
				else
					printf("消息'%s'发送成功,共发送了%d个字节!
",buf, len);
				bzero(buffer, MAXBUF + 1);
				/* SSL read 参数是和recevfrom函数个数不同 */
				len = SSL_read(ssl, buffer, MAXBUF);
				if (len > 0)
					printf("接收消息成功:'%s',共%d个字节的数据
",buffer, len);
				else 
				{
					printf("消息接收失败!错误代码是%d,错误信息是'%s'
",errno, strerror(errno));
					/* 关闭连接 */
					SSL_shutdown(ssl);
					SSL_free(ssl);
					close(sockfd);
					SSL_CTX_free(ctx);
					return 0;
				}
			}
			else if(FD_ISSET(fileno(fp),&readFdSet))
			{
				memset(buf,0,sizeof(buf));
				if((n = readline(fileno(fp),buf,MAXLINE)) == 0)
				{
					shutdown(sockfd,SHUT_WR);
					FD_CLR(fileno(fp),&rset);
					INFO_PRINT("nothing input!");
					continue;
				}
				else if(n >0)
				{
					SSL_writen(sockfd,buf,n);
				}
				else
				{
					ERR_EXIT("some error occurred ");
				}
				printf("FD_ISSET(fileno(fp),&rset)----%d--%s
",n,buf);
				SSL_writen(sockfd,buf,n);
			}
			else
			{
				printf("消息接收失败!错误代码是%d,错误信息是'%s'
",errno, strerror(errno));
			}
		}
		else
		{
			printf("消息接收失败!错误代码是%d,错误信息是'%s'
",errno, strerror(errno));
		}
		
	}
	SSL_shutdown(ssl);
	SSL_free(ssl);
	SSL_CTX_free(ctx);
	close(new_fd);
	return ;
}

Client端程序

#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>
#include "utils.h"
#define MAXBUF 1024
#define PORT 7838
#define IP "127.0.0.1"
 
void ShowCerts(SSL * ssl)
{
	X509 *cert;
	char *line;
	 
	cert = SSL_get_peer_certificate(ssl);
	if (cert != NULL) 
	{
		printf("数字证书信息:
");
		line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
		printf("证书: %s
", line);
		free(line);
		line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
		printf("颁发者: %s
", line);
		free(line);
		X509_free(cert);
	} 
	else
	    printf("无证书信息!
");
}
 
int main()
{
	int sockfd, len;
	FILE *fp
	struct sockaddr_in dest;
	char buffer[MAXBUF + 1];
	SSL_CTX *ctx;
	SSL *ssl;
	fd_set readFdSet;

	/* 初始化OpenSSL */
	SSL_library_init();
	/*加载算法库 */
	OpenSSL_add_all_algorithms();
	/*加载错误处理信息 */
	SSL_load_error_strings();
	/* 选择会话协议 */
	meth = (SSL_METHOD *) SSLv23_client_method ();
	/* 创建会话环境 */
	ctx = SSL_CTX_new(meth);
	if (ctx == NULL) 
	{
		ERR_print_errors_fp(stdout);
		exit(1);
	}
	
	/* 建立TCP的socket */
	if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	{
		perror("Socket");
		exit(errno);
	}
	printf("socket created
");
	
	bzero(&dest, sizeof(dest));
	dest.sin_family = AF_INET;
	dest.sin_port = htons(PORT);
	if (inet_aton(IP, (struct in_addr *) &dest.sin_addr.s_addr) == 0) 
	{
		perror(IP);
		exit(errno);
	}
	printf("address created
");
	/* 建立TCP 连接 */
	if (connect(sockfd, (struct sockaddr *) &dest, sizeof(dest)) != 0) 
	{
		perror("Connect ");
		exit(errno);
	}
	printf("server connected
");

	ssl = SSL_new(ctx);
	/* 将socket号转换为ssl结构体相关 */
	SSL_set_fd(ssl, sockfd);
	/* 建立SSL连接 */
	if (SSL_connect(ssl) == -1)
		ERR_print_errors_fp(stderr);
	else 
	{
		printf("Connected with %s encryption
", SSL_get_cipher(ssl));
		/* SSL的单向认证 所以此处没有Client证书递交过程,这里是显示一下收到的来自服务器的证书部分内容 */
		ShowCerts(ssl);
	}

	/* 对readFdSet的操作和普通select一样 */
	FD_ZERO(&readFdSet);
	while(1)
	{
		FD_SET(fileno(fp),&readFdSet);
	    FD_SET(sockfd,&readFdSet);
		/* select函数调用 */
		if(select(sockfd+1,&readFdSet,NULL,NULL,NULL)>0)
		{
			if(FD_ISSET(sockfd,&readFdSet))
			{
				bzero(buffer, MAXBUF + 1);
				/* SSL read 参数是和recevfrom函数个数不同 */
				len = SSL_read(ssl, buffer, MAXBUF);
				if (len > 0)
				    printf("接收消息成功:'%s',共%d个字节的数据
",buffer, len);
				else 
				{
					printf("消息接收失败!错误代码是%d,错误信息是'%s'
",errno, strerror(errno));
					/* 关闭连接 */
					SSL_shutdown(ssl);
					SSL_free(ssl);
					close(sockfd);
					SSL_CTX_free(ctx);
					return 0;
				}
			}
			else if(FD_ISSET(fileno(fp),&readFdSet))
			{
				memset(buf,0,sizeof(buf));
				if((n = readline(fileno(fp),buf,MAXLINE)) == 0)
				{
					shutdown(sockfd,SHUT_WR);
					FD_CLR(fileno(fp),&rset);
					INFO_PRINT("nothing input!");
					continue;
				}
				else if(n >0)
				{
					/* do nothing */
				}
				else
				{
					ERR_EXIT("some error occurred ");
				}
				printf("FD_ISSET(fileno(fp),&rset)----%d--%s
",n,buf);
				SSL_writen(ssl,buf,n);
			}
			else
			{
				printf("消息接收失败!错误代码是%d,错误信息是'%s'
",errno, strerror(errno));
			}
		}
		else
		{
			printf("消息接收失败!错误代码是%d,错误信息是'%s'
",errno, strerror(errno));
		}
	}
	/* 关闭连接 */
	SSL_shutdown(ssl);
	SSL_free(ssl);
	close(sockfd);
	SSL_CTX_free(ctx);
	return 1;
}

makefile

all:ss
	@echo ""
	@echo "This is Client compile......."
	@echo ""
ss:client.c 
	gcc -g -o Client client.c -lssl -lcrypto
	gcc -g -o Server server.c -lssl -lcrypto
clean :
	-rm Client Server
原文地址:https://www.cnblogs.com/y-c-y/p/12133303.html