概述
因項目需要在系統中引入https雙向認證,由于程序使用C/C++和cpprestsdk庫編寫,從網上經過一頓檢索折騰,總算測試通過,故而博文記錄用以備忘。
系統環境
Ubuntu 22.04.3 LTS
libcpprest-dev(jammy,now 2.10.18-1build2 amd64)
步驟
- 參考自簽名根證書、中間證書、服務器證書生成流程詳解,生成根證書、服務器證書以及客戶端證書,然后用根證書對服務器、客戶端的證書進行簽名
- 編寫代碼,具體代碼看后面
- 測試
測試可以使用curl
curl命令
curl -v -k --cacert root_cert.pem --cert client_cert.pem --key client_key.pem https://ip:port/
注意:
使用自簽名證書的時候,服務器端的證書Common Name字段一定要填寫對應的IP,不然會出現SSL握手失敗的情況。
服務端代碼
#include <cpprest/http_listener.h>
#include <cpprest/json.h>
#include <string>using namespace std;
using namespace web;
using namespace web::http;
using namespace web::http::experimental::listener;
namespace net = boost::asio;
namespace ssl = net::ssl;bool verify_callback(bool preverified, boost::asio::ssl::verify_context& ctx)
{char subject_name[256];X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle());X509_NAME_oneline(X509_get_subject_name(cert), subject_name, 256);std::cout << "subject_name:" << subject_name << std::endl;return true;
}int main(int argc, char *argv[]) {string host = "192.168.25.112";int port = 6000;string url = "https://" + host + ":" + to_string(port);http_listener_config conf;string cert = "./certs/server_cert.pem";string privkey = "./certs/server_key.pem";string rootcert = "./certs/root_cert.pem";conf.set_ssl_context_callback([&cert, &privkey, &rootcert](ssl::context &ctx) {try {ctx.set_options(ssl::context::default_workarounds |ssl::context::no_sslv2 | ssl::context::no_tlsv1 |ssl::context::no_tlsv1_1 | ssl::context::single_dh_use);//設置自簽名的根證書ctx.load_verify_file(rootcert);//設置根證書簽發的服務器證書ctx.use_certificate_chain_file(cert);//設置服務器證書的私鑰ctx.use_private_key_file(privkey, ssl::context::pem);//設置驗證模式 - 驗證對端證書// boost::asio::ssl::verify_fail_if_no_peer_cert 這個必須設置,不然對端不發送證書的時候,服務器是默認通過ctx.set_verify_mode(boost::asio::ssl::verify_peer | boost::asio::ssl::verify_fail_if_no_peer_cert);//設置認證回調,具體作用,沒太搞懂ctx.set_verify_callback(verify_callback);//私鑰有密碼的情況,通過該回調返回密碼//ctx.set_password_callback([]() { return "PASSWORD";});} catch (exception const &e) {clog << "ERROR: " << e.what() << endl;}});http_listener listener = http_listener(utility::conversions::to_string_t(url), conf);listener.support(methods::GET, [](web::http::http_request request) {request.reply(status_codes::OK,U("hello world"));});listener.open().wait();while(true) {sleep(100);}cout << "Listening for requests at: " << host << ":" << port << endl;
}
客戶端代碼
#define _TURN_OFF_PLATFORM_STRING
#include <cpprest/http_client.h>
#include <cpprest/json.h>
#include <string>using namespace std;
using namespace web;
using namespace web::http;
using namespace web::http::client;
namespace net = boost::asio;
namespace ssl = net::ssl;int main(int argc, char *argv[])
{string host = "192.168.25.112";int port = 6000;string url = "https://" + host + ":" + (to_string(port));string rootcert = "./certs/root_cert.pem";string privkey = "./certs/client_key.pem";string cert = "./certs/client_cert.pem";http_client_config conf;try{conf.set_ssl_context_callback([&cert, &privkey, &rootcert](boost::asio::ssl::context &ctx){// 加載根證書ctx.load_verify_file(rootcert);// 加載根證書簽發的客戶端證書ctx.use_certificate_chain_file(cert);// 加載客戶端證書的私鑰ctx.use_private_key_file(privkey, ssl::context::pem);// 設置驗證模式 - 驗證對端ctx.set_verify_mode(boost::asio::ssl::verify_peer);// 私鑰有密碼的情況,通過該回調返回密碼// ctx.set_password_callback([]() { return "PASSWORD";});});http_client client(uri_builder(url).to_uri(), conf);http_response response = client.request(methods::GET, "/").get();// Check the status code.if (response.status_code() != 200){throw std::runtime_error("Returned " + std::to_string(response.status_code()));}// Read the response body as a string.auto response_body = response.extract_string().get();// Output the response body for debugging.std::cout << "Response Body: " << response_body << std::endl;}catch (const std::runtime_error &e){std::cerr << "Exception caught: " << e.what() << __FILE__ << __FUNCTION__ << __LINE__ << std::endl;}catch (std::exception const &e){clog << "ERROR: " << e.what() << endl;}
}
注意:
以上代碼只是在自簽名證書下驗證過,對于正式環境簽發的證書,請自行嘗試。
Q&A
Q: SSL握手返回,alert number 80
A: 我不知道咋出現的,只有服務器證書的Common Name不一致的情況,該問題會出現
Q: SSL握手返回, Unknown CA
A: 通過ctx.load_verify_file(rootcert);加載根證書即可
參考鏈接
自簽名根證書、中間證書、服務器證書生成流程詳解
HTTPS client server in unix
聯盟鏈系列 - Https雙向驗證
基于boost和QT 實現客戶端/服務端TCP雙向證書認證與SSL加密
https雙向認證