mirror of
https://github.com/nextcloud/desktop.git
synced 2025-10-26 11:17:43 +00:00
Merge pull request #5586 from nextcloud/bugfix/e2eeBetterInit
Bugfix/e2ee better init
This commit is contained in:
commit
bea7bd1a4b
@ -1561,13 +1561,7 @@ void AccountSettings::initializeE2eEncryption()
|
||||
if (!_accountState->account()->e2e()->_mnemonic.isEmpty()) {
|
||||
slotE2eEncryptionMnemonicReady();
|
||||
} else {
|
||||
_ui->encryptionMessage->setMessageType(KMessageWidget::Information);
|
||||
_ui->encryptionMessage->setText(tr("This account supports end-to-end encryption"));
|
||||
_ui->encryptionMessage->setIcon(Theme::createColorAwareIcon(QStringLiteral(":/client/theme/black/state-info.svg")));
|
||||
_ui->encryptionMessage->hide();
|
||||
|
||||
auto *const actionEnableE2e = addActionToEncryptionMessage(tr("Set up encryption"), e2EeUiActionEnableEncryptionId);
|
||||
connect(actionEnableE2e, &QAction::triggered, this, &AccountSettings::slotE2eEncryptionGenerateKeys);
|
||||
initializeE2eEncryptionSettingsMessage();
|
||||
|
||||
connect(_accountState->account()->e2e(), &ClientSideEncryption::initializationFinished, this, [this] {
|
||||
if (!_accountState->account()->e2e()->_publicKey.isNull()) {
|
||||
@ -1590,7 +1584,7 @@ void AccountSettings::resetE2eEncryption()
|
||||
}
|
||||
_ui->encryptionMessage->setText({});
|
||||
_ui->encryptionMessage->setIcon({});
|
||||
initializeE2eEncryption();
|
||||
initializeE2eEncryptionSettingsMessage();
|
||||
checkClientSideEncryptionState();
|
||||
|
||||
const auto account = _accountState->account();
|
||||
@ -1627,6 +1621,17 @@ QAction *AccountSettings::addActionToEncryptionMessage(const QString &actionTitl
|
||||
return action;
|
||||
}
|
||||
|
||||
void AccountSettings::initializeE2eEncryptionSettingsMessage()
|
||||
{
|
||||
_ui->encryptionMessage->setMessageType(KMessageWidget::Information);
|
||||
_ui->encryptionMessage->setText(tr("This account supports end-to-end encryption"));
|
||||
_ui->encryptionMessage->setIcon(Theme::createColorAwareIcon(QStringLiteral(":/client/theme/black/state-info.svg")));
|
||||
_ui->encryptionMessage->hide();
|
||||
|
||||
auto *const actionEnableE2e = addActionToEncryptionMessage(tr("Set up encryption"), e2EeUiActionEnableEncryptionId);
|
||||
connect(actionEnableE2e, &QAction::triggered, this, &AccountSettings::slotE2eEncryptionGenerateKeys);
|
||||
}
|
||||
|
||||
} // namespace OCC
|
||||
|
||||
#include "accountsettings.moc"
|
||||
|
||||
@ -132,6 +132,8 @@ private:
|
||||
bool event(QEvent *) override;
|
||||
QAction *addActionToEncryptionMessage(const QString &actionTitle, const QString &actionId);
|
||||
|
||||
void initializeE2eEncryptionSettingsMessage();
|
||||
|
||||
/// Returns the alias of the selected folder, empty string if none
|
||||
[[nodiscard]] QString selectedFolderAlias() const;
|
||||
|
||||
|
||||
@ -63,9 +63,10 @@ QString e2eeBaseUrl()
|
||||
namespace {
|
||||
constexpr char accountProperty[] = "account";
|
||||
|
||||
const char e2e_cert[] = "_e2e-certificate";
|
||||
const char e2e_private[] = "_e2e-private";
|
||||
const char e2e_mnemonic[] = "_e2e-mnemonic";
|
||||
constexpr char e2e_cert[] = "_e2e-certificate";
|
||||
constexpr char e2e_private[] = "_e2e-private";
|
||||
constexpr char e2e_public[] = "_e2e-public";
|
||||
constexpr char e2e_mnemonic[] = "_e2e-mnemonic";
|
||||
|
||||
constexpr auto metadataKeyJsonKey = "metadataKey";
|
||||
|
||||
@ -145,6 +146,11 @@ public:
|
||||
BIO_free_all(_bio);
|
||||
}
|
||||
|
||||
operator const BIO*() const
|
||||
{
|
||||
return _bio;
|
||||
}
|
||||
|
||||
operator BIO*()
|
||||
{
|
||||
return _bio;
|
||||
@ -881,10 +887,10 @@ void ClientSideEncryption::initialize(const AccountPtr &account)
|
||||
return;
|
||||
}
|
||||
|
||||
fetchFromKeyChain(account);
|
||||
fetchCertificateFromKeyChain(account);
|
||||
}
|
||||
|
||||
void ClientSideEncryption::fetchFromKeyChain(const AccountPtr &account)
|
||||
void ClientSideEncryption::fetchCertificateFromKeyChain(const AccountPtr &account)
|
||||
{
|
||||
const QString kck = AbstractCredentials::keychainKey(
|
||||
account->url().toString(),
|
||||
@ -892,6 +898,22 @@ void ClientSideEncryption::fetchFromKeyChain(const AccountPtr &account)
|
||||
account->id()
|
||||
);
|
||||
|
||||
const auto job = new ReadPasswordJob(Theme::instance()->appName());
|
||||
job->setProperty(accountProperty, QVariant::fromValue(account));
|
||||
job->setInsecureFallback(false);
|
||||
job->setKey(kck);
|
||||
connect(job, &ReadPasswordJob::finished, this, &ClientSideEncryption::publicCertificateFetched);
|
||||
job->start();
|
||||
}
|
||||
|
||||
void ClientSideEncryption::fetchPublicKeyFromKeyChain(const AccountPtr &account)
|
||||
{
|
||||
const QString kck = AbstractCredentials::keychainKey(
|
||||
account->url().toString(),
|
||||
account->credentials()->user() + e2e_public,
|
||||
account->id()
|
||||
);
|
||||
|
||||
auto *job = new ReadPasswordJob(Theme::instance()->appName());
|
||||
job->setProperty(accountProperty, QVariant::fromValue(account));
|
||||
job->setInsecureFallback(false);
|
||||
@ -950,7 +972,7 @@ bool ClientSideEncryption::checkServerPublicKeyValidity(const QByteArray &server
|
||||
return true;
|
||||
}
|
||||
|
||||
void ClientSideEncryption::publicKeyFetched(Job *incoming)
|
||||
void ClientSideEncryption::publicCertificateFetched(Job *incoming)
|
||||
{
|
||||
auto *readJob = dynamic_cast<ReadPasswordJob *>(incoming);
|
||||
auto account = readJob->property(accountProperty).value<AccountPtr>();
|
||||
@ -958,14 +980,14 @@ void ClientSideEncryption::publicKeyFetched(Job *incoming)
|
||||
|
||||
// Error or no valid public key error out
|
||||
if (readJob->error() != NoError || readJob->binaryData().length() == 0) {
|
||||
getPublicKeyFromServer(account);
|
||||
fetchPublicKeyFromKeyChain(account);
|
||||
return;
|
||||
}
|
||||
|
||||
_certificate = QSslCertificate(readJob->binaryData(), QSsl::Pem);
|
||||
|
||||
if (_certificate.isNull()) {
|
||||
getPublicKeyFromServer(account);
|
||||
fetchPublicKeyFromKeyChain(account);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -987,6 +1009,41 @@ void ClientSideEncryption::publicKeyFetched(Job *incoming)
|
||||
job->start();
|
||||
}
|
||||
|
||||
void ClientSideEncryption::publicKeyFetched(QKeychain::Job *incoming)
|
||||
{
|
||||
const auto readJob = dynamic_cast<ReadPasswordJob *>(incoming);
|
||||
const auto account = readJob->property(accountProperty).value<AccountPtr>();
|
||||
Q_ASSERT(account);
|
||||
|
||||
// Error or no valid public key error out
|
||||
if (readJob->error() != NoError || readJob->binaryData().length() == 0) {
|
||||
getPublicKeyFromServer(account);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto publicKey = QSslKey(readJob->binaryData(), QSsl::Rsa, QSsl::Pem, QSsl::PublicKey);
|
||||
|
||||
if (publicKey.isNull()) {
|
||||
getPublicKeyFromServer(account);
|
||||
return;
|
||||
}
|
||||
|
||||
_publicKey = publicKey;
|
||||
|
||||
const QString kck = AbstractCredentials::keychainKey(
|
||||
account->url().toString(),
|
||||
account->credentials()->user() + e2e_private,
|
||||
account->id()
|
||||
);
|
||||
|
||||
const auto job = new ReadPasswordJob(Theme::instance()->appName());
|
||||
job->setProperty(accountProperty, QVariant::fromValue(account));
|
||||
job->setInsecureFallback(false);
|
||||
job->setKey(kck);
|
||||
connect(job, &ReadPasswordJob::finished, this, &ClientSideEncryption::privateKeyFetched);
|
||||
job->start();
|
||||
}
|
||||
|
||||
void ClientSideEncryption::privateKeyFetched(Job *incoming)
|
||||
{
|
||||
auto *readJob = dynamic_cast<ReadPasswordJob *>(incoming);
|
||||
@ -995,8 +1052,7 @@ void ClientSideEncryption::privateKeyFetched(Job *incoming)
|
||||
|
||||
// Error or no valid public key error out
|
||||
if (readJob->error() != NoError || readJob->binaryData().length() == 0) {
|
||||
_certificate = QSslCertificate();
|
||||
_publicKey = QSslKey();
|
||||
forgetSensitiveData(account);
|
||||
getPublicKeyFromServer(account);
|
||||
return;
|
||||
}
|
||||
@ -1033,9 +1089,7 @@ void ClientSideEncryption::mnemonicKeyFetched(QKeychain::Job *incoming)
|
||||
|
||||
// Error or no valid public key error out
|
||||
if (readJob->error() != NoError || readJob->textData().length() == 0) {
|
||||
_certificate = QSslCertificate();
|
||||
_publicKey = QSslKey();
|
||||
_privateKey = QByteArray();
|
||||
forgetSensitiveData(account);
|
||||
getPublicKeyFromServer(account);
|
||||
return;
|
||||
}
|
||||
@ -1044,7 +1098,7 @@ void ClientSideEncryption::mnemonicKeyFetched(QKeychain::Job *incoming)
|
||||
|
||||
qCInfo(lcCse()) << "Mnemonic key fetched from keychain: " << _mnemonic;
|
||||
|
||||
emit initializationFinished();
|
||||
checkServerHasSavedKeys(account);
|
||||
}
|
||||
|
||||
void ClientSideEncryption::writePrivateKey(const AccountPtr &account)
|
||||
@ -1085,7 +1139,15 @@ void ClientSideEncryption::writeCertificate(const AccountPtr &account)
|
||||
job->start();
|
||||
}
|
||||
|
||||
void ClientSideEncryption::writeMnemonic(const AccountPtr &account)
|
||||
void ClientSideEncryption::generateMnemonic()
|
||||
{
|
||||
const auto list = WordList::getRandomWords(12);
|
||||
_mnemonic = list.join(' ');
|
||||
}
|
||||
|
||||
template <typename L>
|
||||
void ClientSideEncryption::writeMnemonic(OCC::AccountPtr account,
|
||||
L nextCall)
|
||||
{
|
||||
const QString kck = AbstractCredentials::keychainKey(
|
||||
account->url().toString(),
|
||||
@ -1097,17 +1159,19 @@ void ClientSideEncryption::writeMnemonic(const AccountPtr &account)
|
||||
job->setInsecureFallback(false);
|
||||
job->setKey(kck);
|
||||
job->setTextData(_mnemonic);
|
||||
connect(job, &WritePasswordJob::finished, [](Job *incoming) {
|
||||
Q_UNUSED(incoming);
|
||||
qCInfo(lcCse()) << "Mnemonic stored in keychain";
|
||||
connect(job, &WritePasswordJob::finished, [this, account, nextCall = std::move(nextCall)](Job *incoming) mutable {
|
||||
if (incoming->error() != Error::NoError) {
|
||||
failedToInitialize(account);
|
||||
return;
|
||||
}
|
||||
|
||||
nextCall();
|
||||
});
|
||||
job->start();
|
||||
}
|
||||
|
||||
void ClientSideEncryption::forgetSensitiveData(const AccountPtr &account)
|
||||
{
|
||||
_publicKey = QSslKey();
|
||||
|
||||
if (!sensitiveDataRemaining()) {
|
||||
checkAllSensitiveDataDeleted();
|
||||
return;
|
||||
@ -1175,22 +1239,40 @@ void ClientSideEncryption::handleMnemonicDeleted(const QKeychain::Job* const inc
|
||||
checkAllSensitiveDataDeleted();
|
||||
}
|
||||
|
||||
void ClientSideEncryption::handlePublicKeyDeleted(const QKeychain::Job * const incoming)
|
||||
{
|
||||
const auto error = incoming->error();
|
||||
if (error != QKeychain::NoError && error != QKeychain::EntryNotFound) {
|
||||
qCWarning(lcCse) << "Public key could not be deleted:" << incoming->errorString();
|
||||
return;
|
||||
}
|
||||
|
||||
_publicKey.clear();
|
||||
Q_EMIT publicKeyDeleted();
|
||||
checkAllSensitiveDataDeleted();
|
||||
}
|
||||
|
||||
bool ClientSideEncryption::sensitiveDataRemaining() const
|
||||
{
|
||||
return !_privateKey.isEmpty() || !_certificate.isNull() || !_mnemonic.isEmpty();
|
||||
}
|
||||
|
||||
void ClientSideEncryption::failedToInitialize(const AccountPtr &account)
|
||||
{
|
||||
forgetSensitiveData(account);
|
||||
Q_EMIT initializationFinished();
|
||||
}
|
||||
|
||||
void ClientSideEncryption::checkAllSensitiveDataDeleted()
|
||||
{
|
||||
if (sensitiveDataRemaining()) {
|
||||
qCDebug(lcCse) << "Some sensitive data emaining:"
|
||||
<< "Private key:" << _privateKey
|
||||
<< "Certificate is null:" << _certificate.isNull()
|
||||
<< "Mnemonic:" << _mnemonic;
|
||||
qCWarning(lcCse) << "Some sensitive data emaining:"
|
||||
<< "Private key:" << (_privateKey.isEmpty() ? "is empty" : "is not empty")
|
||||
<< "Certificate is null:" << (_certificate.isNull() ? "true" : "false")
|
||||
<< "Mnemonic:" << (_mnemonic.isEmpty() ? "is empty" : "is not empty");
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(lcCse) << "All sensitive encryption data has been deleted.";
|
||||
Q_EMIT sensitiveDataForgotten();
|
||||
}
|
||||
|
||||
@ -1207,41 +1289,57 @@ void ClientSideEncryption::generateKeyPair(const AccountPtr &account)
|
||||
|
||||
if(EVP_PKEY_keygen_init(ctx) <= 0) {
|
||||
qCInfo(lcCse()) << "Couldn't initialize the key generator";
|
||||
failedToInitialize(account);
|
||||
return;
|
||||
}
|
||||
|
||||
if(EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, rsaKeyLen) <= 0) {
|
||||
qCInfo(lcCse()) << "Couldn't initialize the key generator bits";
|
||||
failedToInitialize(account);
|
||||
return;
|
||||
}
|
||||
|
||||
auto localKeyPair = PKey::generate(ctx);
|
||||
if(!localKeyPair) {
|
||||
qCInfo(lcCse()) << "Could not generate the key";
|
||||
failedToInitialize(account);
|
||||
return;
|
||||
}
|
||||
|
||||
qCInfo(lcCse()) << "Key correctly generated";
|
||||
qCInfo(lcCse()) << "Storing keys locally";
|
||||
{
|
||||
Bio privKey;
|
||||
if (PEM_write_bio_PrivateKey(privKey, localKeyPair, nullptr, nullptr, 0, nullptr, nullptr) <= 0) {
|
||||
qCWarning(lcCse()) << "Could not read private key from bio.";
|
||||
failedToInitialize(account);
|
||||
return;
|
||||
}
|
||||
|
||||
_privateKey = BIO2ByteArray(privKey);
|
||||
}
|
||||
|
||||
Bio privKey;
|
||||
if (PEM_write_bio_PrivateKey(privKey, localKeyPair, nullptr, nullptr, 0, nullptr, nullptr) <= 0) {
|
||||
qCInfo(lcCse()) << "Could not read private key from bio.";
|
||||
failedToInitialize(account);
|
||||
return;
|
||||
}
|
||||
QByteArray key = BIO2ByteArray(privKey);
|
||||
//_privateKey = QSslKey(key, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey);
|
||||
_privateKey = key;
|
||||
|
||||
qCInfo(lcCse()) << "Keys generated correctly, sending to server.";
|
||||
generateCSR(account, std::move(localKeyPair));
|
||||
qCDebug(lcCse()) << "Key correctly generated";
|
||||
|
||||
auto csrContent = generateCSR(account, std::move(localKeyPair), PKey::readPrivateKey(privKey));
|
||||
writeMnemonic(account, [account, keyPair = std::move(csrContent.second), csrContent = std::move(csrContent.first), this]() mutable -> void {
|
||||
writeKeyPair(account, std::move(keyPair), csrContent);
|
||||
});
|
||||
}
|
||||
|
||||
void ClientSideEncryption::generateCSR(const AccountPtr &account, PKey keyPair)
|
||||
std::pair<QByteArray, ClientSideEncryption::PKey> ClientSideEncryption::generateCSR(const AccountPtr &account,
|
||||
PKey keyPair,
|
||||
PKey privateKey)
|
||||
{
|
||||
auto result = QByteArray{};
|
||||
|
||||
// OpenSSL expects const char.
|
||||
auto cnArray = account->davUser().toLocal8Bit();
|
||||
qCInfo(lcCse()) << "Getting the following array for the account Id" << cnArray;
|
||||
|
||||
auto certParams = std::map<const char *, const char*>{
|
||||
{"C", "DE"},
|
||||
@ -1255,7 +1353,7 @@ void ClientSideEncryption::generateCSR(const AccountPtr &account, PKey keyPair)
|
||||
int nVersion = 1;
|
||||
|
||||
// 2. set version of x509 req
|
||||
X509_REQ *x509_req = X509_REQ_new();
|
||||
auto x509_req = X509_REQ_new();
|
||||
auto release_on_exit_x509_req = qScopeGuard([&] {
|
||||
X509_REQ_free(x509_req);
|
||||
});
|
||||
@ -1268,34 +1366,44 @@ void ClientSideEncryption::generateCSR(const AccountPtr &account, PKey keyPair)
|
||||
for(const auto& v : certParams) {
|
||||
ret = X509_NAME_add_entry_by_txt(x509_name, v.first, MBSTRING_ASC, (const unsigned char*) v.second, -1, -1, 0);
|
||||
if (ret != 1) {
|
||||
qCInfo(lcCse()) << "Error Generating the Certificate while adding" << v.first << v.second;
|
||||
return;
|
||||
qCWarning(lcCse()) << "Error Generating the Certificate while adding" << v.first << v.second;
|
||||
return {result, std::move(keyPair)};
|
||||
}
|
||||
}
|
||||
|
||||
ret = X509_REQ_set_pubkey(x509_req, keyPair);
|
||||
if (ret != 1){
|
||||
qCInfo(lcCse()) << "Error setting the public key on the csr";
|
||||
return;
|
||||
qCWarning(lcCse()) << "Error setting the public key on the csr";
|
||||
return {result, std::move(keyPair)};
|
||||
}
|
||||
|
||||
ret = X509_REQ_sign(x509_req, keyPair, EVP_sha1()); // return x509_req->signature->length
|
||||
ret = X509_REQ_sign(x509_req, privateKey, EVP_sha1()); // return x509_req->signature->length
|
||||
if (ret <= 0){
|
||||
qCInfo(lcCse()) << "Error setting the public key on the csr";
|
||||
return;
|
||||
qCWarning(lcCse()) << "Error signing the csr with the private key";
|
||||
return {result, std::move(keyPair)};
|
||||
}
|
||||
|
||||
Bio out;
|
||||
ret = PEM_write_bio_X509_REQ(out, x509_req);
|
||||
QByteArray output = BIO2ByteArray(out);
|
||||
if (ret <= 0){
|
||||
qCWarning(lcCse()) << "Error exporting the csr to the BIO";
|
||||
return {result, std::move(keyPair)};
|
||||
}
|
||||
|
||||
qCInfo(lcCse()) << "Returning the certificate";
|
||||
qCInfo(lcCse()) << output;
|
||||
result = BIO2ByteArray(out);
|
||||
|
||||
sendSignRequestCSR(account, std::move(keyPair), output);
|
||||
qCDebug(lcCse()) << "CSR generated";
|
||||
|
||||
if (_mnemonic.isEmpty()) {
|
||||
generateMnemonic();
|
||||
}
|
||||
|
||||
return {result, std::move(keyPair)};
|
||||
}
|
||||
|
||||
void ClientSideEncryption::sendSignRequestCSR(const AccountPtr &account, PKey keyPair, const QByteArray &csrContent)
|
||||
void ClientSideEncryption::sendSignRequestCSR(const AccountPtr &account,
|
||||
PKey keyPair,
|
||||
const QByteArray &csrContent)
|
||||
{
|
||||
auto job = new SignPublicKeyApiJob(account, e2eeBaseUrl() + "public-key", this);
|
||||
job->setCsr(csrContent);
|
||||
@ -1312,28 +1420,143 @@ void ClientSideEncryption::sendSignRequestCSR(const AccountPtr &account, PKey ke
|
||||
if (!X509_check_private_key(x509Certificate, keyPair)) {
|
||||
auto lastError = ERR_get_error();
|
||||
while (lastError) {
|
||||
qCInfo(lcCse()) << ERR_lib_error_string(lastError);
|
||||
qCWarning(lcCse()) << ERR_lib_error_string(lastError);
|
||||
lastError = ERR_get_error();
|
||||
}
|
||||
forgetSensitiveData(account);
|
||||
failedToInitialize(account);
|
||||
return;
|
||||
}
|
||||
qCInfo(lcCse()) << "received a valid certificate";
|
||||
fetchAndValidatePublicKeyFromServer(account);
|
||||
} else {
|
||||
qCWarning(lcCse()) << retCode;
|
||||
failedToInitialize(account);
|
||||
return;
|
||||
}
|
||||
qCInfo(lcCse()) << retCode;
|
||||
});
|
||||
job->start();
|
||||
}
|
||||
|
||||
void ClientSideEncryption::writeKeyPair(const AccountPtr &account,
|
||||
PKey keyPair,
|
||||
const QByteArray &csrContent)
|
||||
{
|
||||
const auto privateKeyKeychainId = AbstractCredentials::keychainKey(
|
||||
account->url().toString(),
|
||||
account->credentials()->user() + e2e_private,
|
||||
account->id()
|
||||
);
|
||||
|
||||
const auto publicKeyKeychainId = AbstractCredentials::keychainKey(
|
||||
account->url().toString(),
|
||||
account->credentials()->user() + e2e_public,
|
||||
account->id()
|
||||
);
|
||||
|
||||
Bio privateKey;
|
||||
if (PEM_write_bio_PrivateKey(privateKey, keyPair, nullptr, nullptr, 0, nullptr, nullptr) <= 0) {
|
||||
qCWarning(lcCse()) << "Could not read private key from bio.";
|
||||
failedToInitialize(account);
|
||||
return;
|
||||
}
|
||||
const auto bytearrayPrivateKey = BIO2ByteArray(privateKey);
|
||||
|
||||
const auto privateKeyJob = new WritePasswordJob(Theme::instance()->appName());
|
||||
privateKeyJob->setInsecureFallback(false);
|
||||
privateKeyJob->setKey(privateKeyKeychainId);
|
||||
privateKeyJob->setBinaryData(bytearrayPrivateKey);
|
||||
connect(privateKeyJob, &WritePasswordJob::finished, [keyPair = std::move(keyPair), publicKeyKeychainId, account, csrContent, this] (Job *incoming) mutable {
|
||||
if (incoming->error() != Error::NoError) {
|
||||
failedToInitialize(account);
|
||||
return;
|
||||
}
|
||||
|
||||
Bio publicKey;
|
||||
if (PEM_write_bio_PUBKEY(publicKey, keyPair) <= 0) {
|
||||
qCWarning(lcCse()) << "Could not read public key from bio.";
|
||||
failedToInitialize(account);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto bytearrayPublicKey = BIO2ByteArray(publicKey);
|
||||
|
||||
const auto publicKeyJob = new WritePasswordJob(Theme::instance()->appName());
|
||||
publicKeyJob->setInsecureFallback(false);
|
||||
publicKeyJob->setKey(publicKeyKeychainId);
|
||||
publicKeyJob->setBinaryData(bytearrayPublicKey);
|
||||
connect(publicKeyJob, &WritePasswordJob::finished, [account, keyPair = std::move(keyPair), csrContent, this](Job *incoming) mutable {
|
||||
if (incoming->error() != Error::NoError) {
|
||||
failedToInitialize(account);
|
||||
return;
|
||||
}
|
||||
|
||||
sendSignRequestCSR(account, std::move(keyPair), csrContent);
|
||||
});
|
||||
publicKeyJob->start();
|
||||
});
|
||||
privateKeyJob->start();
|
||||
}
|
||||
|
||||
void ClientSideEncryption::checkServerHasSavedKeys(const AccountPtr &account)
|
||||
{
|
||||
const auto keyIsNotOnServer = [account, this] () {
|
||||
qCInfo(lcCse) << "server is missing keys. deleting local keys";
|
||||
|
||||
failedToInitialize(account);
|
||||
};
|
||||
|
||||
const auto privateKeyOnServerIsValid = [this] () {
|
||||
Q_EMIT initializationFinished();
|
||||
};
|
||||
|
||||
const auto publicKeyOnServerIsValid = [this, account, privateKeyOnServerIsValid, keyIsNotOnServer] () {
|
||||
checkUserPrivateKeyOnServer(account, privateKeyOnServerIsValid, keyIsNotOnServer);
|
||||
};
|
||||
|
||||
checkUserPublicKeyOnServer(account, publicKeyOnServerIsValid, keyIsNotOnServer);
|
||||
}
|
||||
|
||||
template <typename SUCCESS_CALLBACK, typename ERROR_CALLBACK>
|
||||
void ClientSideEncryption::checkUserKeyOnServer(const QString &keyType,
|
||||
const AccountPtr &account,
|
||||
SUCCESS_CALLBACK nextCheck,
|
||||
ERROR_CALLBACK onError)
|
||||
{
|
||||
auto job = new JsonApiJob(account, e2eeBaseUrl() + keyType, this);
|
||||
connect(job, &JsonApiJob::jsonReceived, [nextCheck, onError](const QJsonDocument& doc, int retCode) {
|
||||
Q_UNUSED(doc)
|
||||
|
||||
if (retCode == 200) {
|
||||
nextCheck();
|
||||
} else {
|
||||
onError();
|
||||
}
|
||||
});
|
||||
job->start();
|
||||
}
|
||||
|
||||
template <typename SUCCESS_CALLBACK, typename ERROR_CALLBACK>
|
||||
void ClientSideEncryption::checkUserPublicKeyOnServer(const AccountPtr &account,
|
||||
SUCCESS_CALLBACK nextCheck,
|
||||
ERROR_CALLBACK onError)
|
||||
{
|
||||
checkUserKeyOnServer("public-key", account, nextCheck, onError);
|
||||
}
|
||||
|
||||
template <typename SUCCESS_CALLBACK, typename ERROR_CALLBACK>
|
||||
void ClientSideEncryption::checkUserPrivateKeyOnServer(const AccountPtr &account, SUCCESS_CALLBACK nextCheck, ERROR_CALLBACK onError)
|
||||
{
|
||||
checkUserKeyOnServer("private-key", account, nextCheck, onError);
|
||||
}
|
||||
|
||||
void ClientSideEncryption::encryptPrivateKey(const AccountPtr &account)
|
||||
{
|
||||
QStringList list = WordList::getRandomWords(12);
|
||||
_mnemonic = list.join(' ');
|
||||
qCInfo(lcCse()) << "mnemonic Generated:" << _mnemonic;
|
||||
if (_mnemonic.isEmpty()) {
|
||||
generateMnemonic();
|
||||
}
|
||||
|
||||
QString passPhrase = list.join(QString()).toLower();
|
||||
qCInfo(lcCse()) << "Passphrase Generated:" << passPhrase;
|
||||
auto passPhrase = _mnemonic;
|
||||
passPhrase = passPhrase.remove(' ').toLower();
|
||||
qCDebug(lcCse) << "Passphrase Generated";
|
||||
|
||||
auto salt = EncryptionHelper::generateRandom(40);
|
||||
auto secretKey = EncryptionHelper::generatePassword(passPhrase, salt);
|
||||
@ -1346,14 +1569,15 @@ void ClientSideEncryption::encryptPrivateKey(const AccountPtr &account)
|
||||
Q_UNUSED(doc);
|
||||
switch(retCode) {
|
||||
case 200:
|
||||
qCInfo(lcCse()) << "Private key stored encrypted on server.";
|
||||
writePrivateKey(account);
|
||||
writeCertificate(account);
|
||||
writeMnemonic(account);
|
||||
emit initializationFinished(true);
|
||||
writeMnemonic(account, [this] () {
|
||||
emit initializationFinished(true);
|
||||
});
|
||||
break;
|
||||
default:
|
||||
qCInfo(lcCse()) << "Store private key failed, return code:" << retCode;
|
||||
qCWarning(lcCse) << "Store private key failed, return code:" << retCode;
|
||||
failedToInitialize(account);
|
||||
}
|
||||
});
|
||||
job->start();
|
||||
@ -1362,7 +1586,7 @@ void ClientSideEncryption::encryptPrivateKey(const AccountPtr &account)
|
||||
void ClientSideEncryption::decryptPrivateKey(const AccountPtr &account, const QByteArray &key) {
|
||||
if (!account->askUserForMnemonic()) {
|
||||
qCDebug(lcCse) << "Not allowed to ask user for mnemonic";
|
||||
emit initializationFinished();
|
||||
failedToInitialize(account);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1386,36 +1610,30 @@ void ClientSideEncryption::decryptPrivateKey(const AccountPtr &account, const QB
|
||||
}
|
||||
bool ok = dialog.exec();
|
||||
if (ok) {
|
||||
qCInfo(lcCse()) << "Got mnemonic:" << dialog.textValue();
|
||||
prev = dialog.textValue();
|
||||
|
||||
_mnemonic = prev;
|
||||
QString mnemonic = prev.split(" ").join(QString()).toLower();
|
||||
qCInfo(lcCse()) << "mnemonic:" << mnemonic;
|
||||
|
||||
// split off salt
|
||||
const auto salt = EncryptionHelper::extractPrivateKeySalt(key);
|
||||
|
||||
auto pass = EncryptionHelper::generatePassword(mnemonic, salt);
|
||||
qCInfo(lcCse()) << "Generated key:" << pass;
|
||||
|
||||
QByteArray privateKey = EncryptionHelper::decryptPrivateKey(pass, key);
|
||||
//_privateKey = QSslKey(privateKey, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey);
|
||||
_privateKey = privateKey;
|
||||
|
||||
qCInfo(lcCse()) << "Private key: " << _privateKey;
|
||||
|
||||
if (!_privateKey.isNull() && checkPublicKeyValidity(account)) {
|
||||
writePrivateKey(account);
|
||||
writeCertificate(account);
|
||||
writeMnemonic(account);
|
||||
writeMnemonic(account, [] () {});
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
_mnemonic = QString();
|
||||
_privateKey = QByteArray();
|
||||
qCInfo(lcCse()) << "Cancelled";
|
||||
break;
|
||||
qCDebug(lcCse()) << "Cancelled";
|
||||
failedToInitialize(account);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1424,20 +1642,17 @@ void ClientSideEncryption::decryptPrivateKey(const AccountPtr &account, const QB
|
||||
|
||||
void ClientSideEncryption::getPrivateKeyFromServer(const AccountPtr &account)
|
||||
{
|
||||
qCInfo(lcCse()) << "Retrieving private key from server";
|
||||
auto job = new JsonApiJob(account, e2eeBaseUrl() + "private-key", this);
|
||||
connect(job, &JsonApiJob::jsonReceived, [this, account](const QJsonDocument& doc, int retCode) {
|
||||
if (retCode == 200) {
|
||||
QString key = doc.object()["ocs"].toObject()["data"].toObject()["private-key"].toString();
|
||||
qCInfo(lcCse()) << key;
|
||||
qCInfo(lcCse()) << "Found private key, lets decrypt it!";
|
||||
decryptPrivateKey(account, key.toLocal8Bit());
|
||||
} else if (retCode == 404) {
|
||||
qCInfo(lcCse()) << "No private key on the server: setup is incomplete.";
|
||||
qCWarning(lcCse) << "No private key on the server: setup is incomplete.";
|
||||
emit initializationFinished();
|
||||
return;
|
||||
} else {
|
||||
qCInfo(lcCse()) << "Error while requesting public key: " << retCode;
|
||||
qCWarning(lcCse) << "Error while requesting public key: " << retCode;
|
||||
emit initializationFinished();
|
||||
return;
|
||||
}
|
||||
@ -1447,25 +1662,24 @@ void ClientSideEncryption::getPrivateKeyFromServer(const AccountPtr &account)
|
||||
|
||||
void ClientSideEncryption::getPublicKeyFromServer(const AccountPtr &account)
|
||||
{
|
||||
qCInfo(lcCse()) << "Retrieving public key from server";
|
||||
auto job = new JsonApiJob(account, e2eeBaseUrl() + "public-key", this);
|
||||
connect(job, &JsonApiJob::jsonReceived, [this, account](const QJsonDocument& doc, int retCode) {
|
||||
if (retCode == 200) {
|
||||
QString publicKey = doc.object()["ocs"].toObject()["data"].toObject()["public-keys"].toObject()[account->davUser()].toString();
|
||||
_certificate = QSslCertificate(publicKey.toLocal8Bit(), QSsl::Pem);
|
||||
_publicKey = _certificate.publicKey();
|
||||
qCInfo(lcCse()) << "Found Public key, requesting Server Public Key. Public key:" << publicKey;
|
||||
fetchAndValidatePublicKeyFromServer(account);
|
||||
} else if (retCode == 404) {
|
||||
qCInfo(lcCse()) << "No public key on the server";
|
||||
qCDebug(lcCse()) << "No public key on the server";
|
||||
if (!account->e2eEncryptionKeysGenerationAllowed()) {
|
||||
qCInfo(lcCse()) << "User did not allow E2E keys generation.";
|
||||
emit initializationFinished();
|
||||
qCDebug(lcCse()) << "User did not allow E2E keys generation.";
|
||||
failedToInitialize(account);
|
||||
return;
|
||||
}
|
||||
generateKeyPair(account);
|
||||
} else {
|
||||
qCInfo(lcCse()) << "Error while requesting public key: " << retCode;
|
||||
qCWarning(lcCse) << "Error while requesting public key: " << retCode;
|
||||
failedToInitialize(account);
|
||||
}
|
||||
});
|
||||
job->start();
|
||||
@ -1473,30 +1687,26 @@ void ClientSideEncryption::getPublicKeyFromServer(const AccountPtr &account)
|
||||
|
||||
void ClientSideEncryption::fetchAndValidatePublicKeyFromServer(const AccountPtr &account)
|
||||
{
|
||||
qCInfo(lcCse()) << "Retrieving public key from server";
|
||||
auto job = new JsonApiJob(account, e2eeBaseUrl() + "server-key", this);
|
||||
connect(job, &JsonApiJob::jsonReceived, [this, account](const QJsonDocument& doc, int retCode) {
|
||||
if (retCode == 200) {
|
||||
const auto serverPublicKey = doc.object()["ocs"].toObject()["data"].toObject()["public-key"].toString().toLatin1();
|
||||
qCInfo(lcCse()) << "Found Server Public key, checking it. Server public key:" << serverPublicKey;
|
||||
if (checkServerPublicKeyValidity(serverPublicKey)) {
|
||||
if (_privateKey.isEmpty()) {
|
||||
qCInfo(lcCse()) << "Valid Server Public key, requesting Private Key.";
|
||||
getPrivateKeyFromServer(account);
|
||||
} else {
|
||||
qCInfo(lcCse()) << "Certificate saved, Encrypting Private Key.";
|
||||
encryptPrivateKey(account);
|
||||
}
|
||||
} else {
|
||||
qCInfo(lcCse()) << "Error invalid server public key";
|
||||
_certificate = QSslCertificate();
|
||||
_publicKey = QSslKey();
|
||||
_privateKey = QByteArray();
|
||||
qCWarning(lcCse) << "Error invalid server public key";
|
||||
forgetSensitiveData(account);
|
||||
getPublicKeyFromServer(account);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
qCInfo(lcCse()) << "Error while requesting server public key: " << retCode;
|
||||
qCWarning(lcCse) << "Error while requesting server public key: " << retCode;
|
||||
failedToInitialize(account);
|
||||
return;
|
||||
}
|
||||
});
|
||||
job->start();
|
||||
@ -1513,7 +1723,7 @@ FolderMetadata::FolderMetadata(AccountPtr account,
|
||||
RequiredMetadataVersion requiredMetadataVersion,
|
||||
const QByteArray& metadata,
|
||||
int statusCode)
|
||||
: _account(account)
|
||||
: _account(std::move(account))
|
||||
, _requiredMetadataVersion(requiredMetadataVersion)
|
||||
{
|
||||
if (metadata.isEmpty() || statusCode == 404) {
|
||||
|
||||
@ -135,6 +135,7 @@ signals:
|
||||
void privateKeyDeleted();
|
||||
void certificateDeleted();
|
||||
void mnemonicDeleted();
|
||||
void publicKeyDeleted();
|
||||
|
||||
public slots:
|
||||
void initialize(const OCC::AccountPtr &account);
|
||||
@ -144,6 +145,7 @@ private slots:
|
||||
void generateKeyPair(const OCC::AccountPtr &account);
|
||||
void encryptPrivateKey(const OCC::AccountPtr &account);
|
||||
|
||||
void publicCertificateFetched(QKeychain::Job *incoming);
|
||||
void publicKeyFetched(QKeychain::Job *incoming);
|
||||
void privateKeyFetched(QKeychain::Job *incoming);
|
||||
void mnemonicKeyFetched(QKeychain::Job *incoming);
|
||||
@ -151,6 +153,7 @@ private slots:
|
||||
void handlePrivateKeyDeleted(const QKeychain::Job* const incoming);
|
||||
void handleCertificateDeleted(const QKeychain::Job* const incoming);
|
||||
void handleMnemonicDeleted(const QKeychain::Job* const incoming);
|
||||
void handlePublicKeyDeleted(const QKeychain::Job* const incoming);
|
||||
void checkAllSensitiveDataDeleted();
|
||||
|
||||
void getPrivateKeyFromServer(const OCC::AccountPtr &account);
|
||||
@ -158,19 +161,54 @@ private slots:
|
||||
void fetchAndValidatePublicKeyFromServer(const OCC::AccountPtr &account);
|
||||
void decryptPrivateKey(const OCC::AccountPtr &account, const QByteArray &key);
|
||||
|
||||
void fetchFromKeyChain(const OCC::AccountPtr &account);
|
||||
void fetchCertificateFromKeyChain(const OCC::AccountPtr &account);
|
||||
void fetchPublicKeyFromKeyChain(const OCC::AccountPtr &account);
|
||||
void writePrivateKey(const OCC::AccountPtr &account);
|
||||
void writeCertificate(const OCC::AccountPtr &account);
|
||||
void writeMnemonic(const OCC::AccountPtr &account);
|
||||
|
||||
private:
|
||||
void generateCSR(const AccountPtr &account, PKey keyPair);
|
||||
void sendSignRequestCSR(const AccountPtr &account, PKey keyPair, const QByteArray &csrContent);
|
||||
void generateMnemonic();
|
||||
|
||||
[[nodiscard]] std::pair<QByteArray, PKey> generateCSR(const AccountPtr &account,
|
||||
PKey keyPair,
|
||||
PKey privateKey);
|
||||
|
||||
void sendSignRequestCSR(const AccountPtr &account,
|
||||
PKey keyPair,
|
||||
const QByteArray &csrContent);
|
||||
|
||||
void writeKeyPair(const AccountPtr &account,
|
||||
PKey keyPair,
|
||||
const QByteArray &csrContent);
|
||||
|
||||
template <typename L>
|
||||
void writeMnemonic(OCC::AccountPtr account,
|
||||
L nextCall);
|
||||
|
||||
void checkServerHasSavedKeys(const AccountPtr &account);
|
||||
|
||||
template <typename SUCCESS_CALLBACK, typename ERROR_CALLBACK>
|
||||
void checkUserPublicKeyOnServer(const OCC::AccountPtr &account,
|
||||
SUCCESS_CALLBACK nextCheck,
|
||||
ERROR_CALLBACK onError);
|
||||
|
||||
template <typename SUCCESS_CALLBACK, typename ERROR_CALLBACK>
|
||||
void checkUserPrivateKeyOnServer(const OCC::AccountPtr &account,
|
||||
SUCCESS_CALLBACK nextCheck,
|
||||
ERROR_CALLBACK onError);
|
||||
|
||||
template <typename SUCCESS_CALLBACK, typename ERROR_CALLBACK>
|
||||
void checkUserKeyOnServer(const QString &keyType,
|
||||
const OCC::AccountPtr &account,
|
||||
SUCCESS_CALLBACK nextCheck,
|
||||
ERROR_CALLBACK onError);
|
||||
|
||||
[[nodiscard]] bool checkPublicKeyValidity(const AccountPtr &account) const;
|
||||
[[nodiscard]] bool checkServerPublicKeyValidity(const QByteArray &serverPublicKeyString) const;
|
||||
[[nodiscard]] bool sensitiveDataRemaining() const;
|
||||
|
||||
void failedToInitialize(const AccountPtr &account);
|
||||
|
||||
bool isInitialized = false;
|
||||
};
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user