Browse Source

[Feature] Finish all features of dkim_keygen in Lua

tags/3.6
Vsevolod Stakhov 1 year ago
parent
commit
a070e5a10a
No account linked to committer's email address
4 changed files with 60 additions and 387 deletions
  1. 25
    0
      lualib/rspamadm/dkim_keygen.lua
  2. 33
    0
      src/lua/lua_cryptobox.c
  3. 2
    2
      src/lua/lua_text.c
  4. 0
    385
      src/rspamadm/dkim_keygen.c

+ 25
- 0
lualib/rspamadm/dkim_keygen.lua View File

@@ -16,6 +16,7 @@ limitations under the License.

local argparse = require "argparse"
local rspamd_util = require "rspamd_util"
local rspamd_cryptobox = require "rspamd_cryptobox"

local parser = argparse()
:name 'rspamadm dkim_keygen'
@@ -64,6 +65,8 @@ parser:option '--priv-output'
['der'] = 'der',
}
:default 'pem'
parser:flag '-f --force'
:description 'Force overwrite of existing files'

local function split_string(input, max_length)
max_length = max_length or 253
@@ -114,6 +117,9 @@ local function gen_rsa_key(opts)

local sk,pk = rsa.keypair(opts.bits or 1024)
if opts.privkey then
if opts.force then
os.remove(opts.privkey)
end
sk:save(opts.privkey, opts.priv_output)
else
sk:save("-", opts.priv_output)
@@ -122,6 +128,25 @@ local function gen_rsa_key(opts)
print_public_key(opts, tostring(pk))
end

local function gen_eddsa_key(opts)
local sk,pk = rspamd_cryptobox.gen_dkim_keypair(opts.type)

if opts.privkey and opts.force then
os.remove(opts.privkey)
end
if not sk:save_in_file(opts.privkey, tonumber('0600', 8)) then
io.stderr:write('cannot save private key to ' .. (opts.privkey or 'stdout') .. '\n')
os.exit(1)
end

if not opts.privkey then
io.write("\n")
io.flush()
end

print_public_key(opts, tostring(pk))
end

local function handler(args)
local opts = parser:parse(args)


+ 33
- 0
src/lua/lua_cryptobox.c View File

@@ -2700,6 +2700,39 @@ lua_cryptobox_gen_dkim_keypair (lua_State *L)
rspamd_explicit_memzero (pk, sizeof (pk));
rspamd_explicit_memzero (sk, sizeof (sk));
}
else if (strcmp (alg_str, "ed25519-seed") == 0) {
rspamd_sig_pk_t pk;
rspamd_sig_sk_t sk;
gchar *b64_data;
gsize b64_len;

rspamd_cryptobox_keypair_sig (pk, sk, RSPAMD_CRYPTOBOX_MODE_25519);

/* Process private key */
b64_data = rspamd_encode_base64 (sk,
32,
-1, &b64_len);

priv_out = lua_newuserdata (L, sizeof (*priv_out));
rspamd_lua_setclass (L, "rspamd{text}", -1);
priv_out->start = b64_data;
priv_out->len = b64_len;
priv_out->flags = RSPAMD_TEXT_FLAG_OWN|RSPAMD_TEXT_FLAG_WIPE;

/* Process public key */
b64_data = rspamd_encode_base64 (pk,
rspamd_cryptobox_pk_sig_bytes (RSPAMD_CRYPTOBOX_MODE_25519),
-1, &b64_len);

pub_out = lua_newuserdata (L, sizeof (*pub_out));
rspamd_lua_setclass (L, "rspamd{text}", -1);
pub_out->start = b64_data;
pub_out->len = b64_len;
pub_out->flags = RSPAMD_TEXT_FLAG_OWN;

rspamd_explicit_memzero (pk, sizeof (pk));
rspamd_explicit_memzero (sk, sizeof (sk));
}
else {
return luaL_error (L, "invalid algorithm %s", alg_str);
}

+ 2
- 2
src/lua/lua_text.c View File

@@ -1118,12 +1118,12 @@ lua_text_save_in_file (lua_State *L)
fname = luaL_checkstring (L, 2);

