ssu.cpp 23.6 KB
Newer Older
Aard's avatar
Aard committed
1 2 3 4 5 6 7
/**
 * @file ssu.cpp
 * @copyright 2012 Jolla Ltd.
 * @author Bernd Wachter <bernd.wachter@jollamobile.com>
 * @date 2012
 */

8
#include <QtNetwork>
Aard's avatar
Aard committed
9
#include <QtXml/QDomDocument>
10 11 12
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDBusPendingReply>
13

Aard's avatar
Aard committed
14 15 16 17
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
#include <QUrlQuery>
#endif

18 19
#include <getdef.h>
#include <pwd.h>
20 21
#include <sys/types.h>
#include <unistd.h>
22

Aard's avatar
Aard committed
23
#include "ssu.h"
24
#include "sandbox_p.h"
25 26 27
#include "ssulog_p.h"
#include "ssuvariables_p.h"
#include "ssucoreconfig_p.h"
28
#include "ssurepomanager.h"
29
#include "ssudeviceinfo.h"
Aard's avatar
Aard committed
30

31 32
#include "../constants.h"

33 34
#define SSU_NETWORK_REQUEST_DOMAIN_DATA (static_cast<QNetworkRequest::Attribute>(QNetworkRequest::User + 1))

35 36 37 38 39 40
static void restoreUid()
{
    if (getuid() == 0) {
        seteuid(0);
        setegid(0);
    }
41 42
}

43 44 45 46
Ssu::Ssu(): QObject()
{
    errorFlag = false;
    pendingRequests = 0;
Aard's avatar
Aard committed
47

48
#ifdef SSUCONFHACK
49 50 51 52 53 54 55 56 57 58
    // dirty hack to make sure we can write to the configuration
    // this is currently required since there's no global gconf,
    // and we migth not yet have users on bootstrap
    QFileInfo settingsInfo(SSU_CONFIGURATION);
    if (settingsInfo.groupId() != SSU_GROUP_ID ||
            !settingsInfo.permission(QFile::WriteGroup)) {
        QProcess proc;
        proc.start("/usr/bin/ssuconfperm");
        proc.waitForFinished();
    }
59
#endif
Aard's avatar
Aard committed
60

61
    SsuCoreConfig *settings = SsuCoreConfig::instance();
Aard's avatar
Aard committed
62 63

#ifdef TARGET_ARCH
64 65
    if (!settings->contains("arch"))
        settings->setValue("arch", TARGET_ARCH);
Aard's avatar
Aard committed
66 67 68 69
#else
// FIXME, try to guess a matching architecture
#warning "TARGET_ARCH not defined"
#endif
70
    settings->sync();
Aard's avatar
Aard committed
71

72 73


74 75 76
    manager = new QNetworkAccessManager(this);
    connect(manager, SIGNAL(finished(QNetworkReply *)),
            SLOT(requestFinished(QNetworkReply *)));
Aard's avatar
Aard committed
77 78
}

79 80
// FIXME, the whole credentials stuff needs reworking
// should probably be part of repo handling instead of core configuration
81 82 83 84
QPair<QString, QString> Ssu::credentials(QString scope)
{
    SsuCoreConfig *settings = SsuCoreConfig::instance();
    return settings->credentials(scope);
Aard's avatar
Aard committed
85 86
}

87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
QString Ssu::credentialsScope(QString repoName, bool rndRepo)
{
    SsuCoreConfig *settings = SsuCoreConfig::instance();
    SsuSettings repoSettings(SSU_REPO_CONFIGURATION, QSettings::IniFormat);

    // hardcoded magic for doing special privileges store repositories
    if (repoName == "store" || repoName.startsWith("store-c-"))
        return "store";

    // check if some repos are marked for using store-credentials
    // in current domain, checking first for rnd/release specific
    // settings, and if not found in generic settings
    QString storeAuthReposKey = QString("store-auth-repos-%1")
                                .arg(rndRepo ? "rnd" : "release");
    QStringList storeAuthRepos =
        SsuVariables::variable(&repoSettings,
                               domain() + "-domain",
                               storeAuthReposKey).toStringList();

    if (storeAuthRepos.empty())
        storeAuthRepos =
            SsuVariables::variable(&repoSettings,
                                   domain() + "-domain",
                                   "store-auth-repos").toStringList();

    if (storeAuthRepos.contains(repoName))
        return "store";

    // If we defined explicitly what credentials to use with which secure domain
    // use those.
    QHash<QString, QString> secureDomainAuth;
    SsuVariables::variableSection(&repoSettings, "secure-domain-auth", &secureDomainAuth);
    QHashIterator<QString, QString> i(secureDomainAuth);
    while (i.hasNext()) {
        i.next();
        if (repoUrl(repoName, rndRepo).contains(i.key()) && !i.value().isEmpty()) {
            return i.value();
        }
125 126
    }

127
    return settings->credentialsScope(repoName, rndRepo);
Aard's avatar
Aard committed
128 129
}

