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