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#ifndef DOXYGEN_SHOULD_SKIP_THIS
21
22#include "core/socket/stream/tls/ssl_utils.h"
23
24#include "log/Logger.h"
25#include "utils/PreserveErrno.h"
26
27#include <cerrno>
28#include <cstdint>
29#include <cstdlib>
30#include <cstring>
31#include <openssl/asn1.h>
32#include <openssl/err.h>
33#include <openssl/obj_mac.h>
34#include <openssl/ssl.h>
35#include <openssl/x509.h>
36#include <openssl/x509v3.h>
37#include <string>
38
39#endif /* DOXYGEN_SHOULD_SKIP_THIS */
40
41namespace core::socket::stream::tls {
42
43 static int password_callback(char* buf, int size, [[maybe_unused]] int a, void* u) {
44 strncpy(buf, static_cast<char*>(u), static_cast<std::size_t>(size));
45 buf[size - 1] = '\0';
46
47 memset(u, 0, ::strlen(static_cast<char*>(u))); // garble Password
48 free(u);
49
50 return static_cast<int>(std::strlen(buf));
51 }
52
53 static int verify_callback(int preverify_ok, X509_STORE_CTX* ctx) {
54 SSL* ssl = static_cast<SSL*>(X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()));
55
56 std::string connectionName = *static_cast<std::string*>(SSL_get_ex_data(ssl, 0));
57
58 X509* curr_cert = X509_STORE_CTX_get_current_cert(ctx);
59 const int depth = X509_STORE_CTX_get_error_depth(ctx);
60
61 char subjectName[256];
62 X509_NAME_oneline(X509_get_subject_name(curr_cert), subjectName, 256);
63
64 char issuerName[256];
65 X509_NAME_oneline(X509_get_issuer_name(curr_cert), issuerName, 256);
66
67 if (preverify_ok != 0) {
68 LOG(DEBUG) << connectionName << ": SSL/TLS verify success at depth=" << depth;
69 LOG(DEBUG) << " Issuer: " << issuerName;
70 LOG(DEBUG) << " Subject: " << subjectName;
71 } else {
72 const int err = X509_STORE_CTX_get_error(ctx);
73
74 /*
75 * At this point, err contains the last verification error. We can use
76 * it for something special
77 */
78
79 LOG(DEBUG) << connectionName << ": SSL/TLS verify error at depth=" << depth << ": " << X509_verify_cert_error_string(err);
80 LOG(DEBUG) << " Issuer: " << issuerName;
81 LOG(DEBUG) << " Subject: " << subjectName;
82 }
83
84 return preverify_ok;
85 }
86
88 : server(server) {
89 }
90
91 SSL_CTX* ssl_ctx_new(const SslConfig& sslConfig) {
92 static int sslSessionCtxId = 1;
93
94 SSL_CTX* ctx = SSL_CTX_new(sslConfig.server ? TLS_server_method() : TLS_client_method());
95
96 if (ctx != nullptr) {
97 SSL_CTX_set_read_ahead(ctx, 1);
98
99 SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE);
100 SSL_CTX_set_mode(ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
101
102 bool sslErr = false;
103
104 if (sslConfig.server) {
105 SSL_CTX_set_session_id_context(ctx, reinterpret_cast<const unsigned char*>(&sslSessionCtxId), sizeof(sslSessionCtxId));
106 sslSessionCtxId++;
107 }
108 if (!sslConfig.caCert.empty() || !sslConfig.caCertDir.empty()) {
109 if (SSL_CTX_load_verify_locations(ctx,
110 !sslConfig.caCert.empty() ? sslConfig.caCert.c_str() : nullptr,
111 !sslConfig.caCertDir.empty() ? sslConfig.caCertDir.c_str() : nullptr) == 0) {
112 ssl_log_error(sslConfig.instanceName + " SSL/TLS: CA certificate error loading file '" + sslConfig.caCert + "', dir '" +
113 sslConfig.caCertDir + "'");
114 sslErr = true;
115 } else {
116 if (!sslConfig.caCert.empty()) {
117 LOG(TRACE) << sslConfig.instanceName << " SSL/TLS: CA certificate loaded";
118 LOG(TRACE) << " " << sslConfig.caCert;
119 } else {
120 LOG(TRACE) << sslConfig.instanceName << " SSL/TLS: CA certificate not loaded from a file";
121 }
122 if (!sslConfig.caCertDir.empty()) {
123 LOG(TRACE) << sslConfig.instanceName << " SSL/TLS: CA certificates load from";
124 LOG(TRACE) << " " << sslConfig.caCertDir;
125 } else {
126 LOG(TRACE) << sslConfig.instanceName << " SSL/TLS: CA certificates not loaded from a directory";
127 }
128 }
129 } else {
130 LOG(TRACE) << sslConfig.instanceName << " SSL/TLS: CA certificate not loaded from a file";
131 LOG(TRACE) << sslConfig.instanceName << " SSL/TLS: CA certificates not loaded from a directory";
132 }
133 if (!sslErr && sslConfig.caCertUseDefaultDir) {
134 if (SSL_CTX_set_default_verify_paths(ctx) == 0) {
135 ssl_log_error(sslConfig.instanceName + " SSL/TLS: CA certificates error load from default openssl CA directory");
136 sslErr = true;
137 } else {
138 LOG(TRACE) << sslConfig.instanceName << " SSL/TLS: CA certificates enabled load from default openssl CA directory";
139 }
140 } else {
141 LOG(TRACE) << sslConfig.instanceName << " SSL/TLS: CA certificates not loaded from default openssl CA directory";
142 }
143 if (!sslErr) {
144 SSL_CTX_set_verify_depth(ctx, 5);
145 SSL_CTX_set_verify(ctx,
146 (sslConfig.caCertAcceptUnknown ? SSL_VERIFY_NONE
147 : (!sslConfig.caCert.empty() || !sslConfig.caCertDir.empty() || sslConfig.caCertUseDefaultDir)
148 ? SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE | SSL_VERIFY_FAIL_IF_NO_PEER_CERT
149 : 0),
150 verify_callback);
151 if ((SSL_CTX_get_verify_mode(ctx) & SSL_VERIFY_PEER) != 0) {
152 LOG(TRACE) << sslConfig.instanceName << " SSL/TLS: CA requested verify";
153 }
154 if (!sslConfig.cert.empty()) {
155 if (SSL_CTX_use_certificate_chain_file(ctx, sslConfig.cert.c_str()) == 0) {
156 ssl_log_error(sslConfig.instanceName + " SSL/TLS: Cert chain error loading from file '" + sslConfig.cert + "'");
157 sslErr = true;
158 } else if (!sslConfig.certKey.empty()) {
159 if (!sslConfig.password.empty()) {
160 SSL_CTX_set_default_passwd_cb(ctx, password_callback);
161 SSL_CTX_set_default_passwd_cb_userdata(ctx, ::strdup(sslConfig.password.c_str()));
162 }
163 if (SSL_CTX_use_PrivateKey_file(ctx, sslConfig.certKey.c_str(), SSL_FILETYPE_PEM) == 0) {
164 ssl_log_error(sslConfig.instanceName + " SSL/TLS: Cert chain key error loading file '" + sslConfig.certKey +
165 "'");
166 sslErr = true;
167 } else if (SSL_CTX_check_private_key(ctx) != 1) {
168 ssl_log_error(sslConfig.instanceName + " SSL/TLS: Cert chain key error");
169
170 LOG(TRACE) << " " << sslConfig.certKey;
171 sslErr = true;
172 } else {
173 LOG(TRACE) << sslConfig.instanceName << " SSL/TLS: Cert chain key loaded";
174 LOG(TRACE) << " " << sslConfig.certKey;
175
176 LOG(TRACE) << sslConfig.instanceName << " SSL/TLS: Cert chain loaded";
177 LOG(TRACE) << " " << sslConfig.cert;
178 }
179 }
180 }
181 }
182 if (!sslErr) {
183 if (sslConfig.sslOptions != 0) {
184 SSL_CTX_set_options(ctx, sslConfig.sslOptions);
185 }
186 if (!sslConfig.cipherList.empty()) {
187 SSL_CTX_set_cipher_list(ctx, sslConfig.cipherList.c_str());
188 }
189 } else {
190 SSL_CTX_free(ctx);
191 ctx = nullptr;
192 }
193 }
194
195 return ctx;
196 }
197
199 std::map<std::string, SSL_CTX*> sans;
200
201 if (sslCtx != nullptr) {
202 X509* x509 = SSL_CTX_get0_certificate(sslCtx);
203 if (x509 != nullptr) {
204 GENERAL_NAMES* subjectAltNames =
205 static_cast<GENERAL_NAMES*>(X509_get_ext_d2i(x509, NID_subject_alt_name, nullptr, nullptr));
206#ifdef __GNUC__
207#pragma GCC diagnostic push
208#ifdef __has_warning
209#if __has_warning("-Wused-but-marked-unused")
210#pragma GCC diagnostic ignored "-Wused-but-marked-unused"
211#endif
212#endif
213#endif
214 const int32_t altNameCount = sk_GENERAL_NAME_num(subjectAltNames);
215#ifdef __GNUC__
216#pragma GCC diagnostic pop
217#endif
218 for (int32_t i = 0; i < altNameCount; ++i) {
219#ifdef __GNUC__
220#pragma GCC diagnostic push
221#ifdef __has_warning
222#if __has_warning("-Wused-but-marked-unused")
223#pragma GCC diagnostic ignored "-Wused-but-marked-unused"
224#endif
225#endif
226#endif
227 GENERAL_NAME* generalName = sk_GENERAL_NAME_value(subjectAltNames, i);
228#ifdef __GNUC__
229#pragma GCC diagnostic pop
230#endif
231 if (generalName->type == GEN_DNS || generalName->type == GEN_URI || generalName->type == GEN_EMAIL) {
232 const std::string subjectAltName =
233 std::string(reinterpret_cast<const char*>(ASN1_STRING_get0_data(generalName->d.dNSName)),
234 static_cast<std::size_t>(ASN1_STRING_length(generalName->d.dNSName)));
235 sans.insert({subjectAltName, sslCtx});
236 }
237 }
238#ifdef __GNUC__
239#pragma GCC diagnostic push
240#ifdef __has_warning
241#if __has_warning("-Wused-but-marked-unused")
242#pragma GCC diagnostic ignored "-Wused-but-marked-unused"
243#endif
244#endif
245#endif
246 sk_GENERAL_NAME_pop_free(subjectAltNames, GENERAL_NAME_free);
247#ifdef __GNUC__
248#pragma GCC diagnostic pop
249#endif
250 }
251 }
252
253 return sans;
254 }
255
256 void ssl_set_sni(SSL* ssl, const std::string& sni) {
257 if (!sni.empty()) {
258 SSL_set_tlsext_host_name(ssl, sni.data());
259 }
260 }
261
262 SSL_CTX* ssl_set_ssl_ctx(SSL* ssl, SSL_CTX* sslCtx) {
263 SSL_CTX* newSslCtx = SSL_set_SSL_CTX(ssl, sslCtx);
264 SSL_clear_options(ssl, 0xFFFFFFFFL);
265 SSL_set_options(ssl, SSL_CTX_get_options(sslCtx));
266 SSL_set_verify(ssl, SSL_CTX_get_verify_mode(sslCtx), SSL_CTX_get_verify_callback(sslCtx));
267 SSL_set_verify_depth(ssl, SSL_CTX_get_verify_depth(sslCtx));
268 SSL_set_mode(ssl, SSL_CTX_get_mode(sslCtx));
269
270 return newSslCtx;
271 }
272
273 void ssl_ctx_free(SSL_CTX* ctx) {
274 if (ctx != nullptr) {
275 SSL_CTX_free(ctx);
276 }
277 }
278
279 // From: https://www.bit-hive.com/documents/openssl-tutorial/
281 const unsigned char* ext = nullptr;
282 size_t ext_len = 0;
283 size_t p = 0;
284 size_t server_name_list_len = 0;
285 size_t server_name_len = 0;
286
287 if (SSL_client_hello_get0_ext(ssl, TLSEXT_TYPE_server_name, &ext, &ext_len) == 0) {
288 return "";
289 }
290
291 /* length (2 bytes) + type (1) + length (2) + server name (1+) */
292 if (ext_len < 6) {
293 return "";
294 }
295
296 /* Fetch Server Name list length */
297 server_name_list_len = static_cast<size_t>((ext[p] << 8) + ext[p + 1]);
298 p += 2;
299 if (p + server_name_list_len != ext_len) {
300 return "";
301 }
302
303 /* Fetch Server Name Type */
304 if (ext[p] != TLSEXT_NAMETYPE_host_name) {
305 return "";
306 }
307 p++;
308
309 /* Fetch Server Name Length */
310 server_name_len = static_cast<size_t>((ext[p] << 8) + ext[p + 1]);
311 p += 2;
312 if (p + server_name_len != ext_len) {
313 return "";
314 }
315
316 /* ext_len >= 6 && p == 5 */
317
318 /* Finally fetch Server Name */
319
320 return std::string(reinterpret_cast<const char*>(ext + p), ext_len - p);
321 }
322
323 void ssl_log(const std::string& message, int sslErr) {
324 const utils::PreserveErrno preserveErrno;
325
326 switch (sslErr) {
327 case SSL_ERROR_NONE:
328 [[fallthrough]];
329 case SSL_ERROR_ZERO_RETURN:
330 ssl_log_info(message);
331 break;
332 case SSL_ERROR_SYSCALL:
333 if (errno != 0) {
334 ssl_log_error(message);
335 } else {
336 ssl_log_info(message);
337 }
338 break;
339 case SSL_ERROR_SSL:
340 ssl_log_error(message);
341 break;
342 default:
343 ssl_log_warning(message);
344 break;
345 }
346 }
347
348 void ssl_log_error(const std::string& message) {
349 LOG(ERROR) << message;
350 LOG(ERROR) << " " << ERR_error_string(ERR_get_error(), nullptr);
351
352 unsigned long errorCode = 0;
353 while ((errorCode = ERR_get_error()) != 0) {
354 LOG(ERROR) << " " << ERR_error_string(errorCode, nullptr);
355 }
356 }
357
358 void ssl_log_warning(const std::string& message) {
359 LOG(WARNING) << message;
360 LOG(WARNING) << " " << ERR_error_string(ERR_get_error(), nullptr);
361
362 unsigned long errorCode = 0;
363 while ((errorCode = ERR_get_error()) != 0) {
364 LOG(WARNING) << " " << ERR_error_string(errorCode, nullptr);
365 }
366 }
367
368 void ssl_log_info(const std::string& message) {
369 LOG(INFO) << message;
370 LOG(INFO) << " " << ERR_error_string(ERR_get_error(), nullptr);
371
372 unsigned long errorCode = 0;
373 while ((errorCode = ERR_get_error()) != 0) {
374 LOG(INFO) << " " << ERR_error_string(errorCode, nullptr);
375 }
376 }
377
378 bool match(const char* first, const char* second) {
379 // If we reach at the end of both strings, we are done
380 if (*first == '\0' && *second == '\0') {
381 return true;
382 }
383
384 // Make sure to eliminate consecutive '*'
385 if (*first == '*') {
386 while (*(first + 1) == '*') {
387 first++;
388 }
389 }
390
391 // Make sure that the characters after '*' are present
392 // in second string. This function assumes that the
393 // first string will not contain two consecutive '*'
394 if (*first == '*' && *(first + 1) != '\0' && *second == '\0') {
395 return false;
396 }
397
398 // If the first string contains '?', or current
399 // characters of both strings match
400 if (*first == '?' || *first == *second) {
401 return match(first + 1, second + 1);
402 }
403
404 // If there is *, then there are two possibilities
405 // a) We consider current character of second string
406 // b) We ignore current character of second string.
407 if (*first == '*') {
408 return match(first + 1, second) || match(first, second + 1);
409 }
410
411 return false;
412 }
413
414} // 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:53
void ssl_log_warning(const std::string &message)
static int password_callback(char *buf, int size, int a, void *u)
Definition ssl_utils.cpp:43
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)
Definition ssl_utils.cpp:91
void ssl_log_error(const std::string &message)
Definition Config.h:37