130 131 132 133
QString Ssu::credentialsUrl(QString scope)
{
    SsuCoreConfig *settings = SsuCoreConfig::instance();
    return settings->credentialsUrl(scope);
134 135
}

136 137 138
bool Ssu::error()
{
    return errorFlag;
Aard's avatar
Aard committed
139 140
}

141
// Wrappers around SsuCoreConfig
142 143 144 145
QString Ssu::flavour()
{
    SsuCoreConfig *settings = SsuCoreConfig::instance();
    return settings->flavour();
Aard's avatar
Aard committed
146 147
}

148 149 150 151
Ssu::DeviceModeFlags Ssu::deviceMode()
{
    SsuCoreConfig *settings = SsuCoreConfig::instance();
    return settings->deviceMode();
152 153
}

154 155 156 157
QString Ssu::domain()
{
    SsuCoreConfig *settings = SsuCoreConfig::instance();
    return settings->domain(true);
158 159
}

160 161 162 163
bool Ssu::isRegistered()
{
    SsuCoreConfig *settings = SsuCoreConfig::instance();
    return settings->isRegistered();
Aard's avatar
Aard committed
164 165
}

166 167 168 169
QDateTime Ssu::lastCredentialsUpdate()
{
    SsuCoreConfig *settings = SsuCoreConfig::instance();
    return settings->lastCredentialsUpdate();
170 171
}

172 173 174 175
QString Ssu::release(bool rnd)
{
    SsuCoreConfig *settings = SsuCoreConfig::instance();
    return settings->release(rnd);
176 177
}

178 179 180 181
void Ssu::setDeviceMode(Ssu::DeviceModeFlags mode, enum Ssu::EditMode editMode)
{
    SsuCoreConfig *settings = SsuCoreConfig::instance();
    settings->setDeviceMode(mode, editMode);
182 183
}

184 185 186 187
void Ssu::setFlavour(QString flavour)
{
    SsuCoreConfig *settings = SsuCoreConfig::instance();
    settings->setFlavour(flavour);
188 189
}

190 191 192 193
void Ssu::setRelease(QString release, bool rnd)
{
    SsuCoreConfig *settings = SsuCoreConfig::instance();
    settings->setRelease(release, rnd);
194 195
}

196 197 198 199
void Ssu::setDomain(QString domain)
{
    SsuCoreConfig *settings = SsuCoreConfig::instance();
    settings->setDomain(domain);
200 201
}

202 203 204 205
bool Ssu::useSslVerify()
{
    SsuCoreConfig *settings = SsuCoreConfig::instance();
    return settings->useSslVerify();
Aard's avatar
Aard committed
206 207
}

208 209 210 211
//...



212 213 214
QString Ssu::lastError()
{
    return errorString;
Aard's avatar
Aard committed
215 216
}