if (lua_type (L, 3) == LUA_TNUMBER) {
mode = lua_tonumber (L, 3);
mode = lua_tointeger(L, 3);
}
}
else if (lua_type (L, 2) == LUA_TNUMBER) {
/* Created fd */
fd = lua_tonumber (L, 2);
fd = lua_tointeger (L, 2);
}

if (fd == -1) {

+ 0
- 385
src/rspamadm/dkim_keygen.c View File

@@ -1,385 +0,0 @@
/*-
* 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 "printf.h"
#include "str_util.h"
#include "libcryptobox/cryptobox.h"
#include "contrib/libottery/ottery.h"
#include "lua/lua_common.h"
#include "unix-std.h"

#include <openssl/rsa.h>
#include <openssl/bn.h>
#include <openssl/pem.h>

static gchar *privkey_file = NULL;
static gchar *selector = NULL;
static gchar *domain = NULL;
static guint bits = 1024;
static gchar *type = "rsa";

static void rspamadm_dkim_keygen (gint argc, gchar **argv,
const struct rspamadm_command *cmd);
static const char *rspamadm_dkim_keygen_help (gboolean full_help,
const struct rspamadm_command *cmd);
static void rspamadm_dkim_keygen_lua_subrs (gpointer pL);

struct rspamadm_command dkim_keygen_command = {
.name = "dkim_keygen",
.flags = 0,
.help = rspamadm_dkim_keygen_help,
.run = rspamadm_dkim_keygen,
.lua_subrs = rspamadm_dkim_keygen_lua_subrs,
};

static GOptionEntry entries[] = {
{"domain", 'd', 0, G_OPTION_ARG_STRING, &domain,
"Use the specified domain", NULL},
{"selector", 's', 0, G_OPTION_ARG_STRING, &selector,
"Use the specified selector", NULL},
{"privkey", 'k', 0, G_OPTION_ARG_STRING, &privkey_file,
"Save private key in the specified file", NULL},
{"bits", 'b', 0, G_OPTION_ARG_INT, &bits,
"Set key length to N bits (1024 by default)", NULL},
{"type", 't', 0, G_OPTION_ARG_STRING, &type,
"Key type: rsa or ed25519 (rsa by default)", NULL},
{NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL}
};

static const char *
rspamadm_dkim_keygen_help (gboolean full_help, const struct rspamadm_command *cmd)
{
const char *help_str;

if (full_help) {
help_str = "Create key pairs for dkim signing\n\n"
"Usage: rspamadm dkim_keygen -s selector -d domain [-k privkey] [-b bits]\n"
"Where options are:\n\n"
"-d: use the specified domain\n"
"-s: use the specified selector\n"
"-k: save private key to file instead of printing it to stdout\n"
"-b: set number of bits instead of 1024\n"
"--help: shows available options and commands";
}
else {
help_str = "Create dkim key pairs";
}

return help_str;
}

static void
rspamd_dkim_generate_rsa_keypair (const gchar *domain, const gchar *selector,
const gchar *priv_fname, const gchar *pub_fname,
guint keylen)
{
BIGNUM *e;
RSA *r;
BIO *pubout, *privout;
EVP_PKEY *pk;
gint rc;
glong publen;
gsize b64_len;
gchar *pubdata, *b64_data;
FILE *pubfile = NULL;

if (bits > 4096 || bits < 512) {
fprintf (stderr, "Bits number must be in the interval 512...4096\n");
exit (EXIT_FAILURE);
}

e = BN_new ();
r = RSA_new ();
pk = EVP_PKEY_new ();
g_assert (BN_set_word (e, RSA_F4) == 1);
g_assert (RSA_generate_key_ex (r, bits, e, NULL) == 1);
g_assert (EVP_PKEY_set1_RSA (pk, r) == 1);

if (priv_fname) {
int fd = open (priv_fname, O_WRONLY | O_CREAT | O_TRUNC, 0640);

if (fd < 0) {
rspamd_fprintf (stderr, "cannot open output file %s: %s\n",
priv_fname, strerror (errno));
exit (EXIT_FAILURE);
}

FILE *fp = fdopen (fd, "w");

if (fp == NULL) {
close (fd);
rspamd_fprintf (stderr, "cannot open output file %s: %s\n",
priv_fname, strerror (errno));
exit (EXIT_FAILURE);
}

privout = BIO_new_fp (fp, BIO_CLOSE);

if (privout == NULL) {
fclose (fp);
rspamd_fprintf (stderr, "cannot open output file %s: %s\n",
priv_fname, strerror (errno));
exit (EXIT_FAILURE);
}
} else {
privout = BIO_new_fp (stdout, BIO_NOCLOSE);
}

rc = PEM_write_bio_PrivateKey (privout, pk, NULL, NULL, 0, NULL, NULL);

if (rc != 1) {
rspamd_fprintf (stderr, "cannot write key to the output file %s: %s\n",
priv_fname ? priv_fname : "stdout", strerror (errno));
exit (EXIT_FAILURE);
}

BIO_free (privout);
fflush (stdout);

pubout = BIO_new (BIO_s_mem ());

rc = i2d_RSA_PUBKEY_bio (pubout, r);
publen = BIO_get_mem_data (pubout, &pubdata);

g_assert (publen > 0);
b64_data = rspamd_encode_base64 (pubdata, publen, -1, &b64_len);

if (pub_fname) {
pubfile = fopen (pub_fname, "w");

if (pubfile == NULL) {
rspamd_fprintf (stderr, "cannot open output file %s: %s\n",
pub_fname, strerror (errno));
exit (EXIT_FAILURE);
}
} else {
pubfile = stdout;
}

if (b64_len < 255 - 2) {
rspamd_fprintf (pubfile, "%s._domainkey IN TXT ( \"v=DKIM1; k=rsa; \"\n"
"\t\"p=%s\" ) ;\n",
selector ? selector : "selector",
b64_data);
} else {
guint i;
gint step = 253, remain = b64_len;

rspamd_fprintf (pubfile, "%s._domainkey IN TXT ( \"v=DKIM1; k=rsa; \"\n",
selector ? selector : "selector");

for (i = 0; i < b64_len; i += step, remain -= step) {
if (i == 0) {
rspamd_fprintf (pubfile, "\t\"p=%*s\"\n", MIN(step, remain), &b64_data[i]);
} else {
step = 255;
rspamd_fprintf (pubfile, "\t\"%*s\"\n", MIN(step, remain), &b64_data[i]);
}
}

rspamd_fprintf (pubfile, ") ; \n");
}

if (pubfile != stdout) {
fclose (pubfile);
}

g_free (b64_data);
BIO_free (pubout);
EVP_PKEY_free (pk);
RSA_free (r);
BN_free (e);
}

static void
rspamd_dkim_generate_ed25519_keypair (const gchar *domain, const gchar *selector,
const gchar *priv_fname, const gchar *pub_fname,
guint keylen, gboolean seeded)
{
rspamd_sig_sk_t ed_sk;
rspamd_sig_pk_t ed_pk;
gchar *base64_pk, *base64_sk;
FILE *pubfile = NULL, *privfile = NULL;

rspamd_cryptobox_keypair_sig (ed_pk, ed_sk, RSPAMD_CRYPTOBOX_MODE_25519);
if (seeded) {
/* Just encode seed, not the full sk */
base64_sk = rspamd_encode_base64_common (ed_sk, 32, 0, NULL, FALSE,
RSPAMD_TASK_NEWLINES_LF);
}
else {
base64_sk = rspamd_encode_base64_common (ed_sk,
rspamd_cryptobox_sk_sig_bytes (RSPAMD_CRYPTOBOX_MODE_25519),
0, NULL, FALSE,
RSPAMD_TASK_NEWLINES_LF);
}
base64_pk = rspamd_encode_base64_common (ed_pk, sizeof (ed_pk), 0, NULL, FALSE,
RSPAMD_TASK_NEWLINES_LF);

