使用openSSL和开源httplib库搭建本地https代理服务器及https客户端

第一部分 原理及环境搭建

参考

httplib库原理

httplib搭建简单服务器与浏览器交互

httplib GitHub

1.0 certmgr证书管理工具

该项目本部分需要实现本地客户端与远端服务器进行通信(例如国外网站),那么如果直接进行Socket连接将会非常慢或者撞墙,那么这个时候考虑使用本地客户端先与本地代理服务器进行通信,然后本地客户端每次访问一次URL地址,都需要将证书添加到受信任的根证书颁发机构,不然证书以及私钥对不上服务器将不允许访问。查看证书可以通过cmd命令行certmgr.msc访问证书管理器工具

 

 

 

那么怎么将我们的CA证书添加到受信任的根证书颁发机构是本项目的第一个难点。我查阅了MSDN关于certmgr的相关命令

Certmgr.exe(证书管理器工具)

使用以下命令可以将想要添加的证书添加到root根目录。

certmgr /c /add TrustedCert.cer /s root

 

1.1 设置本地代理服务器

设置本地代理服务器就是需要将要访问的IP地址或者域名添加到本地环回地址127.0.0.1,这样每次访问远端服务器时,客户端先与本地代理服务器进行通信,并且通过CA证书与Server证书和Server私钥进行比对,比对成功则让客户端以为本地代理服务器即是需要访问的地址,现在需要的就是在浏览器中访问 https://www.xxx.com,让浏览器认为本地代理服务器就是 www.xxx.com,并且显示本地代理服务其返回的一句话(随便写,比如 hello xxx!)

添加IP地址或域名到环回地址的方式可以通过修改driver中的hosts文件得到,Win10用户的hosts文件路径如下:

C:\Windows\System32\drivers\etc\hosts

打开hosts文件(无后缀),显示如下:

# BitDefender has cleaned hosts file

127.0.0.1    localhost


#Original code from this file
#<Line Removed>:#  Copyright  (c)  1993-2009  Microsoft  Corp.
#<Line Removed>:#
#<Line Removed>:#  This  is  a  sample  HOSTS  file  used  by  Microsoft  TCP/IP  for  Windows.
#<Line Removed>:#
#<Line Removed>:#  This  file  contains  the  mappings  of  IP  addresses  to  host  names.  Each
#<Line Removed>:#  entry  should  be  kept  on  an  individual  line.  The  IP  address  should
#<Line Removed>:#  be  placed  in  the  first  column  followed  by  the  corresponding  host  name.
#<Line Removed>:#  The  IP  address  and  the  host  name  should  be  separated  by  at  least  one
#<Line Removed>:#  space.
#<Line Removed>:#
#<Line Removed>:#  Additionally,  comments  (such  as  these)  may  be  inserted  on  individual
#<Line Removed>:#  lines  or  following  the  machine  name  denoted  by  a  '#'  symbol.
#<Line Removed>:#
#<Line Removed>:#  For  example:
#<Line Removed>:#
#<Line Removed>:#            102.54.94.97          rhino.acme.com                    #  source  server
#<Line Removed>:#              38.25.63.10          x.acme.com                            #  x  client  host
#<Line Removed>:#  localhost  name  resolution  is  handled  within  DNS  itself.
#<Line Removed>:#	127.0.0.1              localhost
#<Line Removed>:#	::1                          localhost
#<Line Removed>:127.0.0.1	ieonline.microsoft.com
127.0.0.1	ieonline.microsoft.com

在文件尾部添加一行代码,比如添加百度为环回地址:

这里需要注意!!!!如果后面需要正常访问百度则需要删除这一行,不然浏览器访问www.baidu.com会一直认为这是一个环回地址!

127.0.0.1	www.baidu.com

到这里代理服务器环境就搭建好了。

1.2 httplib实现https框架搭建

cpp-httplib是一个c++封装的http库,使用这个库可以在windows平台下完成http客户端、http服务端的搭建。

本次我们主要用到https客户端、https服务器端进行网络通信,值得一提的是httplib提供http、http2、https协议。

在用到https协议的时候,需要使用openSSL签发证书和私钥,另外https客服端的类名是SSLClient,它继承自Client类,https服务器端类名是SSLServer,它继承自Server类。

OpenSSL下载地址

OpenSSL生成CA证书、服务器证书及私钥

生成如下的.pem .crt证书文件

cert.pem转cert.crt文件的OpenSSL环境命令为:

openssl x509 -outform der -in cert.pem -out cert.crt

 

最终需要的证书如下:

 

 

1.3 httplib环境搭建及原理

因为本项目是在windowsPC端进行,所以开发IDE为VS2017

首先httplib库在windows 的头文件include格式为


1、通过宏开关CPPHTTPLIB_OPENSSL_SUPPORT控制是否使用Https

如果项目只使用https的话可以直接在头文件中输入一行代码,表示我们要使用的是https格式。

#define CPPHTTPLIB_OPENSSL_SUPPORT


2、然后要使用openSSL证书签发工具的,加入openssl包含路径:C:\OpenSSL-Win64\include,加入openssl的导入库路径C:\OpenSSL-Win64\lib,并链接libcrypto.lib、libssl.lib、openssl.lib

到此VShttplib环境就搭建好了。

第二部分 代码

2.0SSLServer和SSLCilent

需要支持OpenSSL的话 ,服务器和客户端的初始化格式如下:此过程需要CA证书、服务器证书、服务器私钥

2.1 代理服务器端