217 218 219 220 221 222
bool Ssu::registerDevice(QDomDocument *response)
{
    QString certificateString = response->elementsByTagName("certificate").at(0).toElement().text();
    QSslCertificate certificate(certificateString.toLatin1());
    SsuLog *ssuLog = SsuLog::instance();
    SsuCoreConfig *settings = SsuCoreConfig::instance();
Aard's avatar
Aard committed
223

224 225 226 227 228 229 230
    if (certificate.isNull()) {
        // make sure device is in unregistered state on failed registration
        settings->setValue("registered", false);
        setError("Certificate is invalid");
        return false;
    } else
        settings->setValue("certificate", certificate.toPem());
Aard's avatar
Aard committed
231

232 233
    QString privateKeyString = response->elementsByTagName("privateKey").at(0).toElement().text();
    QSslKey privateKey(privateKeyString.toLatin1(), QSsl::Rsa);
Aard's avatar
Aard committed
234

235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257
    if (privateKey.isNull()) {
        settings->setValue("registered", false);
        setError("Private key is invalid");
        return false;
    } else
        settings->setValue("privateKey", privateKey.toPem());

    // oldUser is just for reference purposes, in case we want to notify
    // about owner changes for the device
    QString oldUser = response->elementsByTagName("user").at(0).toElement().text();
    ssuLog->print(LOG_DEBUG, QString("Old user for your device was: %1").arg(oldUser));

    // if we came that far everything required for device registration is done
    settings->setValue("registered", true);
    settings->sync();

    if (!settings->isWritable()) {
        setError("Configuration is not writable, device registration failed.");
        return false;
    }

    emit registrationStatusChanged();
    return true;
Aard's avatar
Aard committed
258 259 260 261
}

// RND repos have flavour (devel, testing, release), and release (latest, next)
// Release repos only have release (latest, next, version number)
262 263
QString Ssu::repoUrl(QString repoName, bool rndRepo,
                     QHash<QString, QString> repoParameters,
264 265 266 267
                     QHash<QString, QString> parametersOverride)
{
    SsuRepoManager manager;
    return manager.url(repoName, rndRepo, repoParameters, parametersOverride);
Aard's avatar
Aard committed
268 269
}

270 271 272 273 274 275 276
void Ssu::requestFinished(QNetworkReply *reply)
{
    QSslConfiguration sslConfiguration = reply->sslConfiguration();
    SsuLog *ssuLog = SsuLog::instance();
    SsuCoreConfig *settings = SsuCoreConfig::instance();
    QNetworkRequest request = reply->request();
    QVariant originalDomainVariant = request.attribute(SSU_NETWORK_REQUEST_DOMAIN_DATA);
Aard's avatar
Aard committed
277

Aard's avatar
Aard committed
278
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
279 280 281
    ssuLog->print(LOG_DEBUG, QString("Certificate used was issued for '%1' by '%2'. Complete chain:")
                  .arg(sslConfiguration.peerCertificate().subjectInfo(QSslCertificate::CommonName).join(""))
                  .arg(sslConfiguration.peerCertificate().issuerInfo(QSslCertificate::CommonName).join("")));
Aard's avatar
Aard committed
282

283 284 285
    foreach (const QSslCertificate cert, sslConfiguration.peerCertificateChain()) {
        ssuLog->print(LOG_DEBUG, QString("-> %1").arg(cert.subjectInfo(QSslCertificate::CommonName).join("")));
    }
Aard's avatar
Aard committed
286
#else
287 288 289
    ssuLog->print(LOG_DEBUG, QString("Certificate used was issued for '%1' by '%2'. Complete chain:")
                  .arg(sslConfiguration.peerCertificate().subjectInfo(QSslCertificate::CommonName))
                  .arg(sslConfiguration.peerCertificate().issuerInfo(QSslCertificate::CommonName)));
Aard's avatar
Aard committed
290

291 292 293
    foreach (const QSslCertificate cert, sslConfiguration.peerCertificateChain()) {
        ssuLog->print(LOG_DEBUG, QString("-> %1").arg(cert.subjectInfo(QSslCertificate::CommonName)));
    }
Aard's avatar
Aard committed
294
#endif
Aard's avatar
Aard committed
295

296
    pendingRequests--;
297

298 299 300 301
    QString action;
    QByteArray data;
    QDomDocument doc;
    QString xmlError;
302

303 304 305 306
    /// @TODO: indicate that the device is not registered if there's a 404 on credentials update url
    if (settings->contains("home-url")) {
        QString homeUrl = settings->value("home-url").toString().arg("");
        homeUrl.remove(QRegExp("//+$"));
307

308 309 310 311 312 313
        if (request.url().toString().startsWith(homeUrl, Qt::CaseInsensitive)) {
            // we don't care about errors on download request
            if (reply->error() == 0) {
                QByteArray data = reply->readAll();
                storeAuthorizedKeys(data);
            }
314

315 316
            goto success;
        }
317
    }
Aard's avatar
Aard committed
318

319 320 321 322
    if (reply->error() > 0) {
        setError(reply->errorString());
        goto failure;
    }
Aard's avatar
Aard committed
323

324 325 326
    data = reply->readAll();
    ssuLog->print(LOG_DEBUG, QString("RequestOutput %1")
                  .arg(data.data()));
Aard's avatar
Aard committed
327

328 329 330 331
    if (!doc.setContent(data, &xmlError)) {
        setError(tr("Unable to parse server response (%1)").arg(xmlError));
        goto failure;
    }
332

333
    action = doc.elementsByTagName("action").at(0).toElement().text();
334

335 336
    if (!verifyResponse(&doc)) {
        goto failure;
337
    }
338 339 340 341 342 343 344 345 346 347 348 349 350

    ssuLog->print(LOG_DEBUG, QString("Handling request of type %1")
                  .arg(action));
    if (action == "register") {
        if (registerDevice(&doc)) {
            goto success;
        }
    } else if (action == "credentials") {
        if (setCredentials(&doc)) {
            goto success;
        }
    } else {
        setError(tr("Response to unknown action encountered: %1").arg(action));
Aard's avatar
Aard committed
351 352
    }

353
failure:
354 355 356 357 358 359 360 361
    // Restore the original domain in case of failures with the registration
    if (!originalDomainVariant.isNull()) {
        QString originalDomain = originalDomainVariant.toString();
        ssuLog->print(LOG_DEBUG, QString("Restoring domain on error: '%1'").arg(originalDomain));
        setDomain(originalDomain);
    }

    // Fall through to cleanup handling in success from failure label
362
success:
363 364 365 366
    ssuLog->print(LOG_DEBUG, QString("Request finished, pending requests: %1").arg(pendingRequests));
    if (pendingRequests == 0) {
        emit done();
    }
Aard's avatar
Aard committed
367 368
}

