基于openssl的单向和双向认证
1、前言
在openssl的基础上修改私钥校验过程,因此需要对openssl的认证认证流程需要熟悉一下。SSL中涉及到很多概念,开始都不清楚,例如CA,数字签名、数字证书等,本文主要是总结SSL认证的基础知识,openssl的单向和双向认证流程,并写代码测试。
2、基础知识
-
SSL:Secure Socket Layer,安全套接字层,它位于TCP层与Application层之间。提供对Application数据的加密保护(密文),完整性保护(不被篡改)等安全服务,它缺省工作在TCP 443 端口,一般对HTTP加密,即俗称的HTTPS。 -
TLS:Transport Layer Secure,更关注的是提供安全的传输服务,它很灵活,如果可能,它可以工作在TCP,也可以UDP (DTLS),也可以工作在数据链路层,比如802.1x EAP-TLS。
关于SSL/TSL可以参考
-
公钥:大家公用的,可以通过电子邮件发布,通过网站让别人下载,公钥其用来加密和验章。 -
私钥:就是自己的私有的,必须非常小心保存,最好加上 密码,私钥是用来解密和签章。 -
数字签名:将报文按双方约定的HASH算法计算得到一个固定位数的报文摘要。在数学上保证:只要改动报文中任何一位,重新计算出的报文摘要值就会与原先的值不相符。这样就保证了报文的不可更改性。将该报文摘要值用发送者的私人密钥加密,然后连同原报文一起发送给接收者,而产生的报文即称数字签名。
关于数字签名参考:http://www.ruanyifeng.com/blog/2011/08/what_is_a_digital_signature.html 和 http://www.youdzone.com/signature.html
- 数字证书:数字证书就是互联网通讯中标志通讯各方身份信息的一系列数据,提供了一种在Internet上验证您身份的方式,其作用类似于司机的驾驶执照或日常生活中的身份证。它是由一个由权威机构-----CA机构,又称为证书授权(Certificate Authority)中心发行的,人们可以在网上用它来识别对方的身份。数字证书是一个经证书授权中心数字签名的包含公开密钥拥有者信息以及公开密钥的文件。最简单的证书包含一个公开密钥、名称以及证书授权中心的数字签名。
参考:http://blog.csdn.net/oscar999/article/details/9364101
- CA:Certificate Authority,证书授权中心。是一个单位,来管理发放数字证书的。由它发放的证书就叫 CA 证书,以区别于个人使用工具随意生成的数字证书,查看 CA 证书,里面有两项重要内容,一个是颂发给谁,另一个是由谁颂发的。
参考:http://blog.csdn.net/mostone/article/details/22302035
SSL/TLS协议的基本思路是采用公钥加密法,也就是说,客户端先向服务器端索要公钥,然后用公钥加密信息,服务器收到密文后,用自己的私钥解密。
3、认证流程
SSL双向认证和SSL单向认证的区别
双向认证 SSL 协议要求服务器和用户双方都有证书。单向认证 SSL 协议不需要客户拥有CA证书,具体的过程相对于上面的步骤,只需将服务器端验证客户证书的过程去掉,以及在协商对称密码方案,对称通话密钥时,服务器发送给客户的是没有加过密的(这并不影响 SSL 过程的安全性)密码方案。这样,双方具体的通讯内容,就是加过密的数据,如果有第三方攻击,获得的只是加密的数据,第三方要获得有用的信息,就需要对加密的数据进行解密,这时候的安全就依赖于密码方案的安全。而幸运的是,目前所用的密码方案,只要通讯密钥长度足够的长,就足够的安全。这也是我们强调要求使用128位加密通讯的原因。
一般Web应用都是采用SSL单向认证的,原因很简单,用户数目广泛,且无需在通讯层对用户身份进行验证,一般都在应用逻辑层来保证用户的合法登入。但如果是企业应用对接,情况就不一样,可能会要求对客户端(相对而言)做身份验证。这时就需要做SSL双向认证。
参考: http://blog.csdn.net/duanbokan/article/details/50847612 http://blog.csdn.net/it_man/article/details/24698093
4、测试代码
证书生成过程
(1)自签CA证书
#生成根证书私钥(pem文件)
openssl genrsa -out cakey.pem 2048
#生成根证书签发申请文件(csr文件)
openssl req -new -key cakey.pem -out ca.csr -subj "/C=CN/ST=myprovince/L=mycity/O=myorganization/OU=mygroup/CN=myCA"
#自签发根证书(cer文件)
openssl x509 -req -days 365 -sha1 -extensions v3_ca -signkey cakey.pem -in ca.csr -out cacert.pem
(2)服务端私钥和证书
#生成服务端私钥
openssl genrsa -out key.pem 2048
#生成证书请求文件
openssl req -new -key key.pem -out server.csr -subj "/C=CN/ST=myprovince/L=mycity/O=myorganization/OU=mygroup/CN=myServer"
#使用根证书签发服务端证书
openssl x509 -req -days 365 -sha1 -extensions v3_req -CA ../CA/cacert.pem -CAkey ../CA/cakey.pem -CAserial ca.srl -CAcreateserial -in server.csr -out cert.pem
#使用CA证书验证server端证书
openssl verify -CAfile ../CA/cacert.pem cert.pem
(3)客户端私钥和证书
#生成客户端私钥
openssl genrsa -out key.pem 2048
#生成证书请求文件
openssl req -new -key key.pem -out client.csr -subj "/C=CN/ST=myprovince/L=mycity/O=myorganization/OU=mygroup/CN=myClient"
#使用根证书签发客户端证书
openssl x509 -req -days 365 -sha1 -extensions v3_req -CA ../CA/cacert.pem -CAkey ../CA/cakey.pem -CAserial ../server-cert/ca.srl -in client.csr -out cert.pem
#使用CA证书验证客户端证书
openssl verify -CAfile ../CA/cacert.pem cert.pem
验证CA证书出现错误处理:http://stackoverflow.com/questions/19726138/openssl-error-18-at-0-depth-lookupself-signed-certificate
单向认证
客户端代码:不需要配置证书和私钥
#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>
#define MAXBUF 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(int argc, char **argv)
{
int sockfd, len;
struct sockaddr_in dest;
char buffer[MAXBUF + 1];
SSL_CTX *ctx;
SSL *ssl;
if (argc != 3) {
printf("参数格式错误!正确用法如下:\n\t\t%s IP地址 端口\n\t比如:\t%s 127.0.0.1 80\n此程序用来从某个"
"IP 地址的服务器某个端口接收最多 %d 个字节的消息.\n", argv[0], argv[0], MAXBUF);
exit(0);
}
SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
ctx = SSL_CTX_new(SSLv3_client_method());
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Socket");
exit(errno);
}
bzero(&dest, sizeof(dest));
dest.sin_family = AF_INET;
dest.sin_port = htons(atoi(argv[2]));
if (inet_aton(argv[1], (struct in_addr *) &dest.sin_addr.s_addr) == 0) {
perror(argv[1]);
exit(errno);
}
if (connect(sockfd, (struct sockaddr *) &dest, sizeof(dest)) != 0) {
perror("Connect ");
exit(errno);
}
printf("connectd server successly\n");
ssl = SSL_new(ctx);
SSL_set_fd(ssl, sockfd);
if (SSL_connect(ssl) == -1) {
ERR_print_errors_fp(stderr);
} else {
printf("Connected with %s encryption\n", SSL_get_cipher(ssl));
ShowCerts(ssl);
}
bzero(buffer, MAXBUF + 1);
len = SSL_read(ssl, buffer, MAXBUF);
if (len > 0) {
printf("接收消息成功:'%s',共%d个字节的数据\n", buffer, len);
} else {
printf("消息接收失败!错误代码是%d,错误信息是'%s'\n", errno, strerror(errno));
goto finish;
}
bzero(buffer, MAXBUF + 1);
strcpy(buffer, "from client->server");
len = SSL_write(ssl, buffer, strlen(buffer));
if (len < 0) {
printf("消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n", buffer, errno, strerror(errno));
} else {
printf("消息'%s'发送成功,共发送了%d个字节!\n", buffer, len);
}
finish:
SSL_shutdown(ssl);
SSL_free(ssl);
close(sockfd);
SSL_CTX_free(ctx);
return 0;
}
服务端代码:
#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>
#define MAXBUF 1024
#define SERVER_CERT "/home/waf/test/cert/server-cert/cert.pem"
#define SERVER_KEY "/home/waf/test/cert/server-cert/key.pem"
int main(int argc, char **argv)
{
int sockfd, new_fd;
int reuse = 0;
socklen_t len;
struct sockaddr_in my_addr, their_addr;
unsigned int myport, lisnum;
char buf[MAXBUF + 1];
SSL_CTX *ctx;
if (argv[1]) {
myport = atoi(argv[1]);
} else {
myport = 7838;
}
if (argv[2]) {
lisnum = atoi(argv[2]);
} else {
lisnum = 2;
}
SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
ctx = SSL_CTX_new(SSLv3_server_method());
if (ctx == NULL) {
ERR_print_errors_fp(stdout);
exit(1);
}
if (SSL_CTX_use_certificate_file(ctx, SERVER_CERT, SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stdout);
exit(1);
}
if (SSL_CTX_use_PrivateKey_file(ctx, SERVER_KEY, SSL_FILETYPE_PEM) <= 0) {
printf("use private key fail.\n");
ERR_print_errors_fp(stdout);
exit(1);
}
if (!SSL_CTX_check_private_key(ctx)) {
ERR_print_errors_fp(stdout);
exit(1);
}
if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(1);
}
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0){
printf("setsockopet error\n");
return -1;
}
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");
}
while (1) {
SSL *ssl;
len = sizeof(struct sockaddr);
if ((new_fd = accept(sockfd, (struct sockaddr *) &their_addr, &len)) == -1) {
perror("accept");
exit(errno);
}
printf("server: got connection from %s, port %d, socket %d\n",inet_ntoa(their_addr.sin_addr), ntohs(their_addr.sin_port), new_fd);
ssl = SSL_new(ctx);
SSL_set_fd(ssl, new_fd);
if (SSL_accept(ssl) == -1) {
perror("accept");
close(new_fd);
break;
}
bzero(buf, MAXBUF + 1);
strcpy(buf, "server->client");
len = SSL_write(ssl, buf, strlen(buf));
if (len <= 0) {
printf("消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n", buf, errno, strerror(errno));
goto finish;
}
printf("消息'%s'发送成功,共发送了%d个字节!\n", buf, len);
bzero(buf, MAXBUF + 1);
len = SSL_read(ssl, buf, MAXBUF);
if (len > 0) {
printf("接收消息成功:'%s',共%d个字节的数据\n", buf, len);
} else {
printf("消息接收失败!错误代码是%d,错误信息是'%s'\n", errno, strerror(errno));
}
finish:
SSL_shutdown(ssl);
SSL_free(ssl);
close(new_fd);
}
close(sockfd);
SSL_CTX_free(ctx);
return 0;
}
测试结果: 服务端: 客户端:
双向认证
客户端代码:需要设置CA证书,客户端证书和私钥,校验服务器。
客户端代码:
#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>
#define MAXBUF 1024
#define CA_FILE "/home/waf/keyless/test/cert/CA/cacert.pem"
#define CLIENT_KEY "/home/waf/keyless/test/cert/client-cert/key.pem"
#define CLIENT_CERT "/home/waf/keyless/test/cert/client-cert/cert.pem"
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 sockfd, len;
struct sockaddr_in dest;
char buffer[MAXBUF + 1];
SSL_CTX *ctx;
SSL *ssl;
const SSL_METHOD *method;
if (argc != 3) {
printf("参数格式错误!正确用法如下:\n\t\t%s IP地址 端口\n\t比如:\t%s 127.0.0.1 80\n此程序用来从某个"
"IP 地址的服务器某个端口接收最多 MAXBUF 个字节的消息", argv[0], argv[0]);
exit(0);
}
SSL_library_init();
SSL_load_error_strings();
OpenSSL_add_all_algorithms();
method = TLSv1_2_client_method();
ctx = SSL_CTX_new(method);
if (!ctx) {
printf("create ctx is failed.\n");
}
#if 0
const char * cipher_list = "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:AES128-GCM-SHA256:RC4:HIGH:!MD5:!aNULL:!EDH";
if (SSL_CTX_set_cipher_list(ctx, cipher_list) == 0) {
SSL_CTX_free(ctx);
printf("Failed to set cipher list: %s", cipher_list);
}
#endif
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, 0);
if (SSL_CTX_load_verify_locations(ctx, CA_FILE, 0) != 1) {
SSL_CTX_free(ctx);
printf("Failed to load CA file %s", CA_FILE);
}
if (SSL_CTX_set_default_verify_paths(ctx) != 1) {
SSL_CTX_free(ctx);
printf("Call to SSL_CTX_set_default_verify_paths failed");
}
if (SSL_CTX_use_certificate_file(ctx, CLIENT_CERT, SSL_FILETYPE_PEM) != 1) {
SSL_CTX_free(ctx);
printf("Failed to load client certificate from %s", CLIENT_KEY);
}
if (SSL_CTX_use_PrivateKey_file(ctx, CLIENT_KEY, SSL_FILETYPE_PEM) != 1) {
SSL_CTX_free(ctx);
printf("Failed to load client private key from %s", CLIENT_KEY);
}
if (SSL_CTX_check_private_key(ctx) != 1) {
SSL_CTX_free(ctx);
printf("SSL_CTX_check_private_key failed");
}
SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY);
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Socket");
exit(errno);
}
bzero(&dest, sizeof(dest));
dest.sin_family = AF_INET;
dest.sin_port = htons(atoi(argv[2]));
if (inet_aton(argv[1], (struct in_addr *) &dest.sin_addr.s_addr) == 0) {
perror(argv[1]);
exit(errno);
}
if (connect(sockfd, (struct sockaddr *) &dest, sizeof(dest)) != 0) {
perror("Connect ");
exit(errno);
}
ssl = SSL_new(ctx);
if (ssl == NULL) {
printf("SSL_new error.\n");
}
SSL_set_fd(ssl, sockfd);
if (SSL_connect(ssl) == -1) {
printf("SSL_connect fail.\n");
ERR_print_errors_fp(stderr);
} else {
printf("Connected with %s encryption\n", SSL_get_cipher(ssl));
ShowCerts(ssl);
}
bzero(buffer, MAXBUF + 1);
len = SSL_read(ssl, buffer, MAXBUF);
if (len > 0) {
printf("接收消息成功:'%s',共%d个字节的数据\n", buffer, len);
} else {
printf("消息接收失败!错误代码是%d,错误信息是'%s'\n", errno, strerror(errno));
goto finish;
}
bzero(buffer, MAXBUF + 1);
strcpy(buffer, "from client->server");
len = SSL_write(ssl, buffer, strlen(buffer));
if (len < 0) {
printf("消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n", buffer, errno, strerror(errno));
} else {
printf("消息'%s'发送成功,共发送了%d个字节!\n", buffer, len);
}
finish:
SSL_shutdown(ssl);
SSL_free(ssl);
close(sockfd);
SSL_CTX_free(ctx);
return 0;
}
服务端代码:
#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>
#define MAXBUF 1024
#define CA_FILE "/home/waf/keyless/test/cert/CA/cacert.pem"
#define SERVER_KEY "/home/waf/keyless/test/cert/server-cert/key.pem"
#define SERVER_CERT "/home/waf/keyless/test/cert/server-cert/cert.pem"
int main(int argc, char **argv)
{
int sockfd, new_fd;
int reuse = 0;
socklen_t len;
struct sockaddr_in my_addr, their_addr;
unsigned int myport, lisnum;
char buf[MAXBUF + 1];
SSL_CTX *ctx;
const SSL_METHOD *method;
if (argv[1]) {
myport = atoi(argv[1]);
} else {
myport = 7838;
}
if (argv[2]) {
lisnum = atoi(argv[2]);
} else {
lisnum = 2;
}
SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
method = TLSv1_2_server_method();
ctx = SSL_CTX_new(method);
if (ctx == NULL) {
ERR_print_errors_fp(stdout);
exit(1);
}
#if 0
const char *cipher_list = "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384";
if (SSL_CTX_set_cipher_list(ctx, cipher_list) == 0) {
SSL_CTX_free(ctx);
printf("Failed to set cipher list %s", cipher_list);
}
#endif
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, 0);
if (SSL_CTX_load_verify_locations(ctx, CA_FILE, 0) != 1) {
SSL_CTX_free(ctx);
printf("Failed to load CA file %s", CA_FILE);
}
if (SSL_CTX_use_certificate_file(ctx, SERVER_CERT, SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stdout);
exit(1);
}
if (SSL_CTX_use_PrivateKey_file(ctx, SERVER_KEY, SSL_FILETYPE_PEM) <= 0) {
printf("use private key fail.\n");
ERR_print_errors_fp(stdout);
exit(1);
}
if (!SSL_CTX_check_private_key(ctx)) {
ERR_print_errors_fp(stdout);
exit(1);
}
SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY);
if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(1);
} else {
printf("socket created\n");
}
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0){
printf("setsockopet error\n");
return -1;
}
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);
}
printf("Server bind success.\n");
if (listen(sockfd, lisnum) == -1) {
perror("listen");
exit(1);
}
printf("Server begin to listen\n");
while (1) {
SSL *ssl;
len = sizeof(struct sockaddr);
if ((new_fd = accept(sockfd, (struct sockaddr *) &their_addr, &len)) == -1) {
perror("accept");
exit(errno);
}
printf("Server: receive a connection from %s, port %d, socket %d\n", inet_ntoa(their_addr.sin_addr), ntohs(their_addr.sin_port), new_fd);
ssl = SSL_new(ctx);
if (ssl == NULL) {
printf("SSL_new error.\n");
}
SSL_set_fd(ssl, new_fd);
if (SSL_accept(ssl) == -1) {
perror("accept");
ERR_print_errors_fp(stderr);
close(new_fd);
break;
}
printf("Server with %s encryption\n", SSL_get_cipher(ssl));
bzero(buf, MAXBUF + 1);
strcpy(buf, "server->client");
len = SSL_write(ssl, buf, strlen(buf));
if (len <= 0) {
printf("消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n", buf, errno, strerror(errno));
goto finish;
} else {
printf("消息'%s'发送成功,共发送了%d个字节!\n", buf, len);
}
bzero(buf, MAXBUF + 1);
len = SSL_read(ssl, buf, MAXBUF);
if (len > 0) {
printf("接收消息成功:'%s',共%d个字节的数据\n", buf, len);
} else {
printf("消息接收失败!错误代码是%d,错误信息是'%s'\n", errno, strerror(errno));
}
finish:
SSL_shutdown(ssl);
SSL_free(ssl);
close(new_fd);
}
close(sockfd);
SSL_CTX_free(ctx);
return 0;
}
测试结果: 服务端: 客户端:
|