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