Stroika Library 3.0d16
 
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 && defined(_MSC_VER)
29// Use #pragma comment lib instead of explicit entry in the lib entry of the project file
30#if OPENSSL_VERSION_NUMBER < 0x1010000fL
31#pragma comment(lib, "libeay32.lib")
32#pragma comment(lib, "ssleay32.lib")
33#else
34#pragma comment(lib, "libcrypto.lib")
35#pragma comment(lib, "libssl.lib")
36#pragma comment(lib, "ws2_32.lib")
37#pragma comment(lib, "crypt32.lib")
38#endif
39#endif
40
41#if qStroika_HasComponent_OpenSSL
42
43namespace {
44 void AccumulateIntoSetOfCipherNames_ (const ::EVP_CIPHER* ciph, Set<String>* ciphers)
45 {
46 RequireNotNull (ciphers);
47 if (ciph != nullptr) {
48#if USE_NOISY_TRACE_IN_THIS_MODULE_
49#if OPENSSL_VERSION_MAJOR >= 3
50 DbgTrace ("cipher: {} (name: {}), provider: {} (name {})"_f, ciph, CipherAlgorithm{ciph}.name (), ::EVP_CIPHER_get0_provider (ciph),
51 (::EVP_CIPHER_get0_provider (ciph) == nullptr
52 ? L"null"
53 : String::FromNarrowSDKString (::OSSL_PROVIDER_get0_name (::EVP_CIPHER_get0_provider (ciph)))));
54#else
55 DbgTrace ("cipher: {} (name: {})"_f, ciph, CipherAlgorithm{ciph}.name ());
56#endif
57#endif
58 ciphers->Add (CipherAlgorithm{ciph}.name ());
59 }
60 };
61 void AccumulateIntoSetOfDigestNames_ (const ::EVP_MD* digest, Set<String>* digestNames)
62 {
63 RequireNotNull (digestNames);
64 if (digest != nullptr) {
65#if USE_NOISY_TRACE_IN_THIS_MODULE_
66#if OPENSSL_VERSION_MAJOR >= 3
67 DbgTrace ("digest: {} (name: {}), provider: {} (name {})"_f, digest, DigestAlgorithm{digest}.name (), ::EVP_MD_get0_provider (digest),
68 (::EVP_MD_get0_provider (digest) == nullptr
69 ? "null"_k
70 : String::FromNarrowSDKString (::OSSL_PROVIDER_get0_name (::EVP_MD_get0_provider (digest)))));
71#else
72 DbgTrace ("digest: {} (name: {})"_f, digest, DigestAlgorithm{digest}.name ());
73#endif
74#endif
75 digestNames->Add (DigestAlgorithm{digest}.name ());
76 }
77 };
78}
79
80/*
81 ********************************************************************************
82 ************* Cryptography::Providers::OpenSSL::LibraryContext::LibraryInit_ **************
83 ********************************************************************************
84 */
85LibraryContext::LibraryInit_::LibraryInit_ ()
86{
87 constexpr auto kOpts_ = OPENSSL_INIT_LOAD_SSL_STRINGS;
88 Verify (::OPENSSL_init_ssl (kOpts_, nullptr) == 1);
89}
90
91/*
92 ********************************************************************************
93 ******************* Cryptography::Providers::OpenSSL::LibraryContext **********************
94 ********************************************************************************
95 */
96LibraryContext::LibraryContext ()
97 : availableCipherAlgorithms{[qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] const auto* property) -> Set<CipherAlgorithm> {
98 const LibraryContext* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &LibraryContext::availableCipherAlgorithms);
99 AssertExternallySynchronizedMutex::ReadContext declareContext{thisObj->fThisAssertExternallySynchronized_};
100 Set<String> cipherNames;
101#if OPENSSL_VERSION_MAJOR >= 3
102 ::EVP_CIPHER_do_all_provided (
103 nullptr, [] (::EVP_CIPHER* ciph, void* arg) { AccumulateIntoSetOfCipherNames_ (ciph, reinterpret_cast<Set<String>*> (arg)); }, &cipherNames);
104#else
105 ::EVP_CIPHER_do_all_sorted ([] (const ::EVP_CIPHER* ciph, [[maybe_unused]] const char* from, [[maybe_unused]] const char* to,
106 void* arg) { AccumulateIntoSetOfCipherNames_ (ciph, reinterpret_cast<Set<String>*> (arg)); },
107 &cipherNames);
108#endif
109#if USE_NOISY_TRACE_IN_THIS_MODULE_
110 DbgTrace ("Found availableCipherAlgorithms-FIRST-PASS (cnt={}): {}"_f, cipherNames.size (), cipherNames);
111#endif
112
113 Set<CipherAlgorithm> results{cipherNames.Map<Set<CipherAlgorithm>> (
114 [] (const String& n) -> optional<CipherAlgorithm> { return OpenSSL::CipherAlgorithm::GetByNameQuietly (n); })};
115#if USE_NOISY_TRACE_IN_THIS_MODULE_
116 DbgTrace ("Found availableCipherAlgorithms (cnt={}): {}"_f, results.size (), results);
117#endif
118 return results;
119 }}
120 , standardCipherAlgorithms{[qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] const auto* property) -> Set<CipherAlgorithm> {
121 const LibraryContext* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &LibraryContext::standardCipherAlgorithms);
122 AssertExternallySynchronizedMutex::ReadContext declareContext{thisObj->fThisAssertExternallySynchronized_};
123 Set<CipherAlgorithm> results;
124
125 results += CipherAlgorithms::kAES_128_CBC;
126 results += CipherAlgorithms::kAES_128_ECB;
127 results += CipherAlgorithms::kAES_128_OFB;
128 results += CipherAlgorithms::kAES_128_CFB1;
129 results += CipherAlgorithms::kAES_128_CFB8;
130 results += CipherAlgorithms::kAES_128_CFB128;
131 results += CipherAlgorithms::kAES_192_CBC;
132 results += CipherAlgorithms::kAES_192_ECB;
133 results += CipherAlgorithms::kAES_192_OFB;
134 results += CipherAlgorithms::kAES_192_CFB1;
135 results += CipherAlgorithms::kAES_192_CFB8;
136 results += CipherAlgorithms::kAES_192_CFB128;
137 results += CipherAlgorithms::kAES_256_CBC;
138 results += CipherAlgorithms::kAES_256_ECB;
139 results += CipherAlgorithms::kAES_256_OFB;
140 results += CipherAlgorithms::kAES_256_CFB1;
141 results += CipherAlgorithms::kAES_256_CFB8;
142 results += CipherAlgorithms::kAES_256_CFB128;
143
144 /**
145 *
146 * @todo review openssl ciphers -s - and compare with above list - and cleanup - and maybe automate (find in driver source how it does
147 * ciphers -s...)
148 */
149 /*
150 * @todo mark these below as deprecated...??? in openssl3?
151 */
152#if OPENSSL_VERSION_MAJOR < 3
153 results += CipherAlgorithms::kBlowfish_CBC;
154 results += CipherAlgorithms::kBlowfish_ECB;
155 results += CipherAlgorithms::kBlowfish_CFB;
156 results += CipherAlgorithms::kBlowfish_OFB;
157 results += CipherAlgorithms::kBlowfish;
158 results += CipherAlgorithms::kRC2_CBC;
159 results += CipherAlgorithms::kRC2_ECB;
160 results += CipherAlgorithms::kRC2_CFB;
161 results += CipherAlgorithms::kRC2_OFB;
162 results += CipherAlgorithms::kRC4;
163#endif
164
165 return results;
166 }}
167 , availableDigestAlgorithms{[qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] const auto* property) -> Set<DigestAlgorithm> {
168 const LibraryContext* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &LibraryContext::availableDigestAlgorithms);
169 AssertExternallySynchronizedMutex::ReadContext declareContext{thisObj->fThisAssertExternallySynchronized_};
170
171 Set<String> digestNames;
172#if OPENSSL_VERSION_MAJOR >= 3
173 ::EVP_MD_do_all_provided (
174 nullptr, [] (::EVP_MD* md, void* arg) { AccumulateIntoSetOfDigestNames_ (md, reinterpret_cast<Set<String>*> (arg)); }, &digestNames);
175#else
176 ::EVP_MD_do_all_sorted ([] (const ::EVP_MD* md, [[maybe_unused]] const char* from, [[maybe_unused]] const char* to,
177 void* arg) { AccumulateIntoSetOfDigestNames_ (md, reinterpret_cast<Set<String>*> (arg)); },
178 &digestNames);
179#endif
180#if USE_NOISY_TRACE_IN_THIS_MODULE_
181 DbgTrace ("Found availableDigestAlgorithms-FIRST-PASS (cnt={}): {}"_f, digestNames.size (), digestNames);
182#endif
183
184 Set<DigestAlgorithm> results{digestNames.Map<Set<DigestAlgorithm>> (
185 [] (const String& n) -> optional<DigestAlgorithm> { return OpenSSL::DigestAlgorithm::GetByNameQuietly (n); })};
186#if USE_NOISY_TRACE_IN_THIS_MODULE_
187 DbgTrace ("Found availableDigestAlgorithms (cnt={}): {}"_f, results.size (), results);
188#endif
189 return results;
190 }}
191 , standardDigestAlgorithms{[qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] const auto* property) -> Set<DigestAlgorithm> {
192 const LibraryContext* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &LibraryContext::standardDigestAlgorithms);
193 AssertExternallySynchronizedMutex::ReadContext declareContext{thisObj->fThisAssertExternallySynchronized_};
194 Set<DigestAlgorithm> results;
195 results += DigestAlgorithms::kMD5;
196 results += DigestAlgorithms::kSHA1;
197 results += DigestAlgorithms::kSHA1_224;
198 results += DigestAlgorithms::kSHA1_256;
199 results += DigestAlgorithms::kSHA1_384;
200 results += DigestAlgorithms::kSHA1_512;
201 results += DigestAlgorithms::kSHA3_224;
202 results += DigestAlgorithms::kSHA3_256;
203 results += DigestAlgorithms::kSHA3_384;
204 results += DigestAlgorithms::kSHA3_512;
205 return results;
206 }}
207{
208 LoadProvider (kDefaultProvider);
209}
210
211LibraryContext ::~LibraryContext ()
212{
213 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fThisAssertExternallySynchronized_};
214#if OPENSSL_VERSION_MAJOR >= 3
215 // reference counts dont matter here, just unload all the providers we loaded
216 for (auto i : fLoadedProviders_.MappedValues ()) {
217 Verify (::OSSL_PROVIDER_unload (i) == 1);
218 }
219#endif
220}
221
222void LibraryContext::LoadProvider ([[maybe_unused]] const String& providerName)
223{
224 Debug::TraceContextBumper ctx{Stroika_Foundation_Debug_OptionalizeTraceArgs ("OpenSSL::LibraryContext::LoadProvider", "{}"_f, providerName)};
225 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fThisAssertExternallySynchronized_};
226#if OPENSSL_VERSION_MAJOR >= 3
227 auto p = fLoadedProviders_.LookupOneValue (providerName);
228 if (p == nullptr) {
229 // really load cuz not already loaded
230 DbgTrace ("calling OSSL_PROVIDER_load"_f);
231 p = ::OSSL_PROVIDER_load (nullptr, providerName.AsNarrowSDKString ().c_str ());
232 static const Execution::RuntimeErrorException kErr_{"No such SSL provider"sv};
233 Execution::ThrowIfNull (p, kErr_);
234 }
235 fLoadedProviders_.Add (providerName, p); // add association (perhaps redundantly)
236#else
237 Require (providerName == kDefaultProvider or providerName == kLegacyProvider);
238#endif
239}
240
241void LibraryContext ::UnLoadProvider ([[maybe_unused]] const String& providerName)
242{
243 Debug::TraceContextBumper ctx{Stroika_Foundation_Debug_OptionalizeTraceArgs ("OpenSSL::LibraryContext::UnLoadProvider", "{}"_f, providerName)};
244 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fThisAssertExternallySynchronized_};
245#if OPENSSL_VERSION_MAJOR >= 3
246 Require (fLoadedProviders_.ContainsKey (providerName));
247 auto providerToMaybeRemove = fLoadedProviders_.LookupOneValue (providerName);
248 fLoadedProviders_.Remove (providerName);
249 if (not fLoadedProviders_.ContainsKey (providerName)) {
250 DbgTrace ("calling OSSL_PROVIDER_unload"_f);
251 Verify (::OSSL_PROVIDER_unload (providerToMaybeRemove) == 1);
252 }
253#endif
254}
255#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...
void ThrowIfNull(const Private_::ConstVoidStar &p, const HRESULT &hr)
Template specialization for ThrowIfNull (), for thing being thrown HRESULT - really throw HRESULTErro...