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:
authorJanek Bevendorff <janek@jbev.net>2019-02-22 00:28:45 +0300
committerJonathan White <support@dmapps.us>2019-04-21 16:39:28 +0300
commit13eb1c0bbdf07312f099099c7ca571c6a77eafa1 (patch)
treee9f20157bd7cd08abad3f12ddb3a99e8fd14fc6f
parentc7898fdeee07b17939d2e5af4bb507493b2d8a0b (diff)
Improve resilience against memory attacks
To reduce residual fragments of secret data in memory after deallocation, this patch replaces the global delete operator with a version that zeros out previously allocated memory. It makes use of the new C++14 sized deallocation, but provides an unsized fallback with platform-specific size deductions. This change is only a minor mitigation and cannot protect against buffer reallocations by the operating system or non-C++ libraries. Thus, we still cannot guarantee all memory to be wiped after free. As a further improvement, this patch uses libgcrypt and libsodium to write long-lived master key component hashes into a secure memory area and wipe it afterwards. The patch also fixes compiler flags not being set properly on macOS.
-rw-r--r--CMakeLists.txt18
-rw-r--r--INSTALL.md2
-rw-r--r--src/CMakeLists.txt4
-rwxr-xr-xsrc/browser/CMakeLists.txt3
-rw-r--r--src/cli/CMakeLists.txt1
-rw-r--r--src/core/Alloc.cpp89
-rw-r--r--src/keys/FileKey.cpp46
-rw-r--r--src/keys/FileKey.h5
-rw-r--r--src/keys/PasswordKey.cpp24
-rw-r--r--src/keys/PasswordKey.h5
-rw-r--r--src/keys/YkChallengeResponseKey.cpp27
-rw-r--r--src/keys/YkChallengeResponseKey.h6
-rwxr-xr-xsrc/proxy/CMakeLists.txt3
-rw-r--r--src/touchid/TouchID.mm2
14 files changed, 207 insertions, 28 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 407a27542..0a84ef18c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -41,7 +41,7 @@ option(WITH_ASAN "Enable address sanitizer checks (Linux / macOS only)" OFF)
option(WITH_COVERAGE "Use to build with coverage tests (GCC only)." OFF)
option(WITH_APP_BUNDLE "Enable Application Bundle for macOS" ON)
-set(WITH_XC_ALL OFF CACHE BOOLEAN "Build in all available plugins")
+set(WITH_XC_ALL OFF CACHE BOOL "Build in all available plugins")
option(WITH_XC_AUTOTYPE "Include Auto-Type." ON)
option(WITH_XC_NETWORKING "Include networking code (e.g. for downlading website icons)." OFF)
@@ -163,11 +163,15 @@ if("${CMAKE_SIZEOF_VOID_P}" EQUAL "4")
set(IS_32BIT TRUE)
endif()
-if("${CMAKE_C_COMPILER}" MATCHES "clang$" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang")
+if("${CMAKE_C_COMPILER}" MATCHES "clang$"
+ OR "${CMAKE_EXTRA_GENERATOR_C_SYSTEM_DEFINED_MACROS}" MATCHES "__clang__"
+ OR "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang")
set(CMAKE_COMPILER_IS_CLANG 1)
endif()
-if("${CMAKE_CXX_COMPILER}" MATCHES "clang(\\+\\+)?$" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
+if("${CMAKE_CXX_COMPILER}" MATCHES "clang(\\+\\+)?$"
+ OR "${CMAKE_EXTRA_GENERATOR_CXX_SYSTEM_DEFINED_MACROS}" MATCHES "__clang__"
+ OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
set(CMAKE_COMPILER_IS_CLANGXX 1)
endif()
@@ -264,6 +268,11 @@ endif()
add_gcc_compiler_cflags("-std=c99")
add_gcc_compiler_cxxflags("-std=c++11")
+if((CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.9.99) OR
+ (CMAKE_COMPILER_IS_CLANGXX AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 3.6.99))
+ add_gcc_compiler_cxxflags("-fsized-deallocation")
+endif()
+
if(APPLE)
add_gcc_compiler_cxxflags("-stdlib=libc++")
endif()
@@ -387,6 +396,7 @@ find_package(Gcrypt 1.7.0 REQUIRED)
find_package(Argon2 REQUIRED)
find_package(ZLIB REQUIRED)
find_package(QREncode REQUIRED)
+find_package(sodium 1.0.12 REQUIRED)
set(CMAKE_REQUIRED_INCLUDES ${ZLIB_INCLUDE_DIR})
@@ -394,7 +404,7 @@ if(ZLIB_VERSION_STRING VERSION_LESS "1.2.0")
message(FATAL_ERROR "zlib 1.2.0 or higher is required to use the gzip format")
endif()
-include_directories(SYSTEM ${ARGON2_INCLUDE_DIR})
+include_directories(SYSTEM ${ARGON2_INCLUDE_DIR} ${sodium_INCLUDE_DIR})
# Optional
if(WITH_XC_KEESHARE)
diff --git a/INSTALL.md b/INSTALL.md
index d3927536f..3bc9185d9 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -25,7 +25,7 @@ The following libraries are required:
* zlib
* libmicrohttpd
* libxi, libxtst, qtx11extras (optional for auto-type on X11)
-* libsodium (>= 1.0.12, optional for KeePassXC-Browser support)
+* libsodium (>= 1.0.12)
* libargon2
Prepare the Building Environment
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 9b009b698..16d0ef89c 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -27,6 +27,7 @@ if(NOT ZXCVBN_LIBRARIES)
endif(NOT ZXCVBN_LIBRARIES)
set(keepassx_SOURCES
+ core/Alloc.cpp
core/AutoTypeAssociations.cpp
core/AutoTypeMatch.cpp
core/Compare.cpp
@@ -254,6 +255,8 @@ endif()
if(WITH_XC_TOUCHID)
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")
endif()
add_library(autotype STATIC ${autotype_SOURCES})
@@ -270,6 +273,7 @@ target_link_libraries(keepassx_core
Qt5::Concurrent
Qt5::Network
Qt5::Widgets
+ ${sodium_LIBRARY_RELEASE}
${YUBIKEY_LIBRARIES}
${ZXCVBN_LIBRARIES}
${ARGON2_LIBRARIES}
diff --git a/src/browser/CMakeLists.txt b/src/browser/CMakeLists.txt
index 10189d931..7e813eb5b 100755
--- a/src/browser/CMakeLists.txt
+++ b/src/browser/CMakeLists.txt
@@ -16,7 +16,6 @@
if(WITH_XC_BROWSER)
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
- find_package(sodium 1.0.12 REQUIRED)
set(keepassxcbrowser_SOURCES
BrowserAccessControlDialog.cpp
@@ -33,5 +32,5 @@ if(WITH_XC_BROWSER)
Variant.cpp)
add_library(keepassxcbrowser STATIC ${keepassxcbrowser_SOURCES})
- target_link_libraries(keepassxcbrowser Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Network sodium)
+ target_link_libraries(keepassxcbrowser Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Network ${sodium_LIBRARY_RELEASE})
endif()
diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt
index c3f97a2cd..2f4a7275e 100644
--- a/src/cli/CMakeLists.txt
+++ b/src/cli/CMakeLists.txt
@@ -38,6 +38,7 @@ target_link_libraries(keepassxc-cli
keepassx_core
Qt5::Core
${GCRYPT_LIBRARIES}
+ ${sodium_LIBRARY_RELEASE}
${ARGON2_LIBRARIES}
${GPGERROR_LIBRARIES}
${ZLIB_LIBRARIES}
diff --git a/src/core/Alloc.cpp b/src/core/Alloc.cpp
new file mode 100644
index 000000000..a33b56196
--- /dev/null
+++ b/src/core/Alloc.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 or (at your option)
+ * version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QtGlobal>
+#include <cstdint>
+#include <sodium.h>
+#ifdef Q_OS_MACOS
+#include <malloc/malloc.h>
+#else
+#include <malloc.h>
+#endif
+
+#if defined(NDEBUG) && !defined(__cpp_sized_deallocation)
+#warning "KeePassXC is being compiled without sized deallocation support. Deletes may be slow."
+#endif
+
+/**
+ * Custom sized delete operator which securely zeroes out allocated
+ * memory before freeing it (requires C++14 sized deallocation support).
+ */
+void operator delete(void* ptr, std::size_t size) noexcept
+{
+ if (!ptr) {
+ return;
+ }
+
+ sodium_memzero(ptr, size);
+ std::free(ptr);
+}
+
+void operator delete[](void* ptr, std::size_t size) noexcept
+{
+ ::operator delete(ptr, size);
+}
+
+/**
+ * Custom delete operator which securely zeroes out
+ * allocated memory before freeing it.
+ */
+void operator delete(void* ptr) noexcept
+{
+ if (!ptr) {
+ return;
+ }
+
+#if defined(Q_OS_WIN)
+ ::operator delete(ptr, _msize(ptr));
+#elif defined(Q_OS_MACOS)
+ ::operator delete(ptr, malloc_size(ptr));
+#elif defined(Q_OS_UNIX)
+ ::operator delete(ptr, malloc_usable_size(ptr));
+#else
+ // whatever OS this is, give up and simply free stuff
+ std::free(ptr);
+#endif
+}
+
+void operator delete[](void* ptr) noexcept
+{
+ ::operator delete(ptr);
+}
+
+/**
+ * Custom insecure delete operator that does not zero out memory before
+ * freeing a buffer. Can be used for better performance.
+ */
+void operator delete(void* ptr, bool) noexcept
+{
+ std::free(ptr);
+}
+
+void operator delete[](void* ptr, bool) noexcept
+{
+ ::operator delete(ptr, false);
+}
diff --git a/src/keys/FileKey.cpp b/src/keys/FileKey.cpp
index 9d1e8f50f..da25ef4ae 100644
--- a/src/keys/FileKey.cpp
+++ b/src/keys/FileKey.cpp
@@ -18,19 +18,35 @@
#include "FileKey.h"
-#include <QFile>
-
#include "core/Tools.h"
#include "crypto/CryptoHash.h"
#include "crypto/Random.h"
+#include <QFile>
+
+#include <sodium.h>
+#include <gcrypt.h>
+#include <algorithm>
+#include <cstring>
+
QUuid FileKey::UUID("a584cbc4-c9b4-437e-81bb-362ca9709273");
+constexpr int FileKey::SHA256_SIZE;
+
FileKey::FileKey()
: Key(UUID)
+ , m_key(static_cast<char*>(gcry_malloc_secure(SHA256_SIZE)))
{
}
+FileKey::~FileKey()
+{
+ if (m_key) {
+ gcry_free(m_key);
+ m_key = nullptr;
+ }
+}
+
/**
* Read key file from device while trying to detect its file format.
*
@@ -148,7 +164,10 @@ bool FileKey::load(const QString& fileName, QString* errorMsg)
*/
QByteArray FileKey::rawKey() const
{
- return m_key;
+ if (!m_key) {
+ return {};
+ }
+ return QByteArray::fromRawData(m_key, SHA256_SIZE);
}
/**
@@ -223,12 +242,15 @@ bool FileKey::loadXml(QIODevice* device)
}
}
+ bool ok = false;
if (!xmlReader.error() && correctMeta && !data.isEmpty()) {
- m_key = data;
- return true;
+ std::memcpy(m_key, data.data(), std::min(SHA256_SIZE, data.size()));
+ ok = true;
}
- return false;
+ sodium_memzero(data.data(), static_cast<std::size_t>(data.capacity()));
+
+ return ok;
}
/**
@@ -293,7 +315,8 @@ bool FileKey::loadBinary(QIODevice* device)
if (!Tools::readAllFromDevice(device, data) || data.size() != 32) {
return false;
} else {
- m_key = data;
+ std::memcpy(m_key, data.data(), std::min(SHA256_SIZE, data.size()));
+ sodium_memzero(data.data(), static_cast<std::size_t>(data.capacity()));
return true;
}
}
@@ -321,12 +344,15 @@ bool FileKey::loadHex(QIODevice* device)
}
QByteArray key = QByteArray::fromHex(data);
+ sodium_memzero(data.data(), static_cast<std::size_t>(data.capacity()));
if (key.size() != 32) {
return false;
}
- m_key = key;
+ std::memcpy(m_key, key.data(), std::min(SHA256_SIZE, key.size()));
+ sodium_memzero(key.data(), static_cast<std::size_t>(key.capacity()));
+
return true;
}
@@ -348,7 +374,9 @@ bool FileKey::loadHashed(QIODevice* device)
cryptoHash.addData(buffer);
} while (!buffer.isEmpty());
- m_key = cryptoHash.result();
+ auto result = cryptoHash.result();
+ std::memcpy(m_key, result.data(), std::min(SHA256_SIZE, result.size()));
+ sodium_memzero(result.data(), static_cast<std::size_t>(result.capacity()));
return true;
}
diff --git a/src/keys/FileKey.h b/src/keys/FileKey.h
index d7486467b..290a04af0 100644
--- a/src/keys/FileKey.h
+++ b/src/keys/FileKey.h
@@ -40,6 +40,7 @@ public:
};
FileKey();
+ ~FileKey() override;
bool load(QIODevice* device);
bool load(const QString& fileName, QString* errorMsg = nullptr);
QByteArray rawKey() const override;
@@ -48,6 +49,8 @@ public:
static bool create(const QString& fileName, QString* errorMsg = nullptr, int size = 128);
private:
+ static constexpr int SHA256_SIZE = 32;
+
bool loadXml(QIODevice* device);
bool loadXmlMeta(QXmlStreamReader& xmlReader);
QByteArray loadXmlKey(QXmlStreamReader& xmlReader);
@@ -55,7 +58,7 @@ private:
bool loadHex(QIODevice* device);
bool loadHashed(QIODevice* device);
- QByteArray m_key;
+ char* m_key = nullptr;
Type m_type = None;
};
diff --git a/src/keys/PasswordKey.cpp b/src/keys/PasswordKey.cpp
index 35ecb9989..2d0416af8 100644
--- a/src/keys/PasswordKey.cpp
+++ b/src/keys/PasswordKey.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
+ * Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -16,35 +16,51 @@
*/
#include "PasswordKey.h"
+#include "core/Tools.h"
#include "crypto/CryptoHash.h"
+#include <gcrypt.h>
+#include <algorithm>
+#include <cstring>
QUuid PasswordKey::UUID("77e90411-303a-43f2-b773-853b05635ead");
+constexpr int PasswordKey::SHA256_SIZE;
+
PasswordKey::PasswordKey()
: Key(UUID)
+ , m_key(static_cast<char*>(gcry_malloc_secure(SHA256_SIZE)))
{
}
PasswordKey::PasswordKey(const QString& password)
: Key(UUID)
+ , m_key(static_cast<char*>(gcry_malloc_secure(SHA256_SIZE)))
{
setPassword(password);
}
+PasswordKey::~PasswordKey()
+{
+ if (m_key) {
+ gcry_free(m_key);
+ m_key = nullptr;
+ }
+}
+
QSharedPointer<PasswordKey> PasswordKey::fromRawKey(const QByteArray& rawKey)
{
auto result = QSharedPointer<PasswordKey>::create();
- result->m_key = rawKey;
+ std::memcpy(result->m_key, rawKey.data(), std::min(SHA256_SIZE, rawKey.size()));
return result;
}
QByteArray PasswordKey::rawKey() const
{
- return m_key;
+ return QByteArray::fromRawData(m_key, SHA256_SIZE);
}
void PasswordKey::setPassword(const QString& password)
{
- m_key = CryptoHash::hash(password.toUtf8(), CryptoHash::Sha256);
+ std::memcpy(m_key, CryptoHash::hash(password.toUtf8(), CryptoHash::Sha256).data(), SHA256_SIZE);
}
diff --git a/src/keys/PasswordKey.h b/src/keys/PasswordKey.h
index 68ab79895..4408cabcf 100644
--- a/src/keys/PasswordKey.h
+++ b/src/keys/PasswordKey.h
@@ -30,13 +30,16 @@ public:
PasswordKey();
explicit PasswordKey(const QString& password);
+ ~PasswordKey() override;
QByteArray rawKey() const override;
void setPassword(const QString& password);
static QSharedPointer<PasswordKey> fromRawKey(const QByteArray& rawKey);
private:
- QByteArray m_key;
+ static constexpr int SHA256_SIZE = 32;
+
+ char* m_key = nullptr;
};
#endif // KEEPASSX_PASSWORDKEY_H
diff --git a/src/keys/YkChallengeResponseKey.cpp b/src/keys/YkChallengeResponseKey.cpp
index f9cbe3174..759d8d1bc 100644
--- a/src/keys/YkChallengeResponseKey.cpp
+++ b/src/keys/YkChallengeResponseKey.cpp
@@ -1,6 +1,6 @@
/*
+ * Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2014 Kyle Manna <kyle@kylemanna.com>
- * Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -32,6 +32,10 @@
#include <QXmlStreamReader>
#include <QtConcurrent>
+#include <gcrypt.h>
+#include <sodium.h>
+#include <cstring>
+
QUuid YkChallengeResponseKey::UUID("e092495c-e77d-498b-84a1-05ae0d955508");
YkChallengeResponseKey::YkChallengeResponseKey(int slot, bool blocking)
@@ -45,9 +49,18 @@ YkChallengeResponseKey::YkChallengeResponseKey(int slot, bool blocking)
}
}
+YkChallengeResponseKey::~YkChallengeResponseKey()
+{
+ if (m_key) {
+ gcry_free(m_key);
+ m_keySize = 0;
+ m_key = nullptr;
+ }
+}
+
QByteArray YkChallengeResponseKey::rawKey() const
{
- return m_key;
+ return QByteArray::fromRawData(m_key, static_cast<int>(m_keySize));
}
/**
@@ -67,14 +80,22 @@ bool YkChallengeResponseKey::challenge(const QByteArray& challenge, unsigned int
emit userInteractionRequired();
}
+ QByteArray key;
auto result = AsyncTask::runAndWaitForFuture(
- [this, challenge]() { return YubiKey::instance()->challenge(m_slot, true, challenge, m_key); });
+ [this, challenge, &key]() { return YubiKey::instance()->challenge(m_slot, true, challenge, key); });
if (m_blocking) {
emit userConfirmed();
}
if (result == YubiKey::SUCCESS) {
+ if (m_key) {
+ gcry_free(m_key);
+ }
+ m_keySize = static_cast<std::size_t>(key.size());
+ m_key = static_cast<char*>(gcry_malloc_secure(m_keySize));
+ std::memcpy(m_key, key.data(), m_keySize);
+ sodium_memzero(key.data(), static_cast<std::size_t>(key.capacity()));
return true;
}
} while (retries > 0);
diff --git a/src/keys/YkChallengeResponseKey.h b/src/keys/YkChallengeResponseKey.h
index b8467e7a6..5f7c40e72 100644
--- a/src/keys/YkChallengeResponseKey.h
+++ b/src/keys/YkChallengeResponseKey.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
+ * Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -32,6 +32,7 @@ public:
static QUuid UUID;
explicit YkChallengeResponseKey(int slot = -1, bool blocking = false);
+ ~YkChallengeResponseKey() override;
QByteArray rawKey() const override;
bool challenge(const QByteArray& challenge) override;
@@ -52,7 +53,8 @@ signals:
void userConfirmed();
private:
- QByteArray m_key;
+ char* m_key = nullptr;
+ std::size_t m_keySize = 0;
int m_slot;
bool m_blocking;
};
diff --git a/src/proxy/CMakeLists.txt b/src/proxy/CMakeLists.txt
index ff645dadb..bdbfa3b74 100755
--- a/src/proxy/CMakeLists.txt
+++ b/src/proxy/CMakeLists.txt
@@ -18,12 +18,13 @@ if(WITH_XC_BROWSER)
include_directories(${BROWSER_SOURCE_DIR})
set(proxy_SOURCES
+ ../core/Alloc.cpp
keepassxc-proxy.cpp
${BROWSER_SOURCE_DIR}/NativeMessagingBase.cpp
NativeMessagingHost.cpp)
add_library(proxy STATIC ${proxy_SOURCES})
- target_link_libraries(proxy Qt5::Core Qt5::Network)
+ target_link_libraries(proxy Qt5::Core Qt5::Network ${sodium_LIBRARY_RELEASE})
add_executable(keepassxc-proxy keepassxc-proxy.cpp)
target_link_libraries(keepassxc-proxy proxy)
diff --git a/src/touchid/TouchID.mm b/src/touchid/TouchID.mm
index 9ef72189b..7df5ad556 100644
--- a/src/touchid/TouchID.mm
+++ b/src/touchid/TouchID.mm
@@ -15,6 +15,7 @@
inline void debug(const char* message, ...)
{
+ Q_UNUSED(message);
// qWarning(...);
}
@@ -258,6 +259,7 @@ bool TouchID::authenticate(const QString& message) const
NSString* authMessage = msg.toNSString(); // autoreleased
[context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
localizedReason:authMessage reply:^(BOOL success, NSError* error) {
+ Q_UNUSED(error);
result = success ? kTouchIDResultAllowed : kTouchIDResultFailed;
CFRunLoopWakeUp(CFRunLoopGetCurrent());
}];