libhv是一个国产的网络库,在v1.2.5版本提供了SSL客户端支持,可以便携地实现双向认证。 实现 客户端
废话不多说,先show the class="superseo">code:
#include "hv/http_client.h"
using namespace hv;
int main() {
// 创建Client
int ret;
http_client_t *cli = http_client_new(NULL, 8080, 1); // 这里参数填什么无所谓
if(cli == NULL) {
printf("Error: cli is null\n");
return 0;
}
// 设置客户端证书
hssl_ctx_opt_t *ssl_opt = new hssl_ctx_opt_t;
ssl_opt->verify_peer = 1; // 认证对方身份
ssl_opt->endpoint = HSSL_CLIENT;
ssl_opt->ca_path = NULL; // 可以为一个证书目录,参见https://www.openssl.org/docs/man1.0.2/man3/SSL_CTX_load_verify_locations.html
ssl_opt->ca_file = "cert/ca.crt"; // CA证书
ssl_opt->crt_file = "cert/client.crt"; // 客户端证书
ssl_opt->key_file = "cert/client_rsa_private.pem"; // 客户端私钥
hssl_ctx_t ctx = hssl_ctx_new(ssl_opt); // 生成一个hssl_ctx
if(ctx == NULL) {
printf("Error: ctx is null\n");
return 0;
}
// !重点!
ret = http_client_set_ssl_ctx(cli, ctx); // 将上述hssl_ctx与之前创建的Client绑定
if (ret != 0) {
printf("Cert Error: %s : %d\n", http_client_strerror(ret), ret);
return 0;
}
// 创建请求
HttpRequest req;
req.method = HTTP_POST;
req.url = "https://127.0.0.1:8080/echo";
req.headers["Connection"] = "keep-alive";
req.body = "This is a test request.";
req.timeout = 10;
// 发送请求
HttpResponse resp;
ret = http_client_send(cli, &req, &resp);
// 解析
if (ret != 0) {
printf("Request Failed: %s : %d\n", http_client_strerror(ret), ret);
} else {
printf("%d %s\r\n", resp.status_code, resp.status_message());
printf("%s\n", resp.body.c_str());
}
http_client_del(cli);
hssl_ctx_free(ctx); // 若使用http_client_new_ssl_ctx函数来将hssl与client绑定,则无需在此回收ctx
}
Https服务端
服务端的实现就更简单了,也不是本文的重点,所以用Go来实现一下:
package main
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"log"
"net/http"
)
const (
CACertPath = "cert/ca.crt"
ServerCertPath = "cert/server.crt"
ServerKeyPath = "cert/server_rsa_private.pem"
)
func handler(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return
}
fmt.Fprintf(w, "Get From Client: %s", string(body))
}
func main() {
// 设置服务端证书
pool := x509.NewCertPool()
crt, err := ioutil.ReadFile(CACertPath) // CA证书
if err != nil {
log.Fatalln("读取证书失败!", err.Error())
}
pool.AppendCertsFromPEM(crt)
http.HandleFunc("/echo", handler)
s := &http.Server{
Addr: ":8080",
TLSConfig: &tls.Config{
ClientCAs: pool,
ClientAuth: tls.RequireAndVerifyClientCert, // 重点!要求检验客户端证书
},
}
// 启动服务,参数分别为:服务端证书、服务端私钥
log.Fatal(s.ListenAndServeTLS(ServerCertPath, ServerKeyPath))
}
原理
双向认证基本原理
如果已经清楚双向认证原理的同学,建议直接跳过这一节。
双向认证,顾名思义,客户端和服务器端都需要验证对方的身份,可以使得连接更加安全,在建立Https连接的过程中,握手的流程比单向认证多了几步。
单向认证的过程:客户端从服务器端下载服务器端公钥证书进行验证,然后生成对称密钥用以加密数据、建立安全通信通道。
双向通信的过程:除了客户端需要从服务器端下载服务器的公钥证书进行验证外,服务端还会要求客户端提供证书,因此客户端还需要把客户端的公钥证书上传到服务器端给服务器端进行验证,等双方都认证通过了,才开始建立安全通信通道进行数据传输。
因此最基本的一点:服务端要保存服务端的证书和私钥,并在建立连接时将证书发给客户端;同样的,客户端也要保存客户端的证书和私钥,并在建立连接时将证书发给服务端。
libhv客户端实现原理那么libhv是咋做到将一个hssl_ctx绑定到http_client便可以实现双向认证(其实也就是向服务端提供自己的证书认证)的呢?
以下内容皆基于libhv v1.2.5发布版
首先,我们使用hssl_ctx_new
函数来初始化了一个hssl_ctx,而在libhv中,hssl_ctx是一个携带了证书、私钥等的SSL上下文。
对着源码,在ssl/openssl.c:16行
,声明了其初始化函数hssl_ctx_new
:可以看到,该函数执行了Openssl的SSL初始化工作,并且,若初始化时填入了param,也就是hssl_ctx_opt_t
,则hssl_ctx会使用你所设置的CA证书来执行SSL_CTX_load_verify_locations
、使用证书文件来执行SSL_CTX_use_certificate_file
、使用私钥文件来执行SSL_CTX_use_PrivateKey_file
,并随后校验证书及私钥,同时,若设置hssl_ctx_opt_t->verify_peer != 0
则会使SSL验证模式设为SSL_VERIFY_PEER
。
熟悉OpenSSL的同学应该已经发现了,上述过程,正是客户端设置己方证书、私钥并要求对方提供证书,也就是双向认证的初始化流程。
随后,我们使用http_client_set_ssl_ctx
函数来将该hssl_ctx与之前创建的HttpClient绑定,在该函数(http/client/http_client.cpp:114)中,将HttpClient的ssl_ctx字段设为了该hssl_ctx,而该字段可以在http_client_connect
(http/client/http_client.cpp:429)等函数中发现:若该字段不为NULL且为https请求,则会使用此前设置的hssl_ctx来接管整个客户端连接、通信过程,也就是使用hssl_read、hssl_write等函数来代替原本的recv、send等函数,使得整个通信信道被hssl_ctx所管理的SSL连接接管。
而由于此前已经在hssl_ctx_new时设置了己方的证书、私钥,并且SSL连接已接管,因此,在服务端要求客户端提供证书认证时,自然而然地,由OpenSSL处理了相关逻辑,正确地提供了己方证书,完成双向认证。
附加说明这里着重说明一下,为了正常使用libhv的SSL认证功能,需要在安装libhv时附加Openssl选项,这是我的编译安装命令:
# 安装openssl
sudo apt install openssl libssl-dev
# 安装带有openssl的libhv
git clone https://github.com/ithewei/libhv.git
cd libhv
./configure --with-openssl
make
sudo make install
如何编译引用libhv库的c++代码,想必看这篇文章的同学应该都会吧?我就不附上了。
生成证书及私钥:
# 生成CA密钥及其自签名证书
openssl req -newkey rsa:2048 -nodes -keyout ca_rsa_private.pem -x509 -days 365 -out ca.crt -subj "/C=CN/ST=GD/L=SZ/O=COM/OU=NSP/CN=CA/emailAddress=youremail@qq.com"
# 生成服务器密钥及待签名证书
openssl req -newkey rsa:2048 -nodes -keyout server_rsa_private.pem -out server.csr -subj "/C=CN/ST=GD/L=SZ/O=COM/OU=NSP/CN=SERVER/emailAddress=youremail@qq.com"
# 使用CA证书及密钥对服务器证书进行签名:
openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca_rsa_private.pem -CAcreateserial -out server.crt
# 生成客户端密钥及待签名证书
openssl req -newkey rsa:2048 -nodes -keyout client_rsa_private.pem -out client.csr -subj "/C=CN/ST=GD/L=SZ/O=COM/OU=NSP/CN=CLIENT/emailAddress=youremail@qq.com"
# 使用CA证书及密钥对客户端证书进行签名:
openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca_rsa_private.pem -CAcreateserial -out client.crt
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)