369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394
void Ssu::sendRegistration(QString usernameDomain, QString password)
{
    errorFlag = false;

    QString ssuCaCertificate, ssuRegisterUrl;
    QString username, domainName;

    SsuLog *ssuLog = SsuLog::instance();
    SsuCoreConfig *settings = SsuCoreConfig::instance();
    SsuDeviceInfo deviceInfo;

    QNetworkRequest request;
    request.setAttribute(SSU_NETWORK_REQUEST_DOMAIN_DATA, domain());
    ssuLog->print(LOG_DEBUG, QString("Saving current domain before request: '%1'").arg(domain()));

    // Username can include also domain, (user@domain), separate those
    if (usernameDomain.contains('@')) {
        // separate domain/username and set domain
        username = usernameDomain.section('@', 0, 0);
        domainName = usernameDomain.section('@', 1, 1);
        setDomain(domainName);
    } else {
        // No domain defined
        username = usernameDomain;
        if (settings->contains("default-rnd-domain"))
            setDomain(settings->value("default-rnd-domain").toString());
395
    }
Aard's avatar
Aard committed
396

397 398 399 400 401
    ssuCaCertificate = SsuRepoManager::caCertificatePath();
    if (ssuCaCertificate.isEmpty()) {
        setError("CA certificate for ssu not set ('_ca-certificate in domain')");
        return;
    }
Aard's avatar
Aard committed
402

403 404 405 406 407 408 409 410
    if (!settings->contains("register-url")) {
        ssuRegisterUrl = repoUrl("register-url");
        if (ssuRegisterUrl.isEmpty()) {
            setError("URL for ssu registration not set (config key 'register-url')");
            return;
        }
    } else
        ssuRegisterUrl = settings->value("register-url").toString();
Aard's avatar
Aard committed
411

412 413 414 415 416 417 418 419 420
    QString IMEI = deviceInfo.deviceUid();
    if (IMEI == "") {
        setError("No valid UID available for your device. For phones: is your modem online?");
        return;
    }

    QSslConfiguration sslConfiguration;
    if (!useSslVerify())
        sslConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone);
