jamiaccount.cpp 145 KB
Newer Older
Adrien Béraud's avatar
Adrien Béraud committed
1
/*
2
 *  Copyright (C) 2014-2019 Savoir-faire Linux Inc.
Adrien Béraud's avatar
Adrien Béraud committed
3 4
 *
 *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
5
 *  Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
6
 *  Author: Simon Désaulniers <simon.desaulniers@savoirfairelinux.com>
Nicolas Jager's avatar
Nicolas Jager committed
7
 *  Author: Nicolas Jäger <nicolas.jager@savoirfairelinux.com>
8
 *  Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
Adrien Béraud's avatar
Adrien Béraud committed
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
 *
 *  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, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

29
#include "jamiaccount.h"
30 31 32

#include "logger.h"

33
#include "accountarchive.h"
34
#include "ringcontact.h"
35
#include "configkeys.h"
Adrien Béraud's avatar
Adrien Béraud committed
36

Adrien Béraud's avatar
Adrien Béraud committed
37 38 39
#include "sip/sdp.h"
#include "sip/sipvoiplink.h"
#include "sip/sipcall.h"
40
#include "sip/siptransport.h"
41
#include "sip/sip_utils.h"
Adrien Béraud's avatar
Adrien Béraud committed
42

43
#include "sips_transport_ice.h"
44 45
#include "ice_transport.h"

46 47
#include "p2p.h"

48
#include "client/ring_signal.h"
49
#include "dring/call_const.h"
50
#include "dring/account_const.h"
Adrien Béraud's avatar
Adrien Béraud committed
51

52 53 54
#include "upnp/upnp_control.h"
#include "system_codec_container.h"

Adrien Béraud's avatar
Adrien Béraud committed
55 56
#include "account_schema.h"
#include "manager.h"
57
#include "utf8_utils.h"
Adrien Béraud's avatar
Adrien Béraud committed
58

Adrien Béraud's avatar
Adrien Béraud committed
59
#ifdef ENABLE_VIDEO
60
#include "libav_utils.h"
Adrien Béraud's avatar
Adrien Béraud committed
61 62
#endif
#include "fileutils.h"
63 64
#include "string_utils.h"
#include "array_size.h"
65
#include "archiver.h"
Adrien Béraud's avatar
Adrien Béraud committed
66 67

#include "config/yamlparser.h"
68
#include "security/certstore.h"
69 70
#include "libdevcrypto/Common.h"
#include "base64.h"
71

Adrien Béraud's avatar
Adrien Béraud committed
72
#include <opendht/thread_pool.h>
73
#include <opendht/peer_discovery.h>
74
#include <yaml-cpp/yaml.h>
75 76 77
#include <json/json.h>

#include <unistd.h>
78

Adrien Béraud's avatar
Adrien Béraud committed
79 80
#include <algorithm>
#include <array>
81
#include <cctype>
82
#include <cinttypes>
83
#include <cstdarg>
84 85 86 87
#include <initializer_list>
#include <memory>
#include <regex>
#include <sstream>
88
#include <string>
89
#include <system_error>
Adrien Béraud's avatar
Adrien Béraud committed
90

Adrien Béraud's avatar
Adrien Béraud committed
91
namespace jami {
92

Nicolas Jager's avatar
Nicolas Jager committed
93 94 95 96 97 98 99 100 101 102 103 104 105
namespace Migration {

enum class State { // Contains all the Migration states
    SUCCESS,
    INVALID
};

std::string
mapStateNumberToString(const State migrationState)
{
#define CASE_STATE(X) case Migration::State::X: \
                           return #X

106 107 108 109 110
    switch (migrationState) {
        CASE_STATE(INVALID);
        CASE_STATE(SUCCESS);
    }
    return {};
Nicolas Jager's avatar
Nicolas Jager committed
111 112 113 114 115 116 117 118 119
}

void
setState (const std::string& accountID,
          const State migrationState)
{
    emitSignal<DRing::ConfigurationSignal::MigrationEnded>(accountID,
        mapStateNumberToString(migrationState));
}
120

Adrien Béraud's avatar
Adrien Béraud committed
121
} // namespace jami::Migration
Nicolas Jager's avatar
Nicolas Jager committed
122

123
struct JamiAccount::BuddyInfo
124 125 126 127
{
    /* the buddy id */
    dht::InfoHash id;

128 129
    /* number of devices connected on the DHT */
    uint32_t devices_cnt {};
130

131
    /* The disposable object to update buddy info */
132
    std::future<size_t> listenToken;
133 134 135 136

    BuddyInfo(dht::InfoHash id) : id(id) {}
};

137
struct JamiAccount::PendingCall
138 139 140
{
    std::chrono::steady_clock::time_point start;
    std::shared_ptr<IceTransport> ice_sp;
141
    std::shared_ptr<IceTransport> ice_tcp_sp;
142 143 144 145
    std::weak_ptr<SIPCall> call;
    std::future<size_t> listen_key;
    dht::InfoHash call_key;
    dht::InfoHash from;
146
    dht::InfoHash from_account;
147 148 149
    std::shared_ptr<dht::crypto::Certificate> from_cert;
};

150
struct JamiAccount::PendingMessage
151 152 153 154 155
{
    dht::InfoHash to;
    std::chrono::steady_clock::time_point received;
};

156
struct
157
JamiAccount::TrustRequest {
158 159 160 161 162 163
    dht::InfoHash device;
    time_t received;
    std::vector<uint8_t> payload;
    MSGPACK_DEFINE_MAP(device, received, payload)
};

164
/**
Philippe Gorley's avatar
Philippe Gorley committed
165
 * Represents a known device attached to this account
166
 */
167
struct JamiAccount::KnownDevice
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
{
    /** Device certificate */
    std::shared_ptr<dht::crypto::Certificate> certificate;

    /** Device name */
    std::string name {};

    /** Time of last received device sync */
    time_point last_sync {time_point::min()};

    KnownDevice(const std::shared_ptr<dht::crypto::Certificate>& cert,
                const std::string& n = {},
                time_point sync = time_point::min())
        : certificate(cert), name(n), last_sync(sync) {}
};

/**
 * Device announcement stored on DHT.
 */
187
struct JamiAccount::DeviceAnnouncement : public dht::SignedValue<DeviceAnnouncement>
188 189 190 191 192 193 194 195 196
{
private:
    using BaseClass = dht::SignedValue<DeviceAnnouncement>;
public:
    static const constexpr dht::ValueType& TYPE = dht::ValueType::USER_DATA;
    dht::InfoHash dev;
    MSGPACK_DEFINE_MAP(dev);
};

