main.lua

Thu, 07 Apr 2022 18:11:33 +0100

author
Matthew Wild <mwild1@gmail.com>
date
Thu, 07 Apr 2022 18:11:33 +0100
changeset 0
89e39cd5a7cd
permissions
-rwxr-xr-x

Initial commit

#!/usr/bin/env lua5.3

local ciphers = require "openssl.cipher";
local kdf = require "openssl.kdf";
local zlib = require "zlib";
local sqlite = require "lsqlite3";

local dbuffer = require "util.dbuffer";

local file_password = assert(arg[2], "no password specified");

local input_filename = assert(arg[1], "no ceb file specified");
local db_filename = input_filename:gsub("%.ceb$", "")..".sqlite3";

local function read_header(f)
	local function read_int()
		return (">i4"):unpack(f:read(4));
	end
	local function read_short()
		return (">i2"):unpack(f:read(2));
	end
	local function read_long()
		return (">i8"):unpack(f:read(8));
	end
	local function read_string()
		local n = read_short();
		return f:read(n);
	end

	return {
		version = read_int();
		app_id = read_string();
		jid = read_string();
		timestamp = math.floor(read_long()/1000);
		iv = f:read(12);
		salt = f:read(16);
	};
end

local f = io.open(input_filename);

local header = read_header(f);

print("version", header.version);
print("app", header.app_id);
print("jid", header.jid);
print("timestamp", os.date("%c", header.timestamp));


local function generate_key(password, salt)
	return kdf.derive({
		type = "PBKDF2";
		md = "sha1";
		pass = password;
		salt = salt;
		iter = 1024;
		outlen = 128/8;
	});
end

print("k", #(generate_key(file_password, header.salt)));

local decryption_key = generate_key(file_password, header.salt);

local cipher = ciphers.new("AES-128-GCM"):decrypt(decryption_key, header.iv);

local decompress = zlib.inflate();

local db = sqlite.open(db_filename);
do
	local db_tables = {
		[[create table accounts (uuid text primary key, username text, server text, password text, display_name text, status number, status_message text, rosterversion text, options number, avatar text, keys text, hostname text, port number, resource text)]];
		[[create table conversations (uuid text, accountUuid text, name text, contactUuid text, contactJid text, created number, status number, mode number, attributes text)]];
		[[create table messages (uuid text, conversationUuid text, timeSent number, counterpart text, trueCounterpart text, body text, encryption number, status number, type number, relativeFilePath text, serverMsgId text, axolotl_fingerprint text, carbon number, edited number, read number, oob number, errorMsg text, readByMarkers text, markable number, remoteMsgId text, deleted number, bodyLanguage text)]];
		[[create table prekeys (account text, id text, key text)]];
		[[create table signed_prekeys (account text, id text, key text)]];
		[[create table sessions (account text, name text, device_id text, key text)]];
		[[create table identities (account text, name text, ownkey text, fingerprint text, certificate text, trust number, active number, last_activation number, key text)]];
	};

	for _, query in ipairs(db_tables) do
		db:exec(query);
	end
end

local buffer = dbuffer.new(1024*1024, 128);

repeat
	local enc_data = f:read(4096);
	if not enc_data then break; end

	local gz_data = assert(cipher:update(enc_data));

	local status, data, eof = pcall(decompress, gz_data);
	if not status then
		print("EE: Failed to decompress: "..tostring(data));
		return 1;
	end

	buffer:write(data);

	local line = buffer:read_until("\n");
	local query_buffer;
	while line do
		local balanced_quotes = select(2, line:gsub("'", "%0")) % 2 == 0;

		if query_buffer then
			table.insert(query_buffer, line);
			if not balanced_quotes then
				db:exec(table.concat(query_buffer, "\n"));
				query_buffer = nil;
			end
		else
			if balanced_quotes then
				db:exec(line);
			else
				query_buffer = { line };
			end
		end

		line = buffer:read_until("\n");
	end
until eof

print("Done");

mercurial