Cipherpack v1.2.0-dirty
A Cryprographic Stream Processor
commandline.cpp
Go to the documentation of this file.
1/*
2 * Author: Sven Gothel <sgothel@jausoft.com>
3 * Copyright (c) 2020 ZAFENA AB
4 */
5
6#include <iostream>
7#include <fstream>
8#include <cassert>
9#include <cinttypes>
10#include <cstring>
11
13
14#include <jau/debug.hpp>
15
16extern "C" {
17 #include <unistd.h>
18}
19
20using namespace jau::fractions_i64_literals;
21
23 private:
24 bool verbose;
25 public:
26 jau::relaxed_atomic_int count_error;
27 jau::relaxed_atomic_int count_header;
28 jau::relaxed_atomic_int count_progress;
29 jau::relaxed_atomic_int count_end;
30 jau::relaxed_atomic_uint64 count_content;
31
32 LoggingCipherpackListener(const bool verbose_) noexcept
33 : verbose(verbose_),
36 {}
37
38 void notifyError(const bool decrypt_mode, const cipherpack::PackHeader& header, const std::string& msg) noexcept override {
40 (void)decrypt_mode;
41 jau::PLAIN_PRINT(true, "CL::Error[%d]: %s, %s", count_error.load(), msg.c_str(), header.to_string(true, true).c_str());
42 }
43
44 bool notifyHeader(const bool decrypt_mode, const cipherpack::PackHeader& header) noexcept override {
46 (void)decrypt_mode;
47 (void)header;
48 if( verbose ) {
49 jau::PLAIN_PRINT(true, "CL::Header[%d]: %s", count_header.load(), header.to_string(true, true).c_str());
50 }
51 return true;
52 }
53
54 bool notifyProgress(const bool decrypt_mode, const uint64_t plaintext_size, const uint64_t bytes_processed) noexcept override {
56 (void)decrypt_mode;
57 (void)plaintext_size;
58 (void)bytes_processed;
59 return true;
60 }
61
62 void notifyEnd(const bool decrypt_mode, const cipherpack::PackHeader& header) noexcept override {
63 count_end++;
64 (void)decrypt_mode;
65 (void)header;
66 if( verbose ) {
67 jau::PLAIN_PRINT(true, "CL::End[%d]: %s", count_end.load(), header.to_string(true, true).c_str());
68 }
69 }
70
71 bool getSendContent(const bool decrypt_mode) const noexcept override {
72 (void)decrypt_mode;
73 return false;
74 }
75
76 bool contentProcessed(const bool decrypt_mode, const content_type ctype, cipherpack::secure_vector<uint8_t>& data, const bool is_final) noexcept override {
77 count_content.fetch_add( data.size() );
78 (void)decrypt_mode;
79 (void)ctype;
80 (void)data;
81 (void)is_final;
82 return true;
83 }
84 std::string toString() const noexcept override {
85 return "CipherpackListener[count[error "+std::to_string(count_error)+
86 ", header "+std::to_string(count_header)+
87 ", progress "+std::to_string(count_progress)+
88 ", content "+std::to_string(count_content)+
89 ", end "+std::to_string(count_end)+"]";
90 }
91};
92typedef std::shared_ptr<LoggingCipherpackListener> LoggingCipherpackListenerRef;
93
94static void print_version() {
95 fprintf(stderr, "Cipherpack Native Version %s (API %s)\n", cipherpack::VERSION, cipherpack::VERSION_API);
96}
97
98static void print_usage(const char* progname) {
100 std::string bname = jau::fs::basename(progname);
101 fprintf(stderr, "Usage %s pack [-epk <enc-pub-key>]+ -ssk <sign-sec-key> [-sskp <sign-sec-key-passphrase>]? "
102 "[-target_path <target-path-filename>]? [-subject <string>]? [-version <file-version-str>]? [-version_parent <file-version-parent-str>]? "
103 "[-hash <plaintext-hash-algo>]? [-hashout <plaintext-hash-outfile>]? [-verbose]? [-out <output-filename>]? [<input-source>]?\n", bname.c_str());
104 fprintf(stderr, "Usage %s unpack [-spk <sign-pub-key>]+ -dsk <dec-sec-key> [-dskp <dec-sec-key-passphrase>]? "
105 "[-hash <plaintext-hash-algo>]? [-hashout <plaintext-hash-outfile>]? [-verbose]? [-out <output-filename>]? [<input-source>]?\n", bname.c_str());
106 fprintf(stderr, "Usage %s hash [-hash <hash-algo>]? [-verbose]? [-out <output-filename>]? [<input-source>]?\n", bname.c_str());
107 fprintf(stderr, "Usage %s hashcheck [-verbose]? [<input-hash-signatures-file>]?\n", bname.c_str());
108}
109
110/**
111 * cipherpack command line tool.
112 *
113 * Examples:
114 * - File based operation
115 * - `cipherpack pack -epk test_keys/terminal_rsa1.pub.pem -ssk test_keys/host_rsa1 -out a.enc plaintext.bin`
116 * - `cipherpack unpack -spk test_keys/host_rsa1.pub.pem -dsk test_keys/terminal_rsa1 -out a.dec a.enc`
117 * - `cipherpack hash -out a.hash jaulib/test_data`
118 * - Pipe based operation
119 * - `cat plaintext.bin | cipherpack pack -epk test_keys/terminal_rsa1.pub.pem -ssk test_keys/host_rsa1 > a.enc`
120 * - `cat a.enc | cipherpack unpack -spk test_keys/host_rsa1.pub.pem -dsk test_keys/terminal_rsa1 > a.dec`
121 * - `cat a.dec | cipherpack hash jaulib/test_data`
122 * - Pipe based full streaming
123 * - `cat plaintext.bin | cipherpack pack -epk test_keys/terminal_rsa1.pub.pem -ssk test_keys/host_rsa1 | cipherpack unpack -spk test_keys/host_rsa1.pub.pem -dsk test_keys/terminal_rsa1 > a.dec`
124 *
125 * @param argc
126 * @param argv
127 * @return
128 */
129int main(int argc, char *argv[])
130{
132#if 0
133 fprintf(stderr, "Called '%s' with %d arguments:\n", (argc>0?argv[0]:"exe"), argc-1);
134 for(int i=1; i<argc; i++) {
135 fprintf(stderr, "[%d] '%s'\n", i, argv[i]);
136 }
137 fprintf(stderr, "\n");
138#endif
139 int argi = 1;
140
141 if( argi >= argc ) {
142 print_usage(argv[0]);
143 return -1;
144 }
145 const std::string command = argv[argi++];
146
147 if( command == "pack") {
148 std::vector<std::string> enc_pub_keys;
149 std::string sign_sec_key_fname;
150 jau::io::secure_string sign_sec_key_passphrase;
151 std::string target_path;
152 std::string subject;
153 std::string plaintext_version = "0";
154 std::string plaintext_version_parent = "0";
155 std::string plaintext_hash_algo(cipherpack::default_hash_algo());
156 std::string plaintext_fname_output; // none
157 bool verbose = false;
158 std::string fname_output = jau::fs::to_named_fd(1); // stdout default
159 std::string fname_input = jau::fs::to_named_fd(0); // stdin default
160 for(; argi < argc; ++argi) {
161 if( 0 == strcmp("-epk", argv[argi]) && argi + 1 < argc ) {
162 enc_pub_keys.emplace_back(argv[++argi] );
163 } else if( 0 == strcmp("-ssk", argv[argi]) && argi + 1 < argc ) {
164 sign_sec_key_fname = argv[++argi];
165 } else if( 0 == strcmp("-sskp", argv[argi]) && argi + 1 < argc ) {
166 char* argv_pp = argv[++argi];
167 size_t pp_len = strlen(argv_pp);
168 sign_sec_key_passphrase = jau::io::secure_string(argv_pp, pp_len);
169 ::explicit_bzero(argv_pp, pp_len);
170 } else if( 0 == strcmp("-target_path", argv[argi]) && argi + 1 < argc ) {
171 target_path = argv[++argi];
172 } else if( 0 == strcmp("-subject", argv[argi]) && argi + 1 < argc ) {
173 subject = argv[++argi];
174 } else if( 0 == strcmp("-version", argv[argi]) && argi + 1 < argc ) {
175 plaintext_version = argv[++argi];
176 } else if( 0 == strcmp("-version_parent", argv[argi]) && argi + 1 < argc ) {
177 plaintext_version_parent = argv[++argi];
178 } else if( 0 == strcmp("-hash", argv[argi]) && argi + 1 < argc ) {
179 plaintext_hash_algo = argv[++argi];
180 } else if( 0 == strcmp("-hashout", argv[argi]) && argi + 1 < argc ) {
181 plaintext_fname_output = argv[++argi];
182 } else if( 0 == strcmp("-out", argv[argi]) && argi + 1 < argc ) {
183 fname_output = argv[++argi];
184 } else if( 0 == strcmp("-verbose", argv[argi]) ) {
185 verbose = true;
187 } else if( argi == argc - 1 ) {
188 fname_input = argv[argi];
189 }
190 }
191 if( target_path.empty() ) {
192 target_path = fname_input;
193 }
194 if( 0 == enc_pub_keys.size() ||
195 sign_sec_key_fname.empty() )
196 {
197 jau::PLAIN_PRINT(true, "Pack: Error: Arguments incomplete");
198 print_usage(argv[0]);
199 return -1;
200 }
201
202 std::unique_ptr<jau::io::ByteInStream> input = jau::io::to_ByteInStream(fname_input); // 20_s default timeout if uri
203 if( nullptr == input ) {
204 jau::PLAIN_PRINT(true, "Pack: Error: source '%s' failed to open", fname_input.c_str());
205 return -1;
206 }
207 LoggingCipherpackListenerRef cpl = std::make_shared<LoggingCipherpackListener>(false);
209 enc_pub_keys, sign_sec_key_fname, sign_sec_key_passphrase,
210 *input, target_path, subject,
211 plaintext_version, plaintext_version_parent,
212 cpl,
213 plaintext_hash_algo, fname_output);
214 if( !plaintext_fname_output.empty() ) {
215 cipherpack::hash_util::append_to_file(plaintext_fname_output, fname_input, ph.plaintext_hash_algo(), ph.plaintext_hash());
216 }
217 if( verbose ) {
218 jau::PLAIN_PRINT(true, "Pack: Encrypted %s to %s", fname_input.c_str(), fname_output.c_str());
219 jau::PLAIN_PRINT(true, "Pack: %s", ph.to_string(true, true).c_str());
220 jau::PLAIN_PRINT(true, "Pack: %s", cpl->toString().c_str());
221 }
222 return ph.isValid() ? 0 : -1;
223 }
224 if( command == "unpack") {
225 std::vector<std::string> sign_pub_keys;
226 std::string dec_sec_key_fname;
227 jau::io::secure_string dec_sec_key_passphrase;
228 std::string plaintext_hash_algo(cipherpack::default_hash_algo());
229 std::string plaintext_fname_output; // none
230 bool verbose = false;
231 std::string fname_output = jau::fs::to_named_fd(1); // stdout default
232 std::string fname_input = jau::fs::to_named_fd(0); // stdin default
233 for(; argi < argc; ++argi) {
234 if( 0 == strcmp("-spk", argv[argi]) && argi + 1 < argc ) {
235 sign_pub_keys.emplace_back(argv[++argi] );
236 } else if( 0 == strcmp("-dsk", argv[argi]) && argi + 1 < argc ) {
237 dec_sec_key_fname = argv[++argi];
238 } else if( 0 == strcmp("-dskp", argv[argi]) && argi + 1 < argc ) {
239 char* argv_pp = argv[++argi];
240 size_t pp_len = strlen(argv_pp);
241 dec_sec_key_passphrase = jau::io::secure_string(argv_pp, pp_len);
242 ::explicit_bzero(argv_pp, pp_len);
243 } else if( 0 == strcmp("-hash", argv[argi]) && argi + 1 < argc ) {
244 plaintext_hash_algo = argv[++argi];
245 } else if( 0 == strcmp("-hashout", argv[argi]) && argi + 1 < argc ) {
246 plaintext_fname_output = argv[++argi];
247 } else if( 0 == strcmp("-out", argv[argi]) && argi + 1 < argc ) {
248 fname_output = argv[++argi];
249 } else if( 0 == strcmp("-verbose", argv[argi]) ) {
250 verbose = true;
252 } else if( argi == argc - 1 ) {
253 fname_input = argv[argi];
254 }
255 }
256 if( 0 == sign_pub_keys.size() ||
257 dec_sec_key_fname.empty() )
258 {
259 jau::PLAIN_PRINT(true, "Unpack: Error: Arguments incomplete");
260 print_usage(argv[0]);
261 return -1;
262 }
263
264 std::unique_ptr<jau::io::ByteInStream> input = jau::io::to_ByteInStream(fname_input); // 20_s default timeout if uri
265 if( nullptr == input ) {
266 jau::PLAIN_PRINT(true, "Unpack: Error: source '%s' failed to open", fname_input.c_str());
267 return -1;
268 }
269 LoggingCipherpackListenerRef cpl = std::make_shared<LoggingCipherpackListener>(false);
270 cipherpack::PackHeader ph = cipherpack::checkSignThenDecrypt(sign_pub_keys, dec_sec_key_fname, dec_sec_key_passphrase,
271 *input,
272 cpl,
273 plaintext_hash_algo, fname_output);
274 if( !plaintext_fname_output.empty() ) {
276 }
277 // dec_sec_key_passphrase.resize(0);
278 if( verbose ) {
279 jau::PLAIN_PRINT(true, "Unpack: Decypted %s to %s", fname_input.c_str(), fname_output.c_str());
280 jau::PLAIN_PRINT(true, "Unpack: %s", ph.to_string(true, true).c_str());
281 jau::PLAIN_PRINT(true, "Unpack: %s", cpl->toString().c_str());
282 }
283 return ph.isValid() ? 0 : -1;
284 }
285 if( command == "hash") {
286 std::string hash_algo(cipherpack::default_hash_algo());
287 bool verbose = false;
288 std::string fname_output = jau::fs::to_named_fd(1); // stdout default
289 std::string fname_input = jau::fs::to_named_fd(0); // stdin default
290 for(; argi < argc; ++argi) {
291 if( 0 == strcmp("-hash", argv[argi]) && argi + 1 < argc ) {
292 hash_algo = argv[++argi];
293 } else if( 0 == strcmp("-out", argv[argi]) && argi + 1 < argc ) {
294 fname_output = argv[++argi];
295 } else if( 0 == strcmp("-verbose", argv[argi]) ) {
296 verbose = true;
298 } else if( argi == argc - 1 ) {
299 fname_input = argv[argi];
300 }
301 }
302 uint64_t bytes_hashed = 0;
303 std::unique_ptr<std::vector<uint8_t>> hash = cipherpack::hash_util::calc(hash_algo, fname_input, bytes_hashed); // 20_s default timeout if uri
304 if( nullptr != hash ) {
305 std::string hash_str = jau::bytesHexString(hash->data(), 0, hash->size(), true /* lsbFirst */, true /* lowerCase */);
306 cipherpack::hash_util::append_to_file(fname_output, fname_input, hash_algo, *hash);
307 if( verbose ) {
308 jau::PLAIN_PRINT(true, "Hash: algo '%s', bytes %s, '%s' of '%s'", hash_algo.c_str(), jau::to_decstring(bytes_hashed).c_str(),
309 hash_str.c_str(), fname_input.c_str());
310 }
311 return 0;
312 }
313 return -1;
314 }
315 if( command == "hashcheck") {
316 bool verbose = false;
317 std::string fname_input = "/dev/stdin"; // stdin default
318 for(; argi < argc; ++argi) {
319 if( 0 == strcmp("-verbose", argv[argi]) ) {
320 verbose = true;
322 } else if( argi == argc - 1 ) {
323 fname_input = argv[argi];
324 }
325 }
326 std::ifstream in(fname_input, std::ios::in | std::ios::binary);
327 if( in.bad() ) {
328 jau::PLAIN_PRINT(true, "HashCheck: Error: Couldn't open file '%s'", fname_input.c_str());
329 return -1;
330 }
331 int line_no = 0;
332 std::string hash_line0;
333 while( std::getline(in, hash_line0) ) {
334 ++line_no;
335 std::string hash_algo;
336 std::string hash_value1;
337 std::string hashed_file;
338 char* haystack = const_cast<char*>( hash_line0.data() );
339 {
340 char* p = std::strstr(haystack, " ");
341 if( nullptr == p ) {
342 jau::PLAIN_PRINT(true, "HashCheck: Error: %s:%d: No separator to hash value found", fname_input.c_str(), line_no);
343 return -1;
344 }
345 *p = 0; // EOS
346 hash_algo = std::string(haystack);
347 haystack = p + 1;
348 }
349 {
350 char* p = std::strstr(haystack, " *");
351 if( nullptr == p ) {
352 jau::PLAIN_PRINT(true, "HashCheck: Error: %s:%d: No separator to hashed file found", fname_input.c_str(), line_no);
353 return -1;
354 }
355 *p = 0; // EOS
356 hash_value1 = std::string(haystack);
357 haystack = p + 2;
358 }
359 hashed_file = std::string(haystack);
360 {
361 jau::fs::file_stats hashed_file_stats(hashed_file);
362 if( hashed_file_stats.has_fd() ) {
363 jau::PLAIN_PRINT(true, "HashCheck: Ignored: %s:%d: Named file descriptor: %s",
364 fname_input.c_str(), line_no, hashed_file_stats.to_string().c_str());
365 continue;
366 }
367 }
368 const std::string hash_line1 = hash_algo+" "+hash_value1+" *"+hashed_file; // NOLINT(performance-inefficient-string-concatenation)
369 uint64_t bytes_hashed = 0;
370 std::unique_ptr<std::vector<uint8_t>> hash2 = cipherpack::hash_util::calc(hash_algo, hashed_file, bytes_hashed); // 20_s default timeout if uri
371 if( nullptr == hash2 ) {
372 jau::PLAIN_PRINT(true, "HashCheck: Error: %s:%d: Bad format: %s", fname_input.c_str(), line_no, hash_line1.c_str());
373 return -1;
374 }
375 const std::string hash_value2 = jau::bytesHexString(hash2->data(), 0, hash2->size(), true /* lsbFirst */, true /* lowerCase */);
376 if( hash_value2 != hash_value1 ) {
377 const std::string hash_line2 = hash_algo+" "+hash_value2+" *"+hashed_file; // NOLINT(performance-inefficient-string-concatenation)
378 jau::PLAIN_PRINT(true, "HashCheck: Error: %s:%d: Hash value mismatch", fname_input.c_str(), line_no);
379 jau::PLAIN_PRINT(true,"- expected: %s", hash_line1.c_str());
380 jau::PLAIN_PRINT(true,"- produced: %s", hash_line2.c_str());
381 return -1;
382 } else if( verbose ) {
383 jau::PLAIN_PRINT(true, "HashCheck: OK: %s:%d: %s", fname_input.c_str(), line_no, hash_line1.c_str());
384 }
385 }
386 return 0;
387 }
388 fprintf(stderr, "Unknown command\n");
389 print_usage(argv[0]);
390 return -1;
391}
void notifyError(const bool decrypt_mode, const cipherpack::PackHeader &header, const std::string &msg) noexcept override
User notification about an error via text message and preliminary PackHeader.
Definition: commandline.cpp:38
bool notifyHeader(const bool decrypt_mode, const cipherpack::PackHeader &header) noexcept override
User notification of preliminary PackHeader w/o optional hash of the plaintext message.
Definition: commandline.cpp:44
std::string toString() const noexcept override
Definition: commandline.cpp:84
jau::relaxed_atomic_int count_header
Definition: commandline.cpp:27
jau::relaxed_atomic_int count_progress
Definition: commandline.cpp:28
jau::relaxed_atomic_int count_end
Definition: commandline.cpp:29
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: commandline.cpp:54
jau::relaxed_atomic_int count_error
Definition: commandline.cpp:26
void notifyEnd(const bool decrypt_mode, const cipherpack::PackHeader &header) noexcept override
User notification of successful completion.
Definition: commandline.cpp:62
LoggingCipherpackListener(const bool verbose_) noexcept
Definition: commandline.cpp:32
jau::relaxed_atomic_uint64 count_content
Definition: commandline.cpp:30
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: commandline.cpp:76
bool getSendContent(const bool decrypt_mode) const noexcept override
User provided information whether process shall send the processed content via contentProcessed() or ...
Definition: commandline.cpp:71
Listener for events occurring while processing a cipherpack message via encryptThenSign() and checkSi...
Definition: cipherpack.hpp:442
Cipherpack header less encrypted keys or signatures as described in Cipherpack Data Stream.
Definition: cipherpack.hpp:275
const std::vector< uint8_t > & plaintext_hash() const noexcept
Return optional hash value of the plaintext message, produced for convenience and not wired.
Definition: cipherpack.hpp:408
const std::string & plaintext_hash_algo() const noexcept
Return optional hash algorithm for the plaintext message, produced for convenience and not wired.
Definition: cipherpack.hpp:398
bool isValid() const noexcept
Definition: cipherpack.hpp:429
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
const std::string & target_path() const noexcept
Returns the designated target path for this plaintext message, see Cipherpack Data Stream.
Definition: cipherpack.hpp:352
static environment & get() noexcept
Definition: cipherpack.hpp:150
int main(int argc, char *argv[])
cipherpack command line tool.
std::shared_ptr< LoggingCipherpackListener > LoggingCipherpackListenerRef
Definition: commandline.cpp:92
static void print_version()
Definition: commandline.cpp:94
static void print_usage(const char *progname)
Definition: commandline.cpp:98
static std::string to_string(const std::vector< uint8_t > &v)
Definition: crypto1.cpp:72
std::vector< T, Botan::secure_allocator< T > > secure_vector
Definition: cipherpack.hpp:166
PackHeader encryptThenSign(const CryptoConfig &crypto_cfg, const std::vector< std::string > &enc_pub_keys, const std::string &sign_sec_key_fname, const jau::io::secure_string &passphrase, jau::io::ByteInStream &source, const std::string &target_path, const std::string &subject, const std::string &plaintext_version, const std::string &plaintext_version_parent, CipherpackListenerRef listener, const std::string_view &plaintext_hash_algo, const std::string destination_fname="")
Encrypt then sign the source producing a cipherpack stream passed to the CipherpackListener if opt-in...
Definition: crypto1.cpp:518
std::string_view default_hash_algo() noexcept
Name of default hash algo for the plaintext message, e.g.
Definition: crypto0.cpp:110
PackHeader checkSignThenDecrypt(const std::vector< std::string > &sign_pub_keys, const std::string &dec_sec_key_fname, const jau::io::secure_string &passphrase, jau::io::ByteInStream &source, CipherpackListenerRef listener, const std::string_view &plaintext_hash_algo, const std::string destination_fname="")
Verify signature then decrypt the source passing to the CipherpackListener if opt-in and also optiona...
Definition: crypto1.cpp:1134
std::unique_ptr< std::vector< uint8_t > > calc(const std::string_view &algo, jau::io::ByteInStream &source) noexcept
Return the calculated hash value using given algo name and byte input stream.
Definition: crypto0.cpp:386
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:358
const char * VERSION_API
const char * VERSION
static CryptoConfig getDefault() noexcept
Returns default CryptoConfig.
Definition: crypto0.cpp:123