在这个功能上我参考了一下的文章:
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