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 }