/*- * 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 "keypair_private.h" #include "libutil/str_util.h" #include "libutil/util.h" #include "unix-std.h" static gboolean openssl = FALSE; static gboolean verify = FALSE; static gboolean quiet = FALSE; static gchar *suffix = NULL; static gchar *pubkey_file = NULL; static gchar *pubkey = NULL; static gchar *keypair_file = NULL; static void rspamadm_signtool (gint argc, gchar **argv); static const char *rspamadm_signtool_help (gboolean full_help); struct rspamadm_command signtool_command = { .name = "signtool", .flags = 0, .help = rspamadm_signtool_help, .run = rspamadm_signtool }; 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 files", NULL}, {"pubkey", 'p', 0, G_OPTION_ARG_STRING, &pubkey, "Base32 encoded pubkey to verify", 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}, {NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL} }; static const char * rspamadm_signtool_help (gboolean full_help) { const char *help_str; if (full_help) { help_str = "Manage digital signatures\n\n" "Usage: rspamadm signtool [-o] -k [-v -p | -P ] [-S ] 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" "--help: shows available options and commands"; } else { help_str = "Create encryption key pairs"; } return help_str; } static bool rspamadm_sign_file (const gchar *fname, const guchar *sk) { gint fd_sig, fd_input; guchar sig[rspamd_cryptobox_MAX_SIGBYTES], *map; gchar sigpath[PATH_MAX]; struct stat st; if (suffix == NULL) { suffix = ".sig"; } fd_input = rspamd_file_xopen (fname, O_RDONLY, 0); if (fd_input == -1) { rspamd_fprintf (stderr, "cannot open %s: %s\n", fname, strerror (errno)); exit (errno); } 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); if (fd_sig == -1) { close (fd_input); rspamd_fprintf (stderr, "cannot open %s: %s\n", sigpath, strerror (errno)); exit (errno); } 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 (errno); } g_assert (rspamd_cryptobox_MAX_SIGBYTES >= rspamd_cryptobox_signature_bytes ()); rspamd_cryptobox_sign (sig, NULL, map, st.st_size, sk); write (fd_sig, sig, rspamd_cryptobox_signature_bytes ()); close (fd_sig); munmap (map, st.st_size); if (!quiet) { rspamd_fprintf (stdout, "signed %s; stored hash in %s\n", fname, sigpath); } return true; } static bool rspamadm_verify_file (const gchar *fname, const guchar *pk) { gint fd_sig, fd_input; guchar *map, *map_sig; gchar sigpath[PATH_MAX]; struct stat st, st_sig; bool ret; g_assert (rspamd_cryptobox_MAX_SIGBYTES >= rspamd_cryptobox_signature_bytes ()); if (suffix == NULL) { suffix = ".sig"; } fd_input = rspamd_file_xopen (fname, O_RDONLY, 0); if (fd_input == -1) { rspamd_fprintf (stderr, "cannot open %s: %s\n", fname, strerror (errno)); exit (errno); } 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); if (fd_sig == -1) { close (fd_input); rspamd_fprintf (stderr, "cannot open %s: %s\n", sigpath, strerror (errno)); exit (errno); } 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 (errno); } g_assert (fstat (fd_sig, &st_sig) != -1); if (st_sig.st_size != rspamd_cryptobox_signature_bytes ()) { close (fd_sig); rspamd_fprintf (stderr, "invalid signature size %s: %ud\n", fname, (guint)st_sig.st_size); munmap (map, st.st_size); exit (errno); } 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 (errno); } ret = rspamd_cryptobox_verify (map_sig, map, st.st_size, pk); 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 (gint argc, gchar **argv) { GOptionContext *context; GError *error = NULL; struct ucl_parser *parser; ucl_object_t *top; const ucl_object_t *elt; guchar *pk, *sk; gsize fsize, flen, klen; gint 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); exit (1); } if (openssl) { if (!rspamd_cryptobox_openssl_mode (TRUE)) { rspamd_fprintf (stderr, "cannot enable openssl mode (incompatible openssl)\n"); exit (1); } } if (verify && (!pubkey && !pubkey_file)) { rspamd_fprintf (stderr, "no pubkey for verification\n"); exit (1); } else if (!verify && (!keypair_file)) { rspamd_fprintf (stderr, "no keypair for signing\n"); exit (1); } if (verify) { g_assert (pubkey || pubkey_file); if (pubkey_file) { gint fd; gchar *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 (errno); } 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 (errno); } /* XXX: assume base32 pubkey now */ while (flen > 0 && g_ascii_isspace (map[flen - 1])) { flen --; } pk = rspamd_decode_base32 (map, flen, &klen); if (klen != rspamd_cryptobox_pk_sig_bytes () || pk == NULL) { rspamd_fprintf (stderr, "bad size %s: %ud, %ud expected\n", klen, rspamd_cryptobox_pk_sig_bytes ()); exit (errno); } munmap (map, fsize); } else { pk = rspamd_decode_base32 (pubkey, strlen (pubkey), &klen); if (klen != rspamd_cryptobox_pk_sig_bytes () || pk == NULL) { rspamd_fprintf (stderr, "bad size %s: %ud, %ud expected\n", klen, rspamd_cryptobox_pk_sig_bytes ()); exit (errno); } } for (i = 1; i < argc; i++) { /* XXX: support cmd line signature */ if (!rspamadm_verify_file (argv[i], pk)) { 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 (EINVAL); } ucl_parser_free (parser); /* XXX: add generic routine to parse all keypair types */ elt = ucl_object_find_key (top, "keypair"); /* XXX: add secure cleanup */ if (elt == NULL || ucl_object_type (elt) != UCL_OBJECT) { rspamd_fprintf (stderr, "cannot load keypair: absent keypair\n"); ucl_object_unref (top); exit (EINVAL); } elt = ucl_object_find_key (elt, "privkey"); if (elt == NULL || ucl_object_type (elt) != UCL_STRING) { rspamd_fprintf (stderr, "cannot load keypair: absent privkey\n"); ucl_object_unref (top); exit (EINVAL); } sk = rspamd_decode_base32 (ucl_object_tostring (elt), elt->len, &klen); ucl_object_unref (top); if (klen != rspamd_cryptobox_sk_sig_bytes () || sk == NULL) { rspamd_fprintf (stderr, "bad size %s: %ud, %ud expected\n", ucl_object_tostring (elt),klen, rspamd_cryptobox_sk_sig_bytes ()); exit (errno); } for (i = 1; i < argc; i++) { /* XXX: support cmd line signature */ if (!rspamadm_sign_file (argv[i], sk)) { rspamd_explicit_memzero (sk, klen); exit (EXIT_FAILURE); } } rspamd_explicit_memzero (sk, klen); g_free (sk); } }