6#include <cstring> // std::strlen
7#include <filesystem> // std::filesystem::temp_directory_path
8#include <iostream> // std::put_date
9#include <unordered_map> // std::unordered_map
13#include <linux/fs.h> // Flags for ioctl()
15#include <openssl/err.h>
16#include <openssl/ossl_typ.h>
17#include <openssl/ssl.h>
18#include <openssl/x509v3.h>
21#include <sys/stat.h> // fchmod()
25 /*======================================================================*//**
27 The rsocket_sni class provides a specialized interface for collecting a group
28 of SNI (Server Name Identifier) contexts for TLS connections.
31 There's no maximum limit on the number of SNI entries that can be added
32 (aside from system memory limits, of course).
34 @author Randolf Richardson
37 2023-Mar-26 v1.00 Initial version
38 2024-Dec-28 v1.00 Minor improvements to internal documentation within the
39 @ref get_ctx method for easier future maintenance since SNI
40 wildcard matching incurs significant degrees of complexity.
41 *///=========================================================================
45 // --------------------------------------------------------------------------
46 // Internal variables.
47 // --------------------------------------------------------------------------
48 SSL_CTX* __tls_ctx = nullptr; // Current TLS context
49 std::unordered_map<std::string, SSL_CTX*> __tls_ctx_map; // SNI map between hostnames and TLS contexts
50 std::unordered_map<std::string, SSL_CTX*> __tls_ctx_map_w; // SNI map with wildcards
51 inline static std::atomic_int __fn_counter = 0; // Used in generating unique-and-threadsafe temporary filenames
54 /*======================================================================*//**
56 Instantiate a new rsocket_sni object.
58 *///=========================================================================
60 /// OpenSSL's TLS context to use (if set to @c nullptr, this @c rsocket's TLS
61 /// context will be paired with the specified hostname{s})
62 SSL_CTX* ctx = nullptr,
63 /// When @c ctx is @c nullptr, indicates whether to initialize the new TLS
64 /// context with the @c TLS_server_method() or the @c TLS_client_method()
65 /// function (the default is @c true, which is the .
66 const bool is_server_method = true) {
67 tls_ctx(ctx, is_server_method);
68 }; // -x- constructor rsocket_sni -x-
70 /*======================================================================*//**
72 Destructor for rsocket_sni object, which deletes all SNI hostnames and their
73 associated TLS contexts, updating OpenSSL's internal TLS context reference
76 *///=========================================================================
77 ~rsocket_sni() noexcept {
79 // --------------------------------------------------------------------------
80 // Remove all entries so that OpenSSL's internal counters are accounted for.
81 // --------------------------------------------------------------------------
82 for (auto& sni: __tls_ctx_map) del(sni.first);
83 for (auto& sni: __tls_ctx_map_w) del(sni.first);
85 }; // -x- constructor rsocket_sni -x-
87 /*======================================================================*//**
89 Add one new hostname, which will be associated with the current TLS context.
90 SNI wildcards are supported (e.g., `*.example.com`).
92 If the specified hostname already exists, it will be updated with the newer
93 TLS context because re-adding is accepted, by acclamation, as a replacement.
94 @returns The same rsocket_sni object so as to facilitate stacking
98 *///=========================================================================
101 std::string hostname) {
103 // --------------------------------------------------------------------------
104 // Internal variables.
105 // --------------------------------------------------------------------------
106 bool w = hostname.find("*") != std::string::npos;
108 // --------------------------------------------------------------------------
109 // Replace hostname if it already exists and the TLS context is different,
110 // then update OpenSSL's internal reference counters accordingly.
111 // --------------------------------------------------------------------------
113 SSL_CTX* ctx = (w ? __tls_ctx_map_w : __tls_ctx_map).at(hostname); // Throws std::out_of_range exception if key doesn't exist
114 if (ctx == __tls_ctx) return this; // The TLS context didn't change, so return now to save CPU cycles (optimization)
115 SSL_CTX_free(ctx); // Decrement SSL_CTX's internal reference count
116 (w ? __tls_ctx_map_w : __tls_ctx_map)[hostname] = __tls_ctx;
117 SSL_CTX_up_ref(__tls_ctx); // Increment SSL_CTX's internal reference count
118 } catch (const std::out_of_range e) {}
120 // --------------------------------------------------------------------------
121 // Add hostname with current TLS context.
122 // --------------------------------------------------------------------------
123 (w ? __tls_ctx_map_w : __tls_ctx_map).insert({hostname, __tls_ctx});
124 SSL_CTX_up_ref(__tls_ctx); // Increment SSL_CTX's internal reference count
127 }; // -x- rsocket_sni* add -x-
129 /*======================================================================*//**
131 Add one or more new hostnames, which will be associated with the current TLS
132 context. SNI wildcards are supported (e.g., `*.example.com`).
134 If any specified hostnames already exist, they will be updated with the newer
135 TLS context because re-adding is accepted, by acclamation, as a replacement.
136 @returns The same rsocket_sni object so as to facilitate stacking
140 *///=========================================================================
141 template<class... Hs> rsocket_sni* add(
142 /// First hostname (required)
143 std::string hostname,
144 /// Additional hostnames (optional)
147 // --------------------------------------------------------------------------
148 // Recursively loop through the variable arguments.
149 // --------------------------------------------------------------------------
154 }; // -x- rsocket_sni* add -x-
156 /*======================================================================*//**
160 If the specified hostname doesn't exist, nothing will happen.
161 @returns The same rsocket_sni object so as to facilitate stacking
163 *///=========================================================================
165 /// Hostname to delete
166 std::string hostname) {
168 // --------------------------------------------------------------------------
169 // Internal variables.
170 // --------------------------------------------------------------------------
171 bool w = hostname.find("*") != std::string::npos; // Used to determine which map to delete from
173 // --------------------------------------------------------------------------
174 // If hostname exists, delete it and update OpenSSL's internal reference
175 // counters accordingly.
176 // --------------------------------------------------------------------------
178 SSL_CTX* ctx = (w ? __tls_ctx_map_w : __tls_ctx_map).at(hostname); // Throws std::out_of_range exception if key doesn't exist
179 SSL_CTX_free(ctx); // Decrement SSL_CTX's internal reference count
180 (w ? __tls_ctx_map_w : __tls_ctx_map).erase(hostname);
181 } catch (const std::out_of_range e) {}
184 }; // -x- rsocket_sni* del -x-
186 /*======================================================================*//**
188 Delete one or more hostnames.
190 If any specified hostnames don't exist, then no deletion of them will occur.
191 @returns The same rsocket_sni object so as to facilitate stacking
193 *///=========================================================================
194 template<class... Hs> rsocket_sni* del(
195 /// First hostname (required)
196 std::string hostname,
197 /// Additional hostnames (optional)
200 // --------------------------------------------------------------------------
201 // Recursively loop through the variable arguments.
202 // --------------------------------------------------------------------------
207 }; // -x- rsocket_sni* del -x-
209 /*======================================================================*//**
211 Return the TLS context for a given hostname. If any SNI wildcards satisfy
212 hostname-matching criteria (e.g., `www.example.com` matches a `*.example.com`
213 wildcard), the respective TLS context for it will be returned (as expected).
215 For wildcard comparisons, OpenSSL's functions are used for matching to ensure
216 consistency with what's expected, and in order to support any unforseen
217 changes to future standards in wildcard naming rules. (In other words, we're
218 relying on OpenSSL to do what it already does efficiently and correctly.)
220 Exact matches are always checked before wildcards because the exact matching
221 algorithm is faster (uses fewer CPU cycles) than comparing wildcards.
222 @returns TLS context, or @c nullptr (or @c default_tls_ctx override) if
223 hostname doesn't match any SNI entries
225 *///=========================================================================
228 std::string hostname,
229 /// SNI supports wildcards, but if an exact match is needed then setting this
230 /// parameter to @c false will skip attempts to also match wildcards (the
231 /// default is @c true)
232 const bool enable_sni_wildcards = true,
233 /// Override the default context @c nullptr that is returned to indicate that
234 /// the hostname didn't match
235 SSL_CTX* default_tls_ctx = nullptr) {
237 // --------------------------------------------------------------------------
238 // Retrieve exact match. If SNI wildcard checking is disabled, and an exact
239 // match could not be retrieved, then just return the default TLS context.
240 // --------------------------------------------------------------------------
242 return __tls_ctx_map.at(hostname); // Throws std::out_of_range exception if key doesn't exist
243 } catch (const std::out_of_range e) {
244 if (!enable_sni_wildcards) return default_tls_ctx; // No wildcard checks, so return default
247 // --------------------------------------------------------------------------
248 // Find wildcard match -- this takes longer, which is why we don't do this
251 // We're relying on OpenSSL to do all the heavy-lifting when parsing wildcard
252 // labels, which is best because OpenSSL does this correctly and is also in
253 // the best position to accomplish this with the most efficiency.
254 // --------------------------------------------------------------------------
255 for (auto& sni: __tls_ctx_map_w) {
256 X509* cert = SSL_CTX_get0_certificate(sni.second); // Debug: X509_NAME* subject = X509_get_subject_name(cert); std::cout << "Subject: " << X509_NAME_oneline(subject, nullptr, 0) << std::endl;
257 char* peername = nullptr;
258 int rc = X509_check_host(cert, hostname.data(), hostname.length(), 0, &peername); // TO DO: Support flags: https://www.openssl.org/docs/man1.1.1/man3/X509_check_host.html (TODO)
259 // Debug: std::cout << "SNI -> key=" << sni.first << " peername=" << (peername != nullptr ? peername : "nullptr") << " hostname=" << hostname << std::endl;
260 if (peername != nullptr) OPENSSL_free(peername); // Memory management
261 if (rc == 1) return sni.second; // Wildcard match successful, so we're finished with all of this now
262 } // -x- foreach sni -x-
264 return default_tls_ctx;
265 }; // -x- SSL_CTX* get_ctx -x-
267 /*======================================================================*//**
269 Find out what the current TLS context is set to.
271 *///=========================================================================
272 SSL_CTX* tls_ctx() noexcept {
274 }; // -x- SSL_CTX* tls_ctx -x-
276 /*======================================================================*//**
278 Set the current TLS context to a specific context or create a new one.
279 @returns The same rsocket_sni object so as to facilitate stacking
280 *///=========================================================================
281 rsocket_sni* tls_ctx(
282 /// OpenSSL's TLS context to use (if set to @c nullptr, this @c rsocket's TLS
283 /// context will be paired with the specified hostname{s})
285 /// When @c ctx is @c nullptr, indicates whether to initialize the new TLS
286 /// context with the @c TLS_server_method(), which is the default. If set to
287 /// @c false, then the @c TLS_client_method() function will be called as part
288 /// of initializing a new TLS context.
289 const bool is_server_method = true) {
291 // --------------------------------------------------------------------------
292 // Save (ctx != nullptr) or create (ctx == nullptr) OpenSSL context.
293 // --------------------------------------------------------------------------
294 __tls_ctx = ctx == nullptr ? SSL_CTX_new(is_server_method ? TLS_server_method() : TLS_client_method()) // Create anew (default)
295 : ctx; // Use OpenSSL context that was provided
296 if (__tls_ctx == nullptr) randolf::rex::mk_exception("Cannot create TLS context", 0, rex::rex::REX_FLAGS::REX_FIND_ERRNO | rex::rex::REX_FLAGS::REX_TLS);
299 }; // -x- rsocket_sni* tls_ctx -x-
301 /*======================================================================*//**
303 Check the private key it to ensure it's consistent with the corresponding TLS
306 @throws randolf::rex::xALL Catch this exception for now (at this time, the
307 OpenSSL library doesn't document which errors may be returned)
309 @returns The same rsocket_sni object so as to facilitate stacking
310 @see tls_ctx_use_privatekey_file
311 @see tls_ctx_use_privatekey_pem
312 *///=========================================================================
313 rsocket_sni* tls_ctx_check_privatekey() {
314 if (!SSL_CTX_check_private_key(__tls_ctx)) randolf::rex::mk_exception("Cannot validate consistency between certificate chain and private key", 0, rex::rex::REX_FLAGS::REX_FIND_ERRNO | rex::rex::REX_FLAGS::REX_TLS);
316 }; // -x- rsocket_sni* tls_ctx_check_privatekey -x-
318 /*======================================================================*//**
320 Load a TLS certificate chain and private key in PEM format from text files
321 and use them in the TLS context.
323 @throws randolf::rex::xALL Catch this exception for now (at this time, the
324 OpenSSL library doesn't document which errors may be returned)
326 @returns The same rsocket_sni object so as to facilitate stacking
327 @see tls_ctx_use_certificate_chain_and_privatekey_pems
328 @see tls_ctx_use_certificate_chain_file
329 @see tls_ctx_use_certificate_chain_pem
330 @see tls_ctx_use_privatekey_file
331 @see tls_ctx_use_privatekey_pem
332 @see tls_ctx_check_privatekey
333 *///=========================================================================
334 rsocket_sni* tls_ctx_use_certificate_chain_and_privatekey_files(
335 /// Pointer to ASCIIZ path and filename to certificate chain file (@c nullptr
336 /// will simply be ignored)
337 const char* chain_file,
338 /// Pointer to ASCIIZ path and filename to private key file (@c nullptr will
339 /// simply be ignored)
340 const char* key_file) {
341 if (chain_file != nullptr) tls_ctx_use_certificate_chain_file(chain_file);
342 if ( key_file != nullptr) tls_ctx_use_privatekey_file( key_file);
344 }; // -x- rsocket_sni* tls_ctx_use_certificate_chain_and_privatekey_files -x-
346 /*======================================================================*//**
348 Load a TLS certificate chain and private key in PEM format from text files
349 and use them in the TLS context.
351 @throws randolf::rex::xALL Catch this exception for now (at this time, the
352 OpenSSL library doesn't document which errors may be returned)
354 @returns The same rsocket_sni object so as to facilitate stacking
355 @see tls_ctx_use_certificate_chain_and_privatekey_pems
356 @see tls_ctx_use_certificate_chain_file
357 @see tls_ctx_use_certificate_chain_pem
358 @see tls_ctx_use_privatekey_file
359 @see tls_ctx_use_privatekey_pem
360 @see tls_ctx_check_privatekey
361 *///=========================================================================
362 rsocket_sni* tls_ctx_use_certificate_chain_and_privatekey_files(
363 /// Pointer to ASCIIZ path and filename to certificate chain file (an empty
364 /// string will simply be ignored)
365 const std::string chain_file,
366 /// Pointer to ASCIIZ path and filename to private key file (an empty string
367 /// will simply be ignored)
368 const std::string key_file) {
369 if (!chain_file.empty()) tls_ctx_use_certificate_chain_file(chain_file);
370 if ( !key_file.empty()) tls_ctx_use_privatekey_file( key_file);
372 }; // -x- rsocket_sni* tls_ctx_use_certificate_chain_and_privatekey_files -x-
374 /*======================================================================*//**
376 Load a TLS certificate chain and a TLS private key in PEM format from memory
377 and use them in the TLS context.
379 Although this functionality doesn't exist in OpenSSL (at the time of writing
380 this method), it's provided here in a manner that has exactly the same effect
381 as the @ref tls_ctx_use_certificate_chain_and_privatekey_files() methods, but
382 without needing the PEM-formatted certificate chain stored in files
386 The @c cert_pem_data and key_pem_data parameters are pointers to the memory
387 locations that holds the PEM formatted certificate chain data and private key
388 data, respectively. If the corresponding lengths of each of these data aren't
389 specified or are set to zero (default), then they will be treated as multiline
392 Behind the scenes, we're just writing the cert_pem_data and key_pem_data
393 memory to temporary files with severely-limited permissions (), then
394 optionally overwriting those temporary files with random data prior to
395 deleting them (this is the default, since better security practices should be
396 the default, but on a secured system it may not be necessary and so this
397 option can also be disabled to save CPU cycles and reduce overall disk-write
400 @throws randolf::rex::xALL Catch this exception for now (at this time, the
401 OpenSSL library doesn't document which errors may be returned)
403 @returns The same rsocket_sni object so as to facilitate stacking
404 @see tls_ctx_use_certificate_chain_and_privatekey_files
405 @see tls_ctx_use_certificate_chain_file
406 @see tls_ctx_use_certificate_chain_pem
407 @see tls_ctx_use_privatekey_file
408 @see tls_ctx_use_privatekey_pem
409 @see tls_ctx_check_privatekey
410 *///=========================================================================
411 rsocket_sni* tls_ctx_use_certificate_chain_and_privatekey_pems(
412 /// Pointer to certificate chain data in PEM format
413 const char* cert_pem_data,
414 /// Pointer to private key data in PEM format
415 const char* key_pem_data,
416 /// Length of cert_pem_data (in bytes), or 0 to auto-detect length if cert_pem_data is an ASCIIZ string
418 /// Length of key_pem_data (in bytes), or 0 to auto-detect length if key_pem_data is an ASCIIZ string
420 /// Whether to overwrite the temporary files with random data before deleting them
421 const bool random_fill = true) {
422 tls_ctx_use_certificate_chain_pem(cert_pem_data, cert_len, random_fill);
423 tls_ctx_use_privatekey_pem( key_pem_data, key_len, random_fill);
425 }; // -x- rsocket_sni* tls_ctx_use_certificate_chain_and_privatekey_pems -x-
427 /*======================================================================*//**
429 Load a TLS certificate chain in PEM format from a text file and use it in the
432 @throws randolf::rex::xALL Catch this exception for now (at this time, the
433 OpenSSL library doesn't document which errors may be returned)
435 @returns The same rsocket_sni object so as to facilitate stacking
436 @see tls_ctx_use_certificate_chain_pem
437 @see tls_ctx_check_privatekey
438 *///=========================================================================
439 rsocket_sni* tls_ctx_use_certificate_chain_file(
440 /// Pointer to ASCIIZ path and filename to certificate chain file
442 if (SSL_CTX_use_certificate_chain_file(__tls_ctx, file) != 1) randolf::rex::mk_exception(std::string("Cannot use certificate chain file ") + file, 0, rex::rex::REX_FLAGS::REX_FIND_ERRNO | rex::rex::REX_FLAGS::REX_TLS);
444 }; // -x- rsocket_sni* tls_ctx_use_certificate_chain_file -x-
446 /*======================================================================*//**
448 Load a TLS certificate chain in PEM format from a text file and use it in the
451 @throws randolf::rex::xALL Catch this exception for now (at this time, the
452 OpenSSL library doesn't document which errors may be returned)
454 @returns The same rsocket_sni object so as to facilitate stacking
455 @see tls_ctx_use_certificate_chain_pem
456 @see tls_ctx_check_privatekey
457 *///=========================================================================
458 rsocket_sni* tls_ctx_use_certificate_chain_file(
459 /// Path and filename to certificate chain file
460 const std::string file) {
461 if (SSL_CTX_use_certificate_chain_file(__tls_ctx, file.c_str()) != 1) randolf::rex::mk_exception("Cannot use certificate chain file " + file, 0, rex::rex::REX_FLAGS::REX_FIND_ERRNO | rex::rex::REX_FLAGS::REX_TLS);
463 }; // -x- rsocket_sni* tls_ctx_use_certificate_chain_file -x-
465 /*======================================================================*//**
467 Load a TLS certificate chain in PEM format from memory and use it in the TLS
470 Although this functionality doesn't exist in OpenSSL (at the time of writing
471 this method), it's provided here in a manner that has exactly the same effect
472 as the @ref tls_ctx_use_certificate_chain_file() methods, but without needing
473 the PEM-formatted certificate chain stored in a file beforehand.
476 The @c pem_data parameter is a pointer to the memory location that holds
477 the PEM formatted certificate chain data. If the length of this data isn't
478 specified or is set to zero (default), then it will be treated as a multiline
481 Behind the scenes, we're just writing the pem_data memory to a temporary
482 file with severely-limited permissions (), then optionally overwriting that
483 temporary file with random data prior to deleting it (this is the default,
484 since better security practices should be the default, but on a secured
485 system it may not be necessary and so this option can also be disabled to
486 save CPU cycles and reduce overall disk-write I/O operations).
488 @throws randolf::rex::xALL Catch this exception for now (at this time, the
489 OpenSSL library doesn't document which errors may be returned)
491 @returns The same rsocket_sni object so as to facilitate stacking
492 @see tls_ctx_use_certificate_chain_file
493 @see tls_ctx_check_privatekey
494 *///=========================================================================
495 rsocket_sni* tls_ctx_use_certificate_chain_pem(
496 /// Pointer to certificate chain data in PEM format
497 const char* pem_data,
498 /// Length of pem_data (in bytes), or 0 to auto-detect length if pem_data is an ASCIIZ string
500 /// Whether to overwrite the temporary file with random data before deleting it
501 const bool random_fill = true) {
503 // --------------------------------------------------------------------------
504 // Measure size of certificate chain if an ASCIIZ string was indicated.
505 // --------------------------------------------------------------------------
506 if (len == 0) len = std::strlen(pem_data);
508 // --------------------------------------------------------------------------
509 // Generate filename for temporary use.
510 // --------------------------------------------------------------------------
511 std::string file = std::filesystem::temp_directory_path();
512 file.append("/rsocket_sni.")
513 .append(std::to_string(::getpid()))
515 .append(std::to_string(__fn_counter.fetch_add(1, std::memory_order_relaxed)));
517 // --------------------------------------------------------------------------
518 // Open temporary file.
519 // --------------------------------------------------------------------------
520 FILE* fp = fopen(file.c_str(), "w+");
521 if (fp == nullptr) randolf::rex::mk_exception("Cannot open temporary file (to use certificate chain) " + file, 0, rex::rex::REX_FLAGS::REX_FIND_ERRNO);
523 ioctl(fileno(fp), FS_IOC_GETFLAGS, &attr);
524 attr |= FS_NOATIME_FL // Don't update access time attribute
525 | FS_NODUMP_FL // Don't include in filesystem backup dumps
526 | FS_SECRM_FL; // Mark file for secure deletion (where supported)
527 ioctl(fileno(fp), FS_IOC_SETFLAGS, &attr);
529 fchmod(fileno(fp), S_IRUSR | S_IWUSR);
530 if (fputs(pem_data, fp) == EOF) {
532 randolf::rex::mk_exception("Cannot write to temporary file (to use certificate chain) " + file, 0, rex::rex::REX_FLAGS::REX_FIND_ERRNO);
533 } // -x- if !fputs -x-
536 // --------------------------------------------------------------------------
537 // Attempt to load certificate chain file, but save the error code for later
538 // because we need to clean up the temporary file before possibly throwing an
540 // --------------------------------------------------------------------------
541 int rc = SSL_CTX_use_certificate_chain_file(__tls_ctx, file.c_str());
543 // --------------------------------------------------------------------------
544 // Overwrite the contenst of the temporary file before deleting it so as to
545 // sabotage a simple attempt to undelete the file and access the certificate.
547 // We're also re-using the "len" local variable because it's not needed once
548 // we get the loop started, and it's more optimal to not allocate yet another
549 // local variable while "len" goes to waste. :D
550 // --------------------------------------------------------------------------
551 if (random_fill) { // This option is configurable
552 fseek(fp, 0, SEEK_SET); // File file position pointer to the beginning
553 ::srand(::time(0) + __fn_counter); // Current time + atomic __fn_counter value helps add variety
554 for (int i = len / sizeof(int) + (::rand() & 127); i >= 0; i--) { // Fill contents of file with random data
555 len = ::rand() + (len >> (sizeof(int) >> 2)); // Bias random data toward containing more set bits (more 1's)
556 fwrite(&len, sizeof(int), 1, fp); // Write one integer at a time
558 } // -x- if randfill -x-
559 fchmod(fileno(fp), 0); // Remove all permissions
560 fclose(fp); // Close file handle
561 unlink(file.c_str()); // Delete temporary file
563 // --------------------------------------------------------------------------
564 // Error check ... was delayed here until after temporary file cleanup.
565 // --------------------------------------------------------------------------
566 if (rc != 1) randolf::rex::mk_exception("Cannot use certificate chain file " + file, 0, rex::rex::REX_FLAGS::REX_FIND_ERRNO | rex::rex::REX_FLAGS::REX_TLS);
569 }; // -x- rsocket_sni* tls_ctx_use_certificate_chain_pem -x-
571 /*======================================================================*//**
573 Load a TLS private key in PEM format from a text file and use it in the TLS
576 @throws randolf::rex::xALL Catch this exception for now (at this time, the
577 OpenSSL library doesn't document which errors may be returned)
579 @returns The same rsocket_sni object so as to facilitate stacking
580 @see tls_ctx_use_privatekey_pem
581 @see tls_ctx_check_privatekey
582 *///=========================================================================
583 rsocket_sni* tls_ctx_use_privatekey_file(
584 /// Pointer to ASCIIZ path and filename to private key file
586 if (SSL_CTX_use_PrivateKey_file(__tls_ctx, file, SSL_FILETYPE_PEM) != 1) randolf::rex::mk_exception(std::string("Cannot use private key file ") + file, 0, rex::rex::REX_FLAGS::REX_FIND_ERRNO | rex::rex::REX_FLAGS::REX_TLS);
589 }; // -x- rsocket_sni* tls_ctx_use_privatekey_file -x-
591 /*======================================================================*//**
593 Load a TLS private key in PEM format from a text file and use it in the TLS
596 @throws randolf::rex::xALL Catch this exception for now (at this time, the
597 OpenSSL library doesn't document which errors may be returned)
599 @returns The same rsocket_sni object so as to facilitate stacking
600 @see tls_ctx_use_privatekey_pem
601 @see tls_ctx_check_privatekey
602 *///=========================================================================
603 rsocket_sni* tls_ctx_use_privatekey_file(
604 /// Path and filename to private key file
605 const std::string file) {
606 if (SSL_CTX_use_PrivateKey_file(__tls_ctx, file.c_str(), SSL_FILETYPE_PEM) != 1) randolf::rex::mk_exception("Cannot use private key file " + file, 0, rex::rex::REX_FLAGS::REX_FIND_ERRNO | rex::rex::REX_FLAGS::REX_TLS);
609 }; // -x- rsocket_sni* tls_ctx_use_privatekey_file -x-
611 /*======================================================================*//**
613 Load a TLS private key in PEM format from memory and use it in the TLS
616 Although this functionality doesn't exist in OpenSSL (at the time of writing
617 this method), it's provided here in a manner that has exactly the same effect
618 as the @ref tls_ctx_use_privatekey_file() methods, but without needing the
619 PEM-formatted private key stored in a file beforehand.
622 The @c pem_data parameter is a pointer to the memory location that holds the
623 PEM formatted private key data. If the length of this data isn't specified
624 or is set to zero (default), then it will be treated as a multiline ASCIIZ
627 Behind the scenes, we're just writing the pem_data memory to a temporary
628 file (with severely-limited permissions), then optionally overwriting that
629 temporary file with random data prior to deleting it (this is the default,
630 since better security practices should be the default, but on a secured
631 system it may not be necessary and so this option can also be disabled to
632 save CPU cycles and reduce overall disk-write I/O operations).
634 @throws randolf::rex::xALL Catch this exception for now (at this time, the
635 OpenSSL library doesn't document which errors may be returned)
637 @returns The same rsocket_sni object so as to facilitate stacking
638 @see tls_ctx_use_privatekey_file
639 @see tls_ctx_check_privatekey
640 *///=========================================================================
641 rsocket_sni* tls_ctx_use_privatekey_pem(
642 /// Pointer to private key data in PEM format
643 const char* pem_data,
644 /// Length of pem_data (in bytes), or 0 to auto-detect length if pem_data is an ASCIIZ string
646 /// Whether to overwrite the temporary file with random data before deleting it
647 const bool random_fill = true) {
649 // --------------------------------------------------------------------------
650 // Measure size of private key if an ASCIIZ string was indicated.
651 // --------------------------------------------------------------------------
652 if (len == 0) len = std::strlen(pem_data);
654 // --------------------------------------------------------------------------
655 // Generate filename for temporary use.
656 // --------------------------------------------------------------------------
657 std::string file = std::filesystem::temp_directory_path();
658 file.append("/rsocket_sni.")
659 .append(std::to_string(::getpid()))
661 .append(std::to_string(__fn_counter.fetch_add(1, std::memory_order_relaxed)));
663 // --------------------------------------------------------------------------
664 // Open temporary file.
665 // --------------------------------------------------------------------------
666 FILE* fp = fopen(file.c_str(), "w+");
667 if (fp == nullptr) randolf::rex::mk_exception("Cannot cannot open temporary file (to use private key) " + file, 0, rex::rex::REX_FLAGS::REX_FIND_ERRNO);
668 fchmod(fileno(fp), S_IRUSR | S_IWUSR);
669 if (fputs(pem_data, fp) == EOF) {
671 randolf::rex::mk_exception("Cannot cannot write to temporary file (to use private key) " + file, 0, rex::rex::REX_FLAGS::REX_FIND_ERRNO);
672 } // -x- if !fputs -x-
675 // --------------------------------------------------------------------------
676 // Attempt to load private key file, but save the error code for later
677 // because we need to clean up the temporary file before possibly throwing an
679 // --------------------------------------------------------------------------
680 int rc = SSL_CTX_use_PrivateKey_file(__tls_ctx, file.c_str(), SSL_FILETYPE_PEM);
682 // --------------------------------------------------------------------------
683 // Overwrite the contenst of the temporary file before deleting it so as to
684 // sabotage a simple attempt to undelete the file and access the certificate.
686 // We're also re-using the "len" local variable because it's not needed once
687 // we get the loop started, and it's more optimal to not allocate yet another
688 // local variable while "len" goes to waste. :D
689 // --------------------------------------------------------------------------
690 if (random_fill) { // This option is configurable
691 fseek(fp, 0, SEEK_SET); // File file position pointer to the beginning
692 ::srand(::time(0) + __fn_counter); // Current time + atomic __fn_counter value helps add variety
693 for (int i = len / sizeof(int) + (::rand() & 127); i >= 0; i--) { // Fill contents of file with random data
694 len = ::rand() + (len >> (sizeof(int) >> 2)); // Bias random data toward containing more set bits (more 1's)
695 fwrite(&len, sizeof(int), 1, fp); // Write one integer at a time
697 } // -x- if randfill -x-
698 fchmod(fileno(fp), 0); // Remove all permissions
699 fclose(fp); // Close file handle
700 unlink(file.c_str()); // Delete temporary file
702 // --------------------------------------------------------------------------
703 // Error check ... was delayed here until after temporary file cleanup.
704 // --------------------------------------------------------------------------
705 if (rc != 1) randolf::rex::mk_exception("Cannot use private key file " + file, 0, rex::rex::REX_FLAGS::REX_FIND_ERRNO | rex::rex::REX_FLAGS::REX_TLS);
708 }; // -x- rsocket_sni* tls_ctx_use_privatekey_pem -x-
710 }; // -x- class rsocket_sni -x-
712}; // -x- namespace randolf -x-