TrinityCore
Loading...
Searching...
No Matches
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
28
29namespace
30{
31auto CreatePasswordUiMethodFromPemCallback(::pem_password_cb* callback)
32{
33 return Trinity::make_unique_ptr_with_deleter<&::UI_destroy_method>(UI_UTIL_wrap_read_pem_callback(callback, 0));
34}
35
36auto OpenOpenSSLStore(boost::filesystem::path const& storePath, UI_METHOD const* passwordCallback, void* passwordCallbackData)
37{
38 std::string uri;
39 uri.reserve(6 + storePath.size());
40
41 uri += "file:";
42 std::string genericPath = storePath.generic_string();
43 if (!genericPath.empty() && !genericPath.starts_with('/'))
44 uri += '/'; // ensure the path starts with / (windows special case, unix absolute paths already do)
45
46 uri += genericPath;
47
48 return Trinity::make_unique_ptr_with_deleter<&::OSSL_STORE_close>(OSSL_STORE_open(uri.c_str(), passwordCallback, passwordCallbackData, nullptr, nullptr));
49}
50
51boost::system::error_code GetLastOpenSSLError()
52{
53 auto ossl_error = ::ERR_get_error();
54 if (ERR_SYSTEM_ERROR(ossl_error))
55 return boost::system::error_code(static_cast<int>(::ERR_GET_REASON(ossl_error)), boost::asio::error::get_system_category());
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
119 {
120 X509_NAME const* nm = X509_get_subject_name(cert);
121 int32 lastpos = -1;
122 while (true)
123 {
124 lastpos = X509_NAME_get_index_by_NID(nm, NID_commonName, lastpos);
125 if (lastpos == -1)
126 break;
127
128 X509_NAME_ENTRY const* e = X509_NAME_get_entry(nm, lastpos);
129 if (!e)
130 continue;
131
132 ASN1_STRING const* text = X509_NAME_ENTRY_get_data(e);
133 if (!text)
134 continue;
135
136 unsigned char* utf8TextRaw = nullptr;
137 if (int utf8Length = ASN1_STRING_to_UTF8(&utf8TextRaw, text); utf8Length >= 0)
138 {
139 auto utf8Text = Trinity::make_unique_ptr_with_deleter<[](unsigned char* ptr) { ::OPENSSL_free(ptr); } >(utf8TextRaw);
140 if (std::string_view(reinterpret_cast<char const*>(utf8Text.get()), utf8Length) == "*.*")
141 return true;
142 }
143 }
144
145 return false;
146 }();
147
148 SSL_CTX_use_cert_and_key(nativeContext, cert, key, certs, 1);
149 }
150
151 sk_X509_free(certs);
152
153 if (!key)
154 {
155 std::string privateKeyFile = sConfigMgr->GetStringDefault("PrivateKeyFile", "./bnetserver.key.pem");
156 LOAD_CHECK(instance().use_private_key_file(privateKeyFile, boost::asio::ssl::context::pem, err));
157 }
158
159#undef LOAD_CHECK
160
161 return true;
162}
163
164boost::asio::ssl::context& Battlenet::SslContext::instance()
165{
166 static boost::asio::ssl::context context(boost::asio::ssl::context::tls);
167 return context;
168}
#define sConfigMgr
Definition Config.h:64
int32_t int32
Definition Define.h:150
#define TC_LOG_ERROR(filterType__, message__,...)
Definition Log.h:190
#define LOAD_CHECK(fn)
static boost::asio::ssl::context & instance()
static bool Initialize()
static bool _usesDevWildcardCertificate
Definition SslContext.h:35
std::unique_ptr< T, Impl::stateful_unique_ptr_deleter< Ptr, Del > > make_unique_ptr_with_deleter(Ptr ptr, Del deleter)
Definition Memory.h:133