最近工作中需要写TLS的 client, 同时需要一个server 测试 ut, 从学习TLS链接 和写代码花了一点时间 想记录下过程中遇到的问题
首先需要学习下什么是TLS链接,TLS链接就是再TCP的基础上增加双方的证书验证
从代码结果上看就是TCP 链接之后加一个握手 handshake, 这个handshake 用来验证client 和server的证书验证
那什么是证书呢 我附上一些我之前学习的一篇文章,很好
https://blog.csdn.net/ustccw/article/details/76691248?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control
证书验证我的理解是这样的,
首先是公钥 和私钥,公钥加密的东西可以用私钥解密,所以一般是把自己的公钥分发给别人,这样别人就可以解密你用私钥加密的东西?
理解这个之后,我们来理解一下证书,首先有个CA 证书授权中心权威机构,他把自己的公钥分给大家就是Ca证书, 然后client 和server都有各自的公钥和私钥,client 和server都会把自己的公钥和一些各自的信息制作成req文件发给CA机构,机构相信你是正经的以后就会用自己的私钥给你们的req加密(签字)生成client.cert server.cert,这样client 和server就有各自的证书.这样以后就可以双向证书验证
比如client这边又Ca证书,server发过来自己的证书,client用Ca证书(公钥)把server的证书进行解密,从而可以得到server的公钥,同理,server也可以得到client的公钥。这样就算是证书验证了
当然 其中还有其他的过程什么cipher 随机数啥的?可以看附上的链接
这里有一点需要解释就是EE证书
EE证书就是末证书,他需要串成一个证书链,就是要找到他上一级的证书签了他一直找到根证书,根证书就是自签证书,自己签自己的证书
贴一个证书解析的link
https://www.ssleye.com/cer_check.html
证书验证讲解清楚以后 TLS链接就没什么了, 下面贴一下我基于boost asio的TLS链接代码
// client.cpp
#include <sstream>
#include <memory>
#include <limits>
#include <string>
#include <algorithm>
#include <vector>
#include "AsyncHttpClient.hpp"
namespace
{
namespace
{
// 这里传进来的boost::asio::ssl::context 需要之前进行一些证书加载的操作
uint32_t AsyncHttpClient::httpClientId_ = 0;
AsyncHttpClient::AsyncHttpClient(
boost::asio::io_service &io_service,
boost::asio::ssl::context& context)
: sslSocket_(io_service, context)
, sasMessageTimer_(new boost::asio::steady_timer(io_service))
, asyncConnectTimer_(new boost::asio::steady_timer(io_service))
{
}
AsyncHttpClient::~AsyncHttpClient()
{
std::cout << info << "Destructoring AsyncHttpClient";
close();
}
void AsyncHttpClient::send(const std::string& uri, const std::string& payload,
std::function<void(Response)> responseCallback)
{
buffer_.consume(buffer_.size());
responseCallback_ = responseCallback;
boost::beast::http::request<boost::beast::http::string_body> request; // beast来写http消息
request_.set(boost::beast::http::field::connection, "Keep-Alive");
request_.target(uri);
request_.method(boost::beast::http::verb::post);
request_.body() = payload;
request_.prepare_payload();
auto endPoints = createEndpoint(“127.0.0.1”, 1234); // 这里原本需要DNS解析得到endporint, 我简化了
startConnect(endPoints);
}
boost::asio::ip::tcp::endpoint createEndpoint(const std::string& hostName,
unsigned int serverPort)
{
boost::system::error_code ec;
boost::asio::ip::address ipAddr = boost::asio::ip::address::from_string(hostName, ec);
boost::asio::ip::tcp::endpoint ep(ipAddr, serverPort);
return ep;
}
void AsyncHttpClient::startMessageTimer()
{
auto self = shared_from_this();
sasMessageTimer_->expires_from_now(std::chrono::seconds(5));
sasMessageTimer_->async_wait([self](boost::system::error_code ec)
{
if (ec && ec == boost::asio::error::operation_aborted)
{
std::cout << debug << "steady_timer was canceled";
return;
}
self->cancelMessageTimer();
std::cout << warning << "Timeout on and losing connection";
// need add exception handle
});
}
void AsyncHttpClient::cancelMessageTimer()
{
logger_ << info << __FUNCTION__;
if (sasMessageTimer_)
{
sasMessageTimer_->cancel();
}
}
void AsyncHttpClient::startConnectTimer()
{
std::cout << info << "start async Connect Timer with time 5s";
auto self = shared_from_this();
asyncConnectTimer_->expires_from_now(std::chrono::seconds(5));
asyncConnectTimer_->async_wait([self](boost::system::error_code ec)
{
if (ec && ec == boost::asio::error::operation_aborted)
{
std::cout << info << "asyncConnectTimer_ was cancelled.";
return;
}
self->dnsCachePtr_->clear(self->connectCtx_.hostname, self->connectCtx_.servicePort);
self->cancelConnectTimer();
self->sslSocket_.next_layer().cancel();
std::cout << warning
<< "Tcp connection failed due to asyncConnectTimer timeout after 5 seconds or error occur";
// need add exception handle
});
}
void AsyncHttpClient::cancelConnectTimer()
{
logger_ << info << __FUNCTION__;
if (asyncConnectTimer_)
{
asyncConnectTimer_->cancel();
}
}
void AsyncHttpClient::startConnect(std::vector<boost::asio::ip::tcp::endpoint>& results)
{
std::cout << info << "Start to connect some endpoints, size: " << results.size();
startConnectTimer();
boost::asio::async_connect(
sslSocket_.next_layer(),
results,
std::bind(
&AsyncHttpClient::handleTcpConnect,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2));
}
void AsyncHttpClient::handleTcpConnect(const boost::system::error_code& ec,
const boost::asio::ip::tcp::endpoint& endpoint)
{
std::cout << debug << __FUNCTION__;
cancelConnectTimer();
if (!ec)
{
std::cout << info << "TCP Connection to " << endpoint << " successfully.";
boost::asio::ip::tcp::no_delay option(true);
boost::system::error_code errc;
sslSocket_.lowest_layer().set_option(option, errc);
if (errc)
{
std::cout<< debug << "error code: " << errc.value() << ": " << errc.message();
}
logger_ << info << "socket has local address: " << sslSocket_.next_layer().local_endpoint();
handShake();
}
else
{
std::cout << warning << "Connection to " << endpoint << " failed "
<< "with Error: msg is: " << ec.message();
// need add exception handle
}
}
/*
void AsyncHttpClient::setSslContextSNI()
{
std::cout << debug << __FUNCTION__;
boost::system::error_code ec;
boost::asio::ip::make_address(connectCtx_.hostname, ec);
if (!ec)
{
std::cout << info << " does not set SNI for numeric IP host";
return;
}
if (!SSL_set_tlsext_host_name(sslSocket_.native_handle(), connectCtx_.hostname.c_str()))
{
std::cout << warning << "cbsd set SNI failed";
}
}
*/
void AsyncHttpClient::handShake()
{
logger_ << info << __FUNCTION__;
auto self = shared_from_this();
sslSocket_.async_handshake(
boost::asio::ssl::stream_base::client,
[self](const boost::system::error_code& ec)
{
self->logger_ << info << "async_handshake callback.";
if (ec)
{
std::cout << info
<< "SSL handshak failed with: " << ec.value() << ", msg=" << ec.message()
<< " closing socket.";
self->close();
// need add exception handle
return;
}
else
{
std::cout << info << "async_handshake success!";
self->sendHttpRequest();
}
});
}
void AsyncHttpClient::sendHttpRequest()
{
std::cout << debug << __FUNCTION__;
startMessageTimer();
boost::asio::async_write(sslSocket_,
boost::asio::buffer(request_), // 这里用成员变量避免异步的局部变量crash,
boost::bind(
&AsyncHttpClient::waitForResponse,
shared_from_this(),
boost::asio::placeholders::error));
}
void AsyncHttpClient::waitForResponse(const boost::system::error_code& ec)
{
std::cout << debug << "waitForResponse";
cancelMessageTimer();
if (ec)
{
std::cout << warning
<< "Writing: error code: " << ec.value() << ": " << ec.message();
if (ec == boost::asio::error::eof)
{
std::cout << warning << "Connection reset by peer in write";
}
close();
// need add exception
return;
}
std::cout << debug << "start reading message";
readResponse();
}
void AsyncHttpClient::readResponse()
{
std::cout << debug << __FUNCTION__ << " read-timeout will be set";
startMessageTimer();
responseParser_.body_limit(std::numeric_limits<std::uint64_t>::max());
boost::beast::http::async_read(sslSocket_,
buffer_,
responseParser_,
std::bind(
&AsyncHttpClient::handleResponse,
shared_from_this(), // do not use "this", because it will go out of scope and the socket will be closed
std::placeholders::_1,
std::placeholders::_2));
}
void AsyncHttpClient::handleResponse(const boost::system::error_code& ec, std::size_t bytesTransferred)
{
std::cout << debug << "handleResponse";
cancelMessageTimer();
close();
if (ec)
{
std::cout << debug << "Failed to read HTTP response"
<< ", error code ==>" << ec.value() << ": " << ec.message();
// need add exception handle
return;
}
int resultCode = responseParser_.get().result_int();
std::cout << info << "Http status code: " << resultCode << " String body: " << responseParser_.get().body();
// need add successful handle
}
void AsyncHttpClient::close()
{
logger_ << info << __FUNCTION__;
cancelMessageTimer();
cancelConnectTimer();
boost::system::error_code shutdownEc;
sslSocket_.shutdown(shutdownEc);
boost::system::error_code ecClose;
sslSocket_.lowest_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ecClose);
boost::system::error_code eclinkClose;
sslSocket_.lowest_layer().close(eclinkClose);
}
} // namespace
} // namespace
#include <sstream>
#include <memory>
#include <string>
#include <vector>
#include <openssl/ssl.h>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/core.hpp>
namespace
{
namespace
{
class AsyncHttpClient : public std::enable_shared_from_this<AsyncHttpClient>
{
public:
AsyncHttpClient(
boost::asio::io_service &io_service,
boost::asio::ssl::context& context);
~AsyncHttpClient();
void send(const std::string& uri, const std::string& payload,
std::function<void(Response)> responseCallback);
private:
void startMessageTimer();
void cancelMessageTimer();
void startConnectTimer();
void cancelConnectTimer();
void startConnect(std::vector<boost::asio::ip::tcp::endpoint>& results);
void handleTcpConnect(const boost::system::error_code& ec, const boost::asio::ip::tcp::endpoint& endpoint);
void handShake();
void sendHttpRequest();
void waitForResponse(const boost::system::error_code& ec);
void readResponse();
void handleResponse(const boost::system::error_code& ec, std::size_t bytesTransferred);
void close();
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> sslSocket_;
std::unique_ptr<boost::asio::steady_timer> sasMessageTimer_;
std::unique_ptr<boost::asio::steady_timer> asyncConnectTimer_;
std::string request_;
boost::beast::http::response_parser<boost::beast::http::string_body> responseParser_;
boost::beast::flat_buffer buffer_;
static uint32_t httpClientId_;
};
} // namespace
} // namespace
// 一个简单的UTcase,通过创建一个线程来模拟server端,通过端口 0 产生一个随机端口
ssl::context createSslcontext(const std::string caCert)
{
ssl::context ctx{ssl::context::tlsv12};
std::string tls12CipherSuite = "";
SSL_CTX_set_cipher_list(ctx.native_handle(), tls12CipherSuite.c_str());// 设置cipher
boost::system::error_code ec;
ctx.add_certificate_authority(
boost::asio::buffer(caCert.data(), caCert.size()), ec); // 加入Ca证书
ctx.set_verify_mode(ssl::verify_peer); // 验证对方
return ctx;
}
void startHttpServer(unsigned short& serverPort, const std::string& response)
{
httpserver_->start(serverPort, response);
}
TEST_F(AsyncHttpClientTest, sendRequestAndReceiveSuccessfully)
{
Response callbackResponse;
unsigned short serverPort = 0;
auto responseCallback = [&](Response response)
{
callbackResponse = response;
};
std::thread t1(&AsyncHttpClientTest::startHttpServer, this, std::ref(serverPort), RESPONSE);
sleep(1);
auto ctx = createSslcontext(CACERT);
httpclient_ = std::make_shared<AsyncHttpClient>(*io_, ctx);
auto endPoint = createEndpoint("127.0.0.1", serverPort);
std::vector<boost::asio::ip::tcp::endpoint> endpointVec{endPoint};
std::string payload = "***";
httpclient_->send("/v2.0/registration", payload, responseCallback);
io_->run();
t1.join();
}
// server.hpp
#include <stdio.h>
#include <string>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <boost/asio/ssl/context.hpp>
namespace ssl = boost::asio::ssl;
namespace
{
namespace
{
class AsyncHttpServer
{
public:
AsyncHttpServer();
~AsyncHttpServer();
void start(unsigned short& serverPort, const std::string& response);
private:
ssl::context createSslContext();
int create_socket(int port, std::string addr);
void init_openssl();
void cleanup_openssl();
};
} // namespace
} // namespace
#endif
#include <string>
#include <iostream>
#include <unistd.h>
#include "AsyncHttpServer.hpp"
namespace
{
namespace
{
AsyncHttpServer::AsyncHttpServer()
{
std::cout << info << "AsyncHttpServer construct";
}
AsyncHttpServer::~AsyncHttpServer()
{
}
void AsyncHttpServer::start(unsigned short& serverPort, const std::string& response)
{
init_openssl();
int sock = create_socket(0, "127.0.0.1");
if (sock <= 0)
{
std::cout << info << "create_socket failed";
return;
}
struct sockaddr_in sin;
socklen_t len1 = sizeof(sin);
if (getsockname(sock, (struct sockaddr *)&sin, &len1) == -1)
{
std::cout<< info << "Unable to get server port";
}
else
{
serverPort = ntohs(sin.sin_port);
std::cout << info << "server port: " << serverPort;
}
struct sockaddr_in addr;
uint len = sizeof(addr);
const char* reply = response.c_str();
int client = accept(sock, (struct sockaddr*)&addr, &len);
if (client < 0)
{
std::cout << info << "Unable to accept";
return;
}
auto ctx = createSslContext();
std::cout << info << "accept success";
SSL *ssl = SSL_new(ctx.native_handle());
SSL_set_fd(ssl, client);
logger_ << info << "ssl accept success";
char buf[2048] = {0};
int bytes;
if (SSL_accept(ssl) <= 0)
{
std::cout << info << "ssl accept failed";
}
else
{
bytes = SSL_read(ssl, buf, sizeof(buf));
buf[bytes] = '\0';
std::cout << info << "read from CBSD" << bytes << "msg: " << buf;
std::cout << info << "SSL_write << " << reply;
SSL_write(ssl, reply, strlen(reply));
}
SSL_shutdown(ssl);
SSL_free(ssl);
close(client);
cleanup_openssl();
}
ssl::context AsyncHttpServer::createSslContext()
{
ssl::context ctx{ssl::context::sslv23_server};
std::string tls12CipherSuite = "AES128-GCM-SHA256:AES256-GCM-SHA384";
SSL_CTX_set_cipher_list(ctx.native_handle(), tls12CipherSuite.c_str());
boost::system::error_code ecCert;
ctx.use_certificate_chain(boost::asio::buffer(CERT.data(), CERT.size()), ecCert);
boost::system::error_code ecKey;
ctx.use_private_key(boost::asio::buffer(PRIVATEKEY.data(), PRIVATEKEY.size()),
boost::asio::ssl::context::pem, ecKey);
ctx.set_verify_mode(ssl::verify_none);
return ctx;
}
int AsyncHttpServer::create_socket(int port, std::string myaddr)
{
int s;
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
inet_pton(AF_INET, myaddr.c_str(), &addr.sin_addr);
s = socket(AF_INET, SOCK_STREAM, 0);
if (s < 0)
{
std::cout << info << "Unable to create socket";
return -1;
}
if (bind(s, (struct sockaddr*)&addr, sizeof(addr)) < 0)
{
std::cout << info << "Unable to bind";
return -1;
}
logger_ << info << "listening";
if (listen(s, 1) < 0)
{
std::cout << info << "Unable to listen";
return -1;
}
return s;
}
void AsyncHttpServer::init_openssl()
{
SSL_load_error_strings();
OpenSSL_add_ssl_algorithms();
}
void AsyncHttpServer::cleanup_openssl()
{
EVP_cleanup();
}
} // namespace
} // namespace
这里再分享一下如何利用openssl里设置一些证书相关的参数
SslContext_Ptr SSLHandler::createSSLContext()
{
logger_ << debug << "creating new ssl context with mode " << static_cast<int>(mode);
auto ctx = std::make_shared<boost::asio::ssl::context>(boost::asio::ssl::context::sslv23_client);
setVerifyModeAndCallback(ctx);
sslCtxSetOptions(ctx); // 设置一下option
setCiphers(ctx);
setVerifyFile(ctx);
setCertificateChainAndKey(ctx);
setCRLs(ctx);
return ctx;
}
ctx->set_verify_mode(SSL_VERIFY_PEER, ec);// 设置验证对方证书
ctx->set_verify_callback(&SSLHandler::certificateVerificationCallback, ec); // 设置证书验证回调 callback 是static的方法
ctx->set_options(boost::asio::ssl::context::default_workarounds
| boost::asio::ssl::context::no_sslv2
| boost::asio::ssl::context::no_sslv3
| boost::asio::ssl::context::no_tlsv1
| boost::asio::ssl::context::no_tlsv1_1
| boost::asio::ssl::context::no_tlsv1_3
| boost::asio::ssl::context::single_dh_use
| SSL_OP_CIPHER_SERVER_PREFERENCE, ec);
SSL_CTX_set_cipher_list(ctx->native_handle(), ciphers.c_str()) != 1 // 设置cipher 密码套件
ctx->add_certificate_authority(boost::asio::buffer(trustAnchors.data(), trustAnchors.size()), ecWrite); // 设置一下ca 证书
ctx->use_certificate_chain(boost::asio::buffer(cert.data(), cert.size()), ec);// 设置证书链
ctx->use_private_key_file(privateKeyFile, boost::asio::ssl::context::pem, ec);// 设置私钥文件地址
最后需要设置一下crl 比较麻烦
这样就得到一个设置过整数的context
另外, 在学习boost io过程中,我觉得有以下特性
1. 调用io->run()以后会堵塞当前线程,这个当中的堵塞我觉的一种while的循环,当io当中没有task以后他就会跳出了,所以调用run方法太早会 导致什么也没做就结束了,之后放进去的任务会执行不了,这个时候可以用一个work 不让run方法跳出,没有任务一直等着任务井来 那就永远跳不出来了
2. 再多线称中会看到,哪个线程调用了run,回调函数就会到在哪个线程里掉,线程池就是多个线程调用了run 然后多个线程分配任务(回调函数),这时候会看到for循环线程掉run,一开始我以为会for完以后堵塞,其实循环堵塞
线程池可以参考这个连接 挺又意思的https://github.com/senlinzhan/code-for-blog/tree/master/boost_asio
|