1 /*
2 * Copyright (c) 2019-2021, [Ribose Inc](https://www.ribose.com).
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without modification,
6 * are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27 #include "config.h"
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <stdbool.h>
31 #include <errno.h>
32 #include <stdarg.h>
33 #include <stdlib.h>
34 #include <stdio.h>
35 #include <string.h>
36 #include <string>
37 #include <vector>
38 #include <iterator>
39 #include <cassert>
40 #include <ctype.h>
41 #ifdef _MSC_VER
42 #include "uniwin.h"
43 #else
44 #include <sys/param.h>
45 #include <unistd.h>
46 #endif
47
48 #ifndef _WIN32
49 #include <termios.h>
50 #ifdef HAVE_SYS_RESOURCE_H
51 #include <sys/resource.h>
52 #endif
53 #endif
54
55 #include "config.h"
56 #include "fficli.h"
57 #include "str-utils.h"
58 #include "file-utils.h"
59 #include "time-utils.h"
60 #include "defaults.h"
61
62 #ifndef RNP_USE_STD_REGEX
63 #include <regex.h>
64 #else
65 #include <regex>
66 #endif
67
68 #ifdef HAVE_SYS_RESOURCE_H
69 /* When system resource consumption limit controls are available this
70 * can be used to attempt to disable core dumps which may leak
71 * sensitive data.
72 *
73 * Returns false if disabling core dumps failed, returns true if disabling
74 * core dumps succeeded. errno will be set to the result from setrlimit in
75 * the event of failure.
76 */
77 static bool
disable_core_dumps(void)78 disable_core_dumps(void)
79 {
80 struct rlimit limit;
81 int error;
82
83 errno = 0;
84 memset(&limit, 0, sizeof(limit));
85 error = setrlimit(RLIMIT_CORE, &limit);
86
87 if (error == 0) {
88 error = getrlimit(RLIMIT_CORE, &limit);
89 if (error) {
90 ERR_MSG("Warning - cannot turn off core dumps");
91 return false;
92 } else if (limit.rlim_cur == 0) {
93 return true; // disabling core dumps ok
94 } else {
95 return false; // failed for some reason?
96 }
97 }
98 return false;
99 }
100 #endif
101
102 #ifdef _WIN32
103 #include "str-utils.h"
104 #include <windows.h>
105 #include <vector>
106 #include <stdexcept>
107
108 static std::vector<std::string>
get_utf8_args()109 get_utf8_args()
110 {
111 int arg_nb;
112 wchar_t **arg_w;
113
114 arg_w = CommandLineToArgvW(GetCommandLineW(), &arg_nb);
115 if (!arg_w) {
116 throw std::runtime_error("CommandLineToArgvW failed");
117 }
118
119 try {
120 std::vector<std::string> result;
121 result.reserve(arg_nb);
122 for (int i = 0; i < arg_nb; i++) {
123 auto utf8 = wstr_to_utf8(arg_w[i]);
124 result.push_back(utf8);
125 }
126 LocalFree(arg_w);
127 return result;
128 } catch (...) {
129 LocalFree(arg_w);
130 throw;
131 }
132 }
133
134 void
rnp_win_clear_args(int argc,char ** argv)135 rnp_win_clear_args(int argc, char **argv)
136 {
137 for (int i = 0; i < argc; i++) {
138 if (argv[i]) {
139 free(argv[i]);
140 }
141 }
142 delete argv;
143 }
144
145 bool
rnp_win_substitute_cmdline_args(int * argc,char *** argv)146 rnp_win_substitute_cmdline_args(int *argc, char ***argv)
147 {
148 int argc_utf8 = 0;
149 char **argv_utf8_cstrs = NULL;
150 try {
151 auto argv_utf8_strings = get_utf8_args();
152 argc_utf8 = argv_utf8_strings.size();
153 *argc = argc_utf8;
154 argv_utf8_cstrs = new (std::nothrow) char *[argc_utf8]();
155 if (!argv_utf8_cstrs) {
156 throw std::bad_alloc();
157 }
158 for (int i = 0; i < argc_utf8; i++) {
159 auto arg_utf8 = strdup(argv_utf8_strings[i].c_str());
160 if (!arg_utf8) {
161 throw std::bad_alloc();
162 }
163 argv_utf8_cstrs[i] = arg_utf8;
164 }
165 } catch (...) {
166 if (argv_utf8_cstrs) {
167 rnp_win_clear_args(argc_utf8, argv_utf8_cstrs);
168 }
169 throw;
170 }
171 *argc = argc_utf8;
172 *argv = argv_utf8_cstrs;
173 return true;
174 }
175 #endif
176
177 static bool
set_pass_fd(FILE ** file,int passfd)178 set_pass_fd(FILE **file, int passfd)
179 {
180 if (!file) {
181 return false;
182 }
183 *file = fdopen(passfd, "r");
184 if (!*file) {
185 ERR_MSG("cannot open fd %d for reading", passfd);
186 return false;
187 }
188 return true;
189 }
190
191 static char *
ptimestr(char * dest,size_t size,time_t t)192 ptimestr(char *dest, size_t size, time_t t)
193 {
194 struct tm *tm;
195
196 tm = rnp_gmtime(t);
197 (void) snprintf(dest,
198 size,
199 "%s%04d-%02d-%02d",
200 rnp_y2k38_warning(t) ? ">=" : "",
201 tm->tm_year + 1900,
202 tm->tm_mon + 1,
203 tm->tm_mday);
204 return dest;
205 }
206
207 static bool
cli_rnp_get_confirmation(const cli_rnp_t * rnp,const char * msg,...)208 cli_rnp_get_confirmation(const cli_rnp_t *rnp, const char *msg, ...)
209 {
210 char reply[10];
211 va_list ap;
212
213 while (true) {
214 va_start(ap, msg);
215 vfprintf(rnp->userio_out, msg, ap);
216 va_end(ap);
217 fprintf(rnp->userio_out, " (y/N) ");
218 fflush(rnp->userio_out);
219
220 if (fgets(reply, sizeof(reply), rnp->userio_in) == NULL) {
221 return false;
222 }
223
224 rnp::strip_eol(reply);
225
226 if (strlen(reply) > 0) {
227 if (toupper(reply[0]) == 'Y') {
228 return true;
229 } else if (toupper(reply[0]) == 'N') {
230 return false;
231 }
232
233 fprintf(rnp->userio_out, "Sorry, response '%s' not understood.\n", reply);
234 } else {
235 return false;
236 }
237 }
238
239 return false;
240 }
241
242 static bool
rnp_ask_filename(const std::string & msg,std::string & res,cli_rnp_t & rnp)243 rnp_ask_filename(const std::string &msg, std::string &res, cli_rnp_t &rnp)
244 {
245 fprintf(rnp.userio_out, "%s", msg.c_str());
246 fflush(rnp.userio_out);
247 char fname[128] = {0};
248 std::string path;
249 do {
250 if (!fgets(fname, sizeof(fname), rnp.userio_in)) {
251 return false;
252 }
253 path = path + std::string(fname);
254 if (rnp::strip_eol(path)) {
255 res = path;
256 return true;
257 }
258 if (path.size() >= 2048) {
259 fprintf(rnp.userio_out, "%s", "Too long filename, aborting.");
260 fflush(rnp.userio_out);
261 return false;
262 }
263 } while (1);
264 }
265
266 /** @brief checks whether file exists already and asks user for the new filename
267 * @param path output file name with path. May be an empty string, then user is asked for it.
268 * @param res resulting output path will be stored here.
269 * @param rnp initialized cli_rnp_t structure with additional data
270 * @return true on success, or false otherwise (user cancels the operation)
271 **/
272
273 static bool
rnp_get_output_filename(const std::string & path,std::string & res,cli_rnp_t & rnp)274 rnp_get_output_filename(const std::string &path, std::string &res, cli_rnp_t &rnp)
275 {
276 std::string newpath = path;
277 if (newpath.empty() &&
278 !rnp_ask_filename("Please enter the output filename: ", newpath, rnp)) {
279 return false;
280 }
281
282 while (true) {
283 if (!rnp_file_exists(newpath.c_str())) {
284 res = newpath;
285 return true;
286 }
287 if (rnp.cfg().get_bool(CFG_OVERWRITE) ||
288 cli_rnp_get_confirmation(
289 &rnp,
290 "File '%s' already exists. Would you like to overwrite it?",
291 newpath.c_str())) {
292 rnp_unlink(newpath.c_str());
293 res = newpath;
294 return true;
295 }
296
297 if (!rnp_ask_filename("Please enter the new filename: ", newpath, rnp)) {
298 return false;
299 }
300 if (newpath.empty()) {
301 return false;
302 }
303 }
304 }
305
306 static bool
stdin_getpass(const char * prompt,char * buffer,size_t size,cli_rnp_t * rnp)307 stdin_getpass(const char *prompt, char *buffer, size_t size, cli_rnp_t *rnp)
308 {
309 #ifndef _WIN32
310 struct termios saved_flags, noecho_flags;
311 bool restore_ttyflags = false;
312 #endif
313 bool ok = false;
314 FILE *in = NULL;
315 FILE *out = NULL;
316 FILE *userio_in = (rnp && rnp->userio_in) ? rnp->userio_in : stdin;
317
318 // validate args
319 if (!buffer) {
320 goto end;
321 }
322 // doesn't hurt
323 *buffer = '\0';
324
325 #ifndef _WIN32
326 in = fopen("/dev/tty", "w+ce");
327 #endif
328 if (!in) {
329 in = userio_in;
330 out = stderr;
331 } else {
332 out = in;
333 }
334
335 // TODO: Implement alternative for hiding password entry on Windows
336 // TODO: avoid duplicate termios code with pass-provider.cpp
337 #ifndef _WIN32
338 // save the original termios
339 if (tcgetattr(fileno(in), &saved_flags) == 0) {
340 noecho_flags = saved_flags;
341 // disable echo in the local modes
342 noecho_flags.c_lflag = (noecho_flags.c_lflag & ~ECHO) | ECHONL | ISIG;
343 restore_ttyflags = (tcsetattr(fileno(in), TCSANOW, &noecho_flags) == 0);
344 }
345 #endif
346 if (prompt) {
347 fputs(prompt, out);
348 }
349 if (fgets(buffer, size, in) == NULL) {
350 goto end;
351 }
352
353 rnp::strip_eol(buffer);
354 ok = true;
355 end:
356 #ifndef _WIN32
357 if (restore_ttyflags) {
358 tcsetattr(fileno(in), TCSAFLUSH, &saved_flags);
359 }
360 #endif
361 if (in && (in != userio_in)) {
362 fclose(in);
363 }
364 return ok;
365 }
366
367 static bool
ffi_pass_callback_stdin(rnp_ffi_t ffi,void * app_ctx,rnp_key_handle_t key,const char * pgp_context,char buf[],size_t buf_len)368 ffi_pass_callback_stdin(rnp_ffi_t ffi,
369 void * app_ctx,
370 rnp_key_handle_t key,
371 const char * pgp_context,
372 char buf[],
373 size_t buf_len)
374 {
375 char * keyid = NULL;
376 char target[64] = {0};
377 char prompt[128] = {0};
378 char * buffer = NULL;
379 bool ok = false;
380 cli_rnp_t *rnp = static_cast<cli_rnp_t *>(app_ctx);
381
382 if (!ffi || !pgp_context) {
383 goto done;
384 }
385
386 if (strcmp(pgp_context, "decrypt (symmetric)") &&
387 strcmp(pgp_context, "encrypt (symmetric)")) {
388 rnp_key_get_keyid(key, &keyid);
389 snprintf(target, sizeof(target), "key 0x%s", keyid);
390 rnp_buffer_destroy(keyid);
391 }
392 buffer = (char *) calloc(1, buf_len);
393 if (!buffer) {
394 return false;
395 }
396 start:
397 if (!strcmp(pgp_context, "decrypt (symmetric)")) {
398 snprintf(prompt, sizeof(prompt), "Enter password to decrypt data: ");
399 } else if (!strcmp(pgp_context, "encrypt (symmetric)")) {
400 snprintf(prompt, sizeof(prompt), "Enter password to encrypt data: ");
401 } else {
402 snprintf(prompt, sizeof(prompt), "Enter password for %s: ", target);
403 }
404
405 if (!stdin_getpass(prompt, buf, buf_len, rnp)) {
406 goto done;
407 }
408 if (!strcmp(pgp_context, "protect") || !strcmp(pgp_context, "encrypt (symmetric)")) {
409 if (!strcmp(pgp_context, "protect")) {
410 snprintf(prompt, sizeof(prompt), "Repeat password for %s: ", target);
411 } else {
412 snprintf(prompt, sizeof(prompt), "Repeat password: ");
413 }
414
415 if (!stdin_getpass(prompt, buffer, buf_len, rnp)) {
416 goto done;
417 }
418 if (strcmp(buf, buffer) != 0) {
419 fputs("\nPasswords do not match!", rnp->userio_out);
420 // currently will loop forever
421 goto start;
422 }
423 }
424 ok = true;
425 done:
426 fputs("", rnp->userio_out);
427 rnp_buffer_clear(buffer, buf_len);
428 free(buffer);
429 return ok;
430 }
431
432 static bool
ffi_pass_callback_file(rnp_ffi_t ffi,void * app_ctx,rnp_key_handle_t key,const char * pgp_context,char buf[],size_t buf_len)433 ffi_pass_callback_file(rnp_ffi_t ffi,
434 void * app_ctx,
435 rnp_key_handle_t key,
436 const char * pgp_context,
437 char buf[],
438 size_t buf_len)
439 {
440 if (!app_ctx || !buf || !buf_len) {
441 return false;
442 }
443
444 FILE *fp = (FILE *) app_ctx;
445 if (!fgets(buf, buf_len, fp)) {
446 return false;
447 }
448 rnp::strip_eol(buf);
449 return true;
450 }
451
452 static bool
ffi_pass_callback_string(rnp_ffi_t ffi,void * app_ctx,rnp_key_handle_t key,const char * pgp_context,char buf[],size_t buf_len)453 ffi_pass_callback_string(rnp_ffi_t ffi,
454 void * app_ctx,
455 rnp_key_handle_t key,
456 const char * pgp_context,
457 char buf[],
458 size_t buf_len)
459 {
460 if (!app_ctx || !buf || !buf_len) {
461 return false;
462 }
463
464 const char *pswd = (const char *) app_ctx;
465 if (strlen(pswd) >= buf_len) {
466 return false;
467 }
468
469 strncpy(buf, pswd, buf_len);
470 return true;
471 }
472
473 bool
init(const rnp_cfg & cfg)474 cli_rnp_t::init(const rnp_cfg &cfg)
475 {
476 cfg_.copy(cfg);
477
478 /* Configure user's io streams. */
479 if (!cfg_.get_bool(CFG_NOTTY)) {
480 userio_in = (isatty(fileno(stdin)) ? stdin : fopen("/dev/tty", "r"));
481 userio_in = (userio_in ? userio_in : stdin);
482 userio_out = (isatty(fileno(stdout)) ? stdout : fopen("/dev/tty", "a+"));
483 userio_out = (userio_out ? userio_out : stdout);
484 } else {
485 userio_in = stdin;
486 userio_out = stdout;
487 }
488
489 #ifndef _WIN32
490 /* If system resource constraints are in effect then attempt to
491 * disable core dumps.
492 */
493 bool coredumps = true;
494 if (!cfg_.get_bool(CFG_COREDUMPS)) {
495 #ifdef HAVE_SYS_RESOURCE_H
496 coredumps = !disable_core_dumps();
497 #endif
498 }
499
500 if (coredumps) {
501 ERR_MSG("warning: core dumps may be enabled, sensitive data may be leaked to disk");
502 }
503 #endif
504
505 /* Configure the results stream. */
506 // TODO: UTF8?
507 const std::string &ress = cfg_.get_str(CFG_IO_RESS);
508 if (ress.empty() || (ress == "<stderr>")) {
509 resfp = stderr;
510 } else if (ress == "<stdout>") {
511 resfp = stdout;
512 } else if (!(resfp = rnp_fopen(ress.c_str(), "w"))) {
513 ERR_MSG("cannot open results %s for writing", ress.c_str());
514 return false;
515 }
516
517 bool res = false;
518 const std::string pformat = pubformat();
519 const std::string sformat = secformat();
520 if (pformat.empty() || sformat.empty()) {
521 ERR_MSG("Unknown public or secret keyring format");
522 return false;
523 }
524 if (rnp_ffi_create(&ffi, pformat.c_str(), sformat.c_str())) {
525 ERR_MSG("failed to initialize FFI");
526 return false;
527 }
528
529 // by default use stdin password provider
530 if (rnp_ffi_set_pass_provider(ffi, ffi_pass_callback_stdin, this)) {
531 goto done;
532 }
533
534 // setup file/pipe password input if requested
535 if (cfg_.get_int(CFG_PASSFD, -1) >= 0) {
536 if (!set_pass_fd(&passfp, cfg_.get_int(CFG_PASSFD))) {
537 goto done;
538 }
539 if (rnp_ffi_set_pass_provider(ffi, ffi_pass_callback_file, passfp)) {
540 goto done;
541 }
542 }
543 pswdtries = MAX_PASSWORD_ATTEMPTS;
544 res = true;
545 done:
546 if (!res) {
547 rnp_ffi_destroy(ffi);
548 ffi = NULL;
549 }
550 return res;
551 }
552
553 void
end()554 cli_rnp_t::end()
555 {
556 if (passfp) {
557 fclose(passfp);
558 passfp = NULL;
559 }
560 if (resfp && (resfp != stderr) && (resfp != stdout)) {
561 fclose(resfp);
562 resfp = NULL;
563 }
564 if (userio_in && userio_in != stdin) {
565 fclose(userio_in);
566 }
567 userio_in = NULL;
568 if (userio_out && userio_out != stdout) {
569 fclose(userio_out);
570 }
571 userio_out = NULL;
572 rnp_ffi_destroy(ffi);
573 ffi = NULL;
574 cfg_.clear();
575 }
576
577 bool
load_keyring(bool secret)578 cli_rnp_t::load_keyring(bool secret)
579 {
580 const char *path = secret ? secpath().c_str() : pubpath().c_str();
581 bool dir = secret && (secformat() == RNP_KEYSTORE_G10);
582 if (dir && !rnp_dir_exists(path)) {
583 ERR_MSG("warning: keyring directory at '%s' doesn't exist.", path);
584 return true;
585 }
586 if (!dir && !rnp_file_exists(path)) {
587 ERR_MSG("warning: keyring at path '%s' doesn't exist.", path);
588 return true;
589 }
590
591 rnp_input_t keyin = NULL;
592 if (rnp_input_from_path(&keyin, path)) {
593 ERR_MSG("warning: failed to open keyring at path '%s' for reading.", path);
594 return true;
595 }
596
597 const char * format = secret ? secformat().c_str() : pubformat().c_str();
598 uint32_t flags = secret ? RNP_LOAD_SAVE_SECRET_KEYS : RNP_LOAD_SAVE_PUBLIC_KEYS;
599 rnp_result_t ret = rnp_load_keys(ffi, format, keyin, flags);
600 if (ret) {
601 ERR_MSG("error: failed to load keyring from '%s'", path);
602 }
603 rnp_input_destroy(keyin);
604
605 if (ret) {
606 return false;
607 }
608
609 size_t keycount = 0;
610 if (secret) {
611 (void) rnp_get_secret_key_count(ffi, &keycount);
612 } else {
613 (void) rnp_get_public_key_count(ffi, &keycount);
614 }
615 if (!keycount) {
616 ERR_MSG("warning: no keys were loaded from the keyring '%s'.", path);
617 }
618 return true;
619 }
620
621 bool
load_keyrings(bool loadsecret)622 cli_rnp_t::load_keyrings(bool loadsecret)
623 {
624 /* Read public keys */
625 if (rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC)) {
626 ERR_MSG("failed to clear public keyring");
627 return false;
628 }
629
630 if (!load_keyring(false)) {
631 return false;
632 }
633
634 /* Only read secret keys if we need to */
635 if (loadsecret) {
636 if (rnp_unload_keys(ffi, RNP_KEY_UNLOAD_SECRET)) {
637 ERR_MSG("failed to clear secret keyring");
638 return false;
639 }
640
641 if (!load_keyring(true)) {
642 return false;
643 }
644 }
645 if (defkey().empty()) {
646 set_defkey();
647 }
648 return true;
649 }
650
651 void
set_defkey()652 cli_rnp_t::set_defkey()
653 {
654 rnp_identifier_iterator_t it = NULL;
655 rnp_key_handle_t handle = NULL;
656 const char * grip = NULL;
657
658 cfg_.unset(CFG_KR_DEF_KEY);
659 if (rnp_identifier_iterator_create(ffi, &it, "grip")) {
660 ERR_MSG("failed to create key iterator");
661 return;
662 }
663
664 while (!rnp_identifier_iterator_next(it, &grip)) {
665 bool is_subkey = false;
666 bool is_secret = false;
667
668 if (!grip) {
669 break;
670 }
671 if (rnp_locate_key(ffi, "grip", grip, &handle)) {
672 ERR_MSG("failed to locate key");
673 continue;
674 }
675 if (rnp_key_is_sub(handle, &is_subkey) || is_subkey) {
676 goto next;
677 }
678 if (rnp_key_have_secret(handle, &is_secret)) {
679 goto next;
680 }
681 if (!cfg_.has(CFG_KR_DEF_KEY) || is_secret) {
682 cfg_.set_str(CFG_KR_DEF_KEY, grip);
683 /* if we have secret primary key then use it as default */
684 if (is_secret) {
685 break;
686 }
687 }
688 next:
689 rnp_key_handle_destroy(handle);
690 handle = NULL;
691 }
692 rnp_key_handle_destroy(handle);
693 rnp_identifier_iterator_destroy(it);
694 }
695
696 bool
is_cv25519_subkey(rnp_key_handle_t handle)697 cli_rnp_t::is_cv25519_subkey(rnp_key_handle_t handle)
698 {
699 bool primary = false;
700 if (rnp_key_is_primary(handle, &primary)) {
701 ERR_MSG("Error: failed to check for subkey.");
702 return false;
703 }
704 if (primary) {
705 return false;
706 }
707 char *alg = NULL;
708 if (rnp_key_get_alg(handle, &alg)) {
709 ERR_MSG("Error: failed to check key's alg.");
710 return false;
711 }
712 bool ecdh = !strcmp(alg, RNP_ALGNAME_ECDH);
713 rnp_buffer_destroy(alg);
714 if (!ecdh) {
715 return false;
716 }
717 char *curve = NULL;
718 if (rnp_key_get_curve(handle, &curve)) {
719 ERR_MSG("Error: failed to check key's curve.");
720 return false;
721 }
722 bool cv25519 = !strcmp(curve, "Curve25519");
723 rnp_buffer_destroy(curve);
724 return cv25519;
725 }
726
727 bool
get_protection(rnp_key_handle_t handle,std::string & hash,std::string & cipher,size_t & iterations)728 cli_rnp_t::get_protection(rnp_key_handle_t handle,
729 std::string & hash,
730 std::string & cipher,
731 size_t & iterations)
732 {
733 bool prot = false;
734 if (rnp_key_is_protected(handle, &prot)) {
735 ERR_MSG("Error: failed to check key's protection.");
736 return false;
737 }
738 if (!prot) {
739 hash = "";
740 cipher = "";
741 iterations = 0;
742 return true;
743 }
744
745 char *val = NULL;
746 try {
747 if (rnp_key_get_protection_hash(handle, &val)) {
748 ERR_MSG("Error: failed to retrieve key's protection hash.");
749 return false;
750 }
751 hash = val;
752 rnp_buffer_destroy(val);
753 if (rnp_key_get_protection_cipher(handle, &val)) {
754 ERR_MSG("Error: failed to retrieve key's protection cipher.");
755 return false;
756 }
757 cipher = val;
758 rnp_buffer_destroy(val);
759 if (rnp_key_get_protection_iterations(handle, &iterations)) {
760 ERR_MSG("Error: failed to retrieve key's protection iterations.");
761 return false;
762 }
763 return true;
764 } catch (const std::exception &e) {
765 ERR_MSG("Error: failed to retrieve key's properties: %s", e.what());
766 rnp_buffer_destroy(val);
767 return false;
768 }
769 }
770
771 bool
check_cv25519_bits(rnp_key_handle_t key,char * prot_password,bool & tweaked)772 cli_rnp_t::check_cv25519_bits(rnp_key_handle_t key, char *prot_password, bool &tweaked)
773 {
774 /* unlock key first to check whether bits are tweaked */
775 if (prot_password && rnp_key_unlock(key, prot_password)) {
776 ERR_MSG("Error: failed to unlock key. Did you specify valid password?");
777 return false;
778 }
779 rnp_result_t ret = rnp_key_25519_bits_tweaked(key, &tweaked);
780 if (ret) {
781 ERR_MSG("Error: failed to check whether key's bits are tweaked.");
782 }
783 if (prot_password) {
784 rnp_key_lock(key);
785 }
786 return !ret;
787 }
788
789 bool
fix_cv25519_subkey(const std::string & key,bool checkonly)790 cli_rnp_t::fix_cv25519_subkey(const std::string &key, bool checkonly)
791 {
792 std::vector<rnp_key_handle_t> keys;
793 if (!cli_rnp_keys_matching_string(
794 this, keys, key, CLI_SEARCH_SECRET | CLI_SEARCH_SUBKEYS)) {
795 ERR_MSG("Secret keys matching '%s' not found.", key.c_str());
796 return false;
797 }
798 bool res = false;
799 std::string prot_hash;
800 std::string prot_cipher;
801 size_t prot_iterations;
802 char * prot_password = NULL;
803 bool tweaked = false;
804
805 if (keys.size() > 1) {
806 ERR_MSG(
807 "Ambiguous input: too many keys found for '%s'. Did you use keyid or fingerprint?",
808 key.c_str());
809 goto done;
810 }
811 cli_rnp_print_key_info(userio_out, ffi, keys[0], true, false);
812 if (!is_cv25519_subkey(keys[0])) {
813 ERR_MSG("Error: specified key is not Curve25519 ECDH subkey.");
814 goto done;
815 }
816
817 if (!get_protection(keys[0], prot_hash, prot_cipher, prot_iterations)) {
818 goto done;
819 }
820
821 if (!prot_hash.empty() &&
822 (rnp_request_password(ffi, keys[0], "unprotect", &prot_password) || !prot_password)) {
823 ERR_MSG("Error: failed to obtain protection password.");
824 goto done;
825 }
826
827 if (!check_cv25519_bits(keys[0], prot_password, tweaked)) {
828 goto done;
829 }
830
831 if (checkonly) {
832 fprintf(userio_out,
833 tweaked ? "Cv25519 key bits are set correctly and do not require fixing.\n" :
834 "Warning: Cv25519 key bits need fixing.\n");
835 res = tweaked;
836 goto done;
837 }
838
839 if (tweaked) {
840 ERR_MSG("Warning: key's bits are fixed already, no action is required.");
841 res = true;
842 goto done;
843 }
844
845 /* now unprotect so we can tweak bits */
846 if (!prot_hash.empty()) {
847 if (rnp_key_unprotect(keys[0], prot_password)) {
848 ERR_MSG("Error: failed to unprotect key. Did you specify valid password?");
849 goto done;
850 }
851 if (rnp_key_unlock(keys[0], NULL)) {
852 ERR_MSG("Error: failed to unlock key.");
853 goto done;
854 }
855 }
856
857 /* tweak key bits and protect back */
858 if (rnp_key_25519_bits_tweak(keys[0])) {
859 ERR_MSG("Error: failed to tweak key's bits.");
860 goto done;
861 }
862
863 if (!prot_hash.empty() && rnp_key_protect(keys[0],
864 prot_password,
865 prot_cipher.c_str(),
866 NULL,
867 prot_hash.c_str(),
868 prot_iterations)) {
869 ERR_MSG("Error: failed to protect key back.");
870 goto done;
871 }
872
873 res = cli_rnp_save_keyrings(this);
874 done:
875 clear_key_handles(keys);
876 if (prot_password) {
877 rnp_buffer_clear(prot_password, strlen(prot_password) + 1);
878 rnp_buffer_destroy(prot_password);
879 }
880 return res;
881 }
882
883 bool
edit_key(const std::string & key)884 cli_rnp_t::edit_key(const std::string &key)
885 {
886 if (cfg().get_bool(CFG_CHK_25519_BITS)) {
887 return fix_cv25519_subkey(key, true);
888 }
889 if (cfg().get_bool(CFG_FIX_25519_BITS)) {
890 return fix_cv25519_subkey(key, false);
891 }
892
893 /* more options, like --passwd, --unprotect, --expiration are to come */
894 ERR_MSG("You should specify at least one editing option for --edit-key.");
895 return false;
896 }
897
898 const char *
json_obj_get_str(json_object * obj,const char * key)899 json_obj_get_str(json_object *obj, const char *key)
900 {
901 json_object *fld = NULL;
902 if (!json_object_object_get_ex(obj, key, &fld)) {
903 return NULL;
904 }
905 return json_object_get_string(fld);
906 }
907
908 int64_t
json_obj_get_int64(json_object * obj,const char * key)909 json_obj_get_int64(json_object *obj, const char *key)
910 {
911 json_object *fld = NULL;
912 if (!json_object_object_get_ex(obj, key, &fld)) {
913 return 0;
914 }
915 return json_object_get_int64(fld);
916 }
917
918 bool
rnp_casecmp(const std::string & str1,const std::string & str2)919 rnp_casecmp(const std::string &str1, const std::string &str2)
920 {
921 if (str1.size() != str2.size()) {
922 return false;
923 }
924
925 for (size_t i = 0; i < str1.size(); i++) {
926 if (tolower(str1[i]) != tolower(str2[i])) {
927 return false;
928 }
929 }
930 return true;
931 }
932
933 static char *
cli_key_usage_str(rnp_key_handle_t key,char * buf)934 cli_key_usage_str(rnp_key_handle_t key, char *buf)
935 {
936 char *orig = buf;
937 bool allow = false;
938
939 if (!rnp_key_allows_usage(key, "encrypt", &allow) && allow) {
940 *buf++ = 'E';
941 }
942 allow = false;
943 if (!rnp_key_allows_usage(key, "sign", &allow) && allow) {
944 *buf++ = 'S';
945 }
946 allow = false;
947 if (!rnp_key_allows_usage(key, "certify", &allow) && allow) {
948 *buf++ = 'C';
949 }
950 allow = false;
951 if (!rnp_key_allows_usage(key, "authenticate", &allow) && allow) {
952 *buf++ = 'A';
953 }
954 *buf = '\0';
955 return orig;
956 }
957
958 std::string
cli_rnp_escape_string(const std::string & src)959 cli_rnp_escape_string(const std::string &src)
960 {
961 static const int SPECIAL_CHARS_COUNT = 0x20;
962 static const char *escape_map[SPECIAL_CHARS_COUNT + 1] = {
963 "\\x00", "\\x01", "\\x02", "\\x03", "\\x04", "\\x05", "\\x06", "\\x07",
964 "\\b", "\\x09", "\\n", "\\v", "\\f", "\\r", "\\x0e", "\\x0f",
965 "\\x10", "\\x11", "\\x12", "\\x13", "\\x14", "\\x15", "\\x16", "\\x17",
966 "\\x18", "\\x19", "\\x1a", "\\x1b", "\\x1c", "\\x1d", "\\x1e", "\\x1f",
967 "\\x20" // space should not be auto-replaced
968 };
969 std::string result;
970 // we want to replace leading and trailing spaces with escape codes to make them visible
971 auto original_len = src.length();
972 std::string rtrimmed = src;
973 bool leading_space = true;
974 rtrimmed.erase(rtrimmed.find_last_not_of(0x20) + 1);
975 result.reserve(original_len);
976 for (char const &c : rtrimmed) {
977 leading_space &= c == 0x20;
978 if (leading_space || (c >= 0 && c < SPECIAL_CHARS_COUNT)) {
979 result.append(escape_map[(int) c]);
980 } else {
981 result.push_back(c);
982 }
983 }
984 // printing trailing spaces
985 for (auto pos = rtrimmed.length(); pos < original_len; pos++) {
986 result.append(escape_map[0x20]);
987 }
988 return result;
989 }
990
991 static const std::string alg_aliases[] = {
992 "3DES", "TRIPLEDES", "3-DES", "TRIPLEDES", "CAST-5", "CAST5",
993 "AES", "AES128", "AES-128", "AES128", "AES-192", "AES192",
994 "AES-256", "AES256", "CAMELLIA-128", "CAMELLIA128", "CAMELLIA-192", "CAMELLIA192",
995 "CAMELLIA-256", "CAMELLIA256", "SHA", "SHA1", "SHA-1", "SHA1",
996 "SHA-224", "SHA224", "SHA-256", "SHA256", "SHA-384", "SHA384",
997 "SHA-512", "SHA512", "RIPEMD-160", "RIPEMD160"};
998
999 const std::string
cli_rnp_alg_to_ffi(const std::string alg)1000 cli_rnp_alg_to_ffi(const std::string alg)
1001 {
1002 size_t count = sizeof(alg_aliases) / sizeof(alg_aliases[0]);
1003 assert((count % 2) == 0);
1004 for (size_t idx = 0; idx < count; idx += 2) {
1005 if (rnp_casecmp(alg, alg_aliases[idx])) {
1006 return alg_aliases[idx + 1];
1007 }
1008 }
1009 return alg;
1010 }
1011
1012 #ifndef RNP_USE_STD_REGEX
1013 static std::string
cli_rnp_unescape_for_regcomp(const std::string & src)1014 cli_rnp_unescape_for_regcomp(const std::string &src)
1015 {
1016 std::string result;
1017 result.reserve(src.length());
1018 regex_t r = {};
1019 regmatch_t matches[1];
1020 if (regcomp(&r, "\\\\x[0-9a-f]([0-9a-f])?", REG_EXTENDED | REG_ICASE) != 0)
1021 return src;
1022
1023 int offset = 0;
1024 while (regexec(&r, src.c_str() + offset, 1, matches, 0) == 0) {
1025 result.append(src, offset, matches[0].rm_so);
1026 int hexoff = matches[0].rm_so + 2;
1027 std::string hex;
1028 hex.push_back(src[offset + hexoff]);
1029 if (hexoff + 1 < matches[0].rm_eo) {
1030 hex.push_back(src[offset + hexoff + 1]);
1031 }
1032 char decoded = stoi(hex, 0, 16);
1033 if ((decoded >= 0x7B && decoded <= 0x7D) || (decoded >= 0x24 && decoded <= 0x2E) ||
1034 decoded == 0x5C || decoded == 0x5E) {
1035 result.push_back('\\');
1036 result.push_back(decoded);
1037 } else if ((decoded == '[' || decoded == ']') &&
1038 /* not enclosed in [] */ (result.empty() || result.back() != '[')) {
1039 result.push_back('[');
1040 result.push_back(decoded);
1041 result.push_back(']');
1042 } else {
1043 result.push_back(decoded);
1044 }
1045 offset += matches[0].rm_eo;
1046 }
1047
1048 result.append(src.begin() + offset, src.end());
1049
1050 return result;
1051 }
1052 #endif
1053
1054 void
cli_rnp_print_key_info(FILE * fp,rnp_ffi_t ffi,rnp_key_handle_t key,bool psecret,bool psigs)1055 cli_rnp_print_key_info(FILE *fp, rnp_ffi_t ffi, rnp_key_handle_t key, bool psecret, bool psigs)
1056 {
1057 char buf[64] = {0};
1058 const char * header = NULL;
1059 bool secret = false;
1060 bool primary = false;
1061 bool revoked = false;
1062 uint32_t bits = 0;
1063 int64_t create = 0;
1064 uint32_t expiry = 0;
1065 size_t uids = 0;
1066 char * json = NULL;
1067 json_object *pkts = NULL;
1068 json_object *keypkt = NULL;
1069
1070 /* header */
1071 if (rnp_key_have_secret(key, &secret) || rnp_key_is_primary(key, &primary) ||
1072 rnp_key_packets_to_json(key, false, RNP_JSON_DUMP_GRIP, &json)) {
1073 fprintf(fp, "Key error.\n");
1074 return;
1075 }
1076 if (!(pkts = json_tokener_parse(json))) {
1077 fprintf(fp, "Key JSON error.\n");
1078 goto done;
1079 }
1080 if (!(keypkt = json_object_array_get_idx(pkts, 0))) {
1081 fprintf(fp, "Key JSON error.\n");
1082 goto done;
1083 }
1084
1085 if (psecret && secret) {
1086 header = primary ? "sec" : "ssb";
1087 } else {
1088 header = primary ? "pub" : "sub";
1089 }
1090 if (primary) {
1091 fprintf(fp, "\n");
1092 }
1093 fprintf(fp, "%s ", header);
1094
1095 /* key bits */
1096 rnp_key_get_bits(key, &bits);
1097 fprintf(fp, "%d/", (int) bits);
1098 /* key algorithm */
1099 fprintf(fp, "%s ", json_obj_get_str(keypkt, "algorithm.str"));
1100 /* key id */
1101 fprintf(fp, "%s", json_obj_get_str(keypkt, "keyid"));
1102 /* key creation time */
1103 create = json_obj_get_int64(keypkt, "creation time");
1104 fprintf(fp, " %s", ptimestr(buf, sizeof(buf), create));
1105 /* key usage */
1106 fprintf(fp, " [%s]", cli_key_usage_str(key, buf));
1107 /* key expiration */
1108 (void) rnp_key_get_expiration(key, &expiry);
1109 if (expiry > 0) {
1110 uint32_t now = time(NULL);
1111 auto expire_time = create + expiry;
1112 ptimestr(buf, sizeof(buf), expire_time);
1113 fprintf(fp, " [%s %s]", expire_time <= now ? "EXPIRED" : "EXPIRES", buf);
1114 }
1115 /* key is revoked */
1116 (void) rnp_key_is_revoked(key, &revoked);
1117 if (revoked) {
1118 fprintf(fp, " [REVOKED]");
1119 }
1120 /* fingerprint */
1121 fprintf(fp, "\n %s\n", json_obj_get_str(keypkt, "fingerprint"));
1122 /* user ids */
1123 (void) rnp_key_get_uid_count(key, &uids);
1124 for (size_t i = 0; i < uids; i++) {
1125 rnp_uid_handle_t uid = NULL;
1126 bool revoked = false;
1127 char * uid_str = NULL;
1128 size_t sigs = 0;
1129
1130 if (rnp_key_get_uid_handle_at(key, i, &uid)) {
1131 continue;
1132 }
1133 (void) rnp_uid_is_revoked(uid, &revoked);
1134 (void) rnp_key_get_uid_at(key, i, &uid_str);
1135
1136 /* userid itself with revocation status */
1137 fprintf(fp, "uid %s", cli_rnp_escape_string(uid_str).c_str());
1138 fprintf(fp, "%s\n", revoked ? "[REVOKED]" : "");
1139 rnp_buffer_destroy(uid_str);
1140
1141 /* print signatures only if requested */
1142 if (!psigs) {
1143 (void) rnp_uid_handle_destroy(uid);
1144 continue;
1145 }
1146
1147 (void) rnp_uid_get_signature_count(uid, &sigs);
1148 for (size_t j = 0; j < sigs; j++) {
1149 rnp_signature_handle_t sig = NULL;
1150 rnp_key_handle_t signer = NULL;
1151 char * keyid = NULL;
1152 uint32_t creation = 0;
1153 char * signer_uid = NULL;
1154
1155 if (rnp_uid_get_signature_at(uid, j, &sig)) {
1156 continue;
1157 }
1158 if (rnp_signature_get_creation(sig, &creation)) {
1159 goto next;
1160 }
1161 if (rnp_signature_get_keyid(sig, &keyid)) {
1162 goto next;
1163 }
1164 if (keyid) {
1165 /* lowercase key id */
1166 for (char *idptr = keyid; *idptr; ++idptr) {
1167 *idptr = tolower(*idptr);
1168 }
1169 /* signer primary uid */
1170 if (rnp_locate_key(ffi, "keyid", keyid, &signer)) {
1171 goto next;
1172 }
1173 if (signer) {
1174 (void) rnp_key_get_primary_uid(signer, &signer_uid);
1175 }
1176 }
1177
1178 /* signer key id */
1179 fprintf(fp, "sig %s ", keyid ? keyid : "[no key id]");
1180 /* signature creation time */
1181 fprintf(fp, "%s", ptimestr(buf, sizeof(buf), creation));
1182 /* signer's userid */
1183 fprintf(fp, " %s\n", signer_uid ? signer_uid : "[unknown]");
1184 next:
1185 (void) rnp_signature_handle_destroy(sig);
1186 (void) rnp_key_handle_destroy(signer);
1187 rnp_buffer_destroy(keyid);
1188 rnp_buffer_destroy(signer_uid);
1189 }
1190 (void) rnp_uid_handle_destroy(uid);
1191 }
1192
1193 done:
1194 rnp_buffer_destroy(json);
1195 json_object_put(pkts);
1196 }
1197
1198 bool
cli_rnp_save_keyrings(cli_rnp_t * rnp)1199 cli_rnp_save_keyrings(cli_rnp_t *rnp)
1200 {
1201 rnp_output_t output = NULL;
1202 rnp_result_t pub_ret = 0;
1203 rnp_result_t sec_ret = 0;
1204 const std::string &ppath = rnp->pubpath();
1205 const std::string &spath = rnp->secpath();
1206
1207 // check whether we have G10 secret keyring - then need to create directory
1208 if (rnp->secformat() == "G10") {
1209 struct stat path_stat;
1210 if (rnp_stat(spath.c_str(), &path_stat) != -1) {
1211 if (!S_ISDIR(path_stat.st_mode)) {
1212 ERR_MSG("G10 keystore should be a directory: %s", spath.c_str());
1213 return false;
1214 }
1215 } else {
1216 if (errno != ENOENT) {
1217 ERR_MSG("stat(%s): %s", spath.c_str(), strerror(errno));
1218 return false;
1219 }
1220 if (RNP_MKDIR(spath.c_str(), S_IRWXU) != 0) {
1221 ERR_MSG("mkdir(%s, S_IRWXU): %s", spath.c_str(), strerror(errno));
1222 return false;
1223 }
1224 }
1225 }
1226
1227 // public keyring
1228 if (!(pub_ret = rnp_output_to_path(&output, ppath.c_str()))) {
1229 pub_ret =
1230 rnp_save_keys(rnp->ffi, rnp->pubformat().c_str(), output, RNP_LOAD_SAVE_PUBLIC_KEYS);
1231 rnp_output_destroy(output);
1232 }
1233 if (pub_ret) {
1234 ERR_MSG("failed to write pubring to path '%s'", ppath.c_str());
1235 }
1236
1237 // secret keyring
1238 if (!(sec_ret = rnp_output_to_path(&output, spath.c_str()))) {
1239 sec_ret =
1240 rnp_save_keys(rnp->ffi, rnp->secformat().c_str(), output, RNP_LOAD_SAVE_SECRET_KEYS);
1241 rnp_output_destroy(output);
1242 }
1243 if (sec_ret) {
1244 ERR_MSG("failed to write secring to path '%s'", spath.c_str());
1245 }
1246
1247 return !pub_ret && !sec_ret;
1248 }
1249
1250 bool
cli_rnp_generate_key(cli_rnp_t * rnp,const char * username)1251 cli_rnp_generate_key(cli_rnp_t *rnp, const char *username)
1252 {
1253 /* set key generation parameters to rnp_cfg_t */
1254 rnp_cfg &cfg = rnp->cfg();
1255 if (!cli_rnp_set_generate_params(cfg)) {
1256 ERR_MSG("Key generation setup failed.");
1257 return false;
1258 }
1259 /* generate the primary key */
1260 rnp_op_generate_t genkey = NULL;
1261 rnp_key_handle_t primary = NULL;
1262 rnp_key_handle_t subkey = NULL;
1263 bool res = false;
1264
1265 if (rnp_op_generate_create(&genkey, rnp->ffi, cfg.get_cstr(CFG_KG_PRIMARY_ALG))) {
1266 ERR_MSG("Failed to initialize key generation.");
1267 return false;
1268 }
1269 if (username && rnp_op_generate_set_userid(genkey, username)) {
1270 ERR_MSG("Failed to set userid.");
1271 goto done;
1272 }
1273 if (cfg.has(CFG_KG_PRIMARY_BITS) &&
1274 rnp_op_generate_set_bits(genkey, cfg.get_int(CFG_KG_PRIMARY_BITS))) {
1275 ERR_MSG("Failed to set key bits.");
1276 goto done;
1277 }
1278 if (cfg.has(CFG_KG_PRIMARY_CURVE) &&
1279 rnp_op_generate_set_curve(genkey, cfg.get_cstr(CFG_KG_PRIMARY_CURVE))) {
1280 ERR_MSG("Failed to set key curve.");
1281 goto done;
1282 }
1283 if (cfg.has(CFG_KG_PRIMARY_EXPIRATION)) {
1284 uint32_t expiration = 0;
1285 if (!cfg.get_expiration(CFG_KG_PRIMARY_EXPIRATION, expiration) ||
1286 rnp_op_generate_set_expiration(genkey, expiration)) {
1287 ERR_MSG("Failed to set primary key expiration.");
1288 goto done;
1289 }
1290 }
1291 // TODO : set DSA qbits
1292 if (rnp_op_generate_set_hash(genkey, cfg.get_cstr(CFG_KG_HASH))) {
1293 ERR_MSG("Failed to set hash algorithm.");
1294 goto done;
1295 }
1296
1297 fprintf(rnp->userio_out, "Generating a new key...\n");
1298 if (rnp_op_generate_execute(genkey) || rnp_op_generate_get_key(genkey, &primary)) {
1299 ERR_MSG("Primary key generation failed.");
1300 goto done;
1301 }
1302
1303 if (!cfg.has(CFG_KG_SUBKEY_ALG)) {
1304 res = true;
1305 goto done;
1306 }
1307
1308 rnp_op_generate_destroy(genkey);
1309 genkey = NULL;
1310 if (rnp_op_generate_subkey_create(
1311 &genkey, rnp->ffi, primary, cfg.get_cstr(CFG_KG_SUBKEY_ALG))) {
1312 ERR_MSG("Failed to initialize subkey generation.");
1313 goto done;
1314 }
1315 if (cfg.has(CFG_KG_SUBKEY_BITS) &&
1316 rnp_op_generate_set_bits(genkey, cfg.get_int(CFG_KG_SUBKEY_BITS))) {
1317 ERR_MSG("Failed to set subkey bits.");
1318 goto done;
1319 }
1320 if (cfg.has(CFG_KG_SUBKEY_CURVE) &&
1321 rnp_op_generate_set_curve(genkey, cfg.get_cstr(CFG_KG_SUBKEY_CURVE))) {
1322 ERR_MSG("Failed to set subkey curve.");
1323 goto done;
1324 }
1325 if (cfg.has(CFG_KG_SUBKEY_EXPIRATION)) {
1326 uint32_t expiration = 0;
1327 if (!cfg.get_expiration(CFG_KG_SUBKEY_EXPIRATION, expiration) ||
1328 rnp_op_generate_set_expiration(genkey, expiration)) {
1329 ERR_MSG("Failed to set subkey expiration.");
1330 goto done;
1331 }
1332 }
1333 // TODO : set DSA qbits
1334 if (rnp_op_generate_set_hash(genkey, cfg.get_cstr(CFG_KG_HASH))) {
1335 ERR_MSG("Failed to set hash algorithm.");
1336 goto done;
1337 }
1338 if (rnp_op_generate_execute(genkey) || rnp_op_generate_get_key(genkey, &subkey)) {
1339 ERR_MSG("Subkey generation failed.");
1340 goto done;
1341 }
1342
1343 // protect
1344 for (auto key : {primary, subkey}) {
1345 char *password = NULL;
1346 if (rnp_request_password(rnp->ffi, key, "protect", &password)) {
1347 ERR_MSG("Failed to obtain protection password.");
1348 goto done;
1349 }
1350 if (*password) {
1351 rnp_result_t ret = rnp_key_protect(key,
1352 password,
1353 cfg.get_cstr(CFG_KG_PROT_ALG),
1354 NULL,
1355 cfg.get_cstr(CFG_KG_PROT_HASH),
1356 cfg.get_int(CFG_KG_PROT_ITERATIONS));
1357 rnp_buffer_clear(password, strlen(password) + 1);
1358 rnp_buffer_destroy(password);
1359 if (ret) {
1360 ERR_MSG("Failed to protect key.");
1361 goto done;
1362 }
1363 } else {
1364 rnp_buffer_destroy(password);
1365 }
1366 }
1367 res = cli_rnp_save_keyrings(rnp);
1368 done:
1369 if (res) {
1370 cli_rnp_print_key_info(stdout, rnp->ffi, primary, true, false);
1371 if (subkey) {
1372 cli_rnp_print_key_info(stdout, rnp->ffi, subkey, true, false);
1373 }
1374 }
1375 rnp_op_generate_destroy(genkey);
1376 rnp_key_handle_destroy(primary);
1377 rnp_key_handle_destroy(subkey);
1378 return res;
1379 }
1380
1381 static size_t
hex_prefix_len(const std::string & str)1382 hex_prefix_len(const std::string &str)
1383 {
1384 if ((str.length() >= 2) && (str[0] == '0') && ((str[1] == 'x') || (str[1] == 'X'))) {
1385 return 2;
1386 }
1387 return 0;
1388 }
1389
1390 static bool
str_is_hex(const std::string & hexid)1391 str_is_hex(const std::string &hexid)
1392 {
1393 for (size_t i = hex_prefix_len(hexid); i < hexid.length(); i++) {
1394 if ((hexid[i] >= '0') && (hexid[i] <= '9')) {
1395 continue;
1396 }
1397 if ((hexid[i] >= 'a') && (hexid[i] <= 'f')) {
1398 continue;
1399 }
1400 if ((hexid[i] >= 'A') && (hexid[i] <= 'F')) {
1401 continue;
1402 }
1403 if ((hexid[i] == ' ') || (hexid[i] == '\t')) {
1404 continue;
1405 }
1406 return false;
1407 }
1408 return true;
1409 }
1410
1411 static std::string
strip_hex_str(const std::string & str)1412 strip_hex_str(const std::string &str)
1413 {
1414 std::string res = "";
1415 for (size_t idx = hex_prefix_len(str); idx < str.length(); idx++) {
1416 char ch = str[idx];
1417 if ((ch == ' ') || (ch == '\t')) {
1418 continue;
1419 }
1420 res.push_back(ch);
1421 }
1422 return res;
1423 }
1424
1425 static bool
key_matches_string(rnp_key_handle_t handle,const std::string & str)1426 key_matches_string(rnp_key_handle_t handle, const std::string &str)
1427 {
1428 bool matches = false;
1429 char * id = NULL;
1430 size_t idlen = 0;
1431 size_t len = str.length();
1432 #ifndef RNP_USE_STD_REGEX
1433 regex_t r = {};
1434 #else
1435 std::regex re;
1436 #endif
1437 size_t uid_count = 0;
1438 bool boolres = false;
1439
1440 if (str.empty()) {
1441 matches = true;
1442 goto done;
1443 }
1444 if (str_is_hex(str) && (len >= RNP_KEYID_SIZE)) {
1445 std::string hexstr = strip_hex_str(str);
1446
1447 /* check whether it's key id */
1448 if ((len == RNP_KEYID_SIZE * 2) || (len == RNP_KEYID_SIZE)) {
1449 if (rnp_key_get_keyid(handle, &id)) {
1450 goto done;
1451 }
1452 if ((idlen = strlen(id)) < len) {
1453 goto done;
1454 }
1455 if (strncasecmp(hexstr.c_str(), id + idlen - len, len) == 0) {
1456 matches = true;
1457 goto done;
1458 }
1459 rnp_buffer_destroy(id);
1460 id = NULL;
1461 }
1462
1463 /* check fingerprint */
1464 if (len == RNP_FP_SIZE * 2) {
1465 if (rnp_key_get_fprint(handle, &id)) {
1466 goto done;
1467 }
1468 if (strlen(id) != len) {
1469 goto done;
1470 }
1471 if (strncasecmp(hexstr.c_str(), id, len) == 0) {
1472 matches = true;
1473 goto done;
1474 }
1475 rnp_buffer_destroy(id);
1476 id = NULL;
1477 }
1478
1479 /* check grip */
1480 if (len == RNP_GRIP_SIZE * 2) {
1481 if (rnp_key_get_grip(handle, &id)) {
1482 goto done;
1483 }
1484 if (strlen(id) != len) {
1485 goto done;
1486 }
1487 if (strncasecmp(hexstr.c_str(), id, len) == 0) {
1488 matches = true;
1489 goto done;
1490 }
1491 rnp_buffer_destroy(id);
1492 id = NULL;
1493 }
1494 /* let then search for hex userid */
1495 }
1496
1497 /* no need to check for userid over the subkey */
1498 if (rnp_key_is_sub(handle, &boolres) || boolres) {
1499 goto done;
1500 }
1501 if (rnp_key_get_uid_count(handle, &uid_count) || (uid_count == 0)) {
1502 goto done;
1503 }
1504
1505 #ifndef RNP_USE_STD_REGEX
1506 /* match on full name or email address as a NOSUB, ICASE regexp */
1507 if (regcomp(&r, cli_rnp_unescape_for_regcomp(str).c_str(), REG_EXTENDED | REG_ICASE) !=
1508 0) {
1509 goto done;
1510 }
1511 #else
1512 try {
1513 re.assign(str, std::regex_constants::ECMAScript | std::regex_constants::icase);
1514 } catch (const std::exception &e) {
1515 ERR_MSG("Invalid regular expression : %s, error %s.", str.c_str(), e.what());
1516 goto done;
1517 }
1518 #endif
1519
1520 for (size_t idx = 0; idx < uid_count; idx++) {
1521 if (rnp_key_get_uid_at(handle, idx, &id)) {
1522 goto regdone;
1523 }
1524 #ifndef RNP_USE_STD_REGEX
1525 if (regexec(&r, id, 0, NULL, 0) == 0) {
1526 matches = true;
1527 goto regdone;
1528 }
1529 #else
1530 if (std::regex_search(id, re)) {
1531 matches = true;
1532 goto regdone;
1533 }
1534 #endif
1535 rnp_buffer_destroy(id);
1536 id = NULL;
1537 }
1538
1539 regdone:
1540 #ifndef RNP_USE_STD_REGEX
1541 regfree(&r);
1542 #endif
1543 done:
1544 rnp_buffer_destroy(id);
1545 return matches;
1546 }
1547
1548 static bool
key_matches_flags(rnp_key_handle_t key,int flags)1549 key_matches_flags(rnp_key_handle_t key, int flags)
1550 {
1551 /* check whether secret key search is requested */
1552 bool secret = false;
1553 if (rnp_key_have_secret(key, &secret)) {
1554 return false;
1555 }
1556 if ((flags & CLI_SEARCH_SECRET) && !secret) {
1557 return false;
1558 }
1559 /* check whether no subkeys allowed */
1560 bool subkey = false;
1561 if (rnp_key_is_sub(key, &subkey)) {
1562 return false;
1563 }
1564 if (!subkey) {
1565 return true;
1566 }
1567 if (!(flags & CLI_SEARCH_SUBKEYS)) {
1568 return false;
1569 }
1570 /* check whether subkeys should be put after primary (if it is available) */
1571 if ((flags & CLI_SEARCH_SUBKEYS_AFTER) != CLI_SEARCH_SUBKEYS_AFTER) {
1572 return true;
1573 }
1574
1575 char *grip = NULL;
1576 if (rnp_key_get_primary_grip(key, &grip)) {
1577 return false;
1578 }
1579 if (!grip) {
1580 return true;
1581 }
1582 rnp_buffer_destroy(grip);
1583 return false;
1584 }
1585
1586 void
clear_key_handles(std::vector<rnp_key_handle_t> & keys)1587 clear_key_handles(std::vector<rnp_key_handle_t> &keys)
1588 {
1589 for (auto handle : keys) {
1590 rnp_key_handle_destroy(handle);
1591 }
1592 keys.clear();
1593 }
1594
1595 static bool
add_key_to_array(rnp_ffi_t ffi,std::vector<rnp_key_handle_t> & keys,rnp_key_handle_t key,int flags)1596 add_key_to_array(rnp_ffi_t ffi,
1597 std::vector<rnp_key_handle_t> &keys,
1598 rnp_key_handle_t key,
1599 int flags)
1600 {
1601 bool subkey = false;
1602 bool subkeys = (flags & CLI_SEARCH_SUBKEYS_AFTER) == CLI_SEARCH_SUBKEYS_AFTER;
1603 if (rnp_key_is_sub(key, &subkey)) {
1604 return false;
1605 }
1606
1607 try {
1608 keys.push_back(key);
1609 } catch (const std::exception &e) {
1610 ERR_MSG("%s", e.what());
1611 return false;
1612 }
1613 if (!subkeys || subkey) {
1614 return true;
1615 }
1616
1617 std::vector<rnp_key_handle_t> subs;
1618 size_t sub_count = 0;
1619 if (rnp_key_get_subkey_count(key, &sub_count)) {
1620 goto error;
1621 }
1622
1623 try {
1624 for (size_t i = 0; i < sub_count; i++) {
1625 rnp_key_handle_t sub_handle = NULL;
1626 if (rnp_key_get_subkey_at(key, i, &sub_handle)) {
1627 goto error;
1628 }
1629 subs.push_back(sub_handle);
1630 }
1631 std::move(subs.begin(), subs.end(), std::back_inserter(keys));
1632 } catch (const std::exception &e) {
1633 ERR_MSG("%s", e.what());
1634 goto error;
1635 }
1636 return true;
1637 error:
1638 keys.pop_back();
1639 clear_key_handles(subs);
1640 return false;
1641 }
1642
1643 bool
cli_rnp_keys_matching_string(cli_rnp_t * rnp,std::vector<rnp_key_handle_t> & keys,const std::string & str,int flags)1644 cli_rnp_keys_matching_string(cli_rnp_t * rnp,
1645 std::vector<rnp_key_handle_t> &keys,
1646 const std::string & str,
1647 int flags)
1648 {
1649 bool res = false;
1650 rnp_identifier_iterator_t it = NULL;
1651 rnp_key_handle_t handle = NULL;
1652 const char * fp = NULL;
1653
1654 /* iterate through the keys */
1655 if (rnp_identifier_iterator_create(rnp->ffi, &it, "fingerprint")) {
1656 return false;
1657 }
1658
1659 while (!rnp_identifier_iterator_next(it, &fp)) {
1660 if (!fp) {
1661 break;
1662 }
1663 if (rnp_locate_key(rnp->ffi, "fingerprint", fp, &handle) || !handle) {
1664 goto done;
1665 }
1666 if (!key_matches_flags(handle, flags) || !key_matches_string(handle, str.c_str())) {
1667 rnp_key_handle_destroy(handle);
1668 continue;
1669 }
1670 if (!add_key_to_array(rnp->ffi, keys, handle, flags)) {
1671 rnp_key_handle_destroy(handle);
1672 goto done;
1673 }
1674 if (flags & CLI_SEARCH_FIRST_ONLY) {
1675 res = true;
1676 goto done;
1677 }
1678 }
1679 res = !keys.empty();
1680 done:
1681 rnp_identifier_iterator_destroy(it);
1682 return res;
1683 }
1684
1685 bool
cli_rnp_keys_matching_strings(cli_rnp_t * rnp,std::vector<rnp_key_handle_t> & keys,const std::vector<std::string> & strs,int flags)1686 cli_rnp_keys_matching_strings(cli_rnp_t * rnp,
1687 std::vector<rnp_key_handle_t> & keys,
1688 const std::vector<std::string> &strs,
1689 int flags)
1690 {
1691 bool res = false;
1692 clear_key_handles(keys);
1693
1694 for (const std::string &str : strs) {
1695 if (!cli_rnp_keys_matching_string(rnp, keys, str, flags & ~CLI_SEARCH_DEFAULT)) {
1696 ERR_MSG("Cannot find key matching \"%s\"", str.c_str());
1697 goto done;
1698 }
1699 }
1700
1701 /* search for default key */
1702 if (keys.empty() && (flags & CLI_SEARCH_DEFAULT)) {
1703 if (rnp->defkey().empty()) {
1704 ERR_MSG("No userid or default key for operation");
1705 goto done;
1706 }
1707 cli_rnp_keys_matching_string(rnp, keys, rnp->defkey(), flags & ~CLI_SEARCH_DEFAULT);
1708 if (keys.empty()) {
1709 ERR_MSG("Default key not found");
1710 }
1711 }
1712 res = !keys.empty();
1713 done:
1714 if (!res) {
1715 clear_key_handles(keys);
1716 }
1717 return res;
1718 }
1719
1720 /** @brief compose path from dir, subdir and filename, and return it.
1721 * @param dir [in] directory path
1722 * @param subddir [in] subdirectory to add to the path, can be empty
1723 * @param filename [in] filename (or path/filename)
1724 *
1725 * @return constructed path
1726 **/
1727 static std::string
rnp_path_compose(const std::string & dir,const std::string & subdir,const std::string & filename)1728 rnp_path_compose(const std::string &dir,
1729 const std::string &subdir,
1730 const std::string &filename)
1731 {
1732 std::string res = dir;
1733 if (!subdir.empty()) {
1734 if (!res.empty() && (res.back() != '/')) {
1735 res.push_back('/');
1736 }
1737 res.append(subdir);
1738 }
1739
1740 if (!res.empty() && (res.back() != '/')) {
1741 res.push_back('/');
1742 }
1743
1744 res.append(filename);
1745 return res;
1746 }
1747
1748 static bool
rnp_cfg_set_ks_info(rnp_cfg & cfg)1749 rnp_cfg_set_ks_info(rnp_cfg &cfg)
1750 {
1751 if (cfg.get_bool(CFG_KEYSTORE_DISABLED)) {
1752 cfg.set_str(CFG_KR_PUB_PATH, "");
1753 cfg.set_str(CFG_KR_SEC_PATH, "");
1754 cfg.set_str(CFG_KR_PUB_FORMAT, RNP_KEYSTORE_GPG);
1755 cfg.set_str(CFG_KR_SEC_FORMAT, RNP_KEYSTORE_GPG);
1756 return true;
1757 }
1758
1759 /* getting path to keyrings. If it is specified by user in 'homedir' param then it is
1760 * considered as the final path */
1761 bool defhomedir = false;
1762 std::string homedir = cfg.get_str(CFG_HOMEDIR);
1763 if (homedir.empty()) {
1764 const char *home = getenv("HOME");
1765 homedir = home ? home : "";
1766 defhomedir = true;
1767 }
1768
1769 struct stat st;
1770
1771 if (rnp_stat(homedir.c_str(), &st) || rnp_access(homedir.c_str(), R_OK | W_OK)) {
1772 ERR_MSG("Home directory '%s' does not exist or is not writable!", homedir.c_str());
1773 return false;
1774 }
1775
1776 /* detecting key storage format */
1777 std::string subdir = defhomedir ? SUBDIRECTORY_RNP : "";
1778 std::string pubpath;
1779 std::string secpath;
1780 std::string ks_format = cfg.get_str(CFG_KEYSTOREFMT);
1781
1782 if (ks_format.empty()) {
1783 pubpath = rnp_path_compose(homedir, subdir, PUBRING_KBX);
1784 secpath = rnp_path_compose(homedir, subdir, SECRING_G10);
1785
1786 bool pubpath_exists = !rnp_stat(pubpath.c_str(), &st);
1787 bool secpath_exists = !rnp_stat(secpath.c_str(), &st);
1788
1789 if (pubpath_exists && secpath_exists) {
1790 ks_format = RNP_KEYSTORE_GPG21;
1791 } else if (secpath_exists) {
1792 ks_format = RNP_KEYSTORE_G10;
1793 } else if (pubpath_exists) {
1794 ks_format = RNP_KEYSTORE_KBX;
1795 } else {
1796 ks_format = RNP_KEYSTORE_GPG;
1797 }
1798 }
1799
1800 /* creating home dir if needed */
1801 if (!subdir.empty()) {
1802 pubpath = rnp_path_compose(homedir, "", subdir);
1803 if (RNP_MKDIR(pubpath.c_str(), 0700) == -1 && errno != EEXIST) {
1804 ERR_MSG("cannot mkdir '%s' errno = %d", pubpath.c_str(), errno);
1805 return false;
1806 }
1807 }
1808
1809 std::string pub_format = RNP_KEYSTORE_GPG;
1810 std::string sec_format = RNP_KEYSTORE_GPG;
1811
1812 if (ks_format == RNP_KEYSTORE_GPG) {
1813 pubpath = rnp_path_compose(homedir, subdir, PUBRING_GPG);
1814 secpath = rnp_path_compose(homedir, subdir, SECRING_GPG);
1815 pub_format = RNP_KEYSTORE_GPG;
1816 sec_format = RNP_KEYSTORE_GPG;
1817 } else if (ks_format == RNP_KEYSTORE_GPG21) {
1818 pubpath = rnp_path_compose(homedir, subdir, PUBRING_KBX);
1819 secpath = rnp_path_compose(homedir, subdir, SECRING_G10);
1820 pub_format = RNP_KEYSTORE_KBX;
1821 sec_format = RNP_KEYSTORE_G10;
1822 } else if (ks_format == RNP_KEYSTORE_KBX) {
1823 pubpath = rnp_path_compose(homedir, subdir, PUBRING_KBX);
1824 secpath = rnp_path_compose(homedir, subdir, SECRING_KBX);
1825 pub_format = RNP_KEYSTORE_KBX;
1826 sec_format = RNP_KEYSTORE_KBX;
1827 } else if (ks_format == RNP_KEYSTORE_G10) {
1828 pubpath = rnp_path_compose(homedir, subdir, PUBRING_G10);
1829 secpath = rnp_path_compose(homedir, subdir, SECRING_G10);
1830 pub_format = RNP_KEYSTORE_G10;
1831 sec_format = RNP_KEYSTORE_G10;
1832 } else {
1833 ERR_MSG("unsupported keystore format: \"%s\"", ks_format.c_str());
1834 return false;
1835 }
1836
1837 cfg.set_str(CFG_KR_PUB_PATH, pubpath);
1838 cfg.set_str(CFG_KR_SEC_PATH, secpath);
1839 cfg.set_str(CFG_KR_PUB_FORMAT, pub_format);
1840 cfg.set_str(CFG_KR_SEC_FORMAT, sec_format);
1841 return true;
1842 }
1843
1844 /* read any gpg config file */
1845 static bool
conffile(const std::string & homedir,std::string & userid)1846 conffile(const std::string &homedir, std::string &userid)
1847 {
1848 char buf[BUFSIZ];
1849 FILE *fp;
1850
1851 #ifndef RNP_USE_STD_REGEX
1852 regmatch_t matchv[10];
1853 regex_t keyre;
1854 #else
1855 static std::regex keyre("^[ \t]*default-key[ \t]+([0-9a-zA-F]+)",
1856 std::regex_constants::extended);
1857 #endif
1858 (void) snprintf(buf, sizeof(buf), "%s/.gnupg/gpg.conf", homedir.c_str());
1859 if ((fp = rnp_fopen(buf, "r")) == NULL) {
1860 return false;
1861 }
1862 #ifndef RNP_USE_STD_REGEX
1863 (void) memset(&keyre, 0x0, sizeof(keyre));
1864 if (regcomp(&keyre, "^[ \t]*default-key[ \t]+([0-9a-zA-F]+)", REG_EXTENDED) != 0) {
1865 ERR_MSG("failed to compile regular expression");
1866 fclose(fp);
1867 return false;
1868 }
1869 #endif
1870 while (fgets(buf, (int) sizeof(buf), fp) != NULL) {
1871 #ifndef RNP_USE_STD_REGEX
1872 if (regexec(&keyre, buf, 10, matchv, 0) == 0) {
1873 userid =
1874 std::string(&buf[(int) matchv[1].rm_so], matchv[1].rm_eo - matchv[1].rm_so);
1875 ERR_MSG("rnp: default key set to \"%s\"", userid.c_str());
1876 }
1877 #else
1878 std::smatch result;
1879 std::string input = buf;
1880 if (std::regex_search(input, result, keyre)) {
1881 userid = result[1].str();
1882 ERR_MSG("rnp: default key set to \"%s\"", userid.c_str());
1883 }
1884 #endif
1885 }
1886 (void) fclose(fp);
1887 #ifndef RNP_USE_STD_REGEX
1888 regfree(&keyre);
1889 #endif
1890 return true;
1891 }
1892
1893 static void
rnp_cfg_set_defkey(rnp_cfg & cfg)1894 rnp_cfg_set_defkey(rnp_cfg &cfg)
1895 {
1896 bool defhomedir = false;
1897 std::string homedir = cfg.get_str(CFG_HOMEDIR);
1898 if (homedir.empty()) {
1899 const char *home = getenv("HOME");
1900 homedir = home ? home : "";
1901 defhomedir = true;
1902 }
1903
1904 /* If a userid has been given, we'll use it. */
1905 std::string userid = cfg.get_count(CFG_USERID) ? cfg.get_str(CFG_USERID, 0) : "";
1906 if (!userid.empty()) {
1907 cfg.set_str(CFG_KR_DEF_KEY, userid);
1908 return;
1909 }
1910 /* also search in config file for default id */
1911 if (defhomedir) {
1912 std::string id;
1913 if (conffile(homedir, id) && !id.empty()) {
1914 cfg.unset(CFG_USERID);
1915 cfg.add_str(CFG_USERID, id);
1916 cfg.set_str(CFG_KR_DEF_KEY, id);
1917 }
1918 }
1919 }
1920
1921 bool
cli_cfg_set_keystore_info(rnp_cfg & cfg)1922 cli_cfg_set_keystore_info(rnp_cfg &cfg)
1923 {
1924 /* detecting keystore paths and format */
1925 if (!rnp_cfg_set_ks_info(cfg)) {
1926 ERR_MSG("cannot obtain keystore path(s)");
1927 return false;
1928 }
1929
1930 /* default key/userid */
1931 rnp_cfg_set_defkey(cfg);
1932 return true;
1933 }
1934
1935 static bool
stdin_reader(void * app_ctx,void * buf,size_t len,size_t * readres)1936 stdin_reader(void *app_ctx, void *buf, size_t len, size_t *readres)
1937 {
1938 ssize_t res = read(STDIN_FILENO, buf, len);
1939 if (res < 0) {
1940 return false;
1941 }
1942 *readres = res;
1943 return true;
1944 }
1945
1946 /* This produces
1947 runtime error: call to function stdout_writer(void*, void const*, unsigned long) through
1948 pointer to incorrect function type 'bool (*)(void *, const void *, unsigned long)' */
1949 #if defined(__clang__)
1950 __attribute__((no_sanitize("undefined")))
1951 #endif
1952 static bool
stdout_writer(void * app_ctx,const void * buf,size_t len)1953 stdout_writer(void *app_ctx, const void *buf, size_t len)
1954 {
1955 ssize_t wlen = write(STDOUT_FILENO, buf, len);
1956 return (wlen >= 0) && (size_t) wlen == len;
1957 }
1958
1959 rnp_input_t
cli_rnp_input_from_specifier(cli_rnp_t & rnp,const std::string & spec,bool * is_path)1960 cli_rnp_input_from_specifier(cli_rnp_t &rnp, const std::string &spec, bool *is_path)
1961 {
1962 rnp_input_t input = NULL;
1963 rnp_result_t res = RNP_ERROR_GENERIC;
1964 bool path = false;
1965 if (spec.empty() || (spec == "-")) {
1966 /* input from stdin */
1967 res = rnp_input_from_callback(&input, stdin_reader, NULL, NULL);
1968 } else if ((spec.size() > 4) && (spec.compare(0, 4, "env:") == 0)) {
1969 /* input from an environment variable */
1970 const char *envval = getenv(spec.c_str() + 4);
1971 if (!envval) {
1972 ERR_MSG("Failed to get value of the environment variable '%s'.", spec.c_str() + 4);
1973 return NULL;
1974 }
1975 res = rnp_input_from_memory(&input, (const uint8_t *) envval, strlen(envval), true);
1976 } else {
1977 /* input from path */
1978 res = rnp_input_from_path(&input, spec.c_str());
1979 path = true;
1980 }
1981
1982 if (res) {
1983 return NULL;
1984 }
1985 if (is_path) {
1986 *is_path = path;
1987 }
1988 return input;
1989 }
1990
1991 rnp_output_t
cli_rnp_output_to_specifier(cli_rnp_t & rnp,const std::string & spec,bool discard)1992 cli_rnp_output_to_specifier(cli_rnp_t &rnp, const std::string &spec, bool discard)
1993 {
1994 rnp_output_t output = NULL;
1995 rnp_result_t res = RNP_ERROR_GENERIC;
1996 std::string path = spec;
1997 if (discard) {
1998 res = rnp_output_to_null(&output);
1999 } else if (spec.empty() || (spec == "-")) {
2000 res = rnp_output_to_callback(&output, stdout_writer, NULL, NULL);
2001 } else if (!rnp_get_output_filename(spec, path, rnp)) {
2002 ERR_MSG("Operation failed: file '%s' already exists.", spec.c_str());
2003 res = RNP_ERROR_BAD_PARAMETERS;
2004 } else {
2005 res = rnp_output_to_file(&output, path.c_str(), RNP_OUTPUT_FILE_OVERWRITE);
2006 }
2007 return res ? NULL : output;
2008 }
2009
2010 bool
cli_rnp_export_keys(cli_rnp_t * rnp,const char * filter)2011 cli_rnp_export_keys(cli_rnp_t *rnp, const char *filter)
2012 {
2013 bool secret = rnp->cfg().get_bool(CFG_SECRET);
2014 int flags = secret ? CLI_SEARCH_SECRET : 0;
2015 std::vector<rnp_key_handle_t> keys;
2016
2017 if (!cli_rnp_keys_matching_string(rnp, keys, filter, flags)) {
2018 ERR_MSG("Key(s) matching '%s' not found.", filter);
2019 return false;
2020 }
2021
2022 rnp_output_t output = NULL;
2023 rnp_output_t armor = NULL;
2024 uint32_t base_flags = secret ? RNP_KEY_EXPORT_SECRET : RNP_KEY_EXPORT_PUBLIC;
2025 bool result = false;
2026
2027 output = cli_rnp_output_to_specifier(*rnp, rnp->cfg().get_str(CFG_OUTFILE));
2028 if (!output) {
2029 goto done;
2030 }
2031
2032 /* We need single armored stream for all of the keys */
2033 if (rnp_output_to_armor(output, &armor, secret ? "secret key" : "public key")) {
2034 goto done;
2035 }
2036
2037 for (auto key : keys) {
2038 uint32_t flags = base_flags;
2039 bool primary = false;
2040
2041 if (rnp_key_is_primary(key, &primary)) {
2042 goto done;
2043 }
2044 if (primary) {
2045 flags = flags | RNP_KEY_EXPORT_SUBKEYS;
2046 }
2047
2048 if (rnp_key_export(key, armor, flags)) {
2049 goto done;
2050 }
2051 }
2052 result = !rnp_output_finish(armor);
2053 done:
2054 rnp_output_destroy(armor);
2055 rnp_output_destroy(output);
2056 clear_key_handles(keys);
2057 return result;
2058 }
2059
2060 bool
cli_rnp_export_revocation(cli_rnp_t * rnp,const char * key)2061 cli_rnp_export_revocation(cli_rnp_t *rnp, const char *key)
2062 {
2063 std::vector<rnp_key_handle_t> keys;
2064 if (!cli_rnp_keys_matching_string(rnp, keys, key, 0)) {
2065 ERR_MSG("Key matching '%s' not found.", key);
2066 return false;
2067 }
2068 if (keys.size() > 1) {
2069 ERR_MSG("Ambiguous input: too many keys found for '%s'.", key);
2070 clear_key_handles(keys);
2071 return false;
2072 }
2073 rnp_output_t output = NULL;
2074 rnp_output_t armored = NULL;
2075 bool result = false;
2076
2077 output = cli_rnp_output_to_specifier(*rnp, rnp->cfg().get_str(CFG_OUTFILE));
2078 if (!output) {
2079 goto done;
2080 }
2081
2082 /* export it armored by default */
2083 if (rnp_output_to_armor(output, &armored, "public key")) {
2084 goto done;
2085 }
2086
2087 result = !rnp_key_export_revocation(keys[0],
2088 armored,
2089 0,
2090 rnp->cfg().get_cstr(CFG_HASH),
2091 rnp->cfg().get_cstr(CFG_REV_TYPE),
2092 rnp->cfg().get_cstr(CFG_REV_REASON));
2093 done:
2094 rnp_output_destroy(armored);
2095 rnp_output_destroy(output);
2096 clear_key_handles(keys);
2097 return result;
2098 }
2099
2100 bool
cli_rnp_revoke_key(cli_rnp_t * rnp,const char * key)2101 cli_rnp_revoke_key(cli_rnp_t *rnp, const char *key)
2102 {
2103 std::vector<rnp_key_handle_t> keys;
2104 if (!cli_rnp_keys_matching_string(rnp, keys, key, CLI_SEARCH_SUBKEYS)) {
2105 ERR_MSG("Key matching '%s' not found.", key);
2106 return false;
2107 }
2108 bool res = false;
2109 bool revoked = false;
2110 rnp_result_t ret = 0;
2111
2112 if (keys.size() > 1) {
2113 ERR_MSG("Ambiguous input: too many keys found for '%s'.", key);
2114 goto done;
2115 }
2116 if (rnp_key_is_revoked(keys[0], &revoked)) {
2117 ERR_MSG("Error getting key revocation status.");
2118 goto done;
2119 }
2120 if (revoked && !rnp->cfg().get_bool(CFG_FORCE)) {
2121 ERR_MSG("Error: key '%s' is revoked already. Use --force to generate another "
2122 "revocation signature.",
2123 key);
2124 goto done;
2125 }
2126
2127 ret = rnp_key_revoke(keys[0],
2128 0,
2129 rnp->cfg().get_cstr(CFG_HASH),
2130 rnp->cfg().get_cstr(CFG_REV_TYPE),
2131 rnp->cfg().get_cstr(CFG_REV_REASON));
2132 if (ret) {
2133 ERR_MSG("Failed to revoke a key: error %d", (int) ret);
2134 goto done;
2135 }
2136 res = cli_rnp_save_keyrings(rnp);
2137 /* print info about the revoked key */
2138 if (res) {
2139 bool subkey = false;
2140 char *grip = NULL;
2141 if (rnp_key_is_sub(keys[0], &subkey)) {
2142 ERR_MSG("Failed to get key info");
2143 goto done;
2144 }
2145 ret =
2146 subkey ? rnp_key_get_primary_grip(keys[0], &grip) : rnp_key_get_grip(keys[0], &grip);
2147 if (ret || !grip) {
2148 ERR_MSG("Failed to get primary key grip.");
2149 goto done;
2150 }
2151 clear_key_handles(keys);
2152 if (!cli_rnp_keys_matching_string(rnp, keys, grip, CLI_SEARCH_SUBKEYS_AFTER)) {
2153 ERR_MSG("Failed to search for revoked key.");
2154 rnp_buffer_destroy(grip);
2155 goto done;
2156 }
2157 rnp_buffer_destroy(grip);
2158 for (auto handle : keys) {
2159 cli_rnp_print_key_info(rnp->userio_out, rnp->ffi, handle, false, false);
2160 }
2161 }
2162 done:
2163 clear_key_handles(keys);
2164 return res;
2165 }
2166
2167 bool
cli_rnp_remove_key(cli_rnp_t * rnp,const char * key)2168 cli_rnp_remove_key(cli_rnp_t *rnp, const char *key)
2169 {
2170 std::vector<rnp_key_handle_t> keys;
2171 if (!cli_rnp_keys_matching_string(rnp, keys, key, CLI_SEARCH_SUBKEYS)) {
2172 ERR_MSG("Key matching '%s' not found.", key);
2173 return false;
2174 }
2175 bool res = false;
2176 bool secret = false;
2177 bool primary = false;
2178 uint32_t flags = RNP_KEY_REMOVE_PUBLIC;
2179 rnp_result_t ret = 0;
2180
2181 if (keys.size() > 1) {
2182 ERR_MSG("Ambiguous input: too many keys found for '%s'.", key);
2183 goto done;
2184 }
2185 if (rnp_key_have_secret(keys[0], &secret)) {
2186 ERR_MSG("Error getting secret key presence.");
2187 goto done;
2188 }
2189 if (rnp_key_is_primary(keys[0], &primary)) {
2190 ERR_MSG("Key error.");
2191 goto done;
2192 }
2193
2194 if (secret) {
2195 flags |= RNP_KEY_REMOVE_SECRET;
2196 }
2197 if (primary) {
2198 flags |= RNP_KEY_REMOVE_SUBKEYS;
2199 }
2200
2201 if (secret && !rnp->cfg().get_bool(CFG_FORCE)) {
2202 if (!cli_rnp_get_confirmation(
2203 rnp,
2204 "Key '%s' has corresponding secret key. Do you really want to delete it?",
2205 key)) {
2206 goto done;
2207 }
2208 }
2209
2210 ret = rnp_key_remove(keys[0], flags);
2211
2212 if (ret) {
2213 ERR_MSG("Failed to remove the key: error %d", (int) ret);
2214 goto done;
2215 }
2216 res = cli_rnp_save_keyrings(rnp);
2217 done:
2218 clear_key_handles(keys);
2219 return res;
2220 }
2221
2222 bool
cli_rnp_add_key(cli_rnp_t * rnp)2223 cli_rnp_add_key(cli_rnp_t *rnp)
2224 {
2225 const std::string &path = rnp->cfg().get_str(CFG_KEYFILE);
2226 if (path.empty()) {
2227 return false;
2228 }
2229
2230 rnp_input_t input = cli_rnp_input_from_specifier(*rnp, path, NULL);
2231 if (!input) {
2232 ERR_MSG("failed to open key from %s", path.c_str());
2233 return false;
2234 }
2235
2236 bool res = !rnp_import_keys(
2237 rnp->ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS, NULL);
2238 rnp_input_destroy(input);
2239
2240 // set default key if we didn't have one
2241 if (res && rnp->defkey().empty()) {
2242 rnp->set_defkey();
2243 }
2244 return res;
2245 }
2246
2247 static bool
strip_extension(std::string & src)2248 strip_extension(std::string &src)
2249 {
2250 size_t dpos = src.find_last_of('.');
2251 if (dpos == std::string::npos) {
2252 return false;
2253 }
2254 src.resize(dpos);
2255 return true;
2256 }
2257
2258 static bool
has_extension(const std::string & path,const std::string & ext)2259 has_extension(const std::string &path, const std::string &ext)
2260 {
2261 if (path.length() < ext.length()) {
2262 return false;
2263 }
2264 return path.compare(path.length() - ext.length(), ext.length(), ext) == 0;
2265 }
2266
2267 static std::string
output_extension(const rnp_cfg & cfg,const std::string & op)2268 output_extension(const rnp_cfg &cfg, const std::string &op)
2269 {
2270 if (op == "encrypt_sign") {
2271 bool armor = cfg.get_bool(CFG_ARMOR);
2272 if (cfg.get_bool(CFG_DETACHED)) {
2273 return armor ? EXT_ASC : EXT_SIG;
2274 }
2275 if (cfg.get_bool(CFG_CLEARTEXT)) {
2276 return EXT_ASC;
2277 }
2278 return armor ? EXT_ASC : EXT_PGP;
2279 }
2280 if (op == "armor") {
2281 return EXT_ASC;
2282 }
2283 return "";
2284 }
2285
2286 static std::string
extract_filename(const std::string path)2287 extract_filename(const std::string path)
2288 {
2289 size_t lpos = path.find_last_of("/\\");
2290 if (lpos == std::string::npos) {
2291 return path;
2292 }
2293 return path.substr(lpos + 1);
2294 }
2295
2296 static bool
cli_rnp_init_io(const std::string & op,rnp_input_t * input,rnp_output_t * output,cli_rnp_t * rnp)2297 cli_rnp_init_io(const std::string &op,
2298 rnp_input_t * input,
2299 rnp_output_t * output,
2300 cli_rnp_t * rnp)
2301 {
2302 const std::string &in = rnp->cfg().get_str(CFG_INFILE);
2303 bool is_pathin = true;
2304 if (input) {
2305 *input = cli_rnp_input_from_specifier(*rnp, in, &is_pathin);
2306 if (!*input) {
2307 return false;
2308 }
2309 }
2310
2311 if (!output) {
2312 return true;
2313 }
2314 std::string out = rnp->cfg().get_str(CFG_OUTFILE);
2315 bool discard = (op == "verify") && out.empty() && rnp->cfg().get_bool(CFG_NO_OUTPUT);
2316
2317 if (out.empty() && is_pathin && !discard) {
2318 std::string ext = output_extension(rnp->cfg(), op);
2319 if (!ext.empty()) {
2320 out = in + ext;
2321 }
2322 }
2323
2324 *output = cli_rnp_output_to_specifier(*rnp, out, discard);
2325 if (!*output && input) {
2326 rnp_input_destroy(*input);
2327 *input = NULL;
2328 }
2329 return *output;
2330 }
2331
2332 bool
cli_rnp_dump_file(cli_rnp_t * rnp)2333 cli_rnp_dump_file(cli_rnp_t *rnp)
2334 {
2335 rnp_input_t input = NULL;
2336 rnp_output_t output = NULL;
2337 uint32_t flags = 0;
2338 uint32_t jflags = 0;
2339
2340 if (rnp->cfg().get_bool(CFG_GRIPS)) {
2341 flags |= RNP_DUMP_GRIP;
2342 jflags |= RNP_JSON_DUMP_GRIP;
2343 }
2344 if (rnp->cfg().get_bool(CFG_MPIS)) {
2345 flags |= RNP_DUMP_MPI;
2346 jflags |= RNP_JSON_DUMP_MPI;
2347 }
2348 if (rnp->cfg().get_bool(CFG_RAW)) {
2349 flags |= RNP_DUMP_RAW;
2350 jflags |= RNP_JSON_DUMP_RAW;
2351 }
2352
2353 rnp_result_t ret = 0;
2354 if (!cli_rnp_init_io("dump", &input, &output, rnp)) {
2355 ERR_MSG("failed to open source or create output");
2356 ret = 1;
2357 goto done;
2358 }
2359
2360 if (rnp->cfg().get_bool(CFG_JSON)) {
2361 char *json = NULL;
2362 ret = rnp_dump_packets_to_json(input, jflags, &json);
2363 if (!ret) {
2364 size_t len = strlen(json);
2365 size_t written = 0;
2366 ret = rnp_output_write(output, json, len, &written);
2367 if (written < len) {
2368 ret = RNP_ERROR_WRITE;
2369 }
2370 // add trailing empty line
2371 if (!ret) {
2372 ret = rnp_output_write(output, "\n", 1, &written);
2373 }
2374 if (written < 1) {
2375 ret = RNP_ERROR_WRITE;
2376 }
2377 rnp_buffer_destroy(json);
2378 }
2379 } else {
2380 ret = rnp_dump_packets_to_output(input, output, flags);
2381 }
2382 rnp_input_destroy(input);
2383 rnp_output_destroy(output);
2384 done:
2385 return !ret;
2386 }
2387
2388 bool
cli_rnp_armor_file(cli_rnp_t * rnp)2389 cli_rnp_armor_file(cli_rnp_t *rnp)
2390 {
2391 rnp_input_t input = NULL;
2392 rnp_output_t output = NULL;
2393
2394 if (!cli_rnp_init_io("armor", &input, &output, rnp)) {
2395 ERR_MSG("failed to open source or create output");
2396 return false;
2397 }
2398 rnp_result_t ret = rnp_enarmor(input, output, rnp->cfg().get_cstr(CFG_ARMOR_DATA_TYPE));
2399 rnp_input_destroy(input);
2400 rnp_output_destroy(output);
2401 return !ret;
2402 }
2403
2404 bool
cli_rnp_dearmor_file(cli_rnp_t * rnp)2405 cli_rnp_dearmor_file(cli_rnp_t *rnp)
2406 {
2407 rnp_input_t input = NULL;
2408 rnp_output_t output = NULL;
2409
2410 if (!cli_rnp_init_io("dearmor", &input, &output, rnp)) {
2411 ERR_MSG("failed to open source or create output");
2412 return false;
2413 }
2414
2415 rnp_result_t ret = rnp_dearmor(input, output);
2416 rnp_input_destroy(input);
2417 rnp_output_destroy(output);
2418 return !ret;
2419 }
2420
2421 static bool
cli_rnp_sign(const rnp_cfg & cfg,cli_rnp_t * rnp,rnp_input_t input,rnp_output_t output)2422 cli_rnp_sign(const rnp_cfg &cfg, cli_rnp_t *rnp, rnp_input_t input, rnp_output_t output)
2423 {
2424 rnp_op_sign_t op = NULL;
2425 rnp_result_t ret = RNP_ERROR_GENERIC;
2426 bool cleartext = cfg.get_bool(CFG_CLEARTEXT);
2427 bool detached = cfg.get_bool(CFG_DETACHED);
2428
2429 if (cleartext) {
2430 ret = rnp_op_sign_cleartext_create(&op, rnp->ffi, input, output);
2431 } else if (detached) {
2432 ret = rnp_op_sign_detached_create(&op, rnp->ffi, input, output);
2433 } else {
2434 ret = rnp_op_sign_create(&op, rnp->ffi, input, output);
2435 }
2436
2437 if (ret) {
2438 ERR_MSG("failed to initialize signing");
2439 return false;
2440 }
2441
2442 /* setup sign operation via cfg */
2443 bool res = false;
2444 std::vector<std::string> signers;
2445 std::vector<rnp_key_handle_t> signkeys;
2446
2447 if (!cleartext) {
2448 rnp_op_sign_set_armor(op, cfg.get_bool(CFG_ARMOR));
2449 }
2450
2451 if (!cleartext && !detached) {
2452 const std::string &fname = cfg.get_str(CFG_INFILE);
2453 if (!fname.empty()) {
2454 if (rnp_op_sign_set_file_name(op, extract_filename(fname).c_str())) {
2455 goto done;
2456 }
2457 rnp_op_sign_set_file_mtime(op, rnp_filemtime(fname.c_str()));
2458 }
2459 if (rnp_op_sign_set_compression(op, cfg.get_cstr(CFG_ZALG), cfg.get_int(CFG_ZLEVEL))) {
2460 goto done;
2461 }
2462 }
2463
2464 if (rnp_op_sign_set_hash(op, cfg.get_hashalg().c_str())) {
2465 goto done;
2466 }
2467 rnp_op_sign_set_creation_time(op, cfg.get_sig_creation());
2468 {
2469 uint32_t expiration = 0;
2470 if (cfg.get_expiration(CFG_EXPIRATION, expiration)) {
2471 rnp_op_sign_set_expiration_time(op, expiration);
2472 }
2473 }
2474
2475 /* signing keys */
2476 signers = cfg.get_list(CFG_SIGNERS);
2477 if (!cli_rnp_keys_matching_strings(rnp,
2478 signkeys,
2479 signers,
2480 CLI_SEARCH_SECRET | CLI_SEARCH_DEFAULT |
2481 CLI_SEARCH_SUBKEYS | CLI_SEARCH_FIRST_ONLY)) {
2482 ERR_MSG("Failed to build signing keys list");
2483 goto done;
2484 }
2485 for (rnp_key_handle_t key : signkeys) {
2486 if (rnp_op_sign_add_signature(op, key, NULL)) {
2487 ERR_MSG("Failed to add signature");
2488 goto done;
2489 }
2490 }
2491
2492 /* execute sign operation */
2493 res = !rnp_op_sign_execute(op);
2494 done:
2495 clear_key_handles(signkeys);
2496 rnp_op_sign_destroy(op);
2497 return res;
2498 }
2499
2500 static bool
cli_rnp_encrypt_and_sign(const rnp_cfg & cfg,cli_rnp_t * rnp,rnp_input_t input,rnp_output_t output)2501 cli_rnp_encrypt_and_sign(const rnp_cfg &cfg,
2502 cli_rnp_t * rnp,
2503 rnp_input_t input,
2504 rnp_output_t output)
2505 {
2506 rnp_op_encrypt_t op = NULL;
2507
2508 if (rnp_op_encrypt_create(&op, rnp->ffi, input, output)) {
2509 ERR_MSG("failed to initialize encryption");
2510 return false;
2511 }
2512
2513 std::string fname;
2514 std::string aalg;
2515 std::vector<rnp_key_handle_t> enckeys;
2516 std::vector<rnp_key_handle_t> signkeys;
2517 bool res = false;
2518 rnp_result_t ret;
2519
2520 rnp_op_encrypt_set_armor(op, cfg.get_bool(CFG_ARMOR));
2521
2522 fname = cfg.get_str(CFG_INFILE);
2523 if (!fname.empty()) {
2524 if (rnp_op_encrypt_set_file_name(op, extract_filename(fname).c_str())) {
2525 goto done;
2526 }
2527 rnp_op_encrypt_set_file_mtime(op, rnp_filemtime(fname.c_str()));
2528 }
2529 if (rnp_op_encrypt_set_compression(op, cfg.get_cstr(CFG_ZALG), cfg.get_int(CFG_ZLEVEL))) {
2530 goto done;
2531 }
2532 if (rnp_op_encrypt_set_cipher(op, cfg.get_cstr(CFG_CIPHER))) {
2533 goto done;
2534 }
2535 if (rnp_op_encrypt_set_hash(op, cfg.get_hashalg().c_str())) {
2536 goto done;
2537 }
2538 aalg = cfg.has(CFG_AEAD) ? cfg.get_str(CFG_AEAD) : "None";
2539 if (rnp_op_encrypt_set_aead(op, aalg.c_str())) {
2540 goto done;
2541 }
2542 if (cfg.has(CFG_AEAD_CHUNK) &&
2543 rnp_op_encrypt_set_aead_bits(op, cfg.get_int(CFG_AEAD_CHUNK))) {
2544 goto done;
2545 }
2546
2547 /* adding passwords if password-based encryption is used */
2548 if (cfg.get_bool(CFG_ENCRYPT_SK)) {
2549 std::string halg = cfg.get_hashalg();
2550 std::string ealg = cfg.get_str(CFG_CIPHER);
2551
2552 for (int i = 0; i < cfg.get_int(CFG_PASSWORDC, 1); i++) {
2553 if (rnp_op_encrypt_add_password(op, NULL, halg.c_str(), 0, ealg.c_str())) {
2554 ERR_MSG("Failed to add encrypting password");
2555 goto done;
2556 }
2557 }
2558 }
2559
2560 /* adding encrypting keys if pk-encryption is used */
2561 if (cfg.get_bool(CFG_ENCRYPT_PK)) {
2562 std::vector<std::string> keynames = cfg.get_list(CFG_RECIPIENTS);
2563 if (!cli_rnp_keys_matching_strings(rnp,
2564 enckeys,
2565 keynames,
2566 CLI_SEARCH_DEFAULT | CLI_SEARCH_SUBKEYS |
2567 CLI_SEARCH_FIRST_ONLY)) {
2568 ERR_MSG("Failed to build recipients key list");
2569 goto done;
2570 }
2571 for (rnp_key_handle_t key : enckeys) {
2572 if (rnp_op_encrypt_add_recipient(op, key)) {
2573 ERR_MSG("Failed to add recipient");
2574 goto done;
2575 }
2576 }
2577 }
2578
2579 /* adding signatures if encrypt-and-sign is used */
2580 if (cfg.get_bool(CFG_SIGN_NEEDED)) {
2581 rnp_op_encrypt_set_creation_time(op, cfg.get_sig_creation());
2582 uint32_t expiration;
2583 if (cfg.get_expiration(CFG_EXPIRATION, expiration)) {
2584 rnp_op_encrypt_set_expiration_time(op, expiration);
2585 }
2586
2587 /* signing keys */
2588 std::vector<std::string> keynames = cfg.get_list(CFG_SIGNERS);
2589 if (!cli_rnp_keys_matching_strings(rnp,
2590 signkeys,
2591 keynames,
2592 CLI_SEARCH_SECRET | CLI_SEARCH_DEFAULT |
2593 CLI_SEARCH_SUBKEYS | CLI_SEARCH_FIRST_ONLY)) {
2594 ERR_MSG("Failed to build signing keys list");
2595 goto done;
2596 }
2597 for (rnp_key_handle_t key : signkeys) {
2598 if (rnp_op_encrypt_add_signature(op, key, NULL)) {
2599 ERR_MSG("Failed to add signature");
2600 goto done;
2601 }
2602 }
2603 }
2604
2605 /* execute encrypt or encrypt-and-sign operation */
2606 ret = rnp_op_encrypt_execute(op);
2607 res = (ret == RNP_SUCCESS);
2608 if (ret != RNP_SUCCESS) {
2609 ERR_MSG("Operation failed: %s", rnp_result_to_string(ret));
2610 }
2611 done:
2612 clear_key_handles(signkeys);
2613 clear_key_handles(enckeys);
2614 rnp_op_encrypt_destroy(op);
2615 return res;
2616 }
2617
2618 bool
cli_rnp_setup(cli_rnp_t * rnp)2619 cli_rnp_setup(cli_rnp_t *rnp)
2620 {
2621 /* unset CFG_PASSWD and empty CFG_PASSWD are different cases */
2622 if (rnp->cfg().has(CFG_PASSWD)) {
2623 const std::string &passwd = rnp->cfg().get_str(CFG_PASSWD);
2624 if (rnp_ffi_set_pass_provider(
2625 rnp->ffi, ffi_pass_callback_string, (void *) passwd.c_str())) {
2626 return false;
2627 }
2628 }
2629 rnp->pswdtries = rnp->cfg().get_pswdtries();
2630 return true;
2631 }
2632
2633 bool
cli_rnp_protect_file(cli_rnp_t * rnp)2634 cli_rnp_protect_file(cli_rnp_t *rnp)
2635 {
2636 rnp_input_t input = NULL;
2637 rnp_output_t output = NULL;
2638
2639 if (!cli_rnp_init_io("encrypt_sign", &input, &output, rnp)) {
2640 ERR_MSG("failed to open source or create output");
2641 return false;
2642 }
2643
2644 bool res = false;
2645 bool sign = rnp->cfg().get_bool(CFG_SIGN_NEEDED);
2646 bool encrypt = rnp->cfg().get_bool(CFG_ENCRYPT_PK) || rnp->cfg().get_bool(CFG_ENCRYPT_SK);
2647 if (sign && !encrypt) {
2648 res = cli_rnp_sign(rnp->cfg(), rnp, input, output);
2649 } else if (encrypt) {
2650 res = cli_rnp_encrypt_and_sign(rnp->cfg(), rnp, input, output);
2651 } else {
2652 ERR_MSG("No operation specified");
2653 }
2654
2655 rnp_input_destroy(input);
2656 rnp_output_destroy(output);
2657 return res;
2658 }
2659
2660 /* helper function which prints something like 'using RSA (Sign-Only) key 0x0102030405060708 */
2661 static void
cli_rnp_print_sig_key_info(FILE * resfp,rnp_signature_handle_t sig)2662 cli_rnp_print_sig_key_info(FILE *resfp, rnp_signature_handle_t sig)
2663 {
2664 char * keyid = NULL;
2665 const char *alg = "Unknown";
2666
2667 if (!rnp_signature_get_keyid(sig, &keyid)) {
2668 for (char *idptr = keyid; *idptr; ++idptr) {
2669 *idptr = tolower(*idptr);
2670 }
2671 }
2672
2673 char * json = NULL;
2674 json_object *pkts = NULL;
2675 json_object *sigpkt = NULL;
2676
2677 if (rnp_signature_packet_to_json(sig, RNP_JSON_DUMP_GRIP, &json)) {
2678 ERR_MSG("Signature error.");
2679 goto done;
2680 }
2681 if (!(pkts = json_tokener_parse(json))) {
2682 ERR_MSG("Signature JSON error");
2683 goto done;
2684 }
2685 if (!(sigpkt = json_object_array_get_idx(pkts, 0))) {
2686 ERR_MSG("Signature JSON error");
2687 goto done;
2688 }
2689 alg = json_obj_get_str(sigpkt, "algorithm.str");
2690 done:
2691 fprintf(resfp, "using %s key %s\n", alg, keyid ? keyid : "0000000000000000");
2692 rnp_buffer_destroy(keyid);
2693 rnp_buffer_destroy(json);
2694 json_object_put(pkts);
2695 }
2696
2697 static void
cli_rnp_print_signatures(cli_rnp_t * rnp,const std::vector<rnp_op_verify_signature_t> & sigs)2698 cli_rnp_print_signatures(cli_rnp_t *rnp, const std::vector<rnp_op_verify_signature_t> &sigs)
2699 {
2700 unsigned invalidc = 0;
2701 unsigned unknownc = 0;
2702 unsigned validc = 0;
2703 std::string title = "UNKNOWN signature";
2704 FILE * resfp = rnp->resfp;
2705
2706 for (auto sig : sigs) {
2707 rnp_result_t status = rnp_op_verify_signature_get_status(sig);
2708 switch (status) {
2709 case RNP_SUCCESS:
2710 title = "Good signature";
2711 validc++;
2712 break;
2713 case RNP_ERROR_SIGNATURE_EXPIRED:
2714 title = "EXPIRED signature";
2715 invalidc++;
2716 break;
2717 case RNP_ERROR_SIGNATURE_INVALID:
2718 title = "BAD signature";
2719 invalidc++;
2720 break;
2721 case RNP_ERROR_KEY_NOT_FOUND:
2722 title = "NO PUBLIC KEY for signature";
2723 unknownc++;
2724 break;
2725 default:
2726 title = "UNKNOWN signature";
2727 break;
2728 }
2729
2730 uint32_t create = 0;
2731 uint32_t expiry = 0;
2732 rnp_op_verify_signature_get_times(sig, &create, &expiry);
2733
2734 if (create > 0) {
2735 time_t crtime = create;
2736 fprintf(resfp,
2737 "%s made %s%s",
2738 title.c_str(),
2739 rnp_y2k38_warning(crtime) ? ">=" : "",
2740 rnp_ctime(crtime));
2741 if (expiry > 0) {
2742 crtime = rnp_timeadd(crtime, expiry);
2743 fprintf(resfp,
2744 "Valid until %s%s\n",
2745 rnp_y2k38_warning(crtime) ? ">=" : "",
2746 rnp_ctime(crtime));
2747 }
2748 } else {
2749 fprintf(resfp, "%s\n", title.c_str());
2750 }
2751
2752 rnp_signature_handle_t handle = NULL;
2753 if (rnp_op_verify_signature_get_handle(sig, &handle)) {
2754 ERR_MSG("Failed to obtain signature handle.");
2755 continue;
2756 }
2757
2758 cli_rnp_print_sig_key_info(resfp, handle);
2759 rnp_key_handle_t key = NULL;
2760
2761 if ((status != RNP_ERROR_KEY_NOT_FOUND) && !rnp_signature_get_signer(handle, &key)) {
2762 cli_rnp_print_key_info(resfp, rnp->ffi, key, false, false);
2763 rnp_key_handle_destroy(key);
2764 }
2765 rnp_signature_handle_destroy(handle);
2766 }
2767
2768 if (sigs.size() == 0) {
2769 ERR_MSG("No signature(s) found - is this a signed file?");
2770 } else if (invalidc > 0 || unknownc > 0) {
2771 ERR_MSG(
2772 "Signature verification failure: %u invalid signature(s), %u unknown signature(s)",
2773 invalidc,
2774 unknownc);
2775 } else {
2776 ERR_MSG("Signature(s) verified successfully");
2777 }
2778 }
2779
2780 bool
cli_rnp_process_file(cli_rnp_t * rnp)2781 cli_rnp_process_file(cli_rnp_t *rnp)
2782 {
2783 rnp_input_t input = NULL;
2784 if (!cli_rnp_init_io("verify", &input, NULL, rnp)) {
2785 ERR_MSG("failed to open source");
2786 return false;
2787 }
2788
2789 char *contents = NULL;
2790 if (rnp_guess_contents(input, &contents)) {
2791 ERR_MSG("failed to check source contents");
2792 rnp_input_destroy(input);
2793 return false;
2794 }
2795
2796 /* source data for detached signature verification */
2797 rnp_input_t source = NULL;
2798 rnp_output_t output = NULL;
2799 rnp_op_verify_t verify = NULL;
2800 rnp_result_t ret = RNP_ERROR_GENERIC;
2801 bool res = false;
2802 std::vector<rnp_op_verify_signature_t> sigs;
2803 size_t scount = 0;
2804
2805 if (rnp_casecmp(contents, "signature")) {
2806 /* detached signature */
2807 std::string in = rnp->cfg().get_str(CFG_INFILE);
2808 if (in.empty() || in == "-") {
2809 ERR_MSG("Cannot verify detached signature from stdin.");
2810 goto done;
2811 }
2812 if (!has_extension(in, EXT_SIG) && !has_extension(in, EXT_ASC)) {
2813 ERR_MSG("Unsupported detached signature extension.");
2814 goto done;
2815 }
2816 if (!strip_extension(in) || rnp_input_from_path(&source, in.c_str())) {
2817 ERR_MSG("Failed to open source for detached signature verification.");
2818 goto done;
2819 }
2820
2821 ret = rnp_op_verify_detached_create(&verify, rnp->ffi, source, input);
2822 } else {
2823 if (!cli_rnp_init_io("verify", NULL, &output, rnp)) {
2824 ERR_MSG("Failed to create output stream.");
2825 goto done;
2826 }
2827 ret = rnp_op_verify_create(&verify, rnp->ffi, input, output);
2828 }
2829 if (ret) {
2830 ERR_MSG("Failed to initialize verification/decryption operation.");
2831 goto done;
2832 }
2833
2834 res = !rnp_op_verify_execute(verify);
2835
2836 rnp_op_verify_get_signature_count(verify, &scount);
2837 if (!scount) {
2838 goto done;
2839 }
2840
2841 for (size_t i = 0; i < scount; i++) {
2842 rnp_op_verify_signature_t sig = NULL;
2843 if (rnp_op_verify_get_signature_at(verify, i, &sig)) {
2844 ERR_MSG("Failed to obtain signature info.");
2845 res = false;
2846 goto done;
2847 }
2848 try {
2849 sigs.push_back(sig);
2850 } catch (const std::exception &e) {
2851 ERR_MSG("%s", e.what());
2852 res = false;
2853 goto done;
2854 }
2855 }
2856 cli_rnp_print_signatures(rnp, sigs);
2857 done:
2858 rnp_buffer_destroy(contents);
2859 rnp_input_destroy(input);
2860 rnp_input_destroy(source);
2861 rnp_output_destroy(output);
2862 rnp_op_verify_destroy(verify);
2863 return res;
2864 }
2865
2866 void
cli_rnp_print_praise(void)2867 cli_rnp_print_praise(void)
2868 {
2869 printf("%s\n%s\n", PACKAGE_STRING, PACKAGE_BUGREPORT);
2870 printf("Backend: %s\n", rnp_backend_string());
2871 printf("Backend version: %s\n", rnp_backend_version());
2872 printf("Supported algorithms:\n");
2873 cli_rnp_print_feature(stdout, RNP_FEATURE_PK_ALG, "Public key");
2874 cli_rnp_print_feature(stdout, RNP_FEATURE_SYMM_ALG, "Encryption");
2875 cli_rnp_print_feature(stdout, RNP_FEATURE_AEAD_ALG, "AEAD");
2876 cli_rnp_print_feature(stdout, RNP_FEATURE_PROT_MODE, "Key protection");
2877 cli_rnp_print_feature(stdout, RNP_FEATURE_HASH_ALG, "Hash");
2878 cli_rnp_print_feature(stdout, RNP_FEATURE_COMP_ALG, "Compression");
2879 cli_rnp_print_feature(stdout, RNP_FEATURE_CURVE, "Curves");
2880 printf("Please report security issues at (https://www.rnpgp.org/feedback) and\n"
2881 "general bugs at https://github.com/rnpgp/rnp/issues.\n");
2882 }
2883
2884 void
cli_rnp_print_feature(FILE * fp,const char * type,const char * printed_type)2885 cli_rnp_print_feature(FILE *fp, const char *type, const char *printed_type)
2886 {
2887 char * result = NULL;
2888 size_t count;
2889 if (rnp_supported_features(type, &result) != RNP_SUCCESS) {
2890 ERR_MSG("Failed to list supported features: %s", type);
2891 return;
2892 }
2893 json_object *jso = json_tokener_parse(result);
2894 if (!jso) {
2895 ERR_MSG("Failed to parse JSON with features: %s", type);
2896 goto done;
2897 }
2898 fprintf(fp, "%s: ", printed_type);
2899 count = json_object_array_length(jso);
2900 for (size_t idx = 0; idx < count; idx++) {
2901 json_object *val = json_object_array_get_idx(jso, idx);
2902 fprintf(fp, " %s%s", json_object_get_string(val), idx < count - 1 ? "," : "");
2903 }
2904 fputs("\n", fp);
2905 fflush(fp);
2906 json_object_put(jso);
2907 done:
2908 rnp_buffer_destroy(result);
2909 }
2910