Skip to content

Commit

Permalink
Add TouchID support on macOS
Browse files Browse the repository at this point in the history
  • Loading branch information
k6nmx committed Jun 17, 2018
1 parent f4f8be5 commit c77286d
Show file tree
Hide file tree
Showing 17 changed files with 535 additions and 5 deletions.
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ option(WITH_XC_NETWORKING "Include networking code (e.g. for downlading website
option(WITH_XC_BROWSER "Include browser integration with keepassxc-browser." OFF)
option(WITH_XC_YUBIKEY "Include YubiKey support." OFF)
option(WITH_XC_SSHAGENT "Include SSH agent support." OFF)
if(APPLE)
option(WITH_XC_TOUCHID "Include TouchID support for macOS." OFF)
endif()

if(WITH_XC_ALL)
# Enable all options
Expand All @@ -55,6 +58,9 @@ if(WITH_XC_ALL)
set(WITH_XC_BROWSER ON)
set(WITH_XC_YUBIKEY ON)
set(WITH_XC_SSHAGENT ON)
if(APPLE)
set(WITH_XC_TOUCHID ON)
endif()
endif()

# Process ui files automatically from source files
Expand Down
8 changes: 8 additions & 0 deletions share/translations/keepassx_en_US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,14 @@ Please consider generating a new key file.</translation>
<source>Select key file</source>
<translation>Select key file</translation>
</message>
<message>
<source>authenticate to access the database</source>
<translation>authenticate to access the database</translation>
</message>
<message>
<source>authenticate a privileged operation</source>
<translation>authenticate a privileged operation</translation>
</message>
</context>
<context>
<name>DatabaseRepairWidget</name>
Expand Down
16 changes: 16 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,9 @@ add_feature_info(Networking WITH_XC_NETWORKING "Compile KeePassXC with network a
add_feature_info(KeePassXC-Browser WITH_XC_BROWSER "Browser integration with KeePassXC-Browser")
add_feature_info(SSHAgent WITH_XC_SSHAGENT "SSH agent integration compatible with KeeAgent")
add_feature_info(YubiKey WITH_XC_YUBIKEY "YubiKey HMAC-SHA1 challenge-response")
if(APPLE)
add_feature_info(TouchID WITH_XC_TOUCHID "TouchID integration")
endif()

set(BROWSER_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/browser)
add_subdirectory(browser)
Expand Down Expand Up @@ -240,6 +243,10 @@ else()
list(APPEND keepassx_SOURCES keys/drivers/YubiKeyStub.cpp)
endif()

if(WITH_XC_TOUCHID)
list(APPEND keepassx_SOURCES touchid/TouchID.mm)
endif()

add_library(autotype STATIC ${autotype_SOURCES})
target_link_libraries(autotype Qt5::Core Qt5::Widgets)

Expand Down Expand Up @@ -267,6 +274,10 @@ if(APPLE)
if(Qt5MacExtras_FOUND)
target_link_libraries(keepassx_core Qt5::MacExtras)
endif()
if(WITH_XC_TOUCHID)
target_link_libraries(keepassx_core "-framework Security")
target_link_libraries(keepassx_core "-framework LocalAuthentication")
endif()
endif()
if (UNIX AND NOT APPLE)
target_link_libraries(keepassx_core Qt5::DBus)
Expand Down Expand Up @@ -298,6 +309,11 @@ if(APPLE AND WITH_APP_BUNDLE)
MACOSX_BUNDLE ON
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_BINARY_DIR}/Info.plist)

if(WITH_XC_TOUCHID)
set_target_properties(${PROGNAME} PROPERTIES
CPACK_BUNDLE_APPLE_ENTITLEMENTS "${CMAKE_SOURCE_DIR}/share/macosx/keepassxc.entitlements" )
endif()

if(QT_MAC_USE_COCOA AND EXISTS "${QT_LIBRARY_DIR}/Resources/qt_menu.nib")
install(DIRECTORY "${QT_LIBRARY_DIR}/Resources/qt_menu.nib"
DESTINATION "${DATA_INSTALL_DIR}")
Expand Down
1 change: 1 addition & 0 deletions src/config-keepassx.h.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#cmakedefine WITH_XC_BROWSER
#cmakedefine WITH_XC_YUBIKEY
#cmakedefine WITH_XC_SSHAGENT
#cmakedefine WITH_XC_TOUCHID

