Stroika Library 3.0d18
 
Loading...
Searching...
No Matches
LibraryContext.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
6#if qStroika_HasComponent_OpenSSL
7#include <openssl/evp.h>
8#include <openssl/ssl.h>
9#if OPENSSL_VERSION_MAJOR >= 3
10#include <openssl/provider.h>
11#endif
12#endif
13
15#include "Stroika/Foundation/Execution/Exceptions.h"
16
17#include "LibraryContext.h"
18
19using namespace Stroika::Foundation;
21using namespace Stroika::Foundation::Cryptography;
22using namespace Stroika::Foundation::Cryptography::Providers::OpenSSL;
23using namespace Stroika::Foundation::Debug;
24
25// Comment this in to turn on aggressive noisy DbgTrace in this module
26// #define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
27
28#if qStroika_HasComponent_OpenSSL
29
30namespace {
31 void AccumulateIntoSetOfCipherNames_ (const ::EVP_CIPHER* ciph, Set<String>* ciphers)
32 {
33 RequireNotNull (ciphers);
34 if (ciph != nullptr) {
35#if USE_NOISY_TRACE_IN_THIS_MODULE_
36#if OPENSSL_VERSION_MAJOR >= 3
37 DbgTrace ("cipher: {} (name: {}), provider: {} (name {})"_f, ciph, CipherAlgorithm{ciph}.name (), ::EVP_CIPHER_get0_provider (ciph),
38 (::EVP_CIPHER_get0_provider (ciph) == nullptr
39 ? L"null"
40 : String::FromNarrowSDKString (::OSSL_PROVIDER_get0_name (::EVP_CIPHER_get0_provider (ciph)))));
41#else
42 DbgTrace ("cipher: {} (name: {})"_f, ciph, CipherAlgorithm{ciph}.name ());
43#endif
44#endif
45 ciphers->Add (CipherAlgorithm{ciph}.name ());
46 }
47 };
48 void AccumulateIntoSetOfDigestNames_ (const ::EVP_MD* digest, Set<String>* digestNames)
49 {
50 RequireNotNull (digestNames);
51 if (digest != nullptr) {
52#if USE_NOISY_TRACE_IN_THIS_MODULE_
53#if OPENSSL_VERSION_MAJOR >= 3
54 DbgTrace ("digest: {} (name: {}), provider: {} (name {})"_f, digest, DigestAlgorithm{digest}.name (), ::EVP_MD_get0_provider (digest),
55 (::EVP_MD_get0_provider (digest) == nullptr
56 ? "null"_k
57 : String::FromNarrowSDKString (::OSSL_PROVIDER_get0_name (::EVP_MD_get0_provider (digest)))));
58#else
59 DbgTrace ("digest: {} (name: {})"_f, digest, DigestAlgorithm{digest}.name ());
60#endif
61#endif
62 digestNames->Add (DigestAlgorithm{digest}.name ());
63 }
64 };
65}
66
67/*
68 ********************************************************************************
69 ************* Cryptography::Providers::OpenSSL::LibraryContext::LibraryInit_ **************
70 ********************************************************************************
71 */
72LibraryContext::LibraryInit_::LibraryInit_ ()
73{
74 constexpr auto kOpts_ = OPENSSL_INIT_LOAD_SSL_STRINGS;
75 Verify (::OPENSSL_init_ssl (kOpts_, nullptr) == 1);
76}
77
78/*
79 ********************************************************************************
80 ******************* Cryptography::Providers::OpenSSL::LibraryContext **********************
81 ********************************************************************************
82 */
83LibraryContext::LibraryContext ()
84 : availableCipherAlgorithms{[qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] const auto* property) -> Set<CipherAlgorithm> {
85 const LibraryContext* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &LibraryContext::availableCipherAlgorithms);
86 AssertExternallySynchronizedMutex::ReadContext declareContext{thisObj->fThisAssertExternallySynchronized_};
87 Set<String> cipherNames;
88#if OPENSSL_VERSION_MAJOR >= 3
89 ::EVP_CIPHER_do_all_provided (
90 nullptr, [] (::EVP_CIPHER* ciph, void* arg) { AccumulateIntoSetOfCipherNames_ (ciph, reinterpret_cast<Set<String>*> (arg)); }, &cipherNames);
91#else
92 ::EVP_CIPHER_do_all_sorted ([] (const ::EVP_CIPHER* ciph, [[maybe_unused]] const char* from, [[maybe_unused]] const char* to,
93 void* arg) { AccumulateIntoSetOfCipherNames_ (ciph, reinterpret_cast<Set<String>*> (arg)); },
94 &cipherNames);
95#endif
96#if USE_NOISY_TRACE_IN_THIS_MODULE_
97 DbgTrace ("Found availableCipherAlgorithms-FIRST-PASS (cnt={}): {}"_f, cipherNames.size (), cipherNames);
98#endif
99
100 Set<CipherAlgorithm> results{cipherNames.Map<Set<CipherAlgorithm>> (
101 [] (const String& n) -> optional<CipherAlgorithm> { return OpenSSL::CipherAlgorithm::GetByNameQuietly (n); })};
102#if USE_NOISY_TRACE_IN_THIS_MODULE_
103 DbgTrace ("Found availableCipherAlgorithms (cnt={}): {}"_f, results.size (), results);
104#endif
105 return results;
106 }}
107 , standardCipherAlgorithms{[qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] const auto* property) -> Set<CipherAlgorithm> {
108 const LibraryContext* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &LibraryContext::standardCipherAlgorithms);
109 AssertExternallySynchronizedMutex::ReadContext declareContext{thisObj->fThisAssertExternallySynchronized_};
110 Set<CipherAlgorithm> results;
111
112 results += CipherAlgorithms::kAES_128_CBC;
113 results += CipherAlgorithms::kAES_128_ECB;
114 results += CipherAlgorithms::kAES_128_OFB;
115 results += CipherAlgorithms::kAES_128_CFB1;
116 results += CipherAlgorithms::kAES_128_CFB8;
117 results += CipherAlgorithms::kAES_128_CFB128;
118 results += CipherAlgorithms::kAES_192_CBC;
119 results += CipherAlgorithms::kAES_192_ECB;
120 results += CipherAlgorithms::kAES_192_OFB;
121 results += CipherAlgorithms::kAES_192_CFB1;
122 results += CipherAlgorithms::kAES_192_CFB8;
123 results += CipherAlgorithms::kAES_192_CFB128;
124 results += CipherAlgorithms::kAES_256_CBC;
125 results += CipherAlgorithms::kAES_256_ECB;
126 results += CipherAlgorithms::kAES_256_OFB;
127 results += CipherAlgorithms::kAES_256_CFB1;
128 results += CipherAlgorithms::kAES_256_CFB8;
129 results += CipherAlgorithms::kAES_256_CFB128;
130
131 /**
132 *
133 * @todo review openssl ciphers -s - and compare with above list - and cleanup - and maybe automate (find in driver source how it does
134 * ciphers -s...)
135 */
136 /*
137 * @todo mark these below as deprecated...??? in openssl3?
138 */
139#if OPENSSL_VERSION_MAJOR < 3
140 results += CipherAlgorithms::kBlowfish_CBC;
141 results += CipherAlgorithms::kBlowfish_ECB;
142 results += CipherAlgorithms::kBlowfish_CFB;
143 results += CipherAlgorithms::kBlowfish_OFB;
144 results += CipherAlgorithms::kBlowfish;
145 results += CipherAlgorithms::kRC2_CBC;
146 results += CipherAlgorithms::kRC2_ECB;
147 results += CipherAlgorithms::kRC2_CFB;
148 results += CipherAlgorithms::kRC2_OFB;
149 results += CipherAlgorithms::kRC4;
150#endif
151
152 return results;
153 }}
154 , availableDigestAlgorithms{[qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] const auto* property) -> Set<DigestAlgorithm> {
155 const LibraryContext* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &LibraryContext::availableDigestAlgorithms);
156 AssertExternallySynchronizedMutex::ReadContext declareContext{thisObj->fThisAssertExternallySynchronized_};
157
158 Set<String> digestNames;
159#if OPENSSL_VERSION_MAJOR >= 3
160 ::EVP_MD_do_all_provided (
161 nullptr, [] (::EVP_MD* md, void* arg) { AccumulateIntoSetOfDigestNames_ (md, reinterpret_cast<Set<String>*> (arg)); }, &digestNames);
162#else
163 ::EVP_MD_do_all_sorted ([] (const ::EVP_MD* md, [[maybe_unused]] const char* from, [[maybe_unused]] const char* to,
164 void* arg) { AccumulateIntoSetOfDigestNames_ (md, reinterpret_cast<Set<String>*> (arg)); },
165 &digestNames);
166#endif
167#if USE_NOISY_TRACE_IN_THIS_MODULE_
168 DbgTrace ("Found availableDigestAlgorithms-FIRST-PASS (cnt={}): {}"_f, digestNames.size (), digestNames);
169#endif
170
171 Set<DigestAlgorithm> results{digestNames.Map<Set<DigestAlgorithm>> (
172 [] (const String& n) -> optional<DigestAlgorithm> { return OpenSSL::DigestAlgorithm::GetByNameQuietly (n); })};
173#if USE_NOISY_TRACE_IN_THIS_MODULE_
174 DbgTrace ("Found availableDigestAlgorithms (cnt={}): {}"_f, results.size (), results);
175#endif
176 return results;
177 }}
178 , standardDigestAlgorithms{[qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] const auto* property) -> Set<DigestAlgorithm> {
179 const LibraryContext* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &LibraryContext::standardDigestAlgorithms);
180 AssertExternallySynchronizedMutex::ReadContext declareContext{thisObj->fThisAssertExternallySynchronized_};
181 Set<DigestAlgorithm> results;
182 results += DigestAlgorithms::kMD5;
183 results += DigestAlgorithms::kSHA1;
184 results += DigestAlgorithms::kSHA1_224;
185 results += DigestAlgorithms::kSHA1_256;
186 results += DigestAlgorithms::kSHA1_384;
187 results += DigestAlgorithms::kSHA1_512;
188 results += DigestAlgorithms::kSHA3_224;
189 results += DigestAlgorithms::kSHA3_256;
190 results += DigestAlgorithms::kSHA3_384;
191 results += DigestAlgorithms::kSHA3_512;
192 return results;
193 }}
194{
195 LoadProvider (kDefaultProvider);
196}
197
198LibraryContext ::~LibraryContext ()
199{
200 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fThisAssertExternallySynchronized_};
201#if OPENSSL_VERSION_MAJOR >= 3
202 // reference counts dont matter here, just unload all the providers we loaded
203 for (auto i : fLoadedProviders_.MappedValues ()) {
204 Verify (::OSSL_PROVIDER_unload (i) == 1);
205 }
206#endif
207}
208
209void LibraryContext::LoadProvider ([[maybe_unused]] const String& providerName)
210{
211 Debug::TraceContextBumper ctx{Stroika_Foundation_Debug_OptionalizeTraceArgs ("OpenSSL::LibraryContext::LoadProvider", "{}"_f, providerName)};
212 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fThisAssertExternallySynchronized_};
213#if OPENSSL_VERSION_MAJOR >= 3
214 auto p = fLoadedProviders_.LookupOneValue (providerName);
215 if (p == nullptr) {
216 // really load cuz not already loaded
217 DbgTrace ("calling OSSL_PROVIDER_load"_f);
218 p = ::OSSL_PROVIDER_load (nullptr, providerName.AsNarrowSDKString ().c_str ());
219 static const Execution::RuntimeErrorException kErr_{"No such SSL provider"sv};
220 Execution::ThrowIfNull (p, kErr_);
221 }
222 fLoadedProviders_.Add (providerName, p); // add association (perhaps redundantly)
223#else
224 Require (providerName == kDefaultProvider or providerName == kLegacyProvider);
225#endif
226}
227
228void LibraryContext ::UnLoadProvider ([[maybe_unused]] const String& providerName)
229{
230 Debug::TraceContextBumper ctx{Stroika_Foundation_Debug_OptionalizeTraceArgs ("OpenSSL::LibraryContext::UnLoadProvider", "{}"_f, providerName)};
231 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fThisAssertExternallySynchronized_};
232#if OPENSSL_VERSION_MAJOR >= 3
233 Require (fLoadedProviders_.ContainsKey (providerName));
234 auto providerToMaybeRemove = fLoadedProviders_.LookupOneValue (providerName);
235 fLoadedProviders_.Remove (providerName);
236 if (not fLoadedProviders_.ContainsKey (providerName)) {
237 DbgTrace ("calling OSSL_PROVIDER_unload"_f);
238 Verify (::OSSL_PROVIDER_unload (providerToMaybeRemove) == 1);
239 }
240#endif
241}
242#endif
#define RequireNotNull(p)
Definition Assertions.h:347
#define Verify(c)
Definition Assertions.h:419
#define DbgTrace
Definition Trace.h:309
#define Stroika_Foundation_Debug_OptionalizeTraceArgs(...)
Definition Trace.h:270
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
shared_lock< const AssertExternallySynchronizedMutex > ReadContext
Instantiate AssertExternallySynchronizedMutex::ReadContext to designate an area of code where protect...
unique_lock< AssertExternallySynchronizedMutex > WriteContext
Instantiate AssertExternallySynchronizedMutex::WriteContext to designate an area of code where protec...