上一篇博客介绍了使用openssl对socket加密使用到的库函数,也解决了上一篇博客中说到的问题,首先就是要生成openssl证书,大家可以参考本篇博客(https://blog.csdn.net/fly2010love/article/details/46415307),就不多说了。
还有一个问题就是编译的问题,刚开始我以为是库没安装好,其实是没有生成证书,而且使用gcc编译的时候要使用-L参数链接到库函数所在的路径,刚开始我没加-L会出现以下情况:
这是因为引用openssl静态库libcrypto.a、libssl.a和动态库.so路径出现问题,如果你不知道它的路径,可以学习Linux下grep和find命令,我使用find / -name libssl.*
命令寻找.so和.a的路径,如下:
然后编译的时候加-L跟上路径就可以了。
解决好了问题,再来看一下代码,首先来说一下客户端编写思路:
(1)SSL库初始化;
(2)载入所有SSL算法;
(3)载入所有SSL错误信息;
(4)建立会话环境CTX;
(5)创建socket套接字;
(6)TCP连接connect(sockfd);
(7)申请一个SSL套接字;
(8)将socket套接字加入到SSL;
(9)进行SSL握手;
(10)与服务器进行数据收发;
(11)关闭SSL套接字;
(12)释放SSL套接字;
(13)关闭socket套接字;
(14)释放会话环境;
以下为流程图:
下面是代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "init_socket.c"
#define MAXSIZE 1024
#define CA_CERT_FILE "ca.crt"
#define CLIENT_CERT_FILE "client.crt"
#define CLIENT_KEY_FILE "client.key"
int g_stop=0;
void sig_handler(int signum)/*signal信号处理函数*/
{
if( SIGUSR1==signum )
{
g_stop=-1;
}
}
/*打印证书信息*/
void ShowCerts(SSL * ssl)
{
X509 *cert;
char *line;
cert = SSL_get_peer_certificate(ssl);
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 ch;
int len;
int rv;
int sockfd = -1;
int server_port = 0;
char send_buf[MAXSIZE];
char rev_buf[MAXSIZE];
char *server_ip = NULL;
SSL_CTX *ctx;
SSL *ssl;
struct option opt[] = {
{"ipaddr", required_argument, NULL, 'i'},
{"port", required_argument, NULL, 'p'},
{"help", no_argument, NULL, 'h'},
{NULL, 0, NULL, 0}
};
while ( (ch = getopt_long(argc, argv, "i:p:h", opt, NULL))!=-1 )
{
switch(ch)
{
case 'i':
server_ip = optarg;
break;
case 'p':
server_port = atoi(optarg);
break;
case 'h':
print_help(argv[0]);
return 0;
}
}
if (!server_ip)
{
print_help(argv[0]);
return 0;
}
/*SSL库初始化*/
SSL_library_init();
/*加载所有算法*/
OpenSSL_add_all_algorithms();
/*载入所有错误信息*/
SSL_load_error_strings();
ERR_load_BIO_strings();
ctx = SSL_CTX_new(SSLv23_client_method());
if (NULL == ctx)
{
printf("SSL_CTX_new error!\n");
ERR_print_errors_fp(stdout);
exit(1);
}
// 要求校验对方证书,表示需要验证服务器端,若不需要验证则使用 SSL_VERIFY_NONE
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
// 加载CA的证书
printf("SSL_CTX_load_verify_locations start!\n");
if(!SSL_CTX_load_verify_locations(ctx, CA_CERT_FILE, NULL))
{
printf("SSL_CTX_load_verify_locations error!\n");
ERR_print_errors_fp(stderr);
return -1;
}
// 加载自己的证书
if(SSL_CTX_use_certificate_file(ctx, CLIENT_CERT_FILE, SSL_FILETYPE_PEM) <= 0)
{
printf("SSL_CTX_use_certificate_file error!\n");
ERR_print_errors_fp(stderr);
return -1;
}
if(SSL_CTX_use_PrivateKey_file(ctx, CLIENT_KEY_FILE, SSL_FILETYPE_PEM) <= 0)
{
printf("SSL_CTX_use_PrivateKey_file error!\n");
ERR_print_errors_fp(stderr);
return -1;
}
if(!SSL_CTX_check_private_key(ctx))
{
printf("SSL_CTX_check_private_key error!\n");
ERR_print_errors_fp(stderr);
return -1;
}
/*socket初始化*/
sockfd=client_init(server_ip,server_port);
/*创建SSL套接字*/
ssl = SSL_new(ctx);
if (NULL==ssl)
{
printf("SSL_new error!\n");
ERR_print_errors_fp(stderr);
return -1;
}
SSL_set_fd(ssl, sockfd);
if (SSL_connect(ssl) == -1)
{
printf("SSL_new error!\n");
ERR_print_errors_fp(stderr);
return -1;
}
else
{
printf("Connected with %s encryption\n", SSL_get_cipher(ssl));
ShowCerts(ssl);
}
signal(SIGUSR1,sig_handler);
while (!g_stop)
{
/*进行数据传送*/
printf("Input message:\n");
bzero(send_buf, MAXSIZE + 1);
fgets(send_buf,MAXSIZE,stdin);
len = SSL_write(ssl, send_buf, strlen(send_buf));
if (len<0)
{
printf("消息'%s'发送失败!错误信息是'%s'\n", send_buf, strerror(errno));
goto finish;
}
else
{
printf("消息%s发送成功,共发送了%d个字节!\n", send_buf, len-1);
}
memset(rev_buf, 0, sizeof(rev_buf));
rv = SSL_read(ssl, rev_buf, sizeof(rev_buf));
if (rv<0)
{
printf("read data from server failure:%s\n",strerror(errno));
goto finish;
}
else
{
printf("read %zd bytes data from server is:%s\n",strlen(rev_buf)-1,rev_buf);
}
}
finish:
SSL_shutdown(ssl);
SSL_free(ssl);
close(sockfd);
SSL_CTX_free(ctx);
return 0;
}
服务器端的编程思路为:
(1)SSL库初始化;
(2)载入所有SSL算法;
(3)载入所有SSL错误信息;
(4)建立会话环境CTX;
(5)载入用户证书;
(6)载入用户私钥;
(7)检查用户私钥是否正确;
(8)服务器端socket初始化;
(9)TCP接收客户端请求;
(10)申请一个SSL套接字;
(11)将socket套接字加入到SSL;
(12)进行SSL握手;
(13)与客户端进行数据传送;
(14)关闭SSL套接字;
(15)释放SSL套接字;
(16)关闭socket套接字;
(17)释放会话环境;
流程图:
以下为代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "init_socket.c"
#define MAXSIZE 1024
#define CA_CERT_FILE "ca.crt"
#define SERVER_CERT_FILE "server.crt"
#define SERVER_KEY_FILE "server.key"
int g_stop=0;
void sig_handler(int signum)
{
if( SIGUSR1==signum )
{
g_stop=-1;
}
}
void print_help(char *progname)
{
printf("The progname is:%s\n",progname);
printf("-p(--port):specify listen port!\n");
printf("-h(--help):print help information!\n");
return ;
}
int main(int argc, char **argv)
{
int ch;
int port = 0;
int len = 0;
int rv = 0;
int clifd = -1;
int sock_fd = -1;
char send_buf[MAXSIZE];
char rev_buf[MAXSIZE];
SSL_CTX *ctx;
SSL *ssl;
struct option opt[] = {
{"port", required_argument, NULL, 'p'},
{"help", no_argument, NULL, 'h'},
{NULL, 0, NULL, 0}
};
while ( (ch = getopt_long(argc, argv, "p:h", opt, NULL))!=-1 )
{
switch(ch)
{
case 'p':
port=atoi(optarg);
break;
case 'h':
print_help(argv[0]);
return 0;
}
}
if (!port)
{
print_help(argv[0]);
return 0;
}
/*错误队列*/
ERR_load_BIO_strings();
/*SSL初始化*/
SSL_library_init();
printf("SSL_library_init ok!\n");
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
ERR_load_BIO_strings();
/*建立会话环境*/
printf("建立会话环境....\n");
ctx = SSL_CTX_new(SSLv23_server_method());
if (ctx == NULL)
{
printf("建立会话环境失败!\n");
ERR_print_errors_fp(stdout);
exit(1);
}
// 是否要求校验对方证书 此处不验证客户端身份所以为: SSL_VERIFY_NONE
SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);
// 加载CA的证书
if(!SSL_CTX_load_verify_locations(ctx, CA_CERT_FILE, NULL))
{
printf("SSL_CTX_load_verify_locations error!\n");
ERR_print_errors_fp(stderr);
return -1;
}
// 加载自己的证书
if(SSL_CTX_use_certificate_file(ctx, SERVER_CERT_FILE, SSL_FILETYPE_PEM) <= 0)
{
printf("SSL_CTX_use_certificate_file error!\n");
ERR_print_errors_fp(stderr);
return -1;
}
/* 加载自己的私钥 私钥的作用是,ssl握手过程中,对客户端发送过来的随机
* 消息进行加密,然后客户端再使用服务器的公钥进行解密,若解密后的原始消息跟
* 客户端发送的消息一直,则认为此服务器是客户端想要链接的服务器*/
if(SSL_CTX_use_PrivateKey_file(ctx, SERVER_KEY_FILE, SSL_FILETYPE_PEM) <= 0)
{
printf("SSL_CTX_use_PrivateKey_file error!\n");
ERR_print_errors_fp(stderr);
return -1;
}
/*检查用户私钥是否正确*/
if (!SSL_CTX_check_private_key(ctx))
{
printf("SSL_CTX_check_private_key error!\n");
ERR_print_errors_fp(stdout);
return -1;
}
/*socket初始化*/
sock_fd = server_init(NULL, port);
if (sock_fd<0)
{
printf("socket_init failure!\n");
return -1;
}
printf("socket_init ok!\n");
clifd = accept(sock_fd, (struct sockaddr *)NULL, NULL);
if (clifd<0)
{
printf("accept new client failure:%s\n",strerror(errno));
return -1;
}
printf("Accept new client[%d] successfully!\n",clifd);
/*申请SSL套接字*/
ssl = SSL_new(ctx);
SSL_set_fd(ssl, clifd);
if (SSL_accept(ssl) == -1)
{
printf("SSL_accept failure:%s!\n",strerror(errno));
close(clifd);
return -2;
}
signal(SIGUSR1,sig_handler);
while (!g_stop)
{
//printf("start to accept new client incoming...\n");
memset(rev_buf, 0,sizeof(rev_buf));
len = SSL_read(ssl,rev_buf, MAXSIZE);
if (len > 0)
{
printf("接收消息成功:%s,共%d个字节的数据\n",rev_buf, len-1);
}
else
{
printf("消息接收失败!错误信息是'%s'\n", strerror(errno));
}
memset(send_buf,0,sizeof(send_buf));
printf("Input reply message:\n");
fgets(send_buf,MAXSIZE,stdin);
rv = SSL_write(ssl, send_buf, MAXSIZE);
if (rv>0)
{
printf("send %ld bytes data to client:%s\n", strlen(send_buf)-1, send_buf);
}
else
{
printf("send data to client failure:%s\n", strerror(errno));
break;
}
}
SSL_shutdown(ssl);
SSL_free(ssl);
close(clifd);
close(sock_fd);
SSL_CTX_free(ctx);
return 0;
}
可以自己编写makefile,以下为运行结果:
客户端:
服务器端:
调试代码的时候可以学习一下openssl的命令,输入openssl --help
可以找到s_client和s_server两个命令,具体的用法可以自行百度,这里给出命令格式:
测试客户端代码,就用服务器端命令:openssl s_server -cert server.crt -key server.key -CAfile ca.crt -port 8888
参数:-cert服务器端的证书,-key服务器端的私钥,-CAfile生成的CA证书,-port需要监听的端口。
测试服务端器代码,就用客户端命令:openssl s_client -connect ip:port -cert client.crt -key client.key -CAfile ca.crt
参数:-connect后面跟要连接的客户ip和端口,其它换成客户端的证书和私钥即可。
参考:
1、https://blog.csdn.net/xs574924427/article/details/17240793
2、https://blog.csdn.net/fly2010love/article/details/46458963
3、https://blog.csdn.net/fly2010love/article/details/46458805
4、https://blog.csdn.net/fly2010love/article/details/46415307