]> source.dussan.org Git - rspamd.git/commitdiff
Add utility to manage digital signatures
authorVsevolod Stakhov <vsevolod@highsecure.ru>
Wed, 3 Feb 2016 00:41:01 +0000 (00:41 +0000)
committerVsevolod Stakhov <vsevolod@highsecure.ru>
Wed, 3 Feb 2016 00:41:01 +0000 (00:41 +0000)
src/rspamadm/CMakeLists.txt
src/rspamadm/commands.c
src/rspamadm/signtool.c [new file with mode: 0644]

index 7d5170cf53adb44ad73009e4aa66460c7140a7d2..732086afc2553cfd7e223b5f14de2500177bbc51 100644 (file)
@@ -8,6 +8,7 @@ SET(RSPAMADMSRC rspamadm.c
         control.c
         confighelp.c
         stat_convert.c
+        signtool.c
         ${CMAKE_BINARY_DIR}/src/workers.c
         ${CMAKE_BINARY_DIR}/src/modules.c
         ${CMAKE_SOURCE_DIR}/src/controller.c
index 4514376e1e9e09e395be3107509252d9922d4bed..3cb928afd318c6d37f72165a11ee154983e5e0a1 100644 (file)
@@ -31,6 +31,7 @@ extern struct rspamadm_command configdump_command;
 extern struct rspamadm_command control_command;
 extern struct rspamadm_command confighelp_command;
 extern struct rspamadm_command statconvert_command;
+extern struct rspamadm_command signtool_command;
 
 const struct rspamadm_command *commands[] = {
        &help_command,
@@ -42,6 +43,7 @@ const struct rspamadm_command *commands[] = {
        &control_command,
        &confighelp_command,
        &statconvert_command,
+       &signtool_command,
        NULL
 };
 
diff --git a/src/rspamadm/signtool.c b/src/rspamadm/signtool.c
new file mode 100644 (file)
index 0000000..c63c160
--- /dev/null
@@ -0,0 +1,386 @@
+/*-
+ * 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<suffix> 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 <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"
+                               "--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);
+       }
+}