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 *///=========================================================================
42 // --------------------------------------------------------------------------
43 // Internal variables.
44 // --------------------------------------------------------------------------
45 SSL_CTX* __tls_ctx = nullptr; // Current TLS context
46 std::unordered_map<std::string, SSL_CTX*> __tls_ctx_map; // SNI map between hostnames and TLS contexts
47 std::unordered_map<std::string, SSL_CTX*> __tls_ctx_map_w; // SNI map with wildcards
48 inline static std::atomic_int __fn_counter = 0; // Used in generating unique-and-threadsafe temporary filenames
51 /*======================================================================*//**
53 Instantiate a new rsocket_sni object.
55 *///=========================================================================
57 /// OpenSSL's TLS context to use (if set to @c nullptr, this @c rsocket's TLS
58 /// context will be paired with the specified hostname{s})
59 SSL_CTX* ctx = nullptr,
60 /// When @c ctx is @c nullptr, indicates whether to initialize the new TLS
61 /// context with the @c TLS_server_method() or the @c TLS_client_method()
62 /// function (the default is @c true, which is the .
63 const bool is_server_method = true) {
64 tls_ctx(ctx, is_server_method);
65 }; // -x- constructor rsocket_sni -x-
67 /*======================================================================*//**
69 Destructor for rsocket_sni object, which deletes all SNI hostnames and their
70 associated TLS contexts, updating OpenSSL's internal TLS context reference
73 *///=========================================================================
74 ~rsocket_sni() noexcept {
76 // --------------------------------------------------------------------------
77 // Remove all entries so that OpenSSL's internal counters are accounted for.
78 // --------------------------------------------------------------------------
79 for (auto& sni: __tls_ctx_map) del(sni.first);
80 for (auto& sni: __tls_ctx_map_w) del(sni.first);
82 }; // -x- constructor rsocket_sni -x-
84 /*======================================================================*//**
86 Add one new hostname, which will be associated with the current TLS context.
87 SNI wildcards are supported (e.g., `*.example.com`).
89 If the specified hostname already exists, it will be updated with the newer
90 TLS context because re-adding is accepted, by acclamation, as a replacement.
91 @returns The same rsocket_sni object so as to facilitate stacking
95 *///=========================================================================
98 std::string hostname) {
100 // --------------------------------------------------------------------------
101 // Internal variables.
102 // --------------------------------------------------------------------------
103 bool w = hostname.find("*") != std::string::npos;
105 // --------------------------------------------------------------------------
106 // Replace hostname if it already exists and the TLS context is different,
107 // then update OpenSSL's internal reference counters accordingly.
108 // --------------------------------------------------------------------------
110 SSL_CTX* ctx = (w ? __tls_ctx_map_w : __tls_ctx_map).at(hostname); // Throws std::out_of_range exception if key doesn't exist
111 if (ctx == __tls_ctx) return this; // The TLS context didn't change, so return now to save CPU cycles (optimization)
112 SSL_CTX_free(ctx); // Decrement SSL_CTX's internal reference count
113 (w ? __tls_ctx_map_w : __tls_ctx_map)[hostname] = __tls_ctx;
114 SSL_CTX_up_ref(__tls_ctx); // Increment SSL_CTX's internal reference count
115 } catch (const std::out_of_range e) {}
117 // --------------------------------------------------------------------------
118 // Add hostname with current TLS context.
119 // --------------------------------------------------------------------------
120 (w ? __tls_ctx_map_w : __tls_ctx_map).insert({hostname, __tls_ctx});
121 SSL_CTX_up_ref(__tls_ctx); // Increment SSL_CTX's internal reference count
124 }; // -x- rsocket_sni* add -x-
126 /*======================================================================*//**
128 Add one or more new hostnames, which will be associated with the current TLS
129 context. SNI wildcards are supported (e.g., `*.example.com`).
131 If any specified hostnames already exist, they will be updated with the newer
132 TLS context because re-adding is accepted, by acclamation, as a replacement.
133 @returns The same rsocket_sni object so as to facilitate stacking
137 *///=========================================================================
138 template<class... Hs> rsocket_sni* add(
139 /// First hostname (required)
140 std::string hostname,
141 /// Additional hostnames (optional)
144 // --------------------------------------------------------------------------
145 // Recursively loop through the variable arguments.
146 // --------------------------------------------------------------------------
151 }; // -x- rsocket_sni* add -x-
153 /*======================================================================*//**
157 If the specified hostname doesn't exist, nothing will happen.
158 @returns The same rsocket_sni object so as to facilitate stacking
160 *///=========================================================================
162 /// Hostname to delete
163 std::string hostname) {
165 // --------------------------------------------------------------------------
166 // Internal variables.
167 // --------------------------------------------------------------------------
168 bool w = hostname.find("*") != std::string::npos; // Used to determine which map to delete from
170 // --------------------------------------------------------------------------
171 // If hostname exists, delete it and update OpenSSL's internal reference
172 // counters accordingly.
173 // --------------------------------------------------------------------------
175 SSL_CTX* ctx = (w ? __tls_ctx_map_w : __tls_ctx_map).at(hostname); // Throws std::out_of_range exception if key doesn't exist
176 SSL_CTX_free(ctx); // Decrement SSL_CTX's internal reference count
177 (w ? __tls_ctx_map_w : __tls_ctx_map).erase(hostname);
178 } catch (const std::out_of_range e) {}
181 }; // -x- rsocket_sni* del -x-
183 /*======================================================================*//**
185 Delete one or more hostnames.
187 If any specified hostnames don't exist, then no deletion of them will occur.
188 @returns The same rsocket_sni object so as to facilitate stacking
190 *///=========================================================================
191 template<class... Hs> rsocket_sni* del(
192 /// First hostname (required)
193 std::string hostname,
194 /// Additional hostnames (optional)
197 // --------------------------------------------------------------------------
198 // Recursively loop through the variable arguments.
199 // --------------------------------------------------------------------------
204 }; // -x- rsocket_sni* del -x-
206 /*======================================================================*//**
208 Return the TLS context for a given hostname. If any SNI wildcards satisfy
209 hostname-matching criteria (e.g., `www.example.com` matches a `*.example.com`
210 wildcard), the respective TLS context for it will be returned (as expected).
212 For wildcard comparisons, OpenSSL's functions are used for matching to ensure
213 consistency with what's expected, and in order to support any unforseen
214 changes to future standards in wildcard naming rules. (In other words, we're
215 relying on OpenSSL to do what it already does efficiently and correctly.)
217 Exact matches are always checked before wildcards because the exact matching
218 algorithm is faster (uses fewer CPU cycles) than comparing wildcards.
219 @returns TLS context, or @c nullptr (or @c default_tls_ctx override) if
220 hostname doesn't match any SNI entries
222 *///=========================================================================
225 std::string hostname,
226 /// SNI supports wildcards, but if an exact match is needed then setting this
227 /// parameter to @c false will skip attempts to also match wildcards (the
228 /// default is @c true)
229 const bool enable_sni_wildcards = true,
230 /// Override the default context @c nullptr that is returned to indicate that
231 /// the hostname didn't match
232 SSL_CTX* default_tls_ctx = nullptr) {
234 // --------------------------------------------------------------------------
235 // Retrieve exact match. If SNI wildcard checking is disabled, and an exact
236 // match could not be retrieved, then just return the default TLS context.
237 // --------------------------------------------------------------------------
239 return __tls_ctx_map.at(hostname); // Throws std::out_of_range exception if key doesn't exist
240 } catch (const std::out_of_range e) {
241 if (!enable_sni_wildcards) return default_tls_ctx; // No wildcard checks, so return default
244 // --------------------------------------------------------------------------
245 // Find wildcard match -- this takes longer, which is why we started with the
246 // exact match first.
248 // We're relying on OpenSSL to do all the heavy-lifting when parsing wildcard
249 // labels, which is best because OpenSSL does this efficiently and correctly.
250 // --------------------------------------------------------------------------
251 for (auto& sni: __tls_ctx_map_w) {
252 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;
253 char* peername = nullptr;
254 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)
255 // Debug: std::cout << "SNI -> key=" << sni.first << " peername=" << (peername != nullptr ? peername : "nullptr") << " hostname=" << hostname << std::endl;
256 if (peername != nullptr) OPENSSL_free(peername); // Memory management
257 if (rc == 1) return sni.second; // Wildcard match successful, so we're finished with all of this now
258 } // -x- foreach sni -x-
260 return default_tls_ctx;
261 }; // -x- SSL_CTX* get_ctx -x-
263 /*======================================================================*//**
265 Find out what the current TLS context is set to.
267 *///=========================================================================
268 SSL_CTX* tls_ctx() noexcept {
270 }; // -x- SSL_CTX* tls_ctx -x-
272 /*======================================================================*//**
274 Set the current TLS context to a specific context or create a new one.
275 @returns The same rsocket_sni object so as to facilitate stacking
276 *///=========================================================================
277 rsocket_sni* tls_ctx(
278 /// OpenSSL's TLS context to use (if set to @c nullptr, this @c rsocket's TLS
279 /// context will be paired with the specified hostname{s})
281 /// When @c ctx is @c nullptr, indicates whether to initialize the new TLS
282 /// context with the @c TLS_server_method(), which is the default. If set to
283 /// @c false, then the @c TLS_client_method() function will be called as part
284 /// of initializing a new TLS context.
285 const bool is_server_method = true) {
287 // --------------------------------------------------------------------------
288 // Save (ctx != nullptr) or create (ctx == nullptr) OpenSSL context.
289 // --------------------------------------------------------------------------
290 __tls_ctx = ctx == nullptr ? SSL_CTX_new(is_server_method ? TLS_server_method() : TLS_client_method()) // Create anew (default)
291 : ctx; // Use OpenSSL context that was provided
292 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);
295 }; // -x- rsocket_sni* tls_ctx -x-
297 /*======================================================================*//**
299 Check the private key it to ensure it's consistent with the corresponding TLS
302 @throws randolf::rex::xALL Catch this exception for now (at this time, the
303 OpenSSL library doesn't document which errors may be returned)
305 @returns The same rsocket_sni object so as to facilitate stacking
306 @see tls_ctx_use_privatekey_file
307 @see tls_ctx_use_privatekey_pem
308 *///=========================================================================
309 rsocket_sni* tls_ctx_check_privatekey() {
310 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);
312 }; // -x- rsocket_sni* tls_ctx_check_privatekey -x-
314 /*======================================================================*//**
316 Load a TLS certificate chain and private key in PEM format from text files
317 and use them in the TLS context.
319 @throws randolf::rex::xALL Catch this exception for now (at this time, the
320 OpenSSL library doesn't document which errors may be returned)
322 @returns The same rsocket_sni object so as to facilitate stacking
323 @see tls_ctx_use_certificate_chain_and_privatekey_pems
324 @see tls_ctx_use_certificate_chain_file
325 @see tls_ctx_use_certificate_chain_pem
326 @see tls_ctx_use_privatekey_file
327 @see tls_ctx_use_privatekey_pem
328 @see tls_ctx_check_privatekey
329 *///=========================================================================
330 rsocket_sni* tls_ctx_use_certificate_chain_and_privatekey_files(
331 /// Pointer to ASCIIZ path and filename to certificate chain file (@c nullptr
332 /// will simply be ignored)
333 const char* chain_file,
334 /// Pointer to ASCIIZ path and filename to private key file (@c nullptr will
335 /// simply be ignored)
336 const char* key_file) {
337 if (chain_file != nullptr) tls_ctx_use_certificate_chain_file(chain_file);
338 if ( key_file != nullptr) tls_ctx_use_privatekey_file( key_file);
340 }; // -x- rsocket_sni* tls_ctx_use_certificate_chain_and_privatekey_files -x-
342 /*======================================================================*//**
344 Load a TLS certificate chain and private key in PEM format from text files
345 and use them in the TLS context.
347 @throws randolf::rex::xALL Catch this exception for now (at this time, the
348 OpenSSL library doesn't document which errors may be returned)
350 @returns The same rsocket_sni object so as to facilitate stacking
351 @see tls_ctx_use_certificate_chain_and_privatekey_pems
352 @see tls_ctx_use_certificate_chain_file
353 @see tls_ctx_use_certificate_chain_pem
354 @see tls_ctx_use_privatekey_file
355 @see tls_ctx_use_privatekey_pem
356 @see tls_ctx_check_privatekey
357 *///=========================================================================
358 rsocket_sni* tls_ctx_use_certificate_chain_and_privatekey_files(
359 /// Pointer to ASCIIZ path and filename to certificate chain file (an empty
360 /// string will simply be ignored)
361 const std::string chain_file,
362 /// Pointer to ASCIIZ path and filename to private key file (an empty string
363 /// will simply be ignored)
364 const std::string key_file) {
365 if (!chain_file.empty()) tls_ctx_use_certificate_chain_file(chain_file);
366 if ( !key_file.empty()) tls_ctx_use_privatekey_file( key_file);
368 }; // -x- rsocket_sni* tls_ctx_use_certificate_chain_and_privatekey_files -x-
370 /*======================================================================*//**
372 Load a TLS certificate chain and a TLS private key in PEM format from memory
373 and use them in the TLS context.
375 Although this functionality doesn't exist in OpenSSL (at the time of writing
376 this method), it's provided here in a manner that has exactly the same effect
377 as the @ref tls_ctx_use_certificate_chain_and_privatekey_files() methods, but
378 without needing the PEM-formatted certificate chain stored in files
382 The @c cert_pem_data and key_pem_data parameters are pointers to the memory
383 locations that holds the PEM formatted certificate chain data and private key
384 data, respectively. If the corresponding lengths of each of these data aren't
385 specified or are set to zero (default), then they will be treated as multiline
388 Behind the scenes, we're just writing the cert_pem_data and key_pem_data
389 memory to temporary files with severely-limited permissions (), then
390 optionally overwriting those temporary files with random data prior to
391 deleting them (this is the default, since better security practices should be
392 the default, but on a secured system it may not be necessary and so this
393 option can also be disabled to save CPU cycles and reduce overall disk-write
396 @throws randolf::rex::xALL Catch this exception for now (at this time, the
397 OpenSSL library doesn't document which errors may be returned)
399 @returns The same rsocket_sni object so as to facilitate stacking
400 @see tls_ctx_use_certificate_chain_and_privatekey_files
401 @see tls_ctx_use_certificate_chain_file
402 @see tls_ctx_use_certificate_chain_pem
403 @see tls_ctx_use_privatekey_file
404 @see tls_ctx_use_privatekey_pem
405 @see tls_ctx_check_privatekey
406 *///=========================================================================
407 rsocket_sni* tls_ctx_use_certificate_chain_and_privatekey_pems(
408 /// Pointer to certificate chain data in PEM format
409 const char* cert_pem_data,
410 /// Pointer to private key data in PEM format
411 const char* key_pem_data,
412 /// Length of cert_pem_data (in bytes), or 0 to auto-detect length if cert_pem_data is an ASCIIZ string
414 /// Length of key_pem_data (in bytes), or 0 to auto-detect length if key_pem_data is an ASCIIZ string
416 /// Whether to overwrite the temporary files with random data before deleting them
417 const bool random_fill = true) {
418 tls_ctx_use_certificate_chain_pem(cert_pem_data, cert_len, random_fill);
419 tls_ctx_use_privatekey_pem( key_pem_data, key_len, random_fill);
421 }; // -x- rsocket_sni* tls_ctx_use_certificate_chain_and_privatekey_pems -x-
423 /*======================================================================*//**
425 Load a TLS certificate chain in PEM format from a text file and use it in the
428 @throws randolf::rex::xALL Catch this exception for now (at this time, the
429 OpenSSL library doesn't document which errors may be returned)
431 @returns The same rsocket_sni object so as to facilitate stacking
432 @see tls_ctx_use_certificate_chain_pem
433 @see tls_ctx_check_privatekey
434 *///=========================================================================
435 rsocket_sni* tls_ctx_use_certificate_chain_file(
436 /// Pointer to ASCIIZ path and filename to certificate chain file
438 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);
440 }; // -x- rsocket_sni* tls_ctx_use_certificate_chain_file -x-
442 /*======================================================================*//**
444 Load a TLS certificate chain in PEM format from a text file and use it in the
447 @throws randolf::rex::xALL Catch this exception for now (at this time, the
448 OpenSSL library doesn't document which errors may be returned)
450 @returns The same rsocket_sni object so as to facilitate stacking
451 @see tls_ctx_use_certificate_chain_pem
452 @see tls_ctx_check_privatekey
453 *///=========================================================================
454 rsocket_sni* tls_ctx_use_certificate_chain_file(
455 /// Path and filename to certificate chain file
456 const std::string file) {
457 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);
459 }; // -x- rsocket_sni* tls_ctx_use_certificate_chain_file -x-
461 /*======================================================================*//**
463 Load a TLS certificate chain in PEM format from memory and use it in the TLS
466 Although this functionality doesn't exist in OpenSSL (at the time of writing
467 this method), it's provided here in a manner that has exactly the same effect
468 as the @ref tls_ctx_use_certificate_chain_file() methods, but without needing
469 the PEM-formatted certificate chain stored in a file beforehand.
472 The @c pem_data parameter is a pointer to the memory location that holds
473 the PEM formatted certificate chain data. If the length of this data isn't
474 specified or is set to zero (default), then it will be treated as a multiline
477 Behind the scenes, we're just writing the pem_data memory to a temporary
478 file with severely-limited permissions (), then optionally overwriting that
479 temporary file with random data prior to deleting it (this is the default,
480 since better security practices should be the default, but on a secured
481 system it may not be necessary and so this option can also be disabled to
482 save CPU cycles and reduce overall disk-write I/O operations).
484 @throws randolf::rex::xALL Catch this exception for now (at this time, the
485 OpenSSL library doesn't document which errors may be returned)
487 @returns The same rsocket_sni object so as to facilitate stacking
488 @see tls_ctx_use_certificate_chain_file
489 @see tls_ctx_check_privatekey
490 *///=========================================================================
491 rsocket_sni* tls_ctx_use_certificate_chain_pem(
492 /// Pointer to certificate chain data in PEM format
493 const char* pem_data,
494 /// Length of pem_data (in bytes), or 0 to auto-detect length if pem_data is an ASCIIZ string
496 /// Whether to overwrite the temporary file with random data before deleting it
497 const bool random_fill = true) {
499 // --------------------------------------------------------------------------
500 // Measure size of certificate chain if an ASCIIZ string was indicated.
501 // --------------------------------------------------------------------------
502 if (len == 0) len = std::strlen(pem_data);
504 // --------------------------------------------------------------------------
505 // Generate filename for temporary use.
506 // --------------------------------------------------------------------------
507 std::string file = std::filesystem::temp_directory_path();
508 file.append("/rsocket_sni.")
509 .append(std::to_string(::getpid()))
511 .append(std::to_string(__fn_counter.fetch_add(1, std::memory_order_relaxed)));
513 // --------------------------------------------------------------------------
514 // Open temporary file.
515 // --------------------------------------------------------------------------
516 FILE* fp = fopen(file.c_str(), "w+");
517 if (fp == nullptr) randolf::rex::mk_exception("Cannot open temporary file (to use certificate chain) " + file, 0, rex::rex::REX_FLAGS::REX_FIND_ERRNO);
519 ioctl(fileno(fp), FS_IOC_GETFLAGS, &attr);
520 attr |= FS_NOATIME_FL // Don't update access time attribute
521 | FS_NODUMP_FL // Don't include in filesystem backup dumps
522 | FS_SECRM_FL; // Mark file for secure deletion (where supported)
523 ioctl(fileno(fp), FS_IOC_SETFLAGS, &attr);
525 fchmod(fileno(fp), S_IRUSR | S_IWUSR);
526 if (fputs(pem_data, fp) == EOF) {
528 randolf::rex::mk_exception("Cannot write to temporary file (to use certificate chain) " + file, 0, rex::rex::REX_FLAGS::REX_FIND_ERRNO);
529 } // -x- if !fputs -x-
532 // --------------------------------------------------------------------------
533 // Attempt to load certificate chain file, but save the error code for later
534 // because we need to clean up the temporary file before possibly throwing an
536 // --------------------------------------------------------------------------
537 int rc = SSL_CTX_use_certificate_chain_file(__tls_ctx, file.c_str());
539 // --------------------------------------------------------------------------
540 // Overwrite the contenst of the temporary file before deleting it so as to
541 // sabotage a simple attempt to undelete the file and access the certificate.
543 // We're also re-using the "len" local variable because it's not needed once
544 // we get the loop started, and it's more optimal to not allocate yet another
545 // local variable while "len" goes to waste. :D
546 // --------------------------------------------------------------------------
547 if (random_fill) { // This option is configurable
548 fseek(fp, 0, SEEK_SET); // File file position pointer to the beginning
549 ::srand(::time(0) + __fn_counter); // Current time + atomic __fn_counter value helps add variety
550 for (int i = len / sizeof(int) + (::rand() & 127); i >= 0; i--) { // Fill contents of file with random data
551 len = ::rand() + (len >> (sizeof(int) >> 2)); // Bias random data toward containing more set bits (more 1's)
552 fwrite(&len, sizeof(int), 1, fp); // Write one integer at a time
554 } // -x- if randfill -x-
555 fchmod(fileno(fp), 0); // Remove all permissions
556 fclose(fp); // Close file handle
557 unlink(file.c_str()); // Delete temporary file
559 // --------------------------------------------------------------------------
560 // Error check ... was delayed here until after temporary file cleanup.
561 // --------------------------------------------------------------------------
562 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);
565 }; // -x- rsocket_sni* tls_ctx_use_certificate_chain_pem -x-
567 /*======================================================================*//**
569 Load a TLS private key in PEM format from a text file and use it in the TLS
572 @throws randolf::rex::xALL Catch this exception for now (at this time, the
573 OpenSSL library doesn't document which errors may be returned)
575 @returns The same rsocket_sni object so as to facilitate stacking
576 @see tls_ctx_use_privatekey_pem
577 @see tls_ctx_check_privatekey
578 *///=========================================================================
579 rsocket_sni* tls_ctx_use_privatekey_file(
580 /// Pointer to ASCIIZ path and filename to private key file
582 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);
585 }; // -x- rsocket_sni* tls_ctx_use_privatekey_file -x-
587 /*======================================================================*//**
589 Load a TLS private key in PEM format from a text file and use it in the TLS
592 @throws randolf::rex::xALL Catch this exception for now (at this time, the
593 OpenSSL library doesn't document which errors may be returned)
595 @returns The same rsocket_sni object so as to facilitate stacking
596 @see tls_ctx_use_privatekey_pem
597 @see tls_ctx_check_privatekey
598 *///=========================================================================
599 rsocket_sni* tls_ctx_use_privatekey_file(
600 /// Path and filename to private key file
601 const std::string file) {
602 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);
605 }; // -x- rsocket_sni* tls_ctx_use_privatekey_file -x-
607 /*======================================================================*//**
609 Load a TLS private key in PEM format from memory and use it in the TLS
612 Although this functionality doesn't exist in OpenSSL (at the time of writing
613 this method), it's provided here in a manner that has exactly the same effect
614 as the @ref tls_ctx_use_privatekey_file() methods, but without needing the
615 PEM-formatted private key stored in a file beforehand.
618 The @c pem_data parameter is a pointer to the memory location that holds the
619 PEM formatted private key data. If the length of this data isn't specified
620 or is set to zero (default), then it will be treated as a multiline ASCIIZ
623 Behind the scenes, we're just writing the pem_data memory to a temporary
624 file (with severely-limited permissions), then optionally overwriting that
625 temporary file with random data prior to deleting it (this is the default,
626 since better security practices should be the default, but on a secured
627 system it may not be necessary and so this option can also be disabled to
628 save CPU cycles and reduce overall disk-write I/O operations).
630 @throws randolf::rex::xALL Catch this exception for now (at this time, the
631 OpenSSL library doesn't document which errors may be returned)
633 @returns The same rsocket_sni object so as to facilitate stacking
634 @see tls_ctx_use_privatekey_file
635 @see tls_ctx_check_privatekey
636 *///=========================================================================
637 rsocket_sni* tls_ctx_use_privatekey_pem(
638 /// Pointer to private key data in PEM format
639 const char* pem_data,
640 /// Length of pem_data (in bytes), or 0 to auto-detect length if pem_data is an ASCIIZ string
642 /// Whether to overwrite the temporary file with random data before deleting it
643 const bool random_fill = true) {
645 // --------------------------------------------------------------------------
646 // Measure size of private key if an ASCIIZ string was indicated.
647 // --------------------------------------------------------------------------
648 if (len == 0) len = std::strlen(pem_data);
650 // --------------------------------------------------------------------------
651 // Generate filename for temporary use.
652 // --------------------------------------------------------------------------
653 std::string file = std::filesystem::temp_directory_path();
654 file.append("/rsocket_sni.")
655 .append(std::to_string(::getpid()))
657 .append(std::to_string(__fn_counter.fetch_add(1, std::memory_order_relaxed)));
659 // --------------------------------------------------------------------------
660 // Open temporary file.
661 // --------------------------------------------------------------------------
662 FILE* fp = fopen(file.c_str(), "w+");
663 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);
664 fchmod(fileno(fp), S_IRUSR | S_IWUSR);
665 if (fputs(pem_data, fp) == EOF) {
667 randolf::rex::mk_exception("Cannot cannot write to temporary file (to use private key) " + file, 0, rex::rex::REX_FLAGS::REX_FIND_ERRNO);
668 } // -x- if !fputs -x-
671 // --------------------------------------------------------------------------
672 // Attempt to load private key file, but save the error code for later
673 // because we need to clean up the temporary file before possibly throwing an
675 // --------------------------------------------------------------------------
676 int rc = SSL_CTX_use_PrivateKey_file(__tls_ctx, file.c_str(), SSL_FILETYPE_PEM);
678 // --------------------------------------------------------------------------
679 // Overwrite the contenst of the temporary file before deleting it so as to
680 // sabotage a simple attempt to undelete the file and access the certificate.
682 // We're also re-using the "len" local variable because it's not needed once
683 // we get the loop started, and it's more optimal to not allocate yet another
684 // local variable while "len" goes to waste. :D
685 // --------------------------------------------------------------------------
686 if (random_fill) { // This option is configurable
687 fseek(fp, 0, SEEK_SET); // File file position pointer to the beginning
688 ::srand(::time(0) + __fn_counter); // Current time + atomic __fn_counter value helps add variety
689 for (int i = len / sizeof(int) + (::rand() & 127); i >= 0; i--) { // Fill contents of file with random data
690 len = ::rand() + (len >> (sizeof(int) >> 2)); // Bias random data toward containing more set bits (more 1's)
691 fwrite(&len, sizeof(int), 1, fp); // Write one integer at a time
693 } // -x- if randfill -x-
694 fchmod(fileno(fp), 0); // Remove all permissions
695 fclose(fp); // Close file handle
696 unlink(file.c_str()); // Delete temporary file
698 // --------------------------------------------------------------------------
699 // Error check ... was delayed here until after temporary file cleanup.
700 // --------------------------------------------------------------------------
701 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);
704 }; // -x- rsocket_sni* tls_ctx_use_privatekey_pem -x-
706 }; // -x- class rsocket_sni -x-
708}; // -x- namespace randolf -x-