mirror of
https://git.jami.net/savoirfairelinux/jami-daemon.git
synced 2025-08-12 22:09:25 +08:00
archiver: improve import from archive
If the user decides to transmit its account archive via a webservice in some case the webserver can re-compress the file resulting to a gunzip file into a gunzip file. We can check the header of the file to be able to detect this case. Then, replace inflateInit by inflateInit2 with better header detection to avoid header errors. Cf http://www.zlib.net/manual.html#inflateInit2 Change-Id: I1421affa8815ab347b439ef2e505da10275c80ff GitLab: #575
This commit is contained in:
@ -144,6 +144,14 @@ compressGzip(const std::string& str, const std::string& path)
|
||||
gzclose(fi);
|
||||
}
|
||||
|
||||
void
|
||||
compressGzip(const std::vector<uint8_t>& dat, const std::string& path)
|
||||
{
|
||||
auto fi = openGzip(path, "wb");
|
||||
gzwrite(fi, dat.data(), dat.size());
|
||||
gzclose(fi);
|
||||
}
|
||||
|
||||
std::vector<uint8_t>
|
||||
decompressGzip(const std::string& path)
|
||||
{
|
||||
@ -169,7 +177,7 @@ decompress(const std::vector<uint8_t>& str)
|
||||
z_stream zs; // z_stream is zlib's control structure
|
||||
memset(&zs, 0, sizeof(zs));
|
||||
|
||||
if (inflateInit(&zs) != Z_OK)
|
||||
if (inflateInit2(&zs, 32+MAX_WBITS) != Z_OK)
|
||||
throw std::runtime_error("inflateInit failed while decompressing.");
|
||||
|
||||
zs.next_in = (Bytef*) str.data();
|
||||
|
@ -57,6 +57,7 @@ std::vector<uint8_t> decompress(const std::vector<uint8_t>& dat);
|
||||
* Compress string to a Gzip file
|
||||
*/
|
||||
void compressGzip(const std::string& str, const std::string& path);
|
||||
void compressGzip(const std::vector<uint8_t>& dat, const std::string& path);
|
||||
|
||||
/**
|
||||
* Decompress Gzip file to bytes
|
||||
|
@ -535,24 +535,53 @@ readArchive(const std::string& path, const std::string& pwd)
|
||||
{
|
||||
JAMI_DBG("Reading archive from %s", path.c_str());
|
||||
|
||||
std::vector<uint8_t> data;
|
||||
if (pwd.empty()) {
|
||||
data = archiver::decompressGzip(path);
|
||||
} else {
|
||||
// Read file
|
||||
auto isUnencryptedGzip = [](const std::vector<uint8_t>& data) {
|
||||
// NOTE: some webserver modify gzip files and this can end with a gunzip in a gunzip
|
||||
// file. So, to make the readArchive more robust, we can support this case by detecting
|
||||
// gzip header via 1f8b 08
|
||||
// We don't need to support more than 2 level, else somebody may be able to send
|
||||
// gunzip in loops and abuse.
|
||||
return data.size() > 3 && data[0] == 0x1f && data[1] == 0x8b && data[2] == 0x08;
|
||||
};
|
||||
|
||||
auto decompress = [](std::vector<uint8_t>& data) {
|
||||
try {
|
||||
data = loadFile(path);
|
||||
} catch (const std::exception& e) {
|
||||
JAMI_ERR("Error loading archive: %s", e.what());
|
||||
throw e;
|
||||
}
|
||||
// Decrypt
|
||||
try {
|
||||
data = archiver::decompress(dht::crypto::aesDecrypt(data, pwd));
|
||||
data = archiver::decompress(data);
|
||||
} catch (const std::exception& e) {
|
||||
JAMI_ERR("Error decrypting archive: %s", e.what());
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<uint8_t> data;
|
||||
// Read file
|
||||
try {
|
||||
data = loadFile(path);
|
||||
} catch (const std::exception& e) {
|
||||
JAMI_ERR("Error loading archive: %s", e.what());
|
||||
throw e;
|
||||
}
|
||||
|
||||
|
||||
if (isUnencryptedGzip(data)) {
|
||||
if (!pwd.empty())
|
||||
JAMI_WARN() << "A gunzip in a gunzip is detected. A webserver may have a bad config";
|
||||
|
||||
decompress(data);
|
||||
}
|
||||
|
||||
if (!pwd.empty()) {
|
||||
// Decrypt
|
||||
try {
|
||||
data = dht::crypto::aesDecrypt(data, pwd);
|
||||
} catch (const std::exception& e) {
|
||||
JAMI_ERR("Error decrypting archive: %s", e.what());
|
||||
throw e;
|
||||
}
|
||||
decompress(data);
|
||||
} else if (isUnencryptedGzip(data)) {
|
||||
JAMI_WARN() << "A gunzip in a gunzip is detected. A webserver may have a bad config";
|
||||
decompress(data);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
@ -14,6 +14,12 @@ check_PROGRAMS =
|
||||
check_PROGRAMS += ut_account_factory
|
||||
ut_account_factory_SOURCES = account_factory/testAccount_factory.cpp common.cpp
|
||||
|
||||
#
|
||||
# account_archive
|
||||
#
|
||||
check_PROGRAMS += ut_account_archive
|
||||
ut_account_archive_SOURCES = account_archive/account_archive.cpp common.cpp
|
||||
|
||||
#
|
||||
# certstore
|
||||
#
|
||||
|
180
test/unitTest/account_archive/account_archive.cpp
Normal file
180
test/unitTest/account_archive/account_archive.cpp
Normal file
@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Savoir-faire Linux Inc.
|
||||
* Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <cppunit/TestAssert.h>
|
||||
#include <cppunit/TestFixture.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
|
||||
#include <condition_variable>
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <streambuf>
|
||||
|
||||
#include "manager.h"
|
||||
#include "jamidht/conversationrepository.h"
|
||||
#include "jamidht/connectionmanager.h"
|
||||
#include "jamidht/gitserver.h"
|
||||
#include "jamidht/jamiaccount.h"
|
||||
#include "../../test_runner.h"
|
||||
#include "archiver.h"
|
||||
#include "base64.h"
|
||||
#include "dring.h"
|
||||
#include "fileutils.h"
|
||||
#include "account_const.h"
|
||||
#include "common.h"
|
||||
|
||||
#include <git2.h>
|
||||
#include <filesystem>
|
||||
|
||||
using namespace std::string_literals;
|
||||
using namespace DRing::Account;
|
||||
|
||||
namespace jami {
|
||||
namespace test {
|
||||
|
||||
class AccountArchiveTest : public CppUnit::TestFixture
|
||||
{
|
||||
public:
|
||||
AccountArchiveTest()
|
||||
{
|
||||
// Init daemon
|
||||
DRing::init(DRing::InitFlag(DRing::DRING_FLAG_DEBUG | DRing::DRING_FLAG_CONSOLE_LOG));
|
||||
if (not Manager::instance().initialized)
|
||||
CPPUNIT_ASSERT(DRing::start("dring-sample.yml"));
|
||||
}
|
||||
~AccountArchiveTest() { DRing::fini(); }
|
||||
static std::string name() { return "AccountArchive"; }
|
||||
void setUp();
|
||||
void tearDown();
|
||||
|
||||
std::string aliceId;
|
||||
std::string bobId;
|
||||
|
||||
private:
|
||||
void testExportImportNoPassword();
|
||||
void testExportImportNoPasswordDoubleGunzip();
|
||||
void testExportImportPassword();
|
||||
void testExportImportPasswordDoubleGunzip();
|
||||
|
||||
CPPUNIT_TEST_SUITE(AccountArchiveTest);
|
||||
CPPUNIT_TEST(testExportImportNoPassword);
|
||||
CPPUNIT_TEST(testExportImportNoPasswordDoubleGunzip);
|
||||
CPPUNIT_TEST(testExportImportPassword);
|
||||
CPPUNIT_TEST(testExportImportPasswordDoubleGunzip);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
};
|
||||
|
||||
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(AccountArchiveTest,
|
||||
AccountArchiveTest::name());
|
||||
|
||||
void
|
||||
AccountArchiveTest::setUp()
|
||||
{
|
||||
auto actors = load_actors_and_wait_for_announcement("actors/alice-bob-password.yml");
|
||||
aliceId = actors["alice"];
|
||||
bobId = actors["bob"];
|
||||
}
|
||||
|
||||
void
|
||||
AccountArchiveTest::tearDown()
|
||||
{
|
||||
wait_for_removal_of({aliceId, bobId});
|
||||
}
|
||||
|
||||
void
|
||||
AccountArchiveTest::testExportImportNoPassword()
|
||||
{
|
||||
auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
|
||||
|
||||
CPPUNIT_ASSERT(aliceAccount->exportArchive("test.gz"));
|
||||
|
||||
std::map<std::string, std::string> details = DRing::getAccountTemplate("RING");
|
||||
details[ConfProperties::ARCHIVE_PATH] = "test.gz";
|
||||
|
||||
auto accountId = jami::Manager::instance().addAccount(details);
|
||||
wait_for_announcement_of(accountId);
|
||||
auto alice2Account = Manager::instance().getAccount<JamiAccount>(accountId);
|
||||
CPPUNIT_ASSERT(alice2Account->getUsername() == aliceAccount->getUsername());
|
||||
std::remove("test.gz");
|
||||
wait_for_removal_of(accountId);
|
||||
}
|
||||
|
||||
void
|
||||
AccountArchiveTest::testExportImportNoPasswordDoubleGunzip()
|
||||
{
|
||||
auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
|
||||
|
||||
CPPUNIT_ASSERT(aliceAccount->exportArchive("test.gz"));
|
||||
auto dat = fileutils::loadFile("test.gz");
|
||||
archiver::compressGzip(dat, "test.gz");
|
||||
|
||||
std::map<std::string, std::string> details = DRing::getAccountTemplate("RING");
|
||||
details[ConfProperties::ARCHIVE_PATH] = "test.gz";
|
||||
|
||||
auto accountId = jami::Manager::instance().addAccount(details);
|
||||
wait_for_announcement_of(accountId);
|
||||
auto alice2Account = Manager::instance().getAccount<JamiAccount>(accountId);
|
||||
CPPUNIT_ASSERT(alice2Account->getUsername() == aliceAccount->getUsername());
|
||||
std::remove("test.gz");
|
||||
wait_for_removal_of(accountId);
|
||||
}
|
||||
|
||||
void
|
||||
AccountArchiveTest::testExportImportPassword()
|
||||
{
|
||||
auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
|
||||
|
||||
CPPUNIT_ASSERT(bobAccount->exportArchive("test.gz", "test"));
|
||||
|
||||
std::map<std::string, std::string> details = DRing::getAccountTemplate("RING");
|
||||
details[ConfProperties::ARCHIVE_PATH] = "test.gz";
|
||||
details[ConfProperties::ARCHIVE_PASSWORD] = "test";
|
||||
|
||||
auto accountId = jami::Manager::instance().addAccount(details);
|
||||
wait_for_announcement_of(accountId);
|
||||
auto bob2Account = Manager::instance().getAccount<JamiAccount>(accountId);
|
||||
CPPUNIT_ASSERT(bob2Account->getUsername() == bobAccount->getUsername());
|
||||
std::remove("test.gz");
|
||||
wait_for_removal_of(accountId);
|
||||
}
|
||||
|
||||
void
|
||||
AccountArchiveTest::testExportImportPasswordDoubleGunzip()
|
||||
{
|
||||
auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
|
||||
|
||||
CPPUNIT_ASSERT(bobAccount->exportArchive("test.gz", "test"));
|
||||
auto dat = fileutils::loadFile("test.gz");
|
||||
archiver::compressGzip(dat, "test.gz");
|
||||
|
||||
std::map<std::string, std::string> details = DRing::getAccountTemplate("RING");
|
||||
details[ConfProperties::ARCHIVE_PATH] = "test.gz";
|
||||
details[ConfProperties::ARCHIVE_PASSWORD] = "test";
|
||||
|
||||
auto accountId = jami::Manager::instance().addAccount(details);
|
||||
wait_for_announcement_of(accountId);
|
||||
auto bob2Account = Manager::instance().getAccount<JamiAccount>(accountId);
|
||||
CPPUNIT_ASSERT(bob2Account->getUsername() == bobAccount->getUsername());
|
||||
std::remove("test.gz");
|
||||
wait_for_removal_of(accountId);
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace jami
|
||||
|
||||
RING_TEST_RUNNER(jami::test::AccountArchiveTest::name())
|
Reference in New Issue
Block a user