本文主要介绍使用libevhtp搭建一个HTTPS SERVER(单向验证身份)的方法。非加密的HTTP SERVER搭建方法,请点击此处 (本文的构建环境继承自该博客)。
本文将针对“单向验证身份”场景,介绍HTTPS SERVER的搭建方法。“双向验证身份”的场景,请点击此处。
1 单向验证身份
一般的HTTPS服务都是只需要客户端验证服务器的身份就可以了。
例如,我们想访问银行的网站,我们首先需要确认要访问的这个网站确实是真实的银行网站,而不是一个界面类似的、用来诱骗我们输入银行账号和密码的钓鱼网站,这就是我们作为客户端对银行进行身份验证的过程;而银行网站并不需要通过SSL/TLS来验证我们的身份,因为我们会通过在网页里输入银行账号和密码向服务器展示我们的身份。
2 搭建HTTPS SERVER
我们可以通过以下步骤搭建单向验证身份的HTTPS SERVER。
2.1 生成服务器的证书文件
要搭建HTTPS服务器,需要使用HTTPS服务器的秘钥文件和证书文件。
生成HTTPS服务器的秘钥文件和证书文件的步骤如下:
1. 生成秘钥对(RSA)文件:
openssl genrsa -out server-key.pem 2048
2. 生成身份证申请(CSR)文件:
openssl req -new -key server-key.pem -out server-csr.pem -subj "/CN=192.168.213.133"
3. 签署身份证(CRT)文件:
openssl x509 -req -sha256 -days 365 -in server-csr.pem -signkey server-key.pem -out server-crt.pem
通过上面的3步,我们得到了HTTPS服务器的秘钥文件“server-key.pem”和证书文件“server-crt.pem”。
说明:
- 在上面的例子中,用来签署CSR文件的私钥和CSR里的公钥是一对儿,也就是说这是一个自签名(self-sign)的例子。通常情况下,我们会用一个CA的私钥来签署一个CSR,在双向验证身份的博文中会有相关介绍。
- 上述步骤中的参数需要根据实际情况进行调整,如HTTPS服务器的访问域名“/CN=192.168.213.133”。
2.2 调用libevhtp接口,构建HTTPS服务器
调用libevhtp接口,编写HTTPS服务器,代码(https_server.cpp)如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <getopt.h>
#include "../log.h"
#include "internal.h"
#include "evhtp/evhtp.h"
#ifndef EVHTP_DISABLE_SSL
// 回调方法的具体实现
static void http__callback_(evhtp_request_t* req, void* arg)
{
evbuffer_add_reference(req->buffer_out, "foobar\n", 7, NULL, NULL);
return evhtp_send_reply(req, EVHTP_RES_OK);
}
// 认证客户端的相关信息
static int ssl__x509_verify_(int ok, X509_STORE_CTX* store)
{
char buf[256];
X509 * err_cert;
int err;
int depth;
SSL * ssl;
evhtp_connection_t * connection;
evhtp_ssl_cfg_t * ssl_cfg;
err_cert = X509_STORE_CTX_get_current_cert(store);
err = X509_STORE_CTX_get_error(store);
depth = X509_STORE_CTX_get_error_depth(store);
ssl = (SSL*)X509_STORE_CTX_get_ex_data(store, SSL_get_ex_data_X509_STORE_CTX_idx());
X509_NAME_oneline(X509_get_subject_name(err_cert), buf, 256);
connection = (evhtp_connection_t*)SSL_get_app_data(ssl);
ssl_cfg = connection->htp->ssl_cfg;
if (depth > ssl_cfg->verify_depth)
{
ok = 0;
err = X509_V_ERR_CERT_CHAIN_TOO_LONG;
X509_STORE_CTX_set_error(store, err);
}
if (!ok)
{
log_error("SSL: verify error:num=%d:%s:depth=%d:%s", err, X509_verify_cert_error_string(err), depth, buf);
}
return ok;
}
// https server参数枚举值
enum {
OPTARG_CERT = 1000,
OPTARG_KEY,
OPTARG_CA,
OPTARG_CAPATH,
OPTARG_CIPHERS,
OPTARG_VERIFY_PEER,
OPTARG_ENFORCE_PEER_CERT,
OPTARG_VERIFY_DEPTH,
OPTARG_ENABLE_CACHE,
OPTARG_CACHE_TIMEOUT,
OPTARG_CACHE_SIZE,
OPTARG_CTX_TIMEOUT,
OPTARG_ENABLE_PROTOCOL,
OPTARG_DISABLE_PROTOCOL
};
// https server帮助信息
static const char* help =
"Usage %s [opts] <host>:<port>\n"
" -cert <file> : Server PEM-encoded X.509 Certificate file\n"
" -key <file> : Server PEM-encoded Private Key file\n"
" -ca <file> : File of PEM-encoded Server CA Certificates\n"
" -capath <path> : Directory of PEM-encoded CA Certificates for Client Auth\n"
" -ciphers <str> : Accepted SSL Ciphers\n"
" -verify-peer : Enable SSL client verification\n"
" -enforce-peer-cert : Reject clients without a cert\n"
" -verify-depth <n> : Maximum depth of CA Certificates in Client Certificate verification\n"
" -enable-protocol <p> : Enable one of the following protocols: SSLv2, SSLv3, TLSv1, or ALL\n"
" -disable-protocol <p> : Disable one of the following protocols: SSLv2, SSLv3, TLSv1, or ALL\n"
" -ctx-timeout <n> : SSL Session Timeout (SSL >= 1.0)\n";
// 获取并解析用户输入的https server参数,配置evhtp的ssl信息
evhtp_ssl_cfg_t *parse__ssl_opts_(int argc, char** argv)
{
int opt = 0;
int long_index = 0;
int ssl_verify_mode = 0;
struct stat f_stat;
evhtp_ssl_cfg_t* ssl_config = (evhtp_ssl_cfg_t*)calloc(1, sizeof(evhtp_ssl_cfg_t));
ssl_config->ssl_opts = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1;
static struct option long_options[] = {
{ "cert", required_argument, 0, OPTARG_CERT },
{ "key", required_argument, 0, OPTARG_KEY },
{ "ca", required_argument, 0, OPTARG_CA },
{ "capath", required_argument, 0, OPTARG_CAPATH },
{ "ciphers", required_argument, 0, OPTARG_CIPHERS },
{ "verify-peer", no_argument, 0, OPTARG_VERIFY_PEER },
{ "enforce-peer-cert", no_argument, 0, OPTARG_ENFORCE_PEER_CERT },
{ "verify-depth", required_argument, 0, OPTARG_VERIFY_DEPTH },
{ "enable-cache", no_argument, 0, OPTARG_ENABLE_CACHE },
{ "cache-timeout", required_argument, 0, OPTARG_CACHE_TIMEOUT },
{ "cache-size", required_argument, 0, OPTARG_CACHE_SIZE },
{ "enable-protocol", required_argument, 0, OPTARG_ENABLE_PROTOCOL },
{ "disable-protocol", required_argument, 0, OPTARG_DISABLE_PROTOCOL },
{ "ctx-timeout", required_argument, 0, OPTARG_CTX_TIMEOUT },
{ "help", no_argument, 0, 'h' },
{ NULL, 0, 0, 0 }
};
// 获取用户输入的https server参数
while ((opt = getopt_long_only(argc, argv, "", long_options, &long_index)) != -1) {
switch (opt) {
case 'h':
printf(help, argv[0]);
exit(EXIT_FAILURE);
// 服务端证书文件
case OPTARG_CERT:
ssl_config->pemfile = strdup(optarg);
break;
// 服务端秘钥文件
case OPTARG_KEY:
ssl_config->privfile = strdup(optarg);
break;
// 签署服务器证书文件的CA证书文件
case OPTARG_CA:
ssl_config->cafile = strdup(optarg);
break;
case OPTARG_CAPATH:
ssl_config->capath = strdup(optarg);
break;
case OPTARG_CIPHERS:
ssl_config->ciphers = strdup(optarg);
break;
// 签署客户端证书文件的CA证书文件的最大深度
case OPTARG_VERIFY_DEPTH:
ssl_config->verify_depth = atoi(optarg);
break;
// 启用对于客户端的SSL认证
case OPTARG_VERIFY_PEER:
ssl_verify_mode |= SSL_VERIFY_PEER;
break;
// 拒绝没有证书文件的客户端的连接
case OPTARG_ENFORCE_PEER_CERT:
ssl_verify_mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
break;
case OPTARG_ENABLE_CACHE:
ssl_config->scache_type = evhtp_ssl_scache_type_internal;
break;
case OPTARG_CACHE_TIMEOUT:
ssl_config->scache_timeout = atoi(optarg);
break;
case OPTARG_CACHE_SIZE:
ssl_config->scache_size = atoi(optarg);
break;
case OPTARG_CTX_TIMEOUT:
ssl_config->ssl_ctx_timeout = atoi(optarg);
break;
case OPTARG_ENABLE_PROTOCOL:
if (!strcasecmp(optarg, "SSLv2"))
{
ssl_config->ssl_opts &= ~SSL_OP_NO_SSLv2;
}
else if (!strcasecmp(optarg, "SSLv3"))
{
ssl_config->ssl_opts &= ~SSL_OP_NO_SSLv3;
}
else if (!strcasecmp(optarg, "TLSv1"))
{
ssl_config->ssl_opts &= ~SSL_OP_NO_TLSv1;
}
else if (!strcasecmp(optarg, "ALL"))
{
ssl_config->ssl_opts = 0;
}
break;
case OPTARG_DISABLE_PROTOCOL:
if (!strcasecmp(optarg, "SSLv2"))
{
ssl_config->ssl_opts |= SSL_OP_NO_SSLv2;
}
else if (!strcasecmp(optarg, "SSLv3"))
{
ssl_config->ssl_opts |= SSL_OP_NO_SSLv3;
}
else if (!strcasecmp(optarg, "TLSv1"))
{
ssl_config->ssl_opts |= SSL_OP_NO_TLSv1;
}
else if (!strcasecmp(optarg, "ALL"))
{
ssl_config->ssl_opts = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1;
}
break;
default:
break;
} /* switch */
}
// 需要对客户端进行认证的情况
if (ssl_verify_mode != 0)
{
ssl_config->verify_peer = ssl_verify_mode;
ssl_config->x509_verify_cb = ssl__x509_verify_;
}
// 检查服务端证书文件
if (ssl_config->pemfile)
{
if (stat(ssl_config->pemfile, &f_stat) != 0)
{
log_error("Cannot load SSL cert '%s' (%s)", ssl_config->pemfile, strerror(errno));
exit(EXIT_FAILURE);
}
}
// 检查服务端秘钥文件
if (ssl_config->privfile)
{
if (stat(ssl_config->privfile, &f_stat) != 0)
{
log_error("Cannot load SSL key '%s' (%s)", ssl_config->privfile, strerror(errno));
exit(EXIT_FAILURE);
}
}
// 检查签署服务器证书文件的CA证书文件
if (ssl_config->cafile)
{
if (stat(ssl_config->cafile, &f_stat) != 0)
{
log_error("Cannot find SSL CA File '%s' (%s)", ssl_config->cafile, strerror(errno));
exit(EXIT_FAILURE);
}
}
if (ssl_config->capath) {
if (stat(ssl_config->capath, &f_stat) != 0) {
log_error("Cannot find SSL CA PATH '%s' (%s)", ssl_config->capath, strerror(errno));
exit(EXIT_FAILURE);
}
}
return ssl_config;
}
#endif
int main(int argc, char **argv)
{
#ifndef EVHTP_DISABLE_SSL
struct event_base *evbase = event_base_new();
evhtp_alloc_assert(evbase);
evhtp_t *htp = evhtp_new(evbase, NULL);
evhtp_alloc_assert(htp);
// 根据用户输入的https server参数,初始化evhtp的ssl相关配置
evhtp_ssl_init(htp, parse__ssl_opts_(argc, argv));
// 设置回调函数
evhtp_set_gencb(htp, http__callback_, NULL);
// 监听本机IP"192.168.213.133"的4443端口, backlog为128
evhtp_bind_socket(htp, "192.168.213.133", 4443, 128);
log_info("[https server info] https://192.168.213.133:4443/");
/* 进入循环、监听连接,http server开始工作 */
event_base_loop(evbase, 0);
return 0;
#else
log_error("Not compiled with SSL support, go away");
return EXIT_FAILURE;
#endif
}
编译生成HTTPS服务器程序,命令如下:
g++ -o https_server https_server.cpp -I/opt/liitdar/libevhtp/libevhtp-1.2.15/include/ -L/opt/liitdar/libevhtp/libevhtp-1.2.15/build/ -levhtp -levent -lpthread -levent_openssl -lssl -lcrypto
说明:
- 需要根据实际情况设置头文件路径,并在头文件路径中添加需要的头文件;
- 需要根据实际情况设置静态库“libevhtp.a”的地址;
- 因为libevhtp的静态库“libevhtp.a”中包含了一些ssl的内容,所以在我们使用该静态库时,也需要连接一下ssl的相关库。
2.3 测试HTTPS服务器
打开一个终端,运行刚刚生成的HTTPS服务器程序,如下:
./https_server -cert ssl/server-crt.pem -key ssl/server-key.pem
在客户端机器上,打开一个终端,使用curl命令测试HTTPS服务器,如下:
curl https://192.168.213.133:4443/ --cacert ssl/server-crt.pem
说明:上述命令使用的HTTPS服务器的证书和秘钥是在本文前面生成的。
上面的curl命令的执行结果如下:
如果客户端的终端中出现上述信息,说明单向身份验证的HTTPS服务器部署成功了。