http://blog.163.com/xiaohu345@126/blog/static/6358736720107194658637/
本程序可以自由使用,请遵守GPL规范。
OpenSSL中的SSL安全通信可以分为两类,两类基本上的操作相同,一类是建立SSL环境后使用BIO读写,另一类是直接在socket上建立SSL上下文环境。本文主要讨论在socket上建立SSL环境,以实现安全通信。首先需要生成一对客户机和服务器证书,这可以使用openssl的命令实现。使用赵春平前辈的OpenSSL编程一书中建立SSL测试环境的命令,可以建立一个模拟的CA,生成数字证书。如下:1、建立自己的CA
在OpenSSL的安装目录下的misc目录下,运行脚本sudo ./CA.sh -newCA。运行完后会生成一个demoCA的目录,里面存放了CA的证书和私钥。
2、生成客户端和服务器证书申请
openssl req -newkey rsa:1204 -out req1.pem -keyout sslclientkey.pem
openssl req -newkey rsa:1204 -out req2.pem -keyout sslserverkey.pem
3、签发客户端和服务器证书
openssl ca -in req1.pem -out sslclientcert.pem
openssl ca -in req2.pem -out sslservercert.pem
然后就可以使用OpenSSL的开源库实现SSL安全通信,本文设计了两种模式的通信,一种是没有建立SSL环境的TCP,另一种是建立了SSL环境的TCP,之后可以使用Linux下的嗅探软件wireshark嗅探出两种模式下的数据包,比较不同。程序设计参考了周立发老师的程序。
下面首先是服务器端的程序:
Profile: ssl tcp ip server
Time : 2010.07.31
Edition: 1.0
********************************/
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <resolv.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/ssl.h>
#include "tcp_video.h"
#define MAXSIZE 1024 //每次最大数据传输量
#define PKEY_FILE "sslserverkey.pem"
#define CERT_FILE "sslservercert.pem"
int main()
{
int sockfd,client_fd;
socklen_t len;
SSL_CTX *ctx;
char serverbuf[MAXSIZE];
ERR_load_BIO_strings();
/* SSL 库初始化 */
SSL_library_init();
/* 载入所有 SSL 算法 */
OpenSSL_add_all_algorithms();
/* 载入所有 SSL 错误消息 */
SSL_load_error_strings();
/* 以 SSL V2 和 V3 标准兼容方式产生一个 SSL_CTX ,即 SSL Content Text */
ctx = SSL_CTX_new(SSLv23_server_method());
/* 也可以用 SSLv2_server_method() 或 SSLv3_server_method() 单独表示 V2 或 V3标准 */
if (ctx == NULL)
{
ERR_print_errors_fp(stdout);
exit(1);
}
/* 载入用户的数字证书, 此证书用来发送给客户端。 证书里包含有公钥 */
if (!SSL_CTX_use_certificate_file(ctx, CERT_FILE, SSL_FILETYPE_PEM))
{
ERR_print_errors_fp(stdout);
exit(1);
}
/* 载入用户私钥 */
if (!SSL_CTX_use_PrivateKey_file(ctx, PKEY_FILE, SSL_FILETYPE_PEM) )
{
ERR_print_errors_fp(stdout);
exit(1);
}
/* 检查用户私钥是否正确 */
if (!SSL_CTX_check_private_key(ctx))
{
ERR_print_errors_fp(stdout);
exit(1);
}
int chose=0;
signal(SIGPIPE,SIG_IGN);
tcpserver_init(&sockfd);
while(1)
{
printf("Please Chose Channel No.:\n");
printf("1.:SSL Protocol Channel\n");
printf("2.:TCP Protocol Channel\n");
scanf("%d",&chose);
if(chose==1)
{
SSL *ssl;
tcp_accept(sockfd,&client_fd);
/* 基于 ctx 产生一个新的 SSL */
ssl = SSL_new(ctx);
/* 将连接用户的 socket 加入到 SSL */
SSL_set_fd(ssl, client_fd);
/* 建立 SSL 连接 */
if (SSL_accept(ssl) == -1)
{
perror("accept");
close(client_fd);
break;
}
// 接收消息
bzero(serverbuf, MAXSIZE);
/* 接收客户端的消息 */
len = SSL_read(ssl,serverbuf, MAXSIZE);
if (len > 0)
printf("接收消息成功:'%s',共%d个字节的数据\n",serverbuf, len);
else
printf("消息接收失败!错误代码是%d,错误信息是'%s'\n",errno, strerror(errno));
/* 关闭 SSL 连接 */
SSL_shutdown(ssl);
/* 释放 SSL */
SSL_free(ssl);
/* 关闭 socket */
close(client_fd);
}
else if(chose==2)
{
tcp_accept(sockfd,&client_fd);
len=recv(client_fd,serverbuf, MAXSIZE,0);
if (len > 0)
printf("接收消息成功:'%s',共%d个字节的数据\n",serverbuf, len);
else
printf("消息接收失败!错误代码是%d,错误信息是'%s'\n",errno, strerror(errno));
/* 关闭 socket */
close(client_fd);
}
chose=0;
}
/* 关闭监听的 socket */
close(sockfd);
/* 释放 CTX */
SSL_CTX_free(ctx);
return 0;
}//main
再次是客户端编程
/*********************************
Profile: ssl tcp ip client
Time : 2010.07.31
Edition: 1.0
*********************************/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <resolv.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/ssl.h>
#include "tcp_video.h"
#define MAXSIZE 1024 //每次最大数据传输量
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()
{
char *hostname="127.0.0.1";
int sockfd, len;
char clientbuf[MAXSIZE];
struct hostent *host;//gethostbyname函数的参数返回
struct sockaddr_in serv_addr;
SSL_CTX *ctx;
SSL *ssl;
/* SSL 库初始化 */
SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
ctx = SSL_CTX_new(SSLv23_client_method());
if (ctx == NULL)
{
ERR_print_errors_fp(stdout);
exit(1);
}
int chose=0;
printf("Please Chose Channel No.:\n");
printf("1.:SSL Protocol Channel\n");
printf("2.:TCP Protocol Channel\n");
scanf("%d",&chose);
serv_addr=tcpclient_init(&sockfd);
if(chose==1)
{
tcp_connect(&sockfd,serv_addr);
/* 基于 ctx 产生一个新的 SSL */
ssl = SSL_new(ctx);
SSL_set_fd(ssl, sockfd);
/* 建立 SSL 连接 */
if (SSL_connect(ssl) == -1)
ERR_print_errors_fp(stderr);
else
{
printf("Connected with %s encryption\n", SSL_get_cipher(ssl));
ShowCerts(ssl);
}
/* 发消息给服务器 */
bzero(clientbuf, MAXSIZE);
strcpy(clientbuf, "id:am3517&pw:am3517\n");
len = SSL_write(ssl, clientbuf, strlen(clientbuf));
if (len < 0)
printf("消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n",clientbuf, errno, strerror(errno));
else
printf("消息'%s'发送成功,共发送了%d个字节!\n",clientbuf, len);
/* 关闭连接 */
SSL_shutdown(ssl);
SSL_free(ssl);
}
else if(chose==2)
{
tcp_connect(&sockfd,serv_addr);
/* 发消息给服务器 */
bzero(clientbuf, MAXSIZE);
strcpy(clientbuf, "id:am3517&pw:am3517\n");
len = send(sockfd, clientbuf, strlen(clientbuf),0);
if (len < 0)
printf("消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n",clientbuf, errno, strerror(errno));
else
printf("消息'%s'发送成功,共发送了%d个字节!\n",clientbuf, len);
}
close(sockfd);
SSL_CTX_free(ctx);
return 0;
}
编译是需要OpenSSL库的支持,怎么安装OpenSSL在这里就不说了,网上有很多例子,另外在Ubuntu系统下可以直接使用新力得软件包安装。编辑时需要加 -lssl 选项,另外如果要调试,需要加 -g选项。
另外,程序中使用的tcp连接的函数,可以使用下面的函数,本系统使用端口7838。
/*********************************
服务器socket建立
*********************************/
void tcpserver_init(int *sockfd)
{
socklen_t len;
struct sockaddr_in my_addr;
unsigned int myport, lisnum;
myport = 7838;
lisnum = 1;
if ((*sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket");
exit(1);
}
else
printf("socket created\n");
bzero(&my_addr, sizeof(my_addr));
my_addr.sin_family = PF_INET;
my_addr.sin_port = htons(myport);
my_addr.sin_addr.s_addr = INADDR_ANY;
if(bind(*sockfd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr)) == -1)
{
perror("bind");
exit(1);
}
else
printf("binded\n");
if (listen(*sockfd, lisnum) == -1)
{
perror("listen");
exit(1);
}
else
printf("begin listen\n");
}
/*********************************
响应客户端请求,建立连接
*********************************/
void tcp_accept(int sockfd,int *new_fd)
{
struct sockaddr_in their_addr;
socklen_t len;
len = sizeof(struct sockaddr);
if ((*new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &len)) == -1)
{
perror("accept");
exit(errno);
}
else
printf("server: got connection from %s, port %d, socket %d\n",inet_ntoa(their_addr.sin_addr), ntohs(their_addr.sin_port), *new_fd);
int flags;
flags = fcntl (sockfd, F_GETFL);
if (flags & O_NONBLOCK) /* if O_NONBLOCK is set */
{
//fcntl (sockfd, F_SETFL, flags-O_NONBLOCK); /* unset it */
fcntl(sockfd,F_SETFL,flags&(~O_NONBLOCK));
}
}
/*********************************
客户端socket建立
*********************************/
struct sockaddr_in tcpclient_init(int *sockfd)
{
int len;
struct sockaddr_in dest;
char parainfo[3][20];
printf("input server IP:\n");
scanf("%s",parainfo[0]);
printf("input server port:\n");
scanf("%s",parainfo[1]);
/* 创建一个 socket 用于 tcp 通信 */
if((*sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("Socket");
exit(errno);
}
printf("socket created\n");
/* 初始化服务器端(对方)的地址和端口信息 */
bzero(&dest, sizeof(dest));
dest.sin_family = AF_INET;
dest.sin_port = htons(atoi(parainfo[1]));
if (inet_aton(parainfo[0], (struct in_addr *) &dest.sin_addr.s_addr) == 0)
{
perror(parainfo[0]);
exit(errno);
}
printf("address created\n");
return dest;
}
/*********************************
与服务器建立连接
*********************************/
void tcp_connect(int *sockfd,struct sockaddr_in dest)
{
/* 连接服务器 */
if (connect(*sockfd, (struct sockaddr *) &dest, sizeof(dest)) != 0)
{
perror("Connect ");
exit(errno);
}
printf("server connected\n");
int flags;
flags = fcntl (sockfd, F_GETFL);
if (flags & O_NONBLOCK) /* if O_NONBLOCK is set */
{
//fcntl (sockfd, F_SETFL, flags-O_NONBLOCK); /* unset it */
fcntl(sockfd,F_SETFL,flags&(~O_NONBLOCK));
}
}
参考资料:赵春平。OpenSSL编程
周立发。http://zhoulifa.bokee.com/