123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623 |
- /*-
- * Copyright 2016 Vsevolod Stakhov
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
- #include "config.h"
- #include "rspamadm.h"
- #include "cryptobox.h"
- #include "printf.h"
- #include "ucl.h"
- #include "libcryptobox/keypair.h"
- #include "libutil/str_util.h"
- #include "libutil/util.h"
- #include "unix-std.h"
- #ifdef HAVE_SYS_WAIT_H
- #include <sys/wait.h>
- #endif
-
- static gboolean openssl = FALSE;
- static gboolean verify = FALSE;
- static gboolean quiet = FALSE;
- static char *suffix = NULL;
- static char *pubkey_file = NULL;
- static char *pubkey = NULL;
- static char *pubout = NULL;
- static char *keypair_file = NULL;
- static char *editor = NULL;
- static gboolean edit = FALSE;
- enum rspamd_cryptobox_mode mode = RSPAMD_CRYPTOBOX_MODE_25519;
-
- static void rspamadm_signtool(int argc, char **argv,
- const struct rspamadm_command *cmd);
- static const char *rspamadm_signtool_help(gboolean full_help,
- const struct rspamadm_command *cmd);
-
- struct rspamadm_command signtool_command = {
- .name = "signtool",
- .flags = 0,
- .help = rspamadm_signtool_help,
- .run = rspamadm_signtool,
- .lua_subrs = NULL,
- };
-
- static GOptionEntry entries[] = {
- {"openssl", 'o', 0, G_OPTION_ARG_NONE, &openssl,
- "Generate openssl nistp256 keypair not curve25519 one", NULL},
- {"verify", 'v', 0, G_OPTION_ARG_NONE, &verify,
- "Verify signatures and not sign", NULL},
- {"suffix", 'S', 0, G_OPTION_ARG_STRING, &suffix,
- "Save signatures in file<suffix> files", NULL},
- {"pubkey", 'p', 0, G_OPTION_ARG_STRING, &pubkey,
- "Base32 encoded pubkey to verify", NULL},
- {"pubout", '\0', 0, G_OPTION_ARG_FILENAME, &pubout,
- "Output public key to the specified file", NULL},
- {"pubfile", 'P', 0, G_OPTION_ARG_FILENAME, &pubkey_file,
- "Load base32 encoded pubkey to verify from the file", NULL},
- {"keypair", 'k', 0, G_OPTION_ARG_STRING, &keypair_file,
- "UCL with keypair to load for signing", NULL},
- {"quiet", 'q', 0, G_OPTION_ARG_NONE, &quiet,
- "Be quiet", NULL},
- {"edit", 'e', 0, G_OPTION_ARG_NONE, &edit,
- "Run editor and sign the edited file", NULL},
- {"editor", '\0', 0, G_OPTION_ARG_STRING, &editor,
- "Use the specified editor instead of $EDITOR environment var", NULL},
- {NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL}};
-
- static const char *
- rspamadm_signtool_help(gboolean full_help,
- const struct rspamadm_command *cmd)
- {
- const char *help_str;
-
- if (full_help) {
- help_str = "Manage digital signatures\n\n"
- "Usage: rspamadm signtool [-o] -k <keypair_file> [-v -p <pubkey> | -P <pubkey_file>] [-S <suffix>] file1 ...\n"
- "Where options are:\n\n"
- "-v: verify against pubkey instead of \n"
- "-o: use ECDSA instead of EdDSA\n"
- "-p: load pubkey as base32 string\n"
- "-P: load pubkey paced in file\n"
- "-k: load signing keypair from ucl file\n"
- "-S: append suffix for signatures and store them in files\n"
- "-q: be quiet\n"
- "-e: opens file for editing and sign the result\n"
- "--editor: use the specified editor instead of $EDITOR environment var\n"
- "--help: shows available options and commands";
- }
- else {
- help_str = "Sign and verify files tool";
- }
-
- return help_str;
- }
-
- static int
- rspamadm_edit_file(const char *fname)
- {
- char tmppath[PATH_MAX], run_cmdline[PATH_MAX];
- unsigned char *map;
- gsize len = 0;
- int fd_out, retcode, child_argc;
- GPid child_pid;
- char *tmpdir, **child_argv = NULL;
- struct stat st;
- GError *err = NULL;
-
- if (editor == NULL) {
- editor = getenv("EDITOR");
- }
-
- if (editor == NULL) {
- rspamd_fprintf(stderr, "cannot find editor: specify $EDITOR "
- "environment variable or pass --editor argument\n");
- exit(EXIT_FAILURE);
- }
-
- tmpdir = getenv("TMPDIR");
- if (tmpdir == NULL) {
- tmpdir = "/tmp";
- }
-
- if (stat(fname, &st) == -1 || st.st_size == 0) {
- /* The source does not exist, but that shouldn't be a problem */
- len = 0;
- map = NULL;
-
- /* Try to touch source anyway */
- fd_out = rspamd_file_xopen(fname, O_WRONLY | O_CREAT | O_EXCL, 00644,
- 0);
-
- if (fd_out == -1) {
- rspamd_fprintf(stderr, "cannot open %s: %s\n", fname,
- strerror(errno));
- exit(EXIT_FAILURE);
- }
-
- close(fd_out);
- }
- else {
- map = rspamd_file_xmap(fname, PROT_READ, &len, TRUE);
-
- if (map == NULL) {
- rspamd_fprintf(stderr, "cannot open %s: %s\n", fname,
- strerror(errno));
- exit(EXIT_FAILURE);
- }
- }
-
- rspamd_snprintf(tmppath, sizeof(tmppath),
- "%s/rspamd_sign-XXXXXXXXXX", tmpdir);
- mode_t cur_umask = umask(S_IRWXO | S_IRWXG);
- fd_out = mkstemp(tmppath);
- (void) umask(cur_umask);
-
- if (fd_out == -1) {
- rspamd_fprintf(stderr, "cannot open tempfile %s: %s\n", tmppath,
- strerror(errno));
- exit(EXIT_FAILURE);
- }
-
- if (len > 0 && write(fd_out, map, len) == -1) {
- rspamd_fprintf(stderr, "cannot write to tempfile %s: %s\n", tmppath,
- strerror(errno));
- unlink(tmppath);
- munmap(map, len);
- close(fd_out);
- exit(EXIT_FAILURE);
- }
-
- if (len > 0) {
- munmap(map, len);
- }
-
- fsync(fd_out);
- close(fd_out);
-
- /* Now we spawn editor with the filename as argument */
- rspamd_snprintf(run_cmdline, sizeof(run_cmdline), "%s %s", editor, tmppath);
- if (!g_shell_parse_argv(run_cmdline, &child_argc,
- &child_argv, &err)) {
- rspamd_fprintf(stderr, "cannot exec %s: %e\n", editor,
- err);
- unlink(tmppath);
- exit(EXIT_FAILURE);
- }
-
- if (!g_spawn_async(NULL, child_argv, NULL,
- G_SPAWN_CHILD_INHERITS_STDIN | G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
- NULL, NULL, &child_pid, &err)) {
- rspamd_fprintf(stderr, "cannot exec %s: %e\n", editor,
- err);
- unlink(tmppath);
- exit(EXIT_FAILURE);
- }
-
- g_strfreev(child_argv);
-
- for (;;) {
- if (waitpid((pid_t) child_pid, &retcode, 0) != -1) {
- break;
- }
-
- if (errno != EINTR) {
- rspamd_fprintf(stderr, "failed to wait for %s: %s\n", editor,
- strerror(errno));
- unlink(tmppath);
- exit(EXIT_FAILURE);
- }
- }
-
- #if GLIB_MAJOR_VERSION >= 2 && GLIB_MINOR_VERSION >= 34
- #if GLIB_MINOR_VERSION >= 70
- if (!g_spawn_check_wait_status(retcode, &err)) {
- #else
- if (!g_spawn_check_exit_status(retcode, &err)) {
- #endif
- unlink(tmppath);
- rspamd_fprintf(stderr, "%s returned error code: %d - %e\n", editor,
- retcode, err);
- exit(EXIT_FAILURE);
- }
- #else
- if (retcode != 0) {
- unlink(tmppath);
- rspamd_fprintf(stderr, "%s returned error code: %d\n", editor,
- retcode);
- exit(retcode);
- }
- #endif
-
- map = rspamd_file_xmap(tmppath, PROT_READ, &len, TRUE);
-
- if (map == NULL) {
- rspamd_fprintf(stderr, "cannot map %s: %s\n", tmppath,
- strerror(errno));
- unlink(tmppath);
- exit(EXIT_FAILURE);
- }
-
- rspamd_snprintf(run_cmdline, sizeof(run_cmdline), "%s.new", fname);
- fd_out = rspamd_file_xopen(run_cmdline, O_RDWR | O_CREAT | O_TRUNC, 00600,
- 0);
-
- if (fd_out == -1) {
- rspamd_fprintf(stderr, "cannot open new file %s: %s\n", run_cmdline,
- strerror(errno));
- unlink(tmppath);
- munmap(map, len);
- exit(EXIT_FAILURE);
- }
-
- if (write(fd_out, map, len) == -1) {
- rspamd_fprintf(stderr, "cannot write new file %s: %s\n", run_cmdline,
- strerror(errno));
- unlink(tmppath);
- unlink(run_cmdline);
- close(fd_out);
- munmap(map, len);
- exit(EXIT_FAILURE);
- }
-
- unlink(tmppath);
- (void) lseek(fd_out, 0, SEEK_SET);
- munmap(map, len);
-
- return fd_out;
- }
-
- static bool
- rspamadm_sign_file(const char *fname, struct rspamd_cryptobox_keypair *kp)
- {
- int fd_sig, fd_input;
- unsigned char sig[rspamd_cryptobox_MAX_SIGBYTES], *map;
- char sigpath[PATH_MAX];
- FILE *pub_fp;
- struct stat st;
- const unsigned char *sk;
-
- if (suffix == NULL) {
- suffix = ".sig";
- }
-
- if (edit) {
- /* We need to open editor and then sign the temporary file */
- fd_input = rspamadm_edit_file(fname);
- }
- else {
- fd_input = rspamd_file_xopen(fname, O_RDONLY, 0, TRUE);
- }
-
- if (fd_input == -1) {
- rspamd_fprintf(stderr, "cannot open %s: %s\n", fname,
- strerror(errno));
- exit(EXIT_FAILURE);
- }
-
- g_assert(fstat(fd_input, &st) != -1);
-
- rspamd_snprintf(sigpath, sizeof(sigpath), "%s%s", fname, suffix);
- fd_sig = rspamd_file_xopen(sigpath, O_WRONLY | O_CREAT | O_TRUNC, 00644, 0);
-
- if (fd_sig == -1) {
- close(fd_input);
- rspamd_fprintf(stderr, "cannot open %s: %s\n", sigpath,
- strerror(errno));
- exit(EXIT_FAILURE);
- }
-
- map = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd_input, 0);
- close(fd_input);
-
- if (map == MAP_FAILED) {
- close(fd_sig);
- rspamd_fprintf(stderr, "cannot map %s: %s\n", fname,
- strerror(errno));
- exit(EXIT_FAILURE);
- }
-
- g_assert(rspamd_cryptobox_MAX_SIGBYTES >=
- rspamd_cryptobox_signature_bytes(mode));
-
- sk = rspamd_keypair_component(kp, RSPAMD_KEYPAIR_COMPONENT_SK, NULL);
- rspamd_cryptobox_sign(sig, NULL, map, st.st_size, sk, mode);
-
- if (edit) {
- /* We also need to rename .new file */
- rspamd_snprintf(sigpath, sizeof(sigpath), "%s.new", fname);
-
- if (rename(sigpath, fname) == -1) {
- rspamd_fprintf(stderr, "cannot rename %s to %s: %s\n", sigpath, fname,
- strerror(errno));
- exit(EXIT_FAILURE);
- }
-
- unlink(sigpath);
- }
-
- rspamd_snprintf(sigpath, sizeof(sigpath), "%s%s", fname, suffix);
-
- if (write(fd_sig, sig, rspamd_cryptobox_signature_bytes(mode)) == -1) {
- rspamd_fprintf(stderr, "cannot write signature to %s: %s\n", sigpath,
- strerror(errno));
- exit(EXIT_FAILURE);
- }
-
- close(fd_sig);
- munmap(map, st.st_size);
-
- if (!quiet) {
- rspamd_fprintf(stdout, "signed %s; stored hash in %s\n",
- fname, sigpath);
- }
-
- if (pubout) {
- GString *b32_pk;
-
- pub_fp = fopen(pubout, "w");
-
- if (pub_fp == NULL) {
- rspamd_fprintf(stderr, "cannot write pubkey to %s: %s",
- pubout, strerror(errno));
- }
- else {
- b32_pk = rspamd_keypair_print(kp,
- RSPAMD_KEYPAIR_PUBKEY | RSPAMD_KEYPAIR_BASE32);
-
- if (b32_pk) {
- rspamd_fprintf(pub_fp, "%v", b32_pk);
- g_string_free(b32_pk, TRUE);
- }
-
- fclose(pub_fp);
- }
- if (!quiet) {
- rspamd_fprintf(stdout, "stored pubkey in %s\n",
- pubout);
- }
- }
-
- return true;
- }
-
- static bool
- rspamadm_verify_file(const char *fname, const unsigned char *pk)
- {
- int fd_sig, fd_input;
- unsigned char *map, *map_sig;
- char sigpath[PATH_MAX];
- struct stat st, st_sig;
- bool ret;
-
- g_assert(rspamd_cryptobox_MAX_SIGBYTES >=
- rspamd_cryptobox_signature_bytes(mode));
-
- if (suffix == NULL) {
- suffix = ".sig";
- }
-
- fd_input = rspamd_file_xopen(fname, O_RDONLY, 0, TRUE);
-
- if (fd_input == -1) {
- rspamd_fprintf(stderr, "cannot open %s: %s\n", fname,
- strerror(errno));
- exit(EXIT_FAILURE);
- }
-
- g_assert(fstat(fd_input, &st) != -1);
-
- rspamd_snprintf(sigpath, sizeof(sigpath), "%s%s", fname, suffix);
- fd_sig = rspamd_file_xopen(sigpath, O_RDONLY, 0, TRUE);
-
- if (fd_sig == -1) {
- close(fd_input);
- rspamd_fprintf(stderr, "cannot open %s: %s\n", sigpath,
- strerror(errno));
- exit(EXIT_FAILURE);
- }
-
- map = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd_input, 0);
- close(fd_input);
-
- if (map == MAP_FAILED) {
- close(fd_sig);
- rspamd_fprintf(stderr, "cannot open %s: %s\n", sigpath,
- strerror(errno));
- exit(EXIT_FAILURE);
- }
-
- g_assert(fstat(fd_sig, &st_sig) != -1);
-
- if (st_sig.st_size != rspamd_cryptobox_signature_bytes(mode)) {
- close(fd_sig);
- rspamd_fprintf(stderr, "invalid signature size %s: %ud\n", fname,
- (unsigned int) st_sig.st_size);
- munmap(map, st.st_size);
- exit(EXIT_FAILURE);
- }
-
- map_sig = mmap(NULL, st_sig.st_size, PROT_READ, MAP_SHARED, fd_sig, 0);
- close(fd_sig);
-
- if (map_sig == MAP_FAILED) {
- munmap(map, st.st_size);
- rspamd_fprintf(stderr, "cannot map %s: %s\n", sigpath,
- strerror(errno));
- exit(EXIT_FAILURE);
- }
-
- ret = rspamd_cryptobox_verify(map_sig, st_sig.st_size,
- map, st.st_size, pk, mode);
- munmap(map, st.st_size);
- munmap(map_sig, st_sig.st_size);
-
- if (!ret) {
- rspamd_fprintf(stderr, "cannot verify %s using %s: invalid signature\n",
- fname, sigpath);
- }
- else if (!quiet) {
- rspamd_fprintf(stdout, "verified %s using %s\n",
- fname, sigpath);
- }
-
- return ret;
- }
-
-
- static void
- rspamadm_signtool(int argc, char **argv, const struct rspamadm_command *cmd)
- {
- GOptionContext *context;
- GError *error = NULL;
- struct ucl_parser *parser;
- ucl_object_t *top;
- struct rspamd_cryptobox_pubkey *pk;
- struct rspamd_cryptobox_keypair *kp;
- gsize fsize, flen;
- int i;
-
- context = g_option_context_new(
- "keypair - create encryption keys");
- g_option_context_set_summary(context,
- "Summary:\n Rspamd administration utility version " RVERSION
- "\n Release id: " RID);
- g_option_context_add_main_entries(context, entries, NULL);
-
- if (!g_option_context_parse(context, &argc, &argv, &error)) {
- rspamd_fprintf(stderr, "option parsing failed: %s\n", error->message);
- g_error_free(error);
- g_option_context_free(context);
- exit(EXIT_FAILURE);
- }
-
- g_option_context_free(context);
-
- if (openssl) {
- mode = RSPAMD_CRYPTOBOX_MODE_NIST;
- }
-
- if (verify && (!pubkey && !pubkey_file)) {
- rspamd_fprintf(stderr, "no pubkey for verification\n");
- exit(EXIT_FAILURE);
- }
- else if (!verify && (!keypair_file)) {
- rspamd_fprintf(stderr, "no keypair for signing\n");
- exit(EXIT_FAILURE);
- }
-
- if (verify) {
- g_assert(pubkey || pubkey_file);
-
- if (pubkey_file) {
- int fd;
- char *map;
- struct stat st;
-
- fd = open(pubkey_file, O_RDONLY);
-
- if (fd == -1) {
- rspamd_fprintf(stderr, "cannot open %s: %s\n", pubkey_file,
- strerror(errno));
- exit(EXIT_FAILURE);
- }
-
- g_assert(fstat(fd, &st) != -1);
- fsize = st.st_size;
- flen = fsize;
- map = mmap(NULL, fsize, PROT_READ, MAP_SHARED, fd, 0);
- close(fd);
-
- if (map == MAP_FAILED) {
- rspamd_fprintf(stderr, "cannot read %s: %s\n", pubkey_file,
- strerror(errno));
- exit(EXIT_FAILURE);
- }
-
- /* XXX: assume base32 pubkey now */
- while (flen > 0 && g_ascii_isspace(map[flen - 1])) {
- flen--;
- }
-
- pk = rspamd_pubkey_from_base32(map, flen,
- RSPAMD_KEYPAIR_SIGN, mode);
-
- if (pk == NULL) {
- rspamd_fprintf(stderr, "bad size %s: %ud, %ud expected\n",
- pubkey_file,
- (unsigned int) flen,
- rspamd_cryptobox_pk_sig_bytes(mode));
- exit(EXIT_FAILURE);
- }
-
- munmap(map, fsize);
- }
- else {
- pk = rspamd_pubkey_from_base32(pubkey, strlen(pubkey),
- RSPAMD_KEYPAIR_SIGN, mode);
-
- if (pk == NULL) {
- rspamd_fprintf(stderr, "bad size %s: %ud, %ud expected\n",
- pubkey_file,
- (unsigned int) strlen(pubkey),
- rspamd_cryptobox_pk_sig_bytes(mode));
- exit(EXIT_FAILURE);
- }
- }
-
- for (i = 1; i < argc; i++) {
- /* XXX: support cmd line signature */
- if (!rspamadm_verify_file(argv[i], rspamd_pubkey_get_pk(pk, NULL))) {
- exit(EXIT_FAILURE);
- }
- }
-
- g_free(pk);
- }
- else {
- g_assert(keypair_file != NULL);
-
- parser = ucl_parser_new(0);
-
- if (!ucl_parser_add_file(parser, keypair_file) ||
- (top = ucl_parser_get_object(parser)) == NULL) {
- rspamd_fprintf(stderr, "cannot load keypair: %s\n",
- ucl_parser_get_error(parser));
- exit(EXIT_FAILURE);
- }
-
- ucl_parser_free(parser);
-
- kp = rspamd_keypair_from_ucl(top);
-
- if (kp == NULL) {
- rspamd_fprintf(stderr, "invalid signing key\n");
- exit(EXIT_FAILURE);
- }
-
- if (rspamd_keypair_type(kp) != RSPAMD_KEYPAIR_SIGN) {
- rspamd_fprintf(stderr, "unsuitable for signing key\n");
- exit(EXIT_FAILURE);
- }
-
- for (i = 1; i < argc; i++) {
- /* XXX: support cmd line signature */
- if (!rspamadm_sign_file(argv[i], kp)) {
- rspamd_keypair_unref(kp);
- exit(EXIT_FAILURE);
- }
- }
-
- rspamd_keypair_unref(kp);
- }
- }
|