TrinityCore
SslContext.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "SslContext.h"
19#include "Config.h"
20#include "Log.h"
21#include "Memory.h"
22#include <boost/filesystem/operations.hpp>
23#include <boost/filesystem/path.hpp>
24#include <openssl/store.h>
25#include <openssl/ui.h>
26
27namespace
28{
29auto CreatePasswordUiMethodFromPemCallback(::pem_password_cb* callback)
30{
31 return Trinity::make_unique_ptr_with_deleter(UI_UTIL_wrap_read_pem_callback(callback, 0), ::UI_destroy_method);
32}
33
34auto OpenOpenSSLStore(boost::filesystem::path const& storePath, UI_METHOD const* passwordCallback, void* passwordCallbackData)
35{
36 std::string uri;
37 uri.reserve(6 + storePath.size());
38
39 uri += "file:";
40 std::string genericPath = storePath.generic_string();
41 if (!genericPath.empty() && !genericPath.starts_with('/'))
42 uri += '/'; // ensure the path starts with / (windows special case, unix absolute paths already do)
43
44 uri += genericPath;
45
46 return Trinity::make_unique_ptr_with_deleter(OSSL_STORE_open(uri.c_str(), passwordCallback, passwordCallbackData, nullptr, nullptr), ::OSSL_STORE_close);
47}
48
49boost::system::error_code GetLastOpenSSLError()
50{
51 auto ossl_error = ::ERR_get_error();
52#if OPENSSL_VERSION_NUMBER >= 0x30000000L
53 if (ERR_SYSTEM_ERROR(ossl_error))
54 return boost::system::error_code(static_cast<int>(::ERR_GET_REASON(ossl_error)), boost::asio::error::get_system_category());
55#endif
56
57 return boost::system::error_code(static_cast<int>(ossl_error), boost::asio::error::get_ssl_category());
58}
59}
60
62{
63 boost::system::error_code err;
64#define LOAD_CHECK(fn) do { fn; \
65 if (err) \
66 { \
67 TC_LOG_ERROR("server.ssl", #fn " failed: {}", err.message()); \
68 return false; \
69 } } while (0)
70
71 std::string certificateChainFile = sConfigMgr->GetStringDefault("CertificatesFile", "./bnetserver.cert.pem");
72
73 auto passwordCallback = [](std::size_t /*max_length*/, boost::asio::ssl::context::password_purpose /*purpose*/) -> std::string
74 {
75 return sConfigMgr->GetStringDefault("PrivateKeyPassword", "");
76 };
77
78 LOAD_CHECK(instance().set_password_callback(passwordCallback, err));
79
80 SSL_CTX* nativeContext = instance().native_handle();
81 auto password_ui_method = CreatePasswordUiMethodFromPemCallback(SSL_CTX_get_default_passwd_cb(nativeContext));
82
83 auto store = OpenOpenSSLStore(boost::filesystem::absolute(certificateChainFile),
84 password_ui_method.get(), SSL_CTX_get_default_passwd_cb_userdata(nativeContext));
85
86 if (!store)
87 {
88 err = GetLastOpenSSLError();
89 TC_LOG_ERROR("server.ssl", "OSSL_STORE_open failed: {}", err.message());
90 return false;
91 }
92
93 EVP_PKEY* key = nullptr;
94 STACK_OF(X509)* certs = sk_X509_new_null();
95 while (!OSSL_STORE_eof(store.get()))
96 {
97 OSSL_STORE_INFO* info = OSSL_STORE_load(store.get());
98 if (!info)
99 continue;
100
101 switch (OSSL_STORE_INFO_get_type(info))
102 {
103 case OSSL_STORE_INFO_PKEY:
104 key = OSSL_STORE_INFO_get1_PKEY(info);
105 break;
106 case OSSL_STORE_INFO_CERT:
107 sk_X509_push(certs, OSSL_STORE_INFO_get1_CERT(info));
108 break;
109 default:
110 break;
111 }
112 }
113
114 if (sk_X509_num(certs) > 0)
115 {
116 X509* cert = sk_X509_shift(certs);
117 SSL_CTX_use_cert_and_key(nativeContext, cert, key, certs, 1);
118 }
119
120 sk_X509_free(certs);
121
122 if (!key)
123 {
124 std::string privateKeyFile = sConfigMgr->GetStringDefault("PrivateKeyFile", "./bnetserver.key.pem");
125 LOAD_CHECK(instance().use_private_key_file(privateKeyFile, boost::asio::ssl::context::pem, err));
126 }
127
128#undef LOAD_CHECK
129
130 return true;
131}
132
133boost::asio::ssl::context& Battlenet::SslContext::instance()
134{
135 static boost::asio::ssl::context context(boost::asio::ssl::context::tls);
136 return context;
137}
#define sConfigMgr
Definition: Config.h:61
#define TC_LOG_ERROR(filterType__,...)
Definition: Log.h:165
#define LOAD_CHECK(fn)
static boost::asio::ssl::context & instance()
Definition: SslContext.cpp:133
static bool Initialize()
Definition: SslContext.cpp:61
auto make_unique_ptr_with_deleter(T ptr, Del &&deleter)
Definition: Memory.h:41