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
40 SNI wildcard matching incurs significant degrees of
42 - 2025-Feb-03 v1.00 Increased use of references and pointers
43 *///=========================================================================
47 // --------------------------------------------------------------------------
48 // Internal variables.
49 // --------------------------------------------------------------------------
50 SSL_CTX* __tls_ctx = nullptr; // Current TLS context
51 std::unordered_map<std::string, SSL_CTX*> __tls_ctx_map; // SNI map between hostnames and TLS contexts
52 std::unordered_map<std::string, SSL_CTX*> __tls_ctx_map_w; // SNI map with wildcards
53 inline static std::atomic_int __fn_counter = 0; // Used in generating unique-and-threadsafe temporary filenames
56 /*======================================================================*//**
58 Instantiate a new rsocket_sni object.
60 *///=========================================================================
62 /// OpenSSL's TLS context to use (if set to @c nullptr, this @c rsocket's TLS
63 /// context will be paired with the specified hostname{s})
64 SSL_CTX* ctx = nullptr,
65 /// When @c ctx is @c nullptr, indicates whether to initialize the new TLS
66 /// context with the @c TLS_server_method() or the @c TLS_client_method()
67 /// function (the default is @c true, which is the .
68 const bool is_server_method = true) {
69 tls_ctx(ctx, is_server_method);
70 } // -x- constructor rsocket_sni -x-
72 /*======================================================================*//**
74 Destructor for rsocket_sni object, which deletes all SNI hostnames and their
75 associated TLS contexts, updating OpenSSL's internal TLS context reference
78 *///=========================================================================
79 ~rsocket_sni() noexcept {
81 // --------------------------------------------------------------------------
82 // Remove all entries so that OpenSSL's internal counters are accounted for.
83 // --------------------------------------------------------------------------
84 for (auto& sni: __tls_ctx_map) del(sni.first);
85 for (auto& sni: __tls_ctx_map_w) del(sni.first);
87 } // -x- constructor rsocket_sni -x-
89 /*======================================================================*//**
91 Add one new hostname, which will be associated with the current TLS context.
92 SNI wildcards are supported (e.g., `*.example.com`).
94 If the specified hostname already exists, it will be updated with the newer
95 TLS context because re-adding is accepted, by acclamation, as a replacement.
96 @returns The same rsocket_sni object so as to facilitate stacking
100 *///=========================================================================
103 const std::string& hostname) {
105 // --------------------------------------------------------------------------
106 // Internal variables.
107 // --------------------------------------------------------------------------
108 bool w = hostname.find("*") != std::string::npos;
110 // --------------------------------------------------------------------------
111 // Replace hostname if it already exists and the TLS context is different,
112 // then update OpenSSL's internal reference counters accordingly.
113 // --------------------------------------------------------------------------
115 SSL_CTX* ctx = (w ? __tls_ctx_map_w : __tls_ctx_map).at(hostname); // Throws std::out_of_range exception if key doesn't exist
116 if (ctx == __tls_ctx) return *this; // The TLS context didn't change, so return now to save CPU cycles (optimization)
117 SSL_CTX_free(ctx); // Decrement SSL_CTX's internal reference count
118 (w ? __tls_ctx_map_w : __tls_ctx_map)[hostname] = __tls_ctx;
119 SSL_CTX_up_ref(__tls_ctx); // Increment SSL_CTX's internal reference count
120 } catch (const std::out_of_range& e) {} // TODO: Handle exceptions properly
122 // --------------------------------------------------------------------------
123 // Add hostname with current TLS context.
124 // --------------------------------------------------------------------------
125 (w ? __tls_ctx_map_w : __tls_ctx_map).insert({hostname, __tls_ctx});
126 SSL_CTX_up_ref(__tls_ctx); // Increment SSL_CTX's internal reference count
129 } // -x- rsocket_sni& add -x-
131 /*======================================================================*//**
133 Add one or more new hostnames, which will be associated with the current TLS
134 context. SNI wildcards are supported (e.g., `*.example.com`).
136 If any specified hostnames already exist, they will be updated with the newer
137 TLS context because re-adding is accepted, by acclamation, as a replacement.
138 @returns The same rsocket_sni object so as to facilitate stacking
142 *///=========================================================================
143 template<class... Hs> rsocket_sni& add(
144 /// First hostname (required)
145 const std::string& hostname,
146 /// Additional hostnames (optional)
149 // --------------------------------------------------------------------------
150 // Recursively loop through the variable arguments.
151 // --------------------------------------------------------------------------
156 } // -x- rsocket_sni& add -x-
158 /*======================================================================*//**
162 If the specified hostname doesn't exist, nothing will happen.
163 @returns The same rsocket_sni object so as to facilitate stacking
165 *///=========================================================================
167 /// Hostname to delete
168 const std::string& hostname) {
170 // --------------------------------------------------------------------------
171 // Internal variables.
172 // --------------------------------------------------------------------------
173 bool w = hostname.find("*") != std::string::npos; // Used to determine which map to delete from
175 // --------------------------------------------------------------------------
176 // If hostname exists, delete it and update OpenSSL's internal reference
177 // counters accordingly.
178 // --------------------------------------------------------------------------
180 SSL_CTX* ctx = (w ? __tls_ctx_map_w : __tls_ctx_map).at(hostname); // Throws std::out_of_range exception if key doesn't exist
181 SSL_CTX_free(ctx); // Decrement SSL_CTX's internal reference count
182// (w ? __tls_ctx_map_w : __tls_ctx_map).erase(hostname); // This causes segmentation faults after returning from main()
183// std::cout << "rsocket_sni del(" << hostname << ") w=" << w << std::endl; // This reveals a partly corrupted hostname after calling erase
184 } catch (const std::out_of_range& e) {
185 // std::cerr << "rsocket_sni couldn't delete key for " << hostname << std::endl; // This should never happen
189 } // -x- rsocket_sni& del -x-
191 /*======================================================================*//**
193 Delete one or more hostnames.
195 If any specified hostnames don't exist, then no deletion of them will occur.
196 @returns The same rsocket_sni object so as to facilitate stacking
198 *///=========================================================================
199 template<class... Hs> rsocket_sni& del(
200 /// First hostname (required)
201 const std::string& hostname,
202 /// Additional hostnames (optional)
205 // --------------------------------------------------------------------------
206 // Recursively loop through the variable arguments.
207 // --------------------------------------------------------------------------
212 } // -x- rsocket_sni& del -x-
214 /*======================================================================*//**
216 Return the TLS context for a given hostname. If any SNI wildcards satisfy
217 hostname-matching criteria (e.g., `www.example.com` matches a `*.example.com`
218 wildcard), the respective TLS context for it will be returned (as expected).
220 For wildcard comparisons, OpenSSL's functions are used for matching to ensure
221 consistency with what's expected, and in order to support any unforseen
222 changes to future standards in wildcard naming rules. (In other words, we're
223 relying on OpenSSL to do what it already does efficiently and correctly.)
225 Exact matches are always checked before wildcards because the exact matching
226 algorithm is faster (uses fewer CPU cycles) than comparing wildcards.
227 @returns TLS context, or @c nullptr (or @c default_tls_ctx override) if
228 hostname doesn't match any SNI entries
230 *///=========================================================================
233 const std::string& hostname,
234 /// SNI supports wildcards, but if an exact match is needed then setting this
235 /// parameter to @c false will skip attempts to also match wildcards (the
236 /// default is @c true)
237 const bool enable_sni_wildcards = true,
238 /// Override the default context @c nullptr that is returned to indicate that
239 /// the hostname didn't match
240 SSL_CTX* default_tls_ctx = nullptr) {
242 // --------------------------------------------------------------------------
243 // Retrieve exact match. If SNI wildcard checking is disabled, and an exact
244 // match could not be retrieved, then just return the default TLS context.
245 // --------------------------------------------------------------------------
247 return __tls_ctx_map.at(hostname); // Throws std::out_of_range exception if key doesn't exist
248 } catch (const std::out_of_range e) {
249 if (!enable_sni_wildcards) return default_tls_ctx; // No wildcard checks, so return default
252 // --------------------------------------------------------------------------
253 // Find wildcard match -- this takes longer, which is why we don't do this
256 // We're relying on OpenSSL to do all the heavy-lifting when parsing wildcard
257 // labels, which is best because OpenSSL does this correctly and is also in
258 // the best position to accomplish this with the most efficiency.
259 // --------------------------------------------------------------------------
260 for (auto& sni: __tls_ctx_map_w) {
261 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;
262 char* peername = nullptr;
263 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)
264 // Debug: std::cout << "SNI -> key=" << sni.first << " peername=" << (peername != nullptr ? peername : "nullptr") << " hostname=" << hostname << std::endl;
265 if (peername != nullptr) OPENSSL_free(peername); // Memory management
266 if (rc == 1) return sni.second; // Wildcard match successful, so we're finished with all of this now
267 } // -x- foreach sni -x-
269 return default_tls_ctx;
270 } // -x- SSL_CTX* get_ctx -x-
272 /*======================================================================*//**
274 Find out what the current TLS context is set to.
276 *///=========================================================================
277 SSL_CTX* tls_ctx() noexcept {
279 } // -x- SSL_CTX* tls_ctx -x-
281 /*======================================================================*//**
283 Set the current TLS context to a specific context or create a new one.
284 @returns The same rsocket_sni object so as to facilitate stacking
285 *///=========================================================================
286 rsocket_sni& tls_ctx(
287 /// OpenSSL's TLS context to use (if set to @c nullptr, this @c rsocket's TLS
288 /// context will be paired with the specified hostname{s})
290 /// When @c ctx is @c nullptr, indicates whether to initialize the new TLS
291 /// context with the @c TLS_server_method(), which is the default. If set to
292 /// @c false, then the @c TLS_client_method() function will be called as part
293 /// of initializing a new TLS context.
294 const bool is_server_method = true) {
296 // --------------------------------------------------------------------------
297 // Save (ctx != nullptr) or create (ctx == nullptr) OpenSSL context.
298 // --------------------------------------------------------------------------
299 __tls_ctx = ctx == nullptr ? SSL_CTX_new(is_server_method ? TLS_server_method() : TLS_client_method()) // Create anew (default)
300 : ctx; // Use OpenSSL context that was provided
301 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);
304 } // -x- rsocket_sni& tls_ctx -x-
306 /*======================================================================*//**
308 Check the private key it to ensure it's consistent with the corresponding TLS
311 @throws randolf::rex::xALL Catch this exception for now (at this time, the
312 OpenSSL library doesn't document which errors may be returned)
314 @returns The same rsocket_sni object so as to facilitate stacking
315 @see tls_ctx_use_privatekey_file
316 @see tls_ctx_use_privatekey_pem
317 *///=========================================================================
318 rsocket_sni& tls_ctx_check_privatekey() {
319 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);
321 } // -x- rsocket_sni& tls_ctx_check_privatekey -x-
323 /*======================================================================*//**
325 Load a TLS certificate chain and private key in PEM format from text files
326 and use them in the TLS context.
328 @throws randolf::rex::xALL Catch this exception for now (at this time, the
329 OpenSSL library doesn't document which errors may be returned)
331 @returns The same rsocket_sni object so as to facilitate stacking
332 @see tls_ctx_use_certificate_chain_and_privatekey_pems
333 @see tls_ctx_use_certificate_chain_file
334 @see tls_ctx_use_certificate_chain_pem
335 @see tls_ctx_use_privatekey_file
336 @see tls_ctx_use_privatekey_pem
337 @see tls_ctx_check_privatekey
338 *///=========================================================================
339 rsocket_sni& tls_ctx_use_certificate_chain_and_privatekey_files(
340 /// Pointer to ASCIIZ path and filename to certificate chain file (@c nullptr
341 /// will simply be ignored)
342 const char* chain_file,
343 /// Pointer to ASCIIZ path and filename to private key file (@c nullptr will
344 /// simply be ignored)
345 const char* key_file) {
346 if (chain_file != nullptr) tls_ctx_use_certificate_chain_file(chain_file);
347 if ( key_file != nullptr) tls_ctx_use_privatekey_file( key_file);
349 } // -x- rsocket_sni& tls_ctx_use_certificate_chain_and_privatekey_files -x-
351 /*======================================================================*//**
353 Load a TLS certificate chain and private key in PEM format from text files
354 and use them in the TLS context.
356 @throws randolf::rex::xALL Catch this exception for now (at this time, the
357 OpenSSL library doesn't document which errors may be returned)
359 @returns The same rsocket_sni object so as to facilitate stacking
360 @see tls_ctx_use_certificate_chain_and_privatekey_pems
361 @see tls_ctx_use_certificate_chain_file
362 @see tls_ctx_use_certificate_chain_pem
363 @see tls_ctx_use_privatekey_file
364 @see tls_ctx_use_privatekey_pem
365 @see tls_ctx_check_privatekey
366 *///=========================================================================
367 rsocket_sni& tls_ctx_use_certificate_chain_and_privatekey_files(
368 /// Pointer to ASCIIZ path and filename to certificate chain file (an empty
369 /// string will simply be ignored)
370 const std::string& chain_file,
371 /// Pointer to ASCIIZ path and filename to private key file (an empty string
372 /// will simply be ignored)
373 const std::string& key_file) {
374 if (!chain_file.empty()) tls_ctx_use_certificate_chain_file(chain_file);
375 if ( !key_file.empty()) tls_ctx_use_privatekey_file( key_file);
377 } // -x- rsocket_sni& tls_ctx_use_certificate_chain_and_privatekey_files -x-
379 /*======================================================================*//**
381 Load a TLS certificate chain and a TLS private key in PEM format from memory
382 and use them in the TLS context.
384 Although this functionality doesn't exist in OpenSSL (at the time of writing
385 this method), it's provided here in a manner that has exactly the same effect
386 as the @ref tls_ctx_use_certificate_chain_and_privatekey_files() methods, but
387 without needing the PEM-formatted certificate chain stored in files
391 The @c cert_pem_data and key_pem_data parameters are pointers to the memory
392 locations that holds the PEM formatted certificate chain data and private key
393 data, respectively. If the corresponding lengths of each of these data aren't
394 specified or are set to zero (default), then they will be treated as multiline
397 Behind the scenes, we're just writing the cert_pem_data and key_pem_data
398 memory to temporary files with severely-limited permissions (), then
399 optionally overwriting those temporary files with random data prior to
400 deleting them (this is the default, since better security practices should be
401 the default, but on a secured system it may not be necessary and so this
402 option can also be disabled to save CPU cycles and reduce overall disk-write
405 @throws randolf::rex::xALL Catch this exception for now (at this time, the
406 OpenSSL library doesn't document which errors may be returned)
408 @returns The same rsocket_sni object so as to facilitate stacking
409 @see tls_ctx_use_certificate_chain_and_privatekey_files
410 @see tls_ctx_use_certificate_chain_file
411 @see tls_ctx_use_certificate_chain_pem
412 @see tls_ctx_use_privatekey_file
413 @see tls_ctx_use_privatekey_pem
414 @see tls_ctx_check_privatekey
415 *///=========================================================================
416 rsocket_sni& tls_ctx_use_certificate_chain_and_privatekey_pems(
417 /// Pointer to certificate chain data in PEM format
418 const char* cert_pem_data,
419 /// Pointer to private key data in PEM format
420 const char* key_pem_data,
421 /// Length of cert_pem_data (in bytes), or 0 to auto-detect length if cert_pem_data is an ASCIIZ string
423 /// Length of key_pem_data (in bytes), or 0 to auto-detect length if key_pem_data is an ASCIIZ string
425 /// Whether to overwrite the temporary files with random data before deleting them
426 const bool random_fill = true) {
427 tls_ctx_use_certificate_chain_pem(cert_pem_data, cert_len, random_fill);
428 tls_ctx_use_privatekey_pem( key_pem_data, key_len, random_fill);
430 } // -x- rsocket_sni tls_ctx_use_certificate_chain_and_privatekey_pems -x-
432 /*======================================================================*//**
434 Load a TLS certificate chain in PEM format from a text file and use it in the
437 @throws randolf::rex::xALL Catch this exception for now (at this time, the
438 OpenSSL library doesn't document which errors may be returned)
440 @returns The same rsocket_sni object so as to facilitate stacking
441 @see tls_ctx_use_certificate_chain_pem
442 @see tls_ctx_check_privatekey
443 *///=========================================================================
444 rsocket_sni& tls_ctx_use_certificate_chain_file(
445 /// Pointer to ASCIIZ path and filename to certificate chain file
447 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);
449 } // -x- rsocket_sni tls_ctx_use_certificate_chain_file -x-
451 /*======================================================================*//**
453 Load a TLS certificate chain in PEM format from a text file and use it in the
456 @throws randolf::rex::xALL Catch this exception for now (at this time, the
457 OpenSSL library doesn't document which errors may be returned)
459 @returns The same rsocket_sni object so as to facilitate stacking
460 @see tls_ctx_use_certificate_chain_pem
461 @see tls_ctx_check_privatekey
462 *///=========================================================================
463 rsocket_sni& tls_ctx_use_certificate_chain_file(
464 /// Path and filename to certificate chain file
465 const std::string& file) {
466 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);
468 } // -x- rsocket_sni& tls_ctx_use_certificate_chain_file -x-
470 /*======================================================================*//**
472 Load a TLS certificate chain in PEM format from memory and use it in the TLS
475 Although this functionality doesn't exist in OpenSSL (at the time of writing
476 this method), it's provided here in a manner that has exactly the same effect
477 as the @ref tls_ctx_use_certificate_chain_file() methods, but without needing
478 the PEM-formatted certificate chain stored in a file beforehand.
481 The @c pem_data parameter is a pointer to the memory location that holds
482 the PEM formatted certificate chain data. If the length of this data isn't
483 specified or is set to zero (default), then it will be treated as a multiline
486 Behind the scenes, we're just writing the pem_data memory to a temporary
487 file with severely-limited permissions (), then optionally overwriting that
488 temporary file with random data prior to deleting it (this is the default,
489 since better security practices should be the default, but on a secured
490 system it may not be necessary and so this option can also be disabled to
491 save CPU cycles and reduce overall disk-write I/O operations).
493 @throws randolf::rex::xALL Catch this exception for now (at this time, the
494 OpenSSL library doesn't document which errors may be returned)
496 @returns The same rsocket_sni object so as to facilitate stacking
497 @see tls_ctx_use_certificate_chain_file
498 @see tls_ctx_check_privatekey
499 *///=========================================================================
500 rsocket_sni& tls_ctx_use_certificate_chain_pem(
501 /// Pointer to certificate chain data in PEM format
502 const char* pem_data,
503 /// Length of pem_data (in bytes), or 0 to auto-detect length if pem_data is an ASCIIZ string
505 /// Whether to overwrite the temporary file with random data before deleting it
506 const bool random_fill = true) {
508 // --------------------------------------------------------------------------
509 // Measure size of certificate chain if an ASCIIZ string was indicated.
510 // --------------------------------------------------------------------------
511 if (len == 0) len = std::strlen(pem_data);
513 // --------------------------------------------------------------------------
514 // Generate filename for temporary use.
515 // --------------------------------------------------------------------------
516 std::string file = std::filesystem::temp_directory_path();
517 file.append("/rsocket_sni.")
518 .append(std::to_string(::getpid()))
520 .append(std::to_string(__fn_counter.fetch_add(1, std::memory_order_relaxed)));
522 // --------------------------------------------------------------------------
523 // Open temporary file.
524 // --------------------------------------------------------------------------
525 FILE* fp = fopen(file.c_str(), "w+");
526 if (fp == nullptr) randolf::rex::mk_exception("Cannot open temporary file (to use certificate chain) " + file, 0, rex::rex::REX_FLAGS::REX_FIND_ERRNO);
528 ioctl(fileno(fp), FS_IOC_GETFLAGS, &attr);
529 attr |= FS_NOATIME_FL // Don't update access time attribute
530 | FS_NODUMP_FL // Don't include in filesystem backup dumps
531 | FS_SECRM_FL; // Mark file for secure deletion (where supported)
532 ioctl(fileno(fp), FS_IOC_SETFLAGS, &attr);
534 fchmod(fileno(fp), S_IRUSR | S_IWUSR);
535 if (fputs(pem_data, fp) == EOF) {
537 randolf::rex::mk_exception("Cannot write to temporary file (to use certificate chain) " + file, 0, rex::rex::REX_FLAGS::REX_FIND_ERRNO);
538 } // -x- if !fputs -x-
541 // --------------------------------------------------------------------------
542 // Attempt to load certificate chain file, but save the error code for later
543 // because we need to clean up the temporary file before possibly throwing an
545 // --------------------------------------------------------------------------
546 int rc = SSL_CTX_use_certificate_chain_file(__tls_ctx, file.c_str());
548 // --------------------------------------------------------------------------
549 // Overwrite the contenst of the temporary file before deleting it so as to
550 // sabotage a simple attempt to undelete the file and access the certificate.
552 // We're also re-using the "len" local variable because it's not needed once
553 // we get the loop started, and it's more optimal to not allocate yet another
554 // local variable while "len" goes to waste. :D
555 // --------------------------------------------------------------------------
556 if (random_fill) { // This option is configurable
557 fseek(fp, 0, SEEK_SET); // File file position pointer to the beginning
558 ::srand(::time(0) + __fn_counter); // Current time + atomic __fn_counter value helps add variety
559 for (int i = len / sizeof(int) + (::rand() & 127); i >= 0; i--) { // Fill contents of file with random data
560 len = ::rand() + (len >> (sizeof(int) >> 2)); // Bias random data toward containing more set bits (more 1's)
561 fwrite(&len, sizeof(int), 1, fp); // Write one integer at a time
563 } // -x- if randfill -x-
564 fchmod(fileno(fp), 0); // Remove all permissions
565 fclose(fp); // Close file handle
566 unlink(file.c_str()); // Delete temporary file
568 // --------------------------------------------------------------------------
569 // Error check ... was delayed here until after temporary file cleanup.
570 // --------------------------------------------------------------------------
571 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);
574 } // -x- rsocket_sni& tls_ctx_use_certificate_chain_pem -x-
576 /*======================================================================*//**
578 Load a TLS private key in PEM format from a text file and use it in the TLS
581 @throws randolf::rex::xALL Catch this exception for now (at this time, the
582 OpenSSL library doesn't document which errors may be returned)
584 @returns The same rsocket_sni object so as to facilitate stacking
585 @see tls_ctx_use_privatekey_pem
586 @see tls_ctx_check_privatekey
587 *///=========================================================================
588 rsocket_sni& tls_ctx_use_privatekey_file(
589 /// Pointer to ASCIIZ path and filename to private key file
591 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);
594 } // -x- rsocket_sni& tls_ctx_use_privatekey_file -x-
596 /*======================================================================*//**
598 Load a TLS private key in PEM format from a text file and use it in the TLS
601 @throws randolf::rex::xALL Catch this exception for now (at this time, the
602 OpenSSL library doesn't document which errors may be returned)
604 @returns The same rsocket_sni object so as to facilitate stacking
605 @see tls_ctx_use_privatekey_pem
606 @see tls_ctx_check_privatekey
607 *///=========================================================================
608 rsocket_sni& tls_ctx_use_privatekey_file(
609 /// Path and filename to private key file
610 const std::string& file) {
611 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);
614 } // -x- rsocket_sni& tls_ctx_use_privatekey_file -x-
616 /*======================================================================*//**
618 Load a TLS private key in PEM format from memory and use it in the TLS
621 Although this functionality doesn't exist in OpenSSL (at the time of writing
622 this method), it's provided here in a manner that has exactly the same effect
623 as the @ref tls_ctx_use_privatekey_file() methods, but without needing the
624 PEM-formatted private key stored in a file beforehand.
627 The @c pem_data parameter is a pointer to the memory location that holds the
628 PEM formatted private key data. If the length of this data isn't specified
629 or is set to zero (default), then it will be treated as a multiline ASCIIZ
632 Behind the scenes, we're just writing the pem_data memory to a temporary
633 file (with severely-limited permissions), then optionally overwriting that
634 temporary file with random data prior to deleting it (this is the default,
635 since better security practices should be the default, but on a secured
636 system it may not be necessary and so this option can also be disabled to
637 save CPU cycles and reduce overall disk-write I/O operations).
639 @throws randolf::rex::xALL Catch this exception for now (at this time, the
640 OpenSSL library doesn't document which errors may be returned)
642 @returns The same rsocket_sni object so as to facilitate stacking
643 @see tls_ctx_use_privatekey_file
644 @see tls_ctx_check_privatekey
645 *///=========================================================================
646 rsocket_sni& tls_ctx_use_privatekey_pem(
647 /// Pointer to private key data in PEM format
648 const char* pem_data,
649 /// Length of pem_data (in bytes), or 0 to auto-detect length if pem_data is an ASCIIZ string
651 /// Whether to overwrite the temporary file with random data before deleting it
652 const bool random_fill = true) {
654 // --------------------------------------------------------------------------
655 // Measure size of private key if an ASCIIZ string was indicated.
656 // --------------------------------------------------------------------------
657 if (len == 0) len = std::strlen(pem_data);
659 // --------------------------------------------------------------------------
660 // Generate filename for temporary use.
661 // --------------------------------------------------------------------------
662 std::string file = std::filesystem::temp_directory_path();
663 file.append("/rsocket_sni.")
664 .append(std::to_string(::getpid()))
666 .append(std::to_string(__fn_counter.fetch_add(1, std::memory_order_relaxed)));
668 // --------------------------------------------------------------------------
669 // Open temporary file.
670 // --------------------------------------------------------------------------
671 FILE* fp = fopen(file.c_str(), "w+");
672 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);
673 fchmod(fileno(fp), S_IRUSR | S_IWUSR);
674 if (fputs(pem_data, fp) == EOF) {
676 randolf::rex::mk_exception("Cannot cannot write to temporary file (to use private key) " + file, 0, rex::rex::REX_FLAGS::REX_FIND_ERRNO);
677 } // -x- if !fputs -x-
680 // --------------------------------------------------------------------------
681 // Attempt to load private key file, but save the error code for later
682 // because we need to clean up the temporary file before possibly throwing an
684 // --------------------------------------------------------------------------
685 int rc = SSL_CTX_use_PrivateKey_file(__tls_ctx, file.c_str(), SSL_FILETYPE_PEM);
687 // --------------------------------------------------------------------------
688 // Overwrite the contenst of the temporary file before deleting it so as to
689 // sabotage a simple attempt to undelete the file and access the certificate.
691 // We're also re-using the "len" local variable because it's not needed once
692 // we get the loop started, and it's more optimal to not allocate yet another
693 // local variable while "len" goes to waste. :D
694 // --------------------------------------------------------------------------
695 if (random_fill) { // This option is configurable
696 fseek(fp, 0, SEEK_SET); // File file position pointer to the beginning
697 ::srand(::time(0) + __fn_counter); // Current time + atomic __fn_counter value helps add variety
698 for (int i = len / sizeof(int) + (::rand() & 127); i >= 0; i--) { // Fill contents of file with random data
699 len = ::rand() + (len >> (sizeof(int) >> 2)); // Bias random data toward containing more set bits (more 1's)
700 fwrite(&len, sizeof(int), 1, fp); // Write one integer at a time
702 } // -x- if randfill -x-
703 fchmod(fileno(fp), 0); // Remove all permissions
704 fclose(fp); // Close file handle
705 unlink(file.data()); // Delete temporary file
707 // --------------------------------------------------------------------------
708 // Error check ... was delayed here until after temporary file cleanup.
709 // --------------------------------------------------------------------------
710 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);
713 } // -x- rsocket_sni& tls_ctx_use_privatekey_pem -x-
715 }; // -x- class rsocket_sni -x-
717}; // -x- namespace randolf -x-