SNode.C
Loading...
Searching...
No Matches
ssl_utils.cpp
Go to the documentation of this file.
1/*
2 * SNode.C - A Slim Toolkit for Network Communication
3 * Copyright (C) Volker Christian <me@vchrist.at>
4 * 2020, 2021, 2022, 2023, 2024, 2025
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published
8 * by the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20/*
21 * MIT License
22 *
23 * Permission is hereby granted, free of charge, to any person obtaining a copy
24 * of this software and associated documentation files (the "Software"), to deal
25 * in the Software without restriction, including without limitation the rights
26 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
27 * copies of the Software, and to permit persons to whom the Software is
28 * furnished to do so, subject to the following conditions:
29 *
30 * The above copyright notice and this permission notice shall be included in
31 * all copies or substantial portions of the Software.
32 *
33 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
39 * THE SOFTWARE.
40 */
41
42#ifndef DOXYGEN_SHOULD_SKIP_THIS
43
44#include "core/socket/stream/tls/ssl_utils.h"
45
46#include "log/Logger.h"
47#include "utils/PreserveErrno.h"
48
49#include <cerrno>
50#include <cstdint>
51#include <cstdlib>
52#include <cstring>
53#include <openssl/asn1.h>
54#include <openssl/err.h>
55#include <openssl/obj_mac.h>
56#include <openssl/ssl.h>
57#include <openssl/x509.h>
58#include <openssl/x509v3.h>
59#include <string>
60
61#endif /* DOXYGEN_SHOULD_SKIP_THIS */
62
63namespace core::socket::stream::tls {
64
65 static int password_callback(char* buf, int size, [[maybe_unused]] int a, void* u) {
66 strncpy(buf, static_cast<char*>(u), static_cast<std::size_t>(size));
67 buf[size - 1] = '\0';
68
69 memset(u, 0, ::strlen(static_cast<char*>(u))); // garble Password
70 free(u);
71
72 return static_cast<int>(std::strlen(buf));
73 }
74
75 static int verify_callback(int preverify_ok, X509_STORE_CTX* ctx) {
76 SSL* ssl = static_cast<SSL*>(X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()));
77
78 const std::string connectionName = *static_cast<std::string*>(SSL_get_ex_data(ssl, 0));
79
80 X509* curr_cert = X509_STORE_CTX_get_current_cert(ctx);
81 const int depth = X509_STORE_CTX_get_error_depth(ctx);
82
83 char subjectName[256];
84 X509_NAME_oneline(X509_get_subject_name(curr_cert), subjectName, 256);
85
86 char issuerName[256];
87 X509_NAME_oneline(X509_get_issuer_name(curr_cert), issuerName, 256);
88
89 if (preverify_ok != 0) {
90 LOG(DEBUG) << connectionName << ": SSL/TLS verify success at depth=" << depth;
91 LOG(DEBUG) << " Issuer: " << issuerName;
92 LOG(DEBUG) << " Subject: " << subjectName;
93 } else {
94 const int err = X509_STORE_CTX_get_error(ctx);
95
96 LOG(DEBUG) << connectionName << ": SSL/TLS verify error at depth=" << depth << ": " << X509_verify_cert_error_string(err);
97 LOG(DEBUG) << " Issuer: " << issuerName;
98 LOG(DEBUG) << " Subject: " << subjectName;
99
100 /*
101 * At this point, err contains the last verification error. We can use
102 * it for something special and set preverify_ok to 1 in case an unsafe connection
103 * shall be used.
104 */
105 }
106
107 return preverify_ok;
108 }
109
110 SslConfig::SslConfig(bool server)
111 : server(server) {
112 }
113
114 SSL_CTX* ssl_ctx_new(const SslConfig& sslConfig) {
115 static int sslSessionCtxId = 1;
116
117 SSL_CTX* ctx = SSL_CTX_new(sslConfig.server ? TLS_server_method() : TLS_client_method());
118
119 if (ctx != nullptr) {
120 SSL_CTX_set_read_ahead(ctx, 1);
121
122 SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE);
123 SSL_CTX_set_mode(ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
124
125 bool sslErr = false;
126
127 if (sslConfig.server) {
128 SSL_CTX_set_session_id_context(ctx, reinterpret_cast<const unsigned char*>(&sslSessionCtxId), sizeof(sslSessionCtxId));
129 sslSessionCtxId++;
130 }
131 if (!sslConfig.caCert.empty() || !sslConfig.caCertDir.empty()) {
132 if (SSL_CTX_load_verify_locations(ctx,
133 !sslConfig.caCert.empty() ? sslConfig.caCert.c_str() : nullptr,
134 !sslConfig.caCertDir.empty() ? sslConfig.caCertDir.c_str() : nullptr) == 0) {
135 ssl_log_error(sslConfig.instanceName + " SSL/TLS: CA certificate error loading file '" + sslConfig.caCert + "', dir '" +
136 sslConfig.caCertDir + "'");
137 sslErr = true;
138 } else {
139 if (!sslConfig.caCert.empty()) {
140 LOG(TRACE) << sslConfig.instanceName << " SSL/TLS: CA certificate loaded";
141 LOG(TRACE) << " " << sslConfig.caCert;
142 } else {
143 LOG(TRACE) << sslConfig.instanceName << " SSL/TLS: CA certificate not loaded from a file";
144 }
145 if (!sslConfig.caCertDir.empty()) {
146 LOG(TRACE) << sslConfig.instanceName << " SSL/TLS: CA certificates load from";
147 LOG(TRACE) << " " << sslConfig.caCertDir;
148 } else {
149 LOG(TRACE) << sslConfig.instanceName << " SSL/TLS: CA certificates not loaded from a directory";
150 }
151 }
152 } else {
153 LOG(TRACE) << sslConfig.instanceName << " SSL/TLS: CA certificate not loaded from a file";
154 LOG(TRACE) << sslConfig.instanceName << " SSL/TLS: CA certificates not loaded from a directory";
155 }
156 if (!sslErr && sslConfig.caCertUseDefaultDir) {
157 if (SSL_CTX_set_default_verify_paths(ctx) == 0) {
158 ssl_log_error(sslConfig.instanceName + " SSL/TLS: CA certificates error load from default openssl CA directory");
159 sslErr = true;
160 } else {
161 LOG(TRACE) << sslConfig.instanceName << " SSL/TLS: CA certificates enabled load from default openssl CA directory";
162 }
163 } else {
164 LOG(TRACE) << sslConfig.instanceName << " SSL/TLS: CA certificates not loaded from default openssl CA directory";
165 }
166 if (!sslErr) {
167 SSL_CTX_set_verify_depth(ctx, 5);
168 SSL_CTX_set_verify(ctx,
169 (sslConfig.caCertAcceptUnknown ? SSL_VERIFY_NONE
170 : (!sslConfig.caCert.empty() || !sslConfig.caCertDir.empty() || sslConfig.caCertUseDefaultDir)
171 ? SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE | SSL_VERIFY_FAIL_IF_NO_PEER_CERT
172 : 0),
174 if ((SSL_CTX_get_verify_mode(ctx) & SSL_VERIFY_PEER) != 0) {
175 LOG(TRACE) << sslConfig.instanceName << " SSL/TLS: CA requested verify";
176 }
177 if (!sslConfig.cert.empty()) {
178 if (SSL_CTX_use_certificate_chain_file(ctx, sslConfig.cert.c_str()) == 0) {
179 ssl_log_error(sslConfig.instanceName + " SSL/TLS: Cert chain error loading from file '" + sslConfig.cert + "'");
180 sslErr = true;
181 } else if (!sslConfig.certKey.empty()) {
182 if (!sslConfig.password.empty()) {
183 SSL_CTX_set_default_passwd_cb(ctx, password_callback);
184 SSL_CTX_set_default_passwd_cb_userdata(ctx, ::strdup(sslConfig.password.c_str()));
185 }
186 if (SSL_CTX_use_PrivateKey_file(ctx, sslConfig.certKey.c_str(), SSL_FILETYPE_PEM) == 0) {
187 ssl_log_error(sslConfig.instanceName + " SSL/TLS: Cert chain key error loading file '" + sslConfig.certKey +
188 "'");
189 sslErr = true;
190 } else if (SSL_CTX_check_private_key(ctx) != 1) {
191 ssl_log_error(sslConfig.instanceName + " SSL/TLS: Cert chain key error");
192
193 LOG(TRACE) << " " << sslConfig.certKey;
194 sslErr = true;
195 } else {
196 LOG(TRACE) << sslConfig.instanceName << " SSL/TLS: Cert chain key loaded";
197 LOG(TRACE) << " " << sslConfig.certKey;
198
199 LOG(TRACE) << sslConfig.instanceName << " SSL/TLS: Cert chain loaded";
200 LOG(TRACE) << " " << sslConfig.cert;
201 }
202 }
203 }
204 }
205 if (!sslErr) {
206 if (sslConfig.sslOptions != 0) {
207 SSL_CTX_set_options(ctx, sslConfig.sslOptions);
208 }
209 if (!sslConfig.cipherList.empty()) {
210 SSL_CTX_set_cipher_list(ctx, sslConfig.cipherList.c_str());
211 }
212 } else {
213 SSL_CTX_free(ctx);
214 ctx = nullptr;
215 }
216 }
217
218 return ctx;
219 }
220
221 std::map<std::string, SSL_CTX*> ssl_get_sans(SSL_CTX* sslCtx) {
222 std::map<std::string, SSL_CTX*> sans;
223
224 if (sslCtx != nullptr) {
225 X509* x509 = SSL_CTX_get0_certificate(sslCtx);
226 if (x509 != nullptr) {
227 GENERAL_NAMES* subjectAltNames =
228 static_cast<GENERAL_NAMES*>(X509_get_ext_d2i(x509, NID_subject_alt_name, nullptr, nullptr));
229#ifdef __GNUC__
230#pragma GCC diagnostic push
231#ifdef __has_warning
232#if __has_warning("-Wused-but-marked-unused")
233#pragma GCC diagnostic ignored "-Wused-but-marked-unused"
234#endif
235#endif
236#endif
237 const int32_t altNameCount = sk_GENERAL_NAME_num(subjectAltNames);
238#ifdef __GNUC__
239#pragma GCC diagnostic pop
240#endif
241 for (int32_t i = 0; i < altNameCount; ++i) {
242#ifdef __GNUC__
243#pragma GCC diagnostic push
244#ifdef __has_warning
245#if __has_warning("-Wused-but-marked-unused")
246#pragma GCC diagnostic ignored "-Wused-but-marked-unused"
247#endif
248#endif
249#endif
250 GENERAL_NAME* generalName = sk_GENERAL_NAME_value(subjectAltNames, i);
251#ifdef __GNUC__
252#pragma GCC diagnostic pop
253#endif
254 if (generalName->type == GEN_DNS || generalName->type == GEN_URI || generalName->type == GEN_EMAIL) {
255 const std::string subjectAltName =
256 std::string(reinterpret_cast<const char*>(ASN1_STRING_get0_data(generalName->d.dNSName)),
257 static_cast<std::size_t>(ASN1_STRING_length(generalName->d.dNSName)));
258 sans.insert({subjectAltName, sslCtx});
259 }
260 }
261#ifdef __GNUC__
262#pragma GCC diagnostic push
263#ifdef __has_warning
264#if __has_warning("-Wused-but-marked-unused")
265#pragma GCC diagnostic ignored "-Wused-but-marked-unused"
266#endif
267#endif
268#endif
269 sk_GENERAL_NAME_pop_free(subjectAltNames, GENERAL_NAME_free);
270#ifdef __GNUC__
271#pragma GCC diagnostic pop
272#endif
273 }
274 }
275
276 return sans;
277 }
278
279 void ssl_set_sni(SSL* ssl, const std::string& sni) {
280 if (!sni.empty()) {
281 SSL_set_tlsext_host_name(ssl, sni.data());
282 }
283 }
284
285 SSL_CTX* ssl_set_ssl_ctx(SSL* ssl, SSL_CTX* sslCtx) {
286 SSL_CTX* newSslCtx = SSL_set_SSL_CTX(ssl, sslCtx);
287 SSL_clear_options(ssl, 0xFFFFFFFFL);
288 SSL_set_options(ssl, SSL_CTX_get_options(sslCtx));
289 SSL_set_verify(ssl, SSL_CTX_get_verify_mode(sslCtx), SSL_CTX_get_verify_callback(sslCtx));
290 SSL_set_verify_depth(ssl, SSL_CTX_get_verify_depth(sslCtx));
291 SSL_set_mode(ssl, SSL_CTX_get_mode(sslCtx));
292
293 return newSslCtx;
294 }
295
296 void ssl_ctx_free(SSL_CTX* ctx) {
297 if (ctx != nullptr) {
298 SSL_CTX_free(ctx);
299 }
300 }
301
302 // From: https://www.bit-hive.com/documents/openssl-tutorial/
304 const unsigned char* ext = nullptr;
305 size_t ext_len = 0;
306 size_t p = 0;
307 size_t server_name_list_len = 0;
308 size_t server_name_len = 0;
309
310 if (SSL_client_hello_get0_ext(ssl, TLSEXT_TYPE_server_name, &ext, &ext_len) == 0) {
311 return "";
312 }
313
314 /* length (2 bytes) + type (1) + length (2) + server name (1+) */
315 if (ext_len < 6) {
316 return "";
317 }
318
319 /* Fetch Server Name list length */
320 server_name_list_len = static_cast<size_t>((ext[p] << 8) + ext[p + 1]);
321 p += 2;
322 if (p + server_name_list_len != ext_len) {
323 return "";
324 }
325
326 /* Fetch Server Name Type */
327 if (ext[p] != TLSEXT_NAMETYPE_host_name) {
328 return "";
329 }
330 p++;
331
332 /* Fetch Server Name Length */
333 server_name_len = static_cast<size_t>((ext[p] << 8) + ext[p + 1]);
334 p += 2;
335 if (p + server_name_len != ext_len) {
336 return "";
337 }
338
339 /* ext_len >= 6 && p == 5 */
340
341 /* Finally fetch Server Name */
342
343 return std::string(reinterpret_cast<const char*>(ext + p), ext_len - p);
344 }
345
346 void ssl_log(const std::string& message, int sslErr) {
347 const utils::PreserveErrno preserveErrno;
348
349 switch (sslErr) {
350 case SSL_ERROR_NONE:
351 [[fallthrough]];
352 case SSL_ERROR_ZERO_RETURN:
353 ssl_log_info(message);
354 break;
355 case SSL_ERROR_SYSCALL:
356 if (errno != 0) {
357 ssl_log_error(message);
358 } else {
359 ssl_log_info(message);
360 }
361 break;
362 case SSL_ERROR_SSL:
363 ssl_log_error(message);
364 break;
365 default:
366 ssl_log_warning(message);
367 break;
368 }
369 }
370
371 void ssl_log_error(const std::string& message) {
372 LOG(ERROR) << message;
373 LOG(ERROR) << " " << ERR_error_string(ERR_get_error(), nullptr);
374
375 unsigned long errorCode = 0;
376 while ((errorCode = ERR_get_error()) != 0) {
377 LOG(ERROR) << " " << ERR_error_string(errorCode, nullptr);
378 }
379 }
380
381 void ssl_log_warning(const std::string& message) {
382 LOG(WARNING) << message;
383 LOG(WARNING) << " " << ERR_error_string(ERR_get_error(), nullptr);
384
385 unsigned long errorCode = 0;
386 while ((errorCode = ERR_get_error()) != 0) {
387 LOG(WARNING) << " " << ERR_error_string(errorCode, nullptr);
388 }
389 }
390
391 void ssl_log_info(const std::string& message) {
392 LOG(INFO) << message;
393 LOG(INFO) << " " << ERR_error_string(ERR_get_error(), nullptr);
394
395 unsigned long errorCode = 0;
396 while ((errorCode = ERR_get_error()) != 0) {
397 LOG(INFO) << " " << ERR_error_string(errorCode, nullptr);
398 }
399 }
400
401 bool match(const char* first, const char* second) {
402 // If we reach at the end of both strings, we are done
403 if (*first == '\0' && *second == '\0') {
404 return true;
405 }
406
407 // Make sure to eliminate consecutive '*'
408 if (*first == '*') {
409 while (*(first + 1) == '*') {
410 first++;
411 }
412 }
413
414 // Make sure that the characters after '*' are present
415 // in second string. This function assumes that the
416 // first string will not contain two consecutive '*'
417 if (*first == '*' && *(first + 1) != '\0' && *second == '\0') {
418 return false;
419 }
420
421 // If the first string contains '?', or current
422 // characters of both strings match
423 if (*first == '?' || *first == *second) {
424 return match(first + 1, second + 1);
425 }
426
427 // If there is *, then there are two possibilities
428 // a) We consider current character of second string
429 // b) We ignore current character of second string.
430 if (*first == '*') {
431 return match(first + 1, second) || match(first, second + 1);
432 }
433
434 return false;
435 }
436
437} // namespace core::socket::stream::tls
void ssl_set_sni(SSL *ssl, const std::string &sni)
bool match(const char *first, const char *second)
static int verify_callback(int preverify_ok, X509_STORE_CTX *ctx)
Definition ssl_utils.cpp:75
void ssl_log_warning(const std::string &message)
static int password_callback(char *buf, int size, int a, void *u)
Definition ssl_utils.cpp:65
SSL_CTX * ssl_set_ssl_ctx(SSL *ssl, SSL_CTX *sslCtx)
void ssl_log_info(const std::string &message)
std::string ssl_get_servername_from_client_hello(SSL *ssl)
void ssl_log(const std::string &message, int sslErr)
void ssl_ctx_free(SSL_CTX *ctx)
std::map< std::string, SSL_CTX * > ssl_get_sans(SSL_CTX *sslCtx)
SSL_CTX * ssl_ctx_new(const SslConfig &sslConfig)
void ssl_log_error(const std::string &message)