#cmakedefine KEEPASSXC_BUILD_TYPE "@KEEPASSXC_BUILD_TYPE@"
#cmakedefine KEEPASSXC_BUILD_TYPE_RELEASE
Expand Down
3 changes: 3 additions & 0 deletions src/core/Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ void Config::init(const QString& fileName)
m_defaults.insert("security/hidepassworddetails", true);
m_defaults.insert("security/autotypeask", true);
m_defaults.insert("security/IconDownloadFallbackToGoogle", false);
m_defaults.insert("security/resettouchid", false);
m_defaults.insert("security/resettouchidtimeout", 30);
m_defaults.insert("security/resettouchidscreenlock", true);
m_defaults.insert("GUI/Language", "system");
m_defaults.insert("GUI/ShowTrayIcon", false);
m_defaults.insert("GUI/DarkTrayIcon", false);
Expand Down
3 changes: 3 additions & 0 deletions src/gui/AboutDialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ AboutDialog::AboutDialog(QWidget* parent)
#ifdef WITH_XC_YUBIKEY
extensions += "\n- " + tr("YubiKey");
#endif
#ifdef WITH_XC_TOUCHID
extensions += "\n- " + tr("TouchID");
#endif

if (extensions.isEmpty())
extensions = " " + tr("None");
Expand Down
51 changes: 51 additions & 0 deletions src/gui/DatabaseOpenWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include "keys/FileKey.h"
#include "keys/PasswordKey.h"
#include "keys/YkChallengeResponseKey.h"
#include "touchid/TouchID.h"

#include "config-keepassx.h"

Expand Down Expand Up @@ -81,6 +82,14 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent)
m_ui->gridLayout->setContentsMargins(10, 0, 0, 0);
m_ui->labelLayout->setContentsMargins(10, 0, 10, 0);
#endif

#ifndef WITH_XC_TOUCHID
m_ui->checkTouchID->setVisible(false);
#else
if (!TouchID::getInstance().isAvailable()) {
m_ui->checkTouchID->setVisible(false);
}
#endif
}

DatabaseOpenWidget::~DatabaseOpenWidget()
Expand Down Expand Up @@ -131,6 +140,9 @@ void DatabaseOpenWidget::load(const QString& filename)
}
}

QHash<QString, QVariant> useTouchID = config()->get("UseTouchID").toHash();
m_ui->checkTouchID->setChecked(useTouchID.value(m_filename, false).toBool());

m_ui->editPassword->setFocus();
}

Expand All @@ -141,6 +153,7 @@ void DatabaseOpenWidget::clearForms()
m_ui->checkPassword->setChecked(false);
m_ui->checkKeyFile->setChecked(false);
m_ui->checkChallengeResponse->setChecked(false);
m_ui->checkTouchID->setChecked(false);
m_ui->buttonTogglePassword->setChecked(false);
m_db = nullptr;
}
Expand Down Expand Up @@ -187,6 +200,24 @@ void DatabaseOpenWidget::openDatabase()
QApplication::restoreOverrideCursor();

if (m_db) {
#ifdef WITH_XC_TOUCHID
QHash<QString, QVariant> useTouchID = config()->get("UseTouchID").toHash();

// check if TouchID can & should be used to unlock the database next time
if (m_ui->checkTouchID->isChecked() && TouchID::getInstance().isAvailable()) {
// encrypt and store key blob
if (TouchID::getInstance().storeKey(m_filename, PasswordKey(m_ui->editPassword->text()).rawKey())) {
useTouchID.insert(m_filename, true);
}
} else {
// when TouchID not available or unchecked, reset for the current database
TouchID::getInstance().reset(m_filename);
useTouchID.insert(m_filename, false);
}

config()->set("UseTouchID", useTouchID);
#endif

if (m_ui->messageWidget->isVisible()) {
m_ui->messageWidget->animatedHide();
}
Expand All @@ -195,6 +226,11 @@ void DatabaseOpenWidget::openDatabase()
m_ui->messageWidget->showMessage(tr("Unable to open the database.").append("\n").append(reader.errorString()),
MessageWidget::Error);
m_ui->editPassword->clear();

#ifdef WITH_XC_TOUCHID
// unable to unlock database, reset TouchID for the current database
TouchID::getInstance().reset(m_filename);
#endif
}
}

