Cipherpack v1.2.0-dirty
A Cryprographic Stream Processor
crypto1.cpp
Go to the documentation of this file.
1/*
2 * Author: Sven Gothel <sgothel@jausoft.com>
3 * Copyright (c) 2021 Gothel Software e.K.
4 * Copyright (c) 2021 ZAFENA AB
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining
7 * a copy of this software and associated documentation files (the
8 * "Software"), to deal in the Software without restriction, including
9 * without limitation the rights to use, copy, modify, merge, publish,
10 * distribute, sublicense, and/or sell copies of the Software, and to
11 * permit persons to whom the Software is furnished to do so, subject to
12 * the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be
15 * included in all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 */
25
27
28#include <cstdint>
29#include <jau/debug.hpp>
30#include <jau/file_util.hpp>
31
32using namespace cipherpack;
33
34// static uint32_t to_uint32_t(const Botan::BigInt& v) const { return v.to_u32bit(); }
35
36static Botan::BigInt to_BigInt(const uint64_t & v) {
37 return Botan::BigInt::from_u64(v);
38}
39
40static uint64_t to_uint64_t(const Botan::BigInt& v) {
41 if( v.is_negative() ) {
42 throw Botan::Encoding_Error("BigInt::to_u64bit: Number is negative");
43 }
44 if( v.bits() > 64 ) {
45 throw Botan::Encoding_Error("BigInt::to_u64bit: Number is too big to convert");
46 }
47 uint64_t out = 0;
48 for(size_t i = 0; i < 8; ++i) {
49 out = (out << 8) | v.byte_at(7-i);
50 }
51 return out;
52}
53
54static int64_t to_positive_int64_t(const Botan::BigInt& v) {
55 if( v.is_negative() ) {
56 throw Botan::Encoding_Error("BigInt::to_positive_int64_t: Number is negative");
57 }
58 if( v.bits() > 63 ) {
59 throw Botan::Encoding_Error("BigInt::to_positive_int64_t: Number is too big to convert");
60 }
61 uint64_t out = 0;
62 for(size_t i = 0; i < 8; ++i) {
63 out = (out << 8) | v.byte_at(7-i);
64 }
65 return static_cast<int64_t>( out );
66}
67
68static std::vector<uint8_t> to_OctetString(const std::string& s) {
69 return std::vector<uint8_t>( s.begin(), s.end() );
70}
71
72static std::string to_string(const std::vector<uint8_t>& v) {
73 return std::string(reinterpret_cast<const char*>(v.data()), v.size());
74}
75
77 public:
79
80 WrappingCipherpackListener(CipherpackListenerRef parent_) // NOLINT(modernize-pass-by-value)
81 : parent( parent_ ) {} // NOLINT(performance-unnecessary-value-param)
82
83 void notifyError(const bool decrypt_mode, const PackHeader& header, const std::string& msg) noexcept override {
84 parent->notifyError(decrypt_mode, header, msg);
85 }
86
87 bool notifyHeader(const bool decrypt_mode, const PackHeader& header) noexcept override {
88 return parent->notifyHeader(decrypt_mode, header);
89 }
90
91 bool notifyProgress(const bool decrypt_mode, const uint64_t plaintext_size, const uint64_t bytes_processed) noexcept override {
92 return parent->notifyProgress(decrypt_mode, plaintext_size, bytes_processed);
93 }
94
95 void notifyEnd(const bool decrypt_mode, const PackHeader& header) noexcept override {
96 parent->notifyEnd(decrypt_mode, header);
97 }
98
99 bool getSendContent(const bool decrypt_mode) const noexcept override {
100 return parent->getSendContent(decrypt_mode);
101 }
102
103 bool contentProcessed(const bool decrypt_mode, const content_type ctype, cipherpack::secure_vector<uint8_t>& data, const bool is_final) noexcept override {
104 return parent->contentProcessed(decrypt_mode, ctype, data, is_final);
105 }
106
107 ~WrappingCipherpackListener() noexcept override = default;
108
109 std::string toString() const noexcept override { return "WrappingCipherpackListener["+jau::to_hexstring(this)+"]"; }
110};
111
112typedef std::function<bool (secure_vector<uint8_t>& /* data */, bool /* is_final */)> _StreamConsumerFunc;
113
114static uint64_t _read_stream(jau::io::ByteInStream& in,
116 const _StreamConsumerFunc& consumer_fn) noexcept {
117 uint64_t total = 0;
118 bool has_more;
119 do {
120 if( in.available(1) ) { // at least one byte to stream, also considers eof
121 buffer.resize(buffer.capacity());
122 const uint64_t got = in.read(buffer.data(), buffer.capacity());
123
124 buffer.resize(got);
125 total += got;
126 has_more = 1 <= got && !in.fail() && ( !in.has_content_size() || total < in.content_size() );
127 try {
128 if( !consumer_fn(buffer, !has_more) ) {
129 break; // end streaming
130 }
131 } catch (std::exception &e) {
132 ERR_PRINT("read_stream: Caught exception: %s", e.what());
133 break; // end streaming
134 }
135 } else {
136 has_more = false;
137 buffer.resize(0);
138 consumer_fn(buffer, true); // forced final, zero size
139 }
140 } while( has_more );
141 return total;
142}
143
144static uint64_t _read_buffer(jau::io::ByteInStream& in,
145 secure_vector<uint8_t>& buffer) noexcept {
146 if( in.available(1) ) { // at least one byte to stream, also considers eof
147 buffer.resize(buffer.capacity());
148 const uint64_t got = in.read(buffer.data(), buffer.capacity());
149 buffer.resize(got);
150 return got;
151 }
152 return 0;
153}
154
155static uint64_t _read_stream(jau::io::ByteInStream& in,
157 const _StreamConsumerFunc& consumer_fn) noexcept {
158 secure_vector<uint8_t>* buffers[] = { &buffer1, &buffer2 };
159 bool eof[] = { false, false };
160
161 bool eof_read = false;
162 uint64_t total_send = 0;
163 uint64_t total_read = 0;
164 int idx = 0;
165 // fill 1st buffer upfront
166 {
167 uint64_t got = _read_buffer(in, *buffers[idx]);
168 total_read += got;
169 eof_read = 0 == got || in.fail() || ( in.has_content_size() && total_read >= in.content_size() );
170 eof[idx] = eof_read;
171 ++idx;
172 }
173
174 // - buffer_idx was filled
175 // - buffer_idx++
176 //
177 // - while !eof_send do
178 // - read buffer_idx if not eof_read,
179 // - set eof[buffer_idx+1]=true if zero bytes
180 // - buffer_idx++
181 // - sent buffer_idx
182 //
183 bool eof_send = false;
184 while( !eof_send ) {
185 int bidx_next = ( idx + 1 ) % 2;
186 if( !eof_read ) {
187 uint64_t got = _read_buffer(in, *buffers[idx]);
188 total_read += got;
189 eof_read = 0 == got || in.fail() || ( in.has_content_size() && total_read >= in.content_size() );
190 eof[idx] = eof_read;
191 if( 0 == got ) {
192 // read-ahead eof propagation if read zero bytes,
193 // hence next consumer_fn() will send last bytes with is_final=true
194 eof[bidx_next] = true;
195 }
196 }
197 idx = bidx_next;
198
199 secure_vector<uint8_t>* buffer = buffers[idx];
200 eof_send = eof[idx];
201 total_send += buffer->size();
202 try {
203 if( !consumer_fn(*buffer, eof_send) ) {
204 return total_send; // end streaming
205 }
206 } catch (std::exception &e) {
207 ERR_PRINT("read_stream: Caught exception: %s", e.what());
208 return total_send; // end streaming
209 }
210 }
211 return total_send;
212}
213
214static std::vector<uint8_t> _fingerprint_public(const Botan::Public_Key& key, Botan::HashFunction& hash_func) {
215 std::vector<uint8_t> fp( hash_func.output_length() );
216 hash_func.clear();
217 hash_func.update( key.subject_public_key() );
218 hash_func.final( fp.data() );
219 return fp;
220}
221
223 const std::vector<std::string>& enc_pub_keys,
224 const std::string& sign_sec_key_fname, const jau::io::secure_string& passphrase,
225 jau::io::ByteInStream& source,
226 const std::string& target_path, const std::string& subject,
227 const std::string& plaintext_version,
228 const std::string& plaintext_version_parent,
229 CipherpackListenerRef listener,
230 const std::string_view& plaintext_hash_algo) {
231 const bool decrypt_mode = false;
232 environment::get();
233 const jau::fraction_timespec ts_creation = jau::getWallClockTime();
234
235 PackHeader header(target_path,
236 source.content_size(),
237 ts_creation,
238 subject,
239 plaintext_version, plaintext_version_parent,
240 crypto_cfg,
241 std::vector<uint8_t>(),
242 std::vector<std::vector<uint8_t>>(),
243 -1 /* term_key_fingerprint_used_idx */,
244 false /* valid */);
245
246 if( nullptr == listener ) {
247 ERR_PRINT2("Encrypt failed: Listener is nullptr for source %s", source.to_string().c_str());
248 return header;
249 }
250
251 if( source.fail() ) {
252 listener->notifyError(decrypt_mode, header, "Source has an error "+source.to_string());
253 return header;
254 }
255 const bool has_plaintext_size = source.has_content_size();
256 uint64_t plaintext_size = has_plaintext_size ? source.content_size() : 0;
257
258 if( !crypto_cfg.valid() ) {
259 listener->notifyError(decrypt_mode, header, "CryptoConfig incomplete "+crypto_cfg.to_string());
260 return header;
261 }
262
263 const jau::fraction_timespec _t0 = jau::getMonotonicTime();
264
265 uint64_t out_bytes_header = 0;
266
267 try {
268 std::unique_ptr<Botan::HashFunction> plaintext_hash_func = nullptr;
269 if( !plaintext_hash_algo.empty() ) {
270 const std::string plaintext_hash_algo_s(plaintext_hash_algo);
271 plaintext_hash_func = Botan::HashFunction::create(plaintext_hash_algo_s);
272 if( nullptr == plaintext_hash_func ) {
273 listener->notifyError(decrypt_mode, header, "Plaintext hash algo '"+plaintext_hash_algo_s+"' not available");
274 return header;
275 }
276 }
277 std::unique_ptr<Botan::HashFunction> fingerprint_hash_func = Botan::HashFunction::create(crypto_cfg.pk_fingerprt_hash_algo);
278 if( nullptr == fingerprint_hash_func ) {
279 listener->notifyError(decrypt_mode, header, "Fingerprint hash algo '"+crypto_cfg.pk_fingerprt_hash_algo+"' not available");
280 return header;
281 }
282
283 Botan::RandomNumberGenerator& rng = Botan::system_rng();
284
285 std::shared_ptr<Botan::Private_Key> sign_sec_key = load_private_key(sign_sec_key_fname, passphrase);
286 if( !sign_sec_key ) {
287 return header;
288 }
289
290 const Botan::OID sym_enc_algo_oid = Botan::OID::from_string(crypto_cfg.sym_enc_algo);
291 if( sym_enc_algo_oid.empty() ) {
292 listener->notifyError(decrypt_mode, header, "No OID defined for cypher algo '"+crypto_cfg.sym_enc_algo+"'");
293 return header;
294 }
295 std::shared_ptr<Botan::AEAD_Mode> aead = Botan::AEAD_Mode::create(crypto_cfg.sym_enc_algo, Botan::ENCRYPTION);
296 if(!aead) {
297 listener->notifyError(decrypt_mode, header, "AEAD algo '"+crypto_cfg.sym_enc_algo+"' not available");
298 return header;
299 }
300 cipherpack::secure_vector<uint8_t> plain_sym_key = rng.random_vec(aead->key_spec().maximum_keylength());
301 cipherpack::secure_vector<uint8_t> nonce = rng.random_vec(crypto_cfg.sym_enc_nonce_bytes);
302 std::vector<uint8_t> sender_fingerprint = _fingerprint_public(*sign_sec_key, *fingerprint_hash_func);
303
304 struct recevr_data_t {
305 std::shared_ptr<Botan::Public_Key> pub_key;
306 std::vector<uint8_t> fingerprint;
307 std::vector<uint8_t> encrypted_sym_key;
308 std::vector<uint8_t> encrypted_nonce;
309 };
310 std::vector<recevr_data_t> recevr_data_list;
311 std::vector<std::vector<uint8_t>> recevr_fingerprints;
312
313 for( const std::string& pub_key_fname : enc_pub_keys ) {
314 recevr_data_t recevr_data;
315
316 recevr_data.pub_key = load_public_key(pub_key_fname);
317 if( !recevr_data.pub_key ) {
318 listener->notifyError(decrypt_mode, header, "Loading pub-key file '"+pub_key_fname+"' failed");
319 return header;
320 }
321 Botan::PK_Encryptor_EME enc(*recevr_data.pub_key, rng, crypto_cfg.pk_enc_padding_algo+"(" + crypto_cfg.pk_enc_hash_algo + ")");
322 recevr_data.fingerprint = _fingerprint_public(*recevr_data.pub_key, *fingerprint_hash_func);
323 recevr_fingerprints.push_back(recevr_data.fingerprint);
324 recevr_data.encrypted_sym_key = enc.encrypt(plain_sym_key, rng);
325 recevr_data.encrypted_nonce = enc.encrypt(nonce, rng);
326 recevr_data_list.push_back(recevr_data);
327 }
328
329 std::vector<uint8_t> sender_signature;
330 {
332 header_buffer.reserve(Constants::buffer_size);
333
334 // DER-Header-1
335 header_buffer.clear();
336 {
337 Botan::DER_Encoder der(header_buffer);
338 der.start_sequence()
339 .encode( to_OctetString( Constants::package_magic ), Botan::ASN1_Type::OctetString )
340 .encode( to_OctetString( target_path ), Botan::ASN1_Type::OctetString )
341 .encode( to_BigInt( static_cast<uint64_t>( plaintext_size ) ), Botan::ASN1_Type::Integer )
342 .encode( to_BigInt( static_cast<uint64_t>( ts_creation.tv_sec ) ), Botan::ASN1_Type::Integer )
343 .encode( to_BigInt( static_cast<uint64_t>( ts_creation.tv_nsec ) ), Botan::ASN1_Type::Integer )
344 .encode( to_OctetString( subject ), Botan::ASN1_Type::OctetString )
345 .encode( to_OctetString( plaintext_version ), Botan::ASN1_Type::OctetString )
346 .encode( to_OctetString( plaintext_version_parent ), Botan::ASN1_Type::OctetString )
347 .encode( to_OctetString( crypto_cfg.pk_type ), Botan::ASN1_Type::OctetString )
348 .encode( to_OctetString( crypto_cfg.pk_fingerprt_hash_algo ), Botan::ASN1_Type::OctetString )
349 .encode( to_OctetString( crypto_cfg.pk_enc_padding_algo ), Botan::ASN1_Type::OctetString )
350 .encode( to_OctetString( crypto_cfg.pk_enc_hash_algo ), Botan::ASN1_Type::OctetString )
351 .encode( to_OctetString( crypto_cfg.pk_sign_algo ), Botan::ASN1_Type::OctetString )
352 .encode( sym_enc_algo_oid )
353 .encode( sender_fingerprint, Botan::ASN1_Type::OctetString )
354 .encode( recevr_data_list.size(), Botan::ASN1_Type::Integer )
355 .end_cons(); // data push
356 }
357
358 Botan::PK_Signer signer(*sign_sec_key, rng, crypto_cfg.pk_sign_algo);
359 signer.update(header_buffer);
360 out_bytes_header += header_buffer.size();
361 if( listener->getSendContent( decrypt_mode ) ) {
362 listener->contentProcessed(decrypt_mode, CipherpackListener::content_type::header, header_buffer, false /* final */);
363 }
364 DBG_PRINT("Encrypt: DER Header1 written + %zu bytes / %" PRIu64 " bytes", header_buffer.size(), out_bytes_header);
365
366 for(const recevr_data_t& recevr_data : recevr_data_list) {
367 // DER Header recevr_n
368 header_buffer.clear();
369 {
370 Botan::DER_Encoder der(header_buffer);
371 der.start_sequence()
372 .encode( recevr_data.fingerprint, Botan::ASN1_Type::OctetString )
373 .encode( recevr_data.encrypted_sym_key, Botan::ASN1_Type::OctetString )
374 .encode( recevr_data.encrypted_nonce, Botan::ASN1_Type::OctetString )
375 .end_cons();
376 }
377 signer.update(header_buffer);
378 out_bytes_header += header_buffer.size();
379 if( listener->getSendContent( decrypt_mode ) ) {
380 listener->contentProcessed(decrypt_mode, CipherpackListener::content_type::header, header_buffer, false /* final */);
381 }
382 DBG_PRINT("Encrypt: DER Header-recevr written + %zu bytes / %" PRIu64 " bytes", header_buffer.size(), out_bytes_header);
383 }
384
385 // DER-Header-2 (signature)
386 sender_signature = signer.signature(rng);
387 DBG_PRINT("Encrypt: Signature for %" PRIu64 " bytes: %s", out_bytes_header,
388 jau::bytesHexString(sender_signature, true /* lsbFirst */).c_str());
389 header_buffer.clear();
390 {
391 Botan::DER_Encoder der(header_buffer);
392 der.start_sequence()
393 .encode( sender_signature, Botan::ASN1_Type::OctetString )
394 .end_cons();
395 }
396 out_bytes_header += header_buffer.size();
397 if( listener->getSendContent( decrypt_mode ) ) {
398 listener->contentProcessed(decrypt_mode, CipherpackListener::content_type::header, header_buffer, false /* final */);
399 }
400 DBG_PRINT("Encrypt: DER Header2 written + %zu bytes / %" PRIu64 " bytes for %zu keys", header_buffer.size(), out_bytes_header, recevr_data_list.size());
401 }
402
403 header = PackHeader(target_path,
404 plaintext_size,
405 ts_creation,
406 subject,
407 plaintext_version, plaintext_version_parent,
408 crypto_cfg,
409 sender_fingerprint,
410 recevr_fingerprints,
411 -1 /* term_key_fingerprint_used_idx */,
412 false /* valid */);
413
414 DBG_PRINT("Encrypt: DER Header done, %" PRIu64 " header: %s",
415 out_bytes_header, header.to_string(true /* show_crypto_algos */, true /* force_all_fingerprints */).c_str());
416
417 if( !listener->notifyHeader(decrypt_mode, header) ) {
418 DBG_PRINT("Encrypt: notifyHeader() user abort from %s", source.to_string().c_str());
419 return header;
420 }
421
422 //
423 // Symmetric Encryption incl. hash
424 //
425
426 aead->set_key(plain_sym_key);
427 aead->set_associated_data_vec(sender_signature);
428 aead->start(nonce);
429
430 const bool sent_content_to_user = listener->getSendContent( decrypt_mode );
431 uint64_t out_bytes_ciphertext = 0;
432 bool consume_abort = false;
433 _StreamConsumerFunc consume_data = [&](cipherpack::secure_vector<uint8_t>& data, bool is_final) -> bool {
434 bool res = true;
435 // A simple !is_final suffices, since a final call w/ zero bytes shall add a TAG or padding depending on AEAD.
436 if( !is_final ) {
437 if( nullptr != plaintext_hash_func ) {
438 plaintext_hash_func->update(data);
439 }
440 aead->update(data);
441 if( sent_content_to_user ) {
442 res = listener->contentProcessed(decrypt_mode, CipherpackListener::content_type::message, data, false /* is_final */);
443 }
444 out_bytes_ciphertext += data.size();
445 if( res ) {
446 res = listener->notifyProgress(decrypt_mode, plaintext_size, source.tellg());
447 }
448 DBG_PRINT("Encrypt: EncPayload written0 + %zu bytes -> %" PRIu64 " bytes / %zu bytes, user[sent %d, res %d]",
449 data.size(), out_bytes_ciphertext, plaintext_size, sent_content_to_user, res);
450 } else {
451 if( nullptr != plaintext_hash_func ) {
452 plaintext_hash_func->update(data);
453 }
454 aead->finish(data);
455 if( sent_content_to_user ) {
456 res = listener->contentProcessed(decrypt_mode, CipherpackListener::content_type::message, data, true /* is_final */);
457 }
458 out_bytes_ciphertext += data.size();
459 if( !has_plaintext_size ) {
460 plaintext_size = out_bytes_ciphertext;
461 header.set_plaintext_size(plaintext_size);
462 }
463 if( res ) {
464 res = listener->notifyProgress(decrypt_mode, plaintext_size, source.tellg());
465 }
466 DBG_PRINT("Encrypt: EncPayload writtenF + %zu bytes -> %" PRIu64 " bytes / %zu bytes, user[sent %d, res %d]",
467 data.size(), out_bytes_ciphertext, plaintext_size, sent_content_to_user, res);
468 }
469 consume_abort = !res;
470 return res;
471 };
472 // No need for double-buffering here
473 // as long at least one (last) consume_data is_final=true is being made (even w/ zero file size).
474 // Note: The double-buffer variant works as well, manually tested.
476 io_buffer.reserve(Constants::buffer_size);
477 const uint64_t in_bytes_total = _read_stream(source, io_buffer, consume_data);
478 source.close();
479
480 if( nullptr != plaintext_hash_func ) {
481 std::vector<uint8_t> hash_value( plaintext_hash_func->output_length() );
482 plaintext_hash_func->final(hash_value.data());
483 header.set_plaintext_hash(plaintext_hash_func->name(), hash_value);
484 }
485
486 if( consume_abort ) {
487 DBG_PRINT("Encrypt: Processing aborted %s", source.to_string().c_str());
488 return header;
489 }
490 if ( source.fail() ) {
491 listener->notifyError(decrypt_mode, header, "Source read failed "+source.to_string());
492 return header;
493 }
494 if( source.tellg() != in_bytes_total ) {
495 listener->notifyError(decrypt_mode, header, "Writing done, "+jau::to_decstring(in_bytes_total)+" bytes read != "+source.to_string());
496 return header;
497 } else if( jau::environment::get().verbose ) {
498 WORDY_PRINT("Encrypt: Reading done from %s", source.to_string().c_str());
499 WORDY_PRINT("Encrypt: Writing done, %s header + %s ciphertext for %s plaintext bytes written, ratio %lf out/in",
500 jau::to_decstring(out_bytes_header).c_str(),
501 jau::to_decstring(out_bytes_ciphertext).c_str(),
502 jau::to_decstring(in_bytes_total).c_str(), in_bytes_total > 0 ? (double)(out_bytes_header+out_bytes_ciphertext)/(double)in_bytes_total : 0.0);
503 WORDY_PRINT("Encrypt: Writing done: source: %s", source.to_string().c_str());
504 }
505
506 if( jau::environment::get().verbose ) {
507 const jau::fraction_i64 _td = ( jau::getMonotonicTime() - _t0 ).to_fraction_i64();
508 jau::io::print_stats("Encrypt", (out_bytes_header+out_bytes_ciphertext), _td);
509 }
510 header.setValid(true);
511 return header;
512 } catch (std::exception &e) {
513 ERR_PRINT("Encrypt failed: Caught exception: %s on %s", e.what(), source.to_string().c_str());
514 return header;
515 }
516}
517
519 const std::vector<std::string>& enc_pub_keys,
520 const std::string& sign_sec_key_fname, const jau::io::secure_string& passphrase,
521 jau::io::ByteInStream& source,
522 const std::string& target_path, const std::string& subject,
523 const std::string& plaintext_version,
524 const std::string& plaintext_version_parent,
525 CipherpackListenerRef listener, // NOLINT(performance-unnecessary-value-param)
526 const std::string_view& plaintext_hash_algo,
527 const std::string dest_fname) { // NOLINT(performance-unnecessary-value-param)
528 environment::get();
529 const bool decrypt_mode = false;
530
531 if( dest_fname.empty() ) {
532 PackHeader header = encryptThenSign_Impl(crypto_cfg,
533 enc_pub_keys,
534 sign_sec_key_fname, passphrase,
535 source,
536 target_path, subject,
537 plaintext_version,
538 plaintext_version_parent,
539 listener, plaintext_hash_algo);
540 if( header.isValid() ) {
541 listener->notifyEnd(decrypt_mode, header);
542 }
543 return header;
544 }
545 std::string dest_fname2;
546 if( dest_fname == "/dev/stdout" || dest_fname == "-" ) {
547 dest_fname2 = "/dev/stdout";
548 } else {
549 dest_fname2 = dest_fname;
550 }
551
552 const jau::fraction_timespec ts_creation = jau::getWallClockTime();
553
554 PackHeader header(target_path,
555 source.content_size(),
556 ts_creation,
557 subject,
558 plaintext_version, plaintext_version_parent,
559 crypto_cfg,
560 std::vector<uint8_t>(),
561 std::vector<std::vector<uint8_t>>(),
562 -1 /* term_key_fingerprint_used_idx */,
563 false /* valid */);
564
565 if( nullptr == listener ) {
566 ERR_PRINT2("Encrypt failed: Listener is nullptr for source %s", source.to_string().c_str());
567 return header;
568 }
569
570 class MyListener : public WrappingCipherpackListener {
571 private:
572 jau::io::ByteOutStream_File* outfile_;
573 uint64_t& out_bytes_header_;
574 uint64_t& out_bytes_plaintext_;
575 public:
576 MyListener(CipherpackListenerRef parent_, uint64_t& bytes_header, uint64_t& bytes_plaintext)
577 : WrappingCipherpackListener( parent_ ), outfile_(nullptr), // NOLINT(performance-unnecessary-value-param)
578 out_bytes_header_(bytes_header), out_bytes_plaintext_(bytes_plaintext)
579 {}
580
581 void set_outfile(jau::io::ByteOutStream_File* of) noexcept { outfile_ = of; }
582
583 bool getSendContent(const bool decrypt_mode) const noexcept override {
584 return true;
585 }
586
587 bool contentProcessed(const bool decrypt_mode, const content_type ctype, cipherpack::secure_vector<uint8_t>& data, const bool is_final) noexcept override {
588 if( nullptr != outfile_ ) {
589 if( data.size() != outfile_->write(data.data(), data.size()) ) {
590 return false;
591 }
592 if( outfile_->fail() ) {
593 return false;
594 }
595 switch( ctype ) {
597 out_bytes_header_ += data.size();
598 break;
600 [[fallthrough]];
601 default:
602 out_bytes_plaintext_ += data.size();
603 break;
604 }
605 }
606 if( parent->getSendContent(decrypt_mode) ) {
607 return parent->contentProcessed(decrypt_mode, ctype, data, is_final);
608 } else {
609 return true;
610 }
611 }
612 };
613 uint64_t out_bytes_header=0, out_bytes_plaintext=0;
614 std::shared_ptr<MyListener> my_listener = std::make_shared<MyListener>(listener, out_bytes_header, out_bytes_plaintext);
615 {
616 const jau::fs::file_stats output_stats(dest_fname2);
617 if( output_stats.exists() && !output_stats.has_fd() ) {
618 if( output_stats.is_file() ) {
619 if( !jau::fs::remove(dest_fname2) ) {
620 listener->notifyError(decrypt_mode, header, "Failed deletion of existing output file "+output_stats.to_string());
621 return header;
622 }
623 } else if( output_stats.is_dir() || output_stats.is_block() ) {
624 listener->notifyError(decrypt_mode, header, "Not overwriting existing "+output_stats.to_string());
625 return header;
626 }
627 }
628 }
629 jau::io::ByteOutStream_File outfile(dest_fname2);
630 if ( !outfile.good() ) {
631 listener->notifyError(decrypt_mode, header, "Output file not open "+dest_fname2);
632 return header;
633 }
634 my_listener->set_outfile(&outfile);
635
636 header = encryptThenSign_Impl(crypto_cfg,
637 enc_pub_keys,
638 sign_sec_key_fname, passphrase,
639 source,
640 target_path, subject,
641 plaintext_version,
642 plaintext_version_parent,
643 my_listener, plaintext_hash_algo);
644 {
645 const jau::fs::file_stats output_stats(dest_fname2);
646 if ( outfile.fail() ) {
647 if( output_stats.is_file() && !output_stats.has_fd() ) {
648 jau::fs::remove(dest_fname2);
649 }
650 header.setValid(false);
651 listener->notifyError(decrypt_mode, header, "Output file write failed "+dest_fname2);
652 return header;
653 }
654 outfile.close();
655
656 if( !header.isValid() ) {
657 if( output_stats.is_file() && !output_stats.has_fd() ) {
658 jau::fs::remove(dest_fname2);
659 }
660 return header;
661 }
662 }
663 // outfile closed
664 const jau::fs::file_stats output_stats(dest_fname2);
665 // TODO: Perhaps figure out 'ciphertext_size_same' for all streamcipher. true for `ChaCha20Poly1305`
666 constexpr const bool ciphertext_size_same = false;
667 if constexpr ( ciphertext_size_same ) {
668 // Test is only valid, IFF out_bytes_plaintext == out_bytes_ciphertext
669 if( output_stats.is_file() && out_bytes_header + out_bytes_plaintext != output_stats.size() ) {
670 if( output_stats.is_file() && !output_stats.has_fd() ) {
671 jau::fs::remove(dest_fname2);
672 }
673 header.setValid(false);
674 listener->notifyError(decrypt_mode, header,
675 "Writing done, "+jau::to_decstring(out_bytes_header)+" header + "+jau::to_decstring(out_bytes_plaintext)+
676 " plaintext != "+jau::to_decstring(output_stats.size())+" total bytes");
677 return header;
678 }
679 }
680 WORDY_PRINT("Encrypt: Writing done: output: %s", output_stats.to_string().c_str());
681
682 my_listener->notifyEnd(decrypt_mode, header);
683 return header;
684}
685
686
687static PackHeader checkSignThenDecrypt_Impl(const std::vector<std::string>& sign_pub_keys,
688 const std::string& dec_sec_key_fname, const jau::io::secure_string& passphrase,
689 jau::io::ByteInStream& source,
690 CipherpackListenerRef listener,
691 const std::string_view& plaintext_hash_algo) {
692 const bool decrypt_mode = true;
693 environment::get();
694 jau::fraction_timespec ts_creation;
695 PackHeader header(ts_creation);
696
697 if( nullptr == listener ) {
698 ERR_PRINT2("Decrypt failed: Listener is nullptr for source %s", source.to_string().c_str());
699 return header;
700 }
701
702 const jau::fraction_timespec _t0 = jau::getMonotonicTime();
703
704 if( !source.good() ) {
705 listener->notifyError(decrypt_mode, header, "Source is EOS or has an error "+source.to_string());
706 return header;
707 }
708 try {
709 Botan::RandomNumberGenerator& rng = Botan::system_rng();
710
711 std::unique_ptr<Botan::HashFunction> fingerprint_hash_func;
712 struct sender_data_t {
713 std::shared_ptr<Botan::Public_Key> pub_key;
714 std::vector<uint8_t> fingerprint;
715 };
716 std::vector<sender_data_t> sender_data_list;
717 for( const std::string& pub_key_fname : sign_pub_keys ) {
718 sender_data_t sender_data;
719 sender_data.pub_key = load_public_key(pub_key_fname);
720 if( !sender_data.pub_key ) {
721 listener->notifyError(decrypt_mode, header, "Loading pub-key file '"+pub_key_fname+"' failed");
722 return header;
723 }
724 sender_data_list.push_back(sender_data);
725 }
726 std::shared_ptr<Botan::Public_Key> sender_pub_key = nullptr; // not found
727
728 std::vector<std::vector<uint8_t>> recevr_fingerprints;
729 std::shared_ptr<Botan::Private_Key> dec_sec_key = load_private_key(dec_sec_key_fname, passphrase);
730 if( !dec_sec_key ) {
731 listener->notifyError(decrypt_mode, header, "Loading priv-key file '"+dec_sec_key_fname+"' failed");
732 return header;
733 }
734
735 std::string package_magic_in;
736 std::string target_path;
737 std::string subject;
738 bool has_plaintext_size;
739 uint64_t plaintext_size;
740 std::string plaintext_version;
741 std::string plaintext_version_parent;
742
743 CryptoConfig crypto_cfg;
744 Botan::OID sym_enc_algo_oid;
745
746 std::vector<uint8_t> fingerprt_sender;
747 ssize_t recevr_count;
748 ssize_t used_recevr_key_idx = -1;
749 std::vector<uint8_t> encrypted_sym_key;
750 std::vector<uint8_t> encrypted_nonce;
751
752 std::vector<uint8_t> sender_signature;
753
754 std::unique_ptr<Botan::HashFunction> hash_func = nullptr;
755 if( !plaintext_hash_algo.empty() ) {
756 const std::string plaintext_hash_algo_s(plaintext_hash_algo);
757 hash_func = Botan::HashFunction::create(plaintext_hash_algo_s);
758 if( nullptr == hash_func ) {
759 listener->notifyError(decrypt_mode, header, "Payload hash algo '"+plaintext_hash_algo_s+"' not available");
760 return header;
761 }
762 }
763
764 jau::io::secure_vector<uint8_t> input_buffer;
765 jau::io::ByteInStream_Recorder input(source, input_buffer);
766 WrappingDataSource winput(input);
767 uint64_t in_bytes_header = 0;
768
769 try {
770 // DER-Header-1
771 input.start_recording();
772 {
773 std::vector<uint8_t> package_magic_charvec;
774
775 Botan::BER_Decoder ber0(winput);
776
777 Botan::BER_Decoder ber = ber0.start_sequence();
778 ber.decode(package_magic_charvec, Botan::ASN1_Type::OctetString);
779 package_magic_in = to_string(package_magic_charvec);
780
781 if( Constants::package_magic != package_magic_in ) {
782 listener->notifyError(decrypt_mode, header,
783 "Expected Magic '"+Constants::package_magic+"', but got '"+package_magic_in+
784 "' in "+source.to_string());
785 return header;
786 }
787 DBG_PRINT("Decrypt: Magic is %s", package_magic_in.c_str());
788
789 std::vector<uint8_t> target_path_charvec;
790 Botan::BigInt bi_plaintext_size;
791 Botan::BigInt bi_ts_creation_sec;
792 Botan::BigInt bi_ts_creation_nsec;
793 std::vector<uint8_t> subject_charvec;
794 std::vector<uint8_t> plaintext_version_charvec;
795 std::vector<uint8_t> plaintext_version_parent_charvec;
796
797 std::vector<uint8_t> pk_type_cv;
798 std::vector<uint8_t> pk_fingerprt_hash_algo_cv;
799 std::vector<uint8_t> pk_enc_padding_algo_cv;
800 std::vector<uint8_t> pk_enc_hash_algo_cv;
801 std::vector<uint8_t> pk_sign_algo_cv;
802 Botan::BigInt bi_recevr_count;
803
804 ber.decode( target_path_charvec, Botan::ASN1_Type::OctetString )
805 .decode( bi_plaintext_size, Botan::ASN1_Type::Integer )
806 .decode( bi_ts_creation_sec, Botan::ASN1_Type::Integer )
807 .decode( bi_ts_creation_nsec, Botan::ASN1_Type::Integer )
808 .decode( subject_charvec, Botan::ASN1_Type::OctetString )
809 .decode( plaintext_version_charvec, Botan::ASN1_Type::OctetString )
810 .decode( plaintext_version_parent_charvec, Botan::ASN1_Type::OctetString )
811 .decode( pk_type_cv, Botan::ASN1_Type::OctetString )
812 .decode( pk_fingerprt_hash_algo_cv, Botan::ASN1_Type::OctetString )
813 .decode( pk_enc_padding_algo_cv, Botan::ASN1_Type::OctetString )
814 .decode( pk_enc_hash_algo_cv, Botan::ASN1_Type::OctetString )
815 .decode( pk_sign_algo_cv, Botan::ASN1_Type::OctetString )
816 .decode( sym_enc_algo_oid )
817 .decode( fingerprt_sender, Botan::ASN1_Type::OctetString )
818 .decode( bi_recevr_count, Botan::ASN1_Type::Integer )
819 .end_cons()
820 ;
821
822 target_path = to_string(target_path_charvec);
823 subject = to_string(subject_charvec);
824 plaintext_size = to_uint64_t(bi_plaintext_size);
825 has_plaintext_size = 0 < plaintext_size;
826 ts_creation.tv_sec = static_cast<int64_t>( to_uint64_t(bi_ts_creation_sec) );
827 ts_creation.tv_nsec = static_cast<int64_t>( to_uint64_t(bi_ts_creation_nsec) );
828 plaintext_version = to_string(plaintext_version_charvec);
829 plaintext_version_parent = to_string(plaintext_version_parent_charvec);
830 crypto_cfg.pk_type = to_string( pk_type_cv );
831 crypto_cfg.pk_fingerprt_hash_algo = to_string( pk_fingerprt_hash_algo_cv );
832 crypto_cfg.pk_enc_padding_algo = to_string( pk_enc_padding_algo_cv );
833 crypto_cfg.pk_enc_hash_algo = to_string( pk_enc_hash_algo_cv );
834 crypto_cfg.pk_sign_algo = to_string( pk_sign_algo_cv );
835 crypto_cfg.sym_enc_algo = Botan::OIDS::oid2str_or_empty( sym_enc_algo_oid );
836 recevr_count = to_positive_int64_t(bi_recevr_count);
837 }
838
839 header = PackHeader(target_path,
840 plaintext_size,
841 ts_creation,
842 subject,
843 plaintext_version, plaintext_version_parent,
844 crypto_cfg,
845 fingerprt_sender,
846 recevr_fingerprints,
847 used_recevr_key_idx,
848 false /* valid */);
849
850 if( fingerprt_sender.empty() ) {
851 listener->notifyError(decrypt_mode, header, "Fingerprint sender is empty");
852 return header;
853 }
854 fingerprint_hash_func = Botan::HashFunction::create(crypto_cfg.pk_fingerprt_hash_algo);
855 if( nullptr == fingerprint_hash_func ) {
856 listener->notifyError(decrypt_mode, header,
857 "Fingerprint hash algo '"+crypto_cfg.pk_fingerprt_hash_algo+"' not available");
858 return header;
859 }
860
861 for( sender_data_t& sender_data : sender_data_list ) {
862 if( sender_data.pub_key->algo_name() == crypto_cfg.pk_type ) {
863 sender_data.fingerprint = _fingerprint_public( *sender_data.pub_key, *fingerprint_hash_func );
864 if( fingerprt_sender == sender_data.fingerprint ) {
865 sender_pub_key = sender_data.pub_key;
866 break;
867 }
868 }
869 }
870 if( nullptr == sender_pub_key ) {
871 listener->notifyError(decrypt_mode, header,
872 "No matching sender fingerprint, received '"+jau::bytesHexString(fingerprt_sender, true /* lsbFirst */)+
873 "` in "+source.to_string());
874 return header;
875 }
876
877 Botan::PK_Verifier verifier(*sender_pub_key, crypto_cfg.pk_sign_algo);
878 verifier.update( input.get_recording() );
879 in_bytes_header += input.get_recording().size();
880 input.start_recording(); // start over ..
881
882 const std::vector<uint8_t> dec_key_fingerprint = _fingerprint_public( *dec_sec_key, *fingerprint_hash_func );
883 std::vector<uint8_t> receiver_fingerprint_temp;
884 std::vector<uint8_t> encrypted_sym_key_temp;
885 std::vector<uint8_t> encrypted_nonce_temp;
886
887 // DER-Header per receiver
888 for(ssize_t idx=0; idx < recevr_count; idx++) {
889 Botan::BER_Decoder ber(winput);
890 ber.start_sequence()
891 .decode(receiver_fingerprint_temp, Botan::ASN1_Type::OctetString)
892 .decode(encrypted_sym_key_temp, Botan::ASN1_Type::OctetString)
893 .decode(encrypted_nonce_temp, Botan::ASN1_Type::OctetString)
894 .end_cons()
895 ;
896 verifier.update( input.get_recording() );
897 in_bytes_header += input.get_recording().size();
898 input.start_recording(); // start over ..
899
900 recevr_fingerprints.push_back(receiver_fingerprint_temp);
901
902 if( 0 > used_recevr_key_idx ) {
903 if( !receiver_fingerprint_temp.empty() && receiver_fingerprint_temp == dec_key_fingerprint ) {
904 // match, we found our entry
905 used_recevr_key_idx = idx;
906 encrypted_sym_key = encrypted_sym_key_temp; // pick the encrypted key
907 encrypted_nonce = encrypted_nonce_temp; // and encrypted nonce
908 }
909 }
910 }
911 header = PackHeader(target_path,
912 plaintext_size,
913 ts_creation,
914 subject,
915 plaintext_version, plaintext_version_parent,
916 crypto_cfg,
917 fingerprt_sender,
918 recevr_fingerprints,
919 used_recevr_key_idx,
920 false /* valid */);
921
922 if( 0 > used_recevr_key_idx || 0 == recevr_count ) {
923 listener->notifyError(decrypt_mode, header,
924 "No matching receiver key found "+std::to_string(used_recevr_key_idx)+"/"+std::to_string(recevr_count)+
925 " in "+source.to_string());
926 return header;
927 }
928
929 const uint64_t in_bytes_signature = in_bytes_header;
930 {
931 Botan::BER_Decoder ber(winput);
932 ber.start_sequence()
933 .decode(sender_signature, Botan::ASN1_Type::OctetString)
934 .end_cons() // encrypted data follows ..
935 ;
936 in_bytes_header += input.get_recording().size();
937 input.clear_recording(); // implies stop
938 }
939 if( !verifier.check_signature(sender_signature) ) {
940 listener->notifyError(decrypt_mode, header,
941 "Signature mismatch on "+std::to_string(in_bytes_signature)+" header bytes / "+std::to_string(in_bytes_header)+
942 " bytes, received signature '"+jau::bytesHexString(sender_signature, true /* lsbFirst */)+
943 "' in "+source.to_string() );
944 return header;
945 }
946
947 DBG_PRINT("Decrypt: Signature OK for %" PRIu64 " header bytes / %" PRIu64 ": %s from %s",
948 in_bytes_signature, in_bytes_header,
949 jau::bytesHexString(sender_signature, true /* lsbFirst */).c_str(),
950 source.to_string().c_str());
951
952 DBG_PRINT("Decrypt: DER Header*: enc_key %zu/%zu (size %zd): %s",
953 used_recevr_key_idx, recevr_count, encrypted_sym_key.size(),
954 header.to_string(true /* show_crypto_algos */, true /* force_all_fingerprints */).c_str());
955 } catch (Botan::Decoding_Error &e) {
956 listener->notifyError(decrypt_mode, header, "Header Decoding: "+std::string(e.what())+" on "+input.to_string()+" -> "+source.to_string());
957 return header;
958 }
959 DBG_PRINT("Decrypt: target_path '%s', net_file_size %s, version %s (parent %s), subject %s",
960 target_path.c_str(), jau::to_decstring(plaintext_size).c_str(),
961 plaintext_version.c_str(),
962 plaintext_version_parent.c_str(),
963 subject.c_str());
964 DBG_PRINT("Decrypt: creation time %s UTC", ts_creation.to_iso8601_string().c_str());
965
966 if( !listener->notifyHeader(decrypt_mode, header) ) {
967 DBG_PRINT("Decrypt: notifyHeader() user abort from %s", source.to_string().c_str());
968 return header;
969 }
970
971 //
972 // Symmetric Encryption
973 //
974
975 std::shared_ptr<Botan::AEAD_Mode> aead = Botan::AEAD_Mode::create_or_throw(crypto_cfg.sym_enc_algo, Botan::DECRYPTION);
976 if(!aead) {
977 listener->notifyError(decrypt_mode, header, "sym_enc_algo '"+crypto_cfg.sym_enc_algo+"' not available from '"+source.to_string()+"'");
978 return header;
979 }
980 const size_t expected_keylen = aead->key_spec().maximum_keylength();
981
982 Botan::PK_Decryptor_EME dec(*dec_sec_key, rng, crypto_cfg.pk_enc_padding_algo+"(" + crypto_cfg.pk_enc_hash_algo + ")");
983
984 const cipherpack::secure_vector<uint8_t> plain_file_key =
985 dec.decrypt_or_random(encrypted_sym_key.data(), encrypted_sym_key.size(), expected_keylen, rng);
986 const cipherpack::secure_vector<uint8_t> nonce = dec.decrypt(encrypted_nonce);
987 crypto_cfg.sym_enc_nonce_bytes = nonce.size();
988 header = PackHeader(target_path,
989 plaintext_size,
990 ts_creation,
991 subject,
992 plaintext_version, plaintext_version_parent,
993 crypto_cfg,
994 fingerprt_sender,
995 recevr_fingerprints,
996 used_recevr_key_idx,
997 false /* valid */);
998 DBG_PRINT("Decrypt sym_key[sz %zd], %s", plain_file_key.size(), crypto_cfg.to_string().c_str());
999
1000 if( !crypto_cfg.valid() ) {
1001 listener->notifyError(decrypt_mode, header, "CryptoConfig incomplete "+crypto_cfg.to_string()+" from "+source.to_string());
1002 return header;
1003 }
1004
1005 aead->set_key(plain_file_key);
1006 aead->set_associated_data_vec(sender_signature);
1007 aead->start(nonce);
1008
1009 const bool sent_content_to_user = listener->getSendContent( decrypt_mode );
1010 uint64_t out_bytes_plaintext = 0;
1011 bool consume_abort = false;
1012 _StreamConsumerFunc consume_data = [&](cipherpack::secure_vector<uint8_t>& data, bool is_final) -> bool {
1013 bool res = true;
1014 const uint64_t next_total = out_bytes_plaintext + data.size();
1015 const size_t minimum_final_size = aead->minimum_final_size();
1016#if 0
1017 const size_t update_granularity = aead->update_granularity();
1018 DBG_PRINT("Decrypt: update_gran %zu, min_fin_sz %zu, is_final %d, has_plaintext_size %d, %" PRIu64 " + %zu = %" PRIu64 " <= %" PRIu64 " = %d",
1019 update_granularity, minimum_final_size,
1020 is_final, has_plaintext_size,
1021 out_bytes_plaintext, data.size(), next_total, plaintext_size, next_total <= plaintext_size);
1022#endif
1023 // 'next_total <= plaintext_size' included plaintext_size limit since at least one AEAD TAG will be added afterwards (or padding)
1024 if( !is_final && ( !has_plaintext_size || ( 0 < minimum_final_size && next_total <= plaintext_size ) || next_total < plaintext_size ) ) {
1025 try {
1026 aead->update(data);
1027 } catch (std::exception &e) {
1028 consume_abort = true;
1029 listener->notifyError(decrypt_mode, header, "Decrypting (.): "+std::string(e.what())+
1030 " @ plaintext ( "+std::to_string(out_bytes_plaintext)+" + "+std::to_string(data.size())+" ) / "+std::to_string(plaintext_size)+
1031 " bytes, final "+std::to_string(is_final)+" on "+source.to_string());
1032 return false;
1033 }
1034 if( nullptr != hash_func ) {
1035 hash_func->update(data);
1036 }
1037 if( sent_content_to_user ) {
1038 res = listener->contentProcessed(decrypt_mode, CipherpackListener::content_type::message, data, false /* is_final */);
1039 }
1040 out_bytes_plaintext += data.size();
1041 if( res ) {
1042 res = listener->notifyProgress(decrypt_mode, plaintext_size, out_bytes_plaintext);
1043 }
1044 DBG_PRINT("Decrypt: DecPayload written0 + %zu bytes -> %" PRIu64 " bytes / %zu bytes, user[sent %d, res %d]",
1045 data.size(), out_bytes_plaintext, plaintext_size, sent_content_to_user, res);
1046 consume_abort = !res;
1047 return res; // continue if user so desires
1048 } else {
1049 try {
1050 aead->finish(data);
1051 } catch (std::exception &e) {
1052 consume_abort = true;
1053 listener->notifyError(decrypt_mode, header, "Decrypting (F): "+std::string(e.what())+
1054 " @ plaintext ( "+std::to_string(out_bytes_plaintext)+" + "+std::to_string(data.size())+" ) / "+std::to_string(plaintext_size)+
1055 " bytes, final "+std::to_string(is_final)+" on "+source.to_string());
1056 return false;
1057 }
1058 if( nullptr != hash_func ) {
1059 hash_func->update(data);
1060 }
1061 if( sent_content_to_user ) {
1062 res = listener->contentProcessed(decrypt_mode, CipherpackListener::content_type::message, data, true /* is_final */);
1063 }
1064 out_bytes_plaintext += data.size();
1065 if( !has_plaintext_size ) {
1066 plaintext_size = out_bytes_plaintext;
1067 header.set_plaintext_size(plaintext_size);
1068 }
1069 if( res ) {
1070 res = listener->notifyProgress(decrypt_mode, plaintext_size, out_bytes_plaintext);
1071 }
1072 DBG_PRINT("Decrypt: DecPayload writtenF + %zu bytes -> %" PRIu64 " bytes / %zu bytes, user[sent %d, res %d]",
1073 data.size(), out_bytes_plaintext, plaintext_size, sent_content_to_user, res);
1074 consume_abort = !res;
1075 return false; // EOS
1076 }
1077 };
1078 // Note: Utilizing the double-buffered read-ahead read_stream() ensures `is_final` (i.e. eof)
1079 // is delivered with the last concume_data() call and data.size() > 0.
1080 //
1081 // This avoids decryption errors using manual-feeding or a file pipe w/o content-size known.
1082 //
1085 io_buffer1.reserve(Constants::buffer_size);
1086 io_buffer2.reserve(Constants::buffer_size);
1087 const uint64_t in_bytes_total = _read_stream(input, io_buffer1, io_buffer2, consume_data);
1088 input.close();
1089
1090 if( nullptr != hash_func ) {
1091 std::vector<uint8_t> hash_value( hash_func->output_length() );
1092 hash_func->final(hash_value.data());
1093 header.set_plaintext_hash(hash_func->name(), hash_value);
1094 }
1095 if( consume_abort ) {
1096 DBG_PRINT("Decrypt: Processing aborted %s", source.to_string().c_str());
1097 return header;
1098 }
1099 if ( source.fail() ) {
1100 listener->notifyError(decrypt_mode, header, "Reading stream failed "+source.to_string());
1101 return header;
1102 }
1103 if( 0==in_bytes_total ) {
1104 listener->notifyError(decrypt_mode, header, "Processing stream failed "+source.to_string());
1105 return header;
1106 }
1107 if( out_bytes_plaintext != plaintext_size ) {
1108 listener->notifyError(decrypt_mode, header, "Plaintext size mismatch: "+
1109 jau::to_decstring(out_bytes_plaintext)+" output bytes != "+
1110 jau::to_decstring(plaintext_size)+" header plaintext bytes from "+
1111 source.to_string().c_str());
1112 return header;
1113 } else {
1114 WORDY_PRINT("Decrypt: Reading done from %s", source.to_string().c_str());
1115 WORDY_PRINT("Decrypt: Writing done, %s plaintext bytes from %s ciphertext bytes input, ratio %lf in/out",
1116 jau::to_decstring(out_bytes_plaintext).c_str(),
1117 jau::to_decstring(in_bytes_total).c_str(),
1118 in_bytes_total > 0 ? (double)out_bytes_plaintext/(double)in_bytes_total : 0.0);
1119 }
1120
1121 const jau::fraction_i64 _td = ( jau::getMonotonicTime() - _t0 ).to_fraction_i64();
1122 if( jau::environment::get().verbose ) {
1123 jau::io::print_stats("Decrypt", out_bytes_plaintext, _td);
1124 }
1125
1126 header.setValid(true);
1127 return header;
1128 } catch (std::exception &e) {
1129 listener->notifyError(decrypt_mode, header, "Exception: "+std::string(e.what())+" on "+source.to_string());
1130 return header;
1131 }
1132}
1133
1134PackHeader cipherpack::checkSignThenDecrypt(const std::vector<std::string>& sign_pub_keys,
1135 const std::string& dec_sec_key_fname, const jau::io::secure_string& passphrase,
1136 jau::io::ByteInStream& source,
1137 CipherpackListenerRef listener, // NOLINT(performance-unnecessary-value-param)
1138 const std::string_view& plaintext_hash_algo,
1139 const std::string dest_fname) { // NOLINT(performance-unnecessary-value-param)
1140 environment::get();
1141 const bool decrypt_mode = true;
1142
1143 if( dest_fname.empty() ) {
1144 PackHeader header = checkSignThenDecrypt_Impl(sign_pub_keys,
1145 dec_sec_key_fname, passphrase,
1146 source, listener, plaintext_hash_algo);
1147 if( header.isValid() ) {
1148 listener->notifyEnd(decrypt_mode, header);
1149 }
1150 return header;
1151 }
1152 std::string dest_fname2;
1153 if( dest_fname == "/dev/stdout" || dest_fname == "-" ) {
1154 dest_fname2 = "/dev/stdout";
1155 } else {
1156 dest_fname2 = dest_fname;
1157 }
1158
1159 jau::fraction_timespec ts_creation;
1160 PackHeader header(ts_creation);
1161
1162 if( nullptr == listener ) {
1163 ERR_PRINT2("Decrypt failed: Listener is nullptr for source %s", source.to_string().c_str());
1164 return header;
1165 }
1166
1167 class MyListener : public WrappingCipherpackListener {
1168 private:
1169 bool sent_content_to_user;
1170 jau::io::ByteOutStream_File* outfile_;
1171 uint64_t& out_bytes_plaintext_;
1172 public:
1173 MyListener(CipherpackListenerRef parent_, uint64_t& bytes_plaintext) // NOLINT(performance-unnecessary-value-param)
1174 : WrappingCipherpackListener(parent_),
1175 sent_content_to_user(parent_->getSendContent( decrypt_mode )),
1176 outfile_(nullptr),
1177 out_bytes_plaintext_(bytes_plaintext)
1178 {}
1179
1180 void set_outfile(jau::io::ByteOutStream_File* of) noexcept { outfile_ = of; }
1181
1182 bool getSendContent(const bool decrypt_mode) const noexcept override {
1183 return true;
1184 }
1185
1186 bool contentProcessed(const bool decrypt_mode, const content_type ctype, cipherpack::secure_vector<uint8_t>& data, const bool is_final) noexcept override {
1187 if( nullptr != outfile_ && content_type::message == ctype ) {
1188 if( data.size() != outfile_->write(data.data(), data.size()) ) {
1189 return false;
1190 }
1191 if( outfile_->fail() ) {
1192 return false;
1193 }
1194 out_bytes_plaintext_ += data.size();
1195 }
1196 if( sent_content_to_user ) {
1197 return parent->contentProcessed(decrypt_mode, ctype, data, is_final);
1198 } else {
1199 return true;
1200 }
1201 }
1202 };
1203 uint64_t out_bytes_plaintext=0;
1204 std::shared_ptr<MyListener> my_listener = std::make_shared<MyListener>(listener, out_bytes_plaintext);
1205 {
1206 const jau::fs::file_stats output_stats(dest_fname2);
1207 if( output_stats.exists() && !output_stats.has_fd() ) {
1208 if( output_stats.is_file() ) {
1209 if( !jau::fs::remove(dest_fname2) ) {
1210 my_listener->notifyError(decrypt_mode, header, "Failed deletion of existing output file "+output_stats.to_string());
1211 return header;
1212 }
1213 } else if( output_stats.is_dir() || output_stats.is_block() ) {
1214 my_listener->notifyError(decrypt_mode, header, "Not overwriting existing "+output_stats.to_string());
1215 return header;
1216 }
1217 }
1218 }
1219 jau::io::ByteOutStream_File outfile(dest_fname2);
1220 if ( !outfile.good() ) {
1221 my_listener->notifyError(decrypt_mode, header, "Failed to open output file "+outfile.to_string());
1222 return header;
1223 }
1224 my_listener->set_outfile(&outfile);
1225
1226 header = checkSignThenDecrypt_Impl(sign_pub_keys,
1227 dec_sec_key_fname, passphrase,
1228 source, my_listener, plaintext_hash_algo);
1229
1230 {
1231 const jau::fs::file_stats output_stats(dest_fname2);
1232 if ( outfile.fail() ) {
1233 if( output_stats.is_file() && !output_stats.has_fd() ) {
1234 jau::fs::remove(dest_fname2);
1235 }
1236 header.setValid(false);
1237 my_listener->notifyError(decrypt_mode, header, "Failed to write output file "+dest_fname2);
1238 return header;
1239 }
1240 outfile.close();
1241
1242 if( !header.isValid() ) {
1243 if( output_stats.is_file() && !output_stats.has_fd() ) {
1244 jau::fs::remove(dest_fname2);
1245 }
1246 return header;
1247 }
1248 }
1249 // outfile closed
1250 const jau::fs::file_stats output_stats(dest_fname2);
1251 if( output_stats.is_file() && header.plaintext_size() != output_stats.size() ) {
1252 if( output_stats.is_file() && !output_stats.has_fd() ) {
1253 jau::fs::remove(dest_fname2);
1254 }
1255 header.setValid(false);
1256 listener->notifyError(decrypt_mode, header, "Output plaintext file size mismatch: "+
1257 jau::to_decstring(output_stats.size())+" output file bytes != "+
1258 jau::to_decstring(header.plaintext_size())+" header plaintext bytes from "+
1259 source.to_string().c_str());
1260 return header;
1261 }
1262
1263 WORDY_PRINT("Decrypt: Writing done: output: %s", output_stats.to_string().c_str());
1264 my_listener->notifyEnd(decrypt_mode, header);
1265 return header;
1266}
1267
std::string toString() const noexcept override
Definition: crypto1.cpp:109
bool contentProcessed(const bool decrypt_mode, const content_type ctype, cipherpack::secure_vector< uint8_t > &data, const bool is_final) noexcept override
User callback to receive the actual processed content, either the generated cipherpack or plaintext c...
Definition: crypto1.cpp:103
bool notifyHeader(const bool decrypt_mode, const PackHeader &header) noexcept override
User notification of preliminary PackHeader w/o optional hash of the plaintext message.
Definition: crypto1.cpp:87
CipherpackListenerRef parent
Definition: crypto1.cpp:78
WrappingCipherpackListener(CipherpackListenerRef parent_)
Definition: crypto1.cpp:80
void notifyError(const bool decrypt_mode, const PackHeader &header, const std::string &msg) noexcept override
User notification about an error via text message and preliminary PackHeader.
Definition: crypto1.cpp:83
~WrappingCipherpackListener() noexcept override=default
bool notifyProgress(const bool decrypt_mode, const uint64_t plaintext_size, const uint64_t bytes_processed) noexcept override
User notification about content streaming progress.
Definition: crypto1.cpp:91
void notifyEnd(const bool decrypt_mode, const PackHeader &header) noexcept override
User notification of successful completion.
Definition: crypto1.cpp:95
bool getSendContent(const bool decrypt_mode) const noexcept override
User provided information whether process shall send the processed content via contentProcessed() or ...
Definition: crypto1.cpp:99
Listener for events occurring while processing a cipherpack message via encryptThenSign() and checkSi...
Definition: cipherpack.hpp:442
Cipherpack header less encrypted keys or signatures as described in Cipherpack Data Stream.
Definition: cipherpack.hpp:275
void set_plaintext_size(const uint64_t v) noexcept
Definition: cipherpack.hpp:357
void set_plaintext_hash(const std::string &algo, const std::vector< uint8_t > &hash) noexcept
Set optional hash-algo and -value of the plaintext messages, produced for convenience and not wired.
Definition: cipherpack.hpp:415
uint64_t plaintext_size() const noexcept
Returns the plaintext message size in bytes, zero if not determined yet.
Definition: cipherpack.hpp:355
bool isValid() const noexcept
Definition: cipherpack.hpp:429
void setValid(const bool v)
Definition: cipherpack.hpp:428
std::string to_string(const bool show_crypto_algos=false, const bool force_all_fingerprints=false) const noexcept
Return a string representation.
Definition: crypto0.cpp:147
This class represents an abstract data source object.
Definition: cipherpack.hpp:172
std::function< bool(secure_vector< uint8_t > &, bool)> _StreamConsumerFunc
Definition: crypto1.cpp:112
static std::vector< uint8_t > to_OctetString(const std::string &s)
Definition: crypto1.cpp:68
static uint64_t _read_buffer(jau::io::ByteInStream &in, secure_vector< uint8_t > &buffer) noexcept
Definition: crypto1.cpp:144
static std::string to_string(const std::vector< uint8_t > &v)
Definition: crypto1.cpp:72
static PackHeader encryptThenSign_Impl(const CryptoConfig &crypto_cfg, const std::vector< std::string > &enc_pub_keys, const std::string &sign_sec_key_fname, const jau::io::secure_string &passphrase, jau::io::ByteInStream &source, const std::string &target_path, const std::string &subject, const std::string &plaintext_version, const std::string &plaintext_version_parent, CipherpackListenerRef listener, const std::string_view &plaintext_hash_algo)
Definition: crypto1.cpp:222
static uint64_t _read_stream(jau::io::ByteInStream &in, cipherpack::secure_vector< uint8_t > &buffer, const _StreamConsumerFunc &consumer_fn) noexcept
Definition: crypto1.cpp:114
static Botan::BigInt to_BigInt(const uint64_t &v)
Definition: crypto1.cpp:36
static uint64_t to_uint64_t(const Botan::BigInt &v)
Definition: crypto1.cpp:40
static int64_t to_positive_int64_t(const Botan::BigInt &v)
Definition: crypto1.cpp:54
static PackHeader checkSignThenDecrypt_Impl(const std::vector< std::string > &sign_pub_keys, const std::string &dec_sec_key_fname, const jau::io::secure_string &passphrase, jau::io::ByteInStream &source, CipherpackListenerRef listener, const std::string_view &plaintext_hash_algo)
Definition: crypto1.cpp:687
static std::vector< uint8_t > _fingerprint_public(const Botan::Public_Key &key, Botan::HashFunction &hash_func)
Definition: crypto1.cpp:214
std::shared_ptr< Botan::Public_Key > load_public_key(const std::string &pubkey_fname)
Definition: crypto0.cpp:181
std::shared_ptr< CipherpackListener > CipherpackListenerRef
Definition: cipherpack.hpp:559
std::shared_ptr< Botan::Private_Key > load_private_key(const std::string &privatekey_fname, const jau::io::secure_string &passphrase)
Definition: crypto0.cpp:310
std::vector< T, Botan::secure_allocator< T > > secure_vector
Definition: cipherpack.hpp:166
PackHeader encryptThenSign(const CryptoConfig &crypto_cfg, const std::vector< std::string > &enc_pub_keys, const std::string &sign_sec_key_fname, const jau::io::secure_string &passphrase, jau::io::ByteInStream &source, const std::string &target_path, const std::string &subject, const std::string &plaintext_version, const std::string &plaintext_version_parent, CipherpackListenerRef listener, const std::string_view &plaintext_hash_algo, const std::string destination_fname="")
Encrypt then sign the source producing a cipherpack stream passed to the CipherpackListener if opt-in...
Definition: crypto1.cpp:518
PackHeader checkSignThenDecrypt(const std::vector< std::string > &sign_pub_keys, const std::string &dec_sec_key_fname, const jau::io::secure_string &passphrase, jau::io::ByteInStream &source, CipherpackListenerRef listener, const std::string_view &plaintext_hash_algo, const std::string destination_fname="")
Verify signature then decrypt the source passing to the CipherpackListener if opt-in and also optiona...
Definition: crypto1.cpp:1134
CryptoConfig, contains crypto algorithms settings given at encryption wired via the Cipherpack Data S...
Definition: cipherpack.hpp:205
bool valid() const noexcept
Definition: crypto0.cpp:131
std::string pk_enc_padding_algo
Definition: cipherpack.hpp:208
std::string pk_fingerprt_hash_algo
Definition: cipherpack.hpp:207
std::string pk_enc_hash_algo
Definition: cipherpack.hpp:209
std::string to_string() const noexcept
Definition: crypto0.cpp:141