[OpenDocString] kdeconnect-kde (cpp)
kdeconnectconfig.cpp
static QString getDefaultDeviceName()
{
#ifdef SAILFISHOS
    const QString hwReleaseFile = QStringLiteral("/etc/hw-release");
    // QSettings will crash if the file does not exist or can be created, like in this case by us in /etc.
    // E.g. in the SFOS SDK Emulator there is no such file, so check before to protect against the crash.
    if (QFile::exists(hwReleaseFile)) {
        QSettings hwRelease(hwReleaseFile, QSettings::IniFormat);
        auto hwName = hwRelease.value(QStringLiteral("NAME")).toString();
        if (!hwName.isEmpty()) {
            return hwName;
        }
    }
#endif

    return QHostInfo::localHostName();
}
This method returns the local host name from QHostInfo. For SailfishOS, it gets that name from /etc/hw-release if it exists.
KdeConnectConfig &KdeConnectConfig::instance()
{
    static KdeConnectConfig kcc;
    return kcc;
}
This implements the singleton design pattern and returns a reference to a static instance of the KdeConnectConfig class. The object is created only once and persists for the lifetime of the program.
KdeConnectConfig::KdeConnectConfig()
    : d(new KdeConnectConfigPrivate)
{
    // qCDebug(KDECONNECT_CORE) << "QCA supported capabilities:" << QCA::supportedFeatures().join(",");
    if (!QCA::isSupported("rsa")) {
        qCritical() << "Could not find support for RSA in your QCA installation";
        Daemon::instance()->reportError(i18n("KDE Connect failed to start"),
                                        i18n("Could not find support for RSA in your QCA installation. If your "
                                             "distribution provides separate packets for QCA-ossl and QCA-gnupg, "
                                             "make sure you have them installed and try again."));
    }

    // Make sure base directory exists
    QDir().mkpath(baseConfigDir().path());

    //.config/kdeconnect/config
    d->m_config = new QSettings(baseConfigDir().absoluteFilePath(QStringLiteral("config")), QSettings::IniFormat);
    d->m_trustedDevices = new QSettings(baseConfigDir().absoluteFilePath(QStringLiteral("trusted_devices")), QSettings::IniFormat);

    loadPrivateKey();
    loadCertificate();

    if (name().isEmpty()) {
        setName(getDefaultDeviceName());
    }
}
This constructor builds a KdeConnectConfig object and its d-pointer. It makes sure the base directory exists, loads the settings and trusted devices to the d-pointer object. It also loads private key and certificate and sets the default device name to its internal representation.
QString KdeConnectConfig::name()
{
    return d->m_config->value(QStringLiteral("name")).toString();
}
Returns the name stored in the internal configuration list.
void KdeConnectConfig::setName(const QString &name)
{
    d->m_config->setValue(QStringLiteral("name"), name);
    d->m_config->sync();
}
Sets the name value within in the KdeConnect config object, and synchronizes the configuration.
QString KdeConnectConfig::deviceType()
{
#ifdef SAILFISHOS
    return QStringLiteral("phone");
#else
    const QByteArrayList platforms = qgetenv("PLASMA_PLATFORM").split(':');

    if (platforms.contains("phone")) {
        return QStringLiteral("phone");
    } else if (platforms.contains("tablet")) {
        return QStringLiteral("tablet");
    } else if (platforms.contains("mediacenter")) {
        return QStringLiteral("tv");
    }

    // TODO non-Plasma mobile platforms

    return QStringLiteral("desktop");
#endif
}
Returns the device type as a string.
QString KdeConnectConfig::deviceId()
{
    return d->m_certificate.subjectInfo(QSslCertificate::CommonName).constFirst();
}
Returns the device id as a QString, reading it from the certificate information.
QString KdeConnectConfig::privateKeyPath()
{
    return baseConfigDir().absoluteFilePath(QStringLiteral("privateKey.pem"));
}
Returns the private key path as a QString by reading it from the privateKey.pem file path.
QString KdeConnectConfig::certificatePath()
{
    return baseConfigDir().absoluteFilePath(QStringLiteral("certificate.pem"));
}
Returns the certificate path as a QString by reading it from the certificate.pem file path.
QSslCertificate KdeConnectConfig::certificate()
{
    return d->m_certificate;
}
Returns a copy of the certificate object, which uses implicit sharing for its data.
QDir KdeConnectConfig::baseConfigDir()
{
    QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
    QString kdeconnectConfigPath = QDir(configPath).absoluteFilePath(QStringLiteral("kdeconnect"));
    return QDir(kdeconnectConfigPath);
}
Returns the base config path as a QDir object. The config path is the kdeconnect folder inside of the directory where user-specific configuration files shared between multiple applications reside.
QStringList KdeConnectConfig::trustedDevices()
{
    const QStringList &list = d->m_trustedDevices->childGroups();
    return list;
}
Returns a list of trusted devices.
void KdeConnectConfig::addTrustedDevice(const QString &id, const QString &name, const QString &type)
{
    d->m_trustedDevices->beginGroup(id);
    d->m_trustedDevices->setValue(QStringLiteral("name"), name);
    d->m_trustedDevices->setValue(QStringLiteral("type"), type);
    d->m_trustedDevices->endGroup();
    d->m_trustedDevices->sync();

    QDir().mkpath(deviceConfigDir(id).path());
}
Adds a trusted device by setting its id, name and type in the KdeConnect config object, and synchronizes with the settings.
KdeConnectConfig::DeviceInfo KdeConnectConfig::getTrustedDevice(const QString &id)
{
    d->m_trustedDevices->beginGroup(id);

    KdeConnectConfig::DeviceInfo info;
    info.deviceName = d->m_trustedDevices->value(QStringLiteral("name"), QLatin1String("unnamed")).toString();
    info.deviceType = d->m_trustedDevices->value(QStringLiteral("type"), QLatin1String("unknown")).toString();

    d->m_trustedDevices->endGroup();
    return info;
}
This retrieves the trusted device information for the given id, and returns a KdeConnectConfig object.
void KdeConnectConfig::removeTrustedDevice(const QString &deviceId)
{
    d->m_trustedDevices->remove(deviceId);
    d->m_trustedDevices->sync();
    // We do not remove the config files.
}
This removes the trusted device of the given device id, and syncs with the settings backend.
void KdeConnectConfig::setDeviceProperty(const QString &deviceId, const QString &key, const QString &value)
{
    // do not store values for untrusted devices (it would make them trusted)
    if (!trustedDevices().contains(deviceId))
        return;

    d->m_trustedDevices->beginGroup(deviceId);
    d->m_trustedDevices->setValue(key, value);
    d->m_trustedDevices->endGroup();
    d->m_trustedDevices->sync();
}
Adds a device property to the device of the given id, with a key and a value.
QString KdeConnectConfig::getDeviceProperty(const QString &deviceId, const QString &key, const QString &defaultValue)
{
    QString value;
    d->m_trustedDevices->beginGroup(deviceId);
    value = d->m_trustedDevices->value(key, defaultValue).toString();
    d->m_trustedDevices->endGroup();
    return value;
}
Returns the property value within a given deviceId and a key.
void KdeConnectConfig::setCustomDevices(const QStringList &addresses)
{
    d->m_config->setValue(QStringLiteral("customDevices"), addresses);
    d->m_config->sync();
}
Sets addresses of custom devices to the internal config object and syncs the settings.
QStringList KdeConnectConfig::customDevices() const
{
    return d->m_config->value(QStringLiteral("customDevices")).toStringList();
}
Returns the list of custom devices stored in the internal configuration list.
QDir KdeConnectConfig::deviceConfigDir(const QString &deviceId)
{
    QString deviceConfigPath = baseConfigDir().absoluteFilePath(deviceId);
    return QDir(deviceConfigPath);
}
Returns the device config path as a QString.
QDir KdeConnectConfig::pluginConfigDir(const QString &deviceId, const QString &pluginName)
{
    QString deviceConfigPath = baseConfigDir().absoluteFilePath(deviceId);
    QString pluginConfigDir = QDir(deviceConfigPath).absoluteFilePath(pluginName);
    return QDir(pluginConfigDir);
}
Returns the absolute path of the plugin configurations.
void KdeConnectConfig::loadPrivateKey()
{
    QString keyPath = privateKeyPath();
    QFile privKey(keyPath);

    bool needsToGenerateKey = false;
    if (privKey.exists() && privKey.open(QIODevice::ReadOnly)) {
        QCA::ConvertResult result;
        d->m_privateKey = QCA::PrivateKey::fromPEM(QString::fromLatin1(privKey.readAll()), QCA::SecureArray(), &result);
        if (result != QCA::ConvertResult::ConvertGood) {
            qCWarning(KDECONNECT_CORE) << "Private key from" << keyPath << "is not valid";
            needsToGenerateKey = true;
        }
    } else {
        needsToGenerateKey = true;
    }

    if (needsToGenerateKey) {
        generatePrivateKey(keyPath);
    }

    // Extra security check
    if (QFile::permissions(keyPath) != strictPermissions) {
        qCWarning(KDECONNECT_CORE) << "Warning: KDE Connect private key file has too open permissions " << keyPath;
    }
}
This loads the private key from the given PEM file, or generates a new one if it doesn't exist. It logs a warning if the file doesn't have strict permissions.
void KdeConnectConfig::loadCertificate()
{
    QString certPath = certificatePath();
    QFile cert(certPath);

    bool needsToGenerateCert = false;
    if (cert.exists() && cert.open(QIODevice::ReadOnly)) {
        auto loadedCerts = QSslCertificate::fromPath(certPath);
        if (loadedCerts.empty()) {
            qCWarning(KDECONNECT_CORE) << "Certificate from" << certPath << "is not valid";
            needsToGenerateCert = true;
        } else {
            d->m_certificate = loadedCerts.at(0);
        }
    } else {
        needsToGenerateCert = true;
    }

    if (needsToGenerateCert) {
        generateCertificate(certPath);
    }

    // Extra security check
    if (QFile::permissions(certPath) != strictPermissions) {
        qCWarning(KDECONNECT_CORE) << "Warning: KDE Connect certificate file has too open permissions " << certPath;
    }
}
This loads the certificate from the certificate path, if it exists, and generates it if it doesn't exist or is invalid. It shows a warning message when the file has not strict permissions.
void KdeConnectConfig::generatePrivateKey(const QString &keyPath)
{
    qCDebug(KDECONNECT_CORE) << "Generating private key";

    bool error = false;

    d->m_privateKey = QCA::KeyGenerator().createRSA(2048);

    QFile privKey(keyPath);
    if (!privKey.open(QIODevice::ReadWrite | QIODevice::Truncate)) {
        error = true;
    } else {
        privKey.setPermissions(strictPermissions);
        int written = privKey.write(d->m_privateKey.toPEM().toLatin1());
        if (written <= 0) {
            error = true;
        }
    }

    if (error) {
        Daemon::instance()->reportError(QStringLiteral("KDE Connect"), i18n("Could not store private key file: %1", keyPath));
    }
}
This generates a private key using RSA 2048 and writes it to the given PEM file with strict permissions. It returns false if the file cannot be opened or the key couldn't be written.
void KdeConnectConfig::generateCertificate(const QString &certPath)
{
    qCDebug(KDECONNECT_CORE) << "Generating certificate";

    bool error = false;

    QString uuid = QUuid::createUuid().toString();
    DBusHelper::filterNonExportableCharacters(uuid);
    qCDebug(KDECONNECT_CORE) << "My id:" << uuid;

    // FIXME: We only use QCA here to generate the cert and key, would be nice to get rid of it completely.
    // The same thing we are doing with QCA could be done invoking openssl (although it's potentially less portable):
    // openssl req -new -x509 -sha256 -newkey rsa:2048 -nodes -keyout privateKey.pem -days 3650 -out certificate.pem -subj "/O=KDE/OU=KDE
    // Connect/CN=_e6e29ad4_2b31_4b6d_8f7a_9872dbaa9095_"

    QCA::CertificateOptions certificateOptions = QCA::CertificateOptions();
    QDateTime startTime = QDateTime::currentDateTime().addYears(-1);
    QDateTime endTime = startTime.addYears(10);
    QCA::CertificateInfo certificateInfo;
    certificateInfo.insert(QCA::CommonName, uuid);
    certificateInfo.insert(QCA::Organization, QStringLiteral("KDE"));
    certificateInfo.insert(QCA::OrganizationalUnit, QStringLiteral("Kde connect"));
    certificateOptions.setInfo(certificateInfo);
    certificateOptions.setFormat(QCA::PKCS10);
    certificateOptions.setSerialNumber(QCA::BigInteger(10));
    certificateOptions.setValidityPeriod(startTime, endTime);

    d->m_certificate = QSslCertificate(QCA::Certificate(certificateOptions, d->m_privateKey).toPEM().toLatin1());

    QFile cert(certPath);
    if (!cert.open(QIODevice::ReadWrite | QIODevice::Truncate)) {
        error = true;
    } else {
        cert.setPermissions(strictPermissions);
        int written = cert.write(d->m_certificate.toPem());
        if (written <= 0) {
            error = true;
        }
    }

    if (error) {
        Daemon::instance()->reportError(QStringLiteral("KDE Connect"), i18n("Could not store certificate file: %1", certPath));
    }
}
This generates a certificate with PKCS10, assigns it to its internal d-pointer and writes it to the file, and writes it to the given certificate file. It creates a new uuid, sets start time now and end time 10 years later, and adds KDE and Kde connect to the certificate info.