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 *///=========================================================================
39 class rsocket_sni {
40
41 private:
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
49
50 public:
51 /*======================================================================*//**
52 @brief
53 Instantiate a new rsocket_sni object.
54 @see tls_ctx()
55 *///=========================================================================
56 rsocket_sni(
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-
66
67 /*======================================================================*//**
68 @brief
69 Destructor for rsocket_sni object, which deletes all SNI hostnames and their
70 associated TLS contexts, updating OpenSSL's internal TLS context reference
71 counters accordingly.
72 @see del
73 *///=========================================================================
74 ~rsocket_sni() noexcept {
75
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);
81
82 }; // -x- constructor rsocket_sni -x-
83
84 /*======================================================================*//**
85 @brief
86 Add one new hostname, which will be associated with the current TLS context.
87 SNI wildcards are supported (e.g., `*.example.com`).
88 @note
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
92 @see del
93 @see get_ctx
94 @see tls_ctx
95 *///=========================================================================
96 rsocket_sni* add(
97 /// Hostname to add
98 std::string hostname) {
99
100 // --------------------------------------------------------------------------
101 // Internal variables.
102 // --------------------------------------------------------------------------
103 bool w = hostname.find("*") != std::string::npos;
104
105 // --------------------------------------------------------------------------
106 // Replace hostname if it already exists and the TLS context is different,
107 // then update OpenSSL's internal reference counters accordingly.
108 // --------------------------------------------------------------------------
109 try {
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) {}
116
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
122
123 return this;
124 }; // -x- rsocket_sni* add -x-
125
126 /*======================================================================*//**
127 @brief
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`).
130 @note
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
134 @see del
135 @see get_ctx
136 @see tls_ctx
137 *///=========================================================================
138 template<class... Hs> rsocket_sni* add(
139 /// First hostname (required)
140 std::string hostname,
141 /// Additional hostnames (optional)
142 Hs... hostnames) {
143
144 // --------------------------------------------------------------------------
145 // Recursively loop through the variable arguments.
146 // --------------------------------------------------------------------------
147 add(hostname);
148 add(hostnames...);
149
150 return this;
151 }; // -x- rsocket_sni* add -x-
152
153 /*======================================================================*//**
154 @brief
155 Delete one hostname.
156 @note
157 If the specified hostname doesn't exist, nothing will happen.
158 @returns The same rsocket_sni object so as to facilitate stacking
159 @see add
160 *///=========================================================================
161 rsocket_sni* del(
162 /// Hostname to delete
163 std::string hostname) {
164
165 // --------------------------------------------------------------------------
166 // Internal variables.
167 // --------------------------------------------------------------------------
168 bool w = hostname.find("*") != std::string::npos; // Used to determine which map to delete from
169
170 // --------------------------------------------------------------------------
171 // If hostname exists, delete it and update OpenSSL's internal reference
172 // counters accordingly.
173 // --------------------------------------------------------------------------
174 try {
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) {}
179
180 return this;
181 }; // -x- rsocket_sni* del -x-
182
183 /*======================================================================*//**
184 @brief
185 Delete one or more hostnames.
186 @note
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
189 @see add
190 *///=========================================================================
191 template<class... Hs> rsocket_sni* del(
192 /// First hostname (required)
193 std::string hostname,
194 /// Additional hostnames (optional)
195 Hs... hostnames) {
196
197 // --------------------------------------------------------------------------
198 // Recursively loop through the variable arguments.
199 // --------------------------------------------------------------------------
200 del(hostname);
201 del(hostnames...);
202
203 return this;
204 }; // -x- rsocket_sni* del -x-
205
206 /*======================================================================*//**
207 @brief
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).
211
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.)
216
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
221 @see add
222 *///=========================================================================
223 SSL_CTX* get_ctx(
224 /// Hostname
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) {
233
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 // --------------------------------------------------------------------------
238 try {
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
242 }
243
244 // --------------------------------------------------------------------------
245 // Find wildcard match -- this takes longer, which is why we started with the
246 // exact match first.
247 //
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-
259
260 return default_tls_ctx;
261 }; // -x- SSL_CTX* get_ctx -x-
262
263 /*======================================================================*//**
264 @brief
265 Find out what the current TLS context is set to.
266 @returns TLS context
267 *///=========================================================================
268 SSL_CTX* tls_ctx() noexcept {
269 return __tls_ctx;
270 }; // -x- SSL_CTX* tls_ctx -x-
271
272 /*======================================================================*//**
273 @brief
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})
280 SSL_CTX* ctx,
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) {
286
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);
293
294 return this;
295 }; // -x- rsocket_sni* tls_ctx -x-
296
297 /*======================================================================*//**
298 @brief
299 Check the private key it to ensure it's consistent with the corresponding TLS
300 certificate chain.
301
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)
304
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);
311 return this;
312 }; // -x- rsocket_sni* tls_ctx_check_privatekey -x-
313
314 /*======================================================================*//**
315 @brief
316 Load a TLS certificate chain and private key in PEM format from text files
317 and use them in the TLS context.
318
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)
321
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);
339 return this;
340 }; // -x- rsocket_sni* tls_ctx_use_certificate_chain_and_privatekey_files -x-
341
342 /*======================================================================*//**
343 @brief
344 Load a TLS certificate chain and private key in PEM format from text files
345 and use them in the TLS context.
346
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)
349
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);
367 return this;
368 }; // -x- rsocket_sni* tls_ctx_use_certificate_chain_and_privatekey_files -x-
369
370 /*======================================================================*//**
371 @brief
372 Load a TLS certificate chain and a TLS private key in PEM format from memory
373 and use them in the TLS context.
374
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
379 beforehand.
380
381 @note
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
386 ASCIIZ strings.
387
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
394 I/O operations).
395
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)
398
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
413 size_t cert_len = 0,
414 /// Length of key_pem_data (in bytes), or 0 to auto-detect length if key_pem_data is an ASCIIZ string
415 size_t key_len = 0,
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);
420 return this;
421 }; // -x- rsocket_sni* tls_ctx_use_certificate_chain_and_privatekey_pems -x-
422
423 /*======================================================================*//**
424 @brief
425 Load a TLS certificate chain in PEM format from a text file and use it in the
426 TLS context.
427
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)
430
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
437 const char* 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);
439 return this;
440 }; // -x- rsocket_sni* tls_ctx_use_certificate_chain_file -x-
441
442 /*======================================================================*//**
443 @brief
444 Load a TLS certificate chain in PEM format from a text file and use it in the
445 TLS context.
446
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)
449
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);
458 return this;
459 }; // -x- rsocket_sni* tls_ctx_use_certificate_chain_file -x-
460
461 /*======================================================================*//**
462 @brief
463 Load a TLS certificate chain in PEM format from memory and use it in the TLS
464 context.
465
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.
470
471 @note
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
475 ASCIIZ string.
476
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).
483
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)
486
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
495 size_t len = 0,
496 /// Whether to overwrite the temporary file with random data before deleting it
497 const bool random_fill = true) {
498
499 // --------------------------------------------------------------------------
500 // Measure size of certificate chain if an ASCIIZ string was indicated.
501 // --------------------------------------------------------------------------
502 if (len == 0) len = std::strlen(pem_data);
503
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()))
510 .append(".")
511 .append(std::to_string(__fn_counter.fetch_add(1, std::memory_order_relaxed)));
512
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);
518 { int attr;
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);
524 }
525 fchmod(fileno(fp), S_IRUSR | S_IWUSR);
526 if (fputs(pem_data, fp) == EOF) {
527 fclose(fp);
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-
530 fflush(fp);
531
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
535 // exception.
536 // --------------------------------------------------------------------------
537 int rc = SSL_CTX_use_certificate_chain_file(__tls_ctx, file.c_str());
538
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.
542 //
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
553 } // -x- for i -x-
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
558
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);
563
564 return this;
565 }; // -x- rsocket_sni* tls_ctx_use_certificate_chain_pem -x-
566
567 /*======================================================================*//**
568 @brief
569 Load a TLS private key in PEM format from a text file and use it in the TLS
570 context.
571
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)
574
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
581 const char* 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);
583
584 return this;
585 }; // -x- rsocket_sni* tls_ctx_use_privatekey_file -x-
586
587 /*======================================================================*//**
588 @brief
589 Load a TLS private key in PEM format from a text file and use it in the TLS
590 context.
591
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)
594
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);
603
604 return this;
605 }; // -x- rsocket_sni* tls_ctx_use_privatekey_file -x-
606
607 /*======================================================================*//**
608 @brief
609 Load a TLS private key in PEM format from memory and use it in the TLS
610 context.
611
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.
616
617 @note
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
621 string.
622
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).
629
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)
632
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
641 size_t len = 0,
642 /// Whether to overwrite the temporary file with random data before deleting it
643 const bool random_fill = true) {
644
645 // --------------------------------------------------------------------------
646 // Measure size of private key if an ASCIIZ string was indicated.
647 // --------------------------------------------------------------------------
648 if (len == 0) len = std::strlen(pem_data);
649
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()))
656 .append(".")
657 .append(std::to_string(__fn_counter.fetch_add(1, std::memory_order_relaxed)));
658
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) {
666 fclose(fp);
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-
669 fflush(fp);
670
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
674 // exception.
675 // --------------------------------------------------------------------------
676 int rc = SSL_CTX_use_PrivateKey_file(__tls_ctx, file.c_str(), SSL_FILETYPE_PEM);
677
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.
681 //
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
692 } // -x- for i -x-
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
697
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);
702
703 return this;
704 }; // -x- rsocket_sni* tls_ctx_use_privatekey_pem -x-
705
706 }; // -x- class rsocket_sni -x-
707
708}; // -x- namespace randolf -x-