randolf.ca  1.00
Randolf Richardson's C++ classes
Loading...
Searching...
No Matches
rsocket_sni
1#pragma once
2
3#include <randolf/rex>
4
5#include <atomic>
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
10
11#include <unistd.h>
12
13#include <linux/fs.h> // Flags for ioctl()
14
15#include <openssl/err.h>
16#include <openssl/ossl_typ.h>
17#include <openssl/ssl.h>
18#include <openssl/x509v3.h>
19
20#include <sys/ioctl.h>
21#include <sys/stat.h> // fchmod()
22
23namespace randolf {
24
25 /*======================================================================*//**
26 @brief
27 The rsocket_sni class provides a specialized interface for collecting a group
28 of SNI (Server Name Identifier) contexts for TLS connections.
29
30 @note
31 There's no maximum limit on the number of SNI entries that can be added
32 (aside from system memory limits, of course).
33
34 @author Randolf Richardson
35 @version 1.00
36 @par History
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 *///=========================================================================
42 class rsocket_sni {
43
44 private:
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
52
53 public:
54 /*======================================================================*//**
55 @brief
56 Instantiate a new rsocket_sni object.
57 @see tls_ctx()
58 *///=========================================================================
59 rsocket_sni(
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-
69
70 /*======================================================================*//**
71 @brief
72 Destructor for rsocket_sni object, which deletes all SNI hostnames and their
73 associated TLS contexts, updating OpenSSL's internal TLS context reference
74 counters accordingly.
75 @see del
76 *///=========================================================================
77 ~rsocket_sni() noexcept {
78
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);
84
85 }; // -x- constructor rsocket_sni -x-
86
87 /*======================================================================*//**
88 @brief
89 Add one new hostname, which will be associated with the current TLS context.
90 SNI wildcards are supported (e.g., `*.example.com`).
91 @note
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
95 @see del
96 @see get_ctx
97 @see tls_ctx
98 *///=========================================================================
99 rsocket_sni* add(
100 /// Hostname to add
101 std::string hostname) {
102
103 // --------------------------------------------------------------------------
104 // Internal variables.
105 // --------------------------------------------------------------------------
106 bool w = hostname.find("*") != std::string::npos;
107
108 // --------------------------------------------------------------------------
109 // Replace hostname if it already exists and the TLS context is different,
110 // then update OpenSSL's internal reference counters accordingly.
111 // --------------------------------------------------------------------------
112 try {
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) {}
119
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
125
126 return this;
127 }; // -x- rsocket_sni* add -x-
128
129 /*======================================================================*//**
130 @brief
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`).
133 @note
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
137 @see del
138 @see get_ctx
139 @see tls_ctx
140 *///=========================================================================
141 template<class... Hs> rsocket_sni* add(
142 /// First hostname (required)
143 std::string hostname,
144 /// Additional hostnames (optional)
145 Hs... hostnames) {
146
147 // --------------------------------------------------------------------------
148 // Recursively loop through the variable arguments.
149 // --------------------------------------------------------------------------
150 add(hostname);
151 add(hostnames...);
152
153 return this;
154 }; // -x- rsocket_sni* add -x-
155
156 /*======================================================================*//**
157 @brief
158 Delete one hostname.
159 @note
160 If the specified hostname doesn't exist, nothing will happen.
161 @returns The same rsocket_sni object so as to facilitate stacking
162 @see add
163 *///=========================================================================
164 rsocket_sni* del(
165 /// Hostname to delete
166 std::string hostname) {
167
168 // --------------------------------------------------------------------------
169 // Internal variables.
170 // --------------------------------------------------------------------------
171 bool w = hostname.find("*") != std::string::npos; // Used to determine which map to delete from
172
173 // --------------------------------------------------------------------------
174 // If hostname exists, delete it and update OpenSSL's internal reference
175 // counters accordingly.
176 // --------------------------------------------------------------------------
177 try {
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) {}
182
183 return this;
184 }; // -x- rsocket_sni* del -x-
185
186 /*======================================================================*//**
187 @brief
188 Delete one or more hostnames.
189 @note
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
192 @see add
193 *///=========================================================================
194 template<class... Hs> rsocket_sni* del(
195 /// First hostname (required)
196 std::string hostname,
197 /// Additional hostnames (optional)
198 Hs... hostnames) {
199
200 // --------------------------------------------------------------------------
201 // Recursively loop through the variable arguments.
202 // --------------------------------------------------------------------------
203 del(hostname);
204 del(hostnames...);
205
206 return this;
207 }; // -x- rsocket_sni* del -x-
208
209 /*======================================================================*//**
210 @brief
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).
214
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.)
219
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
224 @see add
225 *///=========================================================================
226 SSL_CTX* get_ctx(
227 /// Hostname
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) {
236
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 // --------------------------------------------------------------------------
241 try {
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
245 }
246
247 // --------------------------------------------------------------------------
248 // Find wildcard match -- this takes longer, which is why we don't do this
249 // check first.
250 //
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-
263
264 return default_tls_ctx;
265 }; // -x- SSL_CTX* get_ctx -x-
266
267 /*======================================================================*//**
268 @brief
269 Find out what the current TLS context is set to.
270 @returns TLS context
271 *///=========================================================================
272 SSL_CTX* tls_ctx() noexcept {
273 return __tls_ctx;
274 }; // -x- SSL_CTX* tls_ctx -x-
275
276 /*======================================================================*//**
277 @brief
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})
284 SSL_CTX* ctx,
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) {
290
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);
297
298 return this;
299 }; // -x- rsocket_sni* tls_ctx -x-
300
301 /*======================================================================*//**
302 @brief
303 Check the private key it to ensure it's consistent with the corresponding TLS
304 certificate chain.
305
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)
308
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);
315 return this;
316 }; // -x- rsocket_sni* tls_ctx_check_privatekey -x-
317
318 /*======================================================================*//**
319 @brief
320 Load a TLS certificate chain and private key in PEM format from text files
321 and use them in the TLS context.
322
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)
325
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);
343 return this;
344 }; // -x- rsocket_sni* tls_ctx_use_certificate_chain_and_privatekey_files -x-
345
346 /*======================================================================*//**
347 @brief
348 Load a TLS certificate chain and private key in PEM format from text files
349 and use them in the TLS context.
350
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)
353
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);
371 return this;
372 }; // -x- rsocket_sni* tls_ctx_use_certificate_chain_and_privatekey_files -x-
373
374 /*======================================================================*//**
375 @brief
376 Load a TLS certificate chain and a TLS private key in PEM format from memory
377 and use them in the TLS context.
378
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
383 beforehand.
384
385 @note
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
390 ASCIIZ strings.
391
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
398 I/O operations).
399
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)
402
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
417 size_t cert_len = 0,
418 /// Length of key_pem_data (in bytes), or 0 to auto-detect length if key_pem_data is an ASCIIZ string
419 size_t key_len = 0,
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);
424 return this;
425 }; // -x- rsocket_sni* tls_ctx_use_certificate_chain_and_privatekey_pems -x-
426
427 /*======================================================================*//**
428 @brief
429 Load a TLS certificate chain in PEM format from a text file and use it in the
430 TLS context.
431
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)
434
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
441 const char* 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);
443 return this;
444 }; // -x- rsocket_sni* tls_ctx_use_certificate_chain_file -x-
445
446 /*======================================================================*//**
447 @brief
448 Load a TLS certificate chain in PEM format from a text file and use it in the
449 TLS context.
450
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)
453
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);
462 return this;
463 }; // -x- rsocket_sni* tls_ctx_use_certificate_chain_file -x-
464
465 /*======================================================================*//**
466 @brief
467 Load a TLS certificate chain in PEM format from memory and use it in the TLS
468 context.
469
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.
474
475 @note
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
479 ASCIIZ string.
480
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).
487
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)
490
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
499 size_t len = 0,
500 /// Whether to overwrite the temporary file with random data before deleting it
501 const bool random_fill = true) {
502
503 // --------------------------------------------------------------------------
504 // Measure size of certificate chain if an ASCIIZ string was indicated.
505 // --------------------------------------------------------------------------
506 if (len == 0) len = std::strlen(pem_data);
507
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()))
514 .append(".")
515 .append(std::to_string(__fn_counter.fetch_add(1, std::memory_order_relaxed)));
516
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);
522 { int attr;
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);
528 }
529 fchmod(fileno(fp), S_IRUSR | S_IWUSR);
530 if (fputs(pem_data, fp) == EOF) {
531 fclose(fp);
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-
534 fflush(fp);
535
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
539 // exception.
540 // --------------------------------------------------------------------------
541 int rc = SSL_CTX_use_certificate_chain_file(__tls_ctx, file.c_str());
542
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.
546 //
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
557 } // -x- for i -x-
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
562
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);
567
568 return this;
569 }; // -x- rsocket_sni* tls_ctx_use_certificate_chain_pem -x-
570
571 /*======================================================================*//**
572 @brief
573 Load a TLS private key in PEM format from a text file and use it in the TLS
574 context.
575
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)
578
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
585 const char* 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);
587
588 return this;
589 }; // -x- rsocket_sni* tls_ctx_use_privatekey_file -x-
590
591 /*======================================================================*//**
592 @brief
593 Load a TLS private key in PEM format from a text file and use it in the TLS
594 context.
595
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)
598
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);
607
608 return this;
609 }; // -x- rsocket_sni* tls_ctx_use_privatekey_file -x-
610
611 /*======================================================================*//**
612 @brief
613 Load a TLS private key in PEM format from memory and use it in the TLS
614 context.
615
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.
620
621 @note
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
625 string.
626
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).
633
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)
636
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
645 size_t len = 0,
646 /// Whether to overwrite the temporary file with random data before deleting it
647 const bool random_fill = true) {
648
649 // --------------------------------------------------------------------------
650 // Measure size of private key if an ASCIIZ string was indicated.
651 // --------------------------------------------------------------------------
652 if (len == 0) len = std::strlen(pem_data);
653
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()))
660 .append(".")
661 .append(std::to_string(__fn_counter.fetch_add(1, std::memory_order_relaxed)));
662
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) {
670 fclose(fp);
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-
673 fflush(fp);
674
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
678 // exception.
679 // --------------------------------------------------------------------------
680 int rc = SSL_CTX_use_PrivateKey_file(__tls_ctx, file.c_str(), SSL_FILETYPE_PEM);
681
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.
685 //
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
696 } // -x- for i -x-
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
701
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);
706
707 return this;
708 }; // -x- rsocket_sni* tls_ctx_use_privatekey_pem -x-
709
710 }; // -x- class rsocket_sni -x-
711
712}; // -x- namespace randolf -x-