服务端首先Get(),先注册对应关系,先告诉自己的服务器,当我们遇到什么请求方法,请求什么资源,在回调什么函数。将浙西全部都记录在map中。当listen监听的时候,才建立起服务端。
若服务端收到了http请求,解析之后。若请求中的path,对应了Get接口传入的path(也就是能在map中找到对应关系),则服务端会创建一个线程回调这个传入的函数helloworld()对这次的请求进行业务处理
服务器端监听的是www.ludashi.com,端口为8080

#include <iostream>
#include <httplib.h>
#include <Windows.h>
#include <iostream>
#include <shellapi.h>
#define SERVER_CERT_FILE "C:\\cert.pem"
#define SERVER_PRIVATE_KEY_FILE "C:\\key.pem"
//#define CPPHTTPLIB_OPENSSL_SUPPORT
using namespace std;
using namespace httplib;
#pragma comment(lib, "openssl.lib")
#pragma comment(lib, "libcrypto.lib")
#pragma comment(lib, "libssl.lib")

std::string dump_headers(const Headers &headers) {
  std::string s;
  char buf[BUFSIZ];

  for (auto it = headers.begin(); it != headers.end(); ++it) {
    const auto &x = *it;
    snprintf(buf, sizeof(buf), "%s: %s\n", x.first.c_str(), x.second.c_str());
    s += buf;
  }

  return s;
}

std::string log(const Request &req, const Response &res) {
  std::string s;
  char buf[BUFSIZ];

  s += "================================\n";

  snprintf(buf, sizeof(buf), "%s %s %s", req.method.c_str(),
    req.version.c_str(), req.path.c_str());
  s += buf;

  std::string query;
  for (auto it = req.params.begin(); it != req.params.end(); ++it) {
    const auto &x = *it;
    snprintf(buf, sizeof(buf), "%c%s=%s",
      (it == req.params.begin()) ? '?' : '&', x.first.c_str(),
      x.second.c_str());
    query += buf;
  }
  snprintf(buf, sizeof(buf), "%s\n", query.c_str());
  s += buf;

  s += dump_headers(req.headers);

  s += "--------------------------------\n";

  snprintf(buf, sizeof(buf), "%d %s\n", res.status, res.version.c_str());
  s += buf;
  s += dump_headers(res.headers);
  s += "\n";

  if (!res.body.empty()) { s += res.body; }

  s += "\n";

  return s;
}

int main(void) {

  SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
  cout << "Waiting for the connection..."<<endl;

  if (!svr.is_valid()) {
    printf("server has an error...\n");
    return -1;
  }

  svr.Get("/", [=](const Request & /*req*/, Response &res) {
    res.set_redirect("/hi");
  });

  svr.Get("/hi", [](const Request & /*req*/, Response &res) {
    res.set_content("<html><h1>Hello ludashi!</h1></html>", "text/html");
  });

  svr.Get("/slow", [](const Request & /*req*/, Response &res) {
    std::this_thread::sleep_for(std::chrono::seconds(2));
    res.set_content("Slow...\n", "text/plain");
  });

  svr.Get("/dump", [](const Request &req, Response &res) {
    res.set_content(dump_headers(req.headers), "text/plain");
  });

  svr.Get("/stop", [&](const Request & /*req*/, Response & /*res*/) 
  { svr.stop(); });

  svr.set_error_handler([](const Request & /*req*/, Response &res) {
    const char *fmt = "<p>Error Status: <span style='color:red;'>%d</span></p>";
    char buf[BUFSIZ];
    snprintf(buf, sizeof(buf), fmt, res.status);
    res.set_content(buf, "text/html");
  });

  svr.set_logger([](const Request &req, const Response &res) {
    printf("%s", log(req, res).c_str());
  });

  svr.listen("www.baidu.com", 8080);
  system("pause");
  return 0;
}

2.2 客户端

首先向受信任根目录中添加证书,然后向服务器Get请求

system("C:\\certmgr.exe /add /c C:\\cert.crt /s root");
auto res = cli.Get("/hi");

 

#include<httplib.h>
#include<windows.h>
#include<iostream>
#include<shellapi.h>
#define CA_CERT_FILE "./cert.crt"
#pragma comment(lib, "openssl.lib")
#pragma comment(lib, "libcrypto.lib")
#pragma comment(lib, "libssl.lib")
using namespace std;
using namespace httplib;

int main(void) {
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
  system("C:\\certmgr.exe /add /c C:\\cert.crt /s root");
  cout << "Try to connect....." << endl;
  Sleep(5000);
  httplib::SSLClient cli("www.ludashi.com", 8080);

  cli.set_ca_cert_path(CA_CERT_FILE);
  cli.enable_server_certificate_verification(true);
#else
  httplib::Client cli("www.ludashi.com", 8080);
#endif

  auto res = cli.Get("/hi");
  if (res) {
    cout << res->status << endl;
    cout << res->get_header_value("Content-Type") << endl;
    cout << res->body << endl;
  }
  else {
    cout << "error" << endl;
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT

    auto result = cli.get_openssl_verify_result();
    if (result) {
      cout << "verify error: " << X509_verify_cert_error_string(result) << endl;
    }
#endif
  }

  return 0;
}

第三部分 结果

首先回询问是否要安装证书,这里点击是就将证书直接添加到了受信任的证书根目录下,这一步非常重要,因为不添加证书的话,无法进行https访问。

然后,为了防止证书添加的同时可能出现的问题,程序会先等待5秒钟证书添加完毕(实际不需要这么久,这里只是为了测试方便)

最终通信成功!

  • 3
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值