/* Cleanup sensitive data */
rspamd_explicit_memzero (ed_sk, sizeof (ed_sk));

if (priv_fname) {
privfile = fopen (priv_fname, "w");

if (privfile == NULL) {
rspamd_fprintf (stderr, "cannot open output file %s: %s\n",
priv_fname, strerror (errno));
rspamd_explicit_memzero (base64_sk, strlen (base64_sk));
g_free (base64_sk);
g_free (base64_pk);
exit (EXIT_FAILURE);
}
}
else {
privfile = stdout;
}

if (rspamd_fprintf (privfile, "%s\n", base64_sk) == -1) {
rspamd_fprintf (stderr, "cannot write to output file %s: %s\n",
priv_fname, strerror (errno));
rspamd_explicit_memzero (base64_sk, strlen (base64_sk));
g_free (base64_sk);
g_free (base64_pk);

if (privfile != stdout) {
fclose (privfile);
}

exit (EXIT_FAILURE);
}

if (privfile != stdout) {
fclose (privfile);
}

if (pub_fname) {
pubfile = fopen (pub_fname, "w");

if (pubfile == NULL) {
rspamd_fprintf (stderr, "cannot open output file %s: %s\n",
pub_fname, strerror (errno));
rspamd_explicit_memzero (base64_sk, strlen (base64_sk));
g_free (base64_sk);
g_free (base64_pk);
exit (EXIT_FAILURE);
}
}
else {
pubfile = stdout;
}

