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