diff options
Diffstat (limited to '')
-rw-r--r-- | src/lua/lua_rsa.c | 867 |
1 files changed, 867 insertions, 0 deletions
diff --git a/src/lua/lua_rsa.c b/src/lua/lua_rsa.c new file mode 100644 index 0000000..ae5acc8 --- /dev/null +++ b/src/lua/lua_rsa.c @@ -0,0 +1,867 @@ +/*- + * 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. + */ +/** + * @file lua_rsa.c + * This module exports routines to load rsa keys, check inline or external + * rsa signatures. It assumes sha256 based signatures. + */ + +#include "lua_common.h" +#include "unix-std.h" +#include <openssl/err.h> +#include <openssl/pem.h> +#include <openssl/sha.h> +#include <openssl/rsa.h> + +LUA_FUNCTION_DEF(rsa_pubkey, load); +LUA_FUNCTION_DEF(rsa_pubkey, create); +LUA_FUNCTION_DEF(rsa_pubkey, gc); +LUA_FUNCTION_DEF(rsa_pubkey, tostring); + +LUA_FUNCTION_DEF(rsa_privkey, load_file); +LUA_FUNCTION_DEF(rsa_privkey, load_pem); +LUA_FUNCTION_DEF(rsa_privkey, load_raw); +LUA_FUNCTION_DEF(rsa_privkey, load_base64); +LUA_FUNCTION_DEF(rsa_privkey, create); +LUA_FUNCTION_DEF(rsa_privkey, gc); +LUA_FUNCTION_DEF(rsa_privkey, save); + +LUA_FUNCTION_DEF(rsa_signature, create); +LUA_FUNCTION_DEF(rsa_signature, load); +LUA_FUNCTION_DEF(rsa_signature, save); +LUA_FUNCTION_DEF(rsa_signature, base64); +LUA_FUNCTION_DEF(rsa_signature, gc); + +LUA_FUNCTION_DEF(rsa, verify_memory); +LUA_FUNCTION_DEF(rsa, sign_memory); +LUA_FUNCTION_DEF(rsa, keypair); + +static const struct luaL_reg rsalib_f[] = { + LUA_INTERFACE_DEF(rsa, verify_memory), + LUA_INTERFACE_DEF(rsa, sign_memory), + LUA_INTERFACE_DEF(rsa, keypair), + {NULL, NULL}}; + +static const struct luaL_reg rsapubkeylib_f[] = { + LUA_INTERFACE_DEF(rsa_pubkey, load), + LUA_INTERFACE_DEF(rsa_pubkey, create), + {NULL, NULL}}; + +static const struct luaL_reg rsapubkeylib_m[] = { + {"__tostring", lua_rsa_pubkey_tostring}, + {"__gc", lua_rsa_pubkey_gc}, + {NULL, NULL}}; + +static const struct luaL_reg rsaprivkeylib_f[] = { + LUA_INTERFACE_DEF(rsa_privkey, load_file), + LUA_INTERFACE_DEF(rsa_privkey, load_pem), + LUA_INTERFACE_DEF(rsa_privkey, load_raw), + LUA_INTERFACE_DEF(rsa_privkey, load_base64), + LUA_INTERFACE_DEF(rsa_privkey, create), + {NULL, NULL}}; + +static const struct luaL_reg rsaprivkeylib_m[] = { + {"__tostring", rspamd_lua_class_tostring}, + {"__gc", lua_rsa_privkey_gc}, + LUA_INTERFACE_DEF(rsa_privkey, save), + {NULL, NULL}}; + +static const struct luaL_reg rsasignlib_f[] = { + LUA_INTERFACE_DEF(rsa_signature, load), + LUA_INTERFACE_DEF(rsa_signature, create), + {NULL, NULL}}; + +static const struct luaL_reg rsasignlib_m[] = { + LUA_INTERFACE_DEF(rsa_signature, save), + LUA_INTERFACE_DEF(rsa_signature, base64), + {"__tostring", rspamd_lua_class_tostring}, + {"__gc", lua_rsa_signature_gc}, + {NULL, NULL}}; + +static RSA * +lua_check_rsa_pubkey(lua_State *L, int pos) +{ + void *ud = rspamd_lua_check_udata(L, pos, "rspamd{rsa_pubkey}"); + + luaL_argcheck(L, ud != NULL, 1, "'rsa_pubkey' expected"); + return ud ? *((RSA **) ud) : NULL; +} + +static RSA * +lua_check_rsa_privkey(lua_State *L, int pos) +{ + void *ud = rspamd_lua_check_udata(L, pos, "rspamd{rsa_privkey}"); + + luaL_argcheck(L, ud != NULL, 1, "'rsa_privkey' expected"); + return ud ? *((RSA **) ud) : NULL; +} + +static rspamd_fstring_t * +lua_check_rsa_sign(lua_State *L, int pos) +{ + void *ud = rspamd_lua_check_udata(L, pos, "rspamd{rsa_signature}"); + + luaL_argcheck(L, ud != NULL, 1, "'rsa_signature' expected"); + return ud ? *((rspamd_fstring_t **) ud) : NULL; +} + +static gint +lua_rsa_pubkey_load(lua_State *L) +{ + RSA *rsa = NULL, **prsa; + const gchar *filename; + FILE *f; + + filename = luaL_checkstring(L, 1); + if (filename != NULL) { + f = fopen(filename, "r"); + if (f == NULL) { + msg_err("cannot open pubkey from file: %s, %s", + filename, + strerror(errno)); + lua_pushnil(L); + } + else { + if (!PEM_read_RSA_PUBKEY(f, &rsa, NULL, NULL)) { + msg_err("cannot open pubkey from file: %s, %s", filename, + ERR_error_string(ERR_get_error(), NULL)); + lua_pushnil(L); + } + else { + prsa = lua_newuserdata(L, sizeof(RSA *)); + rspamd_lua_setclass(L, "rspamd{rsa_pubkey}", -1); + *prsa = rsa; + } + fclose(f); + } + } + else { + lua_pushnil(L); + } + return 1; +} + +static gint +lua_rsa_privkey_save(lua_State *L) +{ + const gchar *filename; + const gchar *type = "pem"; + FILE *f; + int ret; + + RSA *rsa = lua_check_rsa_privkey(L, 1); + + filename = luaL_checkstring(L, 2); + if (lua_gettop(L) > 2) { + type = luaL_checkstring(L, 3); + } + + if (rsa != NULL && filename != NULL) { + if (strcmp(filename, "-") == 0) { + f = stdout; + } + else { + f = fopen(filename, "wb"); + } + if (f == NULL) { + msg_err("cannot save privkey to file: %s, %s", + filename, + strerror(errno)); + lua_pushboolean(L, FALSE); + } + else { + if (f != stdout) { + /* Set secure permissions for the private key file */ + chmod(filename, S_IRUSR | S_IWUSR); + } + + if (strcmp(type, "der") == 0) { + ret = i2d_RSAPrivateKey_fp(f, rsa); + } + else { + ret = PEM_write_RSAPrivateKey(f, rsa, NULL, NULL, 0, NULL, NULL); + } + + if (!ret) { + msg_err("cannot save privkey to file: %s, %s", filename, + ERR_error_string(ERR_get_error(), NULL)); + lua_pushboolean(L, FALSE); + } + else { + lua_pushboolean(L, TRUE); + } + + if (f != stdout) { + fclose(f); + } + else { + fflush(f); + } + } + } + else { + lua_pushboolean(L, FALSE); + } + + return 1; +} + + +static gint +lua_rsa_pubkey_create(lua_State *L) +{ + RSA *rsa = NULL, **prsa; + const gchar *buf; + BIO *bp; + + buf = luaL_checkstring(L, 1); + if (buf != NULL) { + bp = BIO_new_mem_buf((void *) buf, -1); + + if (!PEM_read_bio_RSA_PUBKEY(bp, &rsa, NULL, NULL)) { + msg_err("cannot parse pubkey: %s", + ERR_error_string(ERR_get_error(), NULL)); + lua_pushnil(L); + } + else { + prsa = lua_newuserdata(L, sizeof(RSA *)); + rspamd_lua_setclass(L, "rspamd{rsa_pubkey}", -1); + *prsa = rsa; + } + BIO_free(bp); + } + else { + lua_pushnil(L); + } + return 1; +} + +static gint +lua_rsa_pubkey_gc(lua_State *L) +{ + RSA *rsa = lua_check_rsa_pubkey(L, 1); + + if (rsa != NULL) { + RSA_free(rsa); + } + + return 0; +} + +static gint +lua_rsa_pubkey_tostring(lua_State *L) +{ + RSA *rsa = lua_check_rsa_pubkey(L, 1); + + if (rsa != NULL) { + BIO *pubout = BIO_new(BIO_s_mem()); + const gchar *pubdata; + gsize publen; + int rc = i2d_RSA_PUBKEY_bio(pubout, rsa); + + if (rc != 1) { + BIO_free(pubout); + + return luaL_error(L, "i2d_RSA_PUBKEY_bio failed"); + } + + publen = BIO_get_mem_data(pubout, &pubdata); + lua_pushlstring(L, pubdata, publen); + BIO_free(pubout); + } + else { + return luaL_error(L, "invalid arguments"); + } + + return 1; +} + +static gint +lua_rsa_privkey_load_file(lua_State *L) +{ + RSA *rsa = NULL, **prsa; + const gchar *filename; + FILE *f; + + filename = luaL_checkstring(L, 1); + if (filename != NULL) { + f = fopen(filename, "r"); + if (f == NULL) { + msg_err("cannot open private key from file: %s, %s", + filename, + strerror(errno)); + lua_pushnil(L); + } + else { + if (!PEM_read_RSAPrivateKey(f, &rsa, NULL, NULL)) { + msg_err("cannot open private key from file: %s, %s", filename, + ERR_error_string(ERR_get_error(), NULL)); + lua_pushnil(L); + } + else { + prsa = lua_newuserdata(L, sizeof(RSA *)); + rspamd_lua_setclass(L, "rspamd{rsa_privkey}", -1); + *prsa = rsa; + } + fclose(f); + } + } + else { + lua_pushnil(L); + } + return 1; +} + +static gint +lua_rsa_privkey_load_pem(lua_State *L) +{ + RSA *rsa = NULL, **prsa; + BIO *b; + struct rspamd_lua_text *t; + const gchar *data; + gsize len; + + if (lua_isuserdata(L, 1)) { + t = lua_check_text(L, 1); + + if (!t) { + return luaL_error(L, "invalid arguments"); + } + + data = t->start; + len = t->len; + } + else { + data = luaL_checklstring(L, 1, &len); + } + + if (data != NULL) { + b = BIO_new_mem_buf(data, len); + + if (!PEM_read_bio_RSAPrivateKey(b, &rsa, NULL, NULL)) { + msg_err("cannot open private key from data, %s", + ERR_error_string(ERR_get_error(), NULL)); + lua_pushnil(L); + } + else { + prsa = lua_newuserdata(L, sizeof(RSA *)); + rspamd_lua_setclass(L, "rspamd{rsa_privkey}", -1); + *prsa = rsa; + } + + BIO_free(b); + } + else { + return luaL_error(L, "invalid arguments"); + } + + return 1; +} + +static gint +lua_rsa_privkey_load_raw(lua_State *L) +{ + RSA *rsa = NULL, **prsa; + BIO *b; + struct rspamd_lua_text *t; + const gchar *data; + gsize len; + + if (lua_isuserdata(L, 1)) { + t = lua_check_text(L, 1); + + if (!t) { + return luaL_error(L, "invalid arguments"); + } + + data = t->start; + len = t->len; + } + else { + data = luaL_checklstring(L, 1, &len); + } + + if (data != NULL) { + b = BIO_new_mem_buf(data, len); + rsa = d2i_RSAPrivateKey_bio(b, NULL); + + if (rsa == NULL) { + msg_err("cannot open private key from data, %s", + ERR_error_string(ERR_get_error(), NULL)); + lua_pushnil(L); + } + else { + prsa = lua_newuserdata(L, sizeof(RSA *)); + rspamd_lua_setclass(L, "rspamd{rsa_privkey}", -1); + *prsa = rsa; + } + + BIO_free(b); + } + else { + return luaL_error(L, "invalid arguments"); + } + + return 1; +} + +static gint +lua_rsa_privkey_load_base64(lua_State *L) +{ + RSA *rsa = NULL, **prsa; + BIO *b; + EVP_PKEY *evp = NULL; + struct rspamd_lua_text *t; + const gchar *data; + guchar *decoded; + gsize len, dec_len; + + if (lua_isuserdata(L, 1)) { + t = lua_check_text(L, 1); + + if (!t) { + return luaL_error(L, "invalid arguments"); + } + + data = t->start; + len = t->len; + } + else { + data = luaL_checklstring(L, 1, &len); + } + + if (data != NULL) { + decoded = g_malloc(len); + + if (!rspamd_cryptobox_base64_decode(data, len, decoded, &dec_len)) { + g_free(decoded); + + return luaL_error(L, "invalid base64 encoding"); + } + + b = BIO_new_mem_buf(decoded, dec_len); + + if (d2i_PrivateKey_bio(b, &evp) != NULL) { + rsa = EVP_PKEY_get1_RSA(evp); + + if (rsa == NULL) { + msg_err("cannot open RSA private key from data, %s", + ERR_error_string(ERR_get_error(), NULL)); + lua_pushnil(L); + } + else { + prsa = lua_newuserdata(L, sizeof(RSA *)); + rspamd_lua_setclass(L, "rspamd{rsa_privkey}", -1); + *prsa = rsa; + } + + EVP_PKEY_free(evp); + } + else { + msg_err("cannot open EVP private key from data, %s", + ERR_error_string(ERR_get_error(), NULL)); + lua_pushnil(L); + } + + BIO_free(b); + g_free(decoded); + } + else { + return luaL_error(L, "invalid arguments"); + } + + return 1; +} + +static gint +lua_rsa_privkey_create(lua_State *L) +{ + RSA *rsa = NULL, **prsa; + const gchar *buf; + BIO *bp; + + buf = luaL_checkstring(L, 1); + if (buf != NULL) { + bp = BIO_new_mem_buf((void *) buf, -1); + + if (!PEM_read_bio_RSAPrivateKey(bp, &rsa, NULL, NULL)) { + msg_err("cannot parse private key: %s", + ERR_error_string(ERR_get_error(), NULL)); + lua_pushnil(L); + } + else { + prsa = lua_newuserdata(L, sizeof(RSA *)); + rspamd_lua_setclass(L, "rspamd{rsa_privkey}", -1); + *prsa = rsa; + } + BIO_free(bp); + } + else { + lua_pushnil(L); + } + return 1; +} + +static gint +lua_rsa_privkey_gc(lua_State *L) +{ + RSA *rsa = lua_check_rsa_privkey(L, 1); + + if (rsa != NULL) { + RSA_free(rsa); + } + + return 0; +} + +static gint +lua_rsa_signature_load(lua_State *L) +{ + rspamd_fstring_t *sig, **psig; + const gchar *filename; + gpointer data; + int fd; + struct stat st; + + filename = luaL_checkstring(L, 1); + if (filename != NULL) { + fd = open(filename, O_RDONLY); + if (fd == -1) { + msg_err("cannot open signature file: %s, %s", filename, + strerror(errno)); + lua_pushnil(L); + } + else { + if (fstat(fd, &st) == -1 || + (data = + mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) { + msg_err("cannot mmap file %s: %s", filename, strerror(errno)); + lua_pushnil(L); + } + else { + sig = rspamd_fstring_new_init(data, st.st_size); + psig = lua_newuserdata(L, sizeof(rspamd_fstring_t *)); + rspamd_lua_setclass(L, "rspamd{rsa_signature}", -1); + *psig = sig; + munmap(data, st.st_size); + } + close(fd); + } + } + else { + lua_pushnil(L); + } + return 1; +} + +static gint +lua_rsa_signature_save(lua_State *L) +{ + rspamd_fstring_t *sig; + gint fd, flags; + const gchar *filename; + gboolean forced = FALSE, res = TRUE; + + sig = lua_check_rsa_sign(L, 1); + filename = luaL_checkstring(L, 2); + if (lua_gettop(L) > 2) { + forced = lua_toboolean(L, 3); + } + + if (sig != NULL && filename != NULL) { + flags = O_WRONLY | O_CREAT; + if (forced) { + flags |= O_TRUNC; + } + else { + flags |= O_EXCL; + } + fd = open(filename, flags, 00644); + if (fd == -1) { + msg_err("cannot create a signature file: %s, %s", + filename, + strerror(errno)); + lua_pushboolean(L, FALSE); + } + else { + while (write(fd, sig->str, sig->len) == -1) { + if (errno == EINTR) { + continue; + } + msg_err("cannot write to a signature file: %s, %s", + filename, + strerror(errno)); + res = FALSE; + break; + } + lua_pushboolean(L, res); + close(fd); + } + } + else { + lua_pushboolean(L, FALSE); + } + + return 1; +} + +static gint +lua_rsa_signature_create(lua_State *L) +{ + rspamd_fstring_t *sig, **psig; + const gchar *data; + gsize dlen; + + data = luaL_checklstring(L, 1, &dlen); + if (data != NULL) { + sig = rspamd_fstring_new_init(data, dlen); + psig = lua_newuserdata(L, sizeof(rspamd_fstring_t *)); + rspamd_lua_setclass(L, "rspamd{rsa_signature}", -1); + *psig = sig; + } + + return 1; +} + +static gint +lua_rsa_signature_gc(lua_State *L) +{ + rspamd_fstring_t *sig = lua_check_rsa_sign(L, 1); + + rspamd_fstring_free(sig); + + return 0; +} + +static gint +lua_rsa_signature_base64(lua_State *L) +{ + rspamd_fstring_t *sig = lua_check_rsa_sign(L, 1); + guint boundary = 0; + gchar *b64; + gsize outlen; + enum rspamd_newlines_type how = RSPAMD_TASK_NEWLINES_CRLF; + + if (lua_isnumber(L, 2)) { + boundary = lua_tonumber(L, 2); + } + + if (lua_isstring(L, 3)) { + const gchar *how_str = lua_tostring(L, 3); + + if (strcmp(how_str, "cr") == 0) { + how = RSPAMD_TASK_NEWLINES_CR; + } + else if (strcmp(how_str, "lf") == 0) { + how = RSPAMD_TASK_NEWLINES_LF; + } + else { + how = RSPAMD_TASK_NEWLINES_CRLF; + } + } + + b64 = rspamd_encode_base64_fold(sig->str, sig->len, boundary, &outlen, how); + + if (b64) { + lua_pushlstring(L, b64, outlen); + g_free(b64); + } + else { + lua_pushnil(L); + } + + return 1; +} + +/** + * Check memory using specified rsa key and signature + * + * arguments: + * (rsa_pubkey, rsa_signature, string) + * + * returns: + * true - if string match rsa signature + * false - otherwise + */ +static gint +lua_rsa_verify_memory(lua_State *L) +{ + RSA *rsa; + rspamd_fstring_t *signature; + const gchar *data; + gsize sz; + gint ret; + + rsa = lua_check_rsa_pubkey(L, 1); + signature = lua_check_rsa_sign(L, 2); + data = luaL_checklstring(L, 3, &sz); + + if (rsa != NULL && signature != NULL && data != NULL) { + ret = RSA_verify(NID_sha256, data, sz, + signature->str, signature->len, rsa); + + if (ret == 0) { + lua_pushboolean(L, FALSE); + lua_pushstring(L, ERR_error_string(ERR_get_error(), NULL)); + + return 2; + } + else { + lua_pushboolean(L, TRUE); + } + } + else { + lua_pushnil(L); + } + + return 1; +} + +/** + * Sign memory using specified rsa key and signature + * + * arguments: + * (rsa_privkey, string) + * + * returns: + * rspamd_signature object + * nil - otherwise + */ +static gint +lua_rsa_sign_memory(lua_State *L) +{ + RSA *rsa; + rspamd_fstring_t *signature, **psig; + const gchar *data; + gsize sz; + gint ret; + + rsa = lua_check_rsa_privkey(L, 1); + data = luaL_checklstring(L, 2, &sz); + + if (rsa != NULL && data != NULL) { + signature = rspamd_fstring_sized_new(RSA_size(rsa)); + + guint siglen = signature->len; + ret = RSA_sign(NID_sha256, data, sz, + signature->str, &siglen, rsa); + + if (ret != 1) { + rspamd_fstring_free(signature); + + return luaL_error(L, "cannot sign: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + else { + signature->len = siglen; + psig = lua_newuserdata(L, sizeof(rspamd_fstring_t *)); + rspamd_lua_setclass(L, "rspamd{rsa_signature}", -1); + *psig = signature; + } + } + else { + return luaL_error(L, "invalid arguments"); + } + + return 1; +} + +static gint +lua_rsa_keypair(lua_State *L) +{ + BIGNUM *e; + RSA *rsa, *pub_rsa, *priv_rsa, **prsa; + gint bits = lua_gettop(L) > 0 ? lua_tointeger(L, 1) : 1024; + + if (bits > 4096 || bits < 512) { + return luaL_error(L, "invalid bits count"); + } + + e = BN_new(); + rsa = RSA_new(); + g_assert(BN_set_word(e, RSA_F4) == 1); + g_assert(RSA_generate_key_ex(rsa, bits, e, NULL) == 1); + + priv_rsa = RSAPrivateKey_dup(rsa); + prsa = lua_newuserdata(L, sizeof(RSA *)); + rspamd_lua_setclass(L, "rspamd{rsa_privkey}", -1); + *prsa = priv_rsa; + + pub_rsa = RSAPublicKey_dup(rsa); + prsa = lua_newuserdata(L, sizeof(RSA *)); + rspamd_lua_setclass(L, "rspamd{rsa_pubkey}", -1); + *prsa = pub_rsa; + + RSA_free(rsa); + BN_free(e); + + return 2; +} + +static gint +lua_load_pubkey(lua_State *L) +{ + lua_newtable(L); + luaL_register(L, NULL, rsapubkeylib_f); + + return 1; +} + +static gint +lua_load_privkey(lua_State *L) +{ + lua_newtable(L); + luaL_register(L, NULL, rsaprivkeylib_f); + + return 1; +} + +static gint +lua_load_signature(lua_State *L) +{ + lua_newtable(L); + luaL_register(L, NULL, rsasignlib_f); + + return 1; +} + +static gint +lua_load_rsa(lua_State *L) +{ + lua_newtable(L); + luaL_register(L, NULL, rsalib_f); + + return 1; +} + +void luaopen_rsa(lua_State *L) +{ + rspamd_lua_new_class(L, "rspamd{rsa_pubkey}", rsapubkeylib_m); + lua_pop(L, 1); + rspamd_lua_add_preload(L, "rspamd_rsa_pubkey", lua_load_pubkey); + + rspamd_lua_new_class(L, "rspamd{rsa_privkey}", rsaprivkeylib_m); + lua_pop(L, 1); + rspamd_lua_add_preload(L, "rspamd_rsa_privkey", lua_load_privkey); + + rspamd_lua_new_class(L, "rspamd{rsa_signature}", rsasignlib_m); + lua_pop(L, 1); + rspamd_lua_add_preload(L, "rspamd_rsa_signature", lua_load_signature); + + rspamd_lua_add_preload(L, "rspamd_rsa", lua_load_rsa); + + lua_settop(L, 0); +} |