我正在编写简单的echo服务器和客户端,作为与 boost :: asio 和 OpenSSL 进行加密网络通信的概念证明。我希望双方使用自签名证书进行相互身份验证。我为双方使用相同的私钥,并从中生成两个证书。但是,当我尝试在双方之间建立安全通信时,我在握手阶段遇到以下错误:
证书验证失败
首先,我用以下内容生成了私钥和服务器证书:
openssl req -x509 -newkey rsa:2048 -keyout private_key.pem -out certificate.pem -days 365 -nodes
然后我用
为客户生成了额外的证书 openssl req -newkey rsa:2048 -nodes -keyout private_key.pem -x509 -days 365 -out client_certificate.pem
服务器中OpenSSL上下文的初始化是:
...
, m_sslContext(ssl::context::sslv23)
...
m_sslContext.set_verify_mode(ssl::verify_peer | ssl::verify_fail_if_no_peer_cert);
m_sslContext.use_private_key_file(options.privateKeyFile, ssl::context::pem);
m_sslContext.use_certificate_file(options.certificateFile, ssl::context::pem);
...
在客户端,它是:
...
, m_sslContext(ssl::context::sslv23)
...
m_sslContext.set_verify_mode(ssl::verify_peer);
m_sslContext.use_private_key_file(options.privateKeyFile, ssl::context::pem);
m_sslContext.use_certificate_file(options.certificateFile, ssl::context::pem);
...
服务器的完整代码在这里:
#include <iostream>
#include <iomanip>
#include <fstream>
#include <vector>
#include <string>
#include <thread>
#include <atomic>
#include <cstdint>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/program_options.hpp>
#include <boost/log/core.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/expressions.hpp>
#include <io_service_pool.hpp>
using namespace std;
using namespace boost::asio;
using namespace boost::system;
namespace po = boost::program_options;
namespace logging = boost::log;
#define CHECK_FOR_ERROR(err) \
if (err) \
{ \
BOOST_LOG_TRIVIAL(error) << err.message() << endl; \
return; \
}
namespace std
{
ostream& operator<<(ostream& ostr, const boost::asio::streambuf& buffer)
{
for (size_t i = 0; i < buffer.size(); ++i)
{
ostr << hex << (unsigned short) buffer_cast<const char*>(buffer.data())[i] << " ";
}
return ostr;
}
} // std namespace
struct Statistics
{
atomic<uint32_t> connectionsAccepted;
atomic<uint32_t> requestsReceived;
atomic<uint32_t> responsesSent;
atomic<uint64_t> dataReceived;
atomic<uint64_t> dataSent;
}; // Statistics struct
Statistics g_statistics;
class TCPConnection : public enable_shared_from_this<TCPConnection>
{
public:
using SSLSocket = ssl::stream<ip::tcp::socket>;
using Ptr = shared_ptr<TCPConnection>;
static Ptr create(io_service& ioService, ssl::context& sslContext)
{
return TCPConnection::Ptr(new TCPConnection(ioService, sslContext));
}
SSLSocket::lowest_layer_type& socket()
{
return m_socket.lowest_layer();
}
void start()
{
ip::tcp::no_delay noDelay(true);
socket().set_option(noDelay);
auto remoteAddress = socket().remote_endpoint().address().to_string();
BOOST_LOG_TRIVIAL(debug) << "Incoming connection from: " << remoteAddress;
g_statistics.connectionsAccepted++;
auto self(shared_from_this());
m_socket.async_handshake(ssl::stream_base::server, [self, remoteAddress](const error_code& err)
{
CHECK_FOR_ERROR(err)
BOOST_LOG_TRIVIAL(debug) << "Handshake with " << remoteAddress << " succeeded.";
async_read_until(self->m_socket, self->m_buffer, '\0',
[self, remoteAddress](const error_code& err, size_t bytesTransfered)
{
CHECK_FOR_ERROR(err)
BOOST_LOG_TRIVIAL(debug) << "Received " << bytesTransfered << " bytes from " << remoteAddress;
BOOST_LOG_TRIVIAL(trace) << "Data: " << self->m_buffer;
g_statistics.requestsReceived++;
g_statistics.dataReceived += bytesTransfered;
async_write(self->m_socket, self->m_buffer,
[self, remoteAddress](const error_code& err, size_t bytesTransfered)
{
CHECK_FOR_ERROR(err)
BOOST_LOG_TRIVIAL(debug) << "Sent " << dec << bytesTransfered << " bytes to " << remoteAddress;
g_statistics.responsesSent++;
g_statistics.dataSent += bytesTransfered;
});
});
});
}
private:
TCPConnection(io_service& ioService, ssl::context& sslContext)
: m_socket(ioService, sslContext)
{
}
private:
SSLSocket m_socket;
boost::asio::streambuf m_buffer;
}; // TCPConnection class
struct ProgramOptions
{
unsigned port = 0;
unsigned threadsCount = 0;
string privateKeyFile;
string certificateFile;
}; // ProgramOptions struct
class Server
{
public:
Server(const ProgramOptions& options)
: m_ioServicePool(options.threadsCount)
, m_acceptor(m_ioServicePool.getIOService(),
ip::tcp::endpoint(ip::tcp::v4(), options.port))
, m_signals(m_ioServicePool.getIOService())
, m_sslContext(ssl::context::sslv23)
{
m_acceptor.set_option(ip::tcp::acceptor::reuse_address(true));
m_signals.add(SIGINT);
m_signals.add(SIGTERM);
m_signals.add(SIGQUIT);
m_signals.async_wait([this](const error_code& err, int signalNumber)
{
if (err)
{
BOOST_LOG_TRIVIAL(error) << err.message();
}
BOOST_LOG_TRIVIAL(debug) << "Received signal: " << signalNumber;
BOOST_LOG_TRIVIAL(info) << "Statistics:";
BOOST_LOG_TRIVIAL(info) << "Connections accepted: " << g_statistics.connectionsAccepted;
BOOST_LOG_TRIVIAL(info) << "Requests received: " << g_statistics.requestsReceived;
BOOST_LOG_TRIVIAL(info) << "Responses sent: " << g_statistics.responsesSent;
BOOST_LOG_TRIVIAL(info) << "Data received: " << g_statistics.dataReceived << " bytes.";
BOOST_LOG_TRIVIAL(info) << "Data sent: " << g_statistics.dataSent << " bytes.";
m_ioServicePool.stop();
});
m_sslContext.set_verify_mode(ssl::verify_peer | ssl::verify_fail_if_no_peer_cert);
m_sslContext.use_private_key_file(options.privateKeyFile, ssl::context::pem);
m_sslContext.use_certificate_file(options.certificateFile, ssl::context::pem);
startAccept();
}
void run()
{
BOOST_LOG_TRIVIAL(info) << "Server started!";
m_ioServicePool.run();
}
private:
void startAccept()
{
auto conn = TCPConnection::create(m_ioServicePool.getIOService(), m_sslContext);
m_acceptor.async_accept(conn->socket(), [this, conn](const error_code& err)
{
CHECK_FOR_ERROR(err)
conn->start();
startAccept();
});
}
private:
IOServicePool m_ioServicePool;
ip::tcp::acceptor m_acceptor;
signal_set m_signals;
ssl::context m_sslContext;
}; // Server class
#undef CHECK_FOR_ERROR
#define SET_SEVERITY_FILTER(svrt) \
logging::core::get()->set_filter(logging::trivial::severity >= logging::trivial::svrt);
void setLoggerSeverity(const string& loggerSeverity,
const po::options_description& options)
{
if ("trace" == loggerSeverity)
{
SET_SEVERITY_FILTER(trace)
}
else if ("debug" == loggerSeverity)
{
SET_SEVERITY_FILTER(debug)
}
else if ("info" == loggerSeverity)
{
SET_SEVERITY_FILTER(info)
}
else if ("warning" == loggerSeverity)
{
SET_SEVERITY_FILTER(warning)
}
else if ("error" == loggerSeverity)
{
SET_SEVERITY_FILTER(error)
}
else if ("fatal" == loggerSeverity)
{
SET_SEVERITY_FILTER(fatal)
}
else
{
cerr << "Error: Invalid logger severity: " << loggerSeverity << endl << endl;
cerr << options << endl;
exit(1);
}
}
#undef SET_SEVERITY_FILTER
void readProgramOptions(int argc, char* argv[], ProgramOptions& options)
{
string configFileName;
string loggerSeverity;
po::options_description genericOptions("Generic options");
genericOptions.add_options()
("help,h", "Produce help message.")
("config,c", po::value<string>(&configFileName),
"Name of a file of a configuration.");
po::options_description configOptions("Configuration");
configOptions.add_options()
("port,p", po::value<unsigned>(&options.port), "Port on which the server listens.")
("threads_count,t", po::value<unsigned>(&options.threadsCount)->default_value(0),
"Number of server threads. 0 for to use hardware threads count.")
("private_key_file,r", po::value<string>(&options.privateKeyFile)->default_value("private_key.pem"),
"Server private key file name.")
("certificate_file,e", po::value<string>(&options.certificateFile)->default_value("certificate.pem"),
"Server certificate file name.")
("log_severity,l", po::value<string>(&loggerSeverity)->default_value("info"),
"Minimum severity of the log messages: [trace, debug, info, warning, error, fatal]");
po::options_description commandLineOptions("Allowed options");
commandLineOptions.add(genericOptions).add(configOptions);
po::variables_map vm;
po::store(po::parse_command_line(argc, argv, commandLineOptions), vm);
po::notify(vm);
if (vm.count("config"))
{
ifstream configFile(configFileName);
if (!configFile)
{
cerr << "Error: Can not open config file: " << configFileName << endl;
exit(1);
}
else
{
po::store(po::parse_config_file(configFile, configOptions), vm);
po::notify(vm);
}
}
if (vm.count("help"))
{
cout << commandLineOptions << endl;
exit(1);
}
if (0 == options.threadsCount)
{
options.threadsCount = thread::hardware_concurrency();
}
if (!vm.count("port"))
{
cerr << "Error: Must specify port on which the server listens!" << endl << endl;
cerr << commandLineOptions << endl;
exit(1);
}
setLoggerSeverity(loggerSeverity, commandLineOptions);
BOOST_LOG_TRIVIAL(info) << "Configuration:";
BOOST_LOG_TRIVIAL(info) << "Listening port: " << options.port;
BOOST_LOG_TRIVIAL(info) << "Worker threads count: " << options.threadsCount;
BOOST_LOG_TRIVIAL(info) << "Private key file name: " << options.privateKeyFile;
BOOST_LOG_TRIVIAL(info) << "Certificate file name: " << options.certificateFile;
}
int main(int argc, char* argv[])
{
try
{
ProgramOptions programOptions;
readProgramOptions(argc, argv, programOptions);
Server server(programOptions);
server.run();
}
catch (const exception& e)
{
BOOST_LOG_TRIVIAL(fatal) << e.what();;
}
catch (...)
{
BOOST_LOG_TRIVIAL(fatal) << "Unknown error!!!" << endl;
}
return 0;
}
客户端代码在这里:
#include <iostream>
#include <fstream>
#include <string>
#include <thread>
#include <atomic>
#include <cstdint>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/program_options.hpp>
#include <boost/log/core.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/expressions.hpp>
#include <io_service_pool.hpp>
#include <speed_limiter.hpp>
using namespace std;
using namespace boost::asio;
using namespace boost::system;
namespace po = boost::program_options;
namespace logging = boost::log;
#define CHECK_FOR_ERROR(err) \
if (err) \
{ \
BOOST_LOG_TRIVIAL(error) << err.message() << endl; \
return; \
}
namespace std
{
ostream& operator<<(ostream& ostr, const boost::asio::streambuf& buffer)
{
for (size_t i = 0; i < buffer.size(); ++i)
{
ostr << hex << (unsigned short) buffer_cast<const char*>(buffer.data())[i] << " ";
}
return ostr;
}
} // std namespace
string generateRandomData(size_t size)
{
static random_device randomDevice;
static default_random_engine randomEngine(randomDevice());
static uniform_int_distribution<unsigned char> randomByte(1, 255);
string data;
data.reserve(size);
for (size_t i = 0; i < size - 1; ++i)
{
data += randomByte(randomEngine);
}
data += '\0';
return data;
}
struct Statistics
{
atomic<uint32_t> connectionsEstablished;
atomic<uint32_t> requestsSent;
atomic<uint32_t> responsesReceived;
atomic<uint64_t> dataSent;
atomic<uint64_t> dataReceived;
}; // Statistics struct
Statistics g_statistics;
void printStatistics()
{
BOOST_LOG_TRIVIAL(info) << "Statistics:";
BOOST_LOG_TRIVIAL(info) << "Connections established: " << g_statistics.connectionsEstablished;
BOOST_LOG_TRIVIAL(info) << "Requests sent: " << g_statistics.requestsSent;
BOOST_LOG_TRIVIAL(info) << "Responses received: " << g_statistics.requestsSent;
BOOST_LOG_TRIVIAL(info) << "Data sent: " << g_statistics.dataSent << " bytes.";
BOOST_LOG_TRIVIAL(info) << "Data received: " << g_statistics.dataReceived << " bytes.";
}
class TCPConnection : public enable_shared_from_this<TCPConnection>
{
public:
using SSLSocket = ssl::stream<ip::tcp::socket>;
using Ptr = shared_ptr<TCPConnection>;
static Ptr create(io_service& ioService, ssl::context& sslContext)
{
return TCPConnection::Ptr(new TCPConnection(ioService, sslContext));
}
SSLSocket::lowest_layer_type& socket()
{
return m_socket.lowest_layer();
}
void setSendData(const std::string& data)
{
ostream stream(&m_writeBuffer);
stream.write(data.data(), data.size());
}
void startConnect(ip::tcp::resolver::iterator endpointIter)
{
if (endpointIter != ip::tcp::resolver::iterator())
{
BOOST_LOG_TRIVIAL(debug) << "Trying " << endpointIter->endpoint() << " ...";
auto self(shared_from_this());
socket().async_connect(endpointIter->endpoint(), [self, endpointIter](const error_code& err) mutable
{
if (!err)
{
ip::tcp::no_delay noDelay(true);
self->socket().set_option(noDelay);
auto endpoint = endpointIter->endpoint();
BOOST_LOG_TRIVIAL(debug) << "Connected to " << endpoint;
g_statistics.connectionsEstablished++;
self->m_socket.async_handshake(ssl::stream_base::client, [self, endpoint](const error_code& err)
{
CHECK_FOR_ERROR(err)
BOOST_LOG_TRIVIAL(debug) << "Handshake with " << endpoint << " succeeded.";
async_write(self->m_socket, self->m_writeBuffer,
[self, endpoint](const error_code& err, size_t bytesTransfered)
{
CHECK_FOR_ERROR(err)
BOOST_LOG_TRIVIAL(debug) << "Sent " << bytesTransfered << " bytes to " << endpoint;
g_statistics.requestsSent++;
g_statistics.dataSent += bytesTransfered;
async_read_until(self->m_socket, self->m_readBuffer, '\0',
[self, endpoint](const error_code& err, size_t bytesTransfered)
{
CHECK_FOR_ERROR(err)
BOOST_LOG_TRIVIAL(debug) << "Received " << bytesTransfered
<< " bytes from " << endpoint;
BOOST_LOG_TRIVIAL(trace) << "Data: " << self->m_readBuffer;
g_statistics.responsesReceived++;
g_statistics.dataReceived += bytesTransfered;
});
});
});
}
else
{
BOOST_LOG_TRIVIAL(error) << err.message();
self->startConnect(++endpointIter);
}
});
}
else
{
BOOST_LOG_TRIVIAL(warning) << "There are no more endpoints to try. Shut down the client.";
socket().close();
}
}
private:
TCPConnection(io_service& ioService, ssl::context& sslContext)
: m_socket(ioService, sslContext)
{
}
private:
SSLSocket m_socket;
boost::asio::streambuf m_writeBuffer;
boost::asio::streambuf m_readBuffer;
}; // TCPConnection class
struct ProgramOptions
{
string serverIP;
string privateKeyFile;
string certificateFile;
unsigned serverPort = 0;
unsigned requestSize = 0;
unsigned threadsCount = 0;
unsigned totalRequests = 0;
unsigned requestsPerSecond = 0;
}; // ProgramOptions struct
class Client
{
public:
Client(const ProgramOptions& options)
: m_requestSize(options.requestSize)
, m_totalRequests(options.totalRequests)
, m_requestsPerSecond(options.requestsPerSecond)
, m_ioServicePool(options.threadsCount)
, m_resolver(m_ioServicePool.getIOService())
, m_sslContext(ssl::context::sslv23)
{
ip::tcp::resolver::query query(options.serverIP, to_string(options.serverPort));
m_resolver.async_resolve(query, [this](const error_code& err, ip::tcp::resolver::iterator endpointIter)
{
CHECK_FOR_ERROR(err)
m_dispatcherThread = thread([this, endpointIter]()
{
SpeedLimiter<> speedLimiter(m_requestsPerSecond);
for (unsigned i = 0; i < m_totalRequests; ++i)
{
auto conn = TCPConnection::create(m_ioServicePool.getIOService(), m_sslContext);
conn->setSendData(generateRandomData(m_requestSize));
conn->startConnect(endpointIter);
if (m_requestsPerSecond != 0)
speedLimiter.limit();
}
this_thread::sleep_for(chrono::seconds(1));
m_ioServicePool.stop();
});
});
m_sslContext.set_verify_mode(ssl::verify_peer);
m_sslContext.use_private_key_file(options.privateKeyFile, ssl::context::pem);
m_sslContext.use_certificate_file(options.certificateFile, ssl::context::pem);
}
~Client()
{
m_dispatcherThread.join();
}
void run()
{
BOOST_LOG_TRIVIAL(info) << "Client started!";
m_ioServicePool.run();
}
private:
unsigned m_requestSize;
unsigned m_totalRequests;
unsigned m_requestsPerSecond;
IOServicePool m_ioServicePool;
ip::tcp::resolver m_resolver;
thread m_dispatcherThread;
ssl::context m_sslContext;
}; // Client class
#define SET_SEVERITY_FILTER(svrt) \
logging::core::get()->set_filter(logging::trivial::severity >= logging::trivial::svrt);
void setLoggerSeverity(const string& loggerSeverity,
const po::options_description& options)
{
if ("trace" == loggerSeverity)
{
SET_SEVERITY_FILTER(trace)
}
else if ("debug" == loggerSeverity)
{
SET_SEVERITY_FILTER(debug)
}
else if ("info" == loggerSeverity)
{
SET_SEVERITY_FILTER(info)
}
else if ("warning" == loggerSeverity)
{
SET_SEVERITY_FILTER(warning)
}
else if ("error" == loggerSeverity)
{
SET_SEVERITY_FILTER(error)
}
else if ("fatal" == loggerSeverity)
{
SET_SEVERITY_FILTER(fatal)
}
else
{
cerr << "Error: Invalid logger severity: " << loggerSeverity << endl << endl;
cerr << options << endl;
exit(1);
}
}
#undef SET_SEVERITY_FILTER
void readProgramOptions(int argc, char* argv[], ProgramOptions& options)
{
string configFileName;
string loggerSeverity;
po::options_description genericOptions("Generic options");
genericOptions.add_options()
("help,h", "Produce help message.")
("config,c", po::value<string>(&configFileName),
"Name of a file of a configuration.");
po::options_description configOptions("Configuration");
configOptions.add_options()
("ip,i", po::value<string>(&options.serverIP), "IP address of the server.")
("port,p", po::value<unsigned>(&options.serverPort), "Port number of the server.")
("threads_count,t", po::value<unsigned>(&options.threadsCount)->default_value(0),
"Number of client threads. 0 for to use hardware threads count.")
("private_key_file,r", po::value<string>(&options.privateKeyFile)->default_value("private_key.pem"),
"Client private key file name.")
("certificate_file,e", po::value<string>(&options.certificateFile)->default_value("certificate.pem"),
"Client certificate file name.")
("request_size,m", po::value(&options.requestSize), "The size of the request in bytes.")
("total_requests,t", po::value(&options.totalRequests), "The total number of requests to be send.")
("requests_per_second,s", po::value(&options.requestsPerSecond)->default_value(0),
"Speed in which requests are send. 0 for unlimited speed.")
("log_severity,l", po::value<string>(&loggerSeverity)->default_value("info"),
"Minimum severity of the log messages: [trace, debug, info, warning, error, fatal]");
po::options_description commandLineOptions("Allowed options");
commandLineOptions.add(genericOptions).add(configOptions);
po::variables_map vm;
po::store(po::parse_command_line(argc, argv, commandLineOptions), vm);
po::notify(vm);
if (vm.count("config"))
{
ifstream configFile(configFileName);
if (!configFile)
{
cerr << "Error: Can not open config file: " << configFileName << endl;
exit(1);
}
else
{
po::store(po::parse_config_file(configFile, configOptions), vm);
po::notify(vm);
}
}
if (vm.count("help"))
{
cout << commandLineOptions << endl;
exit(1);
}
if (0 == options.threadsCount)
{
options.threadsCount = thread::hardware_concurrency();
}
if (!vm.count("ip"))
{
cerr << "Error: Must specify the IP address of the server!" << endl << endl;
cerr << commandLineOptions << endl;
exit(1);
}
if (!vm.count("port"))
{
cerr << "Error: Must specify port on which to connect!" << endl << endl;
cerr << commandLineOptions << endl;
exit(1);
}
if (!vm.count("request_size"))
{
cerr << "Error: Must specify bytes size of the request!" << endl << endl;
cerr << commandLineOptions << endl;
exit(1);
}
setLoggerSeverity(loggerSeverity, commandLineOptions);
BOOST_LOG_TRIVIAL(info) << "Configuration:";
BOOST_LOG_TRIVIAL(info) << "Server IP: " << options.serverIP;
BOOST_LOG_TRIVIAL(info) << "Server port: " << options.serverPort;
BOOST_LOG_TRIVIAL(info) << "Threads count: " << options.threadsCount;
BOOST_LOG_TRIVIAL(info) << "Private key file name: " << options.privateKeyFile;
BOOST_LOG_TRIVIAL(info) << "Certificate file name: " << options.certificateFile;
BOOST_LOG_TRIVIAL(info) << "Request size: " << options.requestSize;
BOOST_LOG_TRIVIAL(info) << "Total requests: " << options.totalRequests;
BOOST_LOG_TRIVIAL(info) << "Requests per second: "
<< (options.requestsPerSecond == 0 ? "unlimited" : to_string(options.requestsPerSecond));
}
int main(int argc, char* argv[])
{
try
{
ProgramOptions programOptions;
readProgramOptions(argc, argv, programOptions);
Client client(programOptions);
client.run();
printStatistics();
}
catch (const exception& e)
{
BOOST_LOG_TRIVIAL(fatal) << e.what();;
}
catch (...)
{
BOOST_LOG_TRIVIAL(fatal) << "Unknown error!!!" << endl;
}
return 0;
}
答案 0 :(得分:2)
use_certificate_file()
和use_private_key_file()
允许您向对等方提供公钥并解密对等方返回的内容,但它们不处理验证。您应该使用add_certificate_authority()
,add_verify_path()
,load_verify_file()
,set_default_verify_paths()
和set_verify_callback()
中的一个(或多个)来配置信任验证。看起来load_verify_file()
可能就是你想要的那个。
看起来您可能误以为私钥用于验证对等证书。情况并非如此 - 密钥不用于验证签名,通常客户端和服务器私钥也不同。 CA证书用于验证对等证书。在您的情况下,因为您使用自签名证书,CA证书和对等证书是相同的。每一方都需要加载另一方的证书进行验证。