29#include <jau/debug.hpp>
30#include <jau/file_util.hpp>
37 return Botan::BigInt::from_u64(v);
41 if( v.is_negative() ) {
42 throw Botan::Encoding_Error(
"BigInt::to_u64bit: Number is negative");
45 throw Botan::Encoding_Error(
"BigInt::to_u64bit: Number is too big to convert");
48 for(
size_t i = 0; i < 8; ++i) {
49 out = (out << 8) | v.byte_at(7-i);
55 if( v.is_negative() ) {
56 throw Botan::Encoding_Error(
"BigInt::to_positive_int64_t: Number is negative");
59 throw Botan::Encoding_Error(
"BigInt::to_positive_int64_t: Number is too big to convert");
62 for(
size_t i = 0; i < 8; ++i) {
63 out = (out << 8) | v.byte_at(7-i);
65 return static_cast<int64_t
>( out );
69 return std::vector<uint8_t>( s.begin(), s.end() );
72static std::string
to_string(
const std::vector<uint8_t>& v) {
73 return std::string(
reinterpret_cast<const char*
>(v.data()), v.size());
84 parent->notifyError(decrypt_mode, header, msg);
88 return parent->notifyHeader(decrypt_mode, header);
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);
96 parent->notifyEnd(decrypt_mode, header);
100 return parent->getSendContent(decrypt_mode);
104 return parent->contentProcessed(decrypt_mode, ctype, data, is_final);
109 std::
string toString() const noexcept
override {
return "WrappingCipherpackListener["+jau::to_hexstring(
this)+
"]"; }
120 if( in.available(1) ) {
121 buffer.resize(buffer.capacity());
122 const uint64_t got = in.read(buffer.data(), buffer.capacity());
126 has_more = 1 <= got && !in.fail() && ( !in.has_content_size() || total < in.content_size() );
128 if( !consumer_fn(buffer, !has_more) ) {
131 }
catch (std::exception &e) {
132 ERR_PRINT(
"read_stream: Caught exception: %s", e.what());
138 consumer_fn(buffer,
true);
146 if( in.available(1) ) {
147 buffer.resize(buffer.capacity());
148 const uint64_t got = in.read(buffer.data(), buffer.capacity());
159 bool eof[] = {
false,
false };
161 bool eof_read =
false;
162 uint64_t total_send = 0;
163 uint64_t total_read = 0;
169 eof_read = 0 == got || in.fail() || ( in.has_content_size() && total_read >= in.content_size() );
183 bool eof_send =
false;
185 int bidx_next = ( idx + 1 ) % 2;
189 eof_read = 0 == got || in.fail() || ( in.has_content_size() && total_read >= in.content_size() );
194 eof[bidx_next] =
true;
201 total_send += buffer->size();
203 if( !consumer_fn(*buffer, eof_send) ) {
206 }
catch (std::exception &e) {
207 ERR_PRINT(
"read_stream: Caught exception: %s", e.what());
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() );
217 hash_func.update( key.subject_public_key() );
218 hash_func.final( fp.data() );
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,
230 const std::string_view& plaintext_hash_algo) {
231 const bool decrypt_mode =
false;
233 const jau::fraction_timespec ts_creation = jau::getWallClockTime();
236 source.content_size(),
239 plaintext_version, plaintext_version_parent,
241 std::vector<uint8_t>(),
242 std::vector<std::vector<uint8_t>>(),
246 if(
nullptr == listener ) {
247 ERR_PRINT2(
"Encrypt failed: Listener is nullptr for source %s", source.to_string().c_str());
251 if( source.fail() ) {
252 listener->notifyError(decrypt_mode, header,
"Source has an error "+source.
to_string());
255 const bool has_plaintext_size = source.has_content_size();
256 uint64_t plaintext_size = has_plaintext_size ? source.content_size() : 0;
258 if( !crypto_cfg.
valid() ) {
259 listener->notifyError(decrypt_mode, header,
"CryptoConfig incomplete "+crypto_cfg.
to_string());
263 const jau::fraction_timespec _t0 = jau::getMonotonicTime();
265 uint64_t out_bytes_header = 0;
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");
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");
283 Botan::RandomNumberGenerator& rng = Botan::system_rng();
285 std::shared_ptr<Botan::Private_Key> sign_sec_key =
load_private_key(sign_sec_key_fname, passphrase);
286 if( !sign_sec_key ) {
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+
"'");
295 std::shared_ptr<Botan::AEAD_Mode> aead = Botan::AEAD_Mode::create(crypto_cfg.
sym_enc_algo, Botan::ENCRYPTION);
297 listener->notifyError(decrypt_mode, header,
"AEAD algo '"+crypto_cfg.
sym_enc_algo+
"' not available");
302 std::vector<uint8_t> sender_fingerprint =
_fingerprint_public(*sign_sec_key, *fingerprint_hash_func);
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;
310 std::vector<recevr_data_t> recevr_data_list;
311 std::vector<std::vector<uint8_t>> recevr_fingerprints;
313 for(
const std::string& pub_key_fname : enc_pub_keys ) {
314 recevr_data_t recevr_data;
317 if( !recevr_data.pub_key ) {
318 listener->notifyError(decrypt_mode, header,
"Loading pub-key file '"+pub_key_fname+
"' failed");
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);
329 std::vector<uint8_t> sender_signature;
332 header_buffer.reserve(Constants::buffer_size);
335 header_buffer.clear();
337 Botan::DER_Encoder der(header_buffer);
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 )
352 .encode( sym_enc_algo_oid )
353 .encode( sender_fingerprint, Botan::ASN1_Type::OctetString )
354 .encode( recevr_data_list.size(), Botan::ASN1_Type::Integer )
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 );
364 DBG_PRINT(
"Encrypt: DER Header1 written + %zu bytes / %" PRIu64
" bytes", header_buffer.size(), out_bytes_header);
366 for(
const recevr_data_t& recevr_data : recevr_data_list) {
368 header_buffer.clear();
370 Botan::DER_Encoder der(header_buffer);
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 )
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 );
382 DBG_PRINT(
"Encrypt: DER Header-recevr written + %zu bytes / %" PRIu64
" bytes", header_buffer.size(), out_bytes_header);
386 sender_signature = signer.signature(rng);
387 DBG_PRINT(
"Encrypt: Signature for %" PRIu64
" bytes: %s", out_bytes_header,
388 jau::bytesHexString(sender_signature,
true ).c_str());
389 header_buffer.clear();
391 Botan::DER_Encoder der(header_buffer);
393 .encode( sender_signature, Botan::ASN1_Type::OctetString )
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 );
400 DBG_PRINT(
"Encrypt: DER Header2 written + %zu bytes / %" PRIu64
" bytes for %zu keys", header_buffer.size(), out_bytes_header, recevr_data_list.size());
407 plaintext_version, plaintext_version_parent,
414 DBG_PRINT(
"Encrypt: DER Header done, %" PRIu64
" header: %s",
415 out_bytes_header, header.
to_string(
true ,
true ).c_str());
417 if( !listener->notifyHeader(decrypt_mode, header) ) {
418 DBG_PRINT(
"Encrypt: notifyHeader() user abort from %s", source.to_string().c_str());
426 aead->set_key(plain_sym_key);
427 aead->set_associated_data_vec(sender_signature);
430 const bool sent_content_to_user = listener->getSendContent( decrypt_mode );
431 uint64_t out_bytes_ciphertext = 0;
432 bool consume_abort =
false;
437 if(
nullptr != plaintext_hash_func ) {
438 plaintext_hash_func->update(data);
441 if( sent_content_to_user ) {
442 res = listener->contentProcessed(decrypt_mode, CipherpackListener::content_type::message, data,
false );
444 out_bytes_ciphertext += data.size();
446 res = listener->notifyProgress(decrypt_mode, plaintext_size, source.tellg());
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);
451 if(
nullptr != plaintext_hash_func ) {
452 plaintext_hash_func->update(data);
455 if( sent_content_to_user ) {
456 res = listener->contentProcessed(decrypt_mode, CipherpackListener::content_type::message, data,
true );
458 out_bytes_ciphertext += data.size();
459 if( !has_plaintext_size ) {
460 plaintext_size = out_bytes_ciphertext;
464 res = listener->notifyProgress(decrypt_mode, plaintext_size, source.tellg());
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);
469 consume_abort = !res;
476 io_buffer.reserve(Constants::buffer_size);
477 const uint64_t in_bytes_total =
_read_stream(source, io_buffer, consume_data);
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());
486 if( consume_abort ) {
487 DBG_PRINT(
"Encrypt: Processing aborted %s", source.to_string().c_str());
490 if ( source.fail() ) {
491 listener->notifyError(decrypt_mode, header,
"Source read failed "+source.
to_string());
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());
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());
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);
512 }
catch (std::exception &e) {
513 ERR_PRINT(
"Encrypt failed: Caught exception: %s on %s", e.what(), source.to_string().c_str());
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,
526 const std::string_view& plaintext_hash_algo,
527 const std::string dest_fname) {
529 const bool decrypt_mode =
false;
531 if( dest_fname.empty() ) {
534 sign_sec_key_fname, passphrase,
536 target_path, subject,
538 plaintext_version_parent,
539 listener, plaintext_hash_algo);
541 listener->notifyEnd(decrypt_mode, header);
545 std::string dest_fname2;
546 if( dest_fname ==
"/dev/stdout" || dest_fname ==
"-" ) {
547 dest_fname2 =
"/dev/stdout";
549 dest_fname2 = dest_fname;
552 const jau::fraction_timespec ts_creation = jau::getWallClockTime();
555 source.content_size(),
558 plaintext_version, plaintext_version_parent,
560 std::vector<uint8_t>(),
561 std::vector<std::vector<uint8_t>>(),
565 if(
nullptr == listener ) {
566 ERR_PRINT2(
"Encrypt failed: Listener is nullptr for source %s", source.to_string().c_str());
572 jau::io::ByteOutStream_File* outfile_;
573 uint64_t& out_bytes_header_;
574 uint64_t& out_bytes_plaintext_;
578 out_bytes_header_(bytes_header), out_bytes_plaintext_(bytes_plaintext)
581 void set_outfile(jau::io::ByteOutStream_File* of)
noexcept { outfile_ = of; }
583 bool getSendContent(
const bool decrypt_mode)
const noexcept override {
588 if(
nullptr != outfile_ ) {
589 if( data.size() != outfile_->write(data.data(), data.size()) ) {
592 if( outfile_->fail() ) {
597 out_bytes_header_ += data.size();
602 out_bytes_plaintext_ += data.size();
606 if( parent->getSendContent(decrypt_mode) ) {
607 return parent->contentProcessed(decrypt_mode, ctype, data, is_final);
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);
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());
623 }
else if( output_stats.is_dir() || output_stats.is_block() ) {
624 listener->notifyError(decrypt_mode, header,
"Not overwriting existing "+output_stats.
to_string());
629 jau::io::ByteOutStream_File outfile(dest_fname2);
630 if ( !outfile.good() ) {
631 listener->notifyError(decrypt_mode, header,
"Output file not open "+dest_fname2);
634 my_listener->set_outfile(&outfile);
638 sign_sec_key_fname, passphrase,
640 target_path, subject,
642 plaintext_version_parent,
643 my_listener, plaintext_hash_algo);
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);
651 listener->notifyError(decrypt_mode, header,
"Output file write failed "+dest_fname2);
657 if( output_stats.is_file() && !output_stats.has_fd() ) {
658 jau::fs::remove(dest_fname2);
664 const jau::fs::file_stats output_stats(dest_fname2);
666 constexpr const bool ciphertext_size_same =
false;
667 if constexpr ( ciphertext_size_same ) {
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);
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");
680 WORDY_PRINT(
"Encrypt: Writing done: output: %s", output_stats.to_string().c_str());
682 my_listener->notifyEnd(decrypt_mode, header);
688 const std::string& dec_sec_key_fname,
const jau::io::secure_string& passphrase,
689 jau::io::ByteInStream& source,
691 const std::string_view& plaintext_hash_algo) {
692 const bool decrypt_mode =
true;
694 jau::fraction_timespec ts_creation;
697 if(
nullptr == listener ) {
698 ERR_PRINT2(
"Decrypt failed: Listener is nullptr for source %s", source.to_string().c_str());
702 const jau::fraction_timespec _t0 = jau::getMonotonicTime();
704 if( !source.good() ) {
705 listener->notifyError(decrypt_mode, header,
"Source is EOS or has an error "+source.
to_string());
709 Botan::RandomNumberGenerator& rng = Botan::system_rng();
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;
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;
720 if( !sender_data.pub_key ) {
721 listener->notifyError(decrypt_mode, header,
"Loading pub-key file '"+pub_key_fname+
"' failed");
724 sender_data_list.push_back(sender_data);
726 std::shared_ptr<Botan::Public_Key> sender_pub_key =
nullptr;
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);
731 listener->notifyError(decrypt_mode, header,
"Loading priv-key file '"+dec_sec_key_fname+
"' failed");
735 std::string package_magic_in;
736 std::string target_path;
738 bool has_plaintext_size;
739 uint64_t plaintext_size;
740 std::string plaintext_version;
741 std::string plaintext_version_parent;
744 Botan::OID sym_enc_algo_oid;
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;
752 std::vector<uint8_t> sender_signature;
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");
764 jau::io::secure_vector<uint8_t> input_buffer;
765 jau::io::ByteInStream_Recorder input(source, input_buffer);
767 uint64_t in_bytes_header = 0;
771 input.start_recording();
773 std::vector<uint8_t> package_magic_charvec;
775 Botan::BER_Decoder ber0(winput);
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);
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+
787 DBG_PRINT(
"Decrypt: Magic is %s", package_magic_in.c_str());
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;
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;
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 )
822 target_path =
to_string(target_path_charvec);
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);
835 crypto_cfg.
sym_enc_algo = Botan::OIDS::oid2str_or_empty( sym_enc_algo_oid );
843 plaintext_version, plaintext_version_parent,
850 if( fingerprt_sender.empty() ) {
851 listener->notifyError(decrypt_mode, header,
"Fingerprint sender is empty");
855 if(
nullptr == fingerprint_hash_func ) {
856 listener->notifyError(decrypt_mode, header,
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;
870 if(
nullptr == sender_pub_key ) {
871 listener->notifyError(decrypt_mode, header,
872 "No matching sender fingerprint, received '"+jau::bytesHexString(fingerprt_sender,
true )+
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();
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;
888 for(ssize_t idx=0; idx < recevr_count; idx++) {
889 Botan::BER_Decoder ber(winput);
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)
896 verifier.update( input.get_recording() );
897 in_bytes_header += input.get_recording().size();
898 input.start_recording();
900 recevr_fingerprints.push_back(receiver_fingerprint_temp);
902 if( 0 > used_recevr_key_idx ) {
903 if( !receiver_fingerprint_temp.empty() && receiver_fingerprint_temp == dec_key_fingerprint ) {
905 used_recevr_key_idx = idx;
906 encrypted_sym_key = encrypted_sym_key_temp;
907 encrypted_nonce = encrypted_nonce_temp;
915 plaintext_version, plaintext_version_parent,
922 if( 0 > used_recevr_key_idx || 0 == recevr_count ) {
923 listener->notifyError(decrypt_mode, header,
925 " in "+source.to_string());
929 const uint64_t in_bytes_signature = in_bytes_header;
931 Botan::BER_Decoder ber(winput);
933 .decode(sender_signature, Botan::ASN1_Type::OctetString)
936 in_bytes_header += input.get_recording().size();
937 input.clear_recording();
939 if( !verifier.check_signature(sender_signature) ) {
940 listener->notifyError(decrypt_mode, header,
942 " bytes, received signature '"+jau::bytesHexString(sender_signature,
true )+
943 "' in "+source.to_string() );
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 ).c_str(),
950 source.to_string().c_str());
952 DBG_PRINT(
"Decrypt: DER Header*: enc_key %zu/%zu (size %zd): %s",
953 used_recevr_key_idx, recevr_count, encrypted_sym_key.size(),
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());
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(),
964 DBG_PRINT(
"Decrypt: creation time %s UTC", ts_creation.to_iso8601_string().c_str());
966 if( !listener->notifyHeader(decrypt_mode, header) ) {
967 DBG_PRINT(
"Decrypt: notifyHeader() user abort from %s", source.to_string().c_str());
975 std::shared_ptr<Botan::AEAD_Mode> aead = Botan::AEAD_Mode::create_or_throw(crypto_cfg.
sym_enc_algo, Botan::DECRYPTION);
977 listener->notifyError(decrypt_mode, header,
"sym_enc_algo '"+crypto_cfg.
sym_enc_algo+
"' not available from '"+source.to_string()+
"'");
980 const size_t expected_keylen = aead->key_spec().maximum_keylength();
985 dec.decrypt_or_random(encrypted_sym_key.data(), encrypted_sym_key.size(), expected_keylen, rng);
992 plaintext_version, plaintext_version_parent,
998 DBG_PRINT(
"Decrypt sym_key[sz %zd], %s", plain_file_key.size(), crypto_cfg.
to_string().c_str());
1000 if( !crypto_cfg.
valid() ) {
1001 listener->notifyError(decrypt_mode, header,
"CryptoConfig incomplete "+crypto_cfg.
to_string()+
" from "+source.to_string());
1005 aead->set_key(plain_file_key);
1006 aead->set_associated_data_vec(sender_signature);
1009 const bool sent_content_to_user = listener->getSendContent( decrypt_mode );
1010 uint64_t out_bytes_plaintext = 0;
1011 bool consume_abort =
false;
1014 const uint64_t next_total = out_bytes_plaintext + data.size();
1015 const size_t minimum_final_size = aead->minimum_final_size();
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);
1024 if( !is_final && ( !has_plaintext_size || ( 0 < minimum_final_size && next_total <= plaintext_size ) || next_total < plaintext_size ) ) {
1027 }
catch (std::exception &e) {
1028 consume_abort =
true;
1029 listener->notifyError(decrypt_mode, header,
"Decrypting (.): "+std::string(e.what())+
1031 " bytes, final "+
std::to_string(is_final)+
" on "+source.to_string());
1034 if(
nullptr != hash_func ) {
1035 hash_func->update(data);
1037 if( sent_content_to_user ) {
1038 res = listener->contentProcessed(decrypt_mode, CipherpackListener::content_type::message, data,
false );
1040 out_bytes_plaintext += data.size();
1042 res = listener->notifyProgress(decrypt_mode, plaintext_size, out_bytes_plaintext);
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;
1051 }
catch (std::exception &e) {
1052 consume_abort =
true;
1053 listener->notifyError(decrypt_mode, header,
"Decrypting (F): "+std::string(e.what())+
1055 " bytes, final "+
std::to_string(is_final)+
" on "+source.to_string());
1058 if(
nullptr != hash_func ) {
1059 hash_func->update(data);
1061 if( sent_content_to_user ) {
1062 res = listener->contentProcessed(decrypt_mode, CipherpackListener::content_type::message, data,
true );
1064 out_bytes_plaintext += data.size();
1065 if( !has_plaintext_size ) {
1066 plaintext_size = out_bytes_plaintext;
1070 res = listener->notifyProgress(decrypt_mode, plaintext_size, out_bytes_plaintext);
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;
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);
1090 if(
nullptr != hash_func ) {
1091 std::vector<uint8_t> hash_value( hash_func->output_length() );
1092 hash_func->final(hash_value.data());
1095 if( consume_abort ) {
1096 DBG_PRINT(
"Decrypt: Processing aborted %s", source.to_string().c_str());
1099 if ( source.fail() ) {
1100 listener->notifyError(decrypt_mode, header,
"Reading stream failed "+source.
to_string());
1103 if( 0==in_bytes_total ) {
1104 listener->notifyError(decrypt_mode, header,
"Processing stream failed "+source.
to_string());
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 "+
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);
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);
1128 }
catch (std::exception &e) {
1129 listener->notifyError(decrypt_mode, header,
"Exception: "+std::string(e.what())+
" on "+source.
to_string());
1135 const std::string& dec_sec_key_fname,
const jau::io::secure_string& passphrase,
1136 jau::io::ByteInStream& source,
1138 const std::string_view& plaintext_hash_algo,
1139 const std::string dest_fname) {
1141 const bool decrypt_mode =
true;
1143 if( dest_fname.empty() ) {
1145 dec_sec_key_fname, passphrase,
1146 source, listener, plaintext_hash_algo);
1148 listener->notifyEnd(decrypt_mode, header);
1152 std::string dest_fname2;
1153 if( dest_fname ==
"/dev/stdout" || dest_fname ==
"-" ) {
1154 dest_fname2 =
"/dev/stdout";
1156 dest_fname2 = dest_fname;
1159 jau::fraction_timespec ts_creation;
1162 if(
nullptr == listener ) {
1163 ERR_PRINT2(
"Decrypt failed: Listener is nullptr for source %s", source.to_string().c_str());
1169 bool sent_content_to_user;
1170 jau::io::ByteOutStream_File* outfile_;
1171 uint64_t& out_bytes_plaintext_;
1175 sent_content_to_user(parent_->getSendContent( decrypt_mode )),
1177 out_bytes_plaintext_(bytes_plaintext)
1180 void set_outfile(jau::io::ByteOutStream_File* of)
noexcept { outfile_ = of; }
1182 bool getSendContent(
const bool decrypt_mode)
const noexcept override {
1187 if(
nullptr != outfile_ && content_type::message == ctype ) {
1188 if( data.size() != outfile_->write(data.data(), data.size()) ) {
1191 if( outfile_->fail() ) {
1194 out_bytes_plaintext_ += data.size();
1196 if( sent_content_to_user ) {
1197 return parent->contentProcessed(decrypt_mode, ctype, data, is_final);
1203 uint64_t out_bytes_plaintext=0;
1204 std::shared_ptr<MyListener> my_listener = std::make_shared<MyListener>(listener, out_bytes_plaintext);
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());
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());
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());
1224 my_listener->set_outfile(&outfile);
1227 dec_sec_key_fname, passphrase,
1228 source, my_listener, plaintext_hash_algo);
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);
1237 my_listener->notifyError(decrypt_mode, header,
"Failed to write output file "+dest_fname2);
1243 if( output_stats.is_file() && !output_stats.has_fd() ) {
1244 jau::fs::remove(dest_fname2);
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);
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 "+
1263 WORDY_PRINT(
"Decrypt: Writing done: output: %s", output_stats.to_string().c_str());
1264 my_listener->notifyEnd(decrypt_mode, header);
std::string toString() const noexcept override
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...
bool notifyHeader(const bool decrypt_mode, const PackHeader &header) noexcept override
User notification of preliminary PackHeader w/o optional hash of the plaintext message.
CipherpackListenerRef parent
WrappingCipherpackListener(CipherpackListenerRef parent_)
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.
~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.
void notifyEnd(const bool decrypt_mode, const PackHeader &header) noexcept override
User notification of successful completion.
bool getSendContent(const bool decrypt_mode) const noexcept override
User provided information whether process shall send the processed content via contentProcessed() or ...
Listener for events occurring while processing a cipherpack message via encryptThenSign() and checkSi...
This class represents an abstract data source object.
std::function< bool(secure_vector< uint8_t > &, bool)> _StreamConsumerFunc
static std::vector< uint8_t > to_OctetString(const std::string &s)
static uint64_t _read_buffer(jau::io::ByteInStream &in, secure_vector< uint8_t > &buffer) noexcept
static std::string to_string(const std::vector< uint8_t > &v)
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)
static uint64_t _read_stream(jau::io::ByteInStream &in, cipherpack::secure_vector< uint8_t > &buffer, const _StreamConsumerFunc &consumer_fn) noexcept
static Botan::BigInt to_BigInt(const uint64_t &v)
static uint64_t to_uint64_t(const Botan::BigInt &v)
static int64_t to_positive_int64_t(const Botan::BigInt &v)
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)
static std::vector< uint8_t > _fingerprint_public(const Botan::Public_Key &key, Botan::HashFunction &hash_func)
std::shared_ptr< Botan::Public_Key > load_public_key(const std::string &pubkey_fname)
std::shared_ptr< CipherpackListener > CipherpackListenerRef
std::shared_ptr< Botan::Private_Key > load_private_key(const std::string &privatekey_fname, const jau::io::secure_string &passphrase)
std::vector< T, Botan::secure_allocator< T > > secure_vector
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...
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...
CryptoConfig, contains crypto algorithms settings given at encryption wired via the Cipherpack Data S...
size_t sym_enc_nonce_bytes
bool valid() const noexcept
std::string pk_enc_padding_algo
std::string pk_fingerprt_hash_algo
std::string pk_enc_hash_algo
std::string to_string() const noexcept