mirror of
https://git.jami.net/savoirfairelinux/jami-daemon.git
synced 2025-08-12 22:09:25 +08:00
jamiaccount: add manager configuration property
Change-Id: I32136e7381b2f3f73f206a83d573822c6b291291
This commit is contained in:

committed by
Sébastien Blin

parent
75b134cf17
commit
4c899b1e15
@ -719,7 +719,9 @@
|
||||
<ClCompile Include="..\src\im\message_engine.cpp" />
|
||||
<ClCompile Include="..\src\ip_utils.cpp" />
|
||||
<ClCompile Include="..\src\jamidht\account_manager.cpp" />
|
||||
<ClCompile Include="..\src\jamidht\archive_account_manager.cpp" />
|
||||
<ClCompile Include="..\src\jamidht\contact_list.cpp" />
|
||||
<ClCompile Include="..\src\jamidht\server_account_manager.cpp" />
|
||||
<ClCompile Include="..\src\logger.cpp" />
|
||||
<ClCompile Include="..\src\manager.cpp" />
|
||||
<ClCompile Include="..\src\media\audio\audiobuffer.cpp" />
|
||||
@ -879,8 +881,10 @@
|
||||
<ClInclude Include="..\src\im\message_engine.h" />
|
||||
<ClInclude Include="..\src\ip_utils.h" />
|
||||
<ClInclude Include="..\src\jamidht\account_manager.h" />
|
||||
<ClInclude Include="..\src\jamidht\archive_account_manager.h" />
|
||||
<ClInclude Include="..\src\jamidht\contact_list.h" />
|
||||
<ClInclude Include="..\src\jamidht\jami_contact.h" />
|
||||
<ClInclude Include="..\src\jamidht\server_account_manager.h" />
|
||||
<ClInclude Include="..\src\logger.h" />
|
||||
<ClInclude Include="..\src\manager.h" />
|
||||
<ClInclude Include="..\src\map_utils.h" />
|
||||
|
@ -427,6 +427,12 @@
|
||||
<ClCompile Include="..\src\jamidht\contact_list.cpp">
|
||||
<Filter>Source Files\jamidht</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\jamidht\archive_account_manager.cpp">
|
||||
<Filter>Source Files\jamidht</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\jamidht\server_account_manager.cpp">
|
||||
<Filter>Source Files\jamidht</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\src\account.h">
|
||||
@ -882,6 +888,12 @@
|
||||
<ClInclude Include="..\src\jamidht\jami_contact.h">
|
||||
<Filter>Source Files\jamidht</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\src\jamidht\archive_account_manager.h">
|
||||
<Filter>Source Files\jamidht</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\src\jamidht\server_account_manager.h">
|
||||
<Filter>Source Files\jamidht</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\src\jamidht\eth\libdevcore\Makefile.am">
|
||||
|
@ -145,6 +145,7 @@ constexpr static const char PROXY_PUSH_TOKEN [] = "Account.proxyPushToken
|
||||
constexpr static const char DHT_PEER_DISCOVERY [] = "Account.peerDiscovery";
|
||||
constexpr static const char ACCOUNT_PEER_DISCOVERY [] = "Account.accountDiscovery";
|
||||
constexpr static const char ACCOUNT_PUBLISH [] = "Account.accountPublish";
|
||||
constexpr static const char MANAGER_URI [] = "Account.managerUri";
|
||||
|
||||
namespace Audio {
|
||||
|
||||
|
@ -23,7 +23,11 @@ libringacc_la_SOURCES = \
|
||||
contact_list.h \
|
||||
contact_list.cpp \
|
||||
account_manager.h \
|
||||
account_manager.cpp
|
||||
account_manager.cpp \
|
||||
archive_account_manager.h \
|
||||
archive_account_manager.cpp \
|
||||
server_account_manager.h \
|
||||
server_account_manager.cpp
|
||||
|
||||
if RINGNS
|
||||
libringacc_la_SOURCES += \
|
||||
|
@ -33,347 +33,6 @@
|
||||
|
||||
namespace jami {
|
||||
|
||||
const constexpr auto EXPORT_KEY_RENEWAL_TIME = std::chrono::minutes(20);
|
||||
constexpr static const char* const DHT_TYPE_NS = "cx.ring";
|
||||
|
||||
template <typename To, typename From>
|
||||
std::unique_ptr<To> dynamic_unique_cast(std::unique_ptr<From>&& p) {
|
||||
if (auto cast = dynamic_cast<To*>(p.get())) {
|
||||
std::unique_ptr<To> result(cast);
|
||||
p.release();
|
||||
return result;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void
|
||||
ArchiveAccountManager::initAuthentication(
|
||||
CertRequest request,
|
||||
std::unique_ptr<AccountCredentials> credentials,
|
||||
AuthSuccessCallback onSuccess,
|
||||
AuthFailureCallback onFailure,
|
||||
OnChangeCallback onChange)
|
||||
{
|
||||
auto ctx = std::make_shared<AuthContext>();
|
||||
ctx->request = std::move(request);
|
||||
ctx->credentials = dynamic_unique_cast<ArchiveAccountCredentials>(std::move(credentials));
|
||||
ctx->onSuccess = std::move(onSuccess);
|
||||
ctx->onFailure = std::move(onFailure);
|
||||
|
||||
if (not ctx->credentials) {
|
||||
onFailure(AuthError::INVALID_ARGUMENTS, "invalid credentials");
|
||||
return;
|
||||
}
|
||||
|
||||
onChange_ = std::move(onChange);
|
||||
|
||||
if (ctx->credentials->scheme == "dht") {
|
||||
loadFromDHT(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
dht::ThreadPool::computation().run([
|
||||
ctx = std::move(ctx),
|
||||
onAsync = onAsync_
|
||||
]() mutable {
|
||||
onAsync([ctx = std::move(ctx)](AccountManager& accountManager) mutable {
|
||||
auto& this_ = *static_cast<ArchiveAccountManager*>(&accountManager);
|
||||
try {
|
||||
if (ctx->credentials->scheme == "file") {
|
||||
this_.loadFromFile(ctx);
|
||||
return;
|
||||
} else {
|
||||
if (ctx->credentials->updateIdentity.first and ctx->credentials->updateIdentity.second) {
|
||||
auto future_keypair = dht::ThreadPool::computation().get<dev::KeyPair>(&dev::KeyPair::create);
|
||||
AccountArchive a;
|
||||
JAMI_WARN("[Auth] converting certificate from old account %s", ctx->credentials->updateIdentity.first->getPublicKey().getId().toString().c_str());
|
||||
a.id = std::move(ctx->credentials->updateIdentity);
|
||||
try {
|
||||
a.ca_key = std::make_shared<dht::crypto::PrivateKey>(fileutils::loadFile("ca.key", this_.path_));
|
||||
} catch (...) {}
|
||||
this_.updateCertificates(a, ctx->credentials->updateIdentity);
|
||||
auto keypair = future_keypair.get();
|
||||
a.eth_key = keypair.secret().makeInsecure().asBytes();
|
||||
this_.onArchiveLoaded(*ctx, std::move(a));
|
||||
} else {
|
||||
this_.createAccount(ctx);
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
ctx->onFailure(AuthError::UNKNOWN, e.what());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
bool
|
||||
ArchiveAccountManager::updateCertificates(AccountArchive& archive, dht::crypto::Identity& device)
|
||||
{
|
||||
JAMI_WARN("Updating certificates");
|
||||
using Certificate = dht::crypto::Certificate;
|
||||
|
||||
// We need the CA key to resign certificates
|
||||
if (not archive.id.first or
|
||||
not *archive.id.first or
|
||||
not archive.id.second or
|
||||
not archive.ca_key or
|
||||
not *archive.ca_key)
|
||||
return false;
|
||||
|
||||
// Currently set the CA flag and update expiration dates
|
||||
bool updated = false;
|
||||
|
||||
auto& cert = archive.id.second;
|
||||
auto ca = cert->issuer;
|
||||
// Update CA if possible and relevant
|
||||
if (not ca or (not ca->issuer and (not ca->isCA() or ca->getExpiration() < clock::now()))) {
|
||||
ca = std::make_shared<Certificate>(Certificate::generate(*archive.ca_key, "Jami CA", {}, true));
|
||||
updated = true;
|
||||
JAMI_DBG("CA CRT re-generated");
|
||||
}
|
||||
|
||||
// Update certificate
|
||||
if (updated or not cert->isCA() or cert->getExpiration() < clock::now()) {
|
||||
cert = std::make_shared<Certificate>(Certificate::generate(*archive.id.first, "Jami", dht::crypto::Identity{archive.ca_key, ca}, true));
|
||||
updated = true;
|
||||
JAMI_DBG("Jami CRT re-generated");
|
||||
}
|
||||
|
||||
if (updated and device.first and *device.first) {
|
||||
// update device certificate
|
||||
device.second = std::make_shared<Certificate>(Certificate::generate(*device.first, "Jami device", archive.id));
|
||||
JAMI_DBG("device CRT re-generated");
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
void
|
||||
ArchiveAccountManager::createAccount(const std::shared_ptr<AuthContext>& ctx)
|
||||
{
|
||||
AccountArchive a;
|
||||
auto future_keypair = dht::ThreadPool::computation().get<dev::KeyPair>(&dev::KeyPair::create);
|
||||
auto ca = dht::crypto::generateIdentity("Jami CA");
|
||||
if (!ca.first || !ca.second) {
|
||||
throw std::runtime_error("Can't generate CA for this account.");
|
||||
}
|
||||
a.id = dht::crypto::generateIdentity("Jami", ca, 4096, true);
|
||||
if (!a.id.first || !a.id.second) {
|
||||
throw std::runtime_error("Can't generate identity for this account.");
|
||||
}
|
||||
JAMI_WARN("[Auth] new account: CA: %s, RingID: %s",
|
||||
ca.second->getId().toString().c_str(),
|
||||
a.id.second->getId().toString().c_str());
|
||||
a.ca_key = ca.first;
|
||||
auto keypair = future_keypair.get();
|
||||
a.eth_key = keypair.secret().makeInsecure().asBytes();
|
||||
onArchiveLoaded(*ctx, std::move(a));
|
||||
}
|
||||
|
||||
void
|
||||
ArchiveAccountManager::loadFromFile(const std::shared_ptr<AuthContext>& ctx)
|
||||
{
|
||||
AccountArchive archive;
|
||||
try {
|
||||
archive = AccountArchive(ctx->credentials->uri, ctx->credentials->password);
|
||||
} catch (const std::exception& ex) {
|
||||
JAMI_WARN("[Auth] can't read file: %s", ex.what());
|
||||
ctx->onFailure(AuthError::UNKNOWN, ex.what());
|
||||
return;
|
||||
}
|
||||
onArchiveLoaded(*ctx, std::move(archive));
|
||||
}
|
||||
|
||||
struct ArchiveAccountManager::DhtLoadContext {
|
||||
dht::DhtRunner dht;
|
||||
std::pair<bool, bool> stateOld {false, true};
|
||||
std::pair<bool, bool> stateNew {false, true};
|
||||
bool found {false};
|
||||
};
|
||||
|
||||
void
|
||||
ArchiveAccountManager::loadFromDHT(const std::shared_ptr<AuthContext>& ctx)
|
||||
{
|
||||
ctx->dhtContext = std::make_unique<DhtLoadContext>();
|
||||
ctx->dhtContext->dht.run(ctx->credentials->dhtPort, {}, true);
|
||||
for (const auto& bootstrap : ctx->credentials->dhtBootstrap)
|
||||
ctx->dhtContext->dht.bootstrap(bootstrap);
|
||||
auto searchEnded = [ctx]() {
|
||||
if (not ctx->dhtContext or ctx->dhtContext->found) {
|
||||
return;
|
||||
}
|
||||
auto& s = *ctx->dhtContext;
|
||||
if (s.stateOld.first && s.stateNew.first) {
|
||||
dht::ThreadPool::computation().run([ctx, network_error = !s.stateOld.second && !s.stateNew.second]{
|
||||
ctx->dhtContext.reset();
|
||||
JAMI_WARN("[Auth] failure looking for archive on DHT: %s", /**/network_error ? "network error" : "not found");
|
||||
ctx->onFailure(network_error ? AuthError::NETWORK : AuthError::UNKNOWN, "");
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
auto search = [ctx, searchEnded, onAsync = onAsync_](bool previous) {
|
||||
std::vector<uint8_t> key;
|
||||
dht::InfoHash loc;
|
||||
auto& s = previous ? ctx->dhtContext->stateOld : ctx->dhtContext->stateNew;
|
||||
|
||||
// compute archive location and decryption keys
|
||||
try {
|
||||
std::tie(key, loc) = computeKeys(ctx->credentials->password, ctx->credentials->uri, previous);
|
||||
JAMI_DBG("[Auth] trying to load account from DHT with %s at %s", /**/ctx->credentials->uri.c_str(), loc.toString().c_str());
|
||||
ctx->dhtContext->dht.get(loc, [ctx, key=std::move(key), onAsync](const std::shared_ptr<dht::Value>& val) {
|
||||
std::vector<uint8_t> decrypted;
|
||||
try {
|
||||
decrypted = archiver::decompress(dht::crypto::aesDecrypt(val->data, key));
|
||||
} catch (const std::exception& ex) {
|
||||
return true;
|
||||
}
|
||||
JAMI_DBG("[Auth] found archive on the DHT");
|
||||
ctx->dhtContext->found = true;
|
||||
dht::ThreadPool::computation().run([
|
||||
ctx,
|
||||
decrypted = std::move(decrypted),
|
||||
onAsync
|
||||
]{
|
||||
try {
|
||||
auto archive = AccountArchive(decrypted);
|
||||
onAsync([&](AccountManager& accountManager) {
|
||||
auto& this_ = *static_cast<ArchiveAccountManager*>(&accountManager);
|
||||
if (ctx->dhtContext) {
|
||||
ctx->dhtContext->dht.join();
|
||||
ctx->dhtContext.reset();
|
||||
}
|
||||
this_.onArchiveLoaded(*ctx, std::move(archive)/*, std::move(contacts)*/);
|
||||
});
|
||||
} catch (const std::exception& e) {
|
||||
ctx->onFailure(AuthError::UNKNOWN, "");
|
||||
}
|
||||
});
|
||||
return not ctx->dhtContext->found;
|
||||
}, [=, &s](bool ok) {
|
||||
JAMI_DBG("[Auth] DHT archive search ended at %s", /**/loc.toString().c_str());
|
||||
s.first = true;
|
||||
s.second = ok;
|
||||
searchEnded();
|
||||
});
|
||||
} catch (const std::exception& e) {
|
||||
// JAMI_ERR("Error computing kedht::ThreadPool::computation().run(ys: %s", e.what());
|
||||
s.first = true;
|
||||
s.second = true;
|
||||
searchEnded();
|
||||
return;
|
||||
}
|
||||
};
|
||||
dht::ThreadPool::computation().run(std::bind(search, true));
|
||||
dht::ThreadPool::computation().run(std::bind(search, false));
|
||||
}
|
||||
|
||||
void
|
||||
ArchiveAccountManager::onArchiveLoaded(
|
||||
AuthContext& ctx,
|
||||
AccountArchive&& a)
|
||||
{
|
||||
auto ethAccount = dev::KeyPair(dev::Secret(a.eth_key)).address().hex();
|
||||
fileutils::check_dir(path_.c_str(), 0700);
|
||||
|
||||
auto path = fileutils::getFullPath(path_, archivePath_);
|
||||
a.save(path, ctx.credentials ? ctx.credentials->password : "");
|
||||
|
||||
if (not a.id.second->isCA()) {
|
||||
JAMI_ERR("[Auth] trying to sign a certificate with a non-CA.");
|
||||
}
|
||||
JAMI_WARN("generating device certificate");
|
||||
|
||||
auto request = ctx.request.get();
|
||||
if (not request->verify()) {
|
||||
JAMI_ERR("[Auth] Invalid certificate request.");
|
||||
ctx.onFailure(AuthError::INVALID_ARGUMENTS, "");
|
||||
return;
|
||||
}
|
||||
|
||||
auto deviceCertificate = std::make_shared<dht::crypto::Certificate>(dht::crypto::Certificate::generate(*request, a.id));
|
||||
auto receipt = makeReceipt(a.id, *deviceCertificate, ethAccount);
|
||||
auto receiptSignature = a.id.first->sign({receipt.first.begin(), receipt.first.end()});
|
||||
|
||||
auto info = std::make_unique<AccountInfo>();
|
||||
info->identity.second = deviceCertificate;
|
||||
info->contacts = std::make_unique<ContactList>(a.id.second, path_, onChange_);
|
||||
info->contacts->setContacts(a.contacts);
|
||||
info->accountId = a.id.second->getId().toString();
|
||||
info->deviceId = deviceCertificate->getPublicKey().getId().toString();
|
||||
info->ethAccount = ethAccount;
|
||||
info->announce = std::move(receipt.second);
|
||||
info_ = std::move(info);
|
||||
|
||||
JAMI_WARN("[Auth] created new device: %s", info_->deviceId.c_str());
|
||||
ctx.onSuccess(*info_, {}, std::move(receipt.first), std::move(receiptSignature));
|
||||
}
|
||||
|
||||
std::pair<std::vector<uint8_t>, dht::InfoHash>
|
||||
ArchiveAccountManager::computeKeys(const std::string& password, const std::string& pin, bool previous)
|
||||
{
|
||||
// Compute time seed
|
||||
auto now = std::chrono::duration_cast<std::chrono::seconds>(clock::now().time_since_epoch());
|
||||
auto tseed = now.count() / std::chrono::seconds(EXPORT_KEY_RENEWAL_TIME).count();
|
||||
if (previous)
|
||||
tseed--;
|
||||
std::stringstream ss;
|
||||
ss << std::hex << tseed;
|
||||
auto tseed_str = ss.str();
|
||||
|
||||
// Generate key for archive encryption, using PIN as the salt
|
||||
std::vector<uint8_t> salt_key;
|
||||
salt_key.reserve(pin.size() + tseed_str.size());
|
||||
salt_key.insert(salt_key.end(), pin.begin(), pin.end());
|
||||
salt_key.insert(salt_key.end(), tseed_str.begin(), tseed_str.end());
|
||||
auto key = dht::crypto::stretchKey(password, salt_key, 256/8);
|
||||
|
||||
// Generate public storage location as SHA1(key).
|
||||
auto loc = dht::InfoHash::get(key);
|
||||
|
||||
return {key, loc};
|
||||
}
|
||||
|
||||
std::pair<std::string, std::shared_ptr<dht::Value>>
|
||||
ArchiveAccountManager::makeReceipt(const dht::crypto::Identity& id, const dht::crypto::Certificate& device, const std::string& ethAccount)
|
||||
{
|
||||
JAMI_DBG("[Auth] signing device receipt");
|
||||
auto devId = device.getId();
|
||||
DeviceAnnouncement announcement;
|
||||
announcement.dev = devId;
|
||||
dht::Value ann_val {announcement};
|
||||
ann_val.sign(*id.first);
|
||||
|
||||
std::ostringstream is;
|
||||
is << "{\"id\":\"" << id.second->getId()
|
||||
<< "\",\"dev\":\"" << devId
|
||||
<< "\",\"eth\":\"" << ethAccount
|
||||
<< "\",\"announce\":\"" << base64::encode(ann_val.getPacked()) << "\"}";
|
||||
|
||||
//auto announce_ = ;
|
||||
return {is.str(), std::make_shared<dht::Value>(std::move(ann_val))};
|
||||
}
|
||||
|
||||
bool
|
||||
ArchiveAccountManager::needsMigration(const dht::crypto::Identity& id)
|
||||
{
|
||||
if (not id.second)
|
||||
return false;
|
||||
auto cert = id.second->issuer;
|
||||
while (cert) {
|
||||
if (not cert->isCA()){
|
||||
JAMI_WARN("certificate %s is not a CA, needs update.", cert->getId().toString().c_str());
|
||||
return true;
|
||||
}
|
||||
if (cert->getExpiration() < clock::now()) {
|
||||
JAMI_WARN("certificate %s is expired, needs update.", cert->getId().toString().c_str());
|
||||
return true;
|
||||
}
|
||||
cert = cert->issuer;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
dht::crypto::Identity
|
||||
AccountManager::loadIdentity(const std::string& crt_path, const std::string& key_path, const std::string& key_pwd) const
|
||||
{
|
||||
@ -404,6 +63,31 @@ AccountManager::loadIdentity(const std::string& crt_path, const std::string& key
|
||||
return {};
|
||||
}
|
||||
|
||||
std::shared_ptr<dht::Value>
|
||||
AccountManager::parseAnnounce(const std::string& announceBase64, const std::string& accountId, const std::string& deviceId)
|
||||
{
|
||||
auto announce_val = std::make_shared<dht::Value>();
|
||||
try {
|
||||
auto announce = base64::decode(announceBase64);
|
||||
msgpack::object_handle announce_msg = msgpack::unpack((const char*)announce.data(), announce.size());
|
||||
announce_val->msgpack_unpack(announce_msg.get());
|
||||
if (not announce_val->checkSignature()) {
|
||||
JAMI_ERR("[Auth] announce signature check failed");
|
||||
return {};
|
||||
}
|
||||
DeviceAnnouncement da;
|
||||
da.unpackValue(*announce_val);
|
||||
if (da.from.toString() != accountId or da.dev.toString() != deviceId) {
|
||||
JAMI_ERR("[Auth] device ID mismatch in announce");
|
||||
return {};
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
JAMI_ERR("[Auth] can't read announce: %s", e.what());
|
||||
return {};
|
||||
}
|
||||
return announce_val;
|
||||
}
|
||||
|
||||
const AccountInfo*
|
||||
AccountManager::useIdentity(
|
||||
const dht::crypto::Identity& identity,
|
||||
@ -458,23 +142,8 @@ AccountManager::useIdentity(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
dht::Value announce_val;
|
||||
try {
|
||||
auto announce = base64::decode(root["announce"].asString());
|
||||
msgpack::object_handle announce_msg = msgpack::unpack((const char*)announce.data(), announce.size());
|
||||
announce_val.msgpack_unpack(announce_msg.get());
|
||||
if (not announce_val.checkSignature()) {
|
||||
JAMI_ERR("[Auth] announce signature check failed");
|
||||
return nullptr;
|
||||
}
|
||||
DeviceAnnouncement da;
|
||||
da.unpackValue(announce_val);
|
||||
if (da.from.toString() != id or da.dev.toString() != dev_id) {
|
||||
JAMI_ERR("[Auth] device ID mismatch in announce");
|
||||
return nullptr;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
JAMI_ERR("[Auth] can't read announce: %s", e.what());
|
||||
auto announce = parseAnnounce(root["announce"].asString(), id, dev_id);
|
||||
if (not announce) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@ -486,7 +155,7 @@ AccountManager::useIdentity(
|
||||
info->contacts->load();
|
||||
info->accountId = id;
|
||||
info->deviceId = identity.first->getPublicKey().getId().toString();
|
||||
info->announce = std::make_shared<dht::Value>(std::move(announce_val));
|
||||
info->announce = std::move(announce);
|
||||
info->ethAccount = root["eth"].asString();
|
||||
info_ = std::move(info);
|
||||
|
||||
@ -494,202 +163,6 @@ AccountManager::useIdentity(
|
||||
return info_.get();
|
||||
}
|
||||
|
||||
AccountArchive
|
||||
ArchiveAccountManager::readArchive(const std::string& pwd) const
|
||||
{
|
||||
JAMI_DBG("[Auth] reading account archive");
|
||||
return AccountArchive(fileutils::getFullPath(path_, archivePath_), pwd);
|
||||
}
|
||||
|
||||
void
|
||||
ArchiveAccountManager::updateArchive(AccountArchive& archive) const
|
||||
{
|
||||
using namespace DRing::Account::ConfProperties;
|
||||
|
||||
// Keys not exported to archive
|
||||
static const auto filtered_keys = { Ringtone::PATH,
|
||||
ARCHIVE_PATH,
|
||||
RING_DEVICE_ID,
|
||||
RING_DEVICE_NAME,
|
||||
Conf::CONFIG_DHT_PORT };
|
||||
|
||||
// Keys with meaning of file path where the contents has to be exported in base64
|
||||
static const auto encoded_keys = { TLS::CA_LIST_FILE,
|
||||
TLS::CERTIFICATE_FILE,
|
||||
TLS::PRIVATE_KEY_FILE };
|
||||
|
||||
JAMI_DBG("[Auth] building account archive");
|
||||
for (const auto& it : onExportConfig_()) {
|
||||
// filter-out?
|
||||
if (std::any_of(std::begin(filtered_keys), std::end(filtered_keys),
|
||||
[&](const auto& key){ return key == it.first; }))
|
||||
continue;
|
||||
|
||||
// file contents?
|
||||
if (std::any_of(std::begin(encoded_keys), std::end(encoded_keys),
|
||||
[&](const auto& key){ return key == it.first; })) {
|
||||
try {
|
||||
archive.config.emplace(it.first, base64::encode(fileutils::loadFile(it.second)));
|
||||
} catch (...) {}
|
||||
} else
|
||||
archive.config.insert(it);
|
||||
}
|
||||
archive.contacts = info_->contacts->getContacts();
|
||||
}
|
||||
|
||||
void
|
||||
ArchiveAccountManager::saveArchive(AccountArchive& archive, const std::string& pwd)
|
||||
{
|
||||
try {
|
||||
updateArchive(archive);
|
||||
if (archivePath_.empty())
|
||||
archivePath_ = "export.gz";
|
||||
archive.save(fileutils::getFullPath(path_, archivePath_), pwd);
|
||||
//archiveHasPassword_ = not pwd.empty();
|
||||
} catch (const std::runtime_error& ex) {
|
||||
JAMI_ERR("[Auth] Can't export archive: %s", ex.what());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
ArchiveAccountManager::changePassword(const std::string& password_old, const std::string& password_new)
|
||||
{
|
||||
try {
|
||||
auto path = fileutils::getFullPath(path_, archivePath_);
|
||||
AccountArchive(path, password_old).save(path, password_new);
|
||||
return true;
|
||||
} catch (const std::exception&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::string
|
||||
generatePIN(size_t length = 8)
|
||||
{
|
||||
static constexpr const char alphabet[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
dht::crypto::random_device rd;
|
||||
std::uniform_int_distribution<size_t> dis(0, sizeof(alphabet)-2);
|
||||
std::string ret;
|
||||
ret.reserve(length);
|
||||
for (size_t i=0; i<length; i++)
|
||||
ret.push_back(alphabet[dis(rd)]);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
ArchiveAccountManager::addDevice(const std::string& password, AddDeviceCallback cb)
|
||||
{
|
||||
dht::ThreadPool::computation().run([onAsync = onAsync_, password, cb = std::move(cb)]() mutable {
|
||||
onAsync([password = std::move(password), cb = std::move(cb)](AccountManager& accountManager) mutable {
|
||||
auto& this_ = *static_cast<ArchiveAccountManager*>(&accountManager);
|
||||
|
||||
std::vector<uint8_t> key;
|
||||
dht::InfoHash loc;
|
||||
std::string pin_str;
|
||||
AccountArchive a;
|
||||
try {
|
||||
JAMI_DBG("[Auth] exporting account");
|
||||
|
||||
a = this_.readArchive(password);
|
||||
|
||||
// Generate random PIN
|
||||
pin_str = generatePIN();
|
||||
|
||||
std::tie(key, loc) = computeKeys(password, pin_str);
|
||||
} catch (const std::exception& e) {
|
||||
JAMI_ERR("[Auth] can't export account: %s", e.what());
|
||||
cb(AddDeviceResult::ERROR_CREDENTIALS, {});
|
||||
return;
|
||||
}
|
||||
// now that key and loc are computed, display to user in lowercase
|
||||
std::transform(pin_str.begin(), pin_str.end(), pin_str.begin(), ::tolower);
|
||||
try {
|
||||
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..");
|
||||
JAMI_WARN("[Auth] exporting account with PIN: %s at %s (size %zu)", pin_str.c_str(), loc.toString().c_str(), encrypted.size());
|
||||
this_.dht_->put(loc, encrypted, [cb, pin = std::move(pin_str)](bool ok) {
|
||||
JAMI_DBG("[Auth] account archive published: %s", ok ? "success" : "failure");
|
||||
if (ok)
|
||||
cb(AddDeviceResult::SUCCESS_SHOW_PIN, pin);
|
||||
else
|
||||
cb(AddDeviceResult::ERROR_NETWORK, {});
|
||||
});
|
||||
} catch (const std::exception& e) {
|
||||
JAMI_ERR("[Auth] can't export account: %s", e.what());
|
||||
cb(AddDeviceResult::ERROR_NETWORK, {});
|
||||
return;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
bool
|
||||
ArchiveAccountManager::revokeDevice(const std::string& password, const std::string& device, RevokeDeviceCallback cb)
|
||||
{
|
||||
auto fa = dht::ThreadPool::computation().getShared<AccountArchive>([this, password] { return readArchive(password); });
|
||||
findCertificate(dht::InfoHash(device),
|
||||
[fa=std::move(fa),password,device,cb,onAsync=onAsync_](const std::shared_ptr<dht::crypto::Certificate>& crt) mutable
|
||||
{
|
||||
if (not crt) {
|
||||
cb(RevokeDeviceResult::ERROR_NETWORK);
|
||||
return;
|
||||
}
|
||||
onAsync([cb, crt=std::move(crt), fa=std::move(fa), password=std::move(password)](AccountManager& accountManager) mutable {
|
||||
auto& this_ = *static_cast<ArchiveAccountManager*>(&accountManager);
|
||||
this_.info_->contacts->foundAccountDevice(crt);
|
||||
AccountArchive a;
|
||||
try {
|
||||
a = fa.get();
|
||||
} catch (...) {
|
||||
cb(RevokeDeviceResult::ERROR_CREDENTIALS);
|
||||
return;
|
||||
}
|
||||
// Add revoked device to the revocation list and resign it
|
||||
if (not a.revoked)
|
||||
a.revoked = std::make_shared<decltype(a.revoked)::element_type>();
|
||||
a.revoked->revoke(*crt);
|
||||
a.revoked->sign(a.id);
|
||||
// add to CRL cache
|
||||
tls::CertificateStore::instance().pinRevocationList(a.id.second->getId().toString(), a.revoked);
|
||||
tls::CertificateStore::instance().loadRevocations(*a.id.second);
|
||||
|
||||
this_.saveArchive(a, password);
|
||||
this_.info_->contacts->removeAccountDevice(crt->getId());
|
||||
cb(RevokeDeviceResult::SUCCESS);
|
||||
this_.syncDevices();
|
||||
});
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
ArchiveAccountManager::exportArchive(const std::string& destinationPath, const std::string& password)
|
||||
{
|
||||
try {
|
||||
// Save contacts if possible before exporting
|
||||
AccountArchive archive = readArchive(password);
|
||||
updateArchive(archive);
|
||||
archive.save(fileutils::getFullPath(path_, archivePath_), password);
|
||||
|
||||
// Export the file
|
||||
auto sourcePath = fileutils::getFullPath(path_, archivePath_);
|
||||
std::ifstream src(sourcePath, std::ios::in | std::ios::binary);
|
||||
if (!src) return false;
|
||||
std::ofstream dst(destinationPath, std::ios::out | std::ios::binary);
|
||||
dst << src.rdbuf();
|
||||
return true;
|
||||
} catch (const std::runtime_error& ex) {
|
||||
JAMI_ERR("[Auth] Can't export archive: %s", ex.what());
|
||||
return false;
|
||||
} catch (...) {
|
||||
JAMI_ERR("[Auth] Can't export archive: can't read archive");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const std::map<dht::InfoHash, KnownDevice>&
|
||||
AccountManager::getKnownDevices() const {
|
||||
return info_->contacts->getKnownDevices();
|
||||
@ -800,23 +273,6 @@ AccountManager::getContactDetails(const std::string& uri) const
|
||||
return info_->contacts->getContactDetails(h);
|
||||
}
|
||||
|
||||
bool
|
||||
ArchiveAccountManager::findCertificate(const dht::InfoHash& h, std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb)
|
||||
{
|
||||
if (auto cert = tls::CertificateStore::instance().getCertificate(h.toString())) {
|
||||
if (cb)
|
||||
cb(cert);
|
||||
} else {
|
||||
dht_->findCertificate(h, [cb](const std::shared_ptr<dht::crypto::Certificate>& crt) {
|
||||
if (crt)
|
||||
tls::CertificateStore::instance().pinCertificate(crt);
|
||||
if (cb)
|
||||
cb(crt);
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
AccountManager::setCertificateStatus(const std::string& cert_id, tls::TrustStore::PermissionStatus status)
|
||||
{
|
||||
@ -928,168 +384,16 @@ AccountManager::forEachDevice(const dht::InfoHash& to,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ArchiveAccountManager::syncDevices()
|
||||
{
|
||||
JAMI_DBG("Building device sync from %s", info_->deviceId.c_str());
|
||||
auto sync_data = info_->contacts->getSyncData();
|
||||
|
||||
for (const auto& dev : getKnownDevices()) {
|
||||
// don't send sync data to ourself
|
||||
if (dev.first.toString() == info_->deviceId)
|
||||
continue;
|
||||
JAMI_DBG("sending device sync to %s %s", dev.second.name.c_str(), dev.first.toString().c_str());
|
||||
auto syncDeviceKey = dht::InfoHash::get("inbox:"+dev.first.toString());
|
||||
dht_->putEncrypted(syncDeviceKey, dev.first, sync_data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ArchiveAccountManager::startSync()
|
||||
{
|
||||
// Put device annoucement
|
||||
if (info_->announce) {
|
||||
auto h = dht::InfoHash(info_->accountId);
|
||||
JAMI_DBG("announcing device at %s", h.toString().c_str());
|
||||
dht_->put(h, info_->announce, dht::DoneCallback{}, {}, true);
|
||||
for (const auto& crl : info_->identity.second->issuer->getRevocationLists())
|
||||
dht_->put(h, crl, dht::DoneCallback{}, {}, true);
|
||||
dht_->listen<DeviceAnnouncement>(h, [this](DeviceAnnouncement&& dev) {
|
||||
findCertificate(dev.dev, [this](const std::shared_ptr<dht::crypto::Certificate>& crt) {
|
||||
foundAccountDevice(crt);
|
||||
});
|
||||
return true;
|
||||
});
|
||||
dht_->listen<dht::crypto::RevocationList>(h, [this](dht::crypto::RevocationList&& crl) {
|
||||
if (crl.isSignedBy(*info_->identity.second->issuer)) {
|
||||
JAMI_DBG("found CRL for account.");
|
||||
tls::CertificateStore::instance().pinRevocationList(
|
||||
info_->accountId,
|
||||
std::make_shared<dht::crypto::RevocationList>(std::move(crl)));
|
||||
}
|
||||
return true;
|
||||
});
|
||||
syncDevices();
|
||||
} else {
|
||||
JAMI_WARN("can't announce device: no annoucement...");
|
||||
}
|
||||
|
||||
auto inboxKey = dht::InfoHash::get("inbox:"+info_->deviceId);
|
||||
dht_->listen<dht::TrustRequest>(
|
||||
inboxKey,
|
||||
[this](dht::TrustRequest&& v) {
|
||||
if (v.service != DHT_TYPE_NS)
|
||||
return true;
|
||||
|
||||
findCertificate(v.from, [this, v](const std::shared_ptr<dht::crypto::Certificate>& cert) mutable {
|
||||
// check peer certificate
|
||||
dht::InfoHash peer_account;
|
||||
if (not foundPeerDevice(cert, peer_account)) {
|
||||
return;
|
||||
}
|
||||
|
||||
JAMI_WARN("Got trust request from: %s / %s", peer_account.toString().c_str(), v.from.toString().c_str());
|
||||
if (info_)
|
||||
if (info_->contacts->onTrustRequest(peer_account, v.from, time(nullptr), v.confirm, std::move(v.payload))) {
|
||||
sendTrustRequestConfirm(peer_account);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
dht_->listen<DeviceSync>(
|
||||
inboxKey,
|
||||
[this](DeviceSync&& sync) {
|
||||
// Received device sync data.
|
||||
// check device certificate
|
||||
findCertificate(sync.from, [this,sync](const std::shared_ptr<dht::crypto::Certificate>& cert) mutable {
|
||||
if (!cert or cert->getId() != sync.from) {
|
||||
JAMI_WARN("Can't find certificate for device %s", sync.from.toString().c_str());
|
||||
return;
|
||||
}
|
||||
if (not foundAccountDevice(cert))
|
||||
return;
|
||||
onSyncData(std::move(sync));
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void
|
||||
ArchiveAccountManager::onSyncData(DeviceSync&& sync)
|
||||
{
|
||||
auto sync_date = clock::time_point(clock::duration(sync.date));
|
||||
if (not info_->contacts->syncDevice(sync.from, sync_date)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sync known devices
|
||||
JAMI_DBG("[Contacts] received device sync data (%lu devices, %lu contacts)", sync.devices_known.size(), sync.peers.size());
|
||||
for (const auto& d : sync.devices_known) {
|
||||
findCertificate(d.first, [this,d](const std::shared_ptr<dht::crypto::Certificate>& crt) {
|
||||
if (not crt)
|
||||
return;
|
||||
//std::lock_guard<std::mutex> lock(deviceListMutex_);
|
||||
foundAccountDevice(crt, d.second);
|
||||
});
|
||||
}
|
||||
//saveKnownDevices();
|
||||
|
||||
// Sync contacts
|
||||
for (const auto& peer : sync.peers)
|
||||
info_->contacts->updateContact(peer.first, peer.second);
|
||||
//saveContacts();
|
||||
|
||||
// Sync trust requests
|
||||
for (const auto& tr : sync.trust_requests)
|
||||
info_->contacts->onTrustRequest(tr.first, tr.second.device, tr.second.received, false, {});
|
||||
}
|
||||
|
||||
|
||||
#if HAVE_RINGNS
|
||||
void
|
||||
ArchiveAccountManager::lookupName(const std::string& name, LookupCallback cb)
|
||||
AccountManager::lookupName(const std::string& name, LookupCallback cb)
|
||||
{
|
||||
nameDir_.get().lookupName(name, cb);
|
||||
}
|
||||
|
||||
void
|
||||
ArchiveAccountManager::lookupAddress(const std::string& addr, LookupCallback cb)
|
||||
AccountManager::lookupAddress(const std::string& addr, LookupCallback cb)
|
||||
{
|
||||
nameDir_.get().lookupAddress(addr, cb);
|
||||
}
|
||||
|
||||
void
|
||||
ArchiveAccountManager::registerName(const std::string& password, const std::string& name, RegistrationCallback cb)
|
||||
{
|
||||
std::string signedName;
|
||||
auto nameLowercase {name};
|
||||
std::transform(nameLowercase.begin(), nameLowercase.end(), nameLowercase.begin(), ::tolower);
|
||||
std::string publickey;
|
||||
std::string accountId;
|
||||
std::string ethAccount;
|
||||
|
||||
try {
|
||||
auto archive = readArchive(password);
|
||||
auto privateKey = archive.id.first;
|
||||
auto pk = privateKey->getPublicKey();
|
||||
publickey = pk.toString();
|
||||
accountId = pk.getId().toString();
|
||||
signedName = base64::encode(privateKey->sign(std::vector<uint8_t>(nameLowercase.begin(), nameLowercase.end())));
|
||||
ethAccount = dev::KeyPair(dev::Secret(archive.eth_key)).address().hex();
|
||||
} catch (const std::exception& e) {
|
||||
//JAMI_ERR("[Auth] can't export account: %s", e.what());
|
||||
cb(NameDirectory::RegistrationResponse::invalidCredentials);
|
||||
return;
|
||||
}
|
||||
|
||||
nameDir_.get().registerName(accountId, nameLowercase, ethAccount, cb, signedName, publickey);
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
@ -50,29 +50,45 @@ struct AccountInfo {
|
||||
std::string ethAccount;
|
||||
};
|
||||
|
||||
template <typename To, typename From>
|
||||
std::unique_ptr<To>
|
||||
dynamic_unique_cast(std::unique_ptr<From>&& p) {
|
||||
if (auto cast = dynamic_cast<To*>(p.get())) {
|
||||
std::unique_ptr<To> result(cast);
|
||||
p.release();
|
||||
return result;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
class AccountManager {
|
||||
public:
|
||||
using AsyncUser = std::function<void(AccountManager&)>;
|
||||
using OnAsync = std::function<void(AsyncUser&&)>;
|
||||
using OnChangeCallback = ContactList::OnChangeCallback;
|
||||
using OnExportConfig = std::function<std::map<std::string, std::string>()>;
|
||||
using clock = std::chrono::system_clock;
|
||||
using time_point = clock::time_point;
|
||||
|
||||
AccountManager(
|
||||
const std::string& path,
|
||||
OnAsync&& onAsync,
|
||||
OnExportConfig&& onExportConfig,
|
||||
std::shared_ptr<dht::DhtRunner> dht)
|
||||
: path_(path), onAsync_(std::move(onAsync)), onExportConfig_(std::move(onExportConfig)), dht_(std::move(dht)) {};
|
||||
std::shared_ptr<dht::DhtRunner> dht,
|
||||
const std::string& nameServer)
|
||||
: path_(path)
|
||||
, onAsync_(std::move(onAsync))
|
||||
, dht_(std::move(dht))
|
||||
, nameDir_(NameDirectory::instance(nameServer)) {};
|
||||
|
||||
virtual ~AccountManager() = default;
|
||||
|
||||
constexpr static const char* const DHT_TYPE_NS = "cx.ring";
|
||||
|
||||
// Auth
|
||||
|
||||
enum class AuthError {
|
||||
UNKNOWN,
|
||||
INVALID_ARGUMENTS,
|
||||
SERVER_ERROR,
|
||||
NETWORK
|
||||
};
|
||||
|
||||
@ -183,101 +199,23 @@ public:
|
||||
tls::TrustStore::PermissionStatus getCertificateStatus(const std::string& cert_id) const;
|
||||
bool isAllowed(const crypto::Certificate& crt, bool allowPublic);
|
||||
|
||||
static std::shared_ptr<dht::Value> parseAnnounce(const std::string& announceBase64, const std::string& accountId, const std::string& deviceId);
|
||||
|
||||
// Name resolver
|
||||
#if HAVE_RINGNS
|
||||
using LookupCallback = NameDirectory::LookupCallback;
|
||||
using RegistrationCallback = NameDirectory::RegistrationCallback;
|
||||
|
||||
virtual void lookupName(const std::string& name, LookupCallback cb) = 0;
|
||||
virtual void lookupAddress(const std::string& address, LookupCallback cb) = 0;
|
||||
virtual void lookupName(const std::string& name, LookupCallback cb);
|
||||
virtual void lookupAddress(const std::string& address, LookupCallback cb);
|
||||
virtual void registerName(const std::string& password, const std::string& name, RegistrationCallback cb) = 0;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
std::string path_;
|
||||
OnAsync onAsync_;
|
||||
OnExportConfig onExportConfig_;
|
||||
OnChangeCallback onChange_;
|
||||
std::unique_ptr<AccountInfo> info_;
|
||||
std::shared_ptr<dht::DhtRunner> dht_;
|
||||
};
|
||||
|
||||
|
||||
class ArchiveAccountManager : public AccountManager {
|
||||
public:
|
||||
ArchiveAccountManager(
|
||||
const std::string& path,
|
||||
std::shared_ptr<dht::DhtRunner> dht,
|
||||
OnAsync&& onAsync,
|
||||
OnExportConfig&& onExportConfig,
|
||||
std::string archivePath,
|
||||
const std::string& nameServer)
|
||||
: AccountManager(path, std::move(onAsync), std::move(onExportConfig), std::move(dht)), archivePath_(std::move(archivePath)), nameDir_(NameDirectory::instance(nameServer)) {};
|
||||
|
||||
struct ArchiveAccountCredentials : AccountCredentials {
|
||||
std::string archivePath;
|
||||
in_port_t dhtPort;
|
||||
std::vector<std::string> dhtBootstrap;
|
||||
dht::crypto::Identity updateIdentity;
|
||||
};
|
||||
|
||||
void initAuthentication(
|
||||
CertRequest request,
|
||||
std::unique_ptr<AccountCredentials> credentials,
|
||||
AuthSuccessCallback onSuccess,
|
||||
AuthFailureCallback onFailure,
|
||||
OnChangeCallback onChange) override;
|
||||
|
||||
void startSync() override;
|
||||
|
||||
bool changePassword(const std::string& password_old, const std::string& password_new) override;
|
||||
|
||||
void syncDevices() override;
|
||||
void onSyncData(DeviceSync&& device);
|
||||
|
||||
bool findCertificate(const dht::InfoHash& h, std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb = {}) override;
|
||||
|
||||
void addDevice(const std::string& password, AddDeviceCallback) override;
|
||||
bool revokeDevice(const std::string& password, const std::string& device, RevokeDeviceCallback) override;
|
||||
bool exportArchive(const std::string& destinationPath, const std::string& password);
|
||||
|
||||
#if HAVE_RINGNS
|
||||
void lookupName(const std::string& name, LookupCallback cb) override;
|
||||
void lookupAddress(const std::string& address, LookupCallback cb) override;
|
||||
void registerName(const std::string& password, const std::string& name, RegistrationCallback cb) override;
|
||||
#endif
|
||||
|
||||
private:
|
||||
struct DhtLoadContext;
|
||||
struct AuthContext {
|
||||
CertRequest request;
|
||||
//std::unique_ptr<dht::crypto::CertificateRequest> request;
|
||||
std::unique_ptr<ArchiveAccountCredentials> credentials;
|
||||
std::unique_ptr<DhtLoadContext> dhtContext;
|
||||
AuthSuccessCallback onSuccess;
|
||||
AuthFailureCallback onFailure;
|
||||
};
|
||||
|
||||
void createAccount(const std::shared_ptr<AuthContext>& ctx);
|
||||
std::pair<std::string, std::shared_ptr<dht::Value>> makeReceipt(const dht::crypto::Identity& id, const dht::crypto::Certificate& device, const std::string& ethAccount);
|
||||
void updateArchive(AccountArchive& content/*, const ContactList& syncData*/) const;
|
||||
void saveArchive(AccountArchive& content, const std::string& pwd);
|
||||
AccountArchive readArchive(const std::string& pwd) const;
|
||||
static std::pair<std::vector<uint8_t>, dht::InfoHash> computeKeys(const std::string& password, const std::string& pin, bool previous=false);
|
||||
bool updateCertificates(AccountArchive& archive, dht::crypto::Identity& device);
|
||||
static bool needsMigration(const dht::crypto::Identity& id);
|
||||
|
||||
void loadFromFile(const std::shared_ptr<AuthContext>& ctx);
|
||||
void loadFromDHT(const std::shared_ptr<AuthContext>& ctx);
|
||||
void onArchiveLoaded(AuthContext& ctx, AccountArchive&& a);
|
||||
|
||||
std::string archivePath_;
|
||||
std::reference_wrapper<NameDirectory> nameDir_;
|
||||
};
|
||||
|
||||
class ServerAccountManager : public AccountManager {
|
||||
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
729
src/jamidht/archive_account_manager.cpp
Normal file
729
src/jamidht/archive_account_manager.cpp
Normal file
@ -0,0 +1,729 @@
|
||||
/*
|
||||
* Copyright (C) 2014-2019 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 "archive_account_manager.h"
|
||||
#include "accountarchive.h"
|
||||
#include "fileutils.h"
|
||||
#include "libdevcrypto/Common.h"
|
||||
#include "archiver.h"
|
||||
#include "base64.h"
|
||||
#include "dring/account_const.h"
|
||||
#include "account_schema.h"
|
||||
|
||||
#include <opendht/dhtrunner.h>
|
||||
#include <opendht/thread_pool.h>
|
||||
|
||||
#include <memory>
|
||||
#include <fstream>
|
||||
|
||||
namespace jami {
|
||||
|
||||
const constexpr auto EXPORT_KEY_RENEWAL_TIME = std::chrono::minutes(20);
|
||||
|
||||
void
|
||||
ArchiveAccountManager::initAuthentication(
|
||||
CertRequest request,
|
||||
std::unique_ptr<AccountCredentials> credentials,
|
||||
AuthSuccessCallback onSuccess,
|
||||
AuthFailureCallback onFailure,
|
||||
OnChangeCallback onChange)
|
||||
{
|
||||
auto ctx = std::make_shared<AuthContext>();
|
||||
ctx->request = std::move(request);
|
||||
ctx->credentials = dynamic_unique_cast<ArchiveAccountCredentials>(std::move(credentials));
|
||||
ctx->onSuccess = std::move(onSuccess);
|
||||
ctx->onFailure = std::move(onFailure);
|
||||
|
||||
if (not ctx->credentials) {
|
||||
onFailure(AuthError::INVALID_ARGUMENTS, "invalid credentials");
|
||||
return;
|
||||
}
|
||||
|
||||
onChange_ = std::move(onChange);
|
||||
|
||||
if (ctx->credentials->scheme == "dht") {
|
||||
loadFromDHT(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
dht::ThreadPool::computation().run([
|
||||
ctx = std::move(ctx),
|
||||
onAsync = onAsync_
|
||||
]() mutable {
|
||||
onAsync([ctx = std::move(ctx)](AccountManager& accountManager) mutable {
|
||||
auto& this_ = *static_cast<ArchiveAccountManager*>(&accountManager);
|
||||
try {
|
||||
if (ctx->credentials->scheme == "file") {
|
||||
this_.loadFromFile(ctx);
|
||||
return;
|
||||
} else {
|
||||
if (ctx->credentials->updateIdentity.first and ctx->credentials->updateIdentity.second) {
|
||||
auto future_keypair = dht::ThreadPool::computation().get<dev::KeyPair>(&dev::KeyPair::create);
|
||||
AccountArchive a;
|
||||
JAMI_WARN("[Auth] converting certificate from old account %s", ctx->credentials->updateIdentity.first->getPublicKey().getId().toString().c_str());
|
||||
a.id = std::move(ctx->credentials->updateIdentity);
|
||||
try {
|
||||
a.ca_key = std::make_shared<dht::crypto::PrivateKey>(fileutils::loadFile("ca.key", this_.path_));
|
||||
} catch (...) {}
|
||||
this_.updateCertificates(a, ctx->credentials->updateIdentity);
|
||||
auto keypair = future_keypair.get();
|
||||
a.eth_key = keypair.secret().makeInsecure().asBytes();
|
||||
this_.onArchiveLoaded(*ctx, std::move(a));
|
||||
} else {
|
||||
this_.createAccount(ctx);
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
ctx->onFailure(AuthError::UNKNOWN, e.what());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
bool
|
||||
ArchiveAccountManager::updateCertificates(AccountArchive& archive, dht::crypto::Identity& device)
|
||||
{
|
||||
JAMI_WARN("Updating certificates");
|
||||
using Certificate = dht::crypto::Certificate;
|
||||
|
||||
// We need the CA key to resign certificates
|
||||
if (not archive.id.first or
|
||||
not *archive.id.first or
|
||||
not archive.id.second or
|
||||
not archive.ca_key or
|
||||
not *archive.ca_key)
|
||||
return false;
|
||||
|
||||
// Currently set the CA flag and update expiration dates
|
||||
bool updated = false;
|
||||
|
||||
auto& cert = archive.id.second;
|
||||
auto ca = cert->issuer;
|
||||
// Update CA if possible and relevant
|
||||
if (not ca or (not ca->issuer and (not ca->isCA() or ca->getExpiration() < clock::now()))) {
|
||||
ca = std::make_shared<Certificate>(Certificate::generate(*archive.ca_key, "Jami CA", {}, true));
|
||||
updated = true;
|
||||
JAMI_DBG("CA CRT re-generated");
|
||||
}
|
||||
|
||||
// Update certificate
|
||||
if (updated or not cert->isCA() or cert->getExpiration() < clock::now()) {
|
||||
cert = std::make_shared<Certificate>(Certificate::generate(*archive.id.first, "Jami", dht::crypto::Identity{archive.ca_key, ca}, true));
|
||||
updated = true;
|
||||
JAMI_DBG("Jami CRT re-generated");
|
||||
}
|
||||
|
||||
if (updated and device.first and *device.first) {
|
||||
// update device certificate
|
||||
device.second = std::make_shared<Certificate>(Certificate::generate(*device.first, "Jami device", archive.id));
|
||||
JAMI_DBG("device CRT re-generated");
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
void
|
||||
ArchiveAccountManager::createAccount(const std::shared_ptr<AuthContext>& ctx)
|
||||
{
|
||||
AccountArchive a;
|
||||
auto future_keypair = dht::ThreadPool::computation().get<dev::KeyPair>(&dev::KeyPair::create);
|
||||
auto ca = dht::crypto::generateIdentity("Jami CA");
|
||||
if (!ca.first || !ca.second) {
|
||||
throw std::runtime_error("Can't generate CA for this account.");
|
||||
}
|
||||
a.id = dht::crypto::generateIdentity("Jami", ca, 4096, true);
|
||||
if (!a.id.first || !a.id.second) {
|
||||
throw std::runtime_error("Can't generate identity for this account.");
|
||||
}
|
||||
JAMI_WARN("[Auth] new account: CA: %s, RingID: %s",
|
||||
ca.second->getId().toString().c_str(),
|
||||
a.id.second->getId().toString().c_str());
|
||||
a.ca_key = ca.first;
|
||||
auto keypair = future_keypair.get();
|
||||
a.eth_key = keypair.secret().makeInsecure().asBytes();
|
||||
onArchiveLoaded(*ctx, std::move(a));
|
||||
}
|
||||
|
||||
void
|
||||
ArchiveAccountManager::loadFromFile(const std::shared_ptr<AuthContext>& ctx)
|
||||
{
|
||||
AccountArchive archive;
|
||||
try {
|
||||
archive = AccountArchive(ctx->credentials->uri, ctx->credentials->password);
|
||||
} catch (const std::exception& ex) {
|
||||
JAMI_WARN("[Auth] can't read file: %s", ex.what());
|
||||
ctx->onFailure(AuthError::UNKNOWN, ex.what());
|
||||
return;
|
||||
}
|
||||
onArchiveLoaded(*ctx, std::move(archive));
|
||||
}
|
||||
|
||||
struct ArchiveAccountManager::DhtLoadContext {
|
||||
dht::DhtRunner dht;
|
||||
std::pair<bool, bool> stateOld {false, true};
|
||||
std::pair<bool, bool> stateNew {false, true};
|
||||
bool found {false};
|
||||
};
|
||||
|
||||
void
|
||||
ArchiveAccountManager::loadFromDHT(const std::shared_ptr<AuthContext>& ctx)
|
||||
{
|
||||
ctx->dhtContext = std::make_unique<DhtLoadContext>();
|
||||
ctx->dhtContext->dht.run(ctx->credentials->dhtPort, {}, true);
|
||||
for (const auto& bootstrap : ctx->credentials->dhtBootstrap)
|
||||
ctx->dhtContext->dht.bootstrap(bootstrap);
|
||||
auto searchEnded = [ctx]() {
|
||||
if (not ctx->dhtContext or ctx->dhtContext->found) {
|
||||
return;
|
||||
}
|
||||
auto& s = *ctx->dhtContext;
|
||||
if (s.stateOld.first && s.stateNew.first) {
|
||||
dht::ThreadPool::computation().run([ctx, network_error = !s.stateOld.second && !s.stateNew.second]{
|
||||
ctx->dhtContext.reset();
|
||||
JAMI_WARN("[Auth] failure looking for archive on DHT: %s", /**/network_error ? "network error" : "not found");
|
||||
ctx->onFailure(network_error ? AuthError::NETWORK : AuthError::UNKNOWN, "");
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
auto search = [ctx, searchEnded, onAsync = onAsync_](bool previous) {
|
||||
std::vector<uint8_t> key;
|
||||
dht::InfoHash loc;
|
||||
auto& s = previous ? ctx->dhtContext->stateOld : ctx->dhtContext->stateNew;
|
||||
|
||||
// compute archive location and decryption keys
|
||||
try {
|
||||
std::tie(key, loc) = computeKeys(ctx->credentials->password, ctx->credentials->uri, previous);
|
||||
JAMI_DBG("[Auth] trying to load account from DHT with %s at %s", /**/ctx->credentials->uri.c_str(), loc.toString().c_str());
|
||||
ctx->dhtContext->dht.get(loc, [ctx, key=std::move(key), onAsync](const std::shared_ptr<dht::Value>& val) {
|
||||
std::vector<uint8_t> decrypted;
|
||||
try {
|
||||
decrypted = archiver::decompress(dht::crypto::aesDecrypt(val->data, key));
|
||||
} catch (const std::exception& ex) {
|
||||
return true;
|
||||
}
|
||||
JAMI_DBG("[Auth] found archive on the DHT");
|
||||
ctx->dhtContext->found = true;
|
||||
dht::ThreadPool::computation().run([
|
||||
ctx,
|
||||
decrypted = std::move(decrypted),
|
||||
onAsync
|
||||
]{
|
||||
try {
|
||||
auto archive = AccountArchive(decrypted);
|
||||
onAsync([&](AccountManager& accountManager) {
|
||||
auto& this_ = *static_cast<ArchiveAccountManager*>(&accountManager);
|
||||
if (ctx->dhtContext) {
|
||||
ctx->dhtContext->dht.join();
|
||||
ctx->dhtContext.reset();
|
||||
}
|
||||
this_.onArchiveLoaded(*ctx, std::move(archive)/*, std::move(contacts)*/);
|
||||
});
|
||||
} catch (const std::exception& e) {
|
||||
ctx->onFailure(AuthError::UNKNOWN, "");
|
||||
}
|
||||
});
|
||||
return not ctx->dhtContext->found;
|
||||
}, [=, &s](bool ok) {
|
||||
JAMI_DBG("[Auth] DHT archive search ended at %s", /**/loc.toString().c_str());
|
||||
s.first = true;
|
||||
s.second = ok;
|
||||
searchEnded();
|
||||
});
|
||||
} catch (const std::exception& e) {
|
||||
// JAMI_ERR("Error computing kedht::ThreadPool::computation().run(ys: %s", e.what());
|
||||
s.first = true;
|
||||
s.second = true;
|
||||
searchEnded();
|
||||
return;
|
||||
}
|
||||
};
|
||||
dht::ThreadPool::computation().run(std::bind(search, true));
|
||||
dht::ThreadPool::computation().run(std::bind(search, false));
|
||||
}
|
||||
|
||||
void
|
||||
ArchiveAccountManager::onArchiveLoaded(
|
||||
AuthContext& ctx,
|
||||
AccountArchive&& a)
|
||||
{
|
||||
auto ethAccount = dev::KeyPair(dev::Secret(a.eth_key)).address().hex();
|
||||
fileutils::check_dir(path_.c_str(), 0700);
|
||||
|
||||
auto path = fileutils::getFullPath(path_, archivePath_);
|
||||
a.save(path, ctx.credentials ? ctx.credentials->password : "");
|
||||
|
||||
if (not a.id.second->isCA()) {
|
||||
JAMI_ERR("[Auth] trying to sign a certificate with a non-CA.");
|
||||
}
|
||||
JAMI_WARN("generating device certificate");
|
||||
|
||||
auto request = ctx.request.get();
|
||||
if (not request->verify()) {
|
||||
JAMI_ERR("[Auth] Invalid certificate request.");
|
||||
ctx.onFailure(AuthError::INVALID_ARGUMENTS, "");
|
||||
return;
|
||||
}
|
||||
|
||||
auto deviceCertificate = std::make_shared<dht::crypto::Certificate>(dht::crypto::Certificate::generate(*request, a.id));
|
||||
auto receipt = makeReceipt(a.id, *deviceCertificate, ethAccount);
|
||||
auto receiptSignature = a.id.first->sign({receipt.first.begin(), receipt.first.end()});
|
||||
|
||||
auto info = std::make_unique<AccountInfo>();
|
||||
info->identity.second = deviceCertificate;
|
||||
info->contacts = std::make_unique<ContactList>(a.id.second, path_, onChange_);
|
||||
info->contacts->setContacts(a.contacts);
|
||||
info->accountId = a.id.second->getId().toString();
|
||||
info->deviceId = deviceCertificate->getPublicKey().getId().toString();
|
||||
info->ethAccount = ethAccount;
|
||||
info->announce = std::move(receipt.second);
|
||||
info_ = std::move(info);
|
||||
|
||||
JAMI_WARN("[Auth] created new device: %s", info_->deviceId.c_str());
|
||||
ctx.onSuccess(*info_, std::move(a.config), std::move(receipt.first), std::move(receiptSignature));
|
||||
}
|
||||
|
||||
std::pair<std::vector<uint8_t>, dht::InfoHash>
|
||||
ArchiveAccountManager::computeKeys(const std::string& password, const std::string& pin, bool previous)
|
||||
{
|
||||
// Compute time seed
|
||||
auto now = std::chrono::duration_cast<std::chrono::seconds>(clock::now().time_since_epoch());
|
||||
auto tseed = now.count() / std::chrono::seconds(EXPORT_KEY_RENEWAL_TIME).count();
|
||||
if (previous)
|
||||
tseed--;
|
||||
std::stringstream ss;
|
||||
ss << std::hex << tseed;
|
||||
auto tseed_str = ss.str();
|
||||
|
||||
// Generate key for archive encryption, using PIN as the salt
|
||||
std::vector<uint8_t> salt_key;
|
||||
salt_key.reserve(pin.size() + tseed_str.size());
|
||||
salt_key.insert(salt_key.end(), pin.begin(), pin.end());
|
||||
salt_key.insert(salt_key.end(), tseed_str.begin(), tseed_str.end());
|
||||
auto key = dht::crypto::stretchKey(password, salt_key, 256/8);
|
||||
|
||||
// Generate public storage location as SHA1(key).
|
||||
auto loc = dht::InfoHash::get(key);
|
||||
|
||||
return {key, loc};
|
||||
}
|
||||
|
||||
std::pair<std::string, std::shared_ptr<dht::Value>>
|
||||
ArchiveAccountManager::makeReceipt(const dht::crypto::Identity& id, const dht::crypto::Certificate& device, const std::string& ethAccount)
|
||||
{
|
||||
JAMI_DBG("[Auth] signing device receipt");
|
||||
auto devId = device.getId();
|
||||
DeviceAnnouncement announcement;
|
||||
announcement.dev = devId;
|
||||
dht::Value ann_val {announcement};
|
||||
ann_val.sign(*id.first);
|
||||
|
||||
std::ostringstream is;
|
||||
is << "{\"id\":\"" << id.second->getId()
|
||||
<< "\",\"dev\":\"" << devId
|
||||
<< "\",\"eth\":\"" << ethAccount
|
||||
<< "\",\"announce\":\"" << base64::encode(ann_val.getPacked()) << "\"}";
|
||||
|
||||
//auto announce_ = ;
|
||||
return {is.str(), std::make_shared<dht::Value>(std::move(ann_val))};
|
||||
}
|
||||
|
||||
bool
|
||||
ArchiveAccountManager::needsMigration(const dht::crypto::Identity& id)
|
||||
{
|
||||
if (not id.second)
|
||||
return false;
|
||||
auto cert = id.second->issuer;
|
||||
while (cert) {
|
||||
if (not cert->isCA()){
|
||||
JAMI_WARN("certificate %s is not a CA, needs update.", cert->getId().toString().c_str());
|
||||
return true;
|
||||
}
|
||||
if (cert->getExpiration() < clock::now()) {
|
||||
JAMI_WARN("certificate %s is expired, needs update.", cert->getId().toString().c_str());
|
||||
return true;
|
||||
}
|
||||
cert = cert->issuer;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
ArchiveAccountManager::syncDevices()
|
||||
{
|
||||
JAMI_DBG("Building device sync from %s", info_->deviceId.c_str());
|
||||
auto sync_data = info_->contacts->getSyncData();
|
||||
|
||||
for (const auto& dev : getKnownDevices()) {
|
||||
// don't send sync data to ourself
|
||||
if (dev.first.toString() == info_->deviceId)
|
||||
continue;
|
||||
JAMI_DBG("sending device sync to %s %s", dev.second.name.c_str(), dev.first.toString().c_str());
|
||||
auto syncDeviceKey = dht::InfoHash::get("inbox:"+dev.first.toString());
|
||||
dht_->putEncrypted(syncDeviceKey, dev.first, sync_data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ArchiveAccountManager::startSync()
|
||||
{
|
||||
// Put device annoucement
|
||||
if (info_->announce) {
|
||||
auto h = dht::InfoHash(info_->accountId);
|
||||
JAMI_DBG("announcing device at %s", h.toString().c_str());
|
||||
dht_->put(h, info_->announce, dht::DoneCallback{}, {}, true);
|
||||
for (const auto& crl : info_->identity.second->issuer->getRevocationLists())
|
||||
dht_->put(h, crl, dht::DoneCallback{}, {}, true);
|
||||
dht_->listen<DeviceAnnouncement>(h, [this](DeviceAnnouncement&& dev) {
|
||||
findCertificate(dev.dev, [this](const std::shared_ptr<dht::crypto::Certificate>& crt) {
|
||||
foundAccountDevice(crt);
|
||||
});
|
||||
return true;
|
||||
});
|
||||
dht_->listen<dht::crypto::RevocationList>(h, [this](dht::crypto::RevocationList&& crl) {
|
||||
if (crl.isSignedBy(*info_->identity.second->issuer)) {
|
||||
JAMI_DBG("found CRL for account.");
|
||||
tls::CertificateStore::instance().pinRevocationList(
|
||||
info_->accountId,
|
||||
std::make_shared<dht::crypto::RevocationList>(std::move(crl)));
|
||||
}
|
||||
return true;
|
||||
});
|
||||
syncDevices();
|
||||
} else {
|
||||
JAMI_WARN("can't announce device: no annoucement...");
|
||||
}
|
||||
|
||||
auto inboxKey = dht::InfoHash::get("inbox:"+info_->deviceId);
|
||||
dht_->listen<dht::TrustRequest>(
|
||||
inboxKey,
|
||||
[this](dht::TrustRequest&& v) {
|
||||
if (v.service != DHT_TYPE_NS)
|
||||
return true;
|
||||
|
||||
findCertificate(v.from, [this, v](const std::shared_ptr<dht::crypto::Certificate>& cert) mutable {
|
||||
// check peer certificate
|
||||
dht::InfoHash peer_account;
|
||||
if (not foundPeerDevice(cert, peer_account)) {
|
||||
return;
|
||||
}
|
||||
|
||||
JAMI_WARN("Got trust request from: %s / %s", peer_account.toString().c_str(), v.from.toString().c_str());
|
||||
if (info_)
|
||||
if (info_->contacts->onTrustRequest(peer_account, v.from, time(nullptr), v.confirm, std::move(v.payload))) {
|
||||
sendTrustRequestConfirm(peer_account);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
dht_->listen<DeviceSync>(
|
||||
inboxKey,
|
||||
[this](DeviceSync&& sync) {
|
||||
// Received device sync data.
|
||||
// check device certificate
|
||||
findCertificate(sync.from, [this,sync](const std::shared_ptr<dht::crypto::Certificate>& cert) mutable {
|
||||
if (!cert or cert->getId() != sync.from) {
|
||||
JAMI_WARN("Can't find certificate for device %s", sync.from.toString().c_str());
|
||||
return;
|
||||
}
|
||||
if (not foundAccountDevice(cert))
|
||||
return;
|
||||
onSyncData(std::move(sync));
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void
|
||||
ArchiveAccountManager::onSyncData(DeviceSync&& sync)
|
||||
{
|
||||
auto sync_date = clock::time_point(clock::duration(sync.date));
|
||||
if (not info_->contacts->syncDevice(sync.from, sync_date)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sync known devices
|
||||
JAMI_DBG("[Contacts] received device sync data (%lu devices, %lu contacts)", sync.devices_known.size(), sync.peers.size());
|
||||
for (const auto& d : sync.devices_known) {
|
||||
findCertificate(d.first, [this,d](const std::shared_ptr<dht::crypto::Certificate>& crt) {
|
||||
if (not crt)
|
||||
return;
|
||||
//std::lock_guard<std::mutex> lock(deviceListMutex_);
|
||||
foundAccountDevice(crt, d.second);
|
||||
});
|
||||
}
|
||||
//saveKnownDevices();
|
||||
|
||||
// Sync contacts
|
||||
for (const auto& peer : sync.peers)
|
||||
info_->contacts->updateContact(peer.first, peer.second);
|
||||
//saveContacts();
|
||||
|
||||
// Sync trust requests
|
||||
for (const auto& tr : sync.trust_requests)
|
||||
info_->contacts->onTrustRequest(tr.first, tr.second.device, tr.second.received, false, {});
|
||||
}
|
||||
|
||||
AccountArchive
|
||||
ArchiveAccountManager::readArchive(const std::string& pwd) const
|
||||
{
|
||||
JAMI_DBG("[Auth] reading account archive");
|
||||
return AccountArchive(fileutils::getFullPath(path_, archivePath_), pwd);
|
||||
}
|
||||
|
||||
void
|
||||
ArchiveAccountManager::updateArchive(AccountArchive& archive) const
|
||||
{
|
||||
using namespace DRing::Account::ConfProperties;
|
||||
|
||||
// Keys not exported to archive
|
||||
static const auto filtered_keys = { Ringtone::PATH,
|
||||
ARCHIVE_PATH,
|
||||
RING_DEVICE_ID,
|
||||
RING_DEVICE_NAME,
|
||||
Conf::CONFIG_DHT_PORT };
|
||||
|
||||
// Keys with meaning of file path where the contents has to be exported in base64
|
||||
static const auto encoded_keys = { TLS::CA_LIST_FILE,
|
||||
TLS::CERTIFICATE_FILE,
|
||||
TLS::PRIVATE_KEY_FILE };
|
||||
|
||||
JAMI_DBG("[Auth] building account archive");
|
||||
for (const auto& it : onExportConfig_()) {
|
||||
// filter-out?
|
||||
if (std::any_of(std::begin(filtered_keys), std::end(filtered_keys),
|
||||
[&](const auto& key){ return key == it.first; }))
|
||||
continue;
|
||||
|
||||
// file contents?
|
||||
if (std::any_of(std::begin(encoded_keys), std::end(encoded_keys),
|
||||
[&](const auto& key){ return key == it.first; })) {
|
||||
try {
|
||||
archive.config.emplace(it.first, base64::encode(fileutils::loadFile(it.second)));
|
||||
} catch (...) {}
|
||||
} else
|
||||
archive.config.insert(it);
|
||||
}
|
||||
archive.contacts = info_->contacts->getContacts();
|
||||
}
|
||||
|
||||
void
|
||||
ArchiveAccountManager::saveArchive(AccountArchive& archive, const std::string& pwd)
|
||||
{
|
||||
try {
|
||||
updateArchive(archive);
|
||||
if (archivePath_.empty())
|
||||
archivePath_ = "export.gz";
|
||||
archive.save(fileutils::getFullPath(path_, archivePath_), pwd);
|
||||
//archiveHasPassword_ = not pwd.empty();
|
||||
} catch (const std::runtime_error& ex) {
|
||||
JAMI_ERR("[Auth] Can't export archive: %s", ex.what());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
ArchiveAccountManager::changePassword(const std::string& password_old, const std::string& password_new)
|
||||
{
|
||||
try {
|
||||
auto path = fileutils::getFullPath(path_, archivePath_);
|
||||
AccountArchive(path, password_old).save(path, password_new);
|
||||
return true;
|
||||
} catch (const std::exception&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::string
|
||||
generatePIN(size_t length = 8)
|
||||
{
|
||||
static constexpr const char alphabet[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
dht::crypto::random_device rd;
|
||||
std::uniform_int_distribution<size_t> dis(0, sizeof(alphabet)-2);
|
||||
std::string ret;
|
||||
ret.reserve(length);
|
||||
for (size_t i=0; i<length; i++)
|
||||
ret.push_back(alphabet[dis(rd)]);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
ArchiveAccountManager::addDevice(const std::string& password, AddDeviceCallback cb)
|
||||
{
|
||||
dht::ThreadPool::computation().run([onAsync = onAsync_, password, cb = std::move(cb)]() mutable {
|
||||
onAsync([password = std::move(password), cb = std::move(cb)](AccountManager& accountManager) mutable {
|
||||
auto& this_ = *static_cast<ArchiveAccountManager*>(&accountManager);
|
||||
|
||||
std::vector<uint8_t> key;
|
||||
dht::InfoHash loc;
|
||||
std::string pin_str;
|
||||
AccountArchive a;
|
||||
try {
|
||||
JAMI_DBG("[Auth] exporting account");
|
||||
|
||||
a = this_.readArchive(password);
|
||||
|
||||
// Generate random PIN
|
||||
pin_str = generatePIN();
|
||||
|
||||
std::tie(key, loc) = computeKeys(password, pin_str);
|
||||
} catch (const std::exception& e) {
|
||||
JAMI_ERR("[Auth] can't export account: %s", e.what());
|
||||
cb(AddDeviceResult::ERROR_CREDENTIALS, {});
|
||||
return;
|
||||
}
|
||||
// now that key and loc are computed, display to user in lowercase
|
||||
std::transform(pin_str.begin(), pin_str.end(), pin_str.begin(), ::tolower);
|
||||
try {
|
||||
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..");
|
||||
JAMI_WARN("[Auth] exporting account with PIN: %s at %s (size %zu)", pin_str.c_str(), loc.toString().c_str(), encrypted.size());
|
||||
this_.dht_->put(loc, encrypted, [cb, pin = std::move(pin_str)](bool ok) {
|
||||
JAMI_DBG("[Auth] account archive published: %s", ok ? "success" : "failure");
|
||||
if (ok)
|
||||
cb(AddDeviceResult::SUCCESS_SHOW_PIN, pin);
|
||||
else
|
||||
cb(AddDeviceResult::ERROR_NETWORK, {});
|
||||
});
|
||||
} catch (const std::exception& e) {
|
||||
JAMI_ERR("[Auth] can't export account: %s", e.what());
|
||||
cb(AddDeviceResult::ERROR_NETWORK, {});
|
||||
return;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
bool
|
||||
ArchiveAccountManager::revokeDevice(const std::string& password, const std::string& device, RevokeDeviceCallback cb)
|
||||
{
|
||||
auto fa = dht::ThreadPool::computation().getShared<AccountArchive>([this, password] { return readArchive(password); });
|
||||
findCertificate(dht::InfoHash(device),
|
||||
[fa=std::move(fa),password,device,cb,onAsync=onAsync_](const std::shared_ptr<dht::crypto::Certificate>& crt) mutable
|
||||
{
|
||||
if (not crt) {
|
||||
cb(RevokeDeviceResult::ERROR_NETWORK);
|
||||
return;
|
||||
}
|
||||
onAsync([cb, crt=std::move(crt), fa=std::move(fa), password=std::move(password)](AccountManager& accountManager) mutable {
|
||||
auto& this_ = *static_cast<ArchiveAccountManager*>(&accountManager);
|
||||
this_.info_->contacts->foundAccountDevice(crt);
|
||||
AccountArchive a;
|
||||
try {
|
||||
a = fa.get();
|
||||
} catch (...) {
|
||||
cb(RevokeDeviceResult::ERROR_CREDENTIALS);
|
||||
return;
|
||||
}
|
||||
// Add revoked device to the revocation list and resign it
|
||||
if (not a.revoked)
|
||||
a.revoked = std::make_shared<decltype(a.revoked)::element_type>();
|
||||
a.revoked->revoke(*crt);
|
||||
a.revoked->sign(a.id);
|
||||
// add to CRL cache
|
||||
tls::CertificateStore::instance().pinRevocationList(a.id.second->getId().toString(), a.revoked);
|
||||
tls::CertificateStore::instance().loadRevocations(*a.id.second);
|
||||
|
||||
this_.saveArchive(a, password);
|
||||
this_.info_->contacts->removeAccountDevice(crt->getId());
|
||||
cb(RevokeDeviceResult::SUCCESS);
|
||||
this_.syncDevices();
|
||||
});
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
ArchiveAccountManager::exportArchive(const std::string& destinationPath, const std::string& password)
|
||||
{
|
||||
try {
|
||||
// Save contacts if possible before exporting
|
||||
AccountArchive archive = readArchive(password);
|
||||
updateArchive(archive);
|
||||
archive.save(fileutils::getFullPath(path_, archivePath_), password);
|
||||
|
||||
// Export the file
|
||||
auto sourcePath = fileutils::getFullPath(path_, archivePath_);
|
||||
std::ifstream src(sourcePath, std::ios::in | std::ios::binary);
|
||||
if (!src) return false;
|
||||
std::ofstream dst(destinationPath, std::ios::out | std::ios::binary);
|
||||
dst << src.rdbuf();
|
||||
return true;
|
||||
} catch (const std::runtime_error& ex) {
|
||||
JAMI_ERR("[Auth] Can't export archive: %s", ex.what());
|
||||
return false;
|
||||
} catch (...) {
|
||||
JAMI_ERR("[Auth] Can't export archive: can't read archive");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
ArchiveAccountManager::findCertificate(const dht::InfoHash& h, std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb)
|
||||
{
|
||||
if (auto cert = tls::CertificateStore::instance().getCertificate(h.toString())) {
|
||||
if (cb)
|
||||
cb(cert);
|
||||
} else {
|
||||
dht_->findCertificate(h, [cb](const std::shared_ptr<dht::crypto::Certificate>& crt) {
|
||||
if (crt)
|
||||
tls::CertificateStore::instance().pinCertificate(crt);
|
||||
if (cb)
|
||||
cb(crt);
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
#if HAVE_RINGNS
|
||||
void
|
||||
ArchiveAccountManager::registerName(const std::string& password, const std::string& name, RegistrationCallback cb)
|
||||
{
|
||||
std::string signedName;
|
||||
auto nameLowercase {name};
|
||||
std::transform(nameLowercase.begin(), nameLowercase.end(), nameLowercase.begin(), ::tolower);
|
||||
std::string publickey;
|
||||
std::string accountId;
|
||||
std::string ethAccount;
|
||||
|
||||
try {
|
||||
auto archive = readArchive(password);
|
||||
auto privateKey = archive.id.first;
|
||||
auto pk = privateKey->getPublicKey();
|
||||
publickey = pk.toString();
|
||||
accountId = pk.getId().toString();
|
||||
signedName = base64::encode(privateKey->sign(std::vector<uint8_t>(nameLowercase.begin(), nameLowercase.end())));
|
||||
ethAccount = dev::KeyPair(dev::Secret(archive.eth_key)).address().hex();
|
||||
} catch (const std::exception& e) {
|
||||
//JAMI_ERR("[Auth] can't export account: %s", e.what());
|
||||
cb(NameDirectory::RegistrationResponse::invalidCredentials);
|
||||
return;
|
||||
}
|
||||
|
||||
nameDir_.get().registerName(accountId, nameLowercase, ethAccount, cb, signedName, publickey);
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
101
src/jamidht/archive_account_manager.h
Normal file
101
src/jamidht/archive_account_manager.h
Normal file
@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright (C) 2014-2019 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 "account_manager.h"
|
||||
|
||||
namespace jami {
|
||||
|
||||
class ArchiveAccountManager : public AccountManager {
|
||||
public:
|
||||
using OnExportConfig = std::function<std::map<std::string, std::string>()>;
|
||||
|
||||
ArchiveAccountManager(
|
||||
const std::string& path,
|
||||
std::shared_ptr<dht::DhtRunner> dht,
|
||||
OnAsync&& onAsync,
|
||||
OnExportConfig&& onExportConfig,
|
||||
std::string archivePath,
|
||||
const std::string& nameServer)
|
||||
: AccountManager(path, std::move(onAsync), std::move(dht), nameServer)
|
||||
, onExportConfig_(std::move(onExportConfig))
|
||||
, archivePath_(std::move(archivePath))
|
||||
{};
|
||||
|
||||
struct ArchiveAccountCredentials : AccountCredentials {
|
||||
std::string archivePath;
|
||||
in_port_t dhtPort;
|
||||
std::vector<std::string> dhtBootstrap;
|
||||
dht::crypto::Identity updateIdentity;
|
||||
};
|
||||
|
||||
void initAuthentication(
|
||||
CertRequest request,
|
||||
std::unique_ptr<AccountCredentials> credentials,
|
||||
AuthSuccessCallback onSuccess,
|
||||
AuthFailureCallback onFailure,
|
||||
OnChangeCallback onChange) override;
|
||||
|
||||
void startSync() override;
|
||||
|
||||
bool changePassword(const std::string& password_old, const std::string& password_new) override;
|
||||
|
||||
void syncDevices() override;
|
||||
void onSyncData(DeviceSync&& device);
|
||||
|
||||
bool findCertificate(const dht::InfoHash& h, std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb = {}) override;
|
||||
|
||||
void addDevice(const std::string& password, AddDeviceCallback) override;
|
||||
bool revokeDevice(const std::string& password, const std::string& device, RevokeDeviceCallback) override;
|
||||
bool exportArchive(const std::string& destinationPath, const std::string& password);
|
||||
|
||||
#if HAVE_RINGNS
|
||||
/*void lookupName(const std::string& name, LookupCallback cb) override;
|
||||
void lookupAddress(const std::string& address, LookupCallback cb) override;*/
|
||||
void registerName(const std::string& password, const std::string& name, RegistrationCallback cb) override;
|
||||
#endif
|
||||
|
||||
private:
|
||||
struct DhtLoadContext;
|
||||
struct AuthContext {
|
||||
CertRequest request;
|
||||
//std::unique_ptr<dht::crypto::CertificateRequest> request;
|
||||
std::unique_ptr<ArchiveAccountCredentials> credentials;
|
||||
std::unique_ptr<DhtLoadContext> dhtContext;
|
||||
AuthSuccessCallback onSuccess;
|
||||
AuthFailureCallback onFailure;
|
||||
};
|
||||
|
||||
void createAccount(const std::shared_ptr<AuthContext>& ctx);
|
||||
std::pair<std::string, std::shared_ptr<dht::Value>> makeReceipt(const dht::crypto::Identity& id, const dht::crypto::Certificate& device, const std::string& ethAccount);
|
||||
void updateArchive(AccountArchive& content/*, const ContactList& syncData*/) const;
|
||||
void saveArchive(AccountArchive& content, const std::string& pwd);
|
||||
AccountArchive readArchive(const std::string& pwd) const;
|
||||
static std::pair<std::vector<uint8_t>, dht::InfoHash> computeKeys(const std::string& password, const std::string& pin, bool previous=false);
|
||||
bool updateCertificates(AccountArchive& archive, dht::crypto::Identity& device);
|
||||
static bool needsMigration(const dht::crypto::Identity& id);
|
||||
|
||||
void loadFromFile(const std::shared_ptr<AuthContext>& ctx);
|
||||
void loadFromDHT(const std::shared_ptr<AuthContext>& ctx);
|
||||
void onArchiveLoaded(AuthContext& ctx, AccountArchive&& a);
|
||||
|
||||
OnExportConfig onExportConfig_;
|
||||
std::string archivePath_;
|
||||
};
|
||||
|
||||
}
|
@ -347,6 +347,28 @@ ContactList::saveKnownDevices() const
|
||||
msgpack::pack(file, devices);
|
||||
}
|
||||
|
||||
void
|
||||
ContactList::foundAccountDevice(const dht::InfoHash& device, const std::string& name, const time_point& updated)
|
||||
{
|
||||
// insert device
|
||||
auto it = knownDevices_.emplace(device, KnownDevice{{}, name, updated});
|
||||
if (it.second) {
|
||||
JAMI_DBG("[Contacts] Found account device: %s %s", name.c_str(),
|
||||
device.toString().c_str());
|
||||
saveKnownDevices();
|
||||
callbacks_.devicesChanged();
|
||||
} else {
|
||||
// update device name
|
||||
if (not name.empty() and it.first->second.name != name) {
|
||||
JAMI_DBG("[Contacts] updating device name: %s %s", name.c_str(),
|
||||
device.toString().c_str());
|
||||
it.first->second.name = name;
|
||||
saveKnownDevices();
|
||||
callbacks_.devicesChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
ContactList::foundAccountDevice(const std::shared_ptr<dht::crypto::Certificate>& crt, const std::string& name, const time_point& updated)
|
||||
{
|
||||
@ -354,8 +376,9 @@ ContactList::foundAccountDevice(const std::shared_ptr<dht::crypto::Certificate>&
|
||||
return false;
|
||||
|
||||
// match certificate chain
|
||||
if (not accountTrust_.verify(*crt)) {
|
||||
JAMI_WARN("[Contacts] Found invalid account device: %s", crt->getId().toString().c_str());
|
||||
auto verifyResult = accountTrust_.verify(*crt);
|
||||
if (not verifyResult) {
|
||||
JAMI_WARN("[Contacts] Found invalid account device: %s: %s", crt->getId().toString().c_str(), verifyResult.toString().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -93,6 +93,7 @@ public:
|
||||
|
||||
/* Devices */
|
||||
const std::map<dht::InfoHash, KnownDevice>& getKnownDevices() const { return knownDevices_; }
|
||||
void foundAccountDevice(const dht::InfoHash& device, const std::string& name = {}, const time_point& last_sync = time_point::min());
|
||||
bool foundAccountDevice(const std::shared_ptr<dht::crypto::Certificate>& crt, const std::string& name = {}, const time_point& last_sync = time_point::min());
|
||||
bool removeAccountDevice(const dht::InfoHash& device);
|
||||
void setAccountDeviceName(const dht::InfoHash& device, const std::string& name);
|
||||
|
@ -34,7 +34,8 @@
|
||||
#include "jami_contact.h"
|
||||
#include "configkeys.h"
|
||||
#include "contact_list.h"
|
||||
#include "account_manager.h"
|
||||
#include "archive_account_manager.h"
|
||||
#include "server_account_manager.h"
|
||||
|
||||
#include "sip/sdp.h"
|
||||
#include "sip/sipvoiplink.h"
|
||||
@ -912,19 +913,28 @@ JamiAccount::loadAccount(const std::string& archive_password, const std::string&
|
||||
};
|
||||
|
||||
try {
|
||||
accountManager_.reset(new ArchiveAccountManager(getPath(),
|
||||
dht_,
|
||||
[w = weak()](AccountManager::AsyncUser&& cb) {
|
||||
if (auto this_ = w.lock())
|
||||
cb(*this_->accountManager_);
|
||||
},
|
||||
[this]() { return getAccountDetails(); },
|
||||
archivePath_.empty() ? "archive.gz" : archivePath_,
|
||||
nameServer_));
|
||||
auto onAsync = [w = weak()](AccountManager::AsyncUser&& cb) {
|
||||
if (auto this_ = w.lock())
|
||||
cb(*this_->accountManager_);
|
||||
};
|
||||
if (managerUri_.empty()) {
|
||||
accountManager_.reset(new ArchiveAccountManager(getPath(),
|
||||
dht_,
|
||||
onAsync,
|
||||
[this]() { return getAccountDetails(); },
|
||||
archivePath_.empty() ? "archive.gz" : archivePath_,
|
||||
nameServer_));
|
||||
} else {
|
||||
accountManager_.reset(new ServerAccountManager(getPath(),
|
||||
dht_,
|
||||
onAsync,
|
||||
managerUri_,
|
||||
nameServer_));
|
||||
}
|
||||
|
||||
auto id = accountManager_->loadIdentity(tlsCertificateFile_, tlsPrivateKeyFile_, tlsPassword_);
|
||||
bool hasArchive = not archivePath_.empty()
|
||||
and fileutils::isFile(fileutils::getFullPath(idPath_, archivePath_));
|
||||
/*bool hasArchive = not archivePath_.empty()
|
||||
and fileutils::isFile(fileutils::getFullPath(idPath_, archivePath_));*/
|
||||
|
||||
if (auto info = accountManager_->useIdentity(id, receipt_, receiptSignature_, std::move(callbacks))) {
|
||||
// normal loading path
|
||||
@ -936,72 +946,92 @@ JamiAccount::loadAccount(const std::string& archive_password, const std::string&
|
||||
}
|
||||
}
|
||||
else if (isEnabled()) {
|
||||
setRegistrationState(RegistrationState::INITIALIZING);
|
||||
auto fDeviceKey = dht::ThreadPool::computation().getShared<std::shared_ptr<dht::crypto::PrivateKey>>([](){
|
||||
return std::make_shared<dht::crypto::PrivateKey>(dht::crypto::PrivateKey::generate());
|
||||
});
|
||||
setRegistrationState(RegistrationState::INITIALIZING);
|
||||
auto fDeviceKey = dht::ThreadPool::computation().getShared<std::shared_ptr<dht::crypto::PrivateKey>>([](){
|
||||
return std::make_shared<dht::crypto::PrivateKey>(dht::crypto::PrivateKey::generate());
|
||||
});
|
||||
|
||||
auto fReq = dht::ThreadPool::computation().get<std::unique_ptr<dht::crypto::CertificateRequest>>([fDeviceKey]{
|
||||
auto request = std::make_unique<dht::crypto::CertificateRequest>();
|
||||
request->setName("Jami device");
|
||||
auto deviceKey = fDeviceKey.get();
|
||||
request->setUID(deviceKey->getPublicKey().getId().toString());
|
||||
request->sign(*deviceKey);
|
||||
return request;
|
||||
});
|
||||
auto fReq = dht::ThreadPool::computation().get<std::unique_ptr<dht::crypto::CertificateRequest>>([fDeviceKey]{
|
||||
auto request = std::make_unique<dht::crypto::CertificateRequest>();
|
||||
request->setName("Jami device");
|
||||
auto deviceKey = fDeviceKey.get();
|
||||
request->setUID(deviceKey->getPublicKey().getId().toString());
|
||||
request->sign(*deviceKey);
|
||||
return request;
|
||||
});
|
||||
|
||||
auto creds = std::make_unique<ArchiveAccountManager::ArchiveAccountCredentials>();
|
||||
creds->archivePath = archivePath_.empty() ? "archive.gz" : archivePath_;
|
||||
std::unique_ptr<AccountManager::AccountCredentials> creds;
|
||||
if (managerUri_.empty()) {
|
||||
auto acreds = std::make_unique<ArchiveAccountManager::ArchiveAccountCredentials>();
|
||||
acreds->archivePath = archivePath_.empty() ? "archive.gz" : archivePath_;
|
||||
if (not archive_path.empty()) {
|
||||
creds->scheme = "file";
|
||||
creds->uri = archive_path;
|
||||
acreds->scheme = "file";
|
||||
acreds->uri = archive_path;
|
||||
}
|
||||
else if (not archive_pin.empty()) {
|
||||
creds->scheme = "dht";
|
||||
creds->uri = archive_pin;
|
||||
creds->dhtBootstrap = loadBootstrap();
|
||||
creds->dhtPort = (in_port_t)dhtPortUsed_;
|
||||
acreds->scheme = "dht";
|
||||
acreds->uri = archive_pin;
|
||||
acreds->dhtBootstrap = loadBootstrap();
|
||||
acreds->dhtPort = (in_port_t)dhtPortUsed_;
|
||||
}
|
||||
creds->password = archive_password;
|
||||
accountManager_->initAuthentication(
|
||||
std::move(fReq),
|
||||
std::move(creds),
|
||||
[this, fDeviceKey](const AccountInfo& info,
|
||||
const std::map<std::string, std::string>& config,
|
||||
std::string&& receipt,
|
||||
std::vector<uint8_t>&& receipt_signature) {
|
||||
JAMI_WARN("Auth success !");
|
||||
creds = std::move(acreds);
|
||||
} else {
|
||||
auto screds = std::make_unique<ServerAccountManager::ServerAccountCredentials>();
|
||||
screds->username = username_;
|
||||
creds = std::move(screds);
|
||||
}
|
||||
creds->password = archive_password;
|
||||
|
||||
fileutils::check_dir(idPath_.c_str(), 0700);
|
||||
accountManager_->initAuthentication(
|
||||
std::move(fReq),
|
||||
std::move(creds),
|
||||
[this, fDeviceKey](const AccountInfo& info,
|
||||
const std::map<std::string, std::string>& config,
|
||||
std::string&& receipt,
|
||||
std::vector<uint8_t>&& receipt_signature)
|
||||
{
|
||||
JAMI_WARN("Auth success !");
|
||||
|
||||
// save the chain including CA
|
||||
auto id = info.identity;
|
||||
id.first = std::move(fDeviceKey.get());
|
||||
std::tie(tlsPrivateKeyFile_, tlsCertificateFile_) = saveIdentity(id, idPath_, "ring_device");
|
||||
id_ = std::move(id);
|
||||
tlsPassword_ = {};
|
||||
fileutils::check_dir(idPath_.c_str(), 0700);
|
||||
|
||||
username_ = RING_URI_PREFIX+info.accountId;
|
||||
// save the chain including CA
|
||||
auto id = info.identity;
|
||||
id.first = std::move(fDeviceKey.get());
|
||||
std::tie(tlsPrivateKeyFile_, tlsCertificateFile_) = saveIdentity(id, idPath_, "ring_device");
|
||||
id_ = std::move(id);
|
||||
tlsPassword_ = {};
|
||||
|
||||
ringDeviceName_ = ip_utils::getDeviceName();
|
||||
if (ringDeviceName_.empty())
|
||||
ringDeviceName_ = info.deviceId.substr(8);
|
||||
username_ = RING_URI_PREFIX+info.accountId;
|
||||
|
||||
receipt_ = std::move(receipt);
|
||||
receiptSignature_ = std::move(receipt_signature);
|
||||
accountManager_->foundAccountDevice(info.identity.second, ringDeviceName_, clock::now());
|
||||
setRegistrationState(RegistrationState::UNREGISTERED);
|
||||
ringDeviceName_ = ip_utils::getDeviceName();
|
||||
if (ringDeviceName_.empty())
|
||||
ringDeviceName_ = info.deviceId.substr(8);
|
||||
|
||||
saveConfig();
|
||||
doRegister();
|
||||
}, [this](AccountManager::AuthError error, const std::string& message) {
|
||||
JAMI_WARN("Auth error: %d %s", (int)error, message.c_str());
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(configurationMutex_);
|
||||
setRegistrationState(RegistrationState::ERROR_GENERIC);
|
||||
}
|
||||
Manager::instance().removeAccount(getAccountID());
|
||||
}, std::move(callbacks));
|
||||
auto nameServerIt = config.find(DRing::Account::ConfProperties::RingNS::ACCOUNT);
|
||||
if (nameServerIt != config.end()) {
|
||||
nameServer_ = nameServerIt->second;
|
||||
}
|
||||
auto displayNameIt = config.find(DRing::Account::ConfProperties::DISPLAYNAME);
|
||||
if (displayNameIt != config.end()) {
|
||||
displayName_ = displayNameIt->second;
|
||||
}
|
||||
|
||||
receipt_ = std::move(receipt);
|
||||
receiptSignature_ = std::move(receipt_signature);
|
||||
accountManager_->foundAccountDevice(info.identity.second, ringDeviceName_, clock::now());
|
||||
setRegistrationState(RegistrationState::UNREGISTERED);
|
||||
|
||||
saveConfig();
|
||||
doRegister();
|
||||
}, [this](AccountManager::AuthError error, const std::string& message)
|
||||
{
|
||||
JAMI_WARN("Auth error: %d %s", (int)error, message.c_str());
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(configurationMutex_);
|
||||
setRegistrationState(RegistrationState::ERROR_GENERIC);
|
||||
}
|
||||
Manager::instance().removeAccount(getAccountID());
|
||||
}, std::move(callbacks));
|
||||
}
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
@ -1037,6 +1067,9 @@ JamiAccount::setAccountDetails(const std::map<std::string, std::string>& details
|
||||
dhtPort_ = getRandomEvenPort(DHT_PORT_RANGE);
|
||||
dhtPortUsed_ = dhtPort_;
|
||||
|
||||
parseString(details, DRing::Account::ConfProperties::MANAGER_URI, managerUri_);
|
||||
parseString(details, DRing::Account::ConfProperties::USERNAME, username_);
|
||||
|
||||
std::string archive_password;
|
||||
std::string archive_pin;
|
||||
std::string archive_path;
|
||||
@ -1119,6 +1152,7 @@ JamiAccount::getAccountDetails() const
|
||||
a.emplace(DRing::Account::ConfProperties::PROXY_ENABLED, proxyEnabled_ ? TRUE_STR : FALSE_STR);
|
||||
a.emplace(DRing::Account::ConfProperties::PROXY_SERVER, proxyServer_);
|
||||
a.emplace(DRing::Account::ConfProperties::PROXY_PUSH_TOKEN, deviceKey_);
|
||||
a.emplace(DRing::Account::ConfProperties::MANAGER_URI, managerUri_);
|
||||
#if HAVE_RINGNS
|
||||
a.emplace(DRing::Account::ConfProperties::RingNS::URI, nameServer_);
|
||||
#endif
|
||||
@ -1590,7 +1624,7 @@ JamiAccount::doRegister_()
|
||||
std::lock_guard<std::mutex> lock(configurationMutex_);
|
||||
|
||||
try {
|
||||
if (not accountManager_->getInfo())
|
||||
if (not accountManager_ or not accountManager_->getInfo())
|
||||
throw std::runtime_error("No identity configured for this account.");
|
||||
|
||||
loadTreatedCalls();
|
||||
@ -2091,7 +2125,7 @@ JamiAccount::isMessageTreated(unsigned int id)
|
||||
std::map<std::string, std::string>
|
||||
JamiAccount::getKnownDevices() const
|
||||
{
|
||||
if (not accountManager_->getInfo())
|
||||
if (not accountManager_ or not accountManager_->getInfo())
|
||||
return {};
|
||||
std::map<std::string, std::string> ids;
|
||||
for (auto& d : accountManager_->getKnownDevices()) {
|
||||
@ -2184,7 +2218,7 @@ JamiAccount::generateDhParams()
|
||||
MatchRank
|
||||
JamiAccount::matches(const std::string &userName, const std::string &server) const
|
||||
{
|
||||
if (not accountManager_->getInfo())
|
||||
if (not accountManager_ or not accountManager_->getInfo())
|
||||
return MatchRank::NONE;
|
||||
|
||||
if (userName == accountManager_->getInfo()->accountId || server == accountManager_->getInfo()->accountId || userName == accountManager_->getInfo()->deviceId) {
|
||||
|
@ -85,7 +85,6 @@ public:
|
||||
constexpr static const in_port_t DHT_DEFAULT_PORT = 4222;
|
||||
constexpr static const char* const DHT_DEFAULT_BOOTSTRAP = "bootstrap.jami.net";
|
||||
constexpr static const char* const DHT_DEFAULT_PROXY = "dhtproxy.jami.net:[80-100]";
|
||||
constexpr static const char* const DHT_TYPE_NS = "cx.ring";
|
||||
|
||||
/* constexpr */ static const std::pair<uint16_t, uint16_t> DHT_PORT_RANGE;
|
||||
|
||||
@ -633,6 +632,8 @@ private:
|
||||
*/
|
||||
std::string receivedParameter_ {};
|
||||
|
||||
std::string managerUri_ {};
|
||||
|
||||
/**
|
||||
* Optional: "rport" parameter from VIA header
|
||||
*/
|
||||
|
@ -55,7 +55,8 @@ public:
|
||||
alreadyTaken,
|
||||
error,
|
||||
incompleteRequest,
|
||||
signatureVerificationFailed
|
||||
signatureVerificationFailed,
|
||||
unsupported
|
||||
};
|
||||
|
||||
using LookupCallback = std::function<void(const std::string& result, Response response)>;
|
||||
|
246
src/jamidht/server_account_manager.cpp
Normal file
246
src/jamidht/server_account_manager.cpp
Normal file
@ -0,0 +1,246 @@
|
||||
/*
|
||||
* Copyright (C) 2014-2019 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 "server_account_manager.h"
|
||||
#include "base64.h"
|
||||
#include "dring/account_const.h"
|
||||
|
||||
#include <opendht/http.h>
|
||||
#include <opendht/log.h>
|
||||
|
||||
#include "manager.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace jami {
|
||||
|
||||
using Request = dht::http::Request;
|
||||
|
||||
static const std::string PATH_AUTH = "/api/auth";
|
||||
static const std::string PATH_DEVICE_REGISTER = PATH_AUTH + "/device/register";
|
||||
static const std::string PATH_DEVICES = PATH_AUTH + "/devices";
|
||||
|
||||
constexpr const char* const HTTPS_PROTO {"https"};
|
||||
|
||||
ServerAccountManager::ServerAccountManager(
|
||||
const std::string& path,
|
||||
std::shared_ptr<dht::DhtRunner> dht,
|
||||
OnAsync&& onAsync,
|
||||
const std::string& managerHostname,
|
||||
const std::string& nameServer)
|
||||
: AccountManager(path, std::move(onAsync), std::move(dht), nameServer)
|
||||
, managerHostname_(managerHostname)
|
||||
, logger_(std::make_shared<dht::Logger>(
|
||||
[](char const* m, va_list args) { Logger::vlog(LOG_ERR, nullptr, 0, true, m, args); },
|
||||
[](char const* m, va_list args) { Logger::vlog(LOG_WARNING, nullptr, 0, true, m, args); },
|
||||
[](char const* m, va_list args) { Logger::vlog(LOG_DEBUG, nullptr, 0, true, m, args); }))
|
||||
{};
|
||||
|
||||
void
|
||||
ServerAccountManager::initAuthentication(
|
||||
CertRequest csrRequest,
|
||||
std::unique_ptr<AccountCredentials> credentials,
|
||||
AuthSuccessCallback onSuccess,
|
||||
AuthFailureCallback onFailure,
|
||||
OnChangeCallback onChange)
|
||||
{
|
||||
auto ctx = std::make_shared<AuthContext>();
|
||||
ctx->request = std::move(csrRequest);
|
||||
ctx->credentials = dynamic_unique_cast<ServerAccountCredentials>(std::move(credentials));
|
||||
ctx->onSuccess = std::move(onSuccess);
|
||||
ctx->onFailure = std::move(onFailure);
|
||||
if (not ctx->credentials or ctx->credentials->username.empty()) {
|
||||
onFailure(AuthError::INVALID_ARGUMENTS, "invalid credentials");
|
||||
return;
|
||||
}
|
||||
|
||||
onChange_ = std::move(onChange);
|
||||
|
||||
const std::string url = managerHostname_ + PATH_DEVICE_REGISTER;
|
||||
JAMI_WARN("[Auth] authentication with: %s %s to %s", ctx->credentials->username.c_str(), ctx->credentials->password.c_str(), url.c_str());
|
||||
auto request = std::make_shared<Request>(*Manager::instance().ioContext(), url, logger_);
|
||||
auto reqid = request->id();
|
||||
request->set_connection_type(restinio::http_connection_header_t::close);
|
||||
request->set_method(restinio::http_method_post());
|
||||
request->set_auth(ctx->credentials->username, ctx->credentials->password);
|
||||
{
|
||||
std::stringstream ss;
|
||||
auto csr = ctx->request.get()->toString();
|
||||
string_replace(csr, "\n", "\\n");
|
||||
string_replace(csr, "\r", "\\r");
|
||||
ss << "{\"csrRequest\":\"" << csr << "\"}";
|
||||
JAMI_WARN("[Auth] Sending request: %s", csr.c_str());
|
||||
request->set_body(ss.str());
|
||||
}
|
||||
|
||||
request->set_header_field(restinio::http_field_t::user_agent, "Jami");
|
||||
request->set_header_field(restinio::http_field_t::accept, "application/json");
|
||||
request->set_header_field(restinio::http_field_t::content_type, "application/json");
|
||||
request->add_on_state_change_callback([reqid, ctx, onAsync = onAsync_]
|
||||
(Request::State state, const dht::http::Response& response){
|
||||
|
||||
JAMI_ERR("[Auth] Got server response: %d", (int)state);
|
||||
if (state != Request::State::DONE)
|
||||
return;
|
||||
if (response.status_code >= 400 && response.status_code < 500)
|
||||
ctx->onFailure(AuthError::INVALID_ARGUMENTS, "");
|
||||
else if (response.status_code < 200 || response.status_code > 299)
|
||||
ctx->onFailure(AuthError::INVALID_ARGUMENTS, "");
|
||||
else {
|
||||
try {
|
||||
Json::Value json;
|
||||
std::string err;
|
||||
Json::CharReaderBuilder rbuilder;
|
||||
auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
|
||||
if (!reader->parse(response.body.data(), response.body.data() + response.body.size(), &json, &err)){
|
||||
JAMI_ERR("[Auth] Can't parse server response: %s", err.c_str());
|
||||
ctx->onFailure(AuthError::SERVER_ERROR, "Can't parse server response");
|
||||
return;
|
||||
}
|
||||
JAMI_WARN("[Auth] Got server response: %s", response.body.c_str());
|
||||
|
||||
auto certStr = json["x509Certificate"].asString();
|
||||
string_replace(certStr, "\\n", "\n");
|
||||
string_replace(certStr, "\\r", "\r");
|
||||
auto cert = std::make_shared<dht::crypto::Certificate>(certStr);
|
||||
|
||||
auto accountCert = cert->issuer;
|
||||
if (not accountCert) {
|
||||
JAMI_ERR("[Auth] Can't parse certificate: no issuer");
|
||||
ctx->onFailure(AuthError::SERVER_ERROR, "Invalid certificate from server");
|
||||
return;
|
||||
}
|
||||
auto receipt = json["receipt"].asString();
|
||||
Json::Value receiptJson;
|
||||
auto receiptReader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
|
||||
if (!receiptReader->parse(receipt.data(), receipt.data() + receipt.size(), &receiptJson, &err)){
|
||||
JAMI_ERR("[Auth] Can't parse receipt from server: %s", err.c_str());
|
||||
ctx->onFailure(AuthError::SERVER_ERROR, "Can't parse receipt from server");
|
||||
return;
|
||||
}
|
||||
onAsync([reqid, ctx,
|
||||
json=std::move(json),
|
||||
receiptJson=std::move(receiptJson),
|
||||
cert,
|
||||
accountCert,
|
||||
receipt=std::move(receipt)] (AccountManager& accountManager) mutable
|
||||
{
|
||||
auto& this_ = *static_cast<ServerAccountManager*>(&accountManager);
|
||||
auto receiptSignature = base64::decode(json["receiptSignature"].asString());
|
||||
|
||||
auto info = std::make_unique<AccountInfo>();
|
||||
info->identity.second = cert;
|
||||
info->contacts = std::make_unique<ContactList>(accountCert, this_.path_, this_.onChange_);
|
||||
//info->contacts->setContacts(a.contacts);
|
||||
info->accountId = accountCert->getId().toString();
|
||||
info->deviceId = cert->getPublicKey().getId().toString();
|
||||
info->ethAccount = receiptJson["eth"].asString();
|
||||
info->announce = parseAnnounce(receiptJson["announce"].asString(), info->accountId, info->deviceId);
|
||||
if (not info->announce) {
|
||||
ctx->onFailure(AuthError::SERVER_ERROR, "Can't parse announce from server");
|
||||
}
|
||||
|
||||
this_.creds_ = std::move(ctx->credentials);
|
||||
this_.info_ = std::move(info);
|
||||
//auto info = useIdentity();
|
||||
std::map<std::string, std::string> config;
|
||||
if (json.isMember("nameServer")) {
|
||||
config.emplace(DRing::Account::ConfProperties::RingNS::ACCOUNT, json["nameServer"].asString());
|
||||
}
|
||||
if (json.isMember("displayName")) {
|
||||
config.emplace(DRing::Account::ConfProperties::DISPLAYNAME, json["displayName"].asString());
|
||||
}
|
||||
|
||||
ctx->onSuccess(*this_.info_, std::move(config), std::move(receipt), std::move(receiptSignature));
|
||||
this_.syncDevices();
|
||||
this_.requests_.erase(reqid);
|
||||
});
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
JAMI_ERR("Error when loading account: %s", e.what());
|
||||
ctx->onFailure(AuthError::NETWORK, "");
|
||||
}
|
||||
}
|
||||
});
|
||||
request->send();
|
||||
requests_[reqid] = std::move(request);
|
||||
}
|
||||
|
||||
void
|
||||
ServerAccountManager::syncDevices()
|
||||
{
|
||||
if (not creds_)
|
||||
return;
|
||||
const std::string url = managerHostname_ + PATH_DEVICES;
|
||||
JAMI_WARN("[Auth] syncDevices with: %s %s to %s", creds_->username.c_str(), creds_->password.c_str(), url.c_str());
|
||||
auto request = std::make_shared<Request>(*Manager::instance().ioContext(), url, logger_);
|
||||
auto reqid = request->id();
|
||||
request->set_connection_type(restinio::http_connection_header_t::close);
|
||||
request->set_method(restinio::http_method_get());
|
||||
request->set_auth(creds_->username, creds_->password);
|
||||
request->set_header_field(restinio::http_field_t::user_agent, "Jami");
|
||||
request->set_header_field(restinio::http_field_t::accept, "application/json");
|
||||
request->set_header_field(restinio::http_field_t::content_type, "application/json");
|
||||
|
||||
request->add_on_state_change_callback([reqid, onAsync = onAsync_]
|
||||
(Request::State state, const dht::http::Response& response){
|
||||
onAsync([reqid, state, response] (AccountManager& accountManager) {
|
||||
JAMI_ERR("[Auth] Got server response: %d", (int)state);
|
||||
auto& this_ = *static_cast<ServerAccountManager*>(&accountManager);
|
||||
if (state != Request::State::DONE)
|
||||
return;
|
||||
if (response.status_code >= 200 || response.status_code < 300) {
|
||||
try {
|
||||
Json::Value json;
|
||||
std::string err;
|
||||
Json::CharReaderBuilder rbuilder;
|
||||
auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
|
||||
if (!reader->parse(response.body.data(), response.body.data() + response.body.size(), &json, &err)){
|
||||
JAMI_ERR("[Auth] Can't parse server response: %s", err.c_str());
|
||||
return;
|
||||
}
|
||||
JAMI_WARN("[Auth] Got server response: %s", response.body.c_str());
|
||||
if (not json.isArray()) {
|
||||
JAMI_ERR("[Auth] Can't parse server response: not an array");
|
||||
return;
|
||||
}
|
||||
for (unsigned i=0, n=json.size(); i<n; i++) {
|
||||
const auto& e = json[i];
|
||||
dht::InfoHash deviceId(e["deviceId"].asString());
|
||||
if (deviceId) {
|
||||
this_.info_->contacts->foundAccountDevice(deviceId, e["alias"].asString(), clock::now());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
JAMI_ERR("Error when loading device list: %s", e.what());
|
||||
}
|
||||
}
|
||||
this_.requests_.erase(reqid);
|
||||
});
|
||||
});
|
||||
request->send();
|
||||
requests_[reqid] = std::move(request);
|
||||
}
|
||||
|
||||
void
|
||||
ServerAccountManager::registerName(const std::string& password, const std::string& name, RegistrationCallback cb)
|
||||
{
|
||||
cb(NameDirectory::RegistrationResponse::unsupported);
|
||||
}
|
||||
|
||||
}
|
81
src/jamidht/server_account_manager.h
Normal file
81
src/jamidht/server_account_manager.h
Normal file
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (C) 2014-2019 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 "account_manager.h"
|
||||
|
||||
namespace jami {
|
||||
|
||||
class ServerAccountManager : public AccountManager {
|
||||
public:
|
||||
ServerAccountManager(
|
||||
const std::string& path,
|
||||
std::shared_ptr<dht::DhtRunner> dht,
|
||||
OnAsync&& onAsync,
|
||||
const std::string& managerHostname,
|
||||
const std::string& nameServer);
|
||||
|
||||
struct ServerAccountCredentials : AccountCredentials {
|
||||
std::string username;
|
||||
std::shared_ptr<dht::crypto::Certificate> ca;
|
||||
};
|
||||
|
||||
void initAuthentication(
|
||||
CertRequest request,
|
||||
std::unique_ptr<AccountCredentials> credentials,
|
||||
AuthSuccessCallback onSuccess,
|
||||
AuthFailureCallback onFailure,
|
||||
OnChangeCallback onChange) override;
|
||||
|
||||
bool changePassword(const std::string& password_old, const std::string& password_new) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void syncDevices();
|
||||
|
||||
bool findCertificate(const dht::InfoHash& h, std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb = {}) {
|
||||
return false;
|
||||
}
|
||||
/*
|
||||
void lookupName(const std::string& name, LookupCallback cb) {
|
||||
|
||||
}
|
||||
|
||||
void lookupAddress(const std::string& address, LookupCallback cb) {
|
||||
|
||||
}*/
|
||||
|
||||
void registerName(const std::string& password, const std::string& name, RegistrationCallback cb);
|
||||
|
||||
private:
|
||||
struct AuthContext {
|
||||
CertRequest request;
|
||||
//std::unique_ptr<dht::crypto::CertificateRequest> request;
|
||||
std::unique_ptr<ServerAccountCredentials> credentials;
|
||||
//std::unique_ptr<DhtLoadContext> dhtContext;
|
||||
AuthSuccessCallback onSuccess;
|
||||
AuthFailureCallback onFailure;
|
||||
};
|
||||
|
||||
const std::string managerHostname_;
|
||||
std::shared_ptr<dht::Logger> logger_;
|
||||
std::map<unsigned int /*id*/, std::shared_ptr<dht::http::Request>> requests_;
|
||||
std::unique_ptr<ServerAccountCredentials> creds_;
|
||||
};
|
||||
|
||||
}
|
Reference in New Issue
Block a user