File indexing completed on 2025-01-05 04:55:04

0001 /*
0002     Copyright (c) 2017 Christian Mollekopf <mollekopf@kolabsys.com>
0003 
0004     This library is free software; you can redistribute it and/or modify it
0005     under the terms of the GNU Library General Public License as published by
0006     the Free Software Foundation; either version 2 of the License, or (at your
0007     option) any later version.
0008 
0009     This library is distributed in the hope that it will be useful, but WITHOUT
0010     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0011     FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
0012     License for more details.
0013 
0014     You should have received a copy of the GNU Library General Public License
0015     along with this library; see the file COPYING.LIB.  If not, write to the
0016     Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
0017     02110-1301, USA.
0018 */
0019 #include "keyring.h"
0020 
0021 #include <sink/secretstore.h>
0022 #include <QtGlobal>
0023 #include <QStandardPaths>
0024 #include <QDataStream>
0025 #include <QSettings>
0026 #include <QVariantMap>
0027 #include <QVariant>
0028 #include <QMap>
0029 #include <QDebug>
0030 #include "async.h"
0031 
0032 using namespace Kube;
0033 
0034 Q_GLOBAL_STATIC(Keyring, sKeyring);
0035 
0036 
0037 static void storeSecret(const QByteArray &accountId, const std::vector<Crypto::Key> &keys, const QVariantMap &secret)
0038 {
0039     QByteArray secretBA;
0040     QDataStream stream(&secretBA, QIODevice::WriteOnly);
0041     stream << secret;
0042     if (auto result = Crypto::signAndEncrypt(secretBA, keys, {})) {
0043         QSettings settings(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QString("/kube/secrets.ini"), QSettings::IniFormat);
0044         settings.setValue(accountId, result.value());
0045     } else {
0046         qWarning() << "Failed to encrypt account secret " << accountId;
0047     }
0048 }
0049 
0050 static QVariantMap loadSecret(const QByteArray &accountId)
0051 {
0052     QSettings settings(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QString("/kube/secrets.ini"), QSettings::IniFormat);
0053 
0054     QByteArray secretBA;
0055     const auto encrypted = settings.value(accountId).value<QByteArray>();
0056     if (encrypted.isEmpty()) {
0057         return {};
0058     }
0059     Crypto::decryptAndVerify(Crypto::OpenPGP, encrypted, secretBA);
0060 
0061     QVariantMap map;
0062     QDataStream stream(&secretBA, QIODevice::ReadOnly);
0063     stream >> map;
0064     return map;
0065 }
0066 
0067 
0068 Keyring::Keyring()
0069     : QObject()
0070 {
0071 
0072 }
0073 
0074 Keyring *Keyring::instance()
0075 {
0076     return sKeyring;
0077 }
0078 
0079 bool Keyring::isUnlocked(const QByteArray &accountId)
0080 {
0081     return mUnlocked.contains(accountId);
0082 }
0083 
0084 void Keyring::unlock(const QByteArray &accountId)
0085 {
0086     mUnlocked.insert(accountId);
0087 }
0088 
0089 void Keyring::addPassword(const QByteArray &resourceId, const QString &password)
0090 {
0091     Sink::SecretStore::instance().insert(resourceId, password);
0092 }
0093 
0094 void Keyring::tryUnlock(const QByteArray &accountId)
0095 {
0096     if (isUnlocked(accountId)) {
0097         qInfo() << "Already unlocked" << accountId;
0098         return;
0099     }
0100 
0101     asyncRun<QVariantMap>(this, [=] {
0102             return loadSecret(accountId);
0103         },
0104         [=](const QVariantMap &secrets) {
0105             for (const auto &resource : secrets.keys()) {
0106                 //"accountSecret" is a magic value from the gpg extension for a key that is used for all resources of the same account.
0107                 //We ignore it to avoid pretending the account is unlocked.
0108                 if (resource == "accountSecret") {
0109                     continue;
0110                 }
0111                 auto secret = secrets.value(resource);
0112                 if (secret.isValid()) {
0113                     addPassword(resource.toLatin1(), secret.toString());
0114                     unlock(accountId);
0115                 } else {
0116                     qWarning() << "Found no stored secret for " << resource;
0117                 }
0118             }
0119             emit unlocked(accountId);
0120         });
0121 }
0122 
0123 AccountKeyring::AccountKeyring(const QByteArray &accountId, QObject *parent)
0124     : QObject(parent),
0125     mAccountIdentifier(accountId)
0126 {
0127 }
0128 
0129 void AccountKeyring::addPassword(const QByteArray &resourceId, const QString &password)
0130 {
0131     Keyring::instance()->addPassword(resourceId, password);
0132     Keyring::instance()->unlock(mAccountIdentifier);
0133     //FIXME: We keep track of secrets stored via this account for when we persist it,
0134     //because we have no other good means to do so.
0135     mAccountResources << resourceId;
0136 }
0137 
0138 void AccountKeyring::save(const std::vector<Crypto::Key> &keys)
0139 {
0140     QVariantMap secrets;
0141     for (const auto &resourceId : mAccountResources) {
0142         secrets.insert(resourceId, Sink::SecretStore::instance().resourceSecret(resourceId));
0143     }
0144     storeSecret(mAccountIdentifier, keys, secrets);
0145 }
0146 
0147 void AccountKeyring::load()
0148 {
0149     asyncRun<QVariantMap>(this, [=] {
0150             const auto secrets = loadSecret(mAccountIdentifier);
0151             return secrets;
0152         },
0153         [this](const QVariantMap &secrets) {
0154             for (const auto &resource : secrets.keys()) {
0155                 //"accountSecret" is a magic value from the gpg extension for a key that is used for all resources of the same account.
0156                 //We ignore it to avoid pretending the account is unlocked.
0157                 if (resource == "accountSecret") {
0158                     continue;
0159                 }
0160                 auto secret = secrets.value(resource);
0161                 if (secret.isValid()) {
0162                     qWarning() << "Found stored secret for " << resource;
0163                     addPassword(resource.toLatin1(), secret.toString());
0164                 } else {
0165                     qWarning() << "Found no stored secret for " << resource;
0166                 }
0167             }
0168             emit loaded();
0169         });
0170 }