diff --git a/src/fileutils.cpp b/src/fileutils.cpp index 0a5564af4768825579eacbb0ede16dbf8e3b840b..0d267a4a32645a4a0c48bd419d2254586979c826 100644 --- a/src/fileutils.cpp +++ b/src/fileutils.cpp @@ -26,7 +26,9 @@ #include "logger.h" #include "fileutils.h" +#include "archiver.h" #include "compiler_intrinsics.h" +#include <opendht/crypto.h> #ifdef RING_UWP #include <io.h> // for access and close @@ -404,6 +406,55 @@ readDirectory(const std::string& dir) return files; } +std::vector<uint8_t> +readArchive(const std::string& path, const std::string& pwd) +{ + RING_DBG("Reading archive from %s", path.c_str()); + + std::vector<uint8_t> data; + if (pwd.empty()) { + data = archiver::decompressGzip(path); + } else { + // Read file + try { + data = loadFile(path); + } catch (const std::exception& e) { + RING_ERR("Error loading archive: %s", e.what()); + throw; + } + // Decrypt + try { + data = archiver::decompress(dht::crypto::aesDecrypt(data, pwd)); + } catch (const std::exception& e) { + RING_ERR("Error decrypting archive: %s", e.what()); + throw; + } + } + return data; +} + +void +writeArchive(const std::string& archive_str, const std::string& path, const std::string& password) +{ + RING_DBG("Writing archive to %s", path.c_str()); + + if (not password.empty()) { + // Encrypt using provided password + std::vector<uint8_t> data = dht::crypto::aesEncrypt(archiver::compress(archive_str), password); + // Write + try { + saveFile(path, data); + } catch (const std::runtime_error& ex) { + RING_ERR("Export failed: %s", ex.what()); + return; + } + } else { + RING_WARN("Unsecured archiving (no password)"); + archiver::compressGzip(archive_str, path); + } +} + + FileHandle::FileHandle(const std::string &n) : fd(-1), name(n) {} diff --git a/src/fileutils.h b/src/fileutils.h index 10e65c28d90a13d9b3d6589b849c0df9d97f6c42..790e46eec39b1741e375dad90988702de69e192a 100644 --- a/src/fileutils.h +++ b/src/fileutils.h @@ -103,6 +103,9 @@ namespace ring { namespace fileutils { std::vector<uint8_t> loadFile(const std::string& path, const std::string& default_dir = {}); void saveFile(const std::string& path, const std::vector<uint8_t>& data, mode_t mode=0644); + std::vector<uint8_t> readArchive(const std::string& path, const std::string& password = {}); + void writeArchive(const std::string& data, const std::string& path, const std::string& password = {}); + struct FileHandle { int fd; const std::string name; diff --git a/src/ringdht/Makefile.am b/src/ringdht/Makefile.am index c5244149ab17f017a32d81b89e9f03a7b57b2393..8727a50d6bd4d306d340a8828f2ea3f8417707d4 100644 --- a/src/ringdht/Makefile.am +++ b/src/ringdht/Makefile.am @@ -19,7 +19,9 @@ libringacc_la_SOURCES = \ ringaccount.cpp \ ringaccount.h \ sips_transport_ice.cpp \ - sips_transport_ice.h + sips_transport_ice.h \ + accountarchive.cpp \ + accountarchive.h if RINGNS libringacc_la_SOURCES += \ diff --git a/src/ringdht/accountarchive.cpp b/src/ringdht/accountarchive.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e6a11cc151b9b6942878707cd757cb2ee607f259 --- /dev/null +++ b/src/ringdht/accountarchive.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2017 Savoir-faire Linux Inc. + * Author : Adrien Béraud <adrien.beraud@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 "accountarchive.h" +#include "account_const.h" +#include "configurationmanager_interface.h" +#include "configkeys.h" +#include "base64.h" +#include "logger.h" + +namespace ring { + +void +AccountArchive::deserialize(const std::vector<uint8_t>& dat) +{ + RING_DBG("Loading account archive (%lu bytes)", dat.size()); + + // Decode string + std::string decoded {dat.begin(), dat.end()}; + Json::Value value; + Json::Reader reader; + if (!reader.parse(decoded.c_str(),value)) { + RING_ERR("Archive JSON parsing error: %s", reader.getFormattedErrorMessages().c_str()); + throw std::runtime_error("failed to parse JSON"); + } + + // Import content + try { + config = DRing::getAccountTemplate(DRing::Account::ProtocolNames::RING); + for (Json::ValueIterator itr = value.begin() ; itr != value.end() ; itr++) { + try { + const auto key = itr.key().asString(); + if (key.empty()) + continue; + if (key.compare(DRing::Account::ConfProperties::TLS::CA_LIST_FILE) == 0) { + } else if (key.compare(DRing::Account::ConfProperties::TLS::PRIVATE_KEY_FILE) == 0) { + } else if (key.compare(DRing::Account::ConfProperties::TLS::CERTIFICATE_FILE) == 0) { + } else if (key.compare(Conf::RING_CA_KEY) == 0) { + ca_key = std::make_shared<dht::crypto::PrivateKey>(base64::decode(itr->asString())); + } else if (key.compare(Conf::RING_ACCOUNT_KEY) == 0) { + id.first = std::make_shared<dht::crypto::PrivateKey>(base64::decode(itr->asString())); + } else if (key.compare(Conf::RING_ACCOUNT_CERT) == 0) { + id.second = std::make_shared<dht::crypto::Certificate>(base64::decode(itr->asString())); + } else if (key.compare(Conf::RING_ACCOUNT_CONTACTS) == 0) { + for (Json::ValueIterator citr = itr->begin() ; citr != itr->end() ; citr++) { + dht::InfoHash h {citr.key().asString()}; + if (h != dht::InfoHash{}) + contacts.emplace(h, Contact{*citr}); + } + } else if (key.compare(Conf::ETH_KEY) == 0) { + eth_key = base64::decode(itr->asString()); + } else if (key.compare(Conf::RING_ACCOUNT_CRL) == 0) { + revoked = std::make_shared<dht::crypto::RevocationList>(base64::decode(itr->asString())); + } else + config[key] = itr->asString(); + } catch (const std::exception& ex) { + RING_ERR("Can't parse JSON entry with value of type %d: %s", (unsigned)itr->type(), ex.what()); + } + } + } catch (const std::exception& ex) { + RING_ERR("Can't parse JSON: %s", ex.what()); + } +} + +std::string +AccountArchive::serialize() const +{ + Json::Value root; + + for (const auto& it : config) + root[it.first] = it.second; + + if (ca_key and *ca_key) + root[Conf::RING_CA_KEY] = base64::encode(ca_key->serialize()); + root[Conf::RING_ACCOUNT_KEY] = base64::encode(id.first->serialize()); + root[Conf::RING_ACCOUNT_CERT] = base64::encode(id.second->getPacked()); + root[Conf::ETH_KEY] = base64::encode(eth_key); + + if (revoked) + root[Conf::RING_ACCOUNT_CRL] = base64::encode(revoked->getPacked()); + + if (not contacts.empty()) { + Json::Value& jsonContacts = root[Conf::RING_ACCOUNT_CONTACTS]; + for (const auto& c : contacts) + jsonContacts[c.first.toString()] = c.second.toJson(); + } + + Json::FastWriter fastWriter; + return fastWriter.write(root); +} + + +} diff --git a/src/ringdht/accountarchive.h b/src/ringdht/accountarchive.h new file mode 100644 index 0000000000000000000000000000000000000000..35c1706174f611358fcaee89a1130705b1007231 --- /dev/null +++ b/src/ringdht/accountarchive.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2017 Savoir-faire Linux Inc. + * Author : Adrien Béraud <adrien.beraud@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/>. + */ +#pragma once + +#include "ringcontact.h" +#include "fileutils.h" + +#include <opendht/crypto.h> +#include <memory> +#include <vector> +#include <map> +#include <string> + +namespace ring { + +/** + * Crypto material contained in the archive, + * not persisted in the account configuration + */ +struct AccountArchive +{ + /** Account main private key and certificate chain */ + dht::crypto::Identity id; + + /** Generated CA key (for self-signed certificates) */ + std::shared_ptr<dht::crypto::PrivateKey> ca_key; + + /** Revoked devices */ + std::shared_ptr<dht::crypto::RevocationList> revoked; + + /** Ethereum private key */ + std::vector<uint8_t> eth_key; + + /** Contacts */ + std::map<dht::InfoHash, Contact> contacts; + + /** Account configuration */ + std::map<std::string, std::string> config; + + AccountArchive() = default; + AccountArchive(const std::vector<uint8_t>& data) { deserialize(data); } + AccountArchive(const std::string& path, const std::string& password) { load(path, password); } + + /** Serialize structured archive data to memory. */ + std::string serialize() const; + + /** Deserialize archive from memory. */ + void deserialize(const std::vector<uint8_t>& data); + + /** Load archive from file, optionally encrypted with provided password. */ + void load(const std::string& path, const std::string& password = {}) { deserialize(fileutils::readArchive(path, password)); } + + /** Save archive to file, optionally encrypted with provided password. */ + void save(const std::string& path, const std::string& password = {}) const { fileutils::writeArchive(serialize(), path, password); } +}; + +} diff --git a/src/ringdht/ringaccount.cpp b/src/ringdht/ringaccount.cpp index 3c146f6d49c761ddbaf13522ad2705e1f77f7607..d59d4930fffe0b01a3d0a7e639103cadaa2e1322 100644 --- a/src/ringdht/ringaccount.cpp +++ b/src/ringdht/ringaccount.cpp @@ -27,6 +27,7 @@ #include "config.h" #endif +#include "accountarchive.h" #include "ringcontact.h" #include "configkeys.h" @@ -174,30 +175,6 @@ struct RingAccount::KnownDevice : certificate(cert), name(n), last_sync(sync) {} }; -/** - * Crypto material contained in the archive, - * not persisted in the account configuration - */ -struct RingAccount::ArchiveContent -{ - /** Account main private key and certificate chain */ - dht::crypto::Identity id; - - /** Generated CA key (for self-signed certificates) */ - std::shared_ptr<dht::crypto::PrivateKey> ca_key; - - /** Revoked devices */ - std::shared_ptr<dht::crypto::RevocationList> revoked; - - /** Ethereum private key */ - std::vector<uint8_t> eth_key; - - /** Contacts */ - std::map<dht::InfoHash, Contact> contacts; - - /** Account configuration */ - std::map<std::string, std::string> config; -}; /** * Device announcement stored on DHT. @@ -753,7 +730,7 @@ RingAccount::createRingDevice(const dht::crypto::Identity& id) } void -RingAccount::initRingDevice(const ArchiveContent& a) +RingAccount::initRingDevice(const AccountArchive& a) { RING_WARN("[Account %s] creating new Ring device from archive", getAccountID().c_str()); SIPAccountBase::setAccountDetails(a.config); @@ -902,99 +879,19 @@ RingAccount::loadIdentity(const std::string& crt_path, const std::string& key_pa return id; } -RingAccount::ArchiveContent +AccountArchive RingAccount::readArchive(const std::string& pwd) const { RING_DBG("[Account %s] reading account archive", getAccountID().c_str()); - - std::vector<uint8_t> data; - - if (pwd.empty()) { - data = archiver::decompressGzip(fileutils::getFullPath(idPath_, archivePath_)); - } else { - // Read file - try { - data = fileutils::loadFile(archivePath_, idPath_); - } catch (const std::exception& e) { - RING_ERR("[Account %s] archive loading error: %s", getAccountID().c_str(), e.what()); - throw; - } - // Decrypt - try { - data = archiver::decompress(dht::crypto::aesDecrypt(data, pwd)); - } catch (const std::exception& e) { - RING_ERR("[Account %s] archive decrypt error: %s", getAccountID().c_str(), e.what()); - throw; - } - } - - // Unserialize data - return loadArchive(data); + return AccountArchive(fileutils::getFullPath(idPath_, archivePath_), pwd); } -RingAccount::ArchiveContent -RingAccount::loadArchive(const std::vector<uint8_t>& dat) -{ - ArchiveContent c; - RING_DBG("Loading account archive (%lu bytes)", dat.size()); - - // Decode string - std::string decoded {dat.begin(), dat.end()}; - Json::Value value; - Json::Reader reader; - if (!reader.parse(decoded.c_str(),value)) { - RING_ERR("Archive JSON parsing error: %s", reader.getFormattedErrorMessages().c_str()); - throw std::runtime_error("failed to parse JSON"); - } - // Import content - try { - c.config = DRing::getAccountTemplate(ACCOUNT_TYPE); - for (Json::ValueIterator itr = value.begin() ; itr != value.end() ; itr++) { - try { - const auto key = itr.key().asString(); - if (key.empty()) - continue; - if (key.compare(DRing::Account::ConfProperties::TLS::CA_LIST_FILE) == 0) { - } else if (key.compare(DRing::Account::ConfProperties::TLS::PRIVATE_KEY_FILE) == 0) { - } else if (key.compare(DRing::Account::ConfProperties::TLS::CERTIFICATE_FILE) == 0) { - } else if (key.compare(Conf::RING_CA_KEY) == 0) { - c.ca_key = std::make_shared<dht::crypto::PrivateKey>(base64::decode(itr->asString())); - } else if (key.compare(Conf::RING_ACCOUNT_KEY) == 0) { - c.id.first = std::make_shared<dht::crypto::PrivateKey>(base64::decode(itr->asString())); - } else if (key.compare(Conf::RING_ACCOUNT_CERT) == 0) { - c.id.second = std::make_shared<dht::crypto::Certificate>(base64::decode(itr->asString())); - } else if (key.compare(Conf::RING_ACCOUNT_CONTACTS) == 0) { - for (Json::ValueIterator citr = itr->begin() ; citr != itr->end() ; citr++) { - dht::InfoHash h {citr.key().asString()}; - if (h != dht::InfoHash{}) - c.contacts.emplace(h, Contact{*citr}); - } - } else if (key.compare(Conf::ETH_KEY) == 0) { - c.eth_key = base64::decode(itr->asString()); - } else if (key.compare(Conf::RING_ACCOUNT_CRL) == 0) { - c.revoked = std::make_shared<dht::crypto::RevocationList>(base64::decode(itr->asString())); - } else - c.config[key] = itr->asString(); - } catch (const std::exception& ex) { - RING_ERR("Can't parse JSON entry with value of type %d: %s", (unsigned)itr->type(), ex.what()); - } - } - } catch (const std::exception& ex) { - RING_ERR("Can't parse JSON: %s", ex.what()); - } - - return c; -} - - -std::string -RingAccount::makeArchive(const ArchiveContent& archive) const +void +RingAccount::updateArchive(AccountArchive& archive) const { RING_DBG("[Account %s] building account archive", getAccountID().c_str()); - Json::Value root; - auto details = getAccountDetails(); for (auto it : details) { if (it.first.compare(DRing::Account::ConfProperties::Ringtone::PATH) == 0) { @@ -1005,61 +902,27 @@ RingAccount::makeArchive(const ArchiveContent& archive) const // replace paths by the files content if (not it.second.empty()) { try { - root[it.first] = base64::encode(fileutils::loadFile(it.second)); + archive.config[it.first] = base64::encode(fileutils::loadFile(it.second)); } catch (...) {} } } else - root[it.first] = it.second; + archive.config[it.first] = it.second; } - - if (archive.ca_key and *archive.ca_key) - root[Conf::RING_CA_KEY] = base64::encode(archive.ca_key->serialize()); - root[Conf::RING_ACCOUNT_KEY] = base64::encode(archive.id.first->serialize()); - root[Conf::RING_ACCOUNT_CERT] = base64::encode(archive.id.second->getPacked()); - root[Conf::ETH_KEY] = base64::encode(archive.eth_key); - - if (archive.revoked) - root[Conf::RING_ACCOUNT_CRL] = base64::encode(archive.revoked->getPacked()); - - if (not contacts_.empty()) { - Json::Value& contacts = root[Conf::RING_ACCOUNT_CONTACTS]; - for (const auto& c : contacts_) - contacts[c.first.toString()] = c.second.toJson(); - } - - Json::FastWriter fastWriter; - return fastWriter.write(root); + archive.contacts = contacts_; } void -RingAccount::saveArchive(const ArchiveContent& archive_content, const std::string& pwd) +RingAccount::saveArchive(AccountArchive& archive, const std::string& pwd) { - std::string archive_str; try { - archive_str = makeArchive(archive_content); + updateArchive(archive); + if (archivePath_.empty()) + archivePath_ = "export.gz"; + archive.save(fileutils::getFullPath(idPath_, archivePath_), pwd); } catch (const std::runtime_error& ex) { RING_ERR("[Account %s] Can't export archive: %s", getAccountID().c_str(), ex.what()); return; } - - if (archivePath_.empty()) - archivePath_ = "export.gz"; - auto fullPath = fileutils::getFullPath(idPath_, archivePath_); - - if (not pwd.empty()) { - // Encrypt using provided password - std::vector<uint8_t> data = dht::crypto::aesEncrypt(archiver::compress(archive_str), pwd); - // Write - try { - fileutils::saveFile(fullPath, data); - } catch (const std::runtime_error& ex) { - RING_ERR("Export failed: %s", ex.what()); - return; - } - } else { - RING_WARN("[account %s] unsecured archiving (no password)", getAccountID().c_str()); - archiver::compressGzip(archive_str, fullPath); - } } std::pair<std::vector<uint8_t>, dht::InfoHash> @@ -1095,7 +958,7 @@ RingAccount::addDevice(const std::string& password) std::vector<uint8_t> key; dht::InfoHash loc; std::string pin_str; - ArchiveContent a; + AccountArchive a; try { RING_DBG("[Account %s] exporting Ring account", this_->getAccountID().c_str()); @@ -1117,8 +980,8 @@ RingAccount::addDevice(const std::string& password) return; } try { - auto archive = this_->makeArchive(a); - auto encrypted = dht::crypto::aesEncrypt(archiver::compress(archive), key); + this_->updateArchive(a); + auto encrypted = dht::crypto::aesEncrypt(archiver::compress(a.serialize()), key); if (not this_->dht_.isRunning()) throw std::runtime_error("DHT is not running.."); this_->dht_.put(loc, encrypted, [this_,pin_str](bool ok) { @@ -1141,7 +1004,7 @@ bool RingAccount::revokeDevice(const std::string& password, const std::string& device) { // shared_ptr of future - auto fa = ThreadPool::instance().getShared<ArchiveContent>( + auto fa = ThreadPool::instance().getShared<AccountArchive>( [this, password] { return readArchive(password); }); auto sthis = shared(); findCertificate(dht::InfoHash(device), @@ -1153,7 +1016,7 @@ RingAccount::revokeDevice(const std::string& password, const std::string& device return; } this_.foundAccountDevice(crt); - ArchiveContent a; + AccountArchive a; try { a = fa->get(); } catch (...) { @@ -1213,7 +1076,7 @@ RingAccount::loadAccountFromDHT(const std::string& archive_password, const std:: auto state_new = std::make_shared<std::pair<bool, bool>>(false, true); auto found = std::make_shared<bool>(false); - auto archiveFound = [w,found,archive_password](const ArchiveContent& a) { + auto archiveFound = [w,found,archive_password](AccountArchive&& a) { *found = true; if (auto this_ = w.lock()) { this_->initRingDevice(a); @@ -1257,7 +1120,7 @@ RingAccount::loadAccountFromDHT(const std::string& archive_password, const std:: RING_DBG("Found archive on the DHT"); runOnMainThread([=]() { try { - archiveFound(loadArchive(decrypted)); + archiveFound(AccountArchive(decrypted)); } catch (const std::exception& e) { if (auto this_ = w.lock()) { RING_WARN("[Account %s] error reading archive: %s", this_->getAccountID().c_str(), e.what()); @@ -1294,7 +1157,7 @@ RingAccount::createAccount(const std::string& archive_password, dht::crypto::Ide setRegistrationState(RegistrationState::INITIALIZING); auto sthis = std::static_pointer_cast<RingAccount>(shared_from_this()); ThreadPool::instance().run([sthis,archive_password,migrate]() mutable { - ArchiveContent a; + AccountArchive a; auto& this_ = *sthis; auto future_keypair = ThreadPool::instance().get<dev::KeyPair>(std::bind(&dev::KeyPair::create)); @@ -1363,7 +1226,7 @@ RingAccount::needsMigration(const dht::crypto::Identity& id) } bool -RingAccount::updateCertificates(ArchiveContent& archive, dht::crypto::Identity& device) +RingAccount::updateCertificates(AccountArchive& archive, dht::crypto::Identity& device) { using Certificate = dht::crypto::Certificate; @@ -1406,7 +1269,7 @@ RingAccount::updateCertificates(ArchiveContent& archive, dht::crypto::Identity& void RingAccount::migrateAccount(const std::string& pwd, dht::crypto::Identity& device) { - ArchiveContent archive; + AccountArchive archive; try { archive = readArchive(pwd); } catch (...) { diff --git a/src/ringdht/ringaccount.h b/src/ringdht/ringaccount.h index ced83ee3c336d78f1d7fdeae33ef0d9816ddbf33..326bdbaba76ea8a9324c8cb720c54c8fb49e744b 100644 --- a/src/ringdht/ringaccount.h +++ b/src/ringdht/ringaccount.h @@ -69,6 +69,7 @@ namespace ring { class IceTransport; struct Contact; +struct AccountArchive; class RingAccount : public SIPAccountBase { public: @@ -317,7 +318,6 @@ class RingAccount : public SIPAccountBase { struct PendingMessage; struct TrustRequest; struct KnownDevice; - struct ArchiveContent; struct DeviceAnnouncement; struct DeviceSync; struct BuddyInfo; @@ -488,15 +488,14 @@ class RingAccount : public SIPAccountBase { std::string makeReceipt(const dht::crypto::Identity& id); void createRingDevice(const dht::crypto::Identity& id); - void initRingDevice(const ArchiveContent& a); + void initRingDevice(const AccountArchive& a); void migrateAccount(const std::string& pwd, dht::crypto::Identity& device); - static bool updateCertificates(ArchiveContent& archive, dht::crypto::Identity& device); + static bool updateCertificates(AccountArchive& archive, dht::crypto::Identity& device); void createAccount(const std::string& archive_password, dht::crypto::Identity&& migrate); - std::string makeArchive(const ArchiveContent& content) const; - void saveArchive(const ArchiveContent& content, const std::string& pwd); - ArchiveContent readArchive(const std::string& pwd) const; - static ArchiveContent loadArchive(const std::vector<uint8_t>& data); + void updateArchive(AccountArchive& content) const; + void saveArchive(AccountArchive& content, const std::string& pwd); + AccountArchive readArchive(const std::string& pwd) const; std::vector<std::pair<sockaddr_storage, socklen_t>> loadBootstrap() const; static std::pair<std::string, std::string> saveIdentity(const dht::crypto::Identity id, const std::string& path, const std::string& name);