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