天天看點

使用openSSL和開源httplib庫搭建本地https代理伺服器及https用戶端第一部分 原理及環境搭建第二部分 代碼第三部分 結果

第一部分 原理及環境搭建

參考

httplib庫原理

httplib搭建簡單伺服器與浏覽器互動

httplib GitHub

1.0 certmgr證書管理工具

該項目本部分需要實作本地用戶端與遠端伺服器進行通信(例如國外網站),那麼如果直接進行Socket連接配接将會非常慢或者撞牆,那麼這個時候考慮使用本地用戶端先與本地代理伺服器進行通信,然後本地用戶端每次通路一次URL位址,都需要将證書添加到受信任的根證書頒發機構,不然證書以及私鑰對不上伺服器将不允許通路。檢視證書可以通過cmd指令行certmgr.msc通路證書管理器工具。

使用openSSL和開源httplib庫搭建本地https代理伺服器及https用戶端第一部分 原理及環境搭建第二部分 代碼第三部分 結果

那麼怎麼将我們的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
           

最終需要的證書如下:

使用openSSL和開源httplib庫搭建本地https代理伺服器及https用戶端第一部分 原理及環境搭建第二部分 代碼第三部分 結果

1.3 httplib環境搭建及原理

因為本項目是在windowsPC端進行,是以開發IDE為VS2017

首先httplib庫在windows 的頭檔案include格式為

使用openSSL和開源httplib庫搭建本地https代理伺服器及https用戶端第一部分 原理及環境搭建第二部分 代碼第三部分 結果

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

使用openSSL和開源httplib庫搭建本地https代理伺服器及https用戶端第一部分 原理及環境搭建第二部分 代碼第三部分 結果
使用openSSL和開源httplib庫搭建本地https代理伺服器及https用戶端第一部分 原理及環境搭建第二部分 代碼第三部分 結果
使用openSSL和開源httplib庫搭建本地https代理伺服器及https用戶端第一部分 原理及環境搭建第二部分 代碼第三部分 結果

到此VShttplib環境就搭建好了。

第二部分 代碼

2.0SSLServer和SSLCilent

需要支援OpenSSL的話 ,伺服器和用戶端的初始化格式如下:此過程需要CA憑證、伺服器證書、伺服器私鑰

使用openSSL和開源httplib庫搭建本地https代理伺服器及https用戶端第一部分 原理及環境搭建第二部分 代碼第三部分 結果

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通路。

使用openSSL和開源httplib庫搭建本地https代理伺服器及https用戶端第一部分 原理及環境搭建第二部分 代碼第三部分 結果

然後,為了防止證書添加的同時可能出現的問題,程式會先等待5秒鐘證書添加完畢(實際不需要這麼久,這裡隻是為了測試友善)

最終通信成功!

使用openSSL和開源httplib庫搭建本地https代理伺服器及https用戶端第一部分 原理及環境搭建第二部分 代碼第三部分 結果

繼續閱讀