Compare commits

...

7 Commits

Author SHA1 Message Date
Daniel Ottiger da746c6e67 SSLClient::set_ca_cert_store: mark as inline (#435) 2020-04-20 12:53:39 -04:00
yhirose 3451da940d Code format 2020-04-19 22:05:04 -04:00
yhirose 38a6b3e69f Fixed warning 2020-04-19 22:04:29 -04:00
yhirose d1037ee9fd Close #433 2020-04-18 16:38:15 -04:00
Daniel Ottiger 2ece5f116b Pass certs and keys from memory (#432)
* SSLServer: add constructor to pass ssl-certificates and key from memory

* SSLClient: add constructor to pass ssl-certificates and key from memory

* add TestCase for passing certificates from memory to SSLClient/SSLServer
2020-04-18 16:26:06 -04:00
yhirose c2b6e4ac04 Fix #431 2020-04-17 21:48:16 -04:00
yhirose 8674555b88 Removed CMakeLists.txt. (Fix #421) 2020-04-13 20:56:21 -04:00
4 changed files with 197 additions and 50 deletions
-29
View File
@@ -1,29 +0,0 @@
cmake_minimum_required(VERSION 3.7.0)
project(httplib)
set(CMAKE_CXX_STANDARD 11)
# Include
include(GNUInstallDirs)
include(ExternalProject)
add_library(${PROJECT_NAME} INTERFACE)
target_compile_features(${PROJECT_NAME} INTERFACE cxx_std_11)
target_include_directories(${PROJECT_NAME} INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:include>)
install(TARGETS ${PROJECT_NAME} EXPORT httplibConfig
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
install(FILES httplib.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME})
install(EXPORT httplibConfig DESTINATION share/httplib/cmake)
export(TARGETS ${PROJECT_NAME} FILE httplibConfig.cmake)
#add_subdirectory(example)
#add_subdirectory(test)
+2
View File
@@ -535,6 +535,8 @@ Include `httplib.h` before `Windows.h` or include `Windows.h` by defining `WIN32
#include <httplib.h>
```
Note: Cygwin on Windows is not supported.
License
-------
+85 -15
View File
@@ -312,7 +312,7 @@ struct Response {
void set_header(const char *key, const char *val);
void set_header(const char *key, const std::string &val);
void set_redirect(const char *url);
void set_redirect(const char *url, int status = 302);
void set_content(const char *s, size_t n, const char *content_type);
void set_content(std::string s, const char *content_type);
@@ -848,6 +848,9 @@ public:
const char *client_ca_cert_file_path = nullptr,
const char *client_ca_cert_dir_path = nullptr);
SSLServer(X509 *cert, EVP_PKEY *private_key,
X509_STORE *client_ca_cert_store = nullptr);
~SSLServer() override;
bool is_valid() const override;
@@ -865,6 +868,9 @@ public:
const std::string &client_cert_path = std::string(),
const std::string &client_key_path = std::string());
SSLClient(const std::string &host, int port, X509 *client_cert,
EVP_PKEY *client_key);
~SSLClient() override;
bool is_valid() const override;
@@ -872,6 +878,8 @@ public:
void set_ca_cert_path(const char *ca_ceert_file_path,
const char *ca_cert_dir_path = nullptr);
void set_ca_cert_store(X509_STORE *ca_cert_store);
void enable_server_certificate_verification(bool enabled);
long get_openssl_verify_result() const;
@@ -897,6 +905,7 @@ private:
std::string ca_cert_file_path_;
std::string ca_cert_dir_path_;
X509_STORE *ca_cert_store_ = nullptr;
bool server_certificate_verification_ = false;
long verify_result_ = 0;
};
@@ -2048,6 +2057,12 @@ inline bool redirect(T &cli, const Request &req, Response &res,
new_req.path = path;
new_req.redirect_count -= 1;
if (res.status == 303 && (req.method != "GET" && req.method != "HEAD")) {
new_req.method = "GET";
new_req.body.clear();
new_req.headers.clear();
}
Response new_res;
auto ret = cli.send(new_req, new_res);
@@ -2524,12 +2539,10 @@ inline bool expect_content(const Request &req) {
return false;
}
inline bool has_crlf(const char* s) {
inline bool has_crlf(const char *s) {
auto p = s;
while (*p) {
if (*p == '\r' || *p == '\n') {
return true;
}
if (*p == '\r' || *p == '\n') { return true; }
p++;
}
return false;
@@ -2790,10 +2803,14 @@ inline void Response::set_header(const char *key, const std::string &val) {
}
}
inline void Response::set_redirect(const char *url) {
inline void Response::set_redirect(const char *url, int status) {
if (!detail::has_crlf(url)) {
set_header("Location", url);
status = 302;
if (300 <= status && status < 400) {
this->status = status;
} else {
this->status = 302;
}
}
}
@@ -3510,8 +3527,7 @@ inline bool Server::routing(Request &req, Response &res, Stream &strm) {
req, res, reader, patch_handlers_for_content_reader_)) {
return true;
}
}
else if (req.method == "DELETE") {
} else if (req.method == "DELETE") {
if (dispatch_request_for_content_reader(
req, res, reader, delete_handlers_for_content_reader_)) {
return true;
@@ -4504,7 +4520,7 @@ inline bool process_and_close_socket_ssl(
}
if (ret) {
SSL_shutdown(ssl); // shutdown only if not already closed by remote
SSL_shutdown(ssl); // shutdown only if not already closed by remote
}
{
std::lock_guard<std::mutex> guard(ctx_mutex);
@@ -4532,11 +4548,11 @@ public:
private:
static void locking_callback(int mode, int type, const char * /*file*/,
int /*line*/) {
auto &locks = *openSSL_locks_;
auto &lk = (*openSSL_locks_)[static_cast<size_t>(type)];
if (mode & CRYPTO_LOCK) {
locks[type].lock();
lk.lock();
} else {
locks[type].unlock();
lk.unlock();
}
}
};
@@ -4644,6 +4660,33 @@ inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path,
}
}
inline SSLServer::SSLServer(X509 *cert, EVP_PKEY *private_key,
X509_STORE *client_ca_cert_store) {
ctx_ = SSL_CTX_new(SSLv23_server_method());
if (ctx_) {
SSL_CTX_set_options(ctx_,
SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
SSL_OP_NO_COMPRESSION |
SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
if (SSL_CTX_use_certificate(ctx_, cert) != 1 ||
SSL_CTX_use_PrivateKey(ctx_, private_key) != 1) {
SSL_CTX_free(ctx_);
ctx_ = nullptr;
} else if (client_ca_cert_store) {
SSL_CTX_set_cert_store(ctx_, client_ca_cert_store);
SSL_CTX_set_verify(
ctx_,
SSL_VERIFY_PEER |
SSL_VERIFY_FAIL_IF_NO_PEER_CERT, // SSL_VERIFY_CLIENT_ONCE,
nullptr);
}
}
}
inline SSLServer::~SSLServer() {
if (ctx_) { SSL_CTX_free(ctx_); }
}
@@ -4683,6 +4726,24 @@ inline SSLClient::SSLClient(const std::string &host, int port,
}
}
inline SSLClient::SSLClient(const std::string &host, int port,
X509 *client_cert, EVP_PKEY *client_key)
: Client(host, port) {
ctx_ = SSL_CTX_new(SSLv23_client_method());
detail::split(&host_[0], &host_[host_.size()], '.',
[&](const char *b, const char *e) {
host_components_.emplace_back(std::string(b, e));
});
if (client_cert != nullptr && client_key != nullptr) {
if (SSL_CTX_use_certificate(ctx_, client_cert) != 1 ||
SSL_CTX_use_PrivateKey(ctx_, client_key) != 1) {
SSL_CTX_free(ctx_);
ctx_ = nullptr;
}
}
}
inline SSLClient::~SSLClient() {
if (ctx_) { SSL_CTX_free(ctx_); }
}
@@ -4695,6 +4756,10 @@ inline void SSLClient::set_ca_cert_path(const char *ca_cert_file_path,
if (ca_cert_dir_path) { ca_cert_dir_path_ = ca_cert_dir_path; }
}
inline void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) {
if (ca_cert_store) { ca_cert_store_ = ca_cert_store; }
}
inline void SSLClient::enable_server_certificate_verification(bool enabled) {
server_certificate_verification_ = enabled;
}
@@ -4718,14 +4783,19 @@ inline bool SSLClient::process_and_close_socket(
true, sock, request_count, read_timeout_sec_, read_timeout_usec_,
ctx_, ctx_mutex_,
[&](SSL *ssl) {
if (ca_cert_file_path_.empty()) {
if (ca_cert_file_path_.empty() && ca_cert_store_ == nullptr) {
SSL_CTX_set_verify(ctx_, SSL_VERIFY_NONE, nullptr);
} else {
} else if (!ca_cert_file_path_.empty()) {
if (!SSL_CTX_load_verify_locations(
ctx_, ca_cert_file_path_.c_str(), nullptr)) {
return false;
}
SSL_CTX_set_verify(ctx_, SSL_VERIFY_PEER, nullptr);
} else if (ca_cert_store_ != nullptr) {
if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store_) {
SSL_CTX_set_cert_store(ctx_, ca_cert_store_);
}
SSL_CTX_set_verify(ctx_, SSL_VERIFY_PEER, nullptr);
}
if (SSL_connect(ssl) != 1) { return false; }
+110 -6
View File
@@ -749,6 +749,13 @@ protected:
})
.Get("/", [&](const Request & /*req*/,
Response &res) { res.set_redirect("/hi"); })
.Post("/1", [](const Request & /*req*/,
Response &res) { res.set_redirect("/2", 303); })
.Get("/2",
[](const Request & /*req*/, Response &res) {
res.set_content("redirected.", "text/plain");
res.status = 200;
})
.Post("/person",
[&](const Request &req, Response &res) {
if (req.has_param("name") && req.has_param("note")) {
@@ -913,10 +920,10 @@ protected:
res.set_content("DELETE", "text/plain");
})
.Delete("/delete-body",
[&](const Request &req, Response &res) {
EXPECT_EQ(req.body, "content");
res.set_content(req.body, "text/plain");
})
[&](const Request &req, Response &res) {
EXPECT_EQ(req.body, "content");
res.set_content(req.body, "text/plain");
})
.Options(R"(\*)",
[&](const Request & /*req*/, Response &res) {
res.set_header("Allow", "GET, POST, HEAD, OPTIONS");
@@ -1116,6 +1123,14 @@ TEST_F(ServerTest, GetMethod302) {
EXPECT_EQ("/hi", res->get_header_value("Location"));
}
TEST_F(ServerTest, GetMethod302Redirect) {
cli_.set_follow_location(true);
auto res = cli_.Get("/");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
EXPECT_EQ("Hello World!", res->body);
}
TEST_F(ServerTest, GetMethod404) {
auto res = cli_.Get("/invalid");
ASSERT_TRUE(res != nullptr);
@@ -1315,6 +1330,21 @@ TEST_F(ServerTest, GetMethodOutOfBaseDirMount2) {
EXPECT_EQ(404, res->status);
}
TEST_F(ServerTest, PostMethod303) {
auto res = cli_.Post("/1", "body", "text/plain");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(303, res->status);
EXPECT_EQ("/2", res->get_header_value("Location"));
}
TEST_F(ServerTest, PostMethod303Redirect) {
cli_.set_follow_location(true);
auto res = cli_.Post("/1", "body", "text/plain");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
EXPECT_EQ("redirected.", res->body);
}
TEST_F(ServerTest, UserDefinedMIMETypeMapping) {
auto res = cli_.Get("/dir/test.abcde");
ASSERT_TRUE(res != nullptr);
@@ -2142,8 +2172,8 @@ TEST(ServerRequestParsingTest, ReadHeadersRegexComplexity) {
test_raw_request(
"GET /hi HTTP/1.1\r\n"
" : "
" "
);
" "
" ");
}
TEST(ServerRequestParsingTest, ReadHeadersRegexComplexity2) {
@@ -2500,6 +2530,80 @@ TEST(SSLClientServerTest, ClientCertPresent) {
t.join();
}
TEST(SSLClientServerTest, MemoryClientCertPresent) {
X509 *server_cert;
EVP_PKEY *server_private_key;
X509_STORE *client_ca_cert_store;
X509 *client_cert;
EVP_PKEY *client_private_key;
FILE *f = fopen(SERVER_CERT_FILE, "r+");
server_cert = PEM_read_X509(f, nullptr, nullptr, nullptr);
fclose(f);
f = fopen(SERVER_PRIVATE_KEY_FILE, "r+");
server_private_key = PEM_read_PrivateKey(f, nullptr, nullptr, nullptr);
fclose(f);
f = fopen(CLIENT_CA_CERT_FILE, "r+");
client_cert = PEM_read_X509(f, nullptr, nullptr, nullptr);
client_ca_cert_store = X509_STORE_new();
X509_STORE_add_cert(client_ca_cert_store, client_cert);
X509_free(client_cert);
fclose(f);
f = fopen(CLIENT_CERT_FILE, "r+");
client_cert = PEM_read_X509(f, nullptr, nullptr, nullptr);
fclose(f);
f = fopen(CLIENT_PRIVATE_KEY_FILE, "r+");
client_private_key = PEM_read_PrivateKey(f, nullptr, nullptr, nullptr);
fclose(f);
SSLServer svr(server_cert, server_private_key, client_ca_cert_store);
ASSERT_TRUE(svr.is_valid());
svr.Get("/test", [&](const Request &req, Response &res) {
res.set_content("test", "text/plain");
svr.stop();
ASSERT_TRUE(true);
auto peer_cert = SSL_get_peer_certificate(req.ssl);
ASSERT_TRUE(peer_cert != nullptr);
auto subject_name = X509_get_subject_name(peer_cert);
ASSERT_TRUE(subject_name != nullptr);
std::string common_name;
{
char name[BUFSIZ];
auto name_len = X509_NAME_get_text_by_NID(subject_name, NID_commonName,
name, sizeof(name));
common_name.assign(name, static_cast<size_t>(name_len));
}
EXPECT_EQ("Common Name", common_name);
X509_free(peer_cert);
});
thread t = thread([&]() { ASSERT_TRUE(svr.listen(HOST, PORT)); });
std::this_thread::sleep_for(std::chrono::milliseconds(1));
httplib::SSLClient cli(HOST, PORT, client_cert, client_private_key);
auto res = cli.Get("/test");
cli.set_timeout_sec(30);
ASSERT_TRUE(res != nullptr);
ASSERT_EQ(200, res->status);
X509_free(server_cert);
EVP_PKEY_free(server_private_key);
X509_free(client_cert);
EVP_PKEY_free(client_private_key);
t.join();
}
TEST(SSLClientServerTest, ClientCertMissing) {
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE, CLIENT_CA_CERT_FILE,
CLIENT_CA_CERT_DIR);