Aard's avatar
Aard committed
421

422
    sslConfiguration.setCaCertificates(QSslCertificate::fromPath(ssuCaCertificate));
Aard's avatar
Aard committed
423

424 425 426 427 428 429 430 431 432 433 434
    request.setUrl(QUrl(QString(ssuRegisterUrl)
                        .arg(IMEI)
                       ));
    request.setSslConfiguration(sslConfiguration);
    request.setRawHeader("Authorization", "Basic " +
                         QByteArray(QString("%1:%2")
                                    .arg(username).arg(password)
                                    .toLatin1()).toBase64());
    request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");

    QUrl form;
Aard's avatar
Aard committed
435 436

#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
437 438 439 440 441 442 443 444
    QUrlQuery q;
    q.addQueryItem("protocolVersion", SSU_PROTOCOL_VERSION);
    q.addQueryItem("deviceModel", deviceInfo.deviceModel());
    if (!domain().isEmpty()) {
        q.addQueryItem("domain", domain());
    }

    form.setQuery(q);
Aard's avatar
Aard committed
445
#else
446 447 448 449 450
    form.addQueryItem("protocolVersion", SSU_PROTOCOL_VERSION);
    form.addQueryItem("deviceModel", deviceInfo.deviceModel());
    if (!domain().isEmpty()) {
        form.addQueryItem("domain", domain());
    }
Aard's avatar
Aard committed
451
#endif
Aard's avatar
Aard committed
452

453 454
    ssuLog->print(LOG_DEBUG, QString("Sending request to %1")
                  .arg(request.url().url()));
455

456
    QNetworkReply *reply;
Aard's avatar
Aard committed
457

458
    pendingRequests++;
Aard's avatar
Aard committed
459
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
460
    reply = manager->post(request, form.query(QUrl::FullyEncoded).toStdString().c_str());
Aard's avatar
Aard committed
461
#else
462
    reply = manager->post(request, form.encodedQuery());
Aard's avatar
Aard committed
463
#endif
464 465 466 467 468 469 470 471 472 473
    // we could expose downloadProgress() from reply in case we want progress info

    QString homeUrl = settings->value("home-url").toString().arg(username);
    if (!homeUrl.isEmpty()) {
        // clear header, the other request bits are reusable
        request.setHeader(QNetworkRequest::ContentTypeHeader, 0);
        request.setUrl(homeUrl + "/authorized_keys");
        ssuLog->print(LOG_DEBUG, QString("Trying to get SSH keys from %1").arg(request.url().toString()));
        pendingRequests++;
        manager->get(request);
Aard's avatar
Aard committed
474
    }
475
}
Aard's avatar
Aard committed
476

477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512
bool Ssu::setCredentials(QDomDocument *response)
{
    SsuCoreConfig *settings = SsuCoreConfig::instance();
    // generate list with all scopes for generic section, add sections
    QDomNodeList credentialsList = response->elementsByTagName("credentials");
    QStringList credentialScopes;
    for (int i = 0; i < credentialsList.size(); i++) {
        QDomNode node = credentialsList.at(i);
        QString scope;

        QDomNamedNodeMap attributes = node.attributes();
        if (attributes.contains("scope")) {
            scope = attributes.namedItem("scope").toAttr().value();
        } else {
            setError(tr("Credentials element does not have scope"));
            return false;
        }

        if (node.hasChildNodes()) {
            QDomElement username = node.firstChildElement("username");
            QDomElement password = node.firstChildElement("password");
            if (username.isNull() || password.isNull()) {
                setError(tr("Username and/or password not set"));
                return false;
            } else {
                settings->beginGroup("credentials-" + scope);
                settings->setValue("username", username.text());
                settings->setValue("password", password.text());
                settings->endGroup();
                settings->sync();
                credentialScopes.append(scope);
            }
        } else {
            setError("");
            return false;
        }
Aard's avatar
Aard committed
513
    }
514 515 516 517
    settings->setValue("credentialScopes", credentialScopes);
    settings->setValue("lastCredentialsUpdate", QDateTime::currentDateTime());
    settings->sync();
    emit credentialsChanged();
Aard's avatar
Aard committed
518

519
    return true;
Aard's avatar
Aard committed
520 521
}