rspamd_fprintf (pubfile, "%s._domainkey IN TXT ( \"v=DKIM1; k=ed25519; \"\n"
"\t\"p=%s\" ) ;\n",
selector ? selector : "selector",
base64_pk);

if (pubfile != stdout) {
fclose (pubfile);
}

rspamd_explicit_memzero (base64_sk, strlen (base64_sk));
g_free (base64_sk);
g_free (base64_pk);
}

static void
rspamadm_dkim_generate_keypair (const gchar *domain, const gchar *selector,
const gchar *priv_fname, const gchar *pub_fname, guint keylen)
{
if (strcmp (type, "rsa") == 0) {
rspamd_dkim_generate_rsa_keypair (domain, selector, priv_fname,
pub_fname, keylen);
}
else if (strcmp (type, "ed25519") == 0) {
rspamd_dkim_generate_ed25519_keypair (domain, selector, priv_fname,
pub_fname, keylen, FALSE);
}
else if (strcmp (type, "ed25519-seed") == 0) {
rspamd_dkim_generate_ed25519_keypair (domain, selector, priv_fname,
pub_fname, keylen, TRUE);
}
else {
fprintf (stderr, "invalid key type: %s\n", type);
exit (EXIT_FAILURE);
}
}

static gint
rspamadm_dkim_keygen_lua_generate (lua_State *L)
{
const gchar *domain = luaL_checkstring (L, 1);
const gchar *selector = luaL_checkstring (L, 2);
const gchar *privfile = NULL, *pubfile = NULL;
guint key_bits = 1024;

if (domain == NULL || selector == NULL) {
return luaL_error (L, "invalid arguments");
}

if (lua_type (L, 3) == LUA_TSTRING) {
privfile = lua_tostring (L, 3);
}

if (lua_type (L, 4) == LUA_TSTRING) {
pubfile = lua_tostring (L, 4);
}

if (lua_type (L, 5) == LUA_TNUMBER) {
key_bits = lua_tonumber (L, 5);
}

rspamadm_dkim_generate_keypair (domain, selector, privfile, pubfile, key_bits);

return 0;
}

static void
rspamadm_dkim_keygen_lua_subrs (gpointer pL)
{
lua_State *L = pL;

lua_pushstring (L, "dkim_keygen");
lua_pushcfunction (L, rspamadm_dkim_keygen_lua_generate);
lua_settable (L, -3);
}

static void
rspamadm_dkim_keygen (gint argc, gchar **argv, const struct rspamadm_command *cmd)
{
GOptionContext *context;
GError *error = NULL;

context = g_option_context_new (
"dkim_keygen - create dkim 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)) {
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);
rspamadm_dkim_generate_keypair (domain, selector, privkey_file, NULL, bits);
}

Loading…
Cancel
Save