Cipherpack v1.3.0-3-ga29431a
A Cryprographic Stream Processor
Loading...
Searching...
No Matches
crypto0.cpp
Go to the documentation of this file.
1/*
2 * Author: Sven Gothel <sgothel@jausoft.com>
3 * Copyright (c) 2021-2026 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#ifdef USE_LIBCURL
29 #include <curl/curl.h>
30#endif
31
32#include <jau/cpuid.hpp>
33#include <jau/os/os_support.hpp>
34
35#include <jau/debug.hpp>
36
37namespace Botan {
38 class CPUID final {
39 public:
40 static bool has_simd_32();
41
42 /**
43 * Return a possibly empty string containing list of known CPU
44 * extensions. Each name will be seperated by a space, and the ordering
45 * will be arbitrary. This list only contains values that are useful to
46 * Botan (for example FMA instructions are not checked).
47 *
48 * Example outputs "sse2 ssse3 rdtsc", "neon arm_aes", "altivec"
49 */
50 static std::string to_string();
51 };
52}
53
54using namespace cipherpack;
55
56static std::string cp_query_hash_provider(const std::string& algo) noexcept {
57 std::unique_ptr<Botan::HashFunction> hash_func = Botan::HashFunction::create(algo);
58 if( nullptr == hash_func ) {
59 return "";
60 }
61 return hash_func->provider();
62}
63
64static void cp_print_hash_provider(const std::string& algo) noexcept {
65 std::string p = cp_query_hash_provider(algo);
66 if( p.empty() ) {
67 jau::fprintf_td(stderr, "hash '%s': Not available, provider {", algo);
68 } else {
69 jau::fprintf_td(stderr, "hash '%s': provider '%s' of {", algo, p);
70 }
71 std::vector<std::string> hash_provider = Botan::HashFunction::providers(algo);
72 for(const std::string& pi : hash_provider) {
73 ::fprintf(stderr, "'%s', ", pi.c_str());
74 }
75 ::fprintf(stderr, "}\n");
76}
77
78environment::environment() noexcept {
79 jau::environment::get("cipherpack");
80
81#ifdef USE_LIBCURL
82 curl_global_init(CURL_GLOBAL_ALL);
83#endif
84}
85
86
87void environment::print_info() noexcept {
88 jau::fprintf_td(stderr, "%s\n", jau::os::get_platform_info());
89
90 jau::fprintf_td(stderr, "Botan cpuid: '%s'\n", Botan::CPUID::to_string());
91 jau::fprintf_td(stderr, "Botan cpuid: has_simd32 %d\n", (int)Botan::CPUID::has_simd_32());
92
93 cp_print_hash_provider("SHA-256");
94 cp_print_hash_provider("SHA-512");
95 cp_print_hash_provider("BLAKE2b(512)");
96}
97
98const std::string Constants::package_magic = "CIPHERPACK_0004";
99
100static const std::string default_pk_type = "RSA";
101static const std::string default_pk_fingerprt_hash_algo = "SHA-256";
102static const std::string default_pk_enc_padding_algo = "OAEP"; // or "EME1"
103static const std::string default_pk_enc_hash_algo = "SHA-256";
104static const std::string default_pk_sign_algo = "EMSA1(SHA-256)";
105
106static const std::string default_sym_enc_mac_algo = "ChaCha20Poly1305"; // or "AES-256/GCM"
107
108static const std::string default_hash_algo_ = "BLAKE2b(512)";
109
110std::string_view cipherpack::default_hash_algo() noexcept {
111 return default_hash_algo_;
112}
113
114/**
115 * Symmetric Encryption nonce size in bytes.
116 *
117 * We only process one message per 'encrypted_key', hence medium nonce size of 96 bit.
118 *
119 * ChaCha Nonce Sizes are usually: 64-bit classic, 96-bit IETF, 192-bit big
120 */
121static constexpr const size_t ChaCha_Nonce_BitSize = 96;
122
130
131bool CryptoConfig::valid() const noexcept {
132 return !pk_type.empty() &&
133 !pk_fingerprt_hash_algo.empty() &&
134 !pk_enc_padding_algo.empty() &&
135 !pk_enc_hash_algo.empty() &&
136 !pk_sign_algo.empty() &&
137 !sym_enc_algo.empty() &&
139}
140
141std::string CryptoConfig::to_string() const noexcept {
142 return "CCfg[pk[type '"+pk_type+"', fingerprt_hash '"+pk_fingerprt_hash_algo+"', enc_padding '"+pk_enc_padding_algo+
143 "', enc_hash '"+pk_enc_hash_algo+"', sign '"+pk_sign_algo+
144 "'], sym['"+sym_enc_algo+"', nonce "+std::to_string(sym_enc_nonce_bytes)+" bytes]]";
145}
146
147std::string PackHeader::to_string(const bool show_crypto_algos, const bool force_all_fingerprints) const noexcept {
148 const std::string crypto_str = show_crypto_algos ? ", "+crypto_cfg_.to_string() : "";
149
150 std::string recevr_fingerprint_str;
151 {
152 if( 0 <= used_recevr_key_idx_ ) {
153 recevr_fingerprint_str.append("dec '")
154 .append(jau::toHexString(recevr_fingerprints_.at(used_recevr_key_idx_), jau::lb_endian_t::little))
155 .append("' ,");
156 }
157 if( force_all_fingerprints || 0 > used_recevr_key_idx_ ) {
158 recevr_fingerprint_str += "enc[";
159 int i = 0;
160 for(const std::vector<uint8_t>& tkf : recevr_fingerprints_) {
161 if( 0 < i ) {
162 recevr_fingerprint_str.append(", ");
163 }
164 recevr_fingerprint_str.append("'").append(jau::toHexString(tkf, jau::lb_endian_t::little)).append("'");
165 ++i;
166 }
167 recevr_fingerprint_str.append("]");
168 }
169 }
170
171 std::string res = "Header[";
172 res += "valid "+std::to_string( isValid() )+
173 ", file[target_path '"+target_path_+"', plaintext_size "+jau::to_decstring(plaintext_size_)+
174 "], creation "+ts_creation_.toISO8601String()+" UTC, subject '"+subject_+"', "+
175 " version['"+plaintext_version_+
176 "', parent '"+plaintext_version_parent_+"']"+crypto_str+
177 ", fingerprints[sender '"+jau::toHexString(sender_fingerprint_, jau::lb_endian_t::little)+
178 "', recevr["+recevr_fingerprint_str+
179 "]], phash['"+plaintext_hash_algo_+"', sz "+std::to_string(plaintext_hash_.size())+"]]";
180 return res;
181}
182
183std::shared_ptr<Botan::Public_Key> cipherpack::load_public_key(const std::string& pubkey_fname) {
184 jau::io::ByteStream_File key_data_(pubkey_fname);
185 WrappingDataSource key_data(key_data_);
186 std::shared_ptr<Botan::Public_Key> key(Botan::X509::load_key(key_data));
187 if( !key ) {
188 jau_ERR_PRINT("Couldn't load Key %s", pubkey_fname);
189 return std::shared_ptr<Botan::Public_Key>();
190 }
191 if( key->algo_name() != "RSA" ) {
192 jau_ERR_PRINT("Key doesn't support RSA %s", pubkey_fname);
193 return std::shared_ptr<Botan::Public_Key>();
194 }
195 return key;
196}
197
198/**
199 * Get info from an EncryptedPrivateKeyInfo
200 *
201 * Copied from Botan, allowing to only pass passphrase by const reference
202 * for later secure erasure not leaving a copy in memory.
203 */
205 Botan::AlgorithmIdentifier& pbe_alg_id)
206{
208
209 Botan::BER_Decoder(source)
210 .start_sequence()
211 .decode(pbe_alg_id)
212 .decode(key_data, Botan::ASN1_Type::OctetString)
213 .verify_end();
214
215 return key_data;
216}
217
218#if defined(BOTAN_HAS_PKCS5_PBES2)
219namespace Botan {
220 /**
221 * Decrypt a PKCS #5 v2.0 encrypted stream
222 * @param key_bits the input
223 * @param passphrase the passphrase to use for decryption
224 * @param params the PBES2 parameters
225 */
227 pbes2_decrypt(const secure_vector<uint8_t>& key_bits,
228 const std::string& passphrase,
229 const std::vector<uint8_t>& params);
230}
231#endif
232
233/**
234 * PEM decode and/or decrypt a private key
235 *
236 * Copied from Botan, allowing to only pass passphrase by const reference
237 * for later secure erasure not leaving a copy in memory.
238 */
240 const std::string& passphrase,
241 Botan::AlgorithmIdentifier& pk_alg_id,
242 bool is_encrypted)
243{
244 Botan::AlgorithmIdentifier pbe_alg_id;
246
247 try {
248 if(Botan::ASN1::maybe_BER(source) && !Botan::PEM_Code::matches(source)) {
249 if(is_encrypted) {
250 key_data = jau_PKCS8_extract(source, pbe_alg_id);
251 } else {
252 // todo read more efficiently
253 while(!source.end_of_data()) {
254 uint8_t b;
255 size_t read = source.read_byte(b);
256 if(read) {
257 key_data.push_back(b);
258 }
259 }
260 }
261 } else {
262 std::string label;
263 key_data = Botan::PEM_Code::decode(source, label);
264
265 // todo remove autodetect for pem as well?
266 if(label == "PRIVATE KEY") {
267 is_encrypted = false;
268 } else if(label == "ENCRYPTED PRIVATE KEY") {
269 Botan::DataSource_Memory key_source(key_data);
270 key_data = jau_PKCS8_extract(key_source, pbe_alg_id);
271 } else {
272 throw Botan::PKCS8_Exception("Unknown PEM label " + label);
273 }
274 }
275
276 if(key_data.empty()) {
277 throw Botan::PKCS8_Exception("No key data found");
278 }
279 } catch(Botan::Decoding_Error& e) {
280 throw Botan::Decoding_Error("PKCS #8 private key decoding", e);
281 }
282
283 try {
284 if(is_encrypted) {
285 if(Botan::OIDS::oid2str_or_throw(pbe_alg_id.get_oid()) != "PBE-PKCS5v20") {
286 throw Botan::PKCS8_Exception("Unknown PBE type " + pbe_alg_id.get_oid().to_string());
287 }
288#if defined(BOTAN_HAS_PKCS5_PBES2)
289 key = Botan::pbes2_decrypt(key_data, passphrase, pbe_alg_id.get_parameters()); // pass passphrase by const reference, OK
290#else
291 #error Private key is encrypted but PBES2 was disabled in build
292 BOTAN_UNUSED(passphrase);
293 throw Botan::Decoding_Error("Private key is encrypted but PBES2 was disabled in build");
294#endif
295 } else {
296 key = key_data;
297 }
298
299 Botan::BER_Decoder(key)
300 .start_sequence()
301 .decode_and_check<size_t>(0, "Unknown PKCS #8 version number")
302 .decode(pk_alg_id)
303 .decode(key, Botan::ASN1_Type::OctetString)
304 .discard_remaining()
305 .end_cons();
306 } catch(std::exception& e) {
307 throw Botan::Decoding_Error("PKCS #8 private key decoding", e);
308 }
309 return key;
310}
311
312std::shared_ptr<Botan::Private_Key> cipherpack::load_private_key(const std::string& privatekey_fname, const jau::io::secure_string& passphrase) {
313 jau::io::ByteStream_File key_data_(privatekey_fname);
314 WrappingDataSource key_data(key_data_);
315 std::shared_ptr<Botan::Private_Key> key;
316 if( passphrase.empty() ) {
317 key = Botan::PKCS8::load_key(key_data);
318 } else {
319 /**
320 * We drop Botan::PKCS8::load_key(), since it copies the std::string passphrase via
321 * `const std::function<std::string ()>& get_pass`
322 * and hence leaves an intact copy of the passphrase in memory.
323 *
324 * Hence we replace it by our own 'jau_PKCS8_decode()' handing down only a const reference w/o copy.
325 *
326 * `key = Botan::PKCS8::load_key(key_data, passphrase);`
327 */
328 std::string insec_passphrase_copy(passphrase);
329 Botan::AlgorithmIdentifier alg_id;
330 cipherpack::secure_vector<uint8_t> pkcs8_key = jau_PKCS8_decode(key_data, insec_passphrase_copy, alg_id, true /* is_encrypted */);
331
332 const std::string alg_name = Botan::OIDS::oid2str_or_empty(alg_id.get_oid());
333 if( alg_name.empty() ) {
334 throw Botan::PKCS8_Exception("Unknown algorithm OID: " + alg_id.get_oid().to_string());
335 }
336 key = Botan::load_private_key(alg_id, pkcs8_key);
337 ::explicit_bzero(insec_passphrase_copy.data(), insec_passphrase_copy.size());
338 }
339 if( !key ) {
340 jau_ERR_PRINT("Couldn't load Key %s", privatekey_fname);
341 return std::shared_ptr<Botan::Private_Key>();
342 }
343 if( key->algo_name() != "RSA" ) {
344 jau_ERR_PRINT("Key doesn't support RSA %s", privatekey_fname);
345 return std::shared_ptr<Botan::Private_Key>();
346 }
347 return key;
348}
349
350std::string cipherpack::hash_util::file_suffix(const std::string& algo) noexcept {
351 std::string s = algo;
352 // lower-case
353 std::transform(s.begin(), s.end(), s.begin(), ::tolower); // NOLINT(modernize-use-ranges)
354 // remove '-'
355 auto it = std::remove( s.begin(), s.end(), '-'); // NOLINT(modernize-use-ranges)
356 s.erase(it, s.end());
357 return s;
358}
359
360bool cipherpack::hash_util::append_to_file(const std::string& out_file, const std::string& hashed_file, const std::string_view& hash_algo, const std::vector<uint8_t>& hash_value) noexcept {
361 const std::string hash_str = jau::toHexString(hash_value.data(), hash_value.size(), jau::lb_endian_t::little);
362
363 jau::io::ByteStream_File out(out_file);
364 if( !out.good() ) {
365 return false;
366 }
367 if( hash_algo.size() != out.write(hash_algo.data(), hash_algo.size()) ) {
368 return false;
369 }
370 if( 1 != out.write(" ", 1) ) {
371 return false;
372 }
373 if( hash_str.size() != out.write(hash_str.data(), hash_str.size()) ) {
374 return false;
375 }
376 if( 2 != out.write(" *", 2) ) {
377 return false;
378 }
379 if( hashed_file.size() != out.write(hashed_file.data(), hashed_file.size()) ) {
380 return false;
381 }
382 if( 1 != out.write("\n", 1) ) {
383 return false;
384 }
385 return out.good();
386}
387
388std::unique_ptr<std::vector<uint8_t>> cipherpack::hash_util::calc(const std::string_view& algo, jau::io::ByteStream& source) noexcept {
389 const std::string algo_s(algo);
390 std::unique_ptr<Botan::HashFunction> hash_func = Botan::HashFunction::create(algo_s);
391 if( nullptr == hash_func ) {
392 jau_ERR_PRINT2("Hash failed: Algo %s not available", algo_s);
393 return nullptr;
394 }
395 jau::io::StreamConsumerFunc consume_data = [&](jau::io::secure_vector<uint8_t>& data, bool is_final) -> bool {
396 (void) is_final;
397 hash_func->update(data.data(), data.size());
398 return true;
399 };
400 jau::io::secure_vector<uint8_t> io_buffer;
401 io_buffer.reserve(Constants::buffer_size);
402 const uint64_t in_bytes_total = jau::io::read_stream(source, io_buffer, consume_data);
403 source.close();
404 if( source.hasContentSize() && in_bytes_total != source.contentSize() ) {
405 jau_ERR_PRINT2("Hash failed: Only read %" PRIu64 " bytes of %s", in_bytes_total, source.toString());
406 return nullptr;
407 }
408 std::unique_ptr<std::vector<uint8_t>> res = std::make_unique<std::vector<uint8_t>>(hash_func->output_length());
409 hash_func->final(res->data());
410 return res;
411}
412
413std::unique_ptr<std::vector<uint8_t>> cipherpack::hash_util::calc(const std::string_view& algo, const std::string& path_or_uri, uint64_t& bytes_hashed, jau::fraction_i64 timeout) noexcept {
414 using namespace jau::io::fs;
415 bytes_hashed = 0;
416
417 if( !jau::io::uri_tk::is_local_file_protocol(path_or_uri) &&
418 jau::io::uri_tk::protocol_supported(path_or_uri) )
419 {
420 jau::io::ByteInStream_URL in(path_or_uri, timeout);
421 if( !in.fail() ) {
422 return calc(algo, in);
423 }
424 }
425 std::unique_ptr<file_stats> stats;
426 if( jau::io::uri_tk::is_local_file_protocol(path_or_uri) ) {
427 // cut of leading `file://`
428 std::string path2 = path_or_uri.substr(7);
429 stats = std::make_unique<file_stats>(path2);
430 } else {
431 stats = std::make_unique<file_stats>(path_or_uri);
432 }
433 if( !stats->is_dir() ) {
434 if( stats->has_fd() ) {
435 jau::io::ByteStream_File in(stats->fd());
436 if( in.fail() ) {
437 return nullptr;
438 }
439 return calc(algo, in);
440 } else {
441 jau::io::ByteStream_File in(stats->path());
442 if( in.fail() ) {
443 return nullptr;
444 }
445 return calc(algo, in);
446 }
447 }
448 //
449 // directory handling
450 //
451 struct context_t {
452 std::vector<int> dirfds;
453 std::unique_ptr<Botan::HashFunction> hash_func;
454 jau::io::secure_vector<uint8_t> io_buffer;
455 jau::io::StreamConsumerFunc consume_data;
456 uint64_t bytes_hashed;
457 };
458 context_t ctx { .dirfds=std::vector<int>(), .hash_func=nullptr, .io_buffer=jau::io::secure_vector<uint8_t>(), .consume_data=nullptr, .bytes_hashed=0 };
459 {
460 const std::string algo_s(algo);
461 ctx.hash_func = Botan::HashFunction::create(algo_s);
462 if( nullptr == ctx.hash_func ) {
463 jau_ERR_PRINT2("Hash failed: Algo %s not available", algo_s);
464 return nullptr;
465 }
466 }
467 ctx.consume_data = [&](jau::io::secure_vector<uint8_t>& data, bool is_final) -> bool {
468 (void) is_final;
469 ctx.hash_func->update(data.data(), data.size());
470 return true;
471 };
472 ctx.io_buffer.reserve(Constants::buffer_size);
473
474 const path_visitor pv = jau::bind_capref<bool, context_t, traverse_event, const file_stats&, size_t>(&ctx,
475 ( bool(*)(context_t*, traverse_event, const file_stats&, size_t) ) /* help template type deduction of function-ptr */
476 ( [](context_t* ctx_ptr, traverse_event tevt, const file_stats& element_stats, size_t depth) -> bool {
477 (void)depth;
478 if( is_set(tevt, traverse_event::file) &&
479 !is_set(tevt, traverse_event::symlink) )
480 {
481 jau::io::ByteStream_File in(ctx_ptr->dirfds.back(), element_stats.item().basename());
482 if( in.fail() ) {
483 return false;
484 }
485 const uint64_t in_bytes_total = jau::io::read_stream(in, ctx_ptr->io_buffer, ctx_ptr->consume_data);
486 in.close();
487 if( in.hasContentSize() && in_bytes_total != in.contentSize() ) {
488 jau_ERR_PRINT2("Hash failed: Only read %" PRIu64 " bytes of %s", in_bytes_total, in.toString());
489 return false;
490 }
491 ctx_ptr->bytes_hashed += in_bytes_total;
492 }
493 return true;
494 } ) );
495 const traverse_options topts = traverse_options::recursive | traverse_options::lexicographical_order;
496 if( visit(*stats, topts, pv, &ctx.dirfds) ) {
497 std::unique_ptr<std::vector<uint8_t>> res = std::make_unique<std::vector<uint8_t>>(ctx.hash_func->output_length());
498 ctx.hash_func->final(res->data());
499 bytes_hashed = ctx.bytes_hashed;
500 return res;
501 }
502 return nullptr;
503}
static std::string to_string()
Return a possibly empty string containing list of known CPU extensions.
static bool has_simd_32()
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.
bool isValid() const noexcept
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.
void print_info() noexcept
Definition crypto0.cpp:87
static const std::string default_pk_sign_algo
Definition crypto0.cpp:104
static constexpr const size_t ChaCha_Nonce_BitSize
Symmetric Encryption nonce size in bytes.
Definition crypto0.cpp:121
static const std::string default_pk_type
Definition crypto0.cpp:100
static std::string cp_query_hash_provider(const std::string &algo) noexcept
Definition crypto0.cpp:56
static const std::string default_sym_enc_mac_algo
Definition crypto0.cpp:106
static void cp_print_hash_provider(const std::string &algo) noexcept
Definition crypto0.cpp:64
static const std::string default_pk_fingerprt_hash_algo
Definition crypto0.cpp:101
static cipherpack::secure_vector< uint8_t > jau_PKCS8_extract(Botan::DataSource &source, Botan::AlgorithmIdentifier &pbe_alg_id)
Get info from an EncryptedPrivateKeyInfo.
Definition crypto0.cpp:204
static const std::string default_pk_enc_padding_algo
Definition crypto0.cpp:102
static cipherpack::secure_vector< uint8_t > jau_PKCS8_decode(Botan::DataSource &source, const std::string &passphrase, Botan::AlgorithmIdentifier &pk_alg_id, bool is_encrypted)
PEM decode and/or decrypt a private key.
Definition crypto0.cpp:239
static const std::string default_hash_algo_
Definition crypto0.cpp:108
static const std::string default_pk_enc_hash_algo
Definition crypto0.cpp:103
std::shared_ptr< Botan::Public_Key > load_public_key(const std::string &pubkey_fname)
Definition crypto0.cpp:183
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::string_view default_hash_algo() noexcept
Name of default hash algo for the plaintext message, e.g.
Definition crypto0.cpp:110
std::vector< T, Botan::secure_allocator< T > > secure_vector
std::string file_suffix(const std::string &algo) noexcept
Return a lower-case file suffix used to store a sha256sum compatible hash signature w/o dot and w/o d...
Definition crypto0.cpp:350
std::unique_ptr< std::vector< uint8_t > > calc(const std::string_view &algo, jau::io::ByteStream &source) noexcept
Return the calculated hash value using given algo name and byte input stream.
Definition crypto0.cpp:388
bool append_to_file(const std::string &out_file, const std::string &hashed_file, const std::string_view &hash_algo, const std::vector< uint8_t > &hash_value) noexcept
Append the hash signature to the text file out_file.
Definition crypto0.cpp:360
bool valid() const noexcept
Definition crypto0.cpp:131
static CryptoConfig getDefault() noexcept
Returns default CryptoConfig.
Definition crypto0.cpp:123
std::string pk_enc_padding_algo
std::string pk_fingerprt_hash_algo
std::string to_string() const noexcept
Definition crypto0.cpp:141