Stroika Library 3.0d18
 
Loading...
Searching...
No Matches
OpenSSLCryptoStream.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/err.h>
8#include <openssl/evp.h>
9#endif
10
11#include "Stroika/Foundation/Containers/Common.h"
13#include "Stroika/Foundation/Execution/Common.h"
15#include "Stroika/Foundation/Streams/InternallySynchronizedInputStream.h"
16#include "Stroika/Foundation/Streams/InternallySynchronizedOutputStream.h"
17
18#include "OpenSSLCryptoStream.h"
19
20using std::byte;
21
22using namespace Stroika::Foundation;
24using namespace Stroika::Foundation::Cryptography;
25using namespace Stroika::Foundation::Cryptography::Encoding;
26using namespace Stroika::Foundation::Memory;
27using namespace Stroika::Foundation::Streams;
28
29using Memory::BLOB;
30
31// @todo examine/test https://github.com/saju/misc/blob/master/misc/openssl_aes.c
32
33//// VERY ROUGH DRAFT - NOT VERY CLOSE TO CORRECT FOR ALL ALGORITHSM
34//// SEE http://www.openssl.org/docs/crypto/EVP_EncryptInit.html
35/// SEE https://wiki.openssl.org/index.php/EVP_Symmetric_Encryption_and_Decryption
36//// for details on what todo
37
38#if qStroika_HasComponent_OpenSSL
39
40namespace {
41 struct InOutStrmCommon_ {
42 InOutStrmCommon_ (const OpenSSLCryptoParams& cryptoParams, Direction d)
43 : fCTX_{::EVP_CIPHER_CTX_new ()}
44 {
45 try {
46 cryptoParams.fInitializer (fCTX_, d);
47 }
48 catch (...) {
49 ::EVP_CIPHER_CTX_free (fCTX_);
50 Execution::ReThrow ();
51 }
52 }
53 InOutStrmCommon_ (const InOutStrmCommon_&) = delete;
54 InOutStrmCommon_& operator= (const InOutStrmCommon_&) = delete;
55 virtual ~InOutStrmCommon_ ()
56 {
57 ::EVP_CIPHER_CTX_free (fCTX_);
58 }
59 static constexpr size_t _GetMinOutBufSize (size_t n)
60 {
61 return n + EVP_MAX_BLOCK_LENGTH;
62 }
63 // return nBytes in outBuf, throws on error
64 span<byte> _runOnce (span<const byte> data2Process, span<byte> outBuf)
65 {
66 Require (outBuf.size () >= _GetMinOutBufSize (data2Process.size ())); // always need out buf big enuf for inbuf
67 int outLen = 0;
68 Cryptography::Providers::OpenSSL::Exception::ThrowLastErrorIfFailed (
69 ::EVP_CipherUpdate (fCTX_, reinterpret_cast<unsigned char*> (outBuf.data ()), &outLen,
70 reinterpret_cast<const unsigned char*> (data2Process.data ()), static_cast<int> (data2Process.size ())));
71 Ensure (outLen >= 0);
72 Ensure (static_cast<size_t> (outLen) <= outBuf.size ());
73 return outBuf.subspan (0, outLen);
74 }
75 // return nBytes in outBuf, throws on error
76 // Can call multiple times - it keeps track itself if finalized.
77 size_t _cipherFinal (byte* outBufStart, [[maybe_unused]] byte* outBufEnd)
78 {
79 Require (outBufStart <= outBufEnd and static_cast<size_t> (outBufEnd - outBufStart) >= _GetMinOutBufSize (0));
80 if (fFinalCalled_) {
81 return 0; // not an error - just zero more bytes
82 }
83 int outLen = 0;
84 Cryptography::Providers::OpenSSL::Exception::ThrowLastErrorIfFailed (
85 ::EVP_CipherFinal_ex (fCTX_, reinterpret_cast<unsigned char*> (outBufStart), &outLen));
86 fFinalCalled_ = true;
87 Ensure (outLen >= 0);
88 Ensure (outLen <= (outBufEnd - outBufStart));
89 return size_t (outLen);
90 }
91 EVP_CIPHER_CTX* fCTX_{nullptr};
92 bool fFinalCalled_{false};
93 };
94}
95
96namespace {
97 class OpenSSLInputStreamRep_ : public InputStream::IRep<byte>, private InOutStrmCommon_ {
98 private:
99 static constexpr size_t kInBufSize_ = 10 * 1024;
100
101 public:
102 OpenSSLInputStreamRep_ (const OpenSSLCryptoParams& cryptoParams, Direction d, const InputStream::Ptr<byte>& realIn)
103 : InOutStrmCommon_{cryptoParams, d}
104 , fRealIn_{realIn}
105 {
106 }
107 virtual bool IsSeekable () const override
108 {
109 return false;
110 }
111 virtual void CloseRead () override
112 {
113 if (fRealIn_ != nullptr) {
114 fRealIn_.Close ();
115 }
116 Assert (fRealIn_ == nullptr);
117 Ensure (not IsOpenRead ());
118 }
119 virtual bool IsOpenRead () const override
120 {
121 return fRealIn_ != nullptr;
122 }
123 virtual SeekOffsetType GetReadOffset () const override
124 {
126 Require (IsOpenRead ());
127 return 0;
128 }
129 virtual optional<size_t> AvailableToRead () override
130 {
131 // must override since not seekable
132 if (not PreRead_ (NoDataAvailableHandling::eDontBlock)) {
133 return nullopt;
134 }
135 return static_cast<size_t> (fOutBufEnd_ > fOutBufStart_);
136 }
137 virtual optional<SeekOffsetType> RemainingLength () override
138 {
139 return nullopt; // generally cannot tell without side-effects on input stream
140 }
141 virtual optional<span<ElementType>> Read (span<ElementType> intoBuffer, NoDataAvailableHandling blockFlag) override
142 {
143 /*
144 * Keep track if unread bytes in fOutBuf_ - bounded by fOutBufStart_ and fOutBufEnd_.
145 * If none to read there - pull from fRealIn_ src, and push those through the cipher.
146 * and use that to re-populate fOutBuf_.
147 */
148 Require (not intoBuffer.empty ());
149 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
150 Require (IsOpenRead ());
151 if (not PreRead_ (blockFlag)) {
152 return nullopt;
153 }
154 if (fOutBufStart_ < fOutBufEnd_) {
155 size_t n2Copy = min<size_t> (fOutBufEnd_ - fOutBufStart_, intoBuffer.size ());
156 (void)::memcpy (intoBuffer.data (), fOutBufStart_, n2Copy);
157 fOutBufStart_ += n2Copy;
158 return intoBuffer.subspan (0, n2Copy);
159 }
160 return span<ElementType>{}; // EOF
161 }
162 // false means EWouldBlock
163 bool PreRead_ (NoDataAvailableHandling blockFlag)
164 {
165 if (fOutBufStart_ == fOutBufEnd_) {
166 /*
167 * Then pull from 'real in' stream until we have reach EOF there, or until we have some bytes to output
168 * on our own.
169 */
170 byte toDecryptBuf[kInBufSize_];
171 Again:
172 size_t n2Decrypt = 0;
173 switch (blockFlag) {
174 case NoDataAvailableHandling::eBlockIfNoDataAvailable: {
175 n2Decrypt = fRealIn_.ReadBlocking (span{toDecryptBuf}).size ();
176 } break;
177 case NoDataAvailableHandling::eDontBlock: {
178 if (auto oRes = fRealIn_.ReadNonBlocking (span{toDecryptBuf})) {
179 n2Decrypt = oRes->size ();
180 }
181 else {
182 return false;
183 }
184 } break;
185 default:
187 }
188
189 if (n2Decrypt == 0) {
190 size_t nBytesInOutBuf = _cipherFinal (fOutBuf_.begin (), fOutBuf_.end ());
191 Assert (nBytesInOutBuf <= fOutBuf_.GetSize ());
192 fOutBufStart_ = fOutBuf_.begin ();
193 fOutBufEnd_ = fOutBufStart_ + nBytesInOutBuf;
194 }
195 else {
196 fOutBuf_.GrowToSize_uninitialized (_GetMinOutBufSize (NEltsOf (toDecryptBuf)));
197 span<byte> outBufUsed = _runOnce (span{toDecryptBuf, n2Decrypt}, span{fOutBuf_});
198 Assert (outBufUsed.size () <= fOutBuf_.GetSize ());
199 if (outBufUsed.size () == 0) {
200 // This can happen with block ciphers - we put stuff in, and get nothing out. But we cannot return EOF
201 // yet, so try again...
202 goto Again;
203 }
204 else {
205 fOutBufStart_ = fOutBuf_.begin ();
206 fOutBufEnd_ = fOutBufStart_ + outBufUsed.size ();
207 }
208 }
209 }
210 return true;
211 }
212
213 private:
214 mutable recursive_mutex fCriticalSection_;
215 Memory::InlineBuffer<byte, kInBufSize_ + EVP_MAX_BLOCK_LENGTH> fOutBuf_{_GetMinOutBufSize (kInBufSize_)};
216 byte* fOutBufStart_{nullptr};
217 byte* fOutBufEnd_{nullptr};
218 InputStream::Ptr<byte> fRealIn_;
219 };
220
221 class OpenSSLOutputStreamRep_ : public OutputStream::IRep<byte>, private InOutStrmCommon_ {
222 public:
223 OpenSSLOutputStreamRep_ (const OpenSSLCryptoParams& cryptoParams, Direction d, const OutputStream::Ptr<byte>& realOut)
224 : InOutStrmCommon_{cryptoParams, d}
225 , fRealOut_{realOut}
226 {
227 }
228 virtual ~OpenSSLOutputStreamRep_ ()
229 {
230 // no need for critical section because at most one thread can be running DTOR at a time, and no other methods can be running
231 try {
232 Flush ();
233 }
234 catch (...) {
235 // not great to do in DTOR, because we must drop exceptions on the floor!
236 }
237 }
238 virtual bool IsSeekable () const override
239 {
240 return false;
241 }
242 virtual void CloseWrite () override
243 {
244 if (fRealOut_ != nullptr) {
245 fRealOut_.Close ();
246 }
247 Ensure (fRealOut_ == nullptr);
248 }
249 virtual bool IsOpenWrite () const override
250 {
251 return fRealOut_ != nullptr;
252 }
253 virtual SeekOffsetType GetWriteOffset () const override
254 {
256 Require (IsOpenWrite ());
257 return 0;
258 }
259 virtual SeekOffsetType SeekWrite (Whence /*whence*/, SignedSeekOffsetType /*offset*/) override
260 {
262 Require (IsOpenWrite ());
263 return 0;
264 }
265 // pointer must refer to valid memory at least bufSize long, and cannot be nullptr. BufSize must always be >= 1.
266 // Writes always succeed fully or throw.
267 virtual void Write (span<const byte> elts) override
268 {
269 Require (not elts.empty ());
270 Require (IsOpenWrite ());
271 StackBuffer<byte, 1000 + EVP_MAX_BLOCK_LENGTH> outBuf{Memory::eUninitialized, _GetMinOutBufSize (elts.size ())};
272 [[maybe_unused]] auto&& critSec = lock_guard{fCriticalSection_};
273 span<byte> outBufUsed = _runOnce (elts, span{outBuf});
274 Assert (outBufUsed.size () <= outBuf.GetSize ());
275 fRealOut_.Write (outBufUsed);
276 }
277 virtual void Flush () override
278 {
279 Require (IsOpenWrite ());
280 byte outBuf[EVP_MAX_BLOCK_LENGTH]; // intentionally uninitialized
281 size_t nBytesInOutBuf = _cipherFinal (begin (outBuf), end (outBuf));
282 Assert (nBytesInOutBuf < sizeof (outBuf));
283 fRealOut_.Write (span{outBuf, nBytesInOutBuf});
284 }
285
286 private:
287 mutable recursive_mutex fCriticalSection_;
288 OutputStream::Ptr<byte> fRealOut_;
289 };
290}
291
292/*
293 ********************************************************************************
294 ******************** Cryptography::OpenSSLInputStream **************************
295 ********************************************************************************
296 */
297namespace {
298 void ApplySettings2CTX_ (EVP_CIPHER_CTX* ctx, const EVP_CIPHER* cipher, Direction d, bool nopad, bool useArgumentKeyLength,
299 const Memory::BLOB& key, const Memory::BLOB& initialIV)
300 {
301 RequireNotNull (ctx);
302 RequireNotNull (cipher);
303 bool enc = (d == Direction::eEncrypt);
304 if (nopad) {
305 Verify (::EVP_CIPHER_CTX_set_padding (ctx, 0) == 1);
306 }
307 Cryptography::Providers::OpenSSL::Exception::ThrowLastErrorIfFailed (::EVP_CipherInit_ex (ctx, cipher, NULL, nullptr, nullptr, enc));
308 size_t keyLen = ::EVP_CIPHER_CTX_key_length (ctx);
309 size_t ivLen = ::EVP_CIPHER_CTX_iv_length (ctx);
310
311 if (useArgumentKeyLength) {
312 keyLen = key.length ();
313 Verify (::EVP_CIPHER_CTX_set_key_length (ctx, static_cast<int> (keyLen)) == 1);
314 }
315
316 StackBuffer<byte> useKey{Memory::eUninitialized, keyLen};
317 StackBuffer<byte> useIV{Memory::eUninitialized, ivLen};
318
319 (void)::memset (useKey.begin (), 0, keyLen);
320 (void)::memset (useIV.begin (), 0, ivLen);
321
322 (void)::memcpy (useKey.begin (), key.begin (), min (keyLen, key.size ()));
323 if (not initialIV.empty ()) {
324 (void)::memcpy (useIV.begin (), initialIV.begin (), min (ivLen, initialIV.size ()));
325 }
326 Cryptography::Providers::OpenSSL::Exception::ThrowLastErrorIfFailed (::EVP_CipherInit_ex (
327 ctx, nullptr, NULL, reinterpret_cast<unsigned char*> (useKey.begin ()), reinterpret_cast<unsigned char*> (useIV.begin ()), enc));
328 }
329}
330
331OpenSSLCryptoParams::OpenSSLCryptoParams (CipherAlgorithm alg, const BLOB& key, const BLOB& initialIV)
332{
333 using namespace Providers::OpenSSL;
334 bool nopad = false;
335 bool useArgumentKeyLength = false;
336 if (alg == CipherAlgorithms::kRC2_CBC () or alg == CipherAlgorithms::kRC2_ECB () or alg == CipherAlgorithms::kRC2_CFB () or
337 alg == CipherAlgorithms::kRC2_OFB () or alg == CipherAlgorithms::kRC4 ()) {
338 useArgumentKeyLength = true;
339 }
340 fInitializer = [=] (::EVP_CIPHER_CTX* ctx, Direction d) {
341 ApplySettings2CTX_ (ctx, alg, d, nopad, useArgumentKeyLength, key, initialIV);
342 };
343}
344
345OpenSSLCryptoParams::OpenSSLCryptoParams (CipherAlgorithm alg, const DerivedKey& derivedKey)
346 : OpenSSLCryptoParams{alg, derivedKey.fKey, derivedKey.fIV}
347{
348}
349
350/*
351 ********************************************************************************
352 ******************** Cryptography::OpenSSLInputStream **************************
353 ********************************************************************************
354 */
355auto OpenSSLInputStream::New (const OpenSSLCryptoParams& cryptoParams, Direction direction, const InputStream::Ptr<byte>& realIn)
357{
358 return Streams::InputStream::Ptr<byte>{make_shared<OpenSSLInputStreamRep_> (cryptoParams, direction, realIn)};
359}
360
361auto OpenSSLInputStream::New (Execution::InternallySynchronized internallySynchronized, const OpenSSLCryptoParams& cryptoParams,
362 Direction direction, const InputStream::Ptr<byte>& realIn) -> Streams::InputStream::Ptr<byte>
363{
364 switch (internallySynchronized) {
365 case Execution::eInternallySynchronized:
366 return Streams::InternallySynchronizedInputStream::New<OpenSSLInputStreamRep_> ({}, cryptoParams, direction, realIn);
367 case Execution::eNotKnownInternallySynchronized:
368 return New (cryptoParams, direction, realIn);
369 default:
371 return New (cryptoParams, direction, realIn);
372 }
373}
374
375/*
376 ********************************************************************************
377 ******************* Cryptography::OpenSSLOutputStream **************************
378 ********************************************************************************
379 */
380auto OpenSSLOutputStream::New (const OpenSSLCryptoParams& cryptoParams, Direction direction, const OutputStream::Ptr<byte>& realOut)
382{
383 return Streams::OutputStream::Ptr<byte>{make_shared<OpenSSLOutputStreamRep_> (cryptoParams, direction, realOut)};
384}
385
386auto OpenSSLOutputStream::New (Execution::InternallySynchronized internallySynchronized, const OpenSSLCryptoParams& cryptoParams,
387 Direction direction, const OutputStream::Ptr<byte>& realOut) -> Streams::OutputStream::Ptr<byte>
388{
389 switch (internallySynchronized) {
390 case Execution::eInternallySynchronized:
391 return Streams::InternallySynchronizedOutputStream::New<OpenSSLOutputStreamRep_> ({}, cryptoParams, direction, realOut);
392 case Execution::eNotKnownInternallySynchronized:
393 return New (cryptoParams, direction, realOut);
394 default:
396 return New (cryptoParams, direction, realOut);
397 }
398}
399#endif
#define RequireNotReached()
Definition Assertions.h:385
#define RequireNotNull(p)
Definition Assertions.h:347
#define Verify(c)
Definition Assertions.h:419
NoDataAvailableHandling
If eDontBlock passed to most Stream APIs, then when the code would do a blocking read,...
Definition Stream.h:90
nonvirtual size_t length() const
Definition BLOB.inl:271
nonvirtual bool empty() const
Definition BLOB.inl:246
nonvirtual const byte * begin() const
Definition BLOB.inl:253
nonvirtual size_t size() const
Definition BLOB.inl:281
Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed...
virtual bool IsSeekable() const =0
virtual SeekOffsetType GetReadOffset() const =0
virtual optional< span< ElementType > > Read(span< ElementType > intoBuffer, NoDataAvailableHandling blockFlag)=0
virtual optional< SeekOffsetType > RemainingLength()
returns nullopt if not known (typical, and the default) - but sometimes it is known,...
virtual optional< size_t > AvailableToRead()
returns nullopt if nothing known available, zero if known EOF, and any other number of elements (typi...
InputStream<>::Ptr is Smart pointer (with abstract Rep) class defining the interface to reading from ...
Abstract interface for output stream object. Don't call directly (use Ptr usually) - but use directly...
virtual void Write(span< const ELEMENT_TYPE > elts)=0
OutputStream<>::Ptr is Smart pointer to a stream-based sink of data.