522 523 524 525
void Ssu::setError(QString errorMessage)
{
    errorFlag = true;
    errorString = errorMessage;
526

527
    SsuLog *ssuLog = SsuLog::instance();
528

529 530
    // dump error message to systemd journal for easier debugging
    ssuLog->print(LOG_WARNING, errorMessage);
531

532 533 534
    // assume that we don't even need to wait for other pending requests,
    // and just die. This is only relevant for CLI, which will exit after done()
    emit done();
Aard's avatar
Aard committed
535 536
}

537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566
void Ssu::storeAuthorizedKeys(QByteArray data)
{
    QDir dir;
    SsuLog *ssuLog = SsuLog::instance();

    int uid_min = getdef_num("UID_MIN", -1);
    QString homePath;

    if (getuid() >= uid_min) {
        homePath = dir.homePath();
    } else if (getuid() == 0) {
        // place authorized_keys in the default users home when run with uid0
        struct passwd *pw = getpwuid(uid_min);
        if (pw == NULL) {
            ssuLog->print(LOG_DEBUG, QString("Unable to find password entry for uid %1")
                          .arg(uid_min));
            return;
        }

        //homePath = QString(pw->pw_dir);
        homePath = pw->pw_dir;

        // use users uid/gid for creating the directories and files
        setegid(pw->pw_gid);
        seteuid(uid_min);
        ssuLog->print(LOG_DEBUG, QString("Dropping to %1/%2 for writing authorized keys")
                      .arg(uid_min)
                      .arg(pw->pw_gid));
    } else
        return;
567

568
    homePath = Sandbox::map(homePath);
569

570 571 572 573 574 575
    if (dir.exists(homePath + "/.ssh/authorized_keys")) {
        ssuLog->print(LOG_DEBUG, QString(".ssh/authorized_keys already exists in %1")
                      .arg(homePath));
        restoreUid();
        return;
    }
576

577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594
    if (!dir.exists(homePath + "/.ssh"))
        if (!dir.mkdir(homePath + "/.ssh")) {
            ssuLog->print(LOG_DEBUG, QString("Unable to create .ssh in %1")
                          .arg(homePath));
            restoreUid();
            return;
        }

    QFile::setPermissions(homePath + "/.ssh",
                          QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner);

    QFile authorizedKeys(homePath + "/.ssh/authorized_keys");
    authorizedKeys.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate);
    authorizedKeys.setPermissions(QFile::ReadOwner | QFile::WriteOwner);
    QTextStream out(&authorizedKeys);
    out << data;
    out.flush();
    authorizedKeys.close();
595

596
    restoreUid();
597
}
598

599 600 601 602 603
void Ssu::updateCredentials(bool force)
{
    SsuCoreConfig *settings = SsuCoreConfig::instance();
    SsuDeviceInfo deviceInfo;
    errorFlag = false;
604

605
    SsuLog *ssuLog = SsuLog::instance();
606

607 608 609 610
    if (deviceInfo.deviceUid() == "") {
        setError("No valid UID available for your device. For phones: is your modem online?");
        return;
    }
611

612 613 614 615 616
    QString ssuCaCertificate, ssuCredentialsUrl;
    ssuCaCertificate = SsuRepoManager::caCertificatePath();
    if (ssuCaCertificate.isEmpty()) {
        setError("CA certificate for ssu not set ('_ca-certificate in domain')");
        return;
617
    }
618 619 620 621 622 623 624 625 626 627 628 629

    if (!settings->contains("credentials-url")) {
        ssuCredentialsUrl = repoUrl("credentials-url");
        if (ssuCredentialsUrl.isEmpty()) {
            setError("URL for credentials update not set (config key 'credentials-url')");
            return;
        }
    } else
        ssuCredentialsUrl = settings->value("credentials-url").toString();

    if (!isRegistered()) {
        setError("Device is not registered.");
Aard's avatar
Aard committed
630 631 632
        return;
    }

633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654
    if (!force) {
        // skip updating if the last update was less than 30 minutes ago
        QDateTime now = QDateTime::currentDateTime();

        if (settings->contains("lastCredentialsUpdate")) {
            QDateTime last = settings->value("lastCredentialsUpdate").toDateTime();
            if (last >= now.addSecs(-1800)) {
                ssuLog->print(LOG_DEBUG, QString("Skipping credentials update, last update was at %1")
                              .arg(last.toString()));
                emit done();
                return;
            }
        }
    }

    // check when the last update was, decide if an update is required
    QSslConfiguration sslConfiguration;
    if (!useSslVerify())
        sslConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone);

    QSslKey privateKey(settings->value("privateKey").toByteArray(), QSsl::Rsa);
    QSslCertificate certificate(settings->value("certificate").toByteArray());
