guint16 *p16 = (guint16 *) (zip->data + lfh_off + 30 + (guint32) strlen(f->name) + 9);
*p16 = GUINT16_TO_LE(actual_method);
#else
- g_byte_array_free(cdata, TRUE);
g_byte_array_free(cd, TRUE);
g_byte_array_free(zip, TRUE);
g_set_error(err, q, ENOTSUP, "AES-CTR encryption requires OpenSSL");
return zip;
}
-GByteArray *
-rspamd_archives_encrypt_aes256_cbc(const unsigned char *in,
- gsize inlen,
- const char *password,
- GError **err)
-{
-#ifndef HAVE_OPENSSL
- (void) in;
- (void) inlen;
- (void) password;
- GQuark q = rspamd_archives_err_quark();
- g_set_error(err, q, ENOTSUP, "OpenSSL is not available");
- return NULL;
-#else
- GQuark q = rspamd_archives_err_quark();
- unsigned char salt[16];
- unsigned char iv[16];
- unsigned char key[32];
- const int kdf_iters = 100000;
- GByteArray *out = NULL;
- EVP_CIPHER_CTX *ctx = NULL;
-
- if (password == NULL || *password == '\0') {
- g_set_error(err, q, EINVAL, "empty password");
- return NULL;
- }
-
- if (RAND_bytes(salt, sizeof(salt)) != 1 || RAND_bytes(iv, sizeof(iv)) != 1) {
- g_set_error(err, q, EIO, "cannot generate random salt/iv: %s", ERR_error_string(ERR_get_error(), NULL));
- return NULL;
- }
-
- if (PKCS5_PBKDF2_HMAC(password, (int) strlen(password), salt, (int) sizeof(salt),
- kdf_iters, EVP_sha256(), (int) sizeof(key), key) != 1) {
- g_set_error(err, q, EIO, "PBKDF2 failed: %s", ERR_error_string(ERR_get_error(), NULL));
- return NULL;
- }
-
- ctx = EVP_CIPHER_CTX_new();
- if (ctx == NULL) {
- g_set_error(err, q, ENOMEM, "cannot alloc cipher ctx");
- rspamd_explicit_memzero(key, sizeof(key));
- return NULL;
- }
-
- if (EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv) != 1) {
- g_set_error(err, q, EIO, "cipher init failed: %s", ERR_error_string(ERR_get_error(), NULL));
- EVP_CIPHER_CTX_free(ctx);
- rspamd_explicit_memzero(key, sizeof(key));
- return NULL;
- }
-
- /* Prepare output: magic + salt + iv + ciphertext; write directly into GByteArray */
- const char magic[8] = {'R', 'Z', 'A', 'E', '0', '0', '0', '1'};
- out = g_byte_array_sized_new(8 + sizeof(salt) + sizeof(iv) + inlen + 32);
- g_byte_array_append(out, (const guint8 *) magic, sizeof(magic));
- g_byte_array_append(out, salt, sizeof(salt));
- g_byte_array_append(out, iv, sizeof(iv));
-
- gsize before = out->len;
- g_byte_array_set_size(out, out->len + inlen + EVP_CIPHER_block_size(EVP_aes_256_cbc()));
- unsigned char *cptr = out->data + before;
- int outlen = 0;
-
- if (EVP_EncryptUpdate(ctx, cptr, &outlen, in, (int) inlen) != 1) {
- g_set_error(err, q, EIO, "encrypt update failed: %s", ERR_error_string(ERR_get_error(), NULL));
- EVP_CIPHER_CTX_free(ctx);
- rspamd_explicit_memzero(key, sizeof(key));
- g_byte_array_set_size(out, before);
- g_byte_array_free(out, TRUE);
- return NULL;
- }
-
- int fin = 0;
- if (EVP_EncryptFinal_ex(ctx, cptr + outlen, &fin) != 1) {
- g_set_error(err, q, EIO, "encrypt final failed: %s", ERR_error_string(ERR_get_error(), NULL));
- EVP_CIPHER_CTX_free(ctx);
- rspamd_explicit_memzero(key, sizeof(key));
- g_byte_array_set_size(out, before);
- g_byte_array_free(out, TRUE);
- return NULL;
- }
-
- g_byte_array_set_size(out, before + outlen + fin);
- EVP_CIPHER_CTX_free(ctx);
- rspamd_explicit_memzero(key, sizeof(key));
-
- msg_info("zip: AES-256-CBC envelope created (PBKDF2-SHA256 iters=%d)", kdf_iters);
- return out;
-#endif
-}
+/* removed obsolete whole-archive AES-256-CBC function */
static bool
rspamd_archive_file_try_utf(struct rspamd_task *task,
};
/**
- * Create a ZIP archive in-memory from provided files (DEFLATE compression)
- * If password is non-NULL, the ZIP is created normally and then encrypted as a whole
- * using AES-256-CBC with PBKDF2-HMAC-SHA256 and a random salt/IV. The result format is:
- * [ 'RZAE0001' (8 bytes) | salt (16 bytes) | iv (16 bytes) | ciphertext ]
- * Returns newly allocated GByteArray on success, NULL on error and sets err
+ * Create an in-memory ZIP archive from provided files.
+ * - Uses DEFLATE (method 8) or STORE (method 0) per entry, depending on gain.
+ * - If 'password' is non-NULL, each entry is encrypted using WinZip AES (AE-2):
+ * method 99 + 0x9901 extra, PBKDF2-HMAC-SHA1(1000), AES-CTR, 10-byte HMAC-SHA1 tag.
+ * Interoperable with 7-Zip/WinZip/libarchive.
+ * - UTF-8 filenames (GPBF bit 11) are used.
+ * Returns newly allocated GByteArray on success, NULL on error and sets 'err'.
*/
GByteArray *rspamd_archives_zip_write(const struct rspamd_zip_file_spec *files,
gsize nfiles,
const char *password,
GError **err);
-/**
- * AES-256-CBC encrypts arbitrary data buffer using PBKDF2-HMAC-SHA256 derived key.
- * Output format: [ 'RZAE0001' | salt(16) | iv(16) | ciphertext ]
- */
-GByteArray *rspamd_archives_encrypt_aes256_cbc(const unsigned char *in,
- gsize inlen,
- const char *password,
- GError **err);
-
/**
* Process archives from a worker task
*/
#include "lua_common.h"
#include "unix-std.h"
+#include "libmime/archives.h"
#include <archive.h>
#include <archive_entry.h>
* @return {text} archive bytes
*/
LUA_FUNCTION_DEF(archive, zip);
+LUA_FUNCTION_DEF(archive, zip_encrypt);
/***
* @function archive.unzip(data)
* Extract files from a ZIP archive.
LUA_INTERFACE_DEF(archive, unpack),
LUA_INTERFACE_DEF(archive, supported_formats),
LUA_INTERFACE_DEF(archive, zip),
+ LUA_INTERFACE_DEF(archive, zip_encrypt),
LUA_INTERFACE_DEF(archive, unzip),
LUA_INTERFACE_DEF(archive, tar),
LUA_INTERFACE_DEF(archive, untar),
return lua_archive_pack(L);
}
+/**
+ * zip_encrypt(files, password) -> text
+ */
+static int
+lua_archive_zip_encrypt(lua_State *L)
+{
+ LUA_TRACE_POINT;
+ luaL_checktype(L, 1, LUA_TTABLE);
+ const char *password = luaL_checkstring(L, 2);
+ GArray *specs = g_array_sized_new(FALSE, FALSE, sizeof(struct rspamd_zip_file_spec), 8);
+ GError *err = NULL;
+
+ /* Iterate files array */
+ lua_pushnil(L);
+
+ while (lua_next(L, 1)) {
+ if (!lua_istable(L, -1)) {
+ g_array_free(specs, TRUE);
+ return luaL_error(L, "invalid file entry (expected table)");
+ }
+
+ int item_idx = lua_gettop(L);
+ const char *name = NULL;
+ const char *sdata = NULL;
+ size_t slen = 0;
+ time_t mtime = (time_t) 0;
+ guint32 mode = 0644;
+
+ lua_getfield(L, item_idx, "name");
+ name = lua_tostring(L, -1);
+ if (name == NULL || *name == '\0') {
+ lua_pop(L, 2);
+ g_array_free(specs, TRUE);
+ return luaL_error(L, "invalid file entry (missing name)");
+ }
+ char *dupname = g_strdup(name);
+ lua_pop(L, 1);
+
+ lua_getfield(L, item_idx, "content");
+ struct rspamd_lua_text *t = NULL;
+ if ((t = lua_check_text_or_string(L, -1)) != NULL) {
+ sdata = (const char *) t->start;
+ slen = t->len;
+ }
+ else if (lua_isstring(L, -1)) {
+ sdata = lua_tolstring(L, -1, &slen);
+ }
+ else {
+ lua_pop(L, 2);
+ g_free(dupname);
+ g_array_free(specs, TRUE);
+ return luaL_error(L, "invalid file entry (missing content)");
+ }
+ unsigned char *dupdata = NULL;
+ if (slen > 0) {
+ dupdata = g_malloc(slen);
+ memcpy(dupdata, sdata, slen);
+ }
+ lua_pop(L, 1);
+
+ lua_getfield(L, item_idx, "mode");
+ if (lua_isnumber(L, -1)) {
+ mode = (guint32) lua_tointeger(L, -1);
+ }
+ lua_pop(L, 1);
+ lua_getfield(L, item_idx, "perms");
+ if (lua_isnumber(L, -1)) {
+ mode = (guint32) lua_tointeger(L, -1);
+ }
+ lua_pop(L, 1);
+
+ lua_getfield(L, item_idx, "mtime");
+ if (lua_isnumber(L, -1)) {
+ mtime = (time_t) lua_tointeger(L, -1);
+ }
+ lua_pop(L, 1);
+
+ struct rspamd_zip_file_spec s;
+ s.name = dupname;
+ s.data = dupdata;
+ s.len = (gsize) slen;
+ s.mtime = mtime;
+ s.mode = mode;
+ g_array_append_val(specs, s);
+
+ lua_pop(L, 1);
+ }
+
+ GByteArray *ba = rspamd_archives_zip_write((const struct rspamd_zip_file_spec *) specs->data,
+ specs->len,
+ password,
+ &err);
+
+ for (guint i = 0; i < specs->len; i++) {
+ struct rspamd_zip_file_spec *s = &g_array_index(specs, struct rspamd_zip_file_spec, i);
+ if (s->name) g_free((gpointer) s->name);
+ if (s->data) g_free((gpointer) s->data);
+ }
+ g_array_free(specs, TRUE);
+
+ if (ba == NULL) {
+ const char *emsg = (err && err->message) ? err->message : "zip encryption failed";
+ if (err) g_error_free(err);
+ return luaL_error(L, "%s", emsg);
+ }
+
+ size_t outlen = ba->len;
+ guint8 *outdata = g_byte_array_free(ba, FALSE);
+ struct rspamd_lua_text *txt = lua_new_text(L, (const char *) outdata, outlen, FALSE);
+ txt->flags |= RSPAMD_TEXT_FLAG_OWN;
+ return 1;
+}
+
static int
lua_archive_enable_read_format_by_name(struct archive *a, const char *fmt)
{