Expand All @@ -206,6 +242,21 @@ QSharedPointer<CompositeKey> DatabaseOpenWidget::databaseKey()
masterKey->addKey(PasswordKey(m_ui->editPassword->text()));
}

#ifdef WITH_XC_TOUCHID
// check if TouchID is available and enabled for unlocking the database
if (m_ui->checkTouchID->isChecked() && TouchID::getInstance().isAvailable() && masterKey->isEmpty()) {
// try to get, decrypt and use PasswordKey
QSharedPointer<QByteArray> passwordKey = TouchID::getInstance().getKey(m_filename);
if (passwordKey != NULL) {
// check if the user cancelled the operation
if (passwordKey.isNull())
return QSharedPointer<CompositeKey>();

masterKey->addKey(PasswordKey::fromRawKey(*passwordKey));
}
}
#endif

QHash<QString, QVariant> lastKeyFiles = config()->get("LastKeyFiles").toHash();
QHash<QString, QVariant> lastChallengeResponse = config()->get("LastChallengeResponse").toHash();

Expand Down
19 changes: 18 additions & 1 deletion src/gui/DatabaseOpenWidget.ui
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<height>302</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,1,0,1,0,0,3">
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,1,0,1,0,0,0,3">
<property name="spacing">
<number>8</number>
</property>
Expand Down Expand Up @@ -225,6 +225,23 @@
</item>
</layout>
</item>
<item>
<layout class="QGridLayout" name="touchIDLayout">
<property name="leftMargin">
<number>5</number>
</property>
<property name="verticalSpacing">
<number>8</number>
</property>
<item row="0" column="0">
<widget class="QCheckBox" name="checkTouchID">
<property name="text">
<string>Use TouchID for quick unlock</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="dialogButtonsLayout">
<property name="leftMargin">
Expand Down
4 changes: 4 additions & 0 deletions src/gui/DatabaseWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
#include "gui/entry/EntryView.h"
#include "gui/group/EditGroupWidget.h"
#include "gui/group/GroupView.h"
#include "touchid/TouchID.h"

#include "config-keepassx.h"

Expand Down Expand Up @@ -828,6 +829,9 @@ void DatabaseWidget::updateMasterKey(bool accepted)
if (accepted) {
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
bool result = m_db->setKey(m_changeMasterKeyWidget->newMasterKey(), true, true);
#ifdef WITH_XC_TOUCHID
TouchID::getInstance().reset(m_filePath);
#endif
QApplication::restoreOverrideCursor();

if (!result) {
Expand Down
34 changes: 34 additions & 0 deletions src/gui/MainWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@
#include "gui/PasswordGeneratorWidget.h"
#include "gui/SettingsWidget.h"

#include "touchid/TouchID.h"

#ifdef WITH_XC_BROWSER
class BrowserPlugin : public ISettingsPage
{
Expand Down Expand Up @@ -183,6 +185,10 @@ MainWindow::MainWindow()

m_inactivityTimer = new InactivityTimer(this);
connect(m_inactivityTimer, SIGNAL(inactivityDetected()), this, SLOT(lockDatabasesAfterInactivity()));
#ifdef WITH_XC_TOUCHID
m_touchIDinactivityTimer = new InactivityTimer(this);
connect(m_touchIDinactivityTimer, SIGNAL(inactivityDetected()), this, SLOT(forgetTouchIDAfterInactivity()));
#endif
applySettingsChanges();

m_ui->actionDatabaseNew->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_N);
Expand Down Expand Up @@ -841,6 +847,21 @@ void MainWindow::applySettingsChanges()
m_inactivityTimer->deactivate();
}

#ifdef WITH_XC_TOUCHID
// forget TouchID (in minutes)
timeout = config()->get("security/resettouchidtimeout").toInt() * 60 * 1000;
if (timeout <= 0) {
timeout = 30 * 60 * 1000;
}

m_touchIDinactivityTimer->setInactivityTimeout(timeout);
if (config()->get("security/resettouchid").toBool()) {
m_touchIDinactivityTimer->activate();
} else {
m_touchIDinactivityTimer->deactivate();
}
#endif

updateTrayIcon();
}

Expand Down Expand Up @@ -905,6 +926,13 @@ void MainWindow::lockDatabasesAfterInactivity()
m_ui->tabWidget->lockDatabases();
}