Aard's avatar
Aard committed
655

656 657 658
    QList<QSslCertificate> caCertificates;
    caCertificates << QSslCertificate::fromPath(ssuCaCertificate);
    sslConfiguration.setCaCertificates(caCertificates);
Aard's avatar
Aard committed
659

660 661
    sslConfiguration.setPrivateKey(privateKey);
    sslConfiguration.setLocalCertificate(certificate);
Aard's avatar
Aard committed
662

663 664
    QNetworkRequest request;
    request.setUrl(QUrl(ssuCredentialsUrl.arg(deviceInfo.deviceUid())));
Aard's avatar
Aard committed
665

666 667 668
    ssuLog->print(LOG_DEBUG, QString("Sending credential update request to %1")
                  .arg(request.url().toString()));
    request.setSslConfiguration(sslConfiguration);
Aard's avatar
Aard committed
669

670 671 672
    pendingRequests++;
    manager->get(request);
}
Aard's avatar
Aard committed
673

674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698
void Ssu::updateStoreCredentials()
{
    SsuCoreConfig *settings = SsuCoreConfig::instance();
    SsuLog *ssuLog = SsuLog::instance();

    QDBusMessage message = QDBusMessage::createMethodCall("com.jolla.jollastore",
                                                          "/StoreClient",
                                                          "com.jolla.jollastore",
                                                          "storeCredentials");
    QDBusPendingReply<QString, QString> reply = SsuCoreConfig::userSessionBus().asyncCall(message);
    reply.waitForFinished();
    if (reply.isError()) {
        if (settings->value("ignore-credential-errors").toBool() == true) {
            ssuLog->print(LOG_WARNING, QString("Warning: ignore-credential-errors is set, passing auth errors down to libzypp"));
            ssuLog->print(LOG_WARNING, QString("Store credentials not received. %1").arg(reply.error().message()));
        } else
            setError(QString("Store credentials not received. %1").arg(reply.error().message()));
    } else {
        SsuCoreConfig *settings = SsuCoreConfig::instance();
        settings->beginGroup("credentials-store");
        settings->setValue("username", reply.argumentAt<0>());
        settings->setValue("password", reply.argumentAt<1>());
        settings->endGroup();
        settings->sync();
    }
Aard's avatar
Aard committed
699 700
}

701 702
void Ssu::unregister()
{
703
    SsuCoreConfig *settings = SsuCoreConfig::instance();
704 705 706
    settings->setValue("privateKey", "");
    settings->setValue("certificate", "");
    settings->setValue("registered", false);
707
    settings->sync();
708
    emit registrationStatusChanged();
709
}
Aard's avatar
Aard committed
710

711 712 713 714 715 716 717 718 719 720 721 722 723 724 725
bool Ssu::verifyResponse(QDomDocument *response)
{
    QString action = response->elementsByTagName("action").at(0).toElement().text();
    QString deviceId = response->elementsByTagName("deviceId").at(0).toElement().text();
    QString protocolVersion = response->elementsByTagName("protocolVersion").at(0).toElement().text();
    // compare device ids

    if (protocolVersion != SSU_PROTOCOL_VERSION) {
        setError(
            tr("Response has unsupported protocol version %1, client requires version %2")
            .arg(protocolVersion)
            .arg(SSU_PROTOCOL_VERSION)
        );
        return false;
    }
Aard's avatar
Aard committed
726

727
    return true;
Aard's avatar
Aard committed
728
}