diff --git a/src/core/Config.cpp b/src/core/Config.cpp index 84b4a35c2..8848a116a 100644 --- a/src/core/Config.cpp +++ b/src/core/Config.cpp @@ -148,7 +148,7 @@ static const QHash configStrings = { {Config::Security_NoConfirmMoveEntryToRecycleBin,{QS("Security/NoConfirmMoveEntryToRecycleBin"), Roaming, true}}, {Config::Security_EnableCopyOnDoubleClick,{QS("Security/EnableCopyOnDoubleClick"), Roaming, false}}, {Config::Security_QuickUnlock, {QS("Security/QuickUnlock"), Local, true}}, - {Config::Security_DatabasePasswordMinimumQuality, {QS("Security/DatabasePasswordMinimumQuality"), Local, 0}}, + {Config::Security_QuickUnlockRemember, {QS("Security/QuickUnlockRemember"), Local, true}}, // Browser {Config::Browser_Enabled, {QS("Browser/Enabled"), Roaming, false}}, diff --git a/src/core/Config.h b/src/core/Config.h index 8131bc9af..0ee17b663 100644 --- a/src/core/Config.h +++ b/src/core/Config.h @@ -129,7 +129,7 @@ public: Security_NoConfirmMoveEntryToRecycleBin, Security_EnableCopyOnDoubleClick, Security_QuickUnlock, - Security_DatabasePasswordMinimumQuality, + Security_QuickUnlockRemember, Browser_Enabled, Browser_ShowNotification, diff --git a/src/gui/ApplicationSettingsWidget.cpp b/src/gui/ApplicationSettingsWidget.cpp index ca7c5fd66..20b448413 100644 --- a/src/gui/ApplicationSettingsWidget.cpp +++ b/src/gui/ApplicationSettingsWidget.cpp @@ -147,6 +147,10 @@ ApplicationSettingsWidget::ApplicationSettingsWidget(QWidget* parent) m_secUi->lockDatabaseMinimizeCheckBox->setEnabled(!state); }); + connect(m_secUi->quickUnlockCheckBox, &QCheckBox::toggled, this, [this](bool state) { + m_secUi->quickUnlockRememberCheckBox->setEnabled(state); + }); + // Set Auto-Type shortcut when changed connect( m_generalUi->autoTypeShortcutWidget, &ShortcutWidget::shortcutChanged, this, [this](auto key, auto modifiers) { @@ -344,6 +348,15 @@ void ApplicationSettingsWidget::loadSettings() m_secUi->quickUnlockCheckBox->setEnabled(getQuickUnlock()->isAvailable()); m_secUi->quickUnlockCheckBox->setChecked(config()->get(Config::Security_QuickUnlock).toBool()); + m_secUi->quickUnlockCheckBox->setToolTip( + m_secUi->quickUnlockCheckBox->isEnabled() ? QString() : tr("Quick unlock is not available on your device.")); + + m_secUi->quickUnlockRememberCheckBox->setEnabled(getQuickUnlock()->isAvailable() + && getQuickUnlock()->canRemember()); + m_secUi->quickUnlockRememberCheckBox->setChecked(config()->get(Config::Security_QuickUnlockRemember).toBool()); + m_secUi->quickUnlockRememberCheckBox->setToolTip(m_secUi->quickUnlockRememberCheckBox->isEnabled() + ? QString() + : tr("Quick unlock cannot be remembered on your device.")); for (const ExtraPage& page : asConst(m_extraPages)) { page.loadSettings(); @@ -460,6 +473,7 @@ void ApplicationSettingsWidget::saveSettings() if (m_secUi->quickUnlockCheckBox->isEnabled()) { config()->set(Config::Security_QuickUnlock, m_secUi->quickUnlockCheckBox->isChecked()); + config()->set(Config::Security_QuickUnlockRemember, m_secUi->quickUnlockRememberCheckBox->isChecked()); } // Security: clear storage if related settings are disabled diff --git a/src/gui/ApplicationSettingsWidgetGeneral.ui b/src/gui/ApplicationSettingsWidgetGeneral.ui index 25a993b23..ebd7c41b2 100644 --- a/src/gui/ApplicationSettingsWidgetGeneral.ui +++ b/src/gui/ApplicationSettingsWidgetGeneral.ui @@ -148,7 +148,7 @@ 40 - 20 + 0 @@ -174,7 +174,7 @@ 30 - 20 + 0 @@ -210,7 +210,7 @@ 30 - 20 + 0 @@ -250,7 +250,7 @@ 30 - 20 + 0 @@ -315,7 +315,7 @@ 40 - 20 + 0 @@ -483,7 +483,7 @@ 30 - 20 + 0 @@ -513,7 +513,7 @@ 40 - 20 + 0 @@ -593,7 +593,7 @@ 30 - 20 + 0 @@ -626,7 +626,7 @@ 30 - 20 + 0 @@ -688,7 +688,7 @@ 40 - 20 + 0 @@ -890,7 +890,7 @@ 30 - 20 + 0 @@ -935,7 +935,7 @@ 40 - 20 + 0 @@ -992,7 +992,7 @@ 30 - 20 + 0 diff --git a/src/gui/ApplicationSettingsWidgetSecurity.ui b/src/gui/ApplicationSettingsWidgetSecurity.ui index c4cb0e01f..aeafce324 100644 --- a/src/gui/ApplicationSettingsWidgetSecurity.ui +++ b/src/gui/ApplicationSettingsWidgetSecurity.ui @@ -138,7 +138,7 @@ 40 - 20 + 0 @@ -168,10 +168,40 @@ - Enable database quick unlock (Touch ID / Windows Hello) + Enable database quick unlock (Touch ID / Windows Hello / Polkit) + + + + 0 + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 30 + 0 + + + + + + + + Remember quick unlock after database is closed + + + + + diff --git a/src/gui/DatabaseOpenWidget.cpp b/src/gui/DatabaseOpenWidget.cpp index 7f633c7fd..3b22eb513 100644 --- a/src/gui/DatabaseOpenWidget.cpp +++ b/src/gui/DatabaseOpenWidget.cpp @@ -356,7 +356,10 @@ void DatabaseOpenWidget::openDatabase() // Save Quick Unlock credentials if available if (!blockQuickUnlock && isQuickUnlockAvailable()) { auto keyData = databaseKey->serialize(); - getQuickUnlock()->setKey(m_db->publicUuid(), keyData); + if (!getQuickUnlock()->setKey(m_db->publicUuid(), keyData) && !getQuickUnlock()->errorString().isEmpty()) { + getMainWindow()->displayTabMessage(getQuickUnlock()->errorString(), + MessageWidget::MessageType::Warning); + } m_ui->messageWidget->hideMessage(); } diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index cbe27ee4e..91aadf839 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -1917,7 +1917,10 @@ void DatabaseWidget::closeEvent(QCloseEvent* event) return; } - m_databaseOpenWidget->resetQuickUnlock(); + // Reset quick unlock if we are not remembering it + if (!config()->get(Config::Security_QuickUnlockRemember).toBool()) { + m_databaseOpenWidget->resetQuickUnlock(); + } event->accept(); } diff --git a/src/quickunlock/Polkit.cpp b/src/quickunlock/Polkit.cpp index d73a7c71b..740ab12a8 100644 --- a/src/quickunlock/Polkit.cpp +++ b/src/quickunlock/Polkit.cpp @@ -110,14 +110,14 @@ bool Polkit::setKey(const QUuid& dbUuid, const QByteArray& key) SymmetricCipher aes256Encrypt; if (!aes256Encrypt.init(SymmetricCipher::Aes256_GCM, SymmetricCipher::Encrypt, randomKey, randomIV)) { - m_error = QObject::tr("AES initialization failed"); + m_error = QObject::tr("Failed to init KeePassXC crypto."); return false; } // Encrypt the master password QByteArray encryptedMasterKey = key; if (!aes256Encrypt.finish(encryptedMasterKey)) { - m_error = QObject::tr("AES encrypt failed"); + m_error = QObject::tr("Failed to encrypt key data."); qDebug() << "polkit aes encrypt failed: " << aes256Encrypt.errorString(); return false; } @@ -129,7 +129,7 @@ bool Polkit::setKey(const QUuid& dbUuid, const QByteArray& key) keychainKeyValue.size(), KEY_SPEC_PROCESS_KEYRING); if (key_serial < 0) { - m_error = QObject::tr("Failed to store in Linux Keyring"); + m_error = QObject::tr("Failed to store key in Linux Keyring. Quick unlock has not been enabled."); qDebug() << "polkit keyring failed to store: " << errno; return false; } @@ -181,7 +181,7 @@ bool Polkit::getKey(const QUuid& dbUuid, QByteArray& key) find_key_by_type_and_desc("user", getKeyName(dbUuid).toStdString().c_str(), KEY_SPEC_PROCESS_KEYRING); if (keySerial == -1) { - m_error = QObject::tr("Could not locate key in keyring"); + m_error = QObject::tr("Could not locate key in Linux Keyring."); qDebug() << "polkit keyring failed to find: " << errno; return false; } @@ -190,7 +190,7 @@ bool Polkit::getKey(const QUuid& dbUuid, QByteArray& key) long keychainDataSize = keyctl_read_alloc(keySerial, &keychainBuffer); if (keychainDataSize == -1) { - m_error = QObject::tr("Could not read key in keyring"); + m_error = QObject::tr("Could not read key in Linux Keyring."); qDebug() << "polkit keyring failed to read: " << errno; return false; } @@ -205,7 +205,7 @@ bool Polkit::getKey(const QUuid& dbUuid, QByteArray& key) SymmetricCipher aes256Decrypt; if (!aes256Decrypt.init(SymmetricCipher::Aes256_GCM, SymmetricCipher::Decrypt, keychainKey, keychainIv)) { - m_error = QObject::tr("AES initialization failed"); + m_error = QObject::tr("Failed to init KeePassXC crypto."); qDebug() << "polkit aes init failed"; return false; } @@ -213,7 +213,7 @@ bool Polkit::getKey(const QUuid& dbUuid, QByteArray& key) key = encryptedMasterKey; if (!aes256Decrypt.finish(key)) { key.clear(); - m_error = QObject::tr("AES decrypt failed"); + m_error = QObject::tr("Failed to decrypt key data."); qDebug() << "polkit aes decrypt failed: " << aes256Decrypt.errorString(); return false; } @@ -229,14 +229,19 @@ bool Polkit::getKey(const QUuid& dbUuid, QByteArray& key) // Failed to authenticate if (authResult.is_challenge) { - m_error = QObject::tr("No Polkit authentication agent was available"); + m_error = QObject::tr("No Polkit authentication agent was available."); } else { - m_error = QObject::tr("Polkit authorization failed"); + m_error = QObject::tr("Polkit authorization failed."); } return false; } +bool Polkit::canRemember() const +{ + return false; +} + bool Polkit::hasKey(const QUuid& dbUuid) const { if (!m_encryptedMasterKeys.contains(dbUuid)) { diff --git a/src/quickunlock/Polkit.h b/src/quickunlock/Polkit.h index 7dfc2db7b..2dd8bbf6c 100644 --- a/src/quickunlock/Polkit.h +++ b/src/quickunlock/Polkit.h @@ -36,6 +36,8 @@ public: bool getKey(const QUuid& dbUuid, QByteArray& key) override; bool hasKey(const QUuid& dbUuid) const override; + bool canRemember() const override; + void reset(const QUuid& dbUuid) override; void reset() override; diff --git a/src/quickunlock/QuickUnlockInterface.cpp b/src/quickunlock/QuickUnlockInterface.cpp index 0e24736e8..a04d7a1da 100644 --- a/src/quickunlock/QuickUnlockInterface.cpp +++ b/src/quickunlock/QuickUnlockInterface.cpp @@ -75,6 +75,11 @@ bool NoQuickUnlock::hasKey(const QUuid& dbUuid) const return false; } +bool NoQuickUnlock::canRemember() const +{ + return false; +} + void NoQuickUnlock::reset(const QUuid& dbUuid) { Q_UNUSED(dbUuid) diff --git a/src/quickunlock/QuickUnlockInterface.h b/src/quickunlock/QuickUnlockInterface.h index 54aeb8a62..419fffe0a 100644 --- a/src/quickunlock/QuickUnlockInterface.h +++ b/src/quickunlock/QuickUnlockInterface.h @@ -35,6 +35,8 @@ public: virtual bool getKey(const QUuid& dbUuid, QByteArray& key) = 0; virtual bool hasKey(const QUuid& dbUuid) const = 0; + virtual bool canRemember() const = 0; + virtual void reset(const QUuid& dbUuid) = 0; virtual void reset() = 0; }; @@ -49,6 +51,8 @@ public: bool getKey(const QUuid& dbUuid, QByteArray& key) override; bool hasKey(const QUuid& dbUuid) const override; + bool canRemember() const override; + void reset(const QUuid& dbUuid) override; void reset() override; }; diff --git a/src/quickunlock/TouchID.h b/src/quickunlock/TouchID.h index 74e5d9474..ef4f5ebf3 100644 --- a/src/quickunlock/TouchID.h +++ b/src/quickunlock/TouchID.h @@ -31,6 +31,8 @@ public: bool getKey(const QUuid& dbUuid, QByteArray& passwordKey) override; bool hasKey(const QUuid& dbUuid) const override; + bool canRemember() const override; + void reset(const QUuid& dbUuid = "") override; void reset() override; diff --git a/src/quickunlock/TouchID.mm b/src/quickunlock/TouchID.mm index 6368d7e6b..0e5d6cc6f 100644 --- a/src/quickunlock/TouchID.mm +++ b/src/quickunlock/TouchID.mm @@ -68,7 +68,7 @@ void TouchID::deleteKeyEntry(const QString& accountName) // get data from the KeyChain OSStatus status = SecItemDelete(query); - LogStatusError("TouchID::deleteKeyEntry - Status deleting existing entry", status); + LogStatusError("TouchID::deleteKeyEntry - Error deleting existing entry", status); } QString TouchID::databaseKeyName(const QUuid& dbUuid) @@ -88,41 +88,20 @@ void TouchID::reset() m_encryptedMasterKeys.clear(); } - - - -bool TouchID::setKey(const QUuid& dbUuid, const QByteArray& passwordKey, const bool ignoreTouchID) +/** + * Store the serialized database key into the macOS key store. The OS handles encrypt/decrypt operations. + * https://developer.apple.com/documentation/security/keychain_services/keychain_items + */ +bool TouchID::setKey(const QUuid& dbUuid, const QByteArray& key, const bool ignoreTouchID) { - if (passwordKey.isEmpty()) { + if (key.isEmpty()) { debug("TouchID::setKey - illegal arguments"); return false; } - if (m_encryptedMasterKeys.contains(dbUuid)) { - debug("TouchID::setKey - Already stored key for this database"); - return true; - } - - // generate random AES 256bit key and IV - QByteArray randomKey = randomGen()->randomArray(SymmetricCipher::keySize(SymmetricCipher::Aes256_GCM)); - QByteArray randomIV = randomGen()->randomArray(SymmetricCipher::defaultIvSize(SymmetricCipher::Aes256_GCM)); - - SymmetricCipher aes256Encrypt; - if (!aes256Encrypt.init(SymmetricCipher::Aes256_GCM, SymmetricCipher::Encrypt, randomKey, randomIV)) { - debug("TouchID::setKey - AES initialisation failed"); - return false; - } - - // encrypt and keep result in memory - QByteArray encryptedMasterKey = passwordKey; - if (!aes256Encrypt.finish(encryptedMasterKey)) { - debug("TouchID::getKey - AES encrypt failed: %s", aes256Encrypt.errorString().toUtf8().constData()); - return false; - } - - const QString keyName = databaseKeyName(dbUuid); - - deleteKeyEntry(keyName); // Try to delete the existing key entry + const auto keyName = databaseKeyName(dbUuid); + // Try to delete the existing key entry + deleteKeyEntry(keyName); // prepare adding secure entry to the macOS KeyChain CFErrorRef error = NULL; @@ -168,30 +147,33 @@ bool TouchID::setKey(const QUuid& dbUuid, const QByteArray& passwordKey, const b kCFAllocatorDefault, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, accessControlFlags, &error); if (sacObject == NULL || error != NULL) { - NSError* e = (__bridge NSError*) error; + auto e = (__bridge NSError*) error; debug("TouchID::setKey - Error creating security flags: %s", e.localizedDescription.UTF8String); return false; } - NSString *accountName = keyName.toNSString(); // The NSString is released by Qt + auto accountName = keyName.toNSString(); + auto keyBase64 = key.toBase64(); // prepare data (key) to be stored - QByteArray keychainKeyValue = (randomKey + randomIV).toHex(); - CFDataRef keychainValueData = - CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, reinterpret_cast(keychainKeyValue.data()), - keychainKeyValue.length(), kCFAllocatorDefault); + auto keyValueData = CFDataCreateWithBytesNoCopy( + kCFAllocatorDefault, reinterpret_cast(keyBase64.data()), + keyBase64.length(), kCFAllocatorDefault); - CFMutableDictionaryRef attributes = makeDictionary(); + auto attributes = makeDictionary(); CFDictionarySetValue(attributes, kSecClass, kSecClassGenericPassword); CFDictionarySetValue(attributes, kSecAttrAccount, (__bridge CFStringRef) accountName); - CFDictionarySetValue(attributes, kSecValueData, (__bridge CFDataRef) keychainValueData); + CFDictionarySetValue(attributes, kSecValueData, (__bridge CFDataRef) keyValueData); CFDictionarySetValue(attributes, kSecAttrSynchronizable, kCFBooleanFalse); CFDictionarySetValue(attributes, kSecUseAuthenticationUI, kSecUseAuthenticationUIAllow); +#ifndef QT_DEBUG + // Only use TouchID when in release build, also requires application entitlements and signing CFDictionarySetValue(attributes, kSecAttrAccessControl, sacObject); +#endif // add to KeyChain OSStatus status = SecItemAdd(attributes, NULL); - LogStatusError("TouchID::setKey - Status adding new entry", status); + LogStatusError("TouchID::setKey - Error adding new keychain item", status); CFRelease(sacObject); CFRelease(attributes); @@ -226,12 +208,12 @@ bool TouchID::setKey(const QUuid& dbUuid, const QByteArray& passwordKey) } /** - * Checks if an encrypted PasswordKey is available for the given database, tries to - * decrypt it using the KeyChain and if successful, returns it. + * Retrieve serialized key data from the macOS Keychain after successful authentication + * with TouchID or Watch interface. */ -bool TouchID::getKey(const QUuid& dbUuid, QByteArray& passwordKey) +bool TouchID::getKey(const QUuid& dbUuid, QByteArray& key) { - passwordKey.clear(); + key.clear(); if (!hasKey(dbUuid)) { debug("TouchID::getKey - No stored key found"); @@ -266,39 +248,30 @@ bool TouchID::getKey(const QUuid& dbUuid, QByteArray& passwordKey) return false; } + // Convert value returned to serialized key CFDataRef valueData = static_cast(dataTypeRef); - QByteArray dataBytes = QByteArray::fromHex(QByteArray(reinterpret_cast(CFDataGetBytePtr(valueData)), - CFDataGetLength(valueData))); + key = QByteArray::fromBase64(QByteArray(reinterpret_cast(CFDataGetBytePtr(valueData)), + CFDataGetLength(valueData))); CFRelease(dataTypeRef); - // extract AES key and IV from data bytes - QByteArray key = dataBytes.left(SymmetricCipher::keySize(SymmetricCipher::Aes256_GCM)); - QByteArray iv = dataBytes.right(SymmetricCipher::defaultIvSize(SymmetricCipher::Aes256_GCM)); - - SymmetricCipher aes256Decrypt; - if (!aes256Decrypt.init(SymmetricCipher::Aes256_GCM, SymmetricCipher::Decrypt, key, iv)) { - debug("TouchID::getKey - AES initialization failed"); - return false; - } - - // decrypt PasswordKey from memory using AES - passwordKey = m_encryptedMasterKeys[dbUuid]; - if (!aes256Decrypt.finish(passwordKey)) { - passwordKey.clear(); - debug("TouchID::getKey - AES decrypt failed: %s", aes256Decrypt.errorString().toUtf8().constData()); - return false; - } - - // Cleanse the key information from the memory - Botan::secure_scrub_memory(key.data(), key.size()); - Botan::secure_scrub_memory(iv.data(), iv.size()); - return true; } bool TouchID::hasKey(const QUuid& dbUuid) const { - return m_encryptedMasterKeys.contains(dbUuid); + const QString keyName = databaseKeyName(dbUuid); + NSString* accountName = keyName.toNSString(); // The NSString is released by Qt + + CFMutableDictionaryRef query = makeDictionary(); + CFDictionarySetValue(query, kSecClass, kSecClassGenericPassword); + CFDictionarySetValue(query, kSecAttrAccount, (__bridge CFStringRef) accountName); + CFDictionarySetValue(query, kSecReturnData, kCFBooleanFalse); + + CFTypeRef item = NULL; + OSStatus status = SecItemCopyMatching(query, &item); + CFRelease(query); + + return status == errSecSuccess; } // TODO: Both functions below should probably handle the returned errors to @@ -320,11 +293,7 @@ bool TouchID::isWatchAvailable() bool canAuthenticate = [context canEvaluatePolicy:policyCode error:&error]; [context release]; if (error) { - debug("Apple Wach available: %d (%ld / %s / %s)", canAuthenticate, - (long)error.code, error.description.UTF8String, - error.localizedDescription.UTF8String); - } else { - debug("Apple Wach available: %d", canAuthenticate); + debug("Apple Watch is not available: %s", error.localizedDescription.UTF8String); } return canAuthenticate; } @catch (NSException *) { @@ -348,11 +317,7 @@ bool TouchID::isTouchIdAvailable() bool canAuthenticate = [context canEvaluatePolicy:policyCode error:&error]; [context release]; if (error) { - debug("Touch ID available: %d (%ld / %s / %s)", canAuthenticate, - (long)error.code, error.description.UTF8String, - error.localizedDescription.UTF8String); - } else { - debug("Touch ID available: %d", canAuthenticate); + debug("Touch ID is not available: %s", error.localizedDescription.UTF8String); } return canAuthenticate; } @catch (NSException *) { @@ -399,10 +364,15 @@ bool TouchID::isAvailable() const return isWatchAvailable() || isTouchIdAvailable() || isPasswordFallbackPossible(); } +bool TouchID::canRemember() const +{ + return true; +} + /** * Resets the inner state either for all or for the given database */ void TouchID::reset(const QUuid& dbUuid) { - m_encryptedMasterKeys.remove(dbUuid); + deleteKeyEntry(databaseKeyName(dbUuid)); } diff --git a/src/quickunlock/WindowsHello.cpp b/src/quickunlock/WindowsHello.cpp index 890e3499a..b1d7c94b2 100644 --- a/src/quickunlock/WindowsHello.cpp +++ b/src/quickunlock/WindowsHello.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -34,6 +35,7 @@ using namespace winrt; using namespace Windows::Foundation; +using namespace Windows::Foundation::Collections; using namespace Windows::Security::Credentials; using namespace Windows::Security::Cryptography; using namespace Windows::Storage::Streams; @@ -97,6 +99,47 @@ namespace } }); } + + void storeCredential(const QUuid& uuid, const QByteArray& data) + { + auto vault = PasswordVault(); + vault.Add({s_winHelloKeyName, + winrt::to_hstring(uuid.toString().toStdString()), + winrt::to_hstring(data.toBase64().toStdString())}); + } + + void removeCredential(const QUuid& uuid) + { + try { + auto vault = PasswordVault(); + vault.Remove({s_winHelloKeyName, winrt::to_hstring(uuid.toString().toStdString()), L"blah"}); + } catch (winrt::hresult_error const& ex) { + } + } + + void resetCredentials() + { + auto vault = PasswordVault(); + auto credentials = vault.FindAllByResource(s_winHelloKeyName); + for (const auto& credential : credentials) { + try { + vault.Remove(credential); + } catch (winrt::hresult_error const& ex) { + } + } + } + + QByteArray loadCredential(const QUuid& uuid) + { + QByteArray data; + try { + auto vault = PasswordVault(); + auto credential = vault.Retrieve(s_winHelloKeyName, winrt::to_hstring(uuid.toString().toStdString())); + data = QByteArray::fromBase64(QByteArray::fromStdString(winrt::to_string(credential.Password()))); + } catch (winrt::hresult_error const& ex) { + } + return data; + } } // namespace bool WindowsHello::isAvailable() const @@ -120,6 +163,7 @@ bool WindowsHello::setKey(const QUuid& dbUuid, const QByteArray& data) auto challenge = Random::instance()->randomArray(ivSize); QByteArray key; if (!deriveEncryptionKey(challenge, key, m_error)) { + m_error = QObject::tr("Windows Hello setup was canceled or failed. Quick unlock has not been enabled."); return false; } @@ -137,7 +181,7 @@ bool WindowsHello::setKey(const QUuid& dbUuid, const QByteArray& data) // Prepend the challenge/IV to the encrypted data encrypted.prepend(challenge); - m_encryptedKeys.insert(dbUuid, encrypted); + storeCredential(dbUuid, encrypted); return true; } @@ -153,7 +197,7 @@ bool WindowsHello::getKey(const QUuid& dbUuid, QByteArray& data) // Read the previously used challenge and encrypted data auto ivSize = SymmetricCipher::defaultIvSize(SymmetricCipher::Aes256_GCM); - const auto& keydata = m_encryptedKeys.value(dbUuid); + const auto& keydata = loadCredential(dbUuid); auto challenge = keydata.left(ivSize); auto encrypted = keydata.mid(ivSize); QByteArray key; @@ -182,15 +226,20 @@ bool WindowsHello::getKey(const QUuid& dbUuid, QByteArray& data) void WindowsHello::reset(const QUuid& dbUuid) { - m_encryptedKeys.remove(dbUuid); + removeCredential(dbUuid); } bool WindowsHello::hasKey(const QUuid& dbUuid) const { - return m_encryptedKeys.contains(dbUuid); + return !loadCredential(dbUuid).isEmpty(); +} + +bool WindowsHello::canRemember() const +{ + return true; } void WindowsHello::reset() { - m_encryptedKeys.clear(); + resetCredentials(); } diff --git a/src/quickunlock/WindowsHello.h b/src/quickunlock/WindowsHello.h index 9da6e4160..3032b89a9 100644 --- a/src/quickunlock/WindowsHello.h +++ b/src/quickunlock/WindowsHello.h @@ -27,18 +27,21 @@ class WindowsHello : public QuickUnlockInterface { public: WindowsHello() = default; + bool isAvailable() const override; QString errorString() const override; - void reset() override; bool setKey(const QUuid& dbUuid, const QByteArray& key) override; bool getKey(const QUuid& dbUuid, QByteArray& key) override; bool hasKey(const QUuid& dbUuid) const override; - void reset(const QUuid& dbUuid) override; + bool canRemember() const override; + + void reset(const QUuid& dbUuid) override; + void reset() override; + private: QString m_error; - QHash m_encryptedKeys; Q_DISABLE_COPY(WindowsHello); };