void MainWindow::forgetTouchIDAfterInactivity()
{
#ifdef WITH_XC_TOUCHID
TouchID::getInstance().reset();
#endif
}

void MainWindow::repairDatabase()
{
QString filter = QString("%1 (*.kdbx);;%2 (*)").arg(tr("KeePass 2 Database"), tr("All files"));
Expand Down Expand Up @@ -1001,6 +1029,12 @@ void MainWindow::handleScreenLock()
if (config()->get("security/lockdatabasescreenlock").toBool()) {
lockDatabasesAfterInactivity();
}

#ifdef WITH_XC_TOUCHID
if (config()->get("security/resettouchidscreenlock").toBool()) {
forgetTouchIDAfterInactivity();
}
#endif
}

QStringList MainWindow::kdbxFilesFromUrls(const QList<QUrl>& urls)
Expand Down
2 changes: 2 additions & 0 deletions src/gui/MainWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ private slots:
void hideWindow();
void toggleWindow();
void lockDatabasesAfterInactivity();
void forgetTouchIDAfterInactivity();
void repairDatabase();
void hideTabMessage();
void handleScreenLock();
Expand Down Expand Up @@ -131,6 +132,7 @@ private slots:
QActionGroup* m_copyAdditionalAttributeActions;
QStringList m_openDatabases;
InactivityTimer* m_inactivityTimer;
InactivityTimer* m_touchIDinactivityTimer;
int m_countDefaultAttributes;
QSystemTrayIcon* m_trayIcon;
ScreenLockListener* m_screenLockListener;
Expand Down
26 changes: 26 additions & 0 deletions src/gui/SettingsWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
#include "core/Global.h"
#include "core/Translator.h"

#include "touchid/TouchID.h"

class SettingsWidget::ExtraPage
{
public:
Expand Down Expand Up @@ -86,9 +88,25 @@ SettingsWidget::SettingsWidget(QWidget* parent)
m_secUi->lockDatabaseIdleSpinBox,
SLOT(setEnabled(bool)));

connect(m_secUi->touchIDResetCheckBox,
SIGNAL(toggled(bool)),
m_secUi->touchIDResetSpinBox,
SLOT(setEnabled(bool)));

#ifndef WITH_XC_NETWORKING
m_secUi->privacy->setVisible(false);
#endif

#ifndef WITH_XC_TOUCHID
bool hideTouchID = true;
#else
bool hideTouchID = !TouchID::getInstance().isAvailable();
#endif
if (hideTouchID) {
m_secUi->touchIDResetCheckBox->setVisible(false);
m_secUi->touchIDResetSpinBox->setVisible(false);
m_secUi->touchIDResetOnScreenLockCheckBox->setVisible(false);
}
}

SettingsWidget::~SettingsWidget()
Expand Down Expand Up @@ -174,6 +192,10 @@ void SettingsWidget::loadSettings()
m_secUi->passwordRepeatCheckBox->setChecked(config()->get("security/passwordsrepeat").toBool());
m_secUi->hideNotesCheckBox->setChecked(config()->get("security/hidenotes").toBool());

m_secUi->touchIDResetCheckBox->setChecked(config()->get("security/resettouchid").toBool());
m_secUi->touchIDResetSpinBox->setValue(config()->get("security/resettouchidtimeout").toInt());
m_secUi->touchIDResetOnScreenLockCheckBox->setChecked(config()->get("security/resettouchidscreenlock").toBool());

for (const ExtraPage& page : asConst(m_extraPages)) {
page.loadSettings();
}
Expand Down Expand Up @@ -239,6 +261,10 @@ void SettingsWidget::saveSettings()
config()->set("security/passwordsrepeat", m_secUi->passwordRepeatCheckBox->isChecked());
config()->set("security/hidenotes", m_secUi->hideNotesCheckBox->isChecked());

config()->set("security/resettouchid", m_secUi->touchIDResetCheckBox->isChecked());
config()->set("security/resettouchidtimeout", m_secUi->touchIDResetSpinBox->value());
config()->set("security/resettouchidscreenlock", m_secUi->touchIDResetOnScreenLockCheckBox->isChecked());

// Security: clear storage if related settings are disabled
if (!config()->get("RememberLastDatabases").toBool()) {
config()->set("LastDatabases", QVariant());
Expand Down
Loading

0 comments on commit c77286d

Please sign in to comment.