197
struct JamiAccount::DeviceSync : public dht::EncryptedValue<DeviceSync>
198 199 200 201 202
{
    static const constexpr dht::ValueType& TYPE = dht::ValueType::USER_DATA;
    uint64_t date;
    std::string device_name;
    std::map<dht::InfoHash, std::string> devices_known;
203
    std::map<dht::InfoHash, Contact> peers;
204 205
    std::map<dht::InfoHash, TrustRequest> trust_requests;
    MSGPACK_DEFINE_MAP(date, device_name, devices_known, peers, trust_requests)
206 207
};

208 209 210 211 212 213 214 215 216 217 218 219 220
struct AccountPeerInfo
{
    dht::InfoHash accountId;
    std::string displayName;
    MSGPACK_DEFINE(accountId, displayName)
};

struct JamiAccount::DiscoveredPeer
{
    std::string displayName;
    std::shared_ptr<Task> cleanupTask;
};

221 222
static constexpr int ICE_COMPONENTS {1};
static constexpr int ICE_COMP_SIP_TRANSPORT {0};
223 224
static constexpr auto ICE_NEGOTIATION_TIMEOUT = std::chrono::seconds(60);
static constexpr auto TLS_TIMEOUT = std::chrono::seconds(30);
225
const constexpr auto EXPORT_KEY_RENEWAL_TIME = std::chrono::minutes(20);
226

227
static constexpr const char * const RING_URI_PREFIX = "ring:";
228
static constexpr const char * DEFAULT_TURN_SERVER = "turn.jami.net";
229 230 231
static constexpr const char * DEFAULT_TURN_USERNAME = "ring";
static constexpr const char * DEFAULT_TURN_PWD = "ring";
static constexpr const char * DEFAULT_TURN_REALM = "ring";
232
static const auto PROXY_REGEX = std::regex("(https?://)?([\\w\\.]+)(:(\\d+)|:\\[(.+)-(.+)\\])?");
233
static const std::string PEER_DISCOVERY_JAMI_SERVICE = "jami";
234
const constexpr auto PEER_DISCOVERY_EXPIRATION = std::chrono::minutes(1);
235

236

237 238
constexpr const char* const JamiAccount::ACCOUNT_TYPE;
/* constexpr */ const std::pair<uint16_t, uint16_t> JamiAccount::DHT_PORT_RANGE {4000, 8888};
Adrien Béraud's avatar
Adrien Béraud committed
239

240
using ValueIdDist = std::uniform_int_distribution<dht::Value::Id>;
241

242
static const std::string
243
stripPrefix(const std::string& toUrl)
244
{
245
    auto dhtf = toUrl.find(RING_URI_PREFIX);
246 247 248 249 250 251 252 253
    if (dhtf != std::string::npos) {
        dhtf = dhtf+5;
    } else {
        dhtf = toUrl.find("sips:");
        dhtf = (dhtf == std::string::npos) ? 0 : dhtf+5;
    }
    while (dhtf < toUrl.length() && toUrl[dhtf] == '/')
        dhtf++;
254 255
    return toUrl.substr(dhtf);
}
256

257 258 259 260 261
static const std::string
parseRingUri(const std::string& toUrl)
{
    auto sufix = stripPrefix(toUrl);
    if (sufix.length() < 40)
262 263
        throw std::invalid_argument("id must be a ring infohash");

264
    const std::string toUri = sufix.substr(0, 40);
265 266 267 268 269
    if (std::find_if_not(toUri.cbegin(), toUri.cend(), ::isxdigit) != toUri.cend())
        throw std::invalid_argument("id must be a ring infohash");
    return toUri;
}

270 271 272 273 274 275 276
static constexpr const char*
dhtStatusStr(dht::NodeStatus status) {
    return status == dht::NodeStatus::Connected  ? "connected"  : (
           status == dht::NodeStatus::Connecting ? "connecting" :
                                                   "disconnected");
}

277 278 279
/**
 * Local ICE Transport factory helper
 *
280
 * JamiAccount must use this helper than direct IceTranportFactory API
281 282 283
 */
template <class... Args>
std::shared_ptr<IceTransport>
284
JamiAccount::createIceTransport(const Args&... args)
285 286
{
    auto ice = Manager::instance().getIceTransportFactory().createTransport(args...);
287
    if (!ice)
288 289 290 291 292
        throw std::runtime_error("ICE transport creation failed");

    return ice;
}

293
JamiAccount::JamiAccount(const std::string& accountID, bool /* presenceEnabled */)
294
    : SIPAccountBase(accountID)
295
#if HAVE_RINGNS
296
    , nameDir_(NameDirectory::instance())
297
#endif
298 299 300
    , idPath_(fileutils::get_data_dir()+DIR_SEPARATOR_STR+getAccountID())
    , cachePath_(fileutils::get_cache_dir()+DIR_SEPARATOR_STR+getAccountID())
    , dataPath_(cachePath_ + DIR_SEPARATOR_STR "values")
301 302 303
    , proxyEnabled_(false)
    , proxyServer_("")
    , deviceKey_("")
304
    , dhtPeerConnector_ {new DhtPeerConnector {*this}}
305 306 307 308 309 310 311
{
    // Force the SFL turn server if none provided yet
    turnServer_ = DEFAULT_TURN_SERVER;
    turnServerUserName_ = DEFAULT_TURN_USERNAME;
    turnServerPwd_ = DEFAULT_TURN_PWD;
    turnServerRealm_ = DEFAULT_TURN_REALM;
    turnEnabled_ = true;
312 313 314 315

    std::ifstream proxyCache(cachePath_ + DIR_SEPARATOR_STR "dhtproxy");
    if (proxyCache)
      std::getline(proxyCache, proxyServerCached_);
316
}
Adrien Béraud's avatar
Adrien Béraud committed
317

318
JamiAccount::~JamiAccount()
Adrien Béraud's avatar
Adrien Béraud committed
319
{
320 321 322 323
    if (eventHandler) {
        eventHandler->cancel();
        eventHandler.reset();
    }
324 325 326 327
    if(peerDiscovery_){
        peerDiscovery_->stopPublish(PEER_DISCOVERY_JAMI_SERVICE);
        peerDiscovery_->stopDiscovery(PEER_DISCOVERY_JAMI_SERVICE);
    }
Adrien Béraud's avatar
Adrien Béraud committed
328 329 330
    dht_.join();
}

331
void
332
JamiAccount::flush()
333 334 335 336 337 338
{
    // Class base method
    SIPAccountBase::flush();

    fileutils::removeAll(dataPath_);
    fileutils::removeAll(cachePath_);
339
    fileutils::removeAll(idPath_, true);
340 341
}

