Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/keepassxreboot/keepassxc.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDennis <yowidin@gmail.com>2022-09-05 17:38:02 +0300
committerGitHub <noreply@github.com>2022-09-05 17:38:02 +0300
commitbd809ba90b25d9a0a7634353025efcd0e4c66880 (patch)
treed6923eb310c546a689e7f153debbfe93c68eb072
parent15b9e82f93971a1cd2f63f3b21eafcf3601ec70c (diff)
TouchID support refactoring (#8311)
Fixes #7695 - Properly set compile flags based on availability of watch unlock in the API.
-rw-r--r--CMakeLists.txt19
-rw-r--r--cmake/compiler-checks/macos/control_biometry_support.mm5
-rw-r--r--cmake/compiler-checks/macos/control_touch_id_support.mm5
-rw-r--r--cmake/compiler-checks/macos/control_watch_support.mm5
-rw-r--r--src/CMakeLists.txt2
-rw-r--r--src/config-keepassx.h.cmake9
-rw-r--r--src/touchid/TouchID.h25
-rw-r--r--src/touchid/TouchID.mm333
8 files changed, 229 insertions, 174 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index db732d677..717214c51 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -62,6 +62,25 @@ if(UNIX AND NOT APPLE)
endif()
option(WITH_XC_DOCS "Enable building of documentation" ON)
+if(APPLE)
+ # Perform the platform checks before applying the stricter compiler flags.
+ # Otherwise the kSecAccessControlTouchIDCurrentSet deprecation warning will result in an error.
+ try_compile(XC_APPLE_COMPILER_SUPPORT_BIOMETRY
+ ${CMAKE_CURRENT_BINARY_DIR}/tiometry_test/
+ ${CMAKE_CURRENT_SOURCE_DIR}/cmake/compiler-checks/macos/control_biometry_support.mm)
+ message(STATUS "Biometry compiler support: ${XC_APPLE_COMPILER_SUPPORT_BIOMETRY}")
+
+ try_compile(XC_APPLE_COMPILER_SUPPORT_TOUCH_ID
+ ${CMAKE_CURRENT_BINARY_DIR}/touch_id_test/
+ ${CMAKE_CURRENT_SOURCE_DIR}/cmake/compiler-checks/macos/control_touch_id_support.mm)
+ message(STATUS "Touch ID compiler support: ${XC_APPLE_COMPILER_SUPPORT_TOUCH_ID}")
+
+ try_compile(XC_APPLE_COMPILER_SUPPORT_WATCH
+ ${CMAKE_CURRENT_BINARY_DIR}/tiometry_test/
+ ${CMAKE_CURRENT_SOURCE_DIR}/cmake/compiler-checks/macos/control_watch_support.mm)
+ message(STATUS "Apple watch compiler support: ${XC_APPLE_COMPILER_SUPPORT_WATCH}")
+endif()
+
if(WITH_CCACHE)
# Use the Compiler Cache (ccache) program
# (install with: sudo apt get ccache)
diff --git a/cmake/compiler-checks/macos/control_biometry_support.mm b/cmake/compiler-checks/macos/control_biometry_support.mm
new file mode 100644
index 000000000..1bfbab184
--- /dev/null
+++ b/cmake/compiler-checks/macos/control_biometry_support.mm
@@ -0,0 +1,5 @@
+#include <Security/Security.h>
+
+int main() {
+ return static_cast<int>(kSecAccessControlBiometryCurrentSet);
+} \ No newline at end of file
diff --git a/cmake/compiler-checks/macos/control_touch_id_support.mm b/cmake/compiler-checks/macos/control_touch_id_support.mm
new file mode 100644
index 000000000..e78767498
--- /dev/null
+++ b/cmake/compiler-checks/macos/control_touch_id_support.mm
@@ -0,0 +1,5 @@
+#include <Security/Security.h>
+
+int main() {
+ return static_cast<int>(kSecAccessControlTouchIDCurrentSet);
+} \ No newline at end of file
diff --git a/cmake/compiler-checks/macos/control_watch_support.mm b/cmake/compiler-checks/macos/control_watch_support.mm
new file mode 100644
index 000000000..fce69edc0
--- /dev/null
+++ b/cmake/compiler-checks/macos/control_watch_support.mm
@@ -0,0 +1,5 @@
+#include <Security/Security.h>
+
+int main() {
+ return static_cast<int>(kSecAccessControlWatch);
+} \ No newline at end of file
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 8e6fbc425..c3f83d54c 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -313,7 +313,7 @@ endif()
if(APPLE)
list(APPEND keepassx_SOURCES touchid/TouchID.mm)
# TODO: Remove -Wno-error once deprecation warnings have been resolved.
- set_source_files_properties(touchid/TouchID.mm PROPERTY COMPILE_FLAGS "-Wno-old-style-cast -Wno-error")
+ set_source_files_properties(touchid/TouchID.mm PROPERTY COMPILE_FLAGS "-Wno-old-style-cast")
endif()
configure_file(config-keepassx.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-keepassx.h)
diff --git a/src/config-keepassx.h.cmake b/src/config-keepassx.h.cmake
index 6caa89d81..33d654847 100644
--- a/src/config-keepassx.h.cmake
+++ b/src/config-keepassx.h.cmake
@@ -37,4 +37,13 @@
#cmakedefine HAVE_RLIMIT_CORE 1
#cmakedefine HAVE_PT_DENY_ATTACH 1
+#cmakedefine01 XC_APPLE_COMPILER_SUPPORT_BIOMETRY()
+#cmakedefine01 XC_APPLE_COMPILER_SUPPORT_TOUCH_ID()
+#cmakedefine01 XC_APPLE_COMPILER_SUPPORT_WATCH()
+
+#define XC_COMPILER_SUPPORT(X) XC_COMPILER_SUPPORT_PRIVATE_DEFINITION_##X()
+#define XC_COMPILER_SUPPORT_PRIVATE_DEFINITION_APPLE_BIOMETRY() XC_APPLE_COMPILER_SUPPORT_BIOMETRY()
+#define XC_COMPILER_SUPPORT_PRIVATE_DEFINITION_TOUCH_ID() XC_APPLE_COMPILER_SUPPORT_TOUCH_ID()
+#define XC_COMPILER_SUPPORT_PRIVATE_DEFINITION_WATCH_UNLOCK() XC_APPLE_COMPILER_SUPPORT_WATCH()
+
#endif // KEEPASSX_CONFIG_KEEPASSX_H
diff --git a/src/touchid/TouchID.h b/src/touchid/TouchID.h
index a5e80f0f9..e32f1fa12 100644
--- a/src/touchid/TouchID.h
+++ b/src/touchid/TouchID.h
@@ -1,10 +1,6 @@
#ifndef KEEPASSX_TOUCHID_H
#define KEEPASSX_TOUCHID_H
-#define TOUCHID_UNDEFINED -1
-#define TOUCHID_AVAILABLE 1
-#define TOUCHID_NOT_AVAILABLE 0
-
#include <QHash>
class TouchID
@@ -15,30 +11,29 @@ public:
private:
TouchID()
{
+ // Nothing to do here
}
- // TouchID(TouchID const&); // Don't Implement
- // void operator=(TouchID const&); // Don't implement
-
- QHash<QString, QByteArray> m_encryptedMasterKeys;
- int m_available = TOUCHID_UNDEFINED;
-
public:
TouchID(TouchID const&) = delete;
-
void operator=(TouchID const&) = delete;
bool storeKey(const QString& databasePath, const QByteArray& passwordKey);
-
bool getKey(const QString& databasePath, QByteArray& passwordKey) const;
-
bool containsKey(const QString& databasePath) const;
+ void reset(const QString& databasePath = "");
bool isAvailable();
- bool authenticate(const QString& message = "") const;
+private:
+ static bool isWatchAvailable();
+ static bool isTouchIdAvailable();
- void reset(const QString& databasePath = "");
+ static void deleteKeyEntry(const QString& accountName);
+ static QString databaseKeyName(const QString& databasePath);
+
+private:
+ QHash<QString, QByteArray> m_encryptedMasterKeys;
};
#endif // KEEPASSX_TOUCHID_H
diff --git a/src/touchid/TouchID.mm b/src/touchid/TouchID.mm
index e7539262e..7d6332cc2 100644
--- a/src/touchid/TouchID.mm
+++ b/src/touchid/TouchID.mm
@@ -1,10 +1,11 @@
-#define SECURITY_ACCOUNT_PREFIX QString("KeepassXC_TouchID_Keys_")
-
#include "touchid/TouchID.h"
#include "crypto/Random.h"
#include "crypto/SymmetricCipher.h"
#include "crypto/CryptoHash.h"
+#include "config-keepassx.h"
+
+#include <botan/mem_ops.h>
#include <Foundation/Foundation.h>
#include <CoreFoundation/CoreFoundation.h>
@@ -13,16 +14,40 @@
#include <QCoreApplication>
-inline void debug(const char* message, ...)
+#define TOUCH_ID_ENABLE_DEBUG_LOGS() 0
+#if TOUCH_ID_ENABLE_DEBUG_LOGS()
+#define debug(...) qWarning(__VA_ARGS__)
+#else
+inline void debug(const char *message, ...)
+{
+ Q_UNUSED(message);
+}
+#endif
+
+inline std::string StatusToErrorMessage(OSStatus status)
{
- Q_UNUSED(message);
- // qWarning(...);
+ CFStringRef text = SecCopyErrorMessageString(status, NULL);
+ if (!text) {
+ return std::to_string(status);
+ }
+
+ std::string result(CFStringGetCStringPtr(text, kCFStringEncodingUTF8));
+ CFRelease(text);
+ return result;
}
-inline QString hash(const QString& value)
+inline void LogStatusError(const char *message, OSStatus status)
{
- QByteArray result = CryptoHash::hash(value.toUtf8(), CryptoHash::Sha256).toHex();
- return QString(result);
+ if (!status) {
+ return;
+ }
+
+ std::string msg = StatusToErrorMessage(status);
+ debug("%s: %s", message, msg.c_str());
+}
+
+inline CFMutableDictionaryRef makeDictionary() {
+ return CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
}
/**
@@ -35,23 +60,42 @@ TouchID& TouchID::getInstance()
return instance;
}
+//! Try to delete an existing keychain entry
+void TouchID::deleteKeyEntry(const QString& accountName)
+{
+ NSString* nsAccountName = accountName.toNSString(); // The NSString is released by Qt
+
+ // try to delete an existing entry
+ CFMutableDictionaryRef query = makeDictionary();
+ CFDictionarySetValue(query, kSecClass, kSecClassGenericPassword);
+ CFDictionarySetValue(query, kSecAttrAccount, (__bridge CFStringRef) nsAccountName);
+ CFDictionarySetValue(query, kSecReturnData, kCFBooleanFalse);
+
+ // get data from the KeyChain
+ OSStatus status = SecItemDelete(query);
+ LogStatusError("TouchID::storeKey - Status deleting existing entry", status);
+}
+
+QString TouchID::databaseKeyName(const QString &databasePath)
+{
+ static const QString keyPrefix = "KeepassXC_TouchID_Keys_";
+ const QByteArray pathHash = CryptoHash::hash(databasePath.toUtf8(), CryptoHash::Sha256).toHex();
+ return keyPrefix + pathHash;
+}
+
/**
* Generates a random AES 256bit key and uses it to encrypt the PasswordKey that
* protects the database. The encrypted PasswordKey is kept in memory while the
- * AES key is stored in the macOS KeyChain protected by TouchID.
+ * AES key is stored in the macOS KeyChain protected by either TouchID or Apple Watch.
*/
bool TouchID::storeKey(const QString& databasePath, const QByteArray& passwordKey)
{
if (databasePath.isEmpty() || passwordKey.isEmpty()) {
- // illegal arguments
- debug("TouchID::storeKey - Illegal arguments: databasePath = %s, len(passwordKey) = %d",
- databasePath.toUtf8().constData(),
- passwordKey.length());
+ debug("TouchID::storeKey - illegal arguments");
return false;
}
- if (this->m_encryptedMasterKeys.contains(databasePath)) {
- // already stored key for this database
+ if (m_encryptedMasterKeys.contains(databasePath)) {
debug("TouchID::storeKey - Already stored key for this database");
return true;
}
@@ -62,59 +106,45 @@ bool TouchID::storeKey(const QString& databasePath, const QByteArray& passwordKe
SymmetricCipher aes256Encrypt;
if (!aes256Encrypt.init(SymmetricCipher::Aes256_GCM, SymmetricCipher::Encrypt, randomKey, randomIV)) {
- debug("TouchID::storeKey - Error initializing encryption: %s",
- aes256Encrypt.errorString().toUtf8().constData());
+ debug("TouchID::storeKey - AES initialisation falied");
return false;
}
// encrypt and keep result in memory
QByteArray encryptedMasterKey = passwordKey;
if (!aes256Encrypt.finish(encryptedMasterKey)) {
- debug("TouchID::storeKey - Error encrypting: %s", aes256Encrypt.errorString().toUtf8().constData());
- debug(aes256Encrypt.errorString().toUtf8().constData());
+ debug("TouchID::getKey - AES encrypt failed: %s", aes256Encrypt.errorString().toUtf8().constData());
return false;
}
- // memorize which database the stored key is for
- m_encryptedMasterKeys.insert(databasePath, encryptedMasterKey);
-
- NSString* accountName = (SECURITY_ACCOUNT_PREFIX + hash(databasePath)).toNSString(); // autoreleased
-
- // try to delete an existing entry
- CFMutableDictionaryRef
- query = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
-
- CFDictionarySetValue(query, kSecClass, kSecClassGenericPassword);
- CFDictionarySetValue(query, kSecAttrAccount, (__bridge CFStringRef) accountName);
- CFDictionarySetValue(query, kSecReturnData, kCFBooleanFalse);
-
- // get data from the KeyChain
- OSStatus status = SecItemDelete(query);
+ const QString keyName = databaseKeyName(databasePath);
- debug("TouchID::storeKey - Status deleting existing entry: %d", status);
+ deleteKeyEntry(keyName); // Try to delete the existing key entry
// prepare adding secure entry to the macOS KeyChain
CFErrorRef error = NULL;
- SecAccessControlRef sacObject;
-#if __clang_major__ >= 9 && MAC_OS_X_VERSION_MIN_REQUIRED >= 101500
- if (@available(macOS 10.15, *)) {
- // kSecAccessControlWatch is only available for macOS 10.15 and later
- sacObject = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
- kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
- kSecAccessControlOr | kSecAccessControlBiometryCurrentSet | kSecAccessControlWatch,
- &error);
- } else {
-#endif
-#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101201
- sacObject = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
- kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
- kSecAccessControlTouchIDCurrentSet, // depr: kSecAccessControlBiometryCurrentSet,
- &error);
+
+ // We need both runtime and compile time checks here to solve the following problems:
+ // - Not all flags are available in all OS versions, so we have to check it at compile time
+ // - Requesting Biometry/TouchID when to fingerprint sensor is available will result in runtime error
+ SecAccessControlCreateFlags accessControlFlags = 0;
+ if (isTouchIdAvailable()) {
+#if XC_COMPILER_SUPPORT(APPLE_BIOMETRY)
+ // Prefer the non-deprecated flag when available
+ accessControlFlags = kSecAccessControlBiometryCurrentSet;
+#elif XC_COMPILER_SUPPORT(TOUCH_ID)
+ accessControlFlags = kSecAccessControlTouchIDCurrentSet;
#endif
-#if __clang_major__ >= 9 && MAC_OS_X_VERSION_MIN_REQUIRED >= 101500
}
+
+ if (isWatchAvailable()) {
+#if XC_COMPILER_SUPPORT(WATCH_UNLOCK)
+ accessControlFlags = accessControlFlags | kSecAccessControlOr | kSecAccessControlWatch;
#endif
+ }
+ SecAccessControlRef sacObject = SecAccessControlCreateWithFlags(
+ kCFAllocatorDefault, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, accessControlFlags, &error);
if (sacObject == NULL || error != NULL) {
NSError* e = (__bridge NSError*) error;
@@ -122,36 +152,40 @@ bool TouchID::storeKey(const QString& databasePath, const QByteArray& passwordKe
return false;
}
- CFMutableDictionaryRef attributes =
- CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ NSString *accountName = keyName.toNSString(); // The NSString is released by Qt
// prepare data (key) to be stored
- QByteArray dataBytes = (randomKey + randomIV).toHex();
-
- CFDataRef valueData =
- CFDataCreateWithBytesNoCopy(NULL, reinterpret_cast<UInt8*>(dataBytes.data()), dataBytes.length(), NULL);
+ QByteArray keychainKeyValue = (randomKey + randomIV).toHex();
+ CFDataRef keychainValueData =
+ CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, reinterpret_cast<UInt8 *>(keychainKeyValue.data()),
+ keychainKeyValue.length(), kCFAllocatorDefault);
+ CFMutableDictionaryRef attributes = makeDictionary();
CFDictionarySetValue(attributes, kSecClass, kSecClassGenericPassword);
CFDictionarySetValue(attributes, kSecAttrAccount, (__bridge CFStringRef) accountName);
- CFDictionarySetValue(attributes, kSecValueData, valueData);
+ CFDictionarySetValue(attributes, kSecValueData, (__bridge CFDataRef) keychainValueData);
CFDictionarySetValue(attributes, kSecAttrSynchronizable, kCFBooleanFalse);
CFDictionarySetValue(attributes, kSecUseAuthenticationUI, kSecUseAuthenticationUIAllow);
CFDictionarySetValue(attributes, kSecAttrAccessControl, sacObject);
// add to KeyChain
- status = SecItemAdd(attributes, NULL);
-
- debug("TouchID::storeKey - Status adding new entry: %d", status); // read w/ e.g. "security error -50" in shell
+ OSStatus status = SecItemAdd(attributes, NULL);
+ LogStatusError("TouchID::storeKey - Status adding new entry", status);
CFRelease(sacObject);
CFRelease(attributes);
if (status != errSecSuccess) {
- debug("TouchID::storeKey - Not successful, resetting TouchID");
- this->m_encryptedMasterKeys.remove(databasePath);
return false;
}
+ // Cleanse the key information from the memory
+ Botan::secure_scrub_memory(randomKey.data(), randomKey.size());
+ Botan::secure_scrub_memory(randomIV.data(), randomIV.size());
+
+ // memorize which database the stored key is for
+ m_encryptedMasterKeys.insert(databasePath, encryptedMasterKey);
+ debug("TouchID::storeKey - Success!");
return true;
}
@@ -163,25 +197,23 @@ bool TouchID::getKey(const QString& databasePath, QByteArray& passwordKey) const
{
passwordKey.clear();
if (databasePath.isEmpty()) {
- // illegal arguments
- debug("TouchID::storeKey - Illegal argument: databasePath = %s", databasePath.toUtf8().constData());
+ debug("TouchID::getKey - missing database path");
return false;
}
- // checks if encrypted PasswordKey is available and is stored for the given database
if (!containsKey(databasePath)) {
debug("TouchID::getKey - No stored key found");
return false;
}
// query the KeyChain for the AES key
- CFMutableDictionaryRef
- query = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ CFMutableDictionaryRef query = makeDictionary();
- NSString* accountName = (SECURITY_ACCOUNT_PREFIX + hash(databasePath)).toNSString(); // autoreleased
+ const QString keyName = databaseKeyName(databasePath);
+ NSString* accountName = keyName.toNSString(); // The NSString is released by Qt
NSString* touchPromptMessage =
QCoreApplication::translate("DatabaseOpenWidget", "authenticate to access the database")
- .toNSString(); // autoreleased
+ .toNSString(); // The NSString is released by Qt
CFDictionarySetValue(query, kSecClass, kSecClassGenericPassword);
CFDictionarySetValue(query, kSecAttrAccount, (__bridge CFStringRef) accountName);
@@ -198,14 +230,14 @@ bool TouchID::getKey(const QString& databasePath, QByteArray& passwordKey) const
debug("TouchID::getKey - User canceled authentication");
return true;
} else if (status != errSecSuccess || dataTypeRef == NULL) {
- debug("TouchID::getKey - Error retrieving result: %d", status);
+ LogStatusError("TouchID::getKey - key query error", status);
return false;
}
CFDataRef valueData = static_cast<CFDataRef>(dataTypeRef);
QByteArray dataBytes = QByteArray::fromHex(QByteArray(reinterpret_cast<const char*>(CFDataGetBytePtr(valueData)),
CFDataGetLength(valueData)));
- CFRelease(valueData);
+ CFRelease(dataTypeRef);
// extract AES key and IV from data bytes
QByteArray key = dataBytes.left(SymmetricCipher::keySize(SymmetricCipher::Aes256_GCM));
@@ -213,7 +245,7 @@ bool TouchID::getKey(const QString& databasePath, QByteArray& passwordKey) const
SymmetricCipher aes256Decrypt;
if (!aes256Decrypt.init(SymmetricCipher::Aes256_GCM, SymmetricCipher::Decrypt, key, iv)) {
- debug("TouchID::getKey - Error initializing decryption: %s", aes256Decrypt.errorString().toUtf8().constData());
+ debug("TouchID::getKey - AES initialization failed");
return false;
}
@@ -221,10 +253,14 @@ bool TouchID::getKey(const QString& databasePath, QByteArray& passwordKey) const
passwordKey = m_encryptedMasterKeys[databasePath];
if (!aes256Decrypt.finish(passwordKey)) {
passwordKey.clear();
- debug("TouchID::getKey - Error decryption: %s", aes256Decrypt.errorString().toUtf8().constData());
+ 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;
}
@@ -233,96 +269,77 @@ bool TouchID::containsKey(const QString& dbPath) const
return m_encryptedMasterKeys.contains(dbPath);
}
-/**
- * Dynamic check if TouchID is available on the current machine.
- */
-bool TouchID::isAvailable()
+// TODO: Both functions below should probably handle the returned errors to
+// provide more information on availability. E.g.: the closed laptop lid results
+// in an error (because touch id is not unavailable). That error could be
+// displayed to the user when we first check for availability instead of just
+// hiding the checkbox.
+
+//! @return true if Apple Watch is available for authentication.
+bool TouchID::isWatchAvailable()
{
-#if MAC_OS_X_VERSION_MIN_REQUIRED < 101201
- return false;
+#if XC_COMPILER_SUPPORT(WATCH_UNLOCK)
+ @try {
+ LAContext *context = [[LAContext alloc] init];
+
+ LAPolicy policyCode = LAPolicyDeviceOwnerAuthenticationWithWatch;
+ NSError *error;
+
+ 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);
+ }
+ return canAuthenticate;
+ } @catch (NSException *) {
+ return false;
+ }
#else
- // cache result
- if (this->m_available != TOUCHID_UNDEFINED) {
- return (this->m_available == TOUCHID_AVAILABLE);
- }
-
- @try {
- LAContext* context = [[LAContext alloc] init];
-
- LAPolicy policyCode;
-#if __clang_major__ >= 9 && MAC_OS_X_VERSION_MIN_REQUIRED >= 101500
- if (@available(macOS 10.15, *)) {
- policyCode = LAPolicyDeviceOwnerAuthenticationWithBiometricsOrWatch;
- } else {
-#endif
- policyCode = LAPolicyDeviceOwnerAuthenticationWithBiometrics;
-#if __clang_major__ >= 9 && MAC_OS_X_VERSION_MIN_REQUIRED >= 101500
- }
-#endif
-
- bool canAuthenticate = [context canEvaluatePolicy:policyCode error:nil];
- [context release];
- this->m_available = canAuthenticate ? TOUCHID_AVAILABLE : TOUCHID_NOT_AVAILABLE;
- return canAuthenticate;
- }
- @catch (NSException*) {
- this->m_available = TOUCHID_NOT_AVAILABLE;
- return false;
- }
+ return false;
#endif
}
-typedef enum
-{
- kTouchIDResultNone,
- kTouchIDResultAllowed,
- kTouchIDResultFailed
-} TouchIDResult;
-
-/**
- * Performs a simple authentication using TouchID.
- */
-bool TouchID::authenticate(const QString& message) const
+//! @return true if Touch ID is available for authentication.
+bool TouchID::isTouchIdAvailable()
{
- // message must not be an empty string
- QString msg = message;
- if (message.length() == 0)
- msg = QCoreApplication::translate("DatabaseOpenWidget", "authenticate a privileged operation");
-
- @try {
- LAContext* context = [[LAContext alloc] init];
- __block TouchIDResult result = kTouchIDResultNone;
- NSString* authMessage = msg.toNSString(); // autoreleased
-
- LAPolicy policyCode;
-#if __clang_major__ >= 9 && MAC_OS_X_VERSION_MIN_REQUIRED >= 101500
- if (@available(macOS 10.15, *)) {
- policyCode = LAPolicyDeviceOwnerAuthenticationWithBiometricsOrWatch;
- } else {
-#endif
-#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101201
- policyCode = LAPolicyDeviceOwnerAuthenticationWithBiometrics;
-#endif
-#if __clang_major__ >= 9 && MAC_OS_X_VERSION_MIN_REQUIRED >= 101500
- }
+#if XC_COMPILER_SUPPORT(TOUCH_ID)
+ @try {
+ LAContext *context = [[LAContext alloc] init];
+
+ LAPolicy policyCode = LAPolicyDeviceOwnerAuthenticationWithBiometrics;
+ NSError *error;
+
+ 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);
+ }
+ return canAuthenticate;
+ } @catch (NSException *) {
+ return false;
+ }
+#else
+ return false;
#endif
+}
- [context evaluatePolicy:policyCode
- localizedReason:authMessage reply:^(BOOL success, NSError* error) {
- Q_UNUSED(error);
- result = success ? kTouchIDResultAllowed : kTouchIDResultFailed;
- CFRunLoopWakeUp(CFRunLoopGetCurrent());
- }];
-
- while (result == kTouchIDResultNone)
- CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true);
-
- [context release];
- return result == kTouchIDResultAllowed;
- }
- @catch (NSException*) {
- return false;
- }
+//! @return true if either TouchID or Apple Watch is available at the moment.
+bool TouchID::isAvailable()
+{
+ // note: we cannot cache the check results because the configuration
+ // is dynamic in its nature. User can close the laptop lid or take off
+ // the watch, thus making one (or both) of the authentication types unavailable.
+ const bool watchAvailable = isWatchAvailable();
+ const bool touchIdAvailable = isTouchIdAvailable();
+ return watchAvailable || touchIdAvailable;
}
/**
@@ -331,9 +348,9 @@ bool TouchID::authenticate(const QString& message) const
void TouchID::reset(const QString& databasePath)
{
if (databasePath.isEmpty()) {
- this->m_encryptedMasterKeys.clear();
+ m_encryptedMasterKeys.clear();
return;
}
- this->m_encryptedMasterKeys.remove(databasePath);
+ m_encryptedMasterKeys.remove(databasePath);
}