Skip to content

Commit 43ee1f0

Browse files
feat: ssl context management (#1398)
1 parent 27dbfd9 commit 43ee1f0

File tree

10 files changed

+261
-43
lines changed

10 files changed

+261
-43
lines changed

include/dpp/appcommand.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -942,7 +942,7 @@ class DPP_EXPORT interaction : public managed, public json_interface<interaction
942942
* @brief Get a resolved object from the resolved set
943943
*
944944
* @tparam T type of object to retrieve
945-
* @tparam C container defintion for resolved container
945+
* @tparam C container definition for resolved container
946946
* @param id Snowflake ID
947947
* @param resolved_set container for the type
948948
* @return const T& retrieved type

include/dpp/http_server.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <dpp/cluster.h>
2626
#include <dpp/socket_listener.h>
2727
#include <dpp/http_server_request.h>
28+
#include <dpp/ssl_context.h>
2829

2930
namespace dpp {
3031

@@ -40,6 +41,11 @@ struct http_server : public socket_listener<http_server_request> {
4041
*/
4142
http_server_request_event request_handler;
4243

44+
/**
45+
* @brief Port we are listening on
46+
*/
47+
uint16_t bound_port;
48+
4349
/**
4450
* @brief Constructor for creation of a HTTP(S) server
4551
* @param creator Cluster creator
@@ -56,6 +62,13 @@ struct http_server : public socket_listener<http_server_request> {
5662
* @param newfd file descriptor of new request
5763
*/
5864
void emplace(socket newfd) override;
65+
66+
/**
67+
* @brief Destructor
68+
*/
69+
virtual ~http_server() {
70+
detail::release_ssl_context(bound_port);
71+
}
5972
};
6073

6174
}

include/dpp/http_server_request.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,12 +165,13 @@ class DPP_EXPORT http_server_request : public ssl_connection {
165165
* have a file descriptor.
166166
* @param creator creating owner
167167
* @param fd file descriptor
168+
* @param port Port the connection came in on
168169
* @param plaintext_downgrade true if plaintext, false if SSL
169170
* @param private_key if SSL, the path to the private key PEM
170171
* @param public_key if SSL, the path to the public key PEM
171172
* @param handle_request request handler callback
172173
*/
173-
http_server_request(cluster* creator, socket fd, bool plaintext_downgrade, const std::string& private_key, const std::string& public_key, http_server_request_event handle_request);
174+
http_server_request(cluster* creator, socket fd, uint16_t port, bool plaintext_downgrade, const std::string& private_key, const std::string& public_key, http_server_request_event handle_request);
174175

175176
/**
176177
* @brief Destroy the https client object

include/dpp/ssl_context.h

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/************************************************************************************
2+
*
3+
* D++, A Lightweight C++ library for Discord
4+
*
5+
* SPDX-License-Identifier: Apache-2.0
6+
* Copyright 2021 Craig Edwards and D++ contributors
7+
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
8+
*
9+
* Licensed under the Apache License, Version 2.0 (the "License");
10+
* you may not use this file except in compliance with the License.
11+
* You may obtain a copy of the License at
12+
*
13+
* http://www.apache.org/licenses/LICENSE-2.0
14+
*
15+
* Unless required by applicable law or agreed to in writing, software
16+
* distributed under the License is distributed on an "AS IS" BASIS,
17+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
* See the License for the specific language governing permissions and
19+
* limitations under the License.
20+
*
21+
************************************************************************************/
22+
#pragma once
23+
#include <string>
24+
#include <cstdint>
25+
26+
namespace dpp::detail {
27+
28+
struct wrapped_ssl_ctx;
29+
30+
/**
31+
* @brief Generate a new wrapped SSL context.
32+
* If an SSL context already exists for the given port number, it will be returned, else a new one will be
33+
* generated and cached. Contexts with port = 0 will be considered client contexts. There can only be one
34+
* client context at a time and it covers all SSL client connections. There can be many SSL server contexts,
35+
* individual ones can be cached per-port, each with their own loaded SSL private and public key PEM certificate.
36+
*
37+
* @param port Port number. Pass zero to create or get the client context.
38+
* @param private_key Private key PEM pathname for server contexts
39+
* @param public_key Public key PEM pathname for server contexts
40+
* @return wrapped SSL context
41+
*/
42+
wrapped_ssl_ctx* generate_ssl_context(uint16_t port = 0, const std::string &private_key = "", const std::string &public_key = "");
43+
44+
/**
45+
* @brief Release an SSL context
46+
* @warning Only do this if you are certain no SSL connections remain that use this context.
47+
* As OpenSSL is a C library it is impossible for us to track this on its behalf. Be careful!
48+
* @param port port number to release
49+
*/
50+
void release_ssl_context(uint16_t port = 0);
51+
52+
};

include/dpp/sslconnection.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,11 +311,12 @@ class DPP_EXPORT ssl_connection
311311
* @brief Accept a new connection from listen()/accept() socket
312312
* @param creator Creating cluster
313313
* @param fd Socket file descriptor assigned by accept()
314+
* @param port Port the new fd came from
314315
* @param plaintext_downgrade Set to true to connect using plaintext only, without initialising SSL.
315316
* @param private_key if plaintext_downgrade is set to false, a private key PEM file for SSL connections
316317
* @param public_key if plaintext_downgrade is set to false, a public key PEM file for SSL connections
317318
*/
318-
ssl_connection(cluster* creator, socket fd, bool plaintext_downgrade = false, const std::string& private_key = "", const std::string& public_key = "");
319+
ssl_connection(cluster* creator, socket fd, uint16_t port, bool plaintext_downgrade = false, const std::string& private_key = "", const std::string& public_key = "");
319320

320321
/**
321322
* @brief Set up non blocking I/O and configure on_read, on_write and on_error.

include/dpp/wrapped_ssl_ctx.h

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/************************************************************************************
2+
*
3+
* D++, A Lightweight C++ library for Discord
4+
*
5+
* SPDX-License-Identifier: Apache-2.0
6+
* Copyright 2021 Craig Edwards and D++ contributors
7+
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
8+
*
9+
* Licensed under the Apache License, Version 2.0 (the "License");
10+
* you may not use this file except in compliance with the License.
11+
* You may obtain a copy of the License at
12+
*
13+
* http://www.apache.org/licenses/LICENSE-2.0
14+
*
15+
* Unless required by applicable law or agreed to in writing, software
16+
* distributed under the License is distributed on an "AS IS" BASIS,
17+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
* See the License for the specific language governing permissions and
19+
* limitations under the License.
20+
*
21+
************************************************************************************/
22+
#include <dpp/exception.h>
23+
#include <openssl/ssl.h>
24+
#pragma once
25+
26+
namespace dpp::detail {
27+
28+
/**
29+
* @brief This class wraps a raw SSL_CTX pointer, managing moving,
30+
* creation, and RAII destruction.
31+
*/
32+
struct wrapped_ssl_ctx {
33+
34+
/**
35+
* @brief SSL_CTX pointer, raw C pointer nastiness
36+
*/
37+
SSL_CTX *context{nullptr};
38+
39+
/**
40+
* @brief Create a wrapped SSL context
41+
* @param is_server true to create a server context, false to create a client context
42+
* @throws dpp::connection_exception if context could not be created
43+
*/
44+
explicit wrapped_ssl_ctx(bool is_server = false) : context(SSL_CTX_new(is_server ? TLS_server_method() : TLS_client_method())) {
45+
if (context == nullptr) {
46+
throw dpp::connection_exception(err_ssl_context, "Failed to create SSL client context!");
47+
}
48+
}
49+
50+
/**
51+
* @brief Copy constructor
52+
* @note Intentionally deleted
53+
*/
54+
wrapped_ssl_ctx(const wrapped_ssl_ctx&) = delete;
55+
56+
/**
57+
* @brief Copy assignment operator
58+
* @note Intentionally deleted
59+
*/
60+
wrapped_ssl_ctx& operator=(const wrapped_ssl_ctx&) = delete;
61+
62+
/**
63+
* @brief Move constructor
64+
* @param other source context
65+
*/
66+
wrapped_ssl_ctx(wrapped_ssl_ctx&& other) noexcept : context(other.context) {
67+
other.context = nullptr;
68+
}
69+
70+
/**
71+
* @brief Move assignment operator
72+
* @param other source context
73+
* @return self
74+
*/
75+
wrapped_ssl_ctx& operator=(wrapped_ssl_ctx&& other) noexcept {
76+
if (this != &other) {
77+
/* Free current context if any and transfer ownership */
78+
SSL_CTX_free(context);
79+
context = other.context;
80+
other.context = nullptr;
81+
}
82+
return *this;
83+
}
84+
85+
~wrapped_ssl_ctx() {
86+
SSL_CTX_free(context);
87+
}
88+
};
89+
90+
};

src/dpp/http_server.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@
2525
namespace dpp {
2626

2727
http_server::http_server(cluster* owner, const std::string_view address, uint16_t port, http_server_request_event handle_request, const std::string& private_key, const std::string& public_key)
28-
: socket_listener<http_server_request>(owner, address, port, private_key.empty() ? li_plaintext : li_ssl, private_key, public_key), request_handler(handle_request)
28+
: socket_listener<http_server_request>(owner, address, port, private_key.empty() ? li_plaintext : li_ssl, private_key, public_key), request_handler(handle_request), bound_port(port)
2929
{
3030
}
3131

3232
void http_server::emplace(socket newfd) {
33-
connections.emplace(newfd, std::make_unique<http_server_request>(creator, newfd, plaintext, private_key_file, public_key_file, request_handler));
33+
connections.emplace(newfd, std::make_unique<http_server_request>(creator, newfd, bound_port, plaintext, private_key_file, public_key_file, request_handler));
3434
}
3535

3636
}

src/dpp/http_server_request.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ constexpr std::array verb {
4343
"TRACE",
4444
};
4545

46-
http_server_request::http_server_request(cluster* creator, socket fd, bool plaintext_downgrade, const std::string& private_key, const std::string& public_key, http_server_request_event handle_request)
47-
: ssl_connection(creator, fd, plaintext_downgrade, private_key, public_key),
46+
http_server_request::http_server_request(cluster* creator, socket fd, uint16_t port, bool plaintext_downgrade, const std::string& private_key, const std::string& public_key, http_server_request_event handle_request)
47+
: ssl_connection(creator, fd, port, plaintext_downgrade, private_key, public_key),
4848
timeout(time(nullptr) + 10),
4949
handler(handle_request),
5050
state(HTTPS_HEADERS),

src/dpp/ssl_context.cpp

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/************************************************************************************
2+
*
3+
* D++, A Lightweight C++ library for Discord
4+
*
5+
* SPDX-License-Identifier: Apache-2.0
6+
* Copyright 2021 Craig Edwards and D++ contributors
7+
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
8+
*
9+
* Licensed under the Apache License, Version 2.0 (the "License");
10+
* you may not use this file except in compliance with the License.
11+
* You may obtain a copy of the License at
12+
*
13+
* http://www.apache.org/licenses/LICENSE-2.0
14+
*
15+
* Unless required by applicable law or agreed to in writing, software
16+
* distributed under the License is distributed on an "AS IS" BASIS,
17+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
* See the License for the specific language governing permissions and
19+
* limitations under the License.
20+
*
21+
************************************************************************************/
22+
#include <dpp/ssl_context.h>
23+
#include <dpp/exception.h>
24+
#include <vector>
25+
#include <memory>
26+
#include <openssl/ssl.h>
27+
#include <mutex>
28+
#include <shared_mutex>
29+
#include <dpp/wrapped_ssl_ctx.h>
30+
31+
namespace dpp::detail {
32+
33+
/**
34+
* @brief The vector of pairs of wrapped contexts is efficient for small numbers of contexts.
35+
* In a real world production application we expect to have 2 to 5 at most contexts, and for
36+
* most bots that do not use server ports, there will be only one context on port 0.
37+
* This is O(n), but in the most common situation of having one entry, it is O(1).
38+
*/
39+
static std::vector<std::pair<uint16_t, std::unique_ptr<wrapped_ssl_ctx>>> contexts;
40+
41+
/**
42+
* @brief Managing SSL contexts is thread-safe.
43+
*/
44+
static std::shared_mutex context_mutex;
45+
46+
void release_ssl_context(uint16_t port) {
47+
std::unique_lock lock(context_mutex);
48+
auto it = std::remove_if(contexts.begin(), contexts.end(), [port](const auto& entry) { return entry.first == port; });
49+
if (it != contexts.end()) {
50+
contexts.erase(it, contexts.end());
51+
}
52+
}
53+
54+
wrapped_ssl_ctx* generate_ssl_context(uint16_t port, const std::string &private_key, const std::string &public_key) {
55+
{
56+
std::shared_lock lock(context_mutex);
57+
for (const auto& [p, ctx] : contexts) {
58+
if (p == port) {
59+
return ctx.get();
60+
}
61+
}
62+
}
63+
64+
std::unique_ptr<wrapped_ssl_ctx> context = std::make_unique<wrapped_ssl_ctx>(port != 0);
65+
66+
if (port != 0) {
67+
if (SSL_CTX_use_certificate_file(context->context, public_key.c_str(), SSL_FILETYPE_PEM) <= 0) {
68+
throw dpp::connection_exception(err_ssl_context, "Failed to set public key certificate");
69+
}
70+
if (SSL_CTX_use_PrivateKey_file(context->context, private_key.c_str(), SSL_FILETYPE_PEM) <= 0) {
71+
throw dpp::connection_exception(err_ssl_context, "Failed to set private key certificate");
72+
}
73+
}
74+
75+
/* This sets the allowed SSL/TLS versions for the connection.
76+
* Do not allow SSL 3.0, TLS 1.0 or 1.1
77+
* https://www.packetlabs.net/posts/tls-1-1-no-longer-secure/
78+
*/
79+
if (!SSL_CTX_set_min_proto_version(context->context, TLS1_2_VERSION)) {
80+
throw dpp::connection_exception(err_ssl_version, "Failed to set minimum SSL version!");
81+
}
82+
83+
std::unique_lock lock(context_mutex);
84+
contexts.emplace_back(port, std::move(context));
85+
return contexts.back().second.get();
86+
}
87+
88+
}

0 commit comments

Comments
 (0)