Adrien Béraud's avatar
Adrien Béraud committed
342
std::shared_ptr<SIPCall>
343
JamiAccount::newIncomingCall(const std::string& from, const std::map<std::string, std::string>& details)
Adrien Béraud's avatar
Adrien Béraud committed
344
{
345 346 347
    std::lock_guard<std::mutex> lock(callsMutex_);
    auto call_it = pendingSipCalls_.begin();
    while (call_it != pendingSipCalls_.end()) {
348 349
        auto call = call_it->call.lock();
        if (not call) {
Adrien Béraud's avatar
Adrien Béraud committed
350
            JAMI_WARN("newIncomingCall: discarding deleted call");
351
            call_it = pendingSipCalls_.erase(call_it);
352 353 354
        } else if (call->getPeerNumber() == from || (call_it->from_cert and
                                                     call_it->from_cert->issuer and
                                                     call_it->from_cert->issuer->getId().toString() == from)) {
Adrien Béraud's avatar
Adrien Béraud committed
355
            JAMI_DBG("newIncomingCall: found matching call for %s", from.c_str());
356
            pendingSipCalls_.erase(call_it);
357
            call->updateDetails(details);
358 359 360
            return call;
        } else {
            ++call_it;
361 362
        }
    }
Adrien Béraud's avatar
Adrien Béraud committed
363
    JAMI_ERR("newIncomingCall: can't find matching call for %s", from.c_str());
364
    return nullptr;
Adrien Béraud's avatar
Adrien Béraud committed
365 366 367 368
}

template <>
std::shared_ptr<SIPCall>
369
JamiAccount::newOutgoingCall(const std::string& toUrl,
370
                             const std::map<std::string, std::string>& volatileCallDetails)
Adrien Béraud's avatar
Adrien Béraud committed
371
{
372
    auto suffix = stripPrefix(toUrl);
Adrien Béraud's avatar
Adrien Béraud committed
373
    JAMI_DBG() << *this << "Calling DHT peer " << suffix;
374
    auto& manager = Manager::instance();
375
    auto call = manager.callFactory.newCall<SIPCall, JamiAccount>(*this, manager.getNewCallID(),
376 377
                                                                  Call::CallType::OUTGOING,
                                                                  volatileCallDetails);
378

379
    call->setIPToIP(true);
380
    call->setSecure(isTlsEnabled());
381 382

    try {
383
        const std::string toUri = parseRingUri(suffix);
384 385 386
        startOutgoingCall(call, toUri);
    } catch (...) {
#if HAVE_RINGNS
Adrien Béraud's avatar
Adrien Béraud committed
387
        NameDirectory::lookupUri(suffix, nameServer_, [wthis_=weak(), call](const std::string& result,
388 389
                                                                   NameDirectory::Response response) {
            // we may run inside an unknown thread, but following code must be called in main thread
Adrien Béraud's avatar
Adrien Béraud committed
390
            runOnMainThread([wthis_, result, response, call]() {
391 392 393 394
                if (response != NameDirectory::Response::found) {
                    call->onFailure(EINVAL);
                    return;
                }
395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410
                if (auto sthis = wthis_.lock()) {
                    try {
                        const std::string toUri = parseRingUri(result);
                        sthis->startOutgoingCall(call, toUri);
                    } catch (...) {
                        call->onFailure(ENOENT);
                    }
                } else {
                    call->onFailure();
                }
            });
        });
#else
        call->onFailure(ENOENT);
#endif
    }
411

412 413 414
    return call;
}

415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438
void
initICE(const std::vector<uint8_t> &msg, const std::shared_ptr<IceTransport> &ice,
        const std::shared_ptr<IceTransport> &ice_tcp, bool &udp_failed, bool &tcp_failed)
{
    auto sdp_list = IceTransport::parseSDPList(msg);
    for (const auto &sdp : sdp_list) {
        if (sdp.candidates.size() > 0) {
            if (sdp.candidates[0].find("TCP") != std::string::npos) {
                // It is a SDP for the TCP component
                tcp_failed = (ice_tcp && !ice_tcp->start(sdp));
            } else {
                // For UDP
                udp_failed = (ice && !ice->start(sdp));
            }
        }
    }

    // During the ICE reply we can start the ICE negotiation
    if (tcp_failed && ice_tcp) {
        ice_tcp->stop();
        JAMI_WARN("ICE over TCP not started, will only use UDP");
    }
}

439
void
440
JamiAccount::startOutgoingCall(const std::shared_ptr<SIPCall>& call, const std::string& toUri)
441
{
442
    // TODO: for now, we automatically trust all explicitly called peers
443
    setCertificateStatus(toUri, tls::TrustStore::PermissionStatus::ALLOWED);
444

445
    call->setPeerNumber(toUri + "@ring.dht");
Philippe Gorley's avatar
Philippe Gorley committed
446
    call->setPeerUri(RING_URI_PREFIX + toUri);
447
    call->setState(Call::ConnectionState::TRYING);
448
    std::weak_ptr<SIPCall> wCall = call;
449

450 451 452
#if HAVE_RINGNS
    nameDir_.get().lookupAddress(toUri, [wCall](const std::string& result, const NameDirectory::Response& response){
        if (response == NameDirectory::Response::found)
Philippe Gorley's avatar
Philippe Gorley committed
453
            if (auto call = wCall.lock()) {
454
                call->setPeerRegistredName(result);
Philippe Gorley's avatar
Philippe Gorley committed
455 456
                call->setPeerUri(RING_URI_PREFIX + result);
            }
457 458 459
    });
#endif

Philippe Gorley's avatar
Philippe Gorley committed
460
    // Find listening devices for this account
461
    dht::InfoHash peer_account(toUri);
462
    forEachDevice(peer_account, [this, wCall, toUri, peer_account](const dht::InfoHash& dev)
463
    {
464 465
        auto call = wCall.lock();
        if (not call) return;
Adrien Béraud's avatar
Adrien Béraud committed
466
        JAMI_DBG("[call %s] calling device %s", call->getCallId().c_str(), dev.toString().c_str());
467

468
        auto& manager = Manager::instance();
469
        auto dev_call = manager.callFactory.newCall<SIPCall, JamiAccount>(*this, manager.getNewCallID(),
470 471
                                                                          Call::CallType::OUTGOING,
                                                                          call->getDetails());
472 473
        std::weak_ptr<SIPCall> weak_dev_call = dev_call;
        dev_call->setIPToIP(true);
474 475 476
        dev_call->setSecure(isTlsEnabled());
        auto ice = createIceTransport(("sip:" + dev_call->getCallId()).c_str(),
                                             ICE_COMPONENTS, true, getIceOptions());
477
        if (not ice) {
Adrien Béraud's avatar
Adrien Béraud committed
478
            JAMI_WARN("Can't create ICE");
479 480 481
            dev_call->removeCall();
            return;
        }
482

483 484 485 486 487 488 489
        auto ice_config = getIceOptions();
        ice_config.tcpEnable = true;
        ice_config.aggressive = true; // This will directly select the first candidate.
        auto ice_tcp = createIceTransport(("sip:" + dev_call->getCallId()).c_str(), ICE_COMPONENTS, true, ice_config);
        if (not ice_tcp) {
            JAMI_WARN("Can't create ICE over TCP, will only use UDP");
        }
490
        call->addSubCall(*dev_call);
491

492
        manager.addTask([sthis=shared(), weak_dev_call, ice, ice_tcp, dev, toUri, peer_account] {
493
            auto call = weak_dev_call.lock();
494

495 496 497
            // call aborted?
            if (not call)
                return false;
498

499
            if (ice->isFailed()) {
Adrien Béraud's avatar
Adrien Béraud committed
500
                JAMI_ERR("[call:%s] ice init failed", call->getCallId().c_str());
501 502 503
                call->onFailure(EIO);
                return false;
            }
504

505 506 507 508
            if (ice_tcp && ice_tcp->isFailed()) {
                JAMI_WARN("[call:%s] ice tcp init failed, will only use UDP", call->getCallId().c_str());
            }

509 510 511
            // Loop until ICE transport is initialized.
            // Note: we suppose that ICE init routine has a an internal timeout (bounded in time)
            // and we let upper layers decide when the call shall be aborded (our first check upper).
512
            if ((not ice->isInitialized()) || (ice_tcp && !ice_tcp->isInitialized()))
513 514 515
                return true;

            sthis->registerDhtAddress(*ice);
516
            if (ice_tcp) sthis->registerDhtAddress(*ice_tcp);
517
            // Next step: sent the ICE data to peer through DHT
518
            const dht::Value::Id callvid  = ValueIdDist()(sthis->rand);
519
            const auto callkey = dht::InfoHash::get("callto:" + dev.toString());
520 521 522 523 524 525
            auto blob = ice->packIceMsg();
            if (ice_tcp)  {
                auto ice_tcp_msg = ice_tcp->packIceMsg(2);
                blob.insert(blob.end(), ice_tcp_msg.begin(), ice_tcp_msg.end());
            }
            dht::Value val { dht::IceCandidates(callvid,  blob) };
526 527 528 529 530 531

            sthis->dht_.putEncrypted(
                callkey, dev,
                std::move(val),
                [=](bool ok) { // Put complete callback
                    if (!ok) {
Adrien Béraud's avatar
Adrien Béraud committed
532
                        JAMI_WARN("Can't put ICE descriptor on DHT");
533 534 535
                        if (auto call = weak_dev_call.lock())
                            call->onFailure();
                    } else
Adrien Béraud's avatar
Adrien Béraud committed
536
                        JAMI_DBG("Successfully put ICE descriptor on DHT");
537 538 539 540 541
                }
            );

            auto listenKey = sthis->dht_.listen<dht::IceCandidates>(
                callkey,
542
                [weak_dev_call, ice, ice_tcp, callvid, dev] (dht::IceCandidates&& msg) {
543 544
                    if (msg.id != callvid or msg.from != dev)
                        return true;
545 546 547 548 549
                    // remove unprintable characters
                    auto iceData = std::string(msg.ice_data.cbegin(), msg.ice_data.cend());
                    iceData.erase(std::remove_if(iceData.begin(), iceData.end(),
                                                 [](unsigned char c){ return !std::isprint(c) && !std::isspace(c); }
                                                ), iceData.end());
Adrien Béraud's avatar
Adrien Béraud committed
550
                    JAMI_WARN("ICE request replied from DHT peer %s\nData: %s", dev.toString().c_str(), iceData.c_str());
551 552
                    if (auto call = weak_dev_call.lock()) {
                        call->setState(Call::ConnectionState::PROGRESSING);
553 554 555 556

                        auto udp_failed = true, tcp_failed = true;
                        initICE(msg.ice_data, ice, ice_tcp, udp_failed, tcp_failed);
                        if (udp_failed && tcp_failed) {
557
                            call->onFailure();
558
                            return true;
559
                        }
560
                    }
561 562 563 564
                    return false;
                }
            );

565
            std::lock_guard<std::mutex> lock(sthis->callsMutex_);
566 567
            sthis->pendingCalls_.emplace_back(PendingCall{
                std::chrono::steady_clock::now(),
568
                ice, ice_tcp, weak_dev_call,
569
                std::move(listenKey),
570 571 572
                callkey,
                dev,
                peer_account,
573
                tls::CertificateStore::instance().getCertificate(toUri)
574
            });
575
            sthis->checkPendingCallsTask();
576
            return false;
577
        });
578
    }, [wCall](bool ok){
579
        if (not ok) {
580
            if (auto call = wCall.lock()) {
Adrien Béraud's avatar
Adrien Béraud committed
581
                JAMI_WARN("[call:%s] no devices found", call->getCallId().c_str());
582 583
                call->onFailure(static_cast<int>(std::errc::no_such_device_or_address));
            }
584
        }
585
    });
Adrien Béraud's avatar
Adrien Béraud committed
586 587 588
}

void
589
JamiAccount::onConnectedOutgoingCall(SIPCall& call, const std::string& to_id, IpAddr target)
Adrien Béraud's avatar
Adrien Béraud committed
590
{
Adrien Béraud's avatar
Adrien Béraud committed
591
    JAMI_DBG("[call:%s] outgoing call connected to %s", call.getCallId().c_str(), to_id.c_str());
592 593 594 595

    call.initIceMediaTransport(true);
    call.setIPToIP(true);
    call.setPeerNumber(getToUri(to_id+"@"+target.toString(true).c_str()));
Adrien Béraud's avatar
Adrien Béraud committed
596

597
    const auto localAddress = ip_utils::getInterfaceAddr(getLocalInterface());
Adrien Béraud's avatar
Adrien Béraud committed
598

599
    IpAddr addrSdp;
600
    if (getUPnPActive()) {
601 602 603 604 605 606 607
        /* use UPnP addr, or published addr if its set */
        addrSdp = getPublishedSameasLocal() ?
            getUPnPIpAddress() : getPublishedIpAddress();
    } else {
        addrSdp = isStunEnabled() or (not getPublishedSameasLocal()) ?
            getPublishedIpAddress() : localAddress;
    }
Adrien Béraud's avatar
Adrien Béraud committed
608

609 610 611
    /* fallback on local address */
    if (not addrSdp) addrSdp = localAddress;

Adrien Béraud's avatar
Adrien Béraud committed
612 613 614
    // Initialize the session using ULAW as default codec in case of early media
    // The session should be ready to receive media once the first INVITE is sent, before
    // the session initialization is completed
Adrien Béraud's avatar
Adrien Béraud committed
615
    if (!getSystemCodecContainer()->searchCodecByName("PCMA", jami::MEDIA_AUDIO))
Adrien Béraud's avatar
Adrien Béraud committed
616 617 618
        throw VoipLinkException("Could not instantiate codec for early media");

    // Building the local SDP offer
619
    auto& sdp = call.getSDP();
Adrien Béraud's avatar
Adrien Béraud committed
620

621
    sdp.setPublishedIP(addrSdp);
Guillaume Roguez's avatar
Guillaume Roguez committed
622
    const bool created = sdp.createOffer(
623
                            getActiveAccountCodecInfoList(MEDIA_AUDIO),
624 625
                            getActiveAccountCodecInfoList(videoEnabled_ and not call.isAudioOnly() ? MEDIA_VIDEO
                                                                                                   : MEDIA_NONE),
Guillaume Roguez's avatar
Guillaume Roguez committed
626 627
                            getSrtpKeyExchange()
                         );
Adrien Béraud's avatar
Adrien Béraud committed
628

629
    if (not created or not SIPStartCall(call, target))
Adrien Béraud's avatar
Adrien Béraud committed
630 631 632 633
        throw VoipLinkException("Could not send outgoing INVITE request for new call");
}

std::shared_ptr<Call>
634
JamiAccount::newOutgoingCall(const std::string& toUrl, const std::map<std::string, std::string>& volatileCallDetails)
Adrien Béraud's avatar
Adrien Béraud committed
635
{
636
    return newOutgoingCall<SIPCall>(toUrl, volatileCallDetails);
Adrien Béraud's avatar
Adrien Béraud committed
637 638 639
}

bool
640
JamiAccount::SIPStartCall(SIPCall& call, IpAddr target)
Adrien Béraud's avatar
Adrien Béraud committed
641
{
642 643
    call.setupLocalSDPFromIce();
    std::string toUri(call.getPeerNumber()); // expecting a fully well formed sip uri
Adrien Béraud's avatar
Adrien Béraud committed
644 645 646 647 648 649 650

    pj_str_t pjTo = pj_str((char*) toUri.c_str());

    // Create the from header
    std::string from(getFromUri());
    pj_str_t pjFrom = pj_str((char*) from.c_str());

651 652 653
    std::string targetStr = getToUri(target.toString(true)/*+";transport=ICE"*/);
    pj_str_t pjTarget = pj_str((char*) targetStr.c_str());

654 655
    pj_str_t pjContact;
    {
656
        auto transport = call.getTransport();
657 658
        pjContact = getContactHeader(transport ? transport->get() : nullptr);
    }
Adrien Béraud's avatar
Adrien Béraud committed
659

Adrien Béraud's avatar
Adrien Béraud committed
660
    JAMI_DBG("contact header: %.*s / %s -> %s / %.*s",
661 662
             (int)pjContact.slen, pjContact.ptr, from.c_str(), toUri.c_str(),
             (int)pjTarget.slen, pjTarget.ptr);
Adrien Béraud's avatar
Adrien Béraud committed
663 664


665
    auto local_sdp = call.getSDP().getLocalSdpSession();
666 667 668
    pjsip_dialog* dialog {nullptr};
    pjsip_inv_session* inv {nullptr};
    if (!CreateClientDialogAndInvite(&pjFrom, &pjContact, &pjTo, &pjTarget, local_sdp, &dialog, &inv))
Adrien Béraud's avatar
Adrien Béraud committed
669 670
        return false;

671 672
    inv->mod_data[link_->getModId()] = &call;
    call.inv.reset(inv);
Adrien Béraud's avatar
Adrien Béraud committed
673 674 675 676 677 678 679 680 681

/*
    updateDialogViaSentBy(dialog);
    if (hasServiceRoute())
        pjsip_dlg_set_route_set(dialog, sip_utils::createRouteSet(getServiceRoute(), call->inv->pool));
*/

    pjsip_tx_data *tdata;

682
    if (pjsip_inv_invite(call.inv.get(), &tdata) != PJ_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
683
        JAMI_ERR("Could not initialize invite messager for this call");
Adrien Béraud's avatar
Adrien Béraud committed
684 685 686
        return false;
    }

687 688 689
    pjsip_tpselector tp_sel;
    tp_sel.type = PJSIP_TPSELECTOR_TRANSPORT;
    tp_sel.u.transport = call.getTransport()->get();
Adrien Béraud's avatar
Adrien Béraud committed
690
    if (pjsip_dlg_set_transport(dialog, &tp_sel) != PJ_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
691
        JAMI_ERR("Unable to associate transport for invite session dialog");
Adrien Béraud's avatar
Adrien Béraud committed
692 693 694
        return false;
    }

Adrien Béraud's avatar
Adrien Béraud committed
695
    JAMI_DBG("[call:%s] Sending SIP invite", call.getCallId().c_str());
696
    if (pjsip_inv_send_msg(call.inv.get(), tdata) != PJ_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
697
        JAMI_ERR("Unable to send invite message for this call");
Adrien Béraud's avatar
Adrien Béraud committed
698 699 700
        return false;
    }

701
    call.setState(Call::CallState::ACTIVE, Call::ConnectionState::PROGRESSING);
Adrien Béraud's avatar
Adrien Béraud committed
702 703 704 705

    return true;
}

706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721
void JamiAccount::saveConfig() const
{
    try {
        YAML::Emitter accountOut;
        serialize(accountOut);
        auto accountConfig = getPath() + DIR_SEPARATOR_STR + "config.yml";

        std::lock_guard<std::mutex> lock(fileutils::getFileLock(accountConfig));
        std::ofstream fout(accountConfig);
        fout << accountOut.c_str();
        JAMI_DBG("Exported account to %s", accountConfig.c_str());
    } catch (const std::exception& e) {
        JAMI_ERR("Error exporting account: %s", e.what());
    }
}

722
void JamiAccount::serialize(YAML::Emitter &out) const
Adrien Béraud's avatar
Adrien Béraud committed
723
{
724 725
    std::lock_guard<std::mutex> lock(configurationMutex_);

726 727 728
    if (registrationState_ == RegistrationState::INITIALIZING)
        return;

Adrien Béraud's avatar
Adrien Béraud committed
729 730
    out << YAML::BeginMap;
    SIPAccountBase::serialize(out);
731
    out << YAML::Key << Conf::DHT_PORT_KEY << YAML::Value << dhtPort_;
732
    out << YAML::Key << Conf::DHT_PUBLIC_IN_CALLS << YAML::Value << dhtPublicInCalls_;
733 734
    out << YAML::Key << Conf::DHT_ALLOW_PEERS_FROM_HISTORY << YAML::Value << allowPeersFromHistory_;
    out << YAML::Key << Conf::DHT_ALLOW_PEERS_FROM_CONTACT << YAML::Value << allowPeersFromContact_;
735
    out << YAML::Key << Conf::DHT_ALLOW_PEERS_FROM_TRUSTED << YAML::Value << allowPeersFromTrusted_;
736
    out << YAML::Key << DRing::Account::ConfProperties::DHT_PEER_DISCOVERY << YAML::Value << dhtPeerDiscovery_;
737
    out << YAML::Key << DRing::Account::ConfProperties::ACCOUNT_PEER_DISCOVERY << YAML::Value << accountPeerDiscovery_;
738
    out << YAML::Key << DRing::Account::ConfProperties::ACCOUNT_PUBLISH << YAML::Value << accountPublish_;
Adrien Béraud's avatar
Adrien Béraud committed
739

740 741 742 743
    out << YAML::Key << Conf::PROXY_ENABLED_KEY << YAML::Value << proxyEnabled_;
    out << YAML::Key << Conf::PROXY_SERVER_KEY << YAML::Value << proxyServer_;
    out << YAML::Key << Conf::PROXY_PUSH_TOKEN_KEY << YAML::Value << deviceKey_;

744
#if HAVE_RINGNS
745
    out << YAML::Key << DRing::Account::ConfProperties::RingNS::URI << YAML::Value <<  nameServer_;
746 747
    if (not registeredName_.empty())
        out << YAML::Key << DRing::Account::VolatileProperties::REGISTERED_NAME << YAML::Value << registeredName_;
748 749
#endif

750
    out << YAML::Key << DRing::Account::ConfProperties::ARCHIVE_PATH << YAML::Value << archivePath_;
751
    out << YAML::Key << DRing::Account::ConfProperties::ARCHIVE_HAS_PASSWORD << YAML::Value << archiveHasPassword_;
752 753
    out << YAML::Key << Conf::RING_ACCOUNT_RECEIPT << YAML::Value << receipt_;
    out << YAML::Key << Conf::RING_ACCOUNT_RECEIPT_SIG << YAML::Value << YAML::Binary(receiptSignature_.data(), receiptSignature_.size());
754
    out << YAML::Key << DRing::Account::ConfProperties::RING_DEVICE_NAME << YAML::Value << ringDeviceName_;
755

Adrien Béraud's avatar
Adrien Béraud committed
756
    // tls submap
757
    out << YAML::Key << Conf::TLS_KEY << YAML::Value << YAML::BeginMap;
Adrien Béraud's avatar
Adrien Béraud committed
758 759 760 761 762 763
    SIPAccountBase::serializeTls(out);
    out << YAML::EndMap;

    out << YAML::EndMap;
}

764
void JamiAccount::unserialize(const YAML::Node &node)
Adrien Béraud's avatar
Adrien Béraud committed
765
{
766 767
    std::lock_guard<std::mutex> lock(configurationMutex_);

768
    using yaml_utils::parseValue;
769
    using yaml_utils::parseValueOptional;
770
    using yaml_utils::parsePath;
771

Adrien Béraud's avatar
Adrien Béraud committed
772
    SIPAccountBase::unserialize(node);
773 774 775 776 777 778 779 780

    // get tls submap
    const auto &tlsMap = node[Conf::TLS_KEY];
    parsePath(tlsMap, Conf::CERTIFICATE_KEY, tlsCertificateFile_, idPath_);
    parsePath(tlsMap, Conf::CALIST_KEY, tlsCaListFile_, idPath_);
    parseValue(tlsMap, Conf::TLS_PASSWORD_KEY, tlsPassword_);
    parsePath(tlsMap, Conf::PRIVATE_KEY_KEY, tlsPrivateKeyFile_, idPath_);

781 782
    parseValue(node, Conf::DHT_ALLOW_PEERS_FROM_HISTORY, allowPeersFromHistory_);
    parseValue(node, Conf::DHT_ALLOW_PEERS_FROM_CONTACT, allowPeersFromContact_);
783
    parseValue(node, Conf::DHT_ALLOW_PEERS_FROM_TRUSTED, allowPeersFromTrusted_);
784 785 786 787 788

    parseValue(node, Conf::PROXY_ENABLED_KEY, proxyEnabled_);
    parseValue(node, Conf::PROXY_SERVER_KEY, proxyServer_);
    parseValue(node, Conf::PROXY_PUSH_TOKEN_KEY, deviceKey_);

789
    parseValueOptional(node, DRing::Account::ConfProperties::RING_DEVICE_NAME, ringDeviceName_);
790 791

    try {
792
        parsePath(node, DRing::Account::ConfProperties::ARCHIVE_PATH, archivePath_, idPath_);
793
        parseValue(node, DRing::Account::ConfProperties::ARCHIVE_HAS_PASSWORD, archiveHasPassword_);
794
    } catch (const std::exception& e) {
Adrien Béraud's avatar
Adrien Béraud committed
795
        JAMI_WARN("can't read archive path: %s", e.what());
796
        archiveHasPassword_ = true;
797 798 799 800 801 802 803
    }

    try {
        parseValue(node, Conf::RING_ACCOUNT_RECEIPT, receipt_);
        auto receipt_sig = node[Conf::RING_ACCOUNT_RECEIPT_SIG].as<YAML::Binary>();
        receiptSignature_ = {receipt_sig.data(), receipt_sig.data()+receipt_sig.size()};
    } catch (const std::exception& e) {
Adrien Béraud's avatar
Adrien Béraud committed
804
        JAMI_WARN("can't read receipt: %s", e.what());
805 806
    }

807 808
    if (not dhtPort_)
        dhtPort_ = getRandomEvenPort(DHT_PORT_RANGE);
809
    dhtPortUsed_ = dhtPort_;
810

811
    parseValueOptional(node, DRing::Account::ConfProperties::DHT_PEER_DISCOVERY, dhtPeerDiscovery_);
812
    parseValueOptional(node, DRing::Account::ConfProperties::ACCOUNT_PEER_DISCOVERY, accountPeerDiscovery_);
813
    parseValueOptional(node, DRing::Account::ConfProperties::ACCOUNT_PUBLISH, accountPublish_);
814

815
#if HAVE_RINGNS
816
    parseValueOptional(node, DRing::Account::ConfProperties::RingNS::URI, nameServer_);
817
    nameDir_ = NameDirectory::instance(nameServer_);
818 819 820
    if (registeredName_.empty()) {
        parseValueOptional(node, DRing::Account::VolatileProperties::REGISTERED_NAME, registeredName_);
    }
821 822
#endif

823 824
    parseValue(node, Conf::DHT_PUBLIC_IN_CALLS, dhtPublicInCalls_);

825
    loadAccount();
826 827 828
}

void
829
JamiAccount::createRingDevice(const dht::crypto::Identity& id)
830
{
831
    if (not id.second->isCA()) {
Adrien Béraud's avatar
Adrien Béraud committed
832
        JAMI_ERR("[Account %s] trying to sign a certificate with a non-CA.", getAccountID().c_str());
833
    }
834 835 836 837 838 839 840 841
    auto dev_id = dht::crypto::generateIdentity("Ring device", id);
    if (!dev_id.first || !dev_id.second) {
        throw VoipLinkException("Can't generate identity for this account.");
    }
    idPath_ = fileutils::get_data_dir() + DIR_SEPARATOR_STR + getAccountID();
    fileutils::check_dir(idPath_.c_str(), 0700);

    // save the chain including CA
842
    std::tie(tlsPrivateKeyFile_, tlsCertificateFile_) = saveIdentity(dev_id, idPath_, "ring_device");
843 844
    tlsPassword_ = {};
    identity_ = dev_id;
845 846
    accountTrust_ = dht::crypto::TrustList{};
    accountTrust_.add(*id.second);
847 848
    auto deviceId = dev_id.first->getPublicKey().getId();
    ringDeviceId_ = deviceId.toString();
849
    ringDeviceName_ = ip_utils::getDeviceName();
850 851
    if (ringDeviceName_.empty())
        ringDeviceName_ = ringDeviceId_.substr(8);
852 853 854 855 856

    {
        std::lock_guard<std::mutex> devicelock(deviceListMutex_);
        knownDevices_.emplace(deviceId, KnownDevice{dev_id.second, ringDeviceName_, clock::now()});
    }
857 858 859

    receipt_ = makeReceipt(id);
    receiptSignature_ = id.first->sign({receipt_.begin(), receipt_.end()});
Adrien Béraud's avatar
Adrien Béraud committed
860
    JAMI_WARN("[Account %s] created new device: %s (%s)",
861
              getAccountID().c_str(), ringDeviceId_.c_str(), ringDeviceName_.c_str());
862 863 864
}

void
865
JamiAccount::initRingDevice(const AccountArchive& a)
866
{
Adrien Béraud's avatar
Adrien Béraud committed
867
    JAMI_WARN("[Account %s] creating new device from archive", getAccountID().c_str());
868 869 870 871 872 873 874 875 876
    SIPAccountBase::setAccountDetails(a.config);
    parseInt(a.config, Conf::CONFIG_DHT_PORT, dhtPort_);
    parseBool(a.config, Conf::CONFIG_DHT_PUBLIC_IN_CALLS, dhtPublicInCalls_);
    parseBool(a.config, DRing::Account::ConfProperties::ALLOW_CERT_FROM_HISTORY, allowPeersFromHistory_);
    parseBool(a.config, DRing::Account::ConfProperties::ALLOW_CERT_FROM_CONTACT, allowPeersFromContact_);
    parseBool(a.config, DRing::Account::ConfProperties::ALLOW_CERT_FROM_TRUSTED, allowPeersFromTrusted_);
    ringAccountId_ = a.id.second->getId().toString();
    username_ = RING_URI_PREFIX+ringAccountId_;
    ethAccount_ = dev::KeyPair(dev::Secret(a.eth_key)).address().hex();
877
    contacts_ = a.contacts;
878
    createRingDevice(a.id);
879
    saveContacts();
880 881 882
}

std::string
883
JamiAccount::makeReceipt(const dht::crypto::Identity& id)
884
{
Adrien Béraud's avatar
Adrien Béraud committed
885
    JAMI_DBG("[Account %s] signing device receipt", getAccountID().c_str());
886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901
    DeviceAnnouncement announcement;
    announcement.dev = identity_.second->getId();
    dht::Value ann_val {announcement};
    ann_val.sign(*id.first);

    std::ostringstream is;
    is << "{\"id\":\"" << id.second->getId()
       << "\",\"dev\":\"" << identity_.second->getId()
       << "\",\"eth\":\"" << ethAccount_
       << "\",\"announce\":\"" << base64::encode(ann_val.getPacked()) << "\"}";

    announce_ = std::make_shared<dht::Value>(std::move(ann_val));
    return is.str();
}

bool
902
JamiAccount::useIdentity(const dht::crypto::Identity& identity)
903 904 905 906
{
    if (receipt_.empty() or receiptSignature_.empty())
        return false;

907
    if (not identity.first or not identity.second) {
Adrien Béraud's avatar
Adrien Béraud committed
908
        JAMI_ERR("[Account %s] no identity provided", getAccountID().c_str());
909 910 911
        return false;
    }

912
    auto accountCertificate = identity.second->issuer;
913
    if (not accountCertificate) {
Adrien Béraud's avatar
Adrien Béraud committed
914
        JAMI_ERR("[Account %s] device certificate must be issued by the account certificate", getAccountID().c_str());
915 916 917 918
        return false;
    }

    // match certificate chain
919 920 921
    dht::crypto::TrustList account_trust;
    account_trust.add(*accountCertificate);
    if (not account_trust.verify(*identity.second)) {
Adrien Béraud's avatar
Adrien Béraud committed
922
        JAMI_ERR("[Account %s] can't use identity: device certificate chain can't be verified", getAccountID().c_str());
923 924 925 926
        return false;
    }

    auto pk = accountCertificate->getPublicKey();
Adrien Béraud's avatar
Adrien Béraud committed
927
    JAMI_DBG("[Account %s] checking device receipt for %s", getAccountID().c_str(), pk.getId().toString().c_str());
928
    if (!pk.checkSignature({receipt_.begin(), receipt_.end()}, receiptSignature_)) {
Adrien Béraud's avatar
Adrien Béraud committed
929
        JAMI_ERR("[Account %s] device receipt signature check failed", getAccountID().c_str());
930 931 932 933
        return false;
    }

    Json::Value root;
Guillaume Roguez's avatar
Guillaume Roguez committed
934 935 936
    Json::CharReaderBuilder rbuilder;
    auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
    if (!reader->parse(&receipt_[0], &receipt_[receipt_.size()], &root, nullptr)) {
Adrien Béraud's avatar
Adrien Béraud committed
937
        JAMI_ERR() << this << " device receipt parsing error";
938
        return false;
Guillaume Roguez's avatar
Guillaume Roguez committed
939
    }
940 941

    auto dev_id = root["dev"].asString();
942
    if (dev_id != identity.second->getId().toString()) {
Adrien Béraud's avatar
Adrien Béraud committed
943
        JAMI_ERR("[Account %s] device ID mismatch between receipt and certificate", getAccountID().c_str());
944 945 946 947
        return false;
    }
    auto id = root["id"].asString();
    if (id != pk.getId().toString()) {
Adrien Béraud's avatar
Adrien Béraud committed
948
        JAMI_ERR("[Account %s] account ID mismatch between receipt and certificate", getAccountID().c_str());
949 950 951 952 953 954 955 956 957 958
        return false;
    }

    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());
        //dht::Value announce_val (announce_msg.get());
        announce_val.msgpack_unpack(announce_msg.get());
        if (not announce_val.checkSignature()) {
Adrien Béraud's avatar
Adrien Béraud committed
959
            JAMI_ERR("[Account %s] announce signature check failed", getAccountID().c_str());
960 961 962 963 964
            return false;
        }
        DeviceAnnouncement da;
        da.unpackValue(announce_val);
        if (da.from.toString() != id or da.dev.toString() != dev_id) {
Adrien Béraud's avatar
Adrien Béraud committed
965
            JAMI_ERR("[Account %s] device ID mismatch in announce", getAccountID().c_str());
966 967 968
            return false;
        }
    } catch (const std::exception& e) {
Adrien Béraud's avatar
Adrien Béraud committed
969
        JAMI_ERR("[Account %s] can't read announce: %s", getAccountID().c_str(), e.what());
970
        return false;
971
    }
972

973 974
    // success, make use of this identity (certificate chain and private key)
    identity_ = identity;
975
    accountTrust_ = std::move(account_trust);
976
    ringAccountId_ = id;
977
    ringDeviceId_ = identity.first->getPublicKey().getId().toString();
978 979
    username_ = RING_URI_PREFIX + id;
    announce_ = std::make_shared<dht::Value>(std::move(announce_val));
980
    ethAccount_ = root["eth"].asString();
981

Adrien Béraud's avatar
Adrien Béraud committed
982
    JAMI_DBG("[Account %s] ring:%s device %s receipt checked successfully", getAccountID().c_str(), id.c_str(), ringDeviceId_.c_str());
983
    return true;
Adrien Béraud's avatar
Adrien Béraud committed
984 985
}

986
dht::crypto::Identity
987
JamiAccount::loadIdentity(const std::string& crt_path, const std::string& key_path, const std::string& key_pwd) const
988
{
Adrien Béraud's avatar
Adrien Béraud committed
989
    JAMI_DBG("[Account %s] loading identity: %s %s", getAccountID().c_str(), crt_path.c_str(), key_path.c_str());
990
    dht::crypto::Identity id;
Adrien Béraud's avatar
Adrien Béraud committed
991
    try {
992 993
        dht::crypto::Certificate dht_cert(fileutils::loadFile(crt_path, idPath_));
        dht::crypto::PrivateKey  dht_key(fileutils::loadFile(key_path, idPath_), key_pwd);
994 995 996 997
        auto crt_id = dht_cert.getId();
        if (crt_id != dht_key.getPublicKey().getId())
            return {};

998
        if (not dht_cert.issuer) {
Adrien Béraud's avatar
Adrien Béraud committed
999
            JAMI_ERR("[Account %s] device certificate %s has no issuer", getAccountID().c_str(), dht_cert.getId().toString().c_str());
1000 1001 1002 1003 1004
            return {};
        }
        // load revocation lists for device authority (account certificate).
        tls::CertificateStore::instance().loadRevocations(*dht_cert.issuer);

1005
        id = {
1006 1007 1008
            std::make_shared<dht::crypto::PrivateKey>(std::move(dht_key)),
            std::make_shared<dht::crypto::Certificate>(std::move(dht_cert))
        };
Adrien Béraud's avatar
Adrien Béraud committed
1009 1010
    }
    catch (const std::exception& e) {
Adrien Béraud's avatar
Adrien Béraud committed
1011
        JAMI_ERR("Error loading identity: %s", e.what());
1012 1013
    }

1014
    return id;
1015 1016
}

1017
AccountArchive
1018
JamiAccount::readArchive(const std::string& pwd) const
1019
{
Adrien Béraud's avatar
Adrien Béraud committed
1020
    JAMI_DBG("[Account %s] reading account archive", getAccountID().c_str());
1021
    return AccountArchive(fileutils::getFullPath(idPath_, archivePath_), pwd);
1022
}
Adrien Béraud's avatar
Adrien Béraud committed
1023 1024


1025
void
1026
JamiAccount::updateArchive(AccountArchive& archive) const
1027
{
1028
    using namespace DRing::Account::ConfProperties;
1029

1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041
    // 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 };

Adrien Béraud's avatar
Adrien Béraud committed
1042
    JAMI_DBG("[Account %s] building account archive", getAccountID().c_str());
1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054
    for (const auto& it : getAccountDetails()) {
        // 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 (...) {}
1055
        } else
1056
            archive.config.insert(it);
Adrien Béraud's avatar
Adrien Béraud committed
1057
    }
1058
    archive.contacts = contacts_;
1059 1060 1061
}

void
1062
JamiAccount::saveArchive(AccountArchive& archive, const std::string& pwd)
1063 1064
{
    try {
1065 1066 1067 1068
        updateArchive(archive);
        if (archivePath_.empty())
            archivePath_ = "export.gz";
        archive.save(fileutils::getFullPath(idPath_, archivePath_), pwd);
1069
        archiveHasPassword_ = not pwd.empty();
1070
    } catch (const std::runtime_error& ex) {
Adrien Béraud's avatar
Adrien Béraud committed
1071
        JAMI_ERR("[Account %s] Can't export archive: %s", getAccountID().c_str(), ex.what());
1072 1073 1074 1075
        return;
    }
}

1076
bool
1077
JamiAccount::changeArchivePassword(const std::string& password_old, const std::string& password_new)
1078 1079 1080 1081
{
    auto path = fileutils::getFullPath(idPath_, archivePath_);
    try {
        AccountArchive(path, password_old).save(path, password_new);
1082
        archiveHasPassword_ = not password_new.empty();
1083
    } catch (const std::exception& ex) {
Adrien Béraud's avatar
Adrien Béraud committed
1084
        JAMI_ERR("[Account %s] Can't change archive password: %s", getAccountID().c_str(), ex.what());
1085 1086 1087 1088
        if (password_old.empty()) {
            archiveHasPassword_ = true;
            emitSignal<DRing::ConfigurationSignal::AccountDetailsChanged>(getAccountID(), getAccountDetails());
        }
1089 1090
        return false;
    }
1091 1092
    if (password_old != password_new)
        emitSignal<DRing::ConfigurationSignal::AccountDetailsChanged>(getAccountID(), getAccountDetails());
1093 1094 1095
    return true;
}

1096
std::pair<std::vector<uint8_t>, dht::InfoHash>
1097
JamiAccount::computeKeys(const std::string& password, const std::string& pin, bool previous)
1098 1099
{
    // Compute time seed
1100
    auto now = std::chrono::duration_cast<std::chrono::seconds>(clock::now().time_since_epoch());
1101
    auto tseed = now.count() / std::chrono::seconds(EXPORT_KEY_RENEWAL_TIME).count();
1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120
    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