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