OpenSSL入门-SSL-TLS编程实战

入门

SSL_library_initOpenSSL_add_ssl_algorithmsSSLeay_add_ssl_algorithms初始化SSL算法库,调用SSL系列函数前必须先调用一个:

1
2
3
int SSL_library_init(); //成功1 否则0
#define OpenSSL_add_ssl_algorithms() SSL_library_init()
#define SSLeay_add_ssl_algorithms() SSL_library_init()

SSL_CTX_newSSL_CTX_free分别初始化和释放SSL上下文环境变量。

1
2
3
4
5
6
SSL_CTX* SSL_CTX_new(
SSL_METHOD* meth //使用的SSL协议算法
); //成功返回指针 否则NULL
void SSL_CTX_free(
SSL_CTX* ctx
);

其中参数meth可以是:

1
2
3
4
5
6
7
8
SSLv2_server_method();
SSLv2_client_method();
SSLv3_server_method();
SSLv3_client_method();
SSLv23_server_method();
SSLv23_client_method();
TLSv1_server_method();
TLSv1_client_method();

SSL_CTX_use_certificate_fileSSL_CTX_use_certificate分别以文件形式和结构体方式设置SSL证书:

1
2
3
4
5
6
7
8
9
int SSL_CTX_use_certificate_file(
SSL_CTX* ctx,
const char* file, //证书路径
int type //证书类型
); //成功1 否则0
int SSL_CTX_use_certificate(
SSL_CTX* ctx,
X509* x
); //成功1 否则0

type取值可以是SSL_FILETYPE_PEM或SSL_FILETYPE_ASN1。

SSL_CTX_use_PrivateKey_fileSSL_CTX_use_PrivateKey分别以文件形式和结构体方式设置SSL私钥。

1
2
3
4
5
6
7
8
9
int SSL_CTX_use_PrivateKey_file(
SSL_CTX* ctx,
const char* file,
int type
); //成功1 否则0
int SSL_CTX_use_PrivateKey(
SSL_CTX* ctx,
EVP_PKEY* pkey //私钥
); //同上

SSL_CTX_check_private_key检查SSL私钥和证书是否匹配。

1
2
3
int SSL_CTX_check_private_key(
const SSL_CTX* ctx
); //成功1 否则0

SSL_newSSL_free分别创建和释放SSL套接字结构体:

1
2
3
4
5
6
SSL* SSL_new(
SSL_CTX* ctx
); //失败0
void SSL_free(
SSL* ssl
);

SSL_set_fdSSL_set_rfdSSL_set_wfd分别设置读写、只读和只写套接字:

1
2
3
4
5
6
7
8
9
10
11
12
int SSL_set_fd(
SSL* s,
int fd //文件描述符
); //成功1 否则0
int SSL_set_rfd(
SSL* s,
int fd
); //同上
int SSL_set_wfd(
SSL* s,
int fd
); //同上

SSL_connect启动TLS/SSL握手,用SSL_accept接受SSL连接,后者失败可用SSL_get_error找出原因。

1
2
3
4
5
6
int SSL_conenct(
SSL* ssl
); //成功1 失败0
int SSL_accept(
SSL* ssl
); //同上

SSL_get_peer_certificate获取对方X509证书:

1
2
3
X509* SSL_get_peer_certificate(
const SSL* ssl
); //失败NULL

SSL_write项TLS/SSL连接写数据,用SSL_read从TLS/SSL连接中读取数据:

1
2
3
4
5
6
7
8
9
10
int SSL_write(
SSL* ssl,
const void* buf, //要写入的数据
int num //写入数据字节长度
); //成功返回实际写入数据长度 失败0
int SSL_read(
SSL* ssl,
void* buf, //读取数据缓冲区
int num //要读的字节数
); //失败0

环境搭建

原理同PKI,假设目前工作目录在Windows的OpenSSL下,配置文件为/bin/openssl.cfg。然后准备好index.txt和serial文件,之后在CA主机上创建根CA证书:

1
2
3
openssl genrsa -des3 -out root.key 1024 #生成CA根证书私钥
openssl req -new -key root.key -out root.csr #生成根证书请求文件
openssl req -new -x509 -key root.key -out root.crt #生成CA自签名证书

在服务端生成证书请求文件:

1
2
openssl genrsa -des3 -out server.key 1024 #生成服务端私钥
openssl req -new -key server.key -out server.csr #生成证书请求文件

在CA主机上签发服务端证书:

1
openssl ca -in server.csr -out server.crt -keyfile root.key -cert root.crt -days 365 -config ./openssl.cfg

在客户端生成证书请求文件:

1
2
openssl genrsa -des3 -out client.key 1024 #生成客户端私钥
openssl req -new -key client.key -out client.csr #生成证书请求文件

在CA上签发客户端证书:

1
openssl ca -in client.csr -out client.crt -keyfile root.key -cert root.crt -days 365 -config ./openssl.cfg

实战

服务端实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <errno.h>
#include <sys/types.h>
#include <winsock2.h>
#include "openssl/rsa.h"
#include "openssl/crypto.h"
#include "openssl/x509.h"
#include "openssl/pem.h"
#include "openssl/ssl.h"
#include "openssl/err.h"
extern "C" {
#include <openssl/applink.c>
};
/*所有需要的参数信息都在此处以#define的形式提供*/
#define CERTF "server.crt" /*服务端的证书(需经CA签名)*/
#define KEYF "server.key" /*服务端的私钥(建议加密存储)*/
#define CACERT "root.crt" /*CA 的证书*/
#define PORT 1111 /*准备绑定的端口*/
#define CHK_NULL(x) if ((x)==NULL) exit (1)
#define CHK_ERR(err,s) if ((err)==-1) { perror(s); exit(1); }
#define CHK_SSL(err) if ((err)==-1) { ERR_print_errors_fp(stderr); exit(2); }
int main() {
int err;
int listen_sd;
int sd;
struct sockaddr_in sa_serv;
struct sockaddr_in sa_cli;
int client_len;
SSL_CTX* ctx;
SSL* ssl;
X509* client_cert;
char* str;
char buf[4096];
const SSL_METHOD* meth;
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
printf("WSAStartup()fail:%d\n", GetLastError());
return -1;
}
SSL_load_error_strings(); /*为打印调试信息作准备*/
OpenSSL_add_ssl_algorithms(); /*初始化*/
meth = TLSv1_server_method(); /*采用什么协议(SSLv2/SSLv3/TLSv1)在此指定*/
ctx = SSL_CTX_new(meth);
CHK_NULL(ctx);
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); /*验证与否*/
SSL_CTX_load_verify_locations(ctx, CACERT, NULL); /*若验证,则放置CA证书*/
if (SSL_CTX_use_certificate_file(ctx, CERTF, SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stderr);
exit(3);
}
if (SSL_CTX_use_PrivateKey_file(ctx, KEYF, SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stderr);
exit(4);
}
if (!SSL_CTX_check_private_key(ctx)) {
printf("Private key does not match the certificate public key\n");
exit(5);
}
SSL_CTX_set_cipher_list(ctx, "RC4-MD5");
printf("I am ssl-server\n");
/*开始正常的TCP socket过程.................................*/
listen_sd = socket(AF_INET, SOCK_STREAM, 0);
CHK_ERR(listen_sd, "socket");
memset(&sa_serv, '\0', sizeof(sa_serv));
sa_serv.sin_family = AF_INET;
sa_serv.sin_addr.s_addr = INADDR_ANY;
sa_serv.sin_port = htons(PORT);
err = bind(listen_sd, (struct sockaddr*)&sa_serv, sizeof(sa_serv));
CHK_ERR(err, "bind");
/*接受TCP链接*/
err = listen(listen_sd, 5);
CHK_ERR(err, "listen");
client_len = sizeof(sa_cli);
sd = accept(listen_sd, (struct sockaddr*)&sa_cli, &client_len);
CHK_ERR(sd, "accept");
closesocket(listen_sd);
printf("Connection from %lx, port %x\n", sa_cli.sin_addr.s_addr, sa_cli.sin_port);
/*TCP连接已建立,进行服务端的SSL过程. */
printf("Begin server side SSL\n");
ssl = SSL_new(ctx);
CHK_NULL(ssl);
SSL_set_fd(ssl, sd);
err = SSL_accept(ssl);
printf("SSL_accept finished\n");
CHK_SSL(err);
/*打印所有加密算法的信息(可选)*/
printf("SSL connection using %s\n", SSL_get_cipher(ssl));
/*得到服务端的证书并打印些信息(可选) */
client_cert = SSL_get_peer_certificate(ssl);
if (client_cert != NULL) {
printf("Client certificate:\n");
str = X509_NAME_oneline(X509_get_subject_name(client_cert), 0, 0);
CHK_NULL(str);
printf("\t subject: %s\n", str);
OPENSSL_free(str);
str = X509_NAME_oneline(X509_get_issuer_name(client_cert), 0, 0);
CHK_NULL(str);
printf("\t issuer: %s\n", str);
OPENSSL_free(str);
X509_free(client_cert);/*如不再需要,需将证书释放 */
}
else
printf("Client does not have certificate.\n");
/* 数据交换开始,用SSL_write,SSL_read代替write,read */
err = SSL_read(ssl, buf, sizeof(buf) - 1);
CHK_SSL(err);
buf[err] = '\0';
printf("Got %d chars:'%s'\n", err, buf);
err = SSL_write(ssl, "I hear you.", strlen("I hear you."));
CHK_SSL(err);
/* 收尾工作*/
shutdown(sd, 2);
SSL_free(ssl);
SSL_CTX_free(ctx);
return 0;
}

客户端实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <errno.h>
#include <sys/types.h>
#include <winsock2.h>
#include "openssl/rsa.h"
#include "openssl/crypto.h"
#include "openssl/x509.h"
#include "openssl/pem.h"
#include "openssl/ssl.h"
#include "openssl/err.h"
#include "openssl/rand.h"
extern "C" {
#include <openssl/applink.c>
};
/*所有需要的参数信息都在此处以#define的形式提供*/
#define CERTF "client.crt" /*客户端的证书(需经CA签名)*/
#define KEYF "client.key" /*客户端的私钥(建议加密存储)*/
#define CACERT "root.crt" /*CA 的证书*/
#define PORT 1111 /*服务端的端口*/
#define SERVER_ADDR "127.0.0.1" /*服务段的IP地址*/
#define CHK_NULL(x) if ((x)==NULL) exit (-1)
#define CHK_ERR(err,s) if ((err)==-1) { perror(s); exit(-2); }
#define CHK_SSL(err) if ((err)==-1) { ERR_print_errors_fp(stderr); exit(-3); }
int main() {
int err;
int sd;
struct sockaddr_in sa;
SSL_CTX* ctx;
SSL* ssl;
X509* server_cert;
char* str;
char buf[4096];
const SSL_METHOD* meth;
int seed_int[100]; /*存放随机序列*/
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
printf("WSAStartup()fail:%d\n", GetLastError());
return -1;
}
/*初始化*/
OpenSSL_add_ssl_algorithms();
/*为打印调试信息作准备*/
SSL_load_error_strings();
/*采用什么协议(SSLv2/SSLv3/TLSv1)在此指定*/
meth = TLSv1_client_method();
/*申请SSL会话环境*/
ctx = SSL_CTX_new(meth);
CHK_NULL(ctx);
/*验证与否,是否要验证对方*/
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
/*若验证对方,则放置CA证书*/
SSL_CTX_load_verify_locations(ctx, CACERT, NULL);
/*加载自己的证书*/
if (SSL_CTX_use_certificate_file(ctx, CERTF, SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stderr);
exit(-2);
}
/*加载自己的私钥,以用于签名*/
if (SSL_CTX_use_PrivateKey_file(ctx, KEYF, SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stderr);
exit(-3);
}
/*调用了以上两个函数后,检验一下自己的证书与私钥是否配对*/
if (!SSL_CTX_check_private_key(ctx)) {
printf("Private key does not match the certificate public key\n");
exit(-4);
}
/*构建随机数生成机制,WIN32平台必需*/
srand((unsigned)time(NULL));
for (int i = 0; i < 100; i++)
seed_int[i] = rand();
RAND_seed(seed_int, sizeof(seed_int));
printf("I am ssl-client\n");
/*开始正常的TCP socket过程.................................*/
sd = socket(AF_INET, SOCK_STREAM, 0);
CHK_ERR(sd, "socket");
memset(&sa, '\0', sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_addr.s_addr = inet_addr(SERVER_ADDR); /* Server IP */
sa.sin_port = htons(PORT); /* Server Port number */
err = connect(sd, (struct sockaddr*)&sa, sizeof(sa));
CHK_ERR(err, "connect");
/* TCP 链接已建立.开始 SSL 握手过程.......................... */
printf("Begin SSL negotiation \n");
/*申请一个SSL套接字*/
ssl = SSL_new(ctx);
CHK_NULL(ssl);
/*绑定读写套接字*/
SSL_set_fd(ssl, sd);
err = SSL_connect(ssl);
CHK_SSL(err);
/*打印所有加密算法的信息(可选)*/
printf("SSL connection using %s\n", SSL_get_cipher(ssl));
/*得到服务端的证书并打印些信息(可选) */
server_cert = SSL_get_peer_certificate(ssl);
CHK_NULL(server_cert);
printf("Server certificate:\n");
str = X509_NAME_oneline(X509_get_subject_name(server_cert), 0, 0);
CHK_NULL(str);
printf("\t subject: %s\n", str);
OPENSSL_free(str);
str = X509_NAME_oneline(X509_get_issuer_name(server_cert), 0, 0);
CHK_NULL(str);
printf("\t issuer: %s\n", str);
OPENSSL_free(str);
X509_free(server_cert); /*如不再需要,需将证书释放 */
/* 数据交换开始,用SSL_write,SSL_read代替write,read */
printf("Begin SSL data exchange\n");
err = SSL_write(ssl, "Hello World!", strlen("Hello World!"));
CHK_SSL(err);
err = SSL_read(ssl, buf, sizeof(buf) - 1);
CHK_SSL(err);
buf[err] = '\0';
printf("Got %d chars:'%s'\n", err, buf);
SSL_shutdown(ssl); /* send SSL/TLS close_notify */
/* 收尾工作 */
shutdown(sd, 2);
SSL_free(ssl);
SSL_CTX_free(ctx);
return 0;
}