Replace all crypto libraries with Botan

Selected the [Botan crypto library](https://github.com/randombit/botan) due to its feature list, maintainer support, availability across all deployment platforms, and ease of use. Also evaluated Crypto++ as a viable candidate, but the additional features of Botan (PKCS#11, TPM, etc) won out.

The random number generator received a backend upgrade. Botan prefers hardware-based RNG's and will provide one if available. This is transparent to KeePassXC and a significant improvement over gcrypt.

Replaced Argon2 library with built-in Botan implementation that supports i, d, and id. This requires Botan 2.11.0 or higher. Also simplified the parameter test across KDF's.

Aligned SymmetricCipher parameters with available modes. All encrypt and decrypt operations are done in-place instead of returning new objects. This allows use of secure vectors in the future with no additional overhead.

Took this opportunity to decouple KeeShare from SSH Agent. Removed leftover code from OpenSSHKey and consolidated the SSH Agent code into the same directory. Removed bcrypt and blowfish inserts since they are provided by Botan.

Additionally simplified KeeShare settings interface by removing raw certificate byte data from the user interface. KeeShare will be further refactored in a future PR.

NOTE: This PR breaks backwards compatibility with KeeShare certificates due to different RSA key storage with Botan. As a result, new "own" certificates will need to be generated and trust re-established.

Removed YKChallengeResponseKeyCLI in favor of just using the original implementation with signal/slots.

Removed TestRandom stub since it was just faking random numbers and not actually using the backend. TestRandomGenerator now uses the actual RNG.

Greatly simplified Secret Service plugin's use of crypto functions with Botan.
This commit is contained in:
Jonathan White 2021-04-04 08:56:00 -04:00
parent 86ddd702fb
commit 80809ace67
121 changed files with 1602 additions and 4532 deletions

View File

@ -86,12 +86,6 @@ if(WITH_XC_ALL)
endif() endif()
endif() endif()
if(WITH_XC_SSHAGENT OR WITH_XC_KEESHARE)
set(WITH_XC_CRYPTO_SSH ON)
else()
set(WITH_XC_CRYPTO_SSH OFF)
endif()
# Prefer WITH_XC_NETWORKING setting over WITH_XC_UPDATECHECK # Prefer WITH_XC_NETWORKING setting over WITH_XC_UPDATECHECK
if(NOT WITH_XC_NETWORKING AND WITH_XC_UPDATECHECK) if(NOT WITH_XC_NETWORKING AND WITH_XC_UPDATECHECK)
message(STATUS "Disabling WITH_XC_UPDATECHECK because WITH_XC_NETWORKING is disabled") message(STATUS "Disabling WITH_XC_UPDATECHECK because WITH_XC_NETWORKING is disabled")
@ -250,6 +244,7 @@ if(WITH_APP_BUNDLE)
endif() endif()
add_gcc_compiler_flags("-fno-common") add_gcc_compiler_flags("-fno-common")
check_add_gcc_compiler_flag("-fopenmp")
add_gcc_compiler_flags("-Wall -Wextra -Wundef -Wpointer-arith -Wno-long-long") add_gcc_compiler_flags("-Wall -Wextra -Wundef -Wpointer-arith -Wno-long-long")
add_gcc_compiler_flags("-Wformat=2 -Wmissing-format-attribute") add_gcc_compiler_flags("-Wformat=2 -Wmissing-format-attribute")
add_gcc_compiler_flags("-fvisibility=hidden") add_gcc_compiler_flags("-fvisibility=hidden")
@ -269,7 +264,6 @@ else()
endif() endif()
endif() endif()
add_gcc_compiler_cxxflags("-fno-exceptions -fno-rtti")
add_gcc_compiler_cxxflags("-Wnon-virtual-dtor -Wold-style-cast -Woverloaded-virtual") add_gcc_compiler_cxxflags("-Wnon-virtual-dtor -Wold-style-cast -Woverloaded-virtual")
add_gcc_compiler_cflags("-Wchar-subscripts -Wwrite-strings") add_gcc_compiler_cflags("-Wchar-subscripts -Wwrite-strings")
@ -324,7 +318,7 @@ if(APPLE AND CMAKE_COMPILER_IS_CLANGXX)
endif() endif()
if(WITH_DEV_BUILD) if(WITH_DEV_BUILD)
add_definitions(-DQT_DEPRECATED_WARNINGS -DGCRYPT_NO_DEPRECATED) add_definitions(-DQT_DEPRECATED_WARNINGS)
else() else()
add_definitions(-DQT_NO_DEPRECATED_WARNINGS) add_definitions(-DQT_NO_DEPRECATED_WARNINGS)
add_gcc_compiler_cxxflags("-Wno-deprecated-declarations") add_gcc_compiler_cxxflags("-Wno-deprecated-declarations")
@ -447,20 +441,22 @@ endif()
# Make sure we don't enable asserts there. # Make sure we don't enable asserts there.
set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS_NONE QT_NO_DEBUG) set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS_NONE QT_NO_DEBUG)
find_package(LibGPGError REQUIRED) # Find Botan2
find_package(Gcrypt 1.7.0 REQUIRED) find_package(Botan2 REQUIRED)
find_package(Argon2 REQUIRED) if(BOTAN2_VERSION VERSION_LESS "2.11.0")
message(FATAL_ERROR "Botan2 2.11.0 or higher is required")
endif()
include_directories(SYSTEM ${BOTAN2_INCLUDE_DIR})
# Find zlib
find_package(ZLIB REQUIRED) find_package(ZLIB REQUIRED)
find_package(QREncode REQUIRED)
find_package(sodium 1.0.12 REQUIRED)
set(CMAKE_REQUIRED_INCLUDES ${ZLIB_INCLUDE_DIR})
if(ZLIB_VERSION_STRING VERSION_LESS "1.2.0") 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") message(FATAL_ERROR "zlib 1.2.0 or higher is required to use the gzip format")
endif() endif()
include_directories(SYSTEM ${ZLIB_INCLUDE_DIR})
include_directories(SYSTEM ${ARGON2_INCLUDE_DIR} ${sodium_INCLUDE_DIR}) # QREncode required for TOTP
find_package(QREncode REQUIRED)
# Optional # Optional
if(WITH_XC_YUBIKEY) if(WITH_XC_YUBIKEY)
@ -499,7 +495,7 @@ if(UNIX)
endif() endif()
endif() endif()
include_directories(SYSTEM ${GCRYPT_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR}) include_directories(SYSTEM ${ZLIB_INCLUDE_DIR})
add_subdirectory(src) add_subdirectory(src)
add_subdirectory(share) add_subdirectory(share)

View File

@ -46,6 +46,10 @@ Files: cmake/FindYubiKey.cmake
Copyright: 2014 Kyle Manna <kyle@kylemanna.com> Copyright: 2014 Kyle Manna <kyle@kylemanna.com>
License: GPL-2 or GPL-3 License: GPL-2 or GPL-3
Files: cmake/FindBotan2.cmake
Copyright: 2018 Ribose Inc.
License: BSD-2-clause
Files: cmake/GenerateProductVersion.cmake Files: cmake/GenerateProductVersion.cmake
Copyright: 2015 halex2005 <akharlov@gmail.com> Copyright: 2015 halex2005 <akharlov@gmail.com>
License: MIT License: MIT

121
cmake/FindBotan2.cmake Normal file
View File

@ -0,0 +1,121 @@
# Copyright (c) 2018 Ribose Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#.rst:
# FindBotan2
# -----------
#
# Find the botan-2 library.
#
# IMPORTED Targets
# ^^^^^^^^^^^^^^^^
#
# This module defines :prop_tgt:`IMPORTED` targets:
#
# ``Botan2::Botan2``
# The botan-2 library, if found.
#
# Result variables
# ^^^^^^^^^^^^^^^^
#
# This module defines the following variables:
#
# ::
#
# BOTAN2_FOUND - true if the headers and library were found
# BOTAN2_INCLUDE_DIRS - where to find headers
# BOTAN2_LIBRARIES - list of libraries to link
# BOTAN2_VERSION - library version that was found, if any
# use pkg-config to get the directories and then use these values
# in the find_path() and find_library() calls
find_package(PkgConfig QUIET)
pkg_check_modules(PC_BOTAN2 QUIET botan-2)
# find the headers
find_path(BOTAN2_INCLUDE_DIR
NAMES botan/version.h
HINTS
${PC_BOTAN2_INCLUDEDIR}
${PC_BOTAN2_INCLUDE_DIRS}
PATH_SUFFIXES botan-2
)
# find the library
find_library(BOTAN2_LIBRARY
NAMES botan-2 libbotan-2
HINTS
${PC_BOTAN2_LIBDIR}
${PC_BOTAN2_LIBRARY_DIRS}
)
# determine the version
if(PC_BOTAN2_VERSION)
set(BOTAN2_VERSION ${PC_BOTAN2_VERSION})
elseif(BOTAN2_INCLUDE_DIR AND EXISTS "${BOTAN2_INCLUDE_DIR}/botan/build.h")
file(STRINGS "${BOTAN2_INCLUDE_DIR}/botan/build.h" botan2_version_str
REGEX "^#define[\t ]+(BOTAN_VERSION_[A-Z]+)[\t ]+[0-9]+")
string(REGEX REPLACE ".*#define[\t ]+BOTAN_VERSION_MAJOR[\t ]+([0-9]+).*"
"\\1" _botan2_version_major "${botan2_version_str}")
string(REGEX REPLACE ".*#define[\t ]+BOTAN_VERSION_MINOR[\t ]+([0-9]+).*"
"\\1" _botan2_version_minor "${botan2_version_str}")
string(REGEX REPLACE ".*#define[\t ]+BOTAN_VERSION_PATCH[\t ]+([0-9]+).*"
"\\1" _botan2_version_patch "${botan2_version_str}")
set(BOTAN2_VERSION "${_botan2_version_major}.${_botan2_version_minor}.${_botan2_version_patch}"
CACHE INTERNAL "The version of Botan which was detected")
endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Botan2
REQUIRED_VARS BOTAN2_LIBRARY BOTAN2_INCLUDE_DIR
VERSION_VAR BOTAN2_VERSION
)
if (BOTAN2_FOUND)
set(BOTAN2_INCLUDE_DIRS ${BOTAN2_INCLUDE_DIR} ${PC_BOTAN2_INCLUDE_DIRS})
set(BOTAN2_LIBRARIES ${BOTAN2_LIBRARY})
endif()
if (BOTAN2_FOUND AND NOT TARGET Botan2::Botan2)
# create the new library target
add_library(Botan2::Botan2 UNKNOWN IMPORTED)
# set the required include dirs for the target
if (BOTAN2_INCLUDE_DIRS)
set_target_properties(Botan2::Botan2
PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${BOTAN2_INCLUDE_DIRS}"
)
endif()
# set the required libraries for the target
if (EXISTS "${BOTAN2_LIBRARY}")
set_target_properties(Botan2::Botan2
PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
IMPORTED_LOCATION "${BOTAN2_LIBRARY}"
)
endif()
endif()
mark_as_advanced(BOTAN2_INCLUDE_DIR BOTAN2_LIBRARY)

View File

@ -7477,7 +7477,7 @@ Kernel: %3 %4</translation>
</message> </message>
<message> <message>
<source>Do you want to trust %1 with the fingerprint of %2 from %3?</source> <source>Do you want to trust %1 with the fingerprint of %2 from %3?</source>
<translation>Do you want to trust %1 with the fingerprint of %2 from %3? {1 ?} {2 ?}</translation> <translation>Do you want to trust %1 with the fingerprint of %2 from %3?</translation>
</message> </message>
<message> <message>
<source>Not this time</source> <source>Not this time</source>

View File

@ -67,7 +67,6 @@ set(keepassx_SOURCES
crypto/CryptoHash.cpp crypto/CryptoHash.cpp
crypto/Random.cpp crypto/Random.cpp
crypto/SymmetricCipher.cpp crypto/SymmetricCipher.cpp
crypto/SymmetricCipherGcrypt.cpp
crypto/kdf/Kdf.cpp crypto/kdf/Kdf.cpp
crypto/kdf/AesKdf.cpp crypto/kdf/AesKdf.cpp
crypto/kdf/Argon2Kdf.cpp crypto/kdf/Argon2Kdf.cpp
@ -182,7 +181,6 @@ set(keepassx_SOURCES
keys/FileKey.cpp keys/FileKey.cpp
keys/PasswordKey.cpp keys/PasswordKey.cpp
keys/YkChallengeResponseKey.cpp keys/YkChallengeResponseKey.cpp
keys/YkChallengeResponseKeyCLI.cpp
streams/HashedBlockStream.cpp streams/HashedBlockStream.cpp
streams/HmacBlockStream.cpp streams/HmacBlockStream.cpp
streams/LayeredStream.cpp streams/LayeredStream.cpp
@ -247,11 +245,6 @@ add_subdirectory(cli)
add_subdirectory(qrcode) add_subdirectory(qrcode)
set(qrcode_LIB qrcode) set(qrcode_LIB qrcode)
add_subdirectory(crypto/ssh)
if(WITH_XC_CRYPTO_SSH)
set(crypto_ssh_LIB crypto_ssh)
endif()
add_subdirectory(keeshare) add_subdirectory(keeshare)
if(WITH_XC_KEESHARE) if(WITH_XC_KEESHARE)
set(keeshare_LIB keeshare) set(keeshare_LIB keeshare)
@ -322,10 +315,9 @@ target_link_libraries(keepassx_core
Qt5::Concurrent Qt5::Concurrent
Qt5::Network Qt5::Network
Qt5::Widgets Qt5::Widgets
${sodium_LIBRARY_RELEASE} ${BOTAN2_LIBRARIES}
${YUBIKEY_LIBRARIES} ${YUBIKEY_LIBRARIES}
${ZXCVBN_LIBRARIES} ${ZXCVBN_LIBRARIES}
${ARGON2_LIBRARIES}
${ZLIB_LIBRARIES} ${ZLIB_LIBRARIES}
) )
@ -370,7 +362,6 @@ if(MINGW)
endif() endif()
add_executable(${PROGNAME} WIN32 ${keepassx_SOURCES_MAINEXE} ${WIN32_ProductVersionFiles}) add_executable(${PROGNAME} WIN32 ${keepassx_SOURCES_MAINEXE} ${WIN32_ProductVersionFiles})
target_link_libraries(keepassx_core ${GCRYPT_LIBRARIES} ${GPGERROR_LIBRARIES})
target_link_libraries(${PROGNAME} keepassx_core) target_link_libraries(${PROGNAME} keepassx_core)
set_target_properties(${PROGNAME} PROPERTIES ENABLE_EXPORTS ON) set_target_properties(${PROGNAME} PROPERTIES ENABLE_EXPORTS ON)
@ -497,7 +488,7 @@ if(MINGW)
find_file(CRYPTO_DLL NAMES libcrypto-1_1.dll libcrypto-1_1-x64.dll) find_file(CRYPTO_DLL NAMES libcrypto-1_1.dll libcrypto-1_1-x64.dll)
if (NOT CRYPTO_DLL) if (NOT CRYPTO_DLL)
message(FATAL_ERROR "Cannot find libcrypto dll, ensure libgcrypt is properly installed.") message(FATAL_ERROR "Cannot find libcrypto dll, ensure openssl is properly installed.")
endif() endif()
install(FILES ${OPENSSL_DLL} ${CRYPTO_DLL} DESTINATION ".") install(FILES ${OPENSSL_DLL} ${CRYPTO_DLL} DESTINATION ".")

View File

@ -24,9 +24,9 @@
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonParseError> #include <QJsonParseError>
#include <sodium.h> #include <botan/sodium.h>
#include <sodium/crypto_box.h>
#include <sodium/randombytes.h> using namespace Botan::Sodium;
namespace namespace
{ {
@ -283,7 +283,7 @@ QJsonObject BrowserAction::handleGetLogins(const QJsonObject& json, const QStrin
const QString id = decrypted.value("id").toString(); const QString id = decrypted.value("id").toString();
const QString formUrl = decrypted.value("submitUrl").toString(); const QString formUrl = decrypted.value("submitUrl").toString();
const QString auth = decrypted.value("httpAuth").toString(); const QString auth = decrypted.value("httpAuth").toString();
const bool httpAuth = auth.compare(TRUE_STR, Qt::CaseSensitive) == 0 ? true : false; const bool httpAuth = auth.compare(TRUE_STR, Qt::CaseSensitive) == 0;
const QJsonArray users = browserService()->findMatchingEntries(id, siteUrl, formUrl, "", keyList, httpAuth); const QJsonArray users = browserService()->findMatchingEntries(id, siteUrl, formUrl, "", keyList, httpAuth);
if (users.isEmpty()) { if (users.isEmpty()) {

View File

@ -25,7 +25,6 @@
#include <QMutexLocker> #include <QMutexLocker>
#include <QtNetwork> #include <QtNetwork>
#include "sodium.h"
#include <iostream> #include <iostream>
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
@ -53,11 +52,6 @@ BrowserHost::~BrowserHost()
void BrowserHost::start() void BrowserHost::start()
{ {
if (sodium_init() == -1) {
qWarning() << "Failed to start browser service: libsodium failed to initialize!";
return;
}
if (!m_localServer->isListening()) { if (!m_localServer->isListening()) {
m_localServer->listen(BrowserShared::localServerPath()); m_localServer->listen(BrowserShared::localServerPath());
} }

View File

@ -32,5 +32,5 @@ if(WITH_XC_BROWSER)
Variant.cpp) Variant.cpp)
add_library(keepassxcbrowser STATIC ${keepassxcbrowser_SOURCES}) add_library(keepassxcbrowser STATIC ${keepassxcbrowser_SOURCES})
target_link_libraries(keepassxcbrowser Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Network ${sodium_LIBRARY_RELEASE}) target_link_libraries(keepassxcbrowser Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Network ${BOTAN2_LIBRARIES})
endif() endif()

View File

@ -54,13 +54,7 @@ add_executable(keepassxc-cli keepassxc-cli.cpp)
target_link_libraries(keepassxc-cli target_link_libraries(keepassxc-cli
${GPGERROR_LIBRARIES} ${GPGERROR_LIBRARIES}
cli cli
keepassx_core keepassx_core)
Qt5::Core
${GCRYPT_LIBRARIES}
${sodium_LIBRARY_RELEASE}
${ARGON2_LIBRARIES}
${ZLIB_LIBRARIES}
${ZXCVBN_LIBRARIES})
install(TARGETS keepassxc-cli install(TARGETS keepassxc-cli
BUNDLE DESTINATION . COMPONENT Runtime BUNDLE DESTINATION . COMPONENT Runtime

View File

@ -18,7 +18,7 @@
#include "Utils.h" #include "Utils.h"
#ifdef WITH_XC_YUBIKEY #ifdef WITH_XC_YUBIKEY
#include "keys/YkChallengeResponseKeyCLI.h" #include "keys/YkChallengeResponseKey.h"
#endif #endif
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
@ -165,9 +165,14 @@ namespace Utils
} }
} }
auto key = QSharedPointer<YkChallengeResponseKeyCLI>(new YkChallengeResponseKeyCLI( auto conn = QObject::connect(YubiKey::instance(), &YubiKey::userInteractionRequest, [&] {
{serial, slot}, QObject::tr("Please touch the button on your YubiKey to continue…"), err)); err << QObject::tr("Please touch the button on your YubiKey to continue…") << "\n\n" << flush;
});
auto key = QSharedPointer<YkChallengeResponseKey>(new YkChallengeResponseKey({serial, slot}));
compositeKey->addChallengeResponseKey(key); compositeKey->addChallengeResponseKey(key);
QObject::disconnect(conn);
} }
#else #else
Q_UNUSED(yubiKeySlot); Q_UNUSED(yubiKeySlot);

View File

@ -16,9 +16,9 @@
*/ */
#include <QtGlobal> #include <QtGlobal>
#include <botan/mem_ops.h>
#include <cstdint> #include <cstdint>
#include <cstdlib> #include <cstdlib>
#include <sodium.h>
#if defined(Q_OS_MACOS) #if defined(Q_OS_MACOS)
#include <malloc/malloc.h> #include <malloc/malloc.h>
#elif defined(Q_OS_FREEBSD) #elif defined(Q_OS_FREEBSD)
@ -43,7 +43,7 @@ void operator delete(void* ptr, std::size_t size) noexcept
return; return;
} }
sodium_memzero(ptr, size); Botan::secure_scrub_memory(ptr, size);
std::free(ptr); std::free(ptr);
} }

View File

@ -352,30 +352,4 @@ namespace Tools
return subbed; return subbed;
} }
Buffer::Buffer()
: raw(nullptr)
, size(0)
{
}
Buffer::~Buffer()
{
clear();
}
void Buffer::clear()
{
if (size > 0) {
free(raw);
}
raw = nullptr;
size = 0;
}
QByteArray Buffer::content() const
{
return QByteArray(reinterpret_cast<char*>(raw), size);
}
} // namespace Tools } // namespace Tools

View File

@ -64,34 +64,6 @@ namespace Tools
} }
} }
template <typename Key, typename Value, void deleter(Value)> struct Map
{
QMap<Key, Value> values;
Value& operator[](const Key index)
{
return values[index];
}
~Map()
{
for (Value m : values) {
deleter(m);
}
}
};
struct Buffer
{
unsigned char* raw;
size_t size;
Buffer();
~Buffer();
void clear();
QByteArray content() const;
};
inline int qtRuntimeVersion() inline int qtRuntimeVersion()
{ {
// Cache the result since the Qt version can't change during // Cache the result since the Qt version can't change during

View File

@ -1,4 +1,5 @@
/* /*
* Copyright (C) 2021 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de> * Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
@ -17,328 +18,249 @@
#include "Crypto.h" #include "Crypto.h"
#include <QMutex>
#include <gcrypt.h>
#include "config-keepassx.h"
#include "crypto/CryptoHash.h" #include "crypto/CryptoHash.h"
#include "crypto/SymmetricCipher.h" #include "crypto/SymmetricCipher.h"
bool Crypto::m_initialized(false); #include <botan/version.h>
QString Crypto::m_errorStr;
QString Crypto::m_backendVersion;
Crypto::Crypto() namespace
{ {
} QString g_cryptoError;
bool Crypto::init() bool testSha256()
{ {
if (m_initialized) { if (CryptoHash::hash("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", CryptoHash::Sha256)
qWarning("Crypto::init: already initialized"); != QByteArray::fromHex("248D6A61D20638B8E5C026930C3E6039A33CE45964FF2167F6ECEDD419DB06C1")) {
return true; g_cryptoError = "SHA-256 mismatch.";
}
m_backendVersion = QString::fromLocal8Bit(gcry_check_version(0));
gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
if (!checkAlgorithms()) {
return false;
}
// has to be set before testing Crypto classes
m_initialized = true;
if (!backendSelfTest() || !selfTest()) {
m_initialized = false;
return false; return false;
} }
return true; return true;
} }
bool Crypto::initialized() bool testSha512()
{ {
return m_initialized; if (CryptoHash::hash("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", CryptoHash::Sha512)
} != QByteArray::fromHex("204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c335"
"96fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445")) {
QString Crypto::errorString() g_cryptoError = "SHA-512 mismatch.";
{
return m_errorStr;
}
QString Crypto::debugInfo()
{
Q_ASSERT(Crypto::initialized());
QString debugInfo = QObject::tr("Cryptographic libraries:").append("\n");
debugInfo.append("- libgcrypt ").append(m_backendVersion).append("\n");
return debugInfo;
}
bool Crypto::backendSelfTest()
{
return (gcry_control(GCRYCTL_SELFTEST) == 0);
}
bool Crypto::checkAlgorithms()
{
if (gcry_cipher_algo_info(GCRY_CIPHER_AES256, GCRYCTL_TEST_ALGO, nullptr, nullptr) != 0) {
m_errorStr = "GCRY_CIPHER_AES256 not found.";
qWarning("Crypto::checkAlgorithms: %s", qPrintable(m_errorStr));
return false;
}
if (gcry_cipher_algo_info(GCRY_CIPHER_TWOFISH, GCRYCTL_TEST_ALGO, nullptr, nullptr) != 0) {
m_errorStr = "GCRY_CIPHER_TWOFISH not found.";
qWarning("Crypto::checkAlgorithms: %s", qPrintable(m_errorStr));
return false;
}
if (gcry_cipher_algo_info(GCRY_CIPHER_SALSA20, GCRYCTL_TEST_ALGO, nullptr, nullptr) != 0) {
m_errorStr = "GCRY_CIPHER_SALSA20 not found.";
qWarning("Crypto::checkAlgorithms: %s", qPrintable(m_errorStr));
return false;
}
if (gcry_cipher_algo_info(GCRY_CIPHER_CHACHA20, GCRYCTL_TEST_ALGO, nullptr, nullptr) != 0) {
m_errorStr = "GCRY_CIPHER_CHACHA20 not found.";
qWarning("Crypto::checkAlgorithms: %s", qPrintable(m_errorStr));
return false;
}
if (gcry_md_test_algo(GCRY_MD_SHA256) != 0) {
m_errorStr = "GCRY_MD_SHA256 not found.";
qWarning("Crypto::checkAlgorithms: %s", qPrintable(m_errorStr));
return false;
}
if (gcry_md_test_algo(GCRY_MD_SHA512) != 0) {
m_errorStr = "GCRY_MD_SHA512 not found.";
qWarning("Crypto::checkAlgorithms: %s", qPrintable(m_errorStr));
return false; return false;
} }
return true; return true;
} }
bool Crypto::selfTest() bool testAes256Cbc()
{ {
return testSha256() && testSha512() && testAes256Cbc() && testAes256Ecb() && testTwofish() && testSalsa20() QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d7781"
&& testChaCha20(); "1f352c073b6108d72d9810a30914dff4");
}
void Crypto::raiseError(const QString& str)
{
m_errorStr = str;
qWarning("Crypto::selfTest: %s", qPrintable(m_errorStr));
}
bool Crypto::testSha256()
{
QByteArray sha256Test =
CryptoHash::hash("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", CryptoHash::Sha256);
if (sha256Test != QByteArray::fromHex("248D6A61D20638B8E5C026930C3E6039A33CE45964FF2167F6ECEDD419DB06C1")) {
raiseError("SHA-256 mismatch.");
return false;
}
return true;
}
bool Crypto::testSha512()
{
QByteArray sha512Test =
CryptoHash::hash("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", CryptoHash::Sha512);
if (sha512Test
!= QByteArray::fromHex("204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b"
"07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445")) {
raiseError("SHA-512 mismatch.");
return false;
}
return true;
}
bool Crypto::testAes256Cbc()
{
QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4");
QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f"); QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f");
QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a"); QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a"
plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51")); "ae2d8a571e03ac9c9eb76fac45af8e51");
QByteArray cipherText = QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd6"); QByteArray cipherText = QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd6"
cipherText.append(QByteArray::fromHex("9cfc4e967edb808d679f777bc6702c7d")); "9cfc4e967edb808d679f777bc6702c7d");
bool ok;
SymmetricCipher aes256Encrypt(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Encrypt); QByteArray data = plainText;
if (!aes256Encrypt.init(key, iv)) { SymmetricCipher aes256;
raiseError(aes256Encrypt.errorString()); if (!aes256.init(SymmetricCipher::Aes256_CBC, SymmetricCipher::Encrypt, key, iv)) {
g_cryptoError = aes256.errorString();
return false; return false;
} }
QByteArray encryptedText = aes256Encrypt.process(plainText, &ok); if (!aes256.process(data)) {
if (!ok) { g_cryptoError = aes256.errorString();
raiseError(aes256Encrypt.errorString());
return false; return false;
} }
if (encryptedText != cipherText) { if (data != cipherText) {
raiseError("AES-256 CBC encryption mismatch."); g_cryptoError = "AES-256 CBC encryption mismatch.";
return false; return false;
} }
SymmetricCipher aes256Decrypt(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt); if (!aes256.init(SymmetricCipher::Aes256_CBC, SymmetricCipher::Decrypt, key, iv)) {
if (!aes256Decrypt.init(key, iv)) { g_cryptoError = aes256.errorString();
raiseError(aes256Decrypt.errorString());
return false; return false;
} }
QByteArray decryptedText = aes256Decrypt.process(cipherText, &ok); if (!aes256.process(data)) {
if (!ok) { g_cryptoError = aes256.errorString();
raiseError(aes256Decrypt.errorString());
return false; return false;
} }
if (decryptedText != plainText) { if (data != plainText) {
raiseError("AES-256 CBC decryption mismatch."); g_cryptoError = "AES-256 CBC decryption mismatch.";
return false; return false;
} }
return true; return true;
} }
bool Crypto::testAes256Ecb() bool testAesKdf()
{ {
QByteArray key = QByteArray::fromHex("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F"); QByteArray key = QByteArray::fromHex("000102030405060708090A0B0C0D0E0F"
QByteArray iv = QByteArray::fromHex("00000000000000000000000000000000"); "101112131415161718191A1B1C1D1E1F");
QByteArray plainText = QByteArray::fromHex("00112233445566778899AABBCCDDEEFF"); QByteArray plainText = QByteArray::fromHex("00112233445566778899AABBCCDDEEFF");
plainText.append(QByteArray::fromHex("00112233445566778899AABBCCDDEEFF"));
QByteArray cipherText = QByteArray::fromHex("8EA2B7CA516745BFEAFC49904B496089"); QByteArray cipherText = QByteArray::fromHex("8EA2B7CA516745BFEAFC49904B496089");
cipherText.append(QByteArray::fromHex("8EA2B7CA516745BFEAFC49904B496089"));
bool ok;
SymmetricCipher aes256Encrypt(SymmetricCipher::Aes256, SymmetricCipher::Ecb, SymmetricCipher::Encrypt); if (!SymmetricCipher::aesKdf(key, 1, plainText)) {
if (!aes256Encrypt.init(key, iv)) { g_cryptoError = "AES KDF Failed.";
raiseError(aes256Encrypt.errorString());
return false;
}
QByteArray encryptedText = aes256Encrypt.process(plainText, &ok);
if (!ok) {
raiseError(aes256Encrypt.errorString());
return false;
}
if (encryptedText != cipherText) {
raiseError("AES-256 ECB encryption mismatch.");
return false;
} }
SymmetricCipher aes256Decrypt(SymmetricCipher::Aes256, SymmetricCipher::Ecb, SymmetricCipher::Decrypt); if (plainText != cipherText) {
if (!aes256Decrypt.init(key, iv)) { g_cryptoError = "AES KDF encryption mismatch.";
raiseError(aes256Decrypt.errorString());
return false;
}
QByteArray decryptedText = aes256Decrypt.process(cipherText, &ok);
if (!ok) {
raiseError(aes256Decrypt.errorString());
return false;
}
if (decryptedText != plainText) {
raiseError("AES-256 ECB decryption mismatch.");
return false; return false;
} }
return true; return true;
} }
bool Crypto::testTwofish() bool testTwofish()
{ {
QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"); QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d7781"
"1f352c073b6108d72d9810a30914dff4");
QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f"); QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f");
QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a"); QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a"
plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51")); "ae2d8a571e03ac9c9eb76fac45af8e51");
QByteArray cipherText = QByteArray::fromHex("e0227c3cc80f3cb1b2ed847cc6f57d3c"); QByteArray cipherText = QByteArray::fromHex("e0227c3cc80f3cb1b2ed847cc6f57d3c"
cipherText.append(QByteArray::fromHex("657b1e7960b30fb7c8d62e72ae37c3a0")); "657b1e7960b30fb7c8d62e72ae37c3a0");
bool ok; QByteArray data = plainText;
SymmetricCipher twofish;
SymmetricCipher twofishEncrypt(SymmetricCipher::Twofish, SymmetricCipher::Cbc, SymmetricCipher::Encrypt); if (!twofish.init(SymmetricCipher::Twofish_CBC, SymmetricCipher::Encrypt, key, iv)) {
if (!twofishEncrypt.init(key, iv)) { g_cryptoError = twofish.errorString();
raiseError(twofishEncrypt.errorString());
return false; return false;
} }
QByteArray encryptedText = twofishEncrypt.process(plainText, &ok); if (!twofish.process(data)) {
if (!ok) { g_cryptoError = twofish.errorString();
raiseError(twofishEncrypt.errorString());
return false; return false;
} }
if (encryptedText != cipherText) { if (data != cipherText) {
raiseError("Twofish encryption mismatch."); g_cryptoError = "Twofish encryption mismatch.";
return false; return false;
} }
SymmetricCipher twofishDecrypt(SymmetricCipher::Twofish, SymmetricCipher::Cbc, SymmetricCipher::Decrypt); if (!twofish.init(SymmetricCipher::Twofish_CBC, SymmetricCipher::Decrypt, key, iv)) {
if (!twofishDecrypt.init(key, iv)) { g_cryptoError = twofish.errorString();
raiseError(twofishEncrypt.errorString());
return false; return false;
} }
QByteArray decryptedText = twofishDecrypt.process(cipherText, &ok); if (!twofish.process(data)) {
if (!ok) { g_cryptoError = twofish.errorString();
raiseError(twofishDecrypt.errorString());
return false; return false;
} }
if (decryptedText != plainText) { if (data != plainText) {
raiseError("Twofish encryption mismatch."); g_cryptoError = "Twofish encryption mismatch.";
return false; return false;
} }
return true; return true;
} }
bool Crypto::testSalsa20() bool testSalsa20()
{ {
QByteArray salsa20Key = QByteArray::fromHex("F3F4F5F6F7F8F9FAFBFCFDFEFF000102030405060708090A0B0C0D0E0F101112"); QByteArray salsa20Key = QByteArray::fromHex("F3F4F5F6F7F8F9FAFBFCFDFEFF000102"
"030405060708090A0B0C0D0E0F101112");
QByteArray salsa20iv = QByteArray::fromHex("0000000000000000"); QByteArray salsa20iv = QByteArray::fromHex("0000000000000000");
QByteArray salsa20Plain = QByteArray::fromHex("00000000000000000000000000000000"); QByteArray salsa20Plain = QByteArray::fromHex("00000000000000000000000000000000");
QByteArray salsa20Cipher = QByteArray::fromHex("B4C0AFA503BE7FC29A62058166D56F8F"); QByteArray salsa20Cipher = QByteArray::fromHex("B4C0AFA503BE7FC29A62058166D56F8F");
bool ok;
SymmetricCipher salsa20Stream(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt); QByteArray data = salsa20Plain;
if (!salsa20Stream.init(salsa20Key, salsa20iv)) { SymmetricCipher salsa20Stream;
raiseError(salsa20Stream.errorString()); if (!salsa20Stream.init(SymmetricCipher::Salsa20, SymmetricCipher::Encrypt, salsa20Key, salsa20iv)) {
g_cryptoError = salsa20Stream.errorString();
return false;
}
if (!salsa20Stream.process(data)) {
g_cryptoError = salsa20Stream.errorString();
return false;
}
if (data != salsa20Cipher) {
g_cryptoError = "Salsa20 stream cipher encrypt mismatch.";
return false; return false;
} }
QByteArray salsaProcessed = salsa20Stream.process(salsa20Plain, &ok); if (!salsa20Stream.init(SymmetricCipher::Salsa20, SymmetricCipher::Decrypt, salsa20Key, salsa20iv)) {
if (!ok) { g_cryptoError = salsa20Stream.errorString();
raiseError(salsa20Stream.errorString());
return false; return false;
} }
if (salsaProcessed != salsa20Cipher) { if (!salsa20Stream.process(data)) {
raiseError("Salsa20 stream cipher mismatch."); g_cryptoError = salsa20Stream.errorString();
return false;
}
if (data != salsa20Plain) {
g_cryptoError = "Salsa20 stream cipher decrypt mismatch.";
return false; return false;
} }
return true; return true;
} }
bool Crypto::testChaCha20() bool testChaCha20()
{ {
QByteArray chacha20Key = QByteArray::fromHex("0000000000000000000000000000000000000000000000000000000000000000"); QByteArray chacha20Key = QByteArray::fromHex("00000000000000000000000000000000"
"00000000000000000000000000000000");
QByteArray chacha20iv = QByteArray::fromHex("0000000000000000"); QByteArray chacha20iv = QByteArray::fromHex("0000000000000000");
QByteArray chacha20Plain = QByteArray::fromHex("0000000000000000000000000000000000000000000000000000000000000000000" QByteArray chacha20Plain =
"0000000000000000000000000000000000000000000000000000000000000"); QByteArray::fromHex("0000000000000000000000000000000000000000000000000000000000000000"
QByteArray chacha20Cipher = QByteArray::fromHex("76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da" "0000000000000000000000000000000000000000000000000000000000000000");
"41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586"); QByteArray chacha20Cipher =
bool ok; QByteArray::fromHex("76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7"
"da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586");
SymmetricCipher chacha20Stream(SymmetricCipher::ChaCha20, SymmetricCipher::Stream, SymmetricCipher::Encrypt); QByteArray data = chacha20Plain;
if (!chacha20Stream.init(chacha20Key, chacha20iv)) { SymmetricCipher chacha20Stream;
raiseError(chacha20Stream.errorString()); if (!chacha20Stream.init(SymmetricCipher::ChaCha20, SymmetricCipher::Encrypt, chacha20Key, chacha20iv)) {
g_cryptoError = chacha20Stream.errorString();
return false;
}
if (!chacha20Stream.process(data)) {
g_cryptoError = chacha20Stream.errorString();
return false;
}
if (data != chacha20Cipher) {
g_cryptoError = "ChaCha20 stream cipher encrypt mismatch.";
return false; return false;
} }
QByteArray chacha20Processed = chacha20Stream.process(chacha20Plain, &ok); if (!chacha20Stream.init(SymmetricCipher::ChaCha20, SymmetricCipher::Decrypt, chacha20Key, chacha20iv)) {
if (!ok) { g_cryptoError = chacha20Stream.errorString();
raiseError(chacha20Stream.errorString());
return false; return false;
} }
if (chacha20Processed != chacha20Cipher) { if (!chacha20Stream.process(data)) {
raiseError("ChaCha20 stream cipher mismatch."); g_cryptoError = chacha20Stream.errorString();
return false;
}
if (data != chacha20Plain) {
g_cryptoError = "ChaCha20 stream cipher decrypt mismatch.";
return false; return false;
} }
return true; return true;
} }
} // namespace
namespace Crypto
{
bool init()
{
if (Botan::version_major() != 2 || Botan::version_minor() < 11) {
g_cryptoError = QObject::tr("Botan library must be at least 2.11.x, found %1.%2.%3")
.arg(Botan::version_major())
.arg(Botan::version_minor())
.arg(Botan::version_patch());
return false;
}
return testSha256() && testSha512() && testAes256Cbc() && testAesKdf() && testTwofish() && testSalsa20()
&& testChaCha20();
}
QString errorString()
{
return g_cryptoError;
}
QString debugInfo()
{
QString debugInfo = QObject::tr("Cryptographic libraries:").append("\n");
debugInfo.append(QString("- Botan %1.%2.%3\n")
.arg(Botan::version_major())
.arg(Botan::version_minor())
.arg(Botan::version_patch()));
return debugInfo;
}
} // namespace Crypto

View File

@ -1,4 +1,5 @@
/* /*
* Copyright (C) 2021 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de> * Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
@ -15,36 +16,16 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef KEEPASSX_CRYPTO_H #ifndef KEEPASSXC_CRYPTO_H
#define KEEPASSX_CRYPTO_H #define KEEPASSXC_CRYPTO_H
#include <QString> #include <QString>
class Crypto namespace Crypto
{ {
public: bool init();
static bool init(); QString errorString();
static bool initialized(); QString debugInfo();
static bool backendSelfTest(); }; // namespace Crypto
static QString errorString();
static QString debugInfo();
private: #endif // KEEPASSXC_CRYPTO_H
Crypto();
static bool checkAlgorithms();
static bool selfTest();
static void raiseError(const QString& str);
static bool testSha256();
static bool testSha512();
static bool testAes256Cbc();
static bool testAes256Ecb();
static bool testTwofish();
static bool testSalsa20();
static bool testChaCha20();
static bool m_initialized;
static QString m_errorStr;
static QString m_backendVersion;
};
#endif // KEEPASSX_CRYPTO_H

View File

@ -1,4 +1,5 @@
/* /*
* Copyright (C) 2021 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de> * Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
@ -17,15 +18,17 @@
#include "CryptoHash.h" #include "CryptoHash.h"
#include <gcrypt.h>
#include "crypto/Crypto.h" #include "crypto/Crypto.h"
#include <QScopedPointer>
#include <botan/hash.h>
#include <botan/mac.h>
class CryptoHashPrivate class CryptoHashPrivate
{ {
public: public:
gcry_md_hd_t ctx; QScopedPointer<Botan::HashFunction> hashFunction;
int hashLen; QScopedPointer<Botan::MessageAuthenticationCode> hmacFunction;
}; };
CryptoHash::CryptoHash(Algorithm algo, bool hmac) CryptoHash::CryptoHash(Algorithm algo, bool hmac)
@ -33,45 +36,31 @@ CryptoHash::CryptoHash(Algorithm algo, bool hmac)
{ {
Q_D(CryptoHash); Q_D(CryptoHash);
Q_ASSERT(Crypto::initialized());
int algoGcrypt = -1;
unsigned int flagsGcrypt = GCRY_MD_FLAG_SECURE;
switch (algo) { switch (algo) {
case CryptoHash::Sha256: case CryptoHash::Sha256:
algoGcrypt = GCRY_MD_SHA256; if (hmac) {
d->hmacFunction.reset(Botan::MessageAuthenticationCode::create("HMAC(SHA-256)").release());
} else {
d->hashFunction.reset(Botan::HashFunction::create("SHA-256").release());
}
break; break;
case CryptoHash::Sha512: case CryptoHash::Sha512:
algoGcrypt = GCRY_MD_SHA512; if (hmac) {
d->hmacFunction.reset(Botan::MessageAuthenticationCode::create("HMAC(SHA-512)").release());
} else {
d->hashFunction.reset(Botan::HashFunction::create("SHA-512").release());
}
break; break;
default: default:
Q_ASSERT(false); Q_ASSERT(false);
break; break;
} }
if (hmac) {
flagsGcrypt |= GCRY_MD_FLAG_HMAC;
}
gcry_error_t error = gcry_md_open(&d->ctx, algoGcrypt, flagsGcrypt);
if (error != GPG_ERR_NO_ERROR) {
qWarning("Gcrypt error (ctor): %s\n %s", gcry_strerror(error), gcry_strsource(error));
}
Q_ASSERT(error == 0); // TODO: error handling
d->hashLen = gcry_md_get_algo_dlen(algoGcrypt);
} }
CryptoHash::~CryptoHash() CryptoHash::~CryptoHash()
{ {
Q_D(CryptoHash); Q_D(CryptoHash);
delete d;
gcry_md_close(d->ctx);
delete d_ptr;
} }
void CryptoHash::addData(const QByteArray& data) void CryptoHash::addData(const QByteArray& data)
@ -82,31 +71,47 @@ void CryptoHash::addData(const QByteArray& data)
return; return;
} }
gcry_md_write(d->ctx, data.constData(), static_cast<size_t>(data.size())); try {
if (d->hmacFunction) {
d->hmacFunction->update(reinterpret_cast<const uint8_t*>(data.data()), data.size());
} else if (d->hashFunction) {
d->hashFunction->update(reinterpret_cast<const uint8_t*>(data.data()), data.size());
}
} catch (std::exception& e) {
qWarning("CryptoHash::update failed to add data: %s", e.what());
Q_ASSERT(false);
}
} }
void CryptoHash::setKey(const QByteArray& data) void CryptoHash::setKey(const QByteArray& data)
{ {
Q_D(CryptoHash); Q_D(CryptoHash);
gcry_error_t error = gcry_md_setkey(d->ctx, data.constData(), static_cast<size_t>(data.size())); if (d->hmacFunction) {
if (error) { try {
qWarning("Gcrypt error (setKey): %s\n %s", gcry_strerror(error), gcry_strsource(error)); d->hmacFunction->set_key(reinterpret_cast<const uint8_t*>(data.data()), data.size());
} catch (std::exception& e) {
qWarning("CryptoHash::setKey failed to set HMAC key: %s", e.what());
Q_ASSERT(false);
}
} }
Q_ASSERT(error == 0);
} }
QByteArray CryptoHash::result() const QByteArray CryptoHash::result() const
{ {
Q_D(const CryptoHash); Q_D(const CryptoHash);
const auto result = reinterpret_cast<const char*>(gcry_md_read(d->ctx, 0)); Botan::secure_vector<uint8_t> result;
return QByteArray(result, d->hashLen); if (d->hmacFunction) {
result = d->hmacFunction->final();
} else if (d->hashFunction) {
result = d->hashFunction->final();
}
return QByteArray(reinterpret_cast<const char*>(result.data()), result.size());
} }
QByteArray CryptoHash::hash(const QByteArray& data, Algorithm algo) QByteArray CryptoHash::hash(const QByteArray& data, Algorithm algo)
{ {
// replace with gcry_md_hash_buffer()?
CryptoHash cryptoHash(algo); CryptoHash cryptoHash(algo);
cryptoHash.addData(data); cryptoHash.addData(data);
return cryptoHash.result(); return cryptoHash.result();
@ -114,7 +119,6 @@ QByteArray CryptoHash::hash(const QByteArray& data, Algorithm algo)
QByteArray CryptoHash::hmac(const QByteArray& data, const QByteArray& key, Algorithm algo) QByteArray CryptoHash::hmac(const QByteArray& data, const QByteArray& key, Algorithm algo)
{ {
// replace with gcry_md_hash_buffer()?
CryptoHash cryptoHash(algo, true); CryptoHash cryptoHash(algo, true);
cryptoHash.setKey(key); cryptoHash.setKey(key);
cryptoHash.addData(data); cryptoHash.addData(data);

View File

@ -1,4 +1,5 @@
/* /*
* Copyright (C) 2021 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de> * Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify

View File

@ -17,46 +17,64 @@
#include "Random.h" #include "Random.h"
#include <gcrypt.h>
#include "core/Global.h" #include "core/Global.h"
#include "crypto/Crypto.h" #include "crypto/Crypto.h"
class RandomBackendGcrypt : public RandomBackend #include <QPointer>
{ #include <QSharedPointer>
public:
void randomize(void* data, int len) override; #include <botan/auto_rng.h>
}; #include <botan/system_rng.h>
QSharedPointer<Random> Random::m_instance; QSharedPointer<Random> Random::m_instance;
QSharedPointer<Random> Random::instance()
{
if (!m_instance) {
m_instance.reset(new Random());
}
return m_instance;
}
Random::Random()
{
#ifdef BOTAN_HAS_SYSTEM_RNG
m_rng.reset(new Botan::System_RNG);
#else
m_rng.reset(new Botan::Autoseeded_RNG);
#endif
}
QSharedPointer<Botan::RandomNumberGenerator> Random::getRng()
{
return m_rng;
}
void Random::randomize(QByteArray& ba) void Random::randomize(QByteArray& ba)
{ {
m_backend->randomize(ba.data(), ba.size()); m_rng->randomize(reinterpret_cast<uint8_t*>(ba.data()), ba.size());
} }
QByteArray Random::randomArray(int len) QByteArray Random::randomArray(int len)
{ {
QByteArray ba; QByteArray ba(len, '\0');
ba.resize(len);
randomize(ba); randomize(ba);
return ba; return ba;
} }
quint32 Random::randomUInt(quint32 limit) quint32 Random::randomUInt(quint32 limit)
{ {
Q_ASSERT(limit != 0);
Q_ASSERT(limit <= QUINT32_MAX); Q_ASSERT(limit <= QUINT32_MAX);
if (limit == 0) {
return 0;
}
quint32 rand; quint32 rand;
const quint32 ceil = QUINT32_MAX - (QUINT32_MAX % limit) - 1; const quint32 ceil = QUINT32_MAX - (QUINT32_MAX % limit) - 1;
// To avoid modulo bias: // To avoid modulo bias make sure rand is below the largest number where rand%limit==0
// Make sure rand is below the largest number where rand%limit==0
do { do {
m_backend->randomize(&rand, 4); m_rng->randomize(reinterpret_cast<uint8_t*>(&rand), 4);
} while (rand > ceil); } while (rand > ceil);
return (rand % limit); return (rand % limit);
@ -66,38 +84,3 @@ quint32 Random::randomUIntRange(quint32 min, quint32 max)
{ {
return min + randomUInt(max - min); return min + randomUInt(max - min);
} }
Random* Random::instance()
{
if (!m_instance) {
m_instance.reset(new Random(new RandomBackendGcrypt()));
}
return m_instance.data();
}
void Random::resetInstance()
{
m_instance.reset();
}
void Random::setInstance(RandomBackend* backend)
{
m_instance.reset(new Random(backend));
}
Random::Random(RandomBackend* backend)
: m_backend(backend)
{
}
void RandomBackendGcrypt::randomize(void* data, int len)
{
Q_ASSERT(Crypto::initialized());
gcry_randomize(data, len, GCRY_STRONG_RANDOM);
}
RandomBackend::~RandomBackend()
{
}

View File

@ -22,16 +22,13 @@
#include <QScopedPointer> #include <QScopedPointer>
#include <QSharedPointer> #include <QSharedPointer>
class RandomBackend #include <botan/rng.h>
{
public:
virtual void randomize(void* data, int len) = 0;
virtual ~RandomBackend();
};
class Random class Random
{ {
public: public:
static QSharedPointer<Random> instance();
void randomize(QByteArray& ba); void randomize(QByteArray& ba);
QByteArray randomArray(int len); QByteArray randomArray(int len);
@ -45,23 +42,17 @@ public:
*/ */
quint32 randomUIntRange(quint32 min, quint32 max); quint32 randomUIntRange(quint32 min, quint32 max);
static Random* instance(); QSharedPointer<Botan::RandomNumberGenerator> getRng();
protected:
static void resetInstance();
static void setInstance(RandomBackend* backend);
private: private:
explicit Random();
Q_DISABLE_COPY(Random);
static QSharedPointer<Random> m_instance; static QSharedPointer<Random> m_instance;
QSharedPointer<Botan::RandomNumberGenerator> m_rng;
explicit Random(RandomBackend* backend);
QScopedPointer<RandomBackend> m_backend;
Q_DISABLE_COPY(Random)
}; };
inline Random* randomGen() static inline QSharedPointer<Random> randomGen()
{ {
return Random::instance(); return Random::instance();
} }

View File

@ -16,143 +16,247 @@
*/ */
#include "SymmetricCipher.h" #include "SymmetricCipher.h"
#include <QtSvg>
#include "config-keepassx.h" #include "config-keepassx.h"
#include "crypto/SymmetricCipherGcrypt.h"
SymmetricCipher::SymmetricCipher(Algorithm algo, Mode mode, Direction direction) #include <botan/block_cipher.h>
: m_backend(createBackend(algo, mode, direction)) #include <botan/cipher_mode.h>
, m_initialized(false)
, m_algo(algo)
{
}
SymmetricCipher::~SymmetricCipher() bool SymmetricCipher::init(Mode mode, Direction direction, const QByteArray& key, const QByteArray& iv)
{ {
} m_mode = mode;
if (mode == InvalidMode) {
bool SymmetricCipher::init(const QByteArray& key, const QByteArray& iv) m_error = QObject::tr("SymmetricCipher::init: Invalid cipher mode.");
{
if (!m_backend->init()) {
return false; return false;
} }
if (!m_backend->setKey(key)) { try {
auto botanMode = modeToString(mode);
auto botanDirection = (direction == SymmetricCipher::Encrypt ? Botan::ENCRYPTION : Botan::DECRYPTION);
auto cipher = Botan::Cipher_Mode::create_or_throw(botanMode.toStdString(), botanDirection);
m_cipher.reset(cipher.release());
m_cipher->set_key(reinterpret_cast<const uint8_t*>(key.data()), key.size());
if (!m_cipher->valid_nonce_length(iv.size())) {
m_mode = InvalidMode;
m_cipher.reset();
m_error = QObject::tr("SymmetricCipher::init: Invalid IV size of %1 for %2.").arg(iv.size()).arg(botanMode);
return false;
}
m_cipher->start(reinterpret_cast<const uint8_t*>(iv.data()), iv.size());
} catch (std::exception& e) {
m_mode = InvalidMode;
m_cipher.reset();
m_error = e.what();
reset();
return false; return false;
} }
if (!m_backend->setIv(iv)) {
return false;
}
m_initialized = true;
return true; return true;
} }
bool SymmetricCipher::isInitalized() const bool SymmetricCipher::isInitalized() const
{ {
return m_initialized; return m_cipher;
} }
SymmetricCipherBackend* SymmetricCipher::createBackend(Algorithm algo, Mode mode, Direction direction) bool SymmetricCipher::process(char* data, int len)
{ {
switch (algo) { Q_ASSERT(isInitalized());
case Aes128: if (!isInitalized()) {
case Aes256: m_error = QObject::tr("Cipher not initialized prior to use.");
case Twofish: return false;
case Salsa20: }
case ChaCha20: if (len == 0) {
return new SymmetricCipherGcrypt(algo, mode, direction); m_error = QObject::tr("Cannot process 0 length data.");
return false;
}
default: try {
Q_ASSERT(false); // Block size is checked by Botan, an exception is thrown if invalid
return nullptr; m_cipher->process(reinterpret_cast<uint8_t*>(data), len);
return true;
} catch (std::exception& e) {
m_error = e.what();
return false;
} }
} }
bool SymmetricCipher::reset() bool SymmetricCipher::process(QByteArray& data)
{ {
return m_backend->reset(); return process(data.data(), data.size());
} }
int SymmetricCipher::keySize() const bool SymmetricCipher::finish(QByteArray& data)
{ {
return m_backend->keySize(); Q_ASSERT(isInitalized());
if (!isInitalized()) {
m_error = QObject::tr("Cipher not initialized prior to use.");
return false;
} }
int SymmetricCipher::blockSize() const try {
// Error checking is done by Botan, an exception is thrown if invalid
Botan::secure_vector<uint8_t> input(data.begin(), data.end());
m_cipher->finish(input);
// Post-finished data may be larger than before due to padding
data.resize(input.size());
// Direct copy the finished data back into the QByteArray
std::copy(input.begin(), input.end(), data.begin());
return true;
} catch (std::exception& e) {
m_error = e.what();
return false;
}
}
void SymmetricCipher::reset()
{ {
return m_backend->blockSize(); m_error.clear();
if (isInitalized()) {
m_cipher.reset();
}
}
SymmetricCipher::Mode SymmetricCipher::mode()
{
return m_mode;
}
bool SymmetricCipher::aesKdf(const QByteArray& key, int rounds, QByteArray& data)
{
try {
std::unique_ptr<Botan::BlockCipher> cipher(Botan::BlockCipher::create("AES-256"));
cipher->set_key(reinterpret_cast<const uint8_t*>(key.data()), key.size());
Botan::secure_vector<uint8_t> out(data.begin(), data.end());
for (int i = 0; i < rounds; ++i) {
cipher->encrypt(out);
}
std::copy(out.begin(), out.end(), data.begin());
return true;
} catch (std::exception& e) {
qWarning("SymmetricCipher::aesKdf: Could not process: %s", e.what());
return false;
}
} }
QString SymmetricCipher::errorString() const QString SymmetricCipher::errorString() const
{ {
return m_backend->error(); return m_error;
} }
SymmetricCipher::Algorithm SymmetricCipher::cipherToAlgorithm(const QUuid& cipher) SymmetricCipher::Mode SymmetricCipher::cipherUuidToMode(const QUuid& uuid)
{ {
if (cipher == KeePass2::CIPHER_AES256) { if (uuid == KeePass2::CIPHER_AES128) {
return Aes256; return Aes128_CBC;
} else if (cipher == KeePass2::CIPHER_CHACHA20) { } else if (uuid == KeePass2::CIPHER_AES256) {
return Aes256_CBC;
} else if (uuid == KeePass2::CIPHER_CHACHA20) {
return ChaCha20; return ChaCha20;
} else if (cipher == KeePass2::CIPHER_TWOFISH) { } else if (uuid == KeePass2::CIPHER_TWOFISH) {
return Twofish; return Twofish_CBC;
} }
qWarning("SymmetricCipher::cipherToAlgorithm: invalid UUID %s", cipher.toString().toLatin1().data()); qWarning("SymmetricCipher: Invalid KeePass2 Cipher UUID %s", uuid.toString().toLatin1().data());
return InvalidAlgorithm; return InvalidMode;
} }
QUuid SymmetricCipher::algorithmToCipher(Algorithm algo) SymmetricCipher::Mode SymmetricCipher::stringToMode(const QString& cipher)
{ {
switch (algo) { auto cs = Qt::CaseInsensitive;
case Aes128: if (cipher.compare("aes-128-cbc", cs) == 0 || cipher.compare("aes128-cbc", cs) == 0) {
return KeePass2::CIPHER_AES128; return Aes128_CBC;
case Aes256: } else if (cipher.compare("aes-256-cbc", cs) == 0 || cipher.compare("aes256-cbc", cs) == 0) {
return KeePass2::CIPHER_AES256; return Aes256_CBC;
case ChaCha20: } else if (cipher.compare("aes-128-ctr", cs) == 0 || cipher.compare("aes128-ctr", cs) == 0) {
return KeePass2::CIPHER_CHACHA20; return Aes128_CTR;
case Twofish: } else if (cipher.compare("aes-256-ctr", cs) == 0 || cipher.compare("aes256-ctr", cs) == 0) {
return KeePass2::CIPHER_TWOFISH; return Aes256_CTR;
default: } else if (cipher.startsWith("twofish", cs)) {
qWarning("SymmetricCipher::algorithmToCipher: invalid algorithm %d", algo); return Twofish_CBC;
return {}; } else if (cipher.startsWith("salsa", cs)) {
} return Salsa20;
} } else if (cipher.startsWith("chacha", cs)) {
return ChaCha20;
int SymmetricCipher::algorithmIvSize(Algorithm algo) } else {
{
switch (algo) {
case ChaCha20:
return 12;
case Aes128:
return 16;
case Aes256:
return 16;
case Twofish:
return 16;
default:
qWarning("SymmetricCipher::algorithmIvSize: invalid algorithm %d", algo);
return -1;
}
}
SymmetricCipher::Mode SymmetricCipher::algorithmMode(Algorithm algo)
{
switch (algo) {
case ChaCha20:
return Stream;
case Aes256:
case Twofish:
return Cbc;
default:
qWarning("SymmetricCipher::algorithmMode: invalid algorithm %d", algo);
return InvalidMode; return InvalidMode;
} }
} }
SymmetricCipher::Algorithm SymmetricCipher::algorithm() const QString SymmetricCipher::modeToString(const Mode mode)
{ {
return m_algo; switch (mode) {
case Aes128_CBC:
return QStringLiteral("AES-128/CBC");
case Aes256_CBC:
return QStringLiteral("AES-256/CBC");
case Aes128_CTR:
return QStringLiteral("CTR(AES-128)");
case Aes256_CTR:
return QStringLiteral("CTR(AES-256)");
case Twofish_CBC:
return QStringLiteral("Twofish/CBC");
case Salsa20:
return QStringLiteral("Salsa20");
case ChaCha20:
return QStringLiteral("ChaCha20");
default:
Q_ASSERT_X(false, "SymmetricCipher::modeToString", "Invalid Mode Specified");
return {};
}
}
int SymmetricCipher::defaultIvSize(Mode mode)
{
switch (mode) {
case Aes128_CBC:
case Aes256_CBC:
case Aes128_CTR:
case Aes256_CTR:
case Twofish_CBC:
return 16;
case Salsa20:
case ChaCha20:
return 12;
default:
return -1;
}
}
int SymmetricCipher::keySize(Mode mode)
{
switch (mode) {
case Aes128_CBC:
case Aes128_CTR:
return 16;
case Aes256_CBC:
case Aes256_CTR:
case Twofish_CBC:
case Salsa20:
case ChaCha20:
return 32;
default:
return 0;
}
}
int SymmetricCipher::blockSize(Mode mode)
{
switch (mode) {
case Aes128_CBC:
case Aes256_CBC:
case Twofish_CBC:
return 16;
case Aes128_CTR:
case Aes256_CTR:
case Salsa20:
case ChaCha20:
return 1;
default:
return 0;
}
} }

View File

@ -23,29 +23,26 @@
#include <QString> #include <QString>
#include <QUuid> #include <QUuid>
#include "crypto/SymmetricCipherBackend.h"
#include "format/KeePass2.h" #include "format/KeePass2.h"
namespace Botan
{
class Cipher_Mode;
}
class SymmetricCipher class SymmetricCipher
{ {
public: public:
enum Algorithm
{
Aes128,
Aes256,
Twofish,
Salsa20,
ChaCha20,
InvalidAlgorithm = -1
};
enum Mode enum Mode
{ {
Cbc, Aes128_CBC,
Ctr, Aes256_CBC,
Ecb, Aes128_CTR,
Stream, Aes256_CTR,
InvalidMode = -1 Twofish_CBC,
ChaCha20,
Salsa20,
InvalidMode = -1,
}; };
enum Direction enum Direction
@ -54,46 +51,36 @@ public:
Encrypt Encrypt
}; };
SymmetricCipher(Algorithm algo, Mode mode, Direction direction); explicit SymmetricCipher() = default;
~SymmetricCipher(); ~SymmetricCipher() = default;
Q_DISABLE_COPY(SymmetricCipher)
bool init(const QByteArray& key, const QByteArray& iv);
bool isInitalized() const; bool isInitalized() const;
Q_REQUIRED_RESULT bool init(Mode mode, Direction direction, const QByteArray& key, const QByteArray& iv);
Q_REQUIRED_RESULT bool process(char* data, int len);
Q_REQUIRED_RESULT bool process(QByteArray& data);
Q_REQUIRED_RESULT bool finish(QByteArray& data);
inline QByteArray process(const QByteArray& data, bool* ok) static bool aesKdf(const QByteArray& key, int rounds, QByteArray& data);
{
return m_backend->process(data, ok);
}
Q_REQUIRED_RESULT inline bool processInPlace(QByteArray& data) void reset();
{ Mode mode();
return m_backend->processInPlace(data);
}
Q_REQUIRED_RESULT inline bool processInPlace(QByteArray& data, quint64 rounds)
{
Q_ASSERT(rounds > 0);
return m_backend->processInPlace(data, rounds);
}
bool reset();
int keySize() const;
int blockSize() const;
QString errorString() const; QString errorString() const;
Algorithm algorithm() const;
static Algorithm cipherToAlgorithm(const QUuid& cipher); static Mode cipherUuidToMode(const QUuid& uuid);
static QUuid algorithmToCipher(Algorithm algo); static Mode stringToMode(const QString& cipher);
static int algorithmIvSize(Algorithm algo); static int defaultIvSize(Mode mode);
static Mode algorithmMode(Algorithm algo); static int keySize(Mode mode);
static int blockSize(Mode mode);
private: private:
static SymmetricCipherBackend* createBackend(Algorithm algo, Mode mode, Direction direction); static QString modeToString(const Mode mode);
const QScopedPointer<SymmetricCipherBackend> m_backend; QString m_error;
bool m_initialized; Mode m_mode;
Algorithm m_algo; QSharedPointer<Botan::Cipher_Mode> m_cipher;
Q_DISABLE_COPY(SymmetricCipher)
}; };
#endif // KEEPASSX_SYMMETRICCIPHER_H #endif // KEEPASSX_SYMMETRICCIPHER_H

View File

@ -1,44 +0,0 @@
/*
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
*
* 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/>.
*/
#ifndef KEEPASSX_SYMMETRICCIPHERBACKEND_H
#define KEEPASSX_SYMMETRICCIPHERBACKEND_H
#include <QByteArray>
class SymmetricCipherBackend
{
public:
virtual ~SymmetricCipherBackend()
{
}
virtual bool init() = 0;
virtual bool setKey(const QByteArray& key) = 0;
virtual bool setIv(const QByteArray& iv) = 0;
virtual QByteArray process(const QByteArray& data, bool* ok) = 0;
Q_REQUIRED_RESULT virtual bool processInPlace(QByteArray& data) = 0;
Q_REQUIRED_RESULT virtual bool processInPlace(QByteArray& data, quint64 rounds) = 0;
virtual bool reset() = 0;
virtual int keySize() const = 0;
virtual int blockSize() const = 0;
virtual QString error() const = 0;
};
#endif // KEEPASSX_SYMMETRICCIPHERBACKEND_H

View File

@ -1,261 +0,0 @@
/*
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
*
* 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 "SymmetricCipherGcrypt.h"
#include "config-keepassx.h"
#include "crypto/Crypto.h"
SymmetricCipherGcrypt::SymmetricCipherGcrypt(SymmetricCipher::Algorithm algo,
SymmetricCipher::Mode mode,
SymmetricCipher::Direction direction)
: m_ctx(nullptr)
, m_algo(gcryptAlgo(algo))
, m_mode(gcryptMode(mode))
, m_direction(direction)
{
}
SymmetricCipherGcrypt::~SymmetricCipherGcrypt()
{
gcry_cipher_close(m_ctx);
}
int SymmetricCipherGcrypt::gcryptAlgo(SymmetricCipher::Algorithm algo)
{
switch (algo) {
case SymmetricCipher::Aes128:
return GCRY_CIPHER_AES128;
case SymmetricCipher::Aes256:
return GCRY_CIPHER_AES256;
case SymmetricCipher::Twofish:
return GCRY_CIPHER_TWOFISH;
case SymmetricCipher::Salsa20:
return GCRY_CIPHER_SALSA20;
case SymmetricCipher::ChaCha20:
return GCRY_CIPHER_CHACHA20;
default:
Q_ASSERT(false);
return -1;
}
}
int SymmetricCipherGcrypt::gcryptMode(SymmetricCipher::Mode mode)
{
switch (mode) {
case SymmetricCipher::Ecb:
return GCRY_CIPHER_MODE_ECB;
case SymmetricCipher::Cbc:
return GCRY_CIPHER_MODE_CBC;
case SymmetricCipher::Ctr:
return GCRY_CIPHER_MODE_CTR;
case SymmetricCipher::Stream:
return GCRY_CIPHER_MODE_STREAM;
default:
Q_ASSERT(false);
return -1;
}
}
void SymmetricCipherGcrypt::setError(const gcry_error_t& err)
{
const char* gcryptError = gcry_strerror(err);
const char* gcryptErrorSource = gcry_strsource(err);
m_error = QString("%1/%2").arg(QString::fromLocal8Bit(gcryptErrorSource), QString::fromLocal8Bit(gcryptError));
}
bool SymmetricCipherGcrypt::init()
{
Q_ASSERT(Crypto::initialized());
gcry_error_t error;
if (m_ctx != nullptr)
gcry_cipher_close(m_ctx);
error = gcry_cipher_open(&m_ctx, m_algo, m_mode, 0);
if (error != 0) {
setError(error);
return false;
}
return true;
}
bool SymmetricCipherGcrypt::setKey(const QByteArray& key)
{
m_key = key;
gcry_error_t error = gcry_cipher_setkey(m_ctx, m_key.constData(), m_key.size());
if (error != 0) {
setError(error);
return false;
}
return true;
}
bool SymmetricCipherGcrypt::setIv(const QByteArray& iv)
{
m_iv = iv;
gcry_error_t error;
if (m_mode == GCRY_CIPHER_MODE_CTR) {
error = gcry_cipher_setctr(m_ctx, m_iv.constData(), m_iv.size());
} else {
error = gcry_cipher_setiv(m_ctx, m_iv.constData(), m_iv.size());
}
if (error != 0) {
setError(error);
return false;
}
return true;
}
QByteArray SymmetricCipherGcrypt::process(const QByteArray& data, bool* ok)
{
// TODO: check block size
QByteArray result;
result.resize(data.size());
gcry_error_t error;
if (m_direction == SymmetricCipher::Decrypt) {
error = gcry_cipher_decrypt(m_ctx, result.data(), data.size(), data.constData(), data.size());
} else {
error = gcry_cipher_encrypt(m_ctx, result.data(), data.size(), data.constData(), data.size());
}
if (error != 0) {
setError(error);
*ok = false;
} else {
*ok = true;
}
return result;
}
bool SymmetricCipherGcrypt::processInPlace(QByteArray& data)
{
// TODO: check block size
gcry_error_t error;
if (m_direction == SymmetricCipher::Decrypt) {
error = gcry_cipher_decrypt(m_ctx, data.data(), data.size(), nullptr, 0);
} else {
error = gcry_cipher_encrypt(m_ctx, data.data(), data.size(), nullptr, 0);
}
if (error != 0) {
setError(error);
return false;
}
return true;
}
bool SymmetricCipherGcrypt::processInPlace(QByteArray& data, quint64 rounds)
{
gcry_error_t error;
char* rawData = data.data();
int size = data.size();
if (m_direction == SymmetricCipher::Decrypt) {
for (quint64 i = 0; i != rounds; ++i) {
error = gcry_cipher_decrypt(m_ctx, rawData, size, nullptr, 0);
if (error != 0) {
setError(error);
return false;
}
}
} else {
for (quint64 i = 0; i != rounds; ++i) {
error = gcry_cipher_encrypt(m_ctx, rawData, size, nullptr, 0);
if (error != 0) {
setError(error);
return false;
}
}
}
return true;
}
bool SymmetricCipherGcrypt::reset()
{
gcry_error_t error;
error = gcry_cipher_reset(m_ctx);
if (error != 0) {
setError(error);
return false;
}
error = gcry_cipher_setiv(m_ctx, m_iv.constData(), m_iv.size());
if (error != 0) {
setError(error);
return false;
}
return true;
}
int SymmetricCipherGcrypt::keySize() const
{
gcry_error_t error;
size_t keySizeT;
error = gcry_cipher_algo_info(m_algo, GCRYCTL_GET_KEYLEN, nullptr, &keySizeT);
if (error != 0)
return -1;
return keySizeT;
}
int SymmetricCipherGcrypt::blockSize() const
{
gcry_error_t error;
size_t blockSizeT;
error = gcry_cipher_algo_info(m_algo, GCRYCTL_GET_BLKLEN, nullptr, &blockSizeT);
if (error != 0)
return -1;
return blockSizeT;
}
QString SymmetricCipherGcrypt::error() const
{
return m_error;
}

View File

@ -1,62 +0,0 @@
/*
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
*
* 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/>.
*/
#ifndef KEEPASSX_SYMMETRICCIPHERGCRYPT_H
#define KEEPASSX_SYMMETRICCIPHERGCRYPT_H
#include <gcrypt.h>
#include "crypto/SymmetricCipher.h"
#include "crypto/SymmetricCipherBackend.h"
class SymmetricCipherGcrypt : public SymmetricCipherBackend
{
public:
SymmetricCipherGcrypt(SymmetricCipher::Algorithm algo,
SymmetricCipher::Mode mode,
SymmetricCipher::Direction direction);
~SymmetricCipherGcrypt();
bool init();
bool setKey(const QByteArray& key);
bool setIv(const QByteArray& iv);
QByteArray process(const QByteArray& data, bool* ok);
Q_REQUIRED_RESULT bool processInPlace(QByteArray& data);
Q_REQUIRED_RESULT bool processInPlace(QByteArray& data, quint64 rounds);
bool reset();
int keySize() const;
int blockSize() const;
QString error() const;
private:
static int gcryptAlgo(SymmetricCipher::Algorithm algo);
static int gcryptMode(SymmetricCipher::Mode mode);
void setError(const gcry_error_t& err);
gcry_cipher_hd_t m_ctx;
const int m_algo;
const int m_mode;
const SymmetricCipher::Direction m_direction;
QByteArray m_key;
QByteArray m_iv;
QString m_error;
};
#endif // KEEPASSX_SYMMETRICCIPHERGCRYPT_H

View File

@ -1,34 +0,0 @@
/*
* Copyright (C) 2018 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/>.
*/
#ifndef KEEPASSXC_CRYPTO_ARGON2_H
#define KEEPASSXC_CRYPTO_ARGON2_H
/*
Argon2 wrapper header with redefined symbols to be used with the
patched libargon2 binary which is generated by the build system.
This is to avoid link-time definition clashes with libsodium on Windows.
*/
#ifdef Q_OS_WIN
#define argon2_hash libargon2_argon2_hash
#define argon2_error_message libargon2_argon2_error_message
#endif
#include <argon2.h>
#endif // KEEPASSXC_CRYPTO_ARGON2_H

View File

@ -20,6 +20,7 @@
#include <QtConcurrent> #include <QtConcurrent>
#include "crypto/CryptoHash.h" #include "crypto/CryptoHash.h"
#include "crypto/SymmetricCipher.h"
#include "format/KeePass2.h" #include "format/KeePass2.h"
AesKdf::AesKdf() AesKdf::AesKdf()
@ -83,22 +84,8 @@ bool AesKdf::transform(const QByteArray& raw, QByteArray& result) const
bool AesKdf::transformKeyRaw(const QByteArray& key, const QByteArray& seed, int rounds, QByteArray* result) bool AesKdf::transformKeyRaw(const QByteArray& key, const QByteArray& seed, int rounds, QByteArray* result)
{ {
QByteArray iv(16, 0);
SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Ecb, SymmetricCipher::Encrypt);
if (!cipher.init(seed, iv)) {
qWarning("AesKdf::transformKeyRaw: error in SymmetricCipher::init: %s", cipher.errorString().toUtf8().data());
return false;
}
*result = key; *result = key;
return SymmetricCipher::aesKdf(seed, rounds, *result);
if (!cipher.processInPlace(*result, rounds)) {
qWarning("AesKdf::transformKeyRaw: error in SymmetricCipher::processInPlace: %s",
cipher.errorString().toUtf8().data());
return false;
}
return true;
} }
QSharedPointer<Kdf> AesKdf::clone() const QSharedPointer<Kdf> AesKdf::clone() const
@ -106,24 +93,24 @@ QSharedPointer<Kdf> AesKdf::clone() const
return QSharedPointer<AesKdf>::create(*this); return QSharedPointer<AesKdf>::create(*this);
} }
int AesKdf::benchmarkImpl(int msec) const int AesKdf::benchmark(int msec) const
{ {
QByteArray key = QByteArray(16, '\x7E'); QByteArray key(16, '\x7E');
QByteArray seed = QByteArray(32, '\x4B'); QByteArray seed(32, '\x4B');
QByteArray iv(16, 0);
SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Ecb, SymmetricCipher::Encrypt); int trials = 3;
cipher.init(seed, iv); int rounds = 1000000;
quint64 rounds = 1000000;
QElapsedTimer timer; QElapsedTimer timer;
timer.start(); timer.start();
for (int i = 0; i < trials; ++i) {
if (!cipher.processInPlace(key, rounds)) { QByteArray result;
return -1; if (!transformKeyRaw(key, seed, rounds, &result)) {
return rounds;
}
} }
return static_cast<int>(rounds * (static_cast<float>(msec) / timer.elapsed())); return static_cast<int>(rounds * trials * static_cast<float>(msec) / timer.elapsed());
} }
QString AesKdf::toString() const QString AesKdf::toString() const

View File

@ -32,8 +32,7 @@ public:
QSharedPointer<Kdf> clone() const override; QSharedPointer<Kdf> clone() const override;
QString toString() const override; QString toString() const override;
protected: int benchmark(int msec) const override;
int benchmarkImpl(int msec) const override;
private: private:
Q_REQUIRED_RESULT static bool Q_REQUIRED_RESULT static bool

View File

@ -18,8 +18,8 @@
#include "Argon2Kdf.h" #include "Argon2Kdf.h"
#include <QtConcurrent> #include <QtConcurrent>
#include <botan/pwdhash.h>
#include "crypto/argon2/argon2.h"
#include "format/KeePass2.h" #include "format/KeePass2.h"
/** /**
@ -163,39 +163,20 @@ bool Argon2Kdf::transform(const QByteArray& raw, QByteArray& result) const
{ {
result.clear(); result.clear();
result.resize(32); result.resize(32);
return transformKeyRaw(raw, seed(), version(), type(), rounds(), memory(), parallelism(), result); try {
} auto algo = type() == Type::Argon2d ? "Argon2d" : "Argon2id";
auto pwhash = Botan::PasswordHashFamily::create_or_throw(algo)->from_params(memory(), rounds(), parallelism());
bool Argon2Kdf::transformKeyRaw(const QByteArray& key, pwhash->derive_key(reinterpret_cast<uint8_t*>(result.data()),
const QByteArray& seed,
quint32 version,
Type type,
quint32 rounds,
quint64 memory,
quint32 parallelism,
QByteArray& result)
{
// Time Cost, Mem Cost, Threads/Lanes, Password, length, Salt, length, out, length
int rc = argon2_hash(rounds,
memory,
parallelism,
key.data(),
key.size(),
seed.data(),
seed.size(),
result.data(),
result.size(), result.size(),
nullptr, raw.constData(),
0, raw.size(),
type == Type::Argon2d ? Argon2_d : Argon2_id, reinterpret_cast<const uint8_t*>(seed().constData()),
version); seed().size());
if (rc != ARGON2_OK) { return true;
qWarning("Argon2 error: %s", argon2_error_message(rc)); } catch (std::exception& e) {
qWarning("Argon2 error: %s", e.what());
return false; return false;
} }
return true;
} }
QSharedPointer<Kdf> Argon2Kdf::clone() const QSharedPointer<Kdf> Argon2Kdf::clone() const
@ -203,21 +184,17 @@ QSharedPointer<Kdf> Argon2Kdf::clone() const
return QSharedPointer<Argon2Kdf>::create(*this); return QSharedPointer<Argon2Kdf>::create(*this);
} }
int Argon2Kdf::benchmarkImpl(int msec) const int Argon2Kdf::benchmark(int msec) const
{ {
QByteArray key = QByteArray(16, '\x7E'); try {
QByteArray seed = QByteArray(32, '\x4B'); auto algo = type() == Type::Argon2d ? "Argon2d" : "Argon2id";
auto pwhash = Botan::PasswordHashFamily::create_or_throw(algo)->tune(
QElapsedTimer timer; 32, std::chrono::milliseconds(msec), memory() / 1024);
timer.start(); return qMax(static_cast<size_t>(1), pwhash->iterations());
} catch (std::exception& e) {
int rounds = 4;
if (transformKeyRaw(key, seed, version(), type(), rounds, memory(), parallelism(), key)) {
return static_cast<int>(rounds * (static_cast<float>(msec) / timer.elapsed()));
}
return 1; return 1;
} }
}
QString Argon2Kdf::toString() const QString Argon2Kdf::toString() const
{ {

View File

@ -45,22 +45,11 @@ public:
bool setParallelism(quint32 threads); bool setParallelism(quint32 threads);
QString toString() const override; QString toString() const override;
protected: int benchmark(int msec) const override;
int benchmarkImpl(int msec) const override;
quint32 m_version; quint32 m_version;
quint64 m_memory; quint64 m_memory;
quint32 m_parallelism; quint32 m_parallelism;
private:
Q_REQUIRED_RESULT static bool transformKeyRaw(const QByteArray& key,
const QByteArray& seed,
quint32 version,
Type type,
quint32 rounds,
quint64 memory,
quint32 parallelism,
QByteArray& result);
}; };
#endif // KEEPASSX_ARGON2KDF_H #endif // KEEPASSX_ARGON2KDF_H

View File

@ -16,7 +16,6 @@
*/ */
#include "Kdf.h" #include "Kdf.h"
#include "Kdf_p.h"
#include <QtConcurrent> #include <QtConcurrent>
@ -68,37 +67,3 @@ void Kdf::randomizeSeed()
{ {
setSeed(randomGen()->randomArray(m_seed.size())); setSeed(randomGen()->randomArray(m_seed.size()));
} }
int Kdf::benchmark(int msec) const
{
// Run the benchmark twice using half the time for each run
BenchmarkThread thread(msec / 2, this);
int rounds = 0;
thread.start();
thread.wait();
rounds += thread.rounds();
thread.start();
thread.wait();
rounds += thread.rounds();
return qMax(1, rounds);
}
Kdf::BenchmarkThread::BenchmarkThread(int msec, const Kdf* kdf)
: m_rounds(1)
, m_msec(msec)
, m_kdf(kdf)
{
}
int Kdf::BenchmarkThread::rounds()
{
return m_rounds;
}
void Kdf::BenchmarkThread::run()
{
m_rounds = m_kdf->benchmarkImpl(m_msec);
}

View File

@ -46,7 +46,7 @@ public:
virtual QString toString() const = 0; virtual QString toString() const = 0;
int benchmark(int msec) const; virtual int benchmark(int msec) const = 0;
/* /*
* Default target encryption time, in MS. * Default target encryption time, in MS.
@ -62,13 +62,10 @@ public:
static const int MAX_ENCRYPTION_TIME = 5000; static const int MAX_ENCRYPTION_TIME = 5000;
protected: protected:
virtual int benchmarkImpl(int msec) const = 0;
int m_rounds; int m_rounds;
QByteArray m_seed; QByteArray m_seed;
private: private:
class BenchmarkThread;
const QUuid m_uuid; const QUuid m_uuid;
}; };
#endif // KEEPASSX_KDF_H #endif // KEEPASSX_KDF_H

View File

@ -1,43 +0,0 @@
/*
* 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
* 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 "Kdf.h"
#include <QThread>
#ifndef KEEPASSXC_KDF_P_H
#define KEEPASSXC_KDF_P_H
class Kdf::BenchmarkThread : public QThread
{
Q_OBJECT
public:
explicit BenchmarkThread(int msec, const Kdf* kdf);
int rounds();
protected:
void run();
private:
int m_rounds;
int m_msec;
const Kdf* m_kdf;
};
#endif // KEEPASSXC_KDF_P_H

View File

@ -1,14 +0,0 @@
if(WITH_XC_CRYPTO_SSH)
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
set(crypto_ssh_SOURCES
bcrypt_pbkdf.cpp
blowfish.c
ASN1Key.cpp
BinaryStream.cpp
OpenSSHKey.cpp
)
add_library(crypto_ssh STATIC ${crypto_ssh_SOURCES})
target_link_libraries(crypto_ssh Qt5::Core ${GCRYPT_LIBRARIES})
endif()

View File

@ -1,172 +0,0 @@
/* $OpenBSD: bcrypt_pbkdf.c,v 1.13 2015/01/12 03:20:04 tedu Exp $ */
/*
* Copyright (c) 2013 Ted Unangst <tedu@openbsd.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <QtCore>
extern "C" {
#include "blf.h"
}
#define MINIMUM(a,b) (((a) < (b)) ? (a) : (b))
/*
* pkcs #5 pbkdf2 implementation using the "bcrypt" hash
*
* The bcrypt hash function is derived from the bcrypt password hashing
* function with the following modifications:
* 1. The input password and salt are preprocessed with SHA512.
* 2. The output length is expanded to 256 bits.
* 3. Subsequently the magic string to be encrypted is lengthened and modifed
* to "OxychromaticBlowfishSwatDynamite"
* 4. The hash function is defined to perform 64 rounds of initial state
* expansion. (More rounds are performed by iterating the hash.)
*
* Note that this implementation pulls the SHA512 operations into the caller
* as a performance optimization.
*
* One modification from official pbkdf2. Instead of outputting key material
* linearly, we mix it. pbkdf2 has a known weakness where if one uses it to
* generate (e.g.) 512 bits of key material for use as two 256 bit keys, an
* attacker can merely run once through the outer loop, but the user
* always runs it twice. Shuffling output bytes requires computing the
* entirety of the key material to assemble any subkey. This is something a
* wise caller could do; we just do it for you.
*/
#define BCRYPT_WORDS 8
#define BCRYPT_HASHSIZE (BCRYPT_WORDS * 4)
#define SHA512_DIGEST_LENGTH 64
// FIXME: explicit_bzero exists to ensure bzero is not optimized out
#define explicit_bzero bzero
static void
bcrypt_hash(const quint8* sha2pass, const quint8* sha2salt, quint8* out)
{
blf_ctx state;
quint8 ciphertext[BCRYPT_HASHSIZE] = // "OxychromaticBlowfishSwatDynamite"
{ 0x4f, 0x78, 0x79, 0x63, 0x68, 0x72, 0x6f, 0x6d,
0x61, 0x74, 0x69, 0x63, 0x42, 0x6c, 0x6f, 0x77,
0x66, 0x69, 0x73, 0x68, 0x53, 0x77, 0x61, 0x74,
0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x74, 0x65 };
quint32 cdata[BCRYPT_WORDS];
int i;
quint16 j;
size_t shalen = SHA512_DIGEST_LENGTH;
/* key expansion */
Blowfish_initstate(&state);
Blowfish_expandstate(&state, sha2salt, shalen, sha2pass, shalen);
for (i = 0; i < 64; i++) {
Blowfish_expand0state(&state, sha2salt, shalen);
Blowfish_expand0state(&state, sha2pass, shalen);
}
/* encryption */
j = 0;
for (i = 0; i < BCRYPT_WORDS; i++)
cdata[i] = Blowfish_stream2word(ciphertext, sizeof(ciphertext),
&j);
for (i = 0; i < 64; i++)
blf_enc(&state, cdata, BCRYPT_WORDS / 2);
/* copy out */
for (i = 0; i < BCRYPT_WORDS; i++) {
out[4 * i + 3] = (cdata[i] >> 24) & 0xff;
out[4 * i + 2] = (cdata[i] >> 16) & 0xff;
out[4 * i + 1] = (cdata[i] >> 8) & 0xff;
out[4 * i + 0] = cdata[i] & 0xff;
}
/* zap */
explicit_bzero(ciphertext, sizeof(ciphertext));
explicit_bzero(cdata, sizeof(cdata));
explicit_bzero(&state, sizeof(state));
}
int bcrypt_pbkdf(const QByteArray& pass, const QByteArray& salt, QByteArray& key, quint32 rounds)
{
QCryptographicHash ctx(QCryptographicHash::Sha512);
QByteArray sha2pass;
QByteArray sha2salt;
quint8 out[BCRYPT_HASHSIZE];
quint8 tmpout[BCRYPT_HASHSIZE];
quint8 countsalt[4];
/* nothing crazy */
if (rounds < 1) {
return -1;
}
if (pass.isEmpty() || salt.isEmpty() || key.isEmpty() ||
static_cast<quint32>(key.length()) > sizeof(out) * sizeof(out)) {
return -1;
}
quint32 stride = (key.length() + sizeof(out) - 1) / sizeof(out);
quint32 amt = (key.length() + stride - 1) / stride;
/* collapse password */
ctx.reset();
ctx.addData(pass);
sha2pass = ctx.result();
/* generate key, sizeof(out) at a time */
for (quint32 count = 1, keylen = key.length(); keylen > 0; count++) {
countsalt[0] = (count >> 24) & 0xff;
countsalt[1] = (count >> 16) & 0xff;
countsalt[2] = (count >> 8) & 0xff;
countsalt[3] = count & 0xff;
/* first round, salt is salt */
ctx.reset();
ctx.addData(salt);
ctx.addData(reinterpret_cast<char *>(countsalt), sizeof(countsalt));
sha2salt = ctx.result();
bcrypt_hash(reinterpret_cast<quint8 *>(sha2pass.data()), reinterpret_cast<quint8 *>(sha2salt.data()), tmpout);
memcpy(out, tmpout, sizeof(out));
for (quint32 i = 1; i < rounds; i++) {
/* subsequent rounds, salt is previous output */
ctx.reset();
ctx.addData(reinterpret_cast<char *>(tmpout), sizeof(tmpout));
sha2salt = ctx.result();
bcrypt_hash(reinterpret_cast<quint8 *>(sha2pass.data()), reinterpret_cast<quint8 *>(sha2salt.data()), tmpout);
for (quint32 j = 0; j < sizeof(out); j++)
out[j] ^= tmpout[j];
}
/*
* pbkdf2 deviation: output the key material non-linearly.
*/
amt = MINIMUM(amt, keylen);
quint32 i;
for (i = 0; i < amt; i++) {
int dest = i * stride + (count - 1);
if (dest >= key.length())
break;
key.data()[dest] = out[i];
}
keylen -= i;
}
/* zap */
explicit_bzero(out, sizeof(out));
return 0;
}

View File

@ -1,88 +0,0 @@
/* $OpenBSD: blf.h,v 1.7 2007/03/14 17:59:41 grunk Exp $ */
/*
* Blowfish - a fast block cipher designed by Bruce Schneier
*
* Copyright 1997 Niels Provos <provos@physnet.uni-hamburg.de>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Niels Provos.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef _BLF_H_
#define _BLF_H_
#include "includes.h"
#if !defined(HAVE_BCRYPT_PBKDF) && !defined(HAVE_BLH_H)
/* Schneier specifies a maximum key length of 56 bytes.
* This ensures that every key bit affects every cipher
* bit. However, the subkeys can hold up to 72 bytes.
* Warning: For normal blowfish encryption only 56 bytes
* of the key affect all cipherbits.
*/
#define BLF_N 16 /* Number of Subkeys */
#define BLF_MAXKEYLEN ((BLF_N-2)*4) /* 448 bits */
#define BLF_MAXUTILIZED ((BLF_N+2)*4) /* 576 bits */
/* Blowfish context */
typedef struct BlowfishContext {
u_int32_t S[4][256]; /* S-Boxes */
u_int32_t P[BLF_N + 2]; /* Subkeys */
} blf_ctx;
/* Raw access to customized Blowfish
* blf_key is just:
* Blowfish_initstate( state )
* Blowfish_expand0state( state, key, keylen )
*/
void Blowfish_encipher(blf_ctx *, u_int32_t *, u_int32_t *);
void Blowfish_decipher(blf_ctx *, u_int32_t *, u_int32_t *);
void Blowfish_initstate(blf_ctx *);
void Blowfish_expand0state(blf_ctx *, const u_int8_t *, u_int16_t);
void Blowfish_expandstate
(blf_ctx *, const u_int8_t *, u_int16_t, const u_int8_t *, u_int16_t);
/* Standard Blowfish */
void blf_key(blf_ctx *, const u_int8_t *, u_int16_t);
void blf_enc(blf_ctx *, u_int32_t *, u_int16_t);
void blf_dec(blf_ctx *, u_int32_t *, u_int16_t);
void blf_ecb_encrypt(blf_ctx *, u_int8_t *, u_int32_t);
void blf_ecb_decrypt(blf_ctx *, u_int8_t *, u_int32_t);
void blf_cbc_encrypt(blf_ctx *, u_int8_t *, u_int8_t *, u_int32_t);
void blf_cbc_decrypt(blf_ctx *, u_int8_t *, u_int8_t *, u_int32_t);
/* Converts u_int8_t to u_int32_t */
u_int32_t Blowfish_stream2word(const u_int8_t *, u_int16_t , u_int16_t *);
#endif /* !defined(HAVE_BCRYPT_PBKDF) && !defined(HAVE_BLH_H) */
#endif /* _BLF_H */

View File

@ -1,696 +0,0 @@
/* $OpenBSD: blowfish.c,v 1.18 2004/11/02 17:23:26 hshoexer Exp $ */
/*
* Blowfish block cipher for OpenBSD
* Copyright 1997 Niels Provos <provos@physnet.uni-hamburg.de>
* All rights reserved.
*
* Implementation advice by David Mazieres <dm@lcs.mit.edu>.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Niels Provos.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* This code is derived from section 14.3 and the given source
* in section V of Applied Cryptography, second edition.
* Blowfish is an unpatented fast block cipher designed by
* Bruce Schneier.
*/
#include "includes.h"
#if !defined(HAVE_BCRYPT_PBKDF) && (!defined(HAVE_BLOWFISH_INITSTATE) || \
!defined(HAVE_BLOWFISH_EXPAND0STATE) || !defined(HAVE_BLF_ENC))
#if 0
#include <stdio.h> /* used for debugging */
#include <string.h>
#endif
#include <sys/types.h>
#ifdef HAVE_BLF_H
#include <blf.h>
#endif
#undef inline
#ifdef __GNUC__
#define inline __inline
#else /* !__GNUC__ */
#define inline
#endif /* !__GNUC__ */
/* Function for Feistel Networks */
#define F(s, x) ((((s)[ (((x)>>24)&0xFF)] \
+ (s)[0x100 + (((x)>>16)&0xFF)]) \
^ (s)[0x200 + (((x)>> 8)&0xFF)]) \
+ (s)[0x300 + ( (x) &0xFF)])
#define BLFRND(s,p,i,j,n) (i ^= F(s,j) ^ (p)[n])
void
Blowfish_encipher(blf_ctx *c, u_int32_t *xl, u_int32_t *xr)
{
u_int32_t Xl;
u_int32_t Xr;
u_int32_t *s = c->S[0];
u_int32_t *p = c->P;
Xl = *xl;
Xr = *xr;
Xl ^= p[0];
BLFRND(s, p, Xr, Xl, 1); BLFRND(s, p, Xl, Xr, 2);
BLFRND(s, p, Xr, Xl, 3); BLFRND(s, p, Xl, Xr, 4);
BLFRND(s, p, Xr, Xl, 5); BLFRND(s, p, Xl, Xr, 6);
BLFRND(s, p, Xr, Xl, 7); BLFRND(s, p, Xl, Xr, 8);
BLFRND(s, p, Xr, Xl, 9); BLFRND(s, p, Xl, Xr, 10);
BLFRND(s, p, Xr, Xl, 11); BLFRND(s, p, Xl, Xr, 12);
BLFRND(s, p, Xr, Xl, 13); BLFRND(s, p, Xl, Xr, 14);
BLFRND(s, p, Xr, Xl, 15); BLFRND(s, p, Xl, Xr, 16);
*xl = Xr ^ p[17];
*xr = Xl;
}
void
Blowfish_decipher(blf_ctx *c, u_int32_t *xl, u_int32_t *xr)
{
u_int32_t Xl;
u_int32_t Xr;
u_int32_t *s = c->S[0];
u_int32_t *p = c->P;
Xl = *xl;
Xr = *xr;
Xl ^= p[17];
BLFRND(s, p, Xr, Xl, 16); BLFRND(s, p, Xl, Xr, 15);
BLFRND(s, p, Xr, Xl, 14); BLFRND(s, p, Xl, Xr, 13);
BLFRND(s, p, Xr, Xl, 12); BLFRND(s, p, Xl, Xr, 11);
BLFRND(s, p, Xr, Xl, 10); BLFRND(s, p, Xl, Xr, 9);
BLFRND(s, p, Xr, Xl, 8); BLFRND(s, p, Xl, Xr, 7);
BLFRND(s, p, Xr, Xl, 6); BLFRND(s, p, Xl, Xr, 5);
BLFRND(s, p, Xr, Xl, 4); BLFRND(s, p, Xl, Xr, 3);
BLFRND(s, p, Xr, Xl, 2); BLFRND(s, p, Xl, Xr, 1);
*xl = Xr ^ p[0];
*xr = Xl;
}
void
Blowfish_initstate(blf_ctx *c)
{
/* P-box and S-box tables initialized with digits of Pi */
static const blf_ctx initstate =
{ {
{
0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7,
0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99,
0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16,
0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e,
0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee,
0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,
0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef,
0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e,
0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60,
0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440,
0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce,
0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,
0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e,
0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677,
0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193,
0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032,
0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88,
0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,
0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e,
0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0,
0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3,
0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98,
0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88,
0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,
0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6,
0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d,
0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b,
0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7,
0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba,
0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,
0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f,
0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09,
0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3,
0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb,
0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279,
0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,
0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab,
0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82,
0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db,
0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573,
0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0,
0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,
0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790,
0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8,
0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4,
0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0,
0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7,
0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,
0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad,
0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1,
0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299,
0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9,
0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477,
0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,
0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49,
0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af,
0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa,
0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5,
0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41,
0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,
0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400,
0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915,
0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664,
0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a},
{
0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623,
0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266,
0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1,
0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e,
0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6,
0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,
0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e,
0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1,
0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737,
0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8,
0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff,
0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,
0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701,
0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7,
0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41,
0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331,
0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf,
0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,
0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e,
0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87,
0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c,
0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2,
0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16,
0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,
0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b,
0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509,
0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e,
0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3,
0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f,
0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,
0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4,
0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960,
0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66,
0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28,
0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802,
0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,
0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510,
0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf,
0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14,
0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e,
0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50,
0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,
0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8,
0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281,
0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99,
0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696,
0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128,
0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,
0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0,
0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0,
0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105,
0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250,
0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3,
0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,
0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00,
0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061,
0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb,
0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e,
0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735,
0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,
0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9,
0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340,
0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20,
0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7},
{
0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934,
0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068,
0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af,
0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840,
0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45,
0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,
0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a,
0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb,
0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee,
0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6,
0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42,
0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,
0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2,
0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb,
0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527,
0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b,
0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33,
0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,
0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3,
0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc,
0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17,
0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564,
0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b,
0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,
0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922,
0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728,
0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0,
0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e,
0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37,
0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,
0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804,
0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b,
0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3,
0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb,
0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d,
0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,
0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350,
0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9,
0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a,
0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe,
0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d,
0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,
0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f,
0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61,
0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2,
0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9,
0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2,
0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,
0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e,
0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633,
0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10,
0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169,
0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52,
0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,
0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5,
0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62,
0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634,
0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76,
0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24,
0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,
0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4,
0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c,
0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837,
0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0},
{
0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b,
0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe,
0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b,
0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4,
0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8,
0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,
0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304,
0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22,
0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4,
0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6,
0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9,
0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,
0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593,
0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51,
0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28,
0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c,
0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b,
0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,
0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c,
0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd,
0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a,
0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319,
0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb,
0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,
0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991,
0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32,
0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680,
0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166,
0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae,
0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,
0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5,
0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47,
0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370,
0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d,
0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84,
0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,
0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8,
0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd,
0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9,
0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7,
0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38,
0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,
0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c,
0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525,
0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1,
0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442,
0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964,
0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,
0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8,
0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d,
0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f,
0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299,
0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02,
0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,
0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614,
0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a,
0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6,
0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b,
0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0,
0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,
0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e,
0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9,
0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f,
0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6}
},
{
0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344,
0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89,
0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917,
0x9216d5d9, 0x8979fb1b
} };
*c = initstate;
}
u_int32_t
Blowfish_stream2word(const u_int8_t *data, u_int16_t databytes,
u_int16_t *current)
{
u_int8_t i;
u_int16_t j;
u_int32_t temp;
temp = 0x00000000;
j = *current;
for (i = 0; i < 4; i++, j++) {
if (j >= databytes)
j = 0;
temp = (temp << 8) | data[j];
}
*current = j;
return temp;
}
void
Blowfish_expand0state(blf_ctx *c, const u_int8_t *key, u_int16_t keybytes)
{
u_int16_t i;
u_int16_t j;
u_int16_t k;
u_int32_t temp;
u_int32_t datal;
u_int32_t datar;
j = 0;
for (i = 0; i < BLF_N + 2; i++) {
/* Extract 4 int8 to 1 int32 from keystream */
temp = Blowfish_stream2word(key, keybytes, &j);
c->P[i] = c->P[i] ^ temp;
}
j = 0;
datal = 0x00000000;
datar = 0x00000000;
for (i = 0; i < BLF_N + 2; i += 2) {
Blowfish_encipher(c, &datal, &datar);
c->P[i] = datal;
c->P[i + 1] = datar;
}
for (i = 0; i < 4; i++) {
for (k = 0; k < 256; k += 2) {
Blowfish_encipher(c, &datal, &datar);
c->S[i][k] = datal;
c->S[i][k + 1] = datar;
}
}
}
void
Blowfish_expandstate(blf_ctx *c, const u_int8_t *data, u_int16_t databytes,
const u_int8_t *key, u_int16_t keybytes)
{
u_int16_t i;
u_int16_t j;
u_int16_t k;
u_int32_t temp;
u_int32_t datal;
u_int32_t datar;
j = 0;
for (i = 0; i < BLF_N + 2; i++) {
/* Extract 4 int8 to 1 int32 from keystream */
temp = Blowfish_stream2word(key, keybytes, &j);
c->P[i] = c->P[i] ^ temp;
}
j = 0;
datal = 0x00000000;
datar = 0x00000000;
for (i = 0; i < BLF_N + 2; i += 2) {
datal ^= Blowfish_stream2word(data, databytes, &j);
datar ^= Blowfish_stream2word(data, databytes, &j);
Blowfish_encipher(c, &datal, &datar);
c->P[i] = datal;
c->P[i + 1] = datar;
}
for (i = 0; i < 4; i++) {
for (k = 0; k < 256; k += 2) {
datal ^= Blowfish_stream2word(data, databytes, &j);
datar ^= Blowfish_stream2word(data, databytes, &j);
Blowfish_encipher(c, &datal, &datar);
c->S[i][k] = datal;
c->S[i][k + 1] = datar;
}
}
}
void
blf_key(blf_ctx *c, const u_int8_t *k, u_int16_t len)
{
/* Initialize S-boxes and subkeys with Pi */
Blowfish_initstate(c);
/* Transform S-boxes and subkeys with key */
Blowfish_expand0state(c, k, len);
}
void
blf_enc(blf_ctx *c, u_int32_t *data, u_int16_t blocks)
{
u_int32_t *d;
u_int16_t i;
d = data;
for (i = 0; i < blocks; i++) {
Blowfish_encipher(c, d, d + 1);
d += 2;
}
}
void
blf_dec(blf_ctx *c, u_int32_t *data, u_int16_t blocks)
{
u_int32_t *d;
u_int16_t i;
d = data;
for (i = 0; i < blocks; i++) {
Blowfish_decipher(c, d, d + 1);
d += 2;
}
}
void
blf_ecb_encrypt(blf_ctx *c, u_int8_t *data, u_int32_t len)
{
u_int32_t l, r;
u_int32_t i;
for (i = 0; i < len; i += 8) {
l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3];
r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7];
Blowfish_encipher(c, &l, &r);
data[0] = l >> 24 & 0xff;
data[1] = l >> 16 & 0xff;
data[2] = l >> 8 & 0xff;
data[3] = l & 0xff;
data[4] = r >> 24 & 0xff;
data[5] = r >> 16 & 0xff;
data[6] = r >> 8 & 0xff;
data[7] = r & 0xff;
data += 8;
}
}
void
blf_ecb_decrypt(blf_ctx *c, u_int8_t *data, u_int32_t len)
{
u_int32_t l, r;
u_int32_t i;
for (i = 0; i < len; i += 8) {
l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3];
r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7];
Blowfish_decipher(c, &l, &r);
data[0] = l >> 24 & 0xff;
data[1] = l >> 16 & 0xff;
data[2] = l >> 8 & 0xff;
data[3] = l & 0xff;
data[4] = r >> 24 & 0xff;
data[5] = r >> 16 & 0xff;
data[6] = r >> 8 & 0xff;
data[7] = r & 0xff;
data += 8;
}
}
void
blf_cbc_encrypt(blf_ctx *c, u_int8_t *iv, u_int8_t *data, u_int32_t len)
{
u_int32_t l, r;
u_int32_t i, j;
for (i = 0; i < len; i += 8) {
for (j = 0; j < 8; j++)
data[j] ^= iv[j];
l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3];
r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7];
Blowfish_encipher(c, &l, &r);
data[0] = l >> 24 & 0xff;
data[1] = l >> 16 & 0xff;
data[2] = l >> 8 & 0xff;
data[3] = l & 0xff;
data[4] = r >> 24 & 0xff;
data[5] = r >> 16 & 0xff;
data[6] = r >> 8 & 0xff;
data[7] = r & 0xff;
iv = data;
data += 8;
}
}
void
blf_cbc_decrypt(blf_ctx *c, u_int8_t *iva, u_int8_t *data, u_int32_t len)
{
u_int32_t l, r;
u_int8_t *iv;
u_int32_t i, j;
iv = data + len - 16;
data = data + len - 8;
for (i = len - 8; i >= 8; i -= 8) {
l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3];
r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7];
Blowfish_decipher(c, &l, &r);
data[0] = l >> 24 & 0xff;
data[1] = l >> 16 & 0xff;
data[2] = l >> 8 & 0xff;
data[3] = l & 0xff;
data[4] = r >> 24 & 0xff;
data[5] = r >> 16 & 0xff;
data[6] = r >> 8 & 0xff;
data[7] = r & 0xff;
for (j = 0; j < 8; j++)
data[j] ^= iv[j];
iv -= 8;
data -= 8;
}
l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3];
r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7];
Blowfish_decipher(c, &l, &r);
data[0] = l >> 24 & 0xff;
data[1] = l >> 16 & 0xff;
data[2] = l >> 8 & 0xff;
data[3] = l & 0xff;
data[4] = r >> 24 & 0xff;
data[5] = r >> 16 & 0xff;
data[6] = r >> 8 & 0xff;
data[7] = r & 0xff;
for (j = 0; j < 8; j++)
data[j] ^= iva[j];
}
#if 0
void
report(u_int32_t data[], u_int16_t len)
{
u_int16_t i;
for (i = 0; i < len; i += 2)
printf("Block %0hd: %08lx %08lx.\n",
i / 2, data[i], data[i + 1]);
}
void
main(void)
{
blf_ctx c;
char key[] = "AAAAA";
char key2[] = "abcdefghijklmnopqrstuvwxyz";
u_int32_t data[10];
u_int32_t data2[] =
{0x424c4f57l, 0x46495348l};
u_int16_t i;
/* First test */
for (i = 0; i < 10; i++)
data[i] = i;
blf_key(&c, (u_int8_t *) key, 5);
blf_enc(&c, data, 5);
blf_dec(&c, data, 1);
blf_dec(&c, data + 2, 4);
printf("Should read as 0 - 9.\n");
report(data, 10);
/* Second test */
blf_key(&c, (u_int8_t *) key2, strlen(key2));
blf_enc(&c, data2, 1);
printf("\nShould read as: 0x324ed0fe 0xf413a203.\n");
report(data2, 2);
blf_dec(&c, data2, 1);
report(data2, 2);
}
#endif
#endif /* !defined(HAVE_BCRYPT_PBKDF) && (!defined(HAVE_BLOWFISH_INITSTATE) || \
!defined(HAVE_BLOWFISH_EXPAND0STATE) || !defined(HAVE_BLF_ENC)) */

View File

@ -1,19 +0,0 @@
// mimic openSSH-portable's includes.h file to be able to use
// its unmodified blowfish code
#define HAVE_BLF_H
#ifndef _GNU_SOURCE
#define _GNU_SOURCE /* activate extra prototypes for glibc */
#endif
#include <sys/types.h>
#if defined(_WIN32) || defined(__HAIKU__)
#include <stdint.h>
typedef uint32_t u_int32_t;
typedef uint16_t u_int16_t;
typedef uint8_t u_int8_t;
#define bzero(p, s) memset(p, 0, s)
#endif

View File

@ -18,9 +18,6 @@ if(WITH_XC_FDOSECRETS)
# setting storage # setting storage
FdoSecretsSettings.cpp FdoSecretsSettings.cpp
# gcrypt MPI wrapper
GcryptMPI.cpp
# dbus objects # dbus objects
dbus/DBusClient.cpp dbus/DBusClient.cpp
dbus/DBusMgr.cpp dbus/DBusMgr.cpp
@ -34,5 +31,5 @@ if(WITH_XC_FDOSECRETS)
objects/Prompt.cpp objects/Prompt.cpp
dbus/DBusTypes.cpp dbus/DBusTypes.cpp
) )
target_link_libraries(fdosecrets Qt5::Core Qt5::Widgets Qt5::DBus ${GCRYPT_LIBRARIES}) target_link_libraries(fdosecrets Qt5::Core Qt5::Widgets Qt5::DBus ${BOTAN2_LIBRARIES})
endif() endif()

View File

@ -1,63 +0,0 @@
/*
* Copyright (C) 2019 Aetf <aetf@unlimitedcodeworks.xyz>
*
* 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 "GcryptMPI.h"
GcryptMPI MpiFromBytes(const QByteArray& bytes, bool secure, gcry_mpi_format format)
{
auto bufLen = static_cast<size_t>(bytes.size());
const char* buf = nullptr;
// gcry_mpi_scan uses secure memory only if input buffer is secure memory, so we have to make a copy
GcryptMemPtr secureBuf;
if (secure) {
secureBuf.reset(static_cast<char*>(gcry_malloc_secure(bufLen)));
memcpy(secureBuf.get(), bytes.data(), bufLen);
buf = secureBuf.get();
} else {
buf = bytes.data();
}
gcry_mpi_t rawMpi;
auto err = gcry_mpi_scan(&rawMpi, format, buf, format == GCRYMPI_FMT_HEX ? 0 : bufLen, nullptr);
GcryptMPI mpi(rawMpi);
if (gcry_err_code(err) != GPG_ERR_NO_ERROR) {
mpi.reset();
}
return mpi;
}
GcryptMPI MpiFromHex(const char* hex, bool secure)
{
return MpiFromBytes(QByteArray::fromRawData(hex, static_cast<int>(strlen(hex) + 1)), secure, GCRYMPI_FMT_HEX);
}
QByteArray MpiToBytes(const GcryptMPI& mpi)
{
unsigned char* buf = nullptr;
size_t buflen = 0;
gcry_mpi_aprint(GCRYMPI_FMT_USG, &buf, &buflen, mpi.get());
QByteArray bytes(reinterpret_cast<char*>(buf), static_cast<int>(buflen));
gcry_free(buf);
return bytes;
}

View File

@ -1,64 +0,0 @@
/*
* Copyright (C) 2019 Aetf <aetf@unlimitedcodeworks.xyz>
*
* 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/>.
*/
#ifndef KEEPASSXC_GCRYPTMPI_H
#define KEEPASSXC_GCRYPTMPI_H
#include <QByteArray>
#include <gcrypt.h>
#include <memory>
#include <type_traits>
template <typename D, D fn> using deleter_from_fn = std::integral_constant<D, fn>;
/**
* A simple RAII wrapper for gcry_mpi_t
*/
using GcryptMPI = std::unique_ptr<gcry_mpi, deleter_from_fn<decltype(&gcry_mpi_release), &gcry_mpi_release>>;
/**
* A simple RAII wrapper for libgcrypt allocated memory
*/
using GcryptMemPtr = std::unique_ptr<char, deleter_from_fn<decltype(&gcry_free), &gcry_free>>;
/**
* Parse a QByteArray as MPI
* @param bytes
* @param secure
* @param format
* @return
*/
GcryptMPI MpiFromBytes(const QByteArray& bytes, bool secure = true, gcry_mpi_format format = GCRYMPI_FMT_USG);
/**
* Parse MPI from hex data
* @param hex
* @param secure
* @return
*/
GcryptMPI MpiFromHex(const char* hex, bool secure = true);
/**
* Dump MPI to bytes in USG format
* @param mpi
* @return
*/
QByteArray MpiToBytes(const GcryptMPI& mpi);
#endif // KEEPASSXC_GCRYPTMPI_H

View File

@ -17,29 +17,11 @@
#include "SessionCipher.h" #include "SessionCipher.h"
#include "crypto/CryptoHash.h"
#include "crypto/Random.h" #include "crypto/Random.h"
#include "crypto/SymmetricCipher.h" #include "crypto/SymmetricCipher.h"
#include <gcrypt.h> #include <botan/dh.h>
#include <botan/pk_ops.h>
#include <memory>
namespace
{
constexpr const auto IETF1024_SECOND_OAKLEY_GROUP_P_HEX = "FFFFFFFFFFFFFFFFC90FDAA22168C234"
"C4C6628B80DC1CD129024E088A67CC74"
"020BBEA63B139B22514A08798E3404DD"
"EF9519B3CD3A431B302B0A6DF25F1437"
"4FE1356D6D51C245E485B576625E7EC6"
"F44C42E9A637ED6B0BFF5CB6F406B7ED"
"EE386BFB5A899FA5AE9F24117C4B1FE6"
"49286651ECE65381FFFFFFFFFFFFFFFF";
constexpr const size_t KEY_SIZE_BYTES = 128;
constexpr const int AES_KEY_LEN = 16; // 128 bits
const auto IETF1024_SECOND_OAKLEY_GROUP_P = MpiFromHex(IETF1024_SECOND_OAKLEY_GROUP_P_HEX, false);
} // namespace
namespace FdoSecrets namespace FdoSecrets
{ {
@ -47,128 +29,56 @@ namespace FdoSecrets
constexpr char PlainCipher::Algorithm[]; constexpr char PlainCipher::Algorithm[];
constexpr char DhIetf1024Sha256Aes128CbcPkcs7::Algorithm[]; constexpr char DhIetf1024Sha256Aes128CbcPkcs7::Algorithm[];
DhIetf1024Sha256Aes128CbcPkcs7::DhIetf1024Sha256Aes128CbcPkcs7(const QByteArray& clientPublicKeyBytes) DhIetf1024Sha256Aes128CbcPkcs7::DhIetf1024Sha256Aes128CbcPkcs7(const QByteArray& clientPublicKey)
: m_valid(false)
{ {
// read client public key try {
auto clientPub = MpiFromBytes(clientPublicKeyBytes, false); m_privateKey.reset(new Botan::DH_PrivateKey(*randomGen()->getRng(), Botan::DL_Group("modp/ietf/1024")));
m_valid = updateClientPublicKey(clientPublicKey);
// generate server side private, 128 bytes } catch (std::exception& e) {
GcryptMPI serverPrivate = nullptr; qCritical("Failed to derive DH key: %s", e.what());
if (NextPrivKey) { m_privateKey.reset();
serverPrivate = std::move(NextPrivKey); m_valid = false;
} else { }
serverPrivate.reset(gcry_mpi_snew(KEY_SIZE_BYTES * 8));
gcry_mpi_randomize(serverPrivate.get(), KEY_SIZE_BYTES * 8, GCRY_STRONG_RANDOM);
} }
// generate server side public key bool DhIetf1024Sha256Aes128CbcPkcs7::updateClientPublicKey(const QByteArray& clientPublicKey)
GcryptMPI serverPublic = nullptr;
if (NextPubKey) {
serverPublic = std::move(NextPubKey);
} else {
serverPublic.reset(gcry_mpi_snew(KEY_SIZE_BYTES * 8));
// the generator of Second Oakley Group is 2
gcry_mpi_powm(
serverPublic.get(), GCRYMPI_CONST_TWO, serverPrivate.get(), IETF1024_SECOND_OAKLEY_GROUP_P.get());
}
initialize(std::move(clientPub), std::move(serverPublic), std::move(serverPrivate));
}
bool
DhIetf1024Sha256Aes128CbcPkcs7::initialize(GcryptMPI clientPublic, GcryptMPI serverPublic, GcryptMPI serverPrivate)
{ {
QByteArray commonSecretBytes; if (!m_privateKey) {
if (!diffieHullman(clientPublic, serverPrivate, commonSecretBytes)) {
return false; return false;
} }
m_privateKey = MpiToBytes(serverPrivate); try {
m_publicKey = MpiToBytes(serverPublic); Botan::secure_vector<uint8_t> salt(32, '\0');
auto dhka = m_privateKey->create_key_agreement_op(*randomGen()->getRng(), "HKDF(SHA-256)", "");
m_aesKey = hkdf(commonSecretBytes); auto aesKey = dhka->agree(16,
reinterpret_cast<const uint8_t*>(clientPublicKey.constData()),
m_valid = true; clientPublicKey.size(),
salt.data(),
salt.size());
m_aesKey = QByteArray(reinterpret_cast<char*>(aesKey.data()), aesKey.size());
return true; return true;
} } catch (std::exception& e) {
qCritical("Failed to update client public key: %s", e.what());
bool DhIetf1024Sha256Aes128CbcPkcs7::diffieHullman(const GcryptMPI& clientPub,
const GcryptMPI& serverPrivate,
QByteArray& commonSecretBytes)
{
if (!clientPub || !serverPrivate) {
return false; return false;
} }
// calc common secret
GcryptMPI commonSecret(gcry_mpi_snew(KEY_SIZE_BYTES * 8));
gcry_mpi_powm(commonSecret.get(), clientPub.get(), serverPrivate.get(), IETF1024_SECOND_OAKLEY_GROUP_P.get());
commonSecretBytes = MpiToBytes(commonSecret);
return true;
}
QByteArray DhIetf1024Sha256Aes128CbcPkcs7::hkdf(const QByteArray& IKM)
{
// HKDF-Extract(salt, IKM) -> PRK
// PRK = HMAC-Hash(salt, IKM)
// we use NULL salt as per spec
auto PRK = CryptoHash::hmac(IKM,
QByteArrayLiteral("\0\0\0\0\0\0\0\0"
"\0\0\0\0\0\0\0\0"
"\0\0\0\0\0\0\0\0"
"\0\0\0\0\0\0\0\0"),
CryptoHash::Sha256);
// HKDF-Expand(PRK, info, L) -> OKM
// N = ceil(L/HashLen)
// T = T(1) | T(2) | T(3) | ... | T(N)
// OKM = first L octets of T
// where:
// T(0) = empty string (zero length)
// T(1) = HMAC-Hash(PRK, T(0) | info | 0x01)
// T(2) = HMAC-Hash(PRK, T(1) | info | 0x02)
// T(3) = HMAC-Hash(PRK, T(2) | info | 0x03)
// ...
//
// (where the constant concatenated to the end of each T(n) is a
// single octet.)
// we use empty info as per spec
// HashLen = 32 (sha256)
// L = 16 (16 * 8 = 128 bits)
// N = ceil(16/32) = 1
auto T1 = CryptoHash::hmac(QByteArrayLiteral("\x01"), PRK, CryptoHash::Sha256);
// resulting AES key is first 128 bits
Q_ASSERT(T1.size() >= AES_KEY_LEN);
auto OKM = T1.left(AES_KEY_LEN);
return OKM;
} }
Secret DhIetf1024Sha256Aes128CbcPkcs7::encrypt(const Secret& input) Secret DhIetf1024Sha256Aes128CbcPkcs7::encrypt(const Secret& input)
{ {
Secret output = input; Secret output = input;
output.value.clear();
output.parameters.clear(); output.parameters.clear();
output.value.clear();
SymmetricCipher encrypter(SymmetricCipher::Aes128, SymmetricCipher::Cbc, SymmetricCipher::Encrypt); SymmetricCipher encrypter;
auto IV = randomGen()->randomArray(SymmetricCipher::defaultIvSize(SymmetricCipher::Aes128_CBC));
auto IV = randomGen()->randomArray(SymmetricCipher::algorithmIvSize(SymmetricCipher::Aes128)); if (!encrypter.init(SymmetricCipher::Aes128_CBC, SymmetricCipher::Encrypt, m_aesKey, IV)) {
if (!encrypter.init(m_aesKey, IV)) {
qWarning() << "Error encrypt: " << encrypter.errorString(); qWarning() << "Error encrypt: " << encrypter.errorString();
return output; return output;
} }
output.parameters = IV; output.parameters = IV;
bool ok;
output.value = input.value; output.value = input.value;
output.value = encrypter.process(padPkcs7(output.value, encrypter.blockSize()), &ok); if (!encrypter.finish(output.value)) {
if (!ok) {
qWarning() << "Error encrypt: " << encrypter.errorString(); qWarning() << "Error encrypt: " << encrypter.errorString();
return output; return output;
} }
@ -176,50 +86,23 @@ namespace FdoSecrets
return output; return output;
} }
QByteArray& DhIetf1024Sha256Aes128CbcPkcs7::padPkcs7(QByteArray& input, int blockSize)
{
// blockSize must be a power of 2.
Q_ASSERT_X(blockSize > 0 && !(blockSize & (blockSize - 1)), "padPkcs7", "blockSize must be a power of 2");
int padLen = blockSize - (input.size() & (blockSize - 1));
input.append(QByteArray(padLen, static_cast<char>(padLen)));
return input;
}
Secret DhIetf1024Sha256Aes128CbcPkcs7::decrypt(const Secret& input) Secret DhIetf1024Sha256Aes128CbcPkcs7::decrypt(const Secret& input)
{ {
auto IV = input.parameters; SymmetricCipher decrypter;
SymmetricCipher decrypter(SymmetricCipher::Aes128, SymmetricCipher::Cbc, SymmetricCipher::Decrypt); if (!decrypter.init(SymmetricCipher::Aes128_CBC, SymmetricCipher::Decrypt, m_aesKey, input.parameters)) {
if (!decrypter.init(m_aesKey, IV)) {
qWarning() << "Error decoding: " << decrypter.errorString(); qWarning() << "Error decoding: " << decrypter.errorString();
return input; return input;
} }
bool ok;
Secret output = input; Secret output = input;
output.parameters.clear(); output.parameters.clear();
output.value = decrypter.process(input.value, &ok); if (!decrypter.finish(output.value)) {
if (!ok) {
qWarning() << "Error decoding: " << decrypter.errorString(); qWarning() << "Error decoding: " << decrypter.errorString();
return input; return input;
} }
unpadPkcs7(output.value);
return output; return output;
} }
QByteArray& DhIetf1024Sha256Aes128CbcPkcs7::unpadPkcs7(QByteArray& input)
{
if (input.isEmpty()) {
return input;
}
int padLen = input[input.size() - 1];
input.chop(padLen);
return input;
}
bool DhIetf1024Sha256Aes128CbcPkcs7::isValid() const bool DhIetf1024Sha256Aes128CbcPkcs7::isValid() const
{ {
return m_valid; return m_valid;
@ -227,16 +110,10 @@ namespace FdoSecrets
QVariant DhIetf1024Sha256Aes128CbcPkcs7::negotiationOutput() const QVariant DhIetf1024Sha256Aes128CbcPkcs7::negotiationOutput() const
{ {
return m_publicKey; if (m_valid) {
auto pubkey = m_privateKey->public_value();
return QByteArray(reinterpret_cast<char*>(pubkey.data()), pubkey.size());
} }
return {};
void DhIetf1024Sha256Aes128CbcPkcs7::fixNextServerKeys(GcryptMPI priv, GcryptMPI pub)
{
NextPrivKey = std::move(priv);
NextPubKey = std::move(pub);
} }
GcryptMPI DhIetf1024Sha256Aes128CbcPkcs7::NextPrivKey = nullptr;
GcryptMPI DhIetf1024Sha256Aes128CbcPkcs7::NextPubKey = nullptr;
} // namespace FdoSecrets } // namespace FdoSecrets

View File

@ -18,15 +18,15 @@
#ifndef KEEPASSXC_FDOSECRETS_SESSIONCIPHER_H #ifndef KEEPASSXC_FDOSECRETS_SESSIONCIPHER_H
#define KEEPASSXC_FDOSECRETS_SESSIONCIPHER_H #define KEEPASSXC_FDOSECRETS_SESSIONCIPHER_H
#include "fdosecrets/GcryptMPI.h"
#include "fdosecrets/objects/Session.h" #include "fdosecrets/objects/Session.h"
class TestFdoSecrets; namespace Botan
class TestGuiFdoSecrets; {
class DH_PrivateKey;
}
namespace FdoSecrets namespace FdoSecrets
{ {
class CipherPair class CipherPair
{ {
Q_DISABLE_COPY(CipherPair) Q_DISABLE_COPY(CipherPair)
@ -69,77 +69,24 @@ namespace FdoSecrets
class DhIetf1024Sha256Aes128CbcPkcs7 : public CipherPair class DhIetf1024Sha256Aes128CbcPkcs7 : public CipherPair
{ {
bool m_valid;
QByteArray m_privateKey;
QByteArray m_publicKey;
QByteArray m_aesKey;
/**
* Diffie Hullman Key Exchange
* Given client public key, generate server private/public key pair and common secret.
* This also sets m_publicKey to server's public key
* @param clientPublicKey client public key
* @param serverPrivate server private key
* @param commonSecretBytes output common secret
* @return true on success.
*/
bool
diffieHullman(const GcryptMPI& clientPublicKey, const GcryptMPI& serverPrivate, QByteArray& commonSecretBytes);
/**
* Perform HKDF defined in RFC5869, using sha256 as hash function
* @param IKM input keying material
* @return derived 128-bit key suitable for AES
*/
QByteArray hkdf(const QByteArray& IKM);
/**
* Add PKCS#7 style padding to input inplace
* @param input
* @param blockSize the block size to use, must be 2's power
* @return reference to input for chaining
*/
QByteArray& padPkcs7(QByteArray& input, int blockSize);
/**
* Remove PKCS#7 style padding from input inplace
* @param input
* @return reference to input for chaining
*/
QByteArray& unpadPkcs7(QByteArray& input);
bool initialize(GcryptMPI clientPublic, GcryptMPI serverPublic, GcryptMPI serverPrivate);
DhIetf1024Sha256Aes128CbcPkcs7()
: m_valid(false)
{
}
public: public:
static constexpr const char Algorithm[] = "dh-ietf1024-sha256-aes128-cbc-pkcs7"; static constexpr const char Algorithm[] = "dh-ietf1024-sha256-aes128-cbc-pkcs7";
explicit DhIetf1024Sha256Aes128CbcPkcs7(const QByteArray& clientPublicKeyBytes); explicit DhIetf1024Sha256Aes128CbcPkcs7(const QByteArray& clientPublicKey);
Secret encrypt(const Secret& input) override; Secret encrypt(const Secret& input) override;
Secret decrypt(const Secret& input) override; Secret decrypt(const Secret& input) override;
bool isValid() const override; bool isValid() const override;
QVariant negotiationOutput() const override; QVariant negotiationOutput() const override;
private: bool updateClientPublicKey(const QByteArray& clientPublicKey);
/**
* For test only, fix the server side private and public key.
*/
static void fixNextServerKeys(GcryptMPI priv, GcryptMPI pub);
static GcryptMPI NextPrivKey;
static GcryptMPI NextPubKey;
private: private:
Q_DISABLE_COPY(DhIetf1024Sha256Aes128CbcPkcs7); Q_DISABLE_COPY(DhIetf1024Sha256Aes128CbcPkcs7);
friend class ::TestFdoSecrets;
friend class ::TestGuiFdoSecrets; bool m_valid = false;
QSharedPointer<Botan::DH_PrivateKey> m_privateKey;
QByteArray m_aesKey;
}; };
} // namespace FdoSecrets } // namespace FdoSecrets

View File

@ -65,10 +65,9 @@ bool Kdbx3Reader::readDatabaseImpl(QIODevice* device,
hash.addData(db->transformedDatabaseKey()); hash.addData(db->transformedDatabaseKey());
QByteArray finalKey = hash.result(); QByteArray finalKey = hash.result();
SymmetricCipher::Algorithm cipher = SymmetricCipher::cipherToAlgorithm(db->cipher()); auto mode = SymmetricCipher::cipherUuidToMode(db->cipher());
SymmetricCipherStream cipherStream( SymmetricCipherStream cipherStream(device);
device, cipher, SymmetricCipher::algorithmMode(cipher), SymmetricCipher::Decrypt); if (!cipherStream.init(mode, SymmetricCipher::Decrypt, finalKey, m_encryptionIV)) {
if (!cipherStream.init(finalKey, m_encryptionIV)) {
raiseError(cipherStream.errorString()); raiseError(cipherStream.errorString());
return false; return false;
} }
@ -106,8 +105,8 @@ bool Kdbx3Reader::readDatabaseImpl(QIODevice* device,
xmlDevice = ioCompressor.data(); xmlDevice = ioCompressor.data();
} }
KeePass2RandomStream randomStream(KeePass2::ProtectedStreamAlgo::Salsa20); KeePass2RandomStream randomStream;
if (!randomStream.init(m_protectedStreamKey)) { if (!randomStream.init(SymmetricCipher::Salsa20, m_protectedStreamKey)) {
raiseError(randomStream.errorString()); raiseError(randomStream.errorString());
return false; return false;
} }

View File

@ -35,8 +35,15 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db)
m_error = false; m_error = false;
m_errorStr.clear(); m_errorStr.clear();
auto mode = SymmetricCipher::cipherUuidToMode(db->cipher());
int ivSize = SymmetricCipher::defaultIvSize(mode);
if (ivSize < 0) {
raiseError(tr("Invalid symmetric cipher IV size.", "IV = Initialization Vector for symmetric cipher"));
return false;
}
QByteArray masterSeed = randomGen()->randomArray(32); QByteArray masterSeed = randomGen()->randomArray(32);
QByteArray encryptionIV = randomGen()->randomArray(16); QByteArray encryptionIV = randomGen()->randomArray(ivSize);
QByteArray protectedStreamKey = randomGen()->randomArray(32); QByteArray protectedStreamKey = randomGen()->randomArray(32);
QByteArray startBytes = randomGen()->randomArray(32); QByteArray startBytes = randomGen()->randomArray(32);
QByteArray endOfHeader = "\r\n\r\n"; QByteArray endOfHeader = "\r\n\r\n";
@ -95,9 +102,8 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db)
const QByteArray headerHash = CryptoHash::hash(header.data(), CryptoHash::Sha256); const QByteArray headerHash = CryptoHash::hash(header.data(), CryptoHash::Sha256);
// write cipher stream // write cipher stream
SymmetricCipher::Algorithm algo = SymmetricCipher::cipherToAlgorithm(db->cipher()); SymmetricCipherStream cipherStream(device);
SymmetricCipherStream cipherStream(device, algo, SymmetricCipher::algorithmMode(algo), SymmetricCipher::Encrypt); cipherStream.init(mode, SymmetricCipher::Encrypt, finalKey, encryptionIV);
cipherStream.init(finalKey, encryptionIV);
if (!cipherStream.open(QIODevice::WriteOnly)) { if (!cipherStream.open(QIODevice::WriteOnly)) {
raiseError(cipherStream.errorString()); raiseError(cipherStream.errorString());
return false; return false;
@ -127,8 +133,8 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db)
Q_ASSERT(outputDevice); Q_ASSERT(outputDevice);
KeePass2RandomStream randomStream(KeePass2::ProtectedStreamAlgo::Salsa20); KeePass2RandomStream randomStream;
if (!randomStream.init(protectedStreamKey)) { if (!randomStream.init(SymmetricCipher::Salsa20, protectedStreamKey)) {
raiseError(randomStream.errorString()); raiseError(randomStream.errorString());
return false; return false;
} }

View File

@ -83,13 +83,13 @@ bool Kdbx4Reader::readDatabaseImpl(QIODevice* device,
return false; return false;
} }
SymmetricCipher::Algorithm cipher = SymmetricCipher::cipherToAlgorithm(db->cipher()); auto mode = SymmetricCipher::cipherUuidToMode(db->cipher());
if (cipher == SymmetricCipher::InvalidAlgorithm) { if (mode == SymmetricCipher::InvalidMode) {
raiseError(tr("Unknown cipher")); raiseError(tr("Unknown cipher"));
return false; return false;
} }
SymmetricCipherStream cipherStream(&hmacStream, cipher, SymmetricCipher::algorithmMode(cipher), SymmetricCipher::Decrypt); SymmetricCipherStream cipherStream(&hmacStream);
if (!cipherStream.init(finalKey, m_encryptionIV)) { if (!cipherStream.init(mode, SymmetricCipher::Decrypt, finalKey, m_encryptionIV)) {
raiseError(cipherStream.errorString()); raiseError(cipherStream.errorString());
return false; return false;
} }
@ -121,8 +121,20 @@ bool Kdbx4Reader::readDatabaseImpl(QIODevice* device,
return false; return false;
} }
KeePass2RandomStream randomStream(m_irsAlgo); // TODO: Convert m_irsAlgo to Mode
if (!randomStream.init(m_protectedStreamKey)) { switch (m_irsAlgo) {
case KeePass2::ProtectedStreamAlgo::Salsa20:
mode = SymmetricCipher::Salsa20;
break;
case KeePass2::ProtectedStreamAlgo::ChaCha20:
mode = SymmetricCipher::ChaCha20;
break;
default:
mode = SymmetricCipher::InvalidMode;
}
KeePass2RandomStream randomStream;
if (!randomStream.init(mode, m_protectedStreamKey)) {
raiseError(randomStream.errorString()); raiseError(randomStream.errorString());
return false; return false;
} }

View File

@ -36,12 +36,12 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db)
m_error = false; m_error = false;
m_errorStr.clear(); m_errorStr.clear();
SymmetricCipher::Algorithm algo = SymmetricCipher::cipherToAlgorithm(db->cipher()); auto mode = SymmetricCipher::cipherUuidToMode(db->cipher());
if (algo == SymmetricCipher::InvalidAlgorithm) { if (mode == SymmetricCipher::InvalidMode) {
raiseError(tr("Invalid symmetric cipher algorithm.")); raiseError(tr("Invalid symmetric cipher algorithm."));
return false; return false;
} }
int ivSize = SymmetricCipher::algorithmIvSize(algo); int ivSize = SymmetricCipher::defaultIvSize(mode);
if (ivSize < 0) { if (ivSize < 0) {
raiseError(tr("Invalid symmetric cipher IV size.", "IV = Initialization Vector for symmetric cipher")); raiseError(tr("Invalid symmetric cipher IV size.", "IV = Initialization Vector for symmetric cipher"));
return false; return false;
@ -124,10 +124,9 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db)
return false; return false;
} }
cipherStream.reset(new SymmetricCipherStream( cipherStream.reset(new SymmetricCipherStream(hmacBlockStream.data()));
hmacBlockStream.data(), algo, SymmetricCipher::algorithmMode(algo), SymmetricCipher::Encrypt));
if (!cipherStream->init(finalKey, encryptionIV)) { if (!cipherStream->init(mode, SymmetricCipher::Encrypt, finalKey, encryptionIV)) {
raiseError(cipherStream->errorString()); raiseError(cipherStream->errorString());
return false; return false;
} }
@ -165,8 +164,8 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db)
CHECK_RETURN_FALSE(writeInnerHeaderField(outputDevice, KeePass2::InnerHeaderFieldID::End, QByteArray())); CHECK_RETURN_FALSE(writeInnerHeaderField(outputDevice, KeePass2::InnerHeaderFieldID::End, QByteArray()));
KeePass2RandomStream randomStream(KeePass2::ProtectedStreamAlgo::ChaCha20); KeePass2RandomStream randomStream;
if (!randomStream.init(protectedStreamKey)) { if (!randomStream.init(SymmetricCipher::ChaCha20, protectedStreamKey)) {
raiseError(randomStream.errorString()); raiseError(randomStream.errorString());
return false; return false;
} }
@ -207,7 +206,7 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db)
bool Kdbx4Writer::writeInnerHeaderField(QIODevice* device, KeePass2::InnerHeaderFieldID fieldId, const QByteArray& data) bool Kdbx4Writer::writeInnerHeaderField(QIODevice* device, KeePass2::InnerHeaderFieldID fieldId, const QByteArray& data)
{ {
QByteArray fieldIdArr; QByteArray fieldIdArr;
fieldIdArr[0] = static_cast<char>(fieldId); fieldIdArr.append(static_cast<char>(fieldId));
CHECK_RETURN_FALSE(writeData(device, fieldIdArr)); CHECK_RETURN_FALSE(writeData(device, fieldIdArr));
CHECK_RETURN_FALSE( CHECK_RETURN_FALSE(
writeData(device, Endian::sizedIntToBytes(static_cast<quint32>(data.size()), KeePass2::BYTEORDER))); writeData(device, Endian::sizedIntToBytes(static_cast<quint32>(data.size()), KeePass2::BYTEORDER)));
@ -294,7 +293,7 @@ bool Kdbx4Writer::serializeVariantMap(const QVariantMap& map, QByteArray& output
return false; return false;
} }
QByteArray typeBytes; QByteArray typeBytes;
typeBytes[0] = static_cast<char>(fieldType); typeBytes.append(static_cast<char>(fieldType));
QByteArray nameBytes = k.toUtf8(); QByteArray nameBytes = k.toUtf8();
QByteArray nameLenBytes = Endian::sizedIntToBytes(nameBytes.size(), KeePass2::BYTEORDER); QByteArray nameLenBytes = Endian::sizedIntToBytes(nameBytes.size(), KeePass2::BYTEORDER);
QByteArray dataLenBytes = Endian::sizedIntToBytes(data.size(), KeePass2::BYTEORDER); QByteArray dataLenBytes = Endian::sizedIntToBytes(data.size(), KeePass2::BYTEORDER);
@ -307,7 +306,7 @@ bool Kdbx4Writer::serializeVariantMap(const QVariantMap& map, QByteArray& output
} }
QByteArray endBytes; QByteArray endBytes;
endBytes[0] = static_cast<char>(KeePass2::VariantMapFieldType::End); endBytes.append(static_cast<char>(KeePass2::VariantMapFieldType::End));
CHECK_RETURN_FALSE(buf.write(endBytes) == 1); CHECK_RETURN_FALSE(buf.write(endBytes) == 1);
return true; return true;
} }

View File

@ -129,7 +129,7 @@ void KdbxReader::setCipher(const QByteArray& data)
return; return;
} }
if (SymmetricCipher::cipherToAlgorithm(uuid) == SymmetricCipher::InvalidAlgorithm) { if (SymmetricCipher::cipherUuidToMode(uuid) == SymmetricCipher::InvalidMode) {
raiseError(tr("Unsupported cipher")); raiseError(tr("Unsupported cipher"));
return; return;
} }

View File

@ -332,15 +332,14 @@ KeePass1Reader::testKeys(const QString& password, const QByteArray& keyfileData,
if (finalKey.isEmpty()) { if (finalKey.isEmpty()) {
return nullptr; return nullptr;
} }
if (m_encryptionFlags & KeePass1::Rijndael) {
cipherStream.reset(new SymmetricCipherStream(
m_device, SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt));
} else {
cipherStream.reset(new SymmetricCipherStream(
m_device, SymmetricCipher::Twofish, SymmetricCipher::Cbc, SymmetricCipher::Decrypt));
}
if (!cipherStream->init(finalKey, m_encryptionIV)) { cipherStream.reset(new SymmetricCipherStream(m_device));
auto mode = SymmetricCipher::Aes256_CBC;
if (m_encryptionFlags & KeePass1::Twofish) {
mode = SymmetricCipher::Twofish_CBC;
}
if (!cipherStream->init(mode, SymmetricCipher::Decrypt, finalKey, m_encryptionIV)) {
raiseError(cipherStream->errorString()); raiseError(cipherStream->errorString());
return nullptr; return nullptr;
} }
@ -362,9 +361,13 @@ KeePass1Reader::testKeys(const QString& password, const QByteArray& keyfileData,
return nullptr; return nullptr;
} }
cipherStream->open(QIODevice::ReadOnly);
if (success) { if (success) {
if (!cipherStream->init(mode, SymmetricCipher::Decrypt, finalKey, m_encryptionIV)) {
raiseError(cipherStream->errorString());
return nullptr;
}
cipherStream->open(QIODevice::ReadOnly);
break; break;
} else { } else {
cipherStream.reset(); cipherStream.reset();

View File

@ -20,23 +20,21 @@
#include "crypto/CryptoHash.h" #include "crypto/CryptoHash.h"
#include "format/KeePass2.h" #include "format/KeePass2.h"
KeePass2RandomStream::KeePass2RandomStream(KeePass2::ProtectedStreamAlgo algo) bool KeePass2RandomStream::init(SymmetricCipher::Mode mode, const QByteArray& key)
: m_cipher(mapAlgo(algo), SymmetricCipher::Stream, SymmetricCipher::Encrypt)
, m_offset(0)
{ {
switch (mode) {
case SymmetricCipher::Salsa20: {
return m_cipher.init(mode,
SymmetricCipher::Encrypt,
CryptoHash::hash(key, CryptoHash::Sha256),
KeePass2::INNER_STREAM_SALSA20_IV);
} }
bool KeePass2RandomStream::init(const QByteArray& key)
{
switch (m_cipher.algorithm()) {
case SymmetricCipher::Salsa20:
return m_cipher.init(CryptoHash::hash(key, CryptoHash::Sha256), KeePass2::INNER_STREAM_SALSA20_IV);
case SymmetricCipher::ChaCha20: { case SymmetricCipher::ChaCha20: {
QByteArray keyIv = CryptoHash::hash(key, CryptoHash::Sha512); QByteArray keyIv = CryptoHash::hash(key, CryptoHash::Sha512);
return m_cipher.init(keyIv.left(32), keyIv.mid(32, 12)); return m_cipher.init(mode, SymmetricCipher::Encrypt, keyIv.left(32), keyIv.mid(32, 12));
} }
default: default:
qWarning("Invalid stream algorithm (%d)", m_cipher.algorithm()); qWarning("Invalid stream cipher mode (%d)", mode);
break; break;
} }
return false; return false;
@ -111,23 +109,11 @@ bool KeePass2RandomStream::loadBlock()
{ {
Q_ASSERT(m_offset == m_buffer.size()); Q_ASSERT(m_offset == m_buffer.size());
m_buffer.fill('\0', m_cipher.blockSize()); m_buffer.fill('\0', m_cipher.blockSize(m_cipher.mode()));
if (!m_cipher.processInPlace(m_buffer)) { if (!m_cipher.process(m_buffer)) {
return false; return false;
} }
m_offset = 0; m_offset = 0;
return true; return true;
} }
SymmetricCipher::Algorithm KeePass2RandomStream::mapAlgo(KeePass2::ProtectedStreamAlgo algo)
{
switch (algo) {
case KeePass2::ProtectedStreamAlgo::ChaCha20:
return SymmetricCipher::ChaCha20;
case KeePass2::ProtectedStreamAlgo::Salsa20:
return SymmetricCipher::Salsa20;
default:
return SymmetricCipher::InvalidAlgorithm;
}
}

View File

@ -26,9 +26,9 @@
class KeePass2RandomStream class KeePass2RandomStream
{ {
public: public:
KeePass2RandomStream(KeePass2::ProtectedStreamAlgo algo); KeePass2RandomStream() = default;
bool init(const QByteArray& key); bool init(SymmetricCipher::Mode mode, const QByteArray& key);
QByteArray randomBytes(int size, bool* ok); QByteArray randomBytes(int size, bool* ok);
QByteArray process(const QByteArray& data, bool* ok); QByteArray process(const QByteArray& data, bool* ok);
Q_REQUIRED_RESULT bool processInPlace(QByteArray& data); Q_REQUIRED_RESULT bool processInPlace(QByteArray& data);
@ -39,9 +39,7 @@ private:
SymmetricCipher m_cipher; SymmetricCipher m_cipher;
QByteArray m_buffer; QByteArray m_buffer;
int m_offset; int m_offset = 0;
static SymmetricCipher::Algorithm mapAlgo(KeePass2::ProtectedStreamAlgo algo);
}; };
#endif // KEEPASSX_KEEPASS2RANDOMSTREAM_H #endif // KEEPASSX_KEEPASS2RANDOMSTREAM_H

View File

@ -69,8 +69,8 @@ bool OpData01::decode(const QByteArray& data, const QByteArray& key, const QByte
return false; return false;
} }
SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt); SymmetricCipher cipher;
if (!cipher.init(key, iv)) { if (!cipher.init(SymmetricCipher::Aes256_CBC, SymmetricCipher::Decrypt, key, iv)) {
m_errorStr = tr("Unable to init cipher for opdata01: %1").arg(cipher.errorString()); m_errorStr = tr("Unable to init cipher for opdata01: %1").arg(cipher.errorString());
return false; return false;
} }
@ -82,7 +82,7 @@ bool OpData01::decode(const QByteArray& data, const QByteArray& key, const QByte
* to the plaintext. Otherwise, between 1 and 15 (inclusive) bytes of random data are prepended to the plaintext * to the plaintext. Otherwise, between 1 and 15 (inclusive) bytes of random data are prepended to the plaintext
* to achieve an even multiple of blocks. * to achieve an even multiple of blocks.
*/ */
const int blockSize = cipher.blockSize(); const int blockSize = cipher.blockSize(cipher.mode());
int randomBytes = blockSize - (len % blockSize); int randomBytes = blockSize - (len % blockSize);
if (randomBytes == 0) { if (randomBytes == 0) {
// add random block // add random block
@ -111,7 +111,7 @@ bool OpData01::decode(const QByteArray& data, const QByteArray& key, const QByte
return false; return false;
} }
if (!cipher.processInPlace(qbaCT)) { if (!cipher.process(qbaCT)) {
m_errorStr = tr("Unable to process clearText in place"); m_errorStr = tr("Unable to process clearText in place");
return false; return false;
} }

View File

@ -28,7 +28,8 @@
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
#include <QUuid> #include <QUuid>
#include <gcrypt.h>
#include <botan/pwdhash.h>
OpVaultReader::OpVaultReader(QObject* parent) OpVaultReader::OpVaultReader(QObject* parent)
: QObject(parent) : QObject(parent)
@ -366,30 +367,12 @@ OpVaultReader::decodeB64CompositeKeys(const QString& b64, const QByteArray& encK
*/ */
OpVaultReader::DerivedKeyHMAC* OpVaultReader::decodeCompositeKeys(const QByteArray& keyKey) OpVaultReader::DerivedKeyHMAC* OpVaultReader::decodeCompositeKeys(const QByteArray& keyKey)
{ {
const int encKeySize = 256 / 8;
const int hmacKeySize = 256 / 8;
const int digestSize = encKeySize + hmacKeySize;
auto result = new DerivedKeyHMAC; auto result = new DerivedKeyHMAC;
result->error = false; result->error = false;
result->encrypt = QByteArray(encKeySize, '\0'); auto digest = CryptoHash::hash(keyKey, CryptoHash::Sha512);
result->hmac = QByteArray(hmacKeySize, '\0'); result->encrypt = digest.left(32);
result->hmac = digest.right(32);
const char* buffer_vp = keyKey.data();
auto buf_len = size_t(keyKey.size());
const int algo = GCRY_MD_SHA512;
unsigned char digest[digestSize];
gcry_md_hash_buffer(algo, digest, buffer_vp, buf_len);
unsigned char* cp = digest;
for (int i = 0, len = encKeySize; i < len; ++i) {
result->encrypt[i] = *(cp++);
}
for (int i = 0, len = hmacKeySize; i < len; ++i) {
result->hmac[i] = *(cp++);
}
return result; return result;
} }
@ -403,43 +386,27 @@ OpVaultReader::DerivedKeyHMAC* OpVaultReader::decodeCompositeKeys(const QByteArr
OpVaultReader::DerivedKeyHMAC* OpVaultReader::DerivedKeyHMAC*
OpVaultReader::deriveKeysFromPassPhrase(QByteArray& salt, const QString& password, unsigned long iterations) OpVaultReader::deriveKeysFromPassPhrase(QByteArray& salt, const QString& password, unsigned long iterations)
{ {
const int derivedEncKeySize = 256 / 8;
const int derivedMACSize = 256 / 8;
const int keysize = derivedEncKeySize + derivedMACSize;
auto result = new DerivedKeyHMAC; auto result = new DerivedKeyHMAC;
result->error = false; result->error = false;
QByteArray keybuffer(keysize, '\0'); QByteArray out(64, '\0');
auto err = gcry_kdf_derive(password.toUtf8().constData(), try {
auto pwhash = Botan::PasswordHashFamily::create_or_throw("PBKDF2(SHA-512)")->from_iterations(iterations);
pwhash->derive_key(reinterpret_cast<uint8_t*>(out.data()),
out.size(),
password.toUtf8().constData(),
password.size(), password.size(),
GCRY_KDF_PBKDF2, reinterpret_cast<const uint8_t*>(salt.constData()),
GCRY_MD_SHA512, salt.size());
salt.constData(), } catch (std::exception& e) {
salt.size(),
iterations,
keysize,
keybuffer.data());
if (err != 0) {
result->error = true; result->error = true;
result->errorStr = tr("Unable to derive master key: %1").arg(gcry_strerror(err)); result->errorStr = tr("Unable to derive master key: %1").arg(e.what());
return result; return result;
} }
if (keysize != keybuffer.size()) {
qWarning() << "Calling PBKDF2(keysize=" << keysize << "yielded" << keybuffer.size() << "bytes";
}
QByteArray::const_iterator it = keybuffer.cbegin(); result->encrypt = out.left(32);
result->hmac = out.right(32);
result->encrypt = QByteArray(derivedEncKeySize, '\0');
for (int i = 0, len = derivedEncKeySize; i < len && it != keybuffer.cend(); ++i, ++it) {
result->encrypt[i] = *it;
}
result->hmac = QByteArray(derivedMACSize, '\0');
for (int i = 0; i < derivedMACSize && it != keybuffer.cend(); ++i, ++it) {
result->hmac[i] = *it;
}
return result; return result;
} }

View File

@ -80,12 +80,12 @@ bool OpVaultReader::decryptBandEntry(const QJsonObject& bandEntry,
QByteArray iv = kBA.mid(0, 16); QByteArray iv = kBA.mid(0, 16);
QByteArray keyAndMacKey = kBA.mid(iv.size(), 64); QByteArray keyAndMacKey = kBA.mid(iv.size(), 64);
SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt); SymmetricCipher cipher;
if (!cipher.init(m_masterKey, iv)) { if (!cipher.init(SymmetricCipher::Aes256_CBC, SymmetricCipher::Decrypt, m_masterKey, iv)) {
qCritical() << "Unable to init cipher using masterKey in UUID " << uuid; qCritical() << "Unable to init cipher using masterKey in UUID " << uuid;
return false; return false;
} }
if (!cipher.processInPlace(keyAndMacKey)) { if (!cipher.process(keyAndMacKey)) {
qCritical() << "Unable to decipher \"k\"(key+hmac) in UUID " << uuid; qCritical() << "Unable to decipher \"k\"(key+hmac) in UUID " << uuid;
return false; return false;
} }

View File

@ -280,13 +280,14 @@ QSharedPointer<CompositeKey> DatabaseOpenWidget::buildDatabaseKey()
databaseKey->clear(); databaseKey->clear();
// try to get, decrypt and use PasswordKey // try to get, decrypt and use PasswordKey
QSharedPointer<QByteArray> passwordKey = TouchID::getInstance().getKey(m_filename); QByteArray passwordKey;
if (passwordKey != NULL) { if (TouchID::getInstance().getKey(m_filename, passwordKey)) {
// check if the user cancelled the operation // check if the user cancelled the operation
if (passwordKey.isNull()) if (passwordKey.isNull()) {
return QSharedPointer<CompositeKey>(); return QSharedPointer<CompositeKey>();
}
databaseKey->addKey(PasswordKey::fromRawKey(*passwordKey)); databaseKey->addKey(PasswordKey::fromRawKey(passwordKey));
} }
} }
#endif #endif

View File

@ -46,8 +46,8 @@
#include "core/TimeDelta.h" #include "core/TimeDelta.h"
#include "core/Tools.h" #include "core/Tools.h"
#ifdef WITH_XC_SSHAGENT #ifdef WITH_XC_SSHAGENT
#include "crypto/ssh/OpenSSHKey.h"
#include "sshagent/KeeAgentSettings.h" #include "sshagent/KeeAgentSettings.h"
#include "sshagent/OpenSSHKey.h"
#include "sshagent/SSHAgent.h" #include "sshagent/SSHAgent.h"
#endif #endif
#ifdef WITH_XC_BROWSER #ifdef WITH_XC_BROWSER

View File

@ -19,7 +19,7 @@ if(WITH_XC_KEESHARE)
) )
add_library(keeshare STATIC ${keeshare_SOURCES}) add_library(keeshare STATIC ${keeshare_SOURCES})
target_link_libraries(keeshare PUBLIC Qt5::Core Qt5::Widgets ${GCRYPT_LIBRARIES} ${crypto_ssh_LIB}) target_link_libraries(keeshare PUBLIC Qt5::Core Qt5::Widgets ${BOTAN2_LIBRARIES})
# Try to find libquazip5, if found, enable secure sharing # Try to find libquazip5, if found, enable secure sharing
find_package(QuaZip) find_package(QuaZip)

View File

@ -21,7 +21,6 @@
#include "core/DatabaseIcons.h" #include "core/DatabaseIcons.h"
#include "core/Group.h" #include "core/Group.h"
#include "core/Metadata.h" #include "core/Metadata.h"
#include "crypto/ssh/OpenSSHKey.h"
#include "keeshare/ShareObserver.h" #include "keeshare/ShareObserver.h"
#include "keeshare/Signature.h" #include "keeshare/Signature.h"

View File

@ -18,6 +18,7 @@
#ifndef KEEPASSXC_KEESHARE_H #ifndef KEEPASSXC_KEESHARE_H
#define KEEPASSXC_KEESHARE_H #define KEEPASSXC_KEESHARE_H
#include <QFileInfo>
#include <QMap> #include <QMap>
#include <QUuid> #include <QUuid>

View File

@ -16,77 +16,41 @@
*/ */
#include "KeeShareSettings.h" #include "KeeShareSettings.h"
#include "core/CustomData.h" #include "core/CustomData.h"
#include "core/Database.h" #include "core/Database.h"
#include "core/DatabaseIcons.h" #include "core/DatabaseIcons.h"
#include "core/Group.h" #include "core/Group.h"
#include "core/Metadata.h" #include "core/Metadata.h"
#include "crypto/ssh/OpenSSHKey.h" #include "crypto/Random.h"
#include "keeshare/Signature.h" #include "keeshare/Signature.h"
#include <QMessageBox> #include <QMessageBox>
#include <QPainter> #include <QPainter>
#include <QPushButton> #include <QPushButton>
#include <QTextCodec>
#include <QXmlStreamWriter>
#include <botan/ber_dec.h>
#include <botan/pk_keys.h>
#include <botan/pkcs8.h>
#include <botan/rsa.h>
#include <functional> #include <functional>
namespace KeeShareSettings namespace KeeShareSettings
{ {
namespace namespace
{ {
Certificate packCertificate(const OpenSSHKey& key, const QString& signer)
{
KeeShareSettings::Certificate extracted;
extracted.signer = signer;
Q_ASSERT(key.type() == "ssh-rsa");
extracted.key = OpenSSHKey::serializeToBinary(OpenSSHKey::Public, key);
return extracted;
}
Key packKey(const OpenSSHKey& key)
{
KeeShareSettings::Key extracted;
Q_ASSERT(key.type() == "ssh-rsa");
extracted.key = OpenSSHKey::serializeToBinary(OpenSSHKey::Private, key);
return extracted;
}
OpenSSHKey unpackKey(const Key& sign)
{
if (sign.key.isEmpty()) {
return OpenSSHKey();
}
OpenSSHKey key = OpenSSHKey::restoreFromBinary(OpenSSHKey::Private, sign.key);
Q_ASSERT(key.type() == "ssh-rsa");
return key;
}
OpenSSHKey unpackCertificate(const Certificate& certificate)
{
if (certificate.key.isEmpty()) {
return OpenSSHKey();
}
OpenSSHKey key = OpenSSHKey::restoreFromBinary(OpenSSHKey::Public, certificate.key);
Q_ASSERT(key.type() == "ssh-rsa");
return key;
}
QString xmlSerialize(std::function<void(QXmlStreamWriter& writer)> specific) QString xmlSerialize(std::function<void(QXmlStreamWriter& writer)> specific)
{ {
QString buffer; QString buffer;
QXmlStreamWriter writer(&buffer); QXmlStreamWriter writer(&buffer);
writer.setCodec(QTextCodec::codecForName("UTF-8")); writer.setCodec(QTextCodec::codecForName("UTF-8"));
writer.setAutoFormatting(true);
writer.setAutoFormattingIndent(2);
writer.writeStartDocument(); writer.writeStartDocument();
writer.writeStartElement("KeeShare"); writer.writeStartElement("KeeShare");
writer.writeAttribute("xmlns:xsd", "http://www.w3.org/2001/XMLSchema");
writer.writeAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
specific(writer); specific(writer);
writer.writeEndElement(); writer.writeEndElement();
writer.writeEndElement();
writer.writeEndDocument(); writer.writeEndDocument();
return buffer; return buffer;
} }
@ -106,17 +70,23 @@ namespace KeeShareSettings
if (certificate.isNull()) { if (certificate.isNull()) {
return; return;
} }
auto berKey = Botan::PKCS8::BER_encode(*certificate.key);
auto baKey = QByteArray::fromRawData(reinterpret_cast<const char*>(berKey.data()), berKey.size());
writer.writeStartElement("Signer"); writer.writeStartElement("Signer");
writer.writeCharacters(certificate.signer); writer.writeCharacters(certificate.signer);
writer.writeEndElement(); writer.writeEndElement();
writer.writeStartElement("Key"); writer.writeStartElement("Key");
writer.writeCharacters(certificate.key.toBase64()); writer.writeCharacters(baKey.toBase64());
writer.writeEndElement(); writer.writeEndElement();
} }
bool Certificate::operator==(const Certificate& other) const bool Certificate::operator==(const Certificate& other) const
{ {
return key == other.key && signer == other.signer; if (isNull() || other.isNull()) {
return isNull() == other.isNull();
}
return key->private_key_bits() == other.key->private_key_bits() && signer == other.signer;
} }
bool Certificate::operator!=(const Certificate& other) const bool Certificate::operator!=(const Certificate& other) const
@ -126,7 +96,7 @@ namespace KeeShareSettings
bool Certificate::isNull() const bool Certificate::isNull() const
{ {
return key.isEmpty() && signer.isEmpty(); return !key || signer.isEmpty();
} }
QString Certificate::fingerprint() const QString Certificate::fingerprint() const
@ -134,20 +104,7 @@ namespace KeeShareSettings
if (isNull()) { if (isNull()) {
return {}; return {};
} }
return sshKey().fingerprint(); return QString::fromStdString(key->fingerprint_public());
}
OpenSSHKey Certificate::sshKey() const
{
return unpackCertificate(*this);
}
QString Certificate::publicKey() const
{
if (isNull()) {
return {};
}
return sshKey().publicKey();
} }
Certificate Certificate::deserialize(QXmlStreamReader& reader) Certificate Certificate::deserialize(QXmlStreamReader& reader)
@ -157,7 +114,16 @@ namespace KeeShareSettings
if (reader.name() == "Signer") { if (reader.name() == "Signer") {
certificate.signer = reader.readElementText(); certificate.signer = reader.readElementText();
} else if (reader.name() == "Key") { } else if (reader.name() == "Key") {
certificate.key = QByteArray::fromBase64(reader.readElementText().toLatin1()); auto rawKey = QByteArray::fromBase64(reader.readElementText().toLatin1());
if (!rawKey.isEmpty()) {
try {
Botan::DataSource_Memory dataSource(reinterpret_cast<const uint8_t*>(rawKey.constData()),
rawKey.size());
certificate.key.reset(Botan::PKCS8::load_key(dataSource).release());
} catch (std::exception& e) {
qWarning("KeeShare: Failed to deserialize key data: %s", e.what());
}
}
} }
} }
return certificate; return certificate;
@ -165,7 +131,10 @@ namespace KeeShareSettings
bool Key::operator==(const Key& other) const bool Key::operator==(const Key& other) const
{ {
return key == other.key; if (isNull() || other.isNull()) {
return isNull() == other.isNull();
}
return key->private_key_bits() == other.key->private_key_bits();
} }
bool Key::operator!=(const Key& other) const bool Key::operator!=(const Key& other) const
@ -175,20 +144,7 @@ namespace KeeShareSettings
bool Key::isNull() const bool Key::isNull() const
{ {
return key.isEmpty(); return !key;
}
QString Key::privateKey() const
{
if (isNull()) {
return {};
}
return sshKey().privateKey();
}
OpenSSHKey Key::sshKey() const
{
return unpackKey(*this);
} }
void Key::serialize(QXmlStreamWriter& writer, const Key& key) void Key::serialize(QXmlStreamWriter& writer, const Key& key)
@ -196,24 +152,37 @@ namespace KeeShareSettings
if (key.isNull()) { if (key.isNull()) {
return; return;
} }
writer.writeCharacters(key.key.toBase64()); auto berKey = Botan::PKCS8::BER_encode(*key.key);
auto baKey = QByteArray::fromRawData(reinterpret_cast<const char*>(berKey.data()), berKey.size());
writer.writeCharacters(baKey.toBase64());
} }
Key Key::deserialize(QXmlStreamReader& reader) Key Key::deserialize(QXmlStreamReader& reader)
{ {
Key key; Key key;
key.key = QByteArray::fromBase64(reader.readElementText().toLatin1()); auto rawKey = QByteArray::fromBase64(reader.readElementText().toLatin1());
if (!rawKey.isEmpty()) {
try {
Botan::DataSource_Memory dataSource(reinterpret_cast<const uint8_t*>(rawKey.constData()),
rawKey.size());
key.key.reset(Botan::PKCS8::load_key(dataSource).release());
} catch (std::exception& e) {
qWarning("KeeShare: Failed to deserialize key data: %s", e.what());
}
}
return key; return key;
} }
Own Own::generate() Own Own::generate()
{ {
OpenSSHKey key = OpenSSHKey::generate(false);
key.openKey(QString());
Own own; Own own;
own.key = packKey(key); own.key.key.reset(new Botan::RSA_PrivateKey(*randomGen()->getRng(), 2048));
const QString name = qgetenv("USER"); // + "@" + QHostInfo::localHostName(); auto name = qgetenv("USER");
own.certificate = packCertificate(key, name); if (name.isEmpty()) {
name = qgetenv("USERNAME");
}
own.certificate.signer = name;
own.certificate.key = own.key.key;
return own; return own;
} }
@ -249,7 +218,7 @@ namespace KeeShareSettings
} }
} }
} else { } else {
::qWarning() << "Unknown KeeShareSettings element" << reader.name(); qWarning("Unknown KeeShareSettings element %s", qPrintable(reader.name().toString()));
reader.skipCurrentElement(); reader.skipCurrentElement();
} }
} }
@ -289,7 +258,7 @@ namespace KeeShareSettings
} else if (reader.name() == "PublicKey") { } else if (reader.name() == "PublicKey") {
own.certificate = Certificate::deserialize(reader); own.certificate = Certificate::deserialize(reader);
} else { } else {
::qWarning() << "Unknown KeeShareSettings element" << reader.name(); qWarning("Unknown KeeShareSettings element %s", qPrintable(reader.name().toString()));
reader.skipCurrentElement(); reader.skipCurrentElement();
} }
} }
@ -360,12 +329,12 @@ namespace KeeShareSettings
if (reader.name() == "Certificate") { if (reader.name() == "Certificate") {
foreign.certificates << ScopedCertificate::deserialize(reader); foreign.certificates << ScopedCertificate::deserialize(reader);
} else { } else {
::qWarning() << "Unknown Cerificates element" << reader.name(); qWarning("Unknown Certificates element %s", qPrintable(reader.name().toString()));
reader.skipCurrentElement(); reader.skipCurrentElement();
} }
} }
} else { } else {
::qWarning() << "Unknown KeeShareSettings element" << reader.name(); qWarning("Unknown KeeShareSettings element %s", qPrintable(reader.name().toString()));
reader.skipCurrentElement(); reader.skipCurrentElement();
} }
} }
@ -459,7 +428,7 @@ namespace KeeShareSettings
} else if (reader.name() == "Password") { } else if (reader.name() == "Password") {
reference.password = QString::fromUtf8(QByteArray::fromBase64(reader.readElementText().toLatin1())); reference.password = QString::fromUtf8(QByteArray::fromBase64(reader.readElementText().toLatin1()));
} else { } else {
::qWarning() << "Unknown Reference element" << reader.name(); qWarning("Unknown Reference element %s", qPrintable(reader.name().toString()));
reader.skipCurrentElement(); reader.skipCurrentElement();
} }
} }
@ -489,7 +458,7 @@ namespace KeeShareSettings
} else if (reader.name() == "Certificate") { } else if (reader.name() == "Certificate") {
sign.certificate = KeeShareSettings::Certificate::deserialize(reader); sign.certificate = KeeShareSettings::Certificate::deserialize(reader);
} else { } else {
::qWarning() << "Unknown Sign element" << reader.name(); qWarning("Unknown Sign element %s", qPrintable(reader.name().toString()));
reader.skipCurrentElement(); reader.skipCurrentElement();
} }
} }

View File

@ -20,8 +20,13 @@
#include <QMap> #include <QMap>
#include <QObject> #include <QObject>
#include <QSharedPointer>
#include <QUuid>
#include "crypto/ssh/OpenSSHKey.h" namespace Botan
{
class Private_Key;
}
class CustomData; class CustomData;
class QXmlStreamWriter; class QXmlStreamWriter;
@ -31,7 +36,7 @@ namespace KeeShareSettings
{ {
struct Certificate struct Certificate
{ {
QByteArray key; QSharedPointer<Botan::Private_Key> key;
QString signer; QString signer;
bool operator==(const Certificate& other) const; bool operator==(const Certificate& other) const;
@ -39,8 +44,6 @@ namespace KeeShareSettings
bool isNull() const; bool isNull() const;
QString fingerprint() const; QString fingerprint() const;
QString publicKey() const;
OpenSSHKey sshKey() const;
static void serialize(QXmlStreamWriter& writer, const Certificate& certificate); static void serialize(QXmlStreamWriter& writer, const Certificate& certificate);
static Certificate deserialize(QXmlStreamReader& reader); static Certificate deserialize(QXmlStreamReader& reader);
@ -48,14 +51,13 @@ namespace KeeShareSettings
struct Key struct Key
{ {
QByteArray key; QSharedPointer<Botan::Private_Key> key;
bool operator==(const Key& other) const; bool operator==(const Key& other) const;
bool operator!=(const Key& other) const; bool operator!=(const Key& other) const;
bool isNull() const; bool isNull() const;
QString privateKey() const; QString privateKey() const;
OpenSSHKey sshKey() const;
static void serialize(QXmlStreamWriter& writer, const Key& key); static void serialize(QXmlStreamWriter& writer, const Key& key);
static Key deserialize(QXmlStreamReader& reader); static Key deserialize(QXmlStreamReader& reader);

View File

@ -22,11 +22,13 @@
#include "core/Group.h" #include "core/Group.h"
#include "core/Metadata.h" #include "core/Metadata.h"
#include "gui/FileDialog.h" #include "gui/FileDialog.h"
#include "gui/MessageBox.h"
#include "keeshare/KeeShare.h" #include "keeshare/KeeShare.h"
#include "keeshare/KeeShareSettings.h" #include "keeshare/KeeShareSettings.h"
#include <QMessageBox>
#include <QStandardItemModel> #include <QStandardItemModel>
#include <QStandardPaths>
#include <QTextStream>
SettingsWidgetKeeShare::SettingsWidgetKeeShare(QWidget* parent) SettingsWidgetKeeShare::SettingsWidgetKeeShare(QWidget* parent)
: QWidget(parent) : QWidget(parent)
@ -72,27 +74,32 @@ void SettingsWidgetKeeShare::updateForeignCertificates()
{ {
auto headers = QStringList() << tr("Path") << tr("Status"); auto headers = QStringList() << tr("Path") << tr("Status");
#if defined(WITH_XC_KEESHARE_SECURE) #if defined(WITH_XC_KEESHARE_SECURE)
headers << tr("Signer") << tr("Fingerprint") << tr("Certificate"); headers << tr("Signer") << tr("Fingerprint");
#endif #endif
m_importedCertificateModel.reset(new QStandardItemModel()); m_importedCertificateModel.reset(new QStandardItemModel());
m_importedCertificateModel->setHorizontalHeaderLabels(headers); m_importedCertificateModel->setHorizontalHeaderLabels(headers);
for (const auto& scopedCertificate : m_foreign.certificates) { for (const auto& scopedCertificate : m_foreign.certificates) {
const auto items = QList<QStandardItem*>() QList<QStandardItem*> items;
<< new QStandardItem(scopedCertificate.path) items << new QStandardItem(scopedCertificate.path);
<< new QStandardItem(scopedCertificate.trust == KeeShareSettings::Trust::Ask
? tr("Ask") switch (scopedCertificate.trust) {
: (scopedCertificate.trust == KeeShareSettings::Trust::Trusted case KeeShareSettings::Trust::Ask:
? tr("Trusted") items << new QStandardItem(tr("Ask"));
: tr("Untrusted"))) break;
case KeeShareSettings::Trust::Trusted:
items << new QStandardItem(tr("Trusted"));
break;
case KeeShareSettings::Trust::Untrusted:
items << new QStandardItem(tr("Untrusted"));
break;
}
#if defined(WITH_XC_KEESHARE_SECURE) #if defined(WITH_XC_KEESHARE_SECURE)
<< new QStandardItem(scopedCertificate.isKnown() ? scopedCertificate.certificate.signer items << new QStandardItem(scopedCertificate.isKnown() ? scopedCertificate.certificate.signer : tr("Unknown"));
: tr("Unknown")) items << new QStandardItem(scopedCertificate.certificate.fingerprint());
<< new QStandardItem(scopedCertificate.certificate.fingerprint())
<< new QStandardItem(scopedCertificate.certificate.publicKey())
#endif #endif
;
m_importedCertificateModel->appendRow(items); m_importedCertificateModel->appendRow(items);
} }
@ -103,8 +110,6 @@ void SettingsWidgetKeeShare::updateForeignCertificates()
void SettingsWidgetKeeShare::updateOwnCertificate() void SettingsWidgetKeeShare::updateOwnCertificate()
{ {
m_ui->ownCertificateSignerEdit->setText(m_own.certificate.signer); m_ui->ownCertificateSignerEdit->setText(m_own.certificate.signer);
m_ui->ownCertificatePublicKeyEdit->setText(m_own.certificate.publicKey());
m_ui->ownCertificatePrivateKeyEdit->setText(m_own.key.privateKey());
m_ui->ownCertificateFingerprintEdit->setText(m_own.certificate.fingerprint()); m_ui->ownCertificateFingerprintEdit->setText(m_own.certificate.fingerprint());
} }
@ -133,8 +138,6 @@ void SettingsWidgetKeeShare::generateCertificate()
{ {
m_own = KeeShareSettings::Own::generate(); m_own = KeeShareSettings::Own::generate();
m_ui->ownCertificateSignerEdit->setText(m_own.certificate.signer); m_ui->ownCertificateSignerEdit->setText(m_own.certificate.signer);
m_ui->ownCertificatePublicKeyEdit->setText(m_own.certificate.publicKey());
m_ui->ownCertificatePrivateKeyEdit->setText(m_own.key.privateKey());
m_ui->ownCertificateFingerprintEdit->setText(m_own.certificate.fingerprint()); m_ui->ownCertificateFingerprintEdit->setText(m_own.certificate.fingerprint());
} }
@ -165,16 +168,14 @@ void SettingsWidgetKeeShare::importCertificate()
void SettingsWidgetKeeShare::exportCertificate() void SettingsWidgetKeeShare::exportCertificate()
{ {
if (KeeShare::own() != m_own) { if (KeeShare::own() != m_own) {
QMessageBox warning; auto ans = MessageBox::warning(
warning.setIcon(QMessageBox::Warning); this,
warning.setWindowTitle(tr("Exporting changed certificate")); tr("Exporting changed certificate"),
warning.setText(tr("The exported certificate is not the same as the one in use. Do you want to export the " tr("The exported certificate is not the same as the one in use. Do you want to export the "
"current certificate?")); "current certificate?"),
auto yes = warning.addButton(QMessageBox::StandardButton::Yes); MessageBox::Yes | MessageBox::No,
auto no = warning.addButton(QMessageBox::StandardButton::No); MessageBox::No);
warning.setDefaultButton(no); if (ans != MessageBox::Yes) {
warning.exec();
if (warning.clickedButton() != yes) {
return; return;
} }
} }
@ -186,8 +187,7 @@ void SettingsWidgetKeeShare::exportCertificate()
const auto filetype = tr("key.share", "Filetype for KeeShare key"); const auto filetype = tr("key.share", "Filetype for KeeShare key");
const auto filters = QString("%1 (*." + filetype + ");;%2 (*)").arg(tr("KeeShare key file"), tr("All files")); const auto filters = QString("%1 (*." + filetype + ");;%2 (*)").arg(tr("KeeShare key file"), tr("All files"));
QString filename = QString("%1.%2").arg(m_own.certificate.signer).arg(filetype); QString filename = QString("%1.%2").arg(m_own.certificate.signer).arg(filetype);
filename = fileDialog()->getSaveFileName( filename = fileDialog()->getSaveFileName(this, tr("Select path"), defaultDirPath, filters);
this, tr("Select path"), defaultDirPath, filters, nullptr, QFileDialog::Options(0));
if (filename.isEmpty()) { if (filename.isEmpty()) {
return; return;
} }

View File

@ -79,87 +79,10 @@
<property name="title"> <property name="title">
<string>Own certificate</string> <string>Own certificate</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout_2" columnstretch="0,1,1"> <layout class="QGridLayout" name="gridLayout_2" columnstretch="0,0,0">
<property name="horizontalSpacing"> <property name="horizontalSpacing">
<number>10</number> <number>10</number>
</property> </property>
<item row="5" column="0">
<widget class="QLabel" name="ownCertificateFingerprintLabel">
<property name="text">
<string>Fingerprint:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="3" column="1" colspan="2">
<widget class="QLineEdit" name="ownCertificatePrivateKeyEdit">
<property name="accessibleName">
<string>Key</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="ownCertificatePublicKeyLabel">
<property name="text">
<string>Certificate:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="ownCertificateSignerLabel">
<property name="text">
<string>Signer:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="ownCertificatePrivateKeyLabel">
<property name="text">
<string>Key:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="4" column="1" colspan="2">
<widget class="QLineEdit" name="ownCertificatePublicKeyEdit">
<property name="accessibleName">
<string>Certificate</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="QLineEdit" name="ownCertificateSignerEdit">
<property name="accessibleName">
<string>Signer name field</string>
</property>
</widget>
</item>
<item row="5" column="1" colspan="2">
<widget class="QLineEdit" name="ownCertificateFingerprintEdit">
<property name="accessibleName">
<string>Fingerprint</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0" colspan="3"> <item row="0" column="0" colspan="3">
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<item> <item>
@ -207,6 +130,43 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="3" column="1" colspan="2">
<widget class="QLineEdit" name="ownCertificateFingerprintEdit">
<property name="accessibleName">
<string>Fingerprint</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="QLineEdit" name="ownCertificateSignerEdit">
<property name="accessibleName">
<string>Signer name field</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="ownCertificateSignerLabel">
<property name="text">
<string>Signer:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="ownCertificateFingerprintLabel">
<property name="text">
<string>Fingerprint:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
@ -333,8 +293,6 @@
<tabstop>importOwnCertificateButton</tabstop> <tabstop>importOwnCertificateButton</tabstop>
<tabstop>exportOwnCertificateButton</tabstop> <tabstop>exportOwnCertificateButton</tabstop>
<tabstop>ownCertificateSignerEdit</tabstop> <tabstop>ownCertificateSignerEdit</tabstop>
<tabstop>ownCertificatePrivateKeyEdit</tabstop>
<tabstop>ownCertificatePublicKeyEdit</tabstop>
<tabstop>ownCertificateFingerprintEdit</tabstop> <tabstop>ownCertificateFingerprintEdit</tabstop>
<tabstop>trustImportedCertificateButton</tabstop> <tabstop>trustImportedCertificateButton</tabstop>
<tabstop>askImportedCertificateButton</tabstop> <tabstop>askImportedCertificateButton</tabstop>

View File

@ -23,6 +23,9 @@
#include "keeshare/Signature.h" #include "keeshare/Signature.h"
#include "keys/PasswordKey.h" #include "keys/PasswordKey.h"
#include <QBuffer>
#include <QTextStream>
#if defined(WITH_XC_KEESHARE_SECURE) #if defined(WITH_XC_KEESHARE_SECURE)
#include <quazip.h> #include <quazip.h>
#include <quazipfile.h> #include <quazipfile.h>
@ -147,9 +150,8 @@ namespace
} }
QTextStream stream(&file); QTextStream stream(&file);
KeeShareSettings::Sign sign; KeeShareSettings::Sign sign;
auto sshKey = own.key.sshKey(); // TODO: check for false return
sshKey.openKey(QString()); Signature::create(bytes, own.key.key, sign.signature);
sign.signature = Signature::create(bytes, sshKey);
sign.certificate = own.certificate; sign.certificate = own.certificate;
stream << KeeShareSettings::Sign::serialize(sign); stream << KeeShareSettings::Sign::serialize(sign);
stream.flush(); stream.flush();

View File

@ -17,6 +17,8 @@
#ifndef KEEPASSXC_SHAREEXPORT_H #ifndef KEEPASSXC_SHAREEXPORT_H
#define KEEPASSXC_SHAREEXPORT_H #define KEEPASSXC_SHAREEXPORT_H
#include <QCoreApplication>
#include "keeshare/KeeShareSettings.h" #include "keeshare/KeeShareSettings.h"
#include "keeshare/ShareObserver.h" #include "keeshare/ShareObserver.h"

View File

@ -22,8 +22,10 @@
#include "keeshare/Signature.h" #include "keeshare/Signature.h"
#include "keys/PasswordKey.h" #include "keys/PasswordKey.h"
#include <QBuffer>
#include <QMessageBox> #include <QMessageBox>
#include <QPushButton> #include <QPushButton>
#include <QTextStream>
#if defined(WITH_XC_KEESHARE_SECURE) #if defined(WITH_XC_KEESHARE_SECURE)
#include <quazip.h> #include <quazip.h>
@ -52,55 +54,43 @@ namespace
KeeShareSettings::Certificate certificate; KeeShareSettings::Certificate certificate;
if (!sign.signature.isEmpty()) { if (!sign.signature.isEmpty()) {
certificate = sign.certificate; certificate = sign.certificate;
auto key = sign.certificate.sshKey(); if (!Signature::verify(data, sign.certificate.key, sign.signature)) {
key.openKey(QString());
const auto signer = Signature();
if (!signer.verify(data, sign.signature, key)) {
qCritical("Invalid signature for shared container %s.", qPrintable(reference.path)); qCritical("Invalid signature for shared container %s.", qPrintable(reference.path));
return {Invalid, KeeShareSettings::Certificate()}; return {Invalid, KeeShareSettings::Certificate()};
} }
if (ownCertificate.key == sign.certificate.key) { // Automatically trust your own certificate
if (ownCertificate == sign.certificate) {
return {Own, ownCertificate}; return {Own, ownCertificate};
} }
} }
enum Scope
{
Invalid,
Global,
Local
};
Scope scope = Invalid;
KeeShareSettings::Trust trusted = KeeShareSettings::Trust::Ask;
for (const auto& scopedCertificate : knownCertificates) { for (const auto& scopedCertificate : knownCertificates) {
if (scopedCertificate.certificate.key == certificate.key && scopedCertificate.path == reference.path) { if (scopedCertificate.certificate == certificate && scopedCertificate.path == reference.path) {
// Global scope is overwritten by local scope if (scopedCertificate.trust == KeeShareSettings::Trust::Trusted) {
scope = Global; return {TrustedForever, certificate};
trusted = scopedCertificate.trust; } else if (scopedCertificate.trust == KeeShareSettings::Trust::Untrusted) {
return {UntrustedForever, certificate};
} }
if (scopedCertificate.certificate.key == certificate.key && scopedCertificate.path == reference.path) { // Default to ask
scope = Local;
trusted = scopedCertificate.trust;
break; break;
} }
} }
if (scope != Invalid && trusted != KeeShareSettings::Trust::Ask) {
// we introduce now scopes if there is a global
return {trusted == KeeShareSettings::Trust::Trusted ? TrustedForever : UntrustedForever, certificate};
}
// Ask the user if they want to trust the certificate
QMessageBox warning; QMessageBox warning;
warning.setWindowTitle(ShareImport::tr("KeeShare Import"));
if (sign.signature.isEmpty()) { if (sign.signature.isEmpty()) {
warning.setIcon(QMessageBox::Warning); warning.setIcon(QMessageBox::Warning);
warning.setWindowTitle(ShareImport::tr("Import from container without signature")); warning.setText(ShareImport::tr("The source of the shared container cannot be verified because it is not "
warning.setText(ShareImport::tr("We cannot verify the source of the shared container because it is not "
"signed. Do you really want to import from %1?") "signed. Do you really want to import from %1?")
.arg(reference.path)); .arg(reference.path));
} else { } else {
warning.setIcon(QMessageBox::Question); warning.setIcon(QMessageBox::Question);
warning.setWindowTitle(ShareImport::tr("Import from container with certificate")); warning.setText(ShareImport::tr("Do you want to trust %1 with certificate fingerprint:\n%2\n%3")
warning.setText(ShareImport::tr("Do you want to trust %1 with the fingerprint of %2 from %3?") .arg(reference.path)
.arg(certificate.signer, certificate.fingerprint(), reference.path)); .arg(certificate.signer)
.arg(certificate.fingerprint()));
} }
auto untrustedOnce = warning.addButton(ShareImport::tr("Not this time"), QMessageBox::ButtonRole::NoRole); auto untrustedOnce = warning.addButton(ShareImport::tr("Not this time"), QMessageBox::ButtonRole::NoRole);
auto untrustedForever = warning.addButton(ShareImport::tr("Never"), QMessageBox::ButtonRole::NoRole); auto untrustedForever = warning.addButton(ShareImport::tr("Never"), QMessageBox::ButtonRole::NoRole);
@ -188,7 +178,7 @@ namespace
const auto trusted = const auto trusted =
trust.first == TrustedForever ? KeeShareSettings::Trust::Trusted : KeeShareSettings::Trust::Untrusted; trust.first == TrustedForever ? KeeShareSettings::Trust::Trusted : KeeShareSettings::Trust::Untrusted;
for (KeeShareSettings::ScopedCertificate& scopedCertificate : foreign.certificates) { for (KeeShareSettings::ScopedCertificate& scopedCertificate : foreign.certificates) {
if (scopedCertificate.certificate.key == trust.second.key && scopedCertificate.path == reference.path) { if (scopedCertificate.certificate == trust.second && scopedCertificate.path == reference.path) {
scopedCertificate.certificate.signer = trust.second.signer; scopedCertificate.certificate.signer = trust.second.signer;
scopedCertificate.path = reference.path; scopedCertificate.path = reference.path;
scopedCertificate.trust = trusted; scopedCertificate.trust = trusted;
@ -279,7 +269,7 @@ namespace
const auto trusted = const auto trusted =
trust.first == TrustedForever ? KeeShareSettings::Trust::Trusted : KeeShareSettings::Trust::Untrusted; trust.first == TrustedForever ? KeeShareSettings::Trust::Trusted : KeeShareSettings::Trust::Untrusted;
for (KeeShareSettings::ScopedCertificate& scopedCertificate : foreign.certificates) { for (KeeShareSettings::ScopedCertificate& scopedCertificate : foreign.certificates) {
if (scopedCertificate.certificate.key == trust.second.key && scopedCertificate.path == reference.path) { if (scopedCertificate.certificate == trust.second && scopedCertificate.path == reference.path) {
scopedCertificate.certificate.signer = trust.second.signer; scopedCertificate.certificate.signer = trust.second.signer;
scopedCertificate.path = reference.path; scopedCertificate.path = reference.path;
scopedCertificate.trust = trusted; scopedCertificate.trust = trusted;

View File

@ -17,6 +17,8 @@
#ifndef KEEPASSXC_SHAREIMPORT_H #ifndef KEEPASSXC_SHAREIMPORT_H
#define KEEPASSXC_SHAREIMPORT_H #define KEEPASSXC_SHAREIMPORT_H
#include <QCoreApplication>
#include "keeshare/ShareObserver.h" #include "keeshare/ShareObserver.h"
class ShareImport class ShareImport

View File

@ -25,6 +25,8 @@
#include "keeshare/ShareExport.h" #include "keeshare/ShareExport.h"
#include "keeshare/ShareImport.h" #include "keeshare/ShareImport.h"
#include <QDir>
namespace namespace
{ {
QString resolvePath(const QString& path, QSharedPointer<Database> database) QString resolvePath(const QString& path, QSharedPointer<Database> database)

View File

@ -16,245 +16,53 @@
*/ */
#include "Signature.h" #include "Signature.h"
#include "core/Tools.h"
#include "crypto/Crypto.h"
#include "crypto/CryptoHash.h"
#include "crypto/ssh/OpenSSHKey.h"
#include <QByteArray> #include "crypto/Random.h"
#include <gcrypt.h>
struct RSASigner bool Signature::create(const QByteArray& data, QSharedPointer<Botan::Private_Key> key, QString& signature)
{
gcry_error_t rc;
QString error;
void raiseError(const QString& message = QString())
{
if (message.isEmpty()) {
error = QString("%1/%2").arg(QString::fromLocal8Bit(gcry_strsource(rc)),
QString::fromLocal8Bit(gcry_strerror(rc)));
} else {
error = message;
}
}
RSASigner()
: rc(GPG_ERR_NO_ERROR)
{
}
QString sign(const QByteArray& data, const OpenSSHKey& key)
{
enum Index
{
N,
E,
D,
P,
Q,
U, // private key
R,
S, // signature
Data,
Key,
Sig
};
const QList<QByteArray> parts = key.privateParts();
if (parts.count() != 6) {
raiseError("Unsupported signing key");
return QString();
}
const QByteArray block = CryptoHash::hash(data, CryptoHash::Sha256);
Tools::Map<Index, gcry_mpi_t, &gcry_mpi_release> mpi;
Tools::Map<Index, gcry_sexp_t, &gcry_sexp_release> sexp;
const gcry_mpi_format format = GCRYMPI_FMT_USG;
rc = gcry_mpi_scan(&mpi[N], format, parts[0].data(), parts[0].size(), nullptr);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return QString();
}
rc = gcry_mpi_scan(&mpi[E], format, parts[1].data(), parts[1].size(), nullptr);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return QString();
}
rc = gcry_mpi_scan(&mpi[D], format, parts[2].data(), parts[2].size(), nullptr);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return QString();
}
rc = gcry_mpi_scan(&mpi[U], format, parts[3].data(), parts[3].size(), nullptr);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return QString();
}
rc = gcry_mpi_scan(&mpi[P], format, parts[4].data(), parts[4].size(), nullptr);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return QString();
}
rc = gcry_mpi_scan(&mpi[Q], format, parts[5].data(), parts[5].size(), nullptr);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return QString();
}
if (gcry_mpi_cmp(mpi[P], mpi[Q]) > 0) {
// see https://www.gnupg.org/documentation/manuals/gcrypt/RSA-key-parameters.html#RSA-key-parameters
gcry_mpi_swap(mpi[P], mpi[Q]);
gcry_mpi_invm(mpi[U], mpi[P], mpi[Q]);
}
rc = gcry_sexp_build(&sexp[Key],
NULL,
"(private-key (rsa (n %m) (e %m) (d %m) (p %m) (q %m) (u %m)))",
mpi[N],
mpi[E],
mpi[D],
mpi[P],
mpi[Q],
mpi[U]);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return QString();
}
rc = gcry_pk_testkey(sexp[Key]);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return QString();
}
rc = gcry_sexp_build(&sexp[Data], NULL, "(data (flags pkcs1) (hash sha256 %b))", block.size(), block.data());
// rc = gcry_sexp_build(&sexp[Data], NULL, "(data (flags raw) (value %b))", data.size(), data.data());
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return QString();
}
rc = gcry_pk_sign(&sexp[Sig], sexp[Data], sexp[Key]);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return QString();
}
sexp[S] = gcry_sexp_find_token(sexp[Sig], "s", 1);
mpi[S] = gcry_sexp_nth_mpi(sexp[S], 1, GCRYMPI_FMT_USG);
Tools::Buffer buffer;
rc = gcry_mpi_aprint(GCRYMPI_FMT_STD, &buffer.raw, &buffer.size, mpi[S]);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return QString();
}
return QString("rsa|%1").arg(QString::fromLatin1(buffer.content().toHex()));
}
bool verify(const QByteArray& data, const OpenSSHKey& key, const QString& signature)
{
const gcry_mpi_format format = GCRYMPI_FMT_USG;
enum MPI
{
N,
E, // public key
R,
S // signature
};
enum SEXP
{
Data,
Key,
Sig
};
const QList<QByteArray> parts = key.publicParts();
if (parts.count() != 2) {
raiseError("Unsupported verification key");
return false;
}
const QByteArray block = CryptoHash::hash(data, CryptoHash::Sha256);
Tools::Map<MPI, gcry_mpi_t, &gcry_mpi_release> mpi;
Tools::Map<SEXP, gcry_sexp_t, &gcry_sexp_release> sexp;
rc = gcry_mpi_scan(&mpi[E], format, parts[0].data(), parts[0].size(), nullptr);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return false;
}
rc = gcry_mpi_scan(&mpi[N], format, parts[1].data(), parts[1].size(), nullptr);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return false;
}
rc = gcry_sexp_build(&sexp[Key], NULL, "(public-key (rsa (n %m) (e %m)))", mpi[N], mpi[E]);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return false;
}
QRegExp extractor("rsa\\|([a-f0-9]+)", Qt::CaseInsensitive);
if (!extractor.exactMatch(signature) || extractor.captureCount() != 1) {
raiseError("Could not unpack signature parts");
return false;
}
const QByteArray sig_s = QByteArray::fromHex(extractor.cap(1).toLatin1());
rc = gcry_mpi_scan(&mpi[S], GCRYMPI_FMT_STD, sig_s.data(), sig_s.size(), nullptr);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return false;
}
rc = gcry_sexp_build(&sexp[Sig], NULL, "(sig-val (rsa (s %m)))", mpi[S]);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return false;
}
rc = gcry_sexp_build(&sexp[Data], NULL, "(data (flags pkcs1) (hash sha256 %b))", block.size(), block.data());
// rc = gcry_sexp_build(&sexp[Data], NULL, "(data (flags raw) (value %b))", data.size(), data.data());
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return false;
}
rc = gcry_pk_verify(sexp[Sig], sexp[Data], sexp[Key]);
if (rc != GPG_ERR_NO_ERROR && rc != GPG_ERR_BAD_SIGNATURE) {
raiseError();
return false;
}
return rc != GPG_ERR_BAD_SIGNATURE;
}
};
QString Signature::create(const QByteArray& data, const OpenSSHKey& key)
{ {
// TODO HNH: currently we publish the signature in our own non-standard format - it would // TODO HNH: currently we publish the signature in our own non-standard format - it would
// be better to use a standard format (like ASN1 - but this would be more easy // be better to use a standard format (like ASN1 - but this would be more easy
// when we integrate a proper library) // when we integrate a proper library)
// Even more, we could publish standard self signed certificates with the container // Even more, we could publish standard self signed certificates with the container
// instead of the custom certificates // instead of the custom certificates
if (key.type() == "ssh-rsa") { if (key->algo_name() == "RSA") {
RSASigner signer; try {
QString result = signer.sign(data, key); Botan::PK_Signer signer(*key, "EMSA3(SHA-256)");
if (signer.rc != GPG_ERR_NO_ERROR) { signer.update(reinterpret_cast<const uint8_t*>(data.constData()), data.size());
::qWarning() << signer.error; auto s = signer.signature(*randomGen()->getRng());
}
return result;
}
::qWarning() << "Unsupported Public/Private key format";
return QString();
}
bool Signature::verify(const QByteArray& data, const QString& signature, const OpenSSHKey& key) auto hex = QByteArray(reinterpret_cast<char*>(s.data()), s.size()).toHex();
{ signature = QString("rsa|%1").arg(QString::fromLatin1(hex));
if (key.type() == "ssh-rsa") { return true;
RSASigner signer; } catch (std::exception& e) {
bool result = signer.verify(data, key, signature); qWarning("KeeShare: Failed to sign data: %s", e.what());
if (signer.rc != GPG_ERR_NO_ERROR) { return false;
::qWarning() << signer.error; }
} }
return result; qWarning("Unsupported Public/Private key format");
} return false;
::qWarning() << "Unsupported Public/Private key format"; }
bool Signature::verify(const QByteArray& data, QSharedPointer<Botan::Public_Key> key, const QString& signature)
{
if (key && key->algo_name() == "RSA") {
QRegExp extractor("rsa\\|([a-f0-9]+)", Qt::CaseInsensitive);
if (!extractor.exactMatch(signature) || extractor.captureCount() != 1) {
qWarning("Could not unpack signature parts");
return false;
}
const QByteArray sig_s = QByteArray::fromHex(extractor.cap(1).toLatin1());
try {
Botan::PK_Verifier verifier(*key, "EMSA3(SHA-256)");
verifier.update(reinterpret_cast<const uint8_t*>(data.constData()), data.size());
return verifier.check_signature(reinterpret_cast<const uint8_t*>(sig_s.constData()), sig_s.size());
} catch (std::exception& e) {
qWarning("KeeShare: Failed to verify signature: %s", e.what());
return false;
}
}
qWarning("Unsupported Public/Private key format");
return false; return false;
} }

View File

@ -18,17 +18,14 @@
#ifndef KEEPASSXC_SIGNATURE_H #ifndef KEEPASSXC_SIGNATURE_H
#define KEEPASSXC_SIGNATURE_H #define KEEPASSXC_SIGNATURE_H
#include <QSharedPointer>
#include <QString> #include <QString>
#include <gcrypt.h> #include <botan/pubkey.h>
class QByteArray; namespace Signature
class OpenSSHKey;
class Signature
{ {
public: bool create(const QByteArray& data, QSharedPointer<Botan::Private_Key> key, QString& signature);
static QString create(const QByteArray& data, const OpenSSHKey& key); bool verify(const QByteArray& data, QSharedPointer<Botan::Public_Key> key, const QString& signature);
static bool verify(const QByteArray& data, const QString& signature, const OpenSSHKey& key); }; // namespace Signature
};
#endif // KEEPASSXC_SIGNATURE_H #endif // KEEPASSXC_SIGNATURE_H

View File

@ -23,7 +23,6 @@
#include "core/Group.h" #include "core/Group.h"
#include "core/Metadata.h" #include "core/Metadata.h"
#include "core/Resources.h" #include "core/Resources.h"
#include "crypto/ssh/OpenSSHKey.h"
#include "gui/FileDialog.h" #include "gui/FileDialog.h"
#include "keeshare/KeeShare.h" #include "keeshare/KeeShare.h"

View File

@ -21,6 +21,7 @@
#include <QByteArray> #include <QByteArray>
#include <QUuid> #include <QUuid>
#include <botan/secmem.h>
class ChallengeResponseKey class ChallengeResponseKey
{ {
@ -31,9 +32,13 @@ public:
} }
virtual ~ChallengeResponseKey() = default; virtual ~ChallengeResponseKey() = default;
virtual QByteArray rawKey() const = 0;
virtual bool challenge(const QByteArray& challenge) = 0; virtual bool challenge(const QByteArray& challenge) = 0;
virtual QUuid uuid() const
Botan::secure_vector<char>& rawKey()
{
return m_key;
}
QUuid uuid() const
{ {
return m_uuid; return m_uuid;
} }
@ -44,6 +49,7 @@ public:
protected: protected:
QString m_error; QString m_error;
Botan::secure_vector<char> m_key;
private: private:
Q_DISABLE_COPY(ChallengeResponseKey); Q_DISABLE_COPY(ChallengeResponseKey);

View File

@ -143,7 +143,7 @@ bool CompositeKey::challenge(const QByteArray& seed, QByteArray& result, QString
qWarning() << "Failed to issue challenge: " << key->error(); qWarning() << "Failed to issue challenge: " << key->error();
return false; return false;
} }
cryptoHash.addData(key->rawKey()); cryptoHash.addData(key->rawKey().data());
} }
result = cryptoHash.result(); result = cryptoHash.result();

View File

@ -24,29 +24,16 @@
#include <QFile> #include <QFile>
#include <algorithm>
#include <cstring>
#include <gcrypt.h>
#include <sodium.h>
QUuid FileKey::UUID("a584cbc4-c9b4-437e-81bb-362ca9709273"); QUuid FileKey::UUID("a584cbc4-c9b4-437e-81bb-362ca9709273");
constexpr int FileKey::SHA256_SIZE; constexpr int FileKey::SHA256_SIZE;
FileKey::FileKey() FileKey::FileKey()
: Key(UUID) : Key(UUID)
, m_key(static_cast<char*>(gcry_malloc_secure(SHA256_SIZE))) , m_key(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. * Read key file from device while trying to detect its file format.
* *
@ -169,10 +156,7 @@ bool FileKey::load(const QString& fileName, QString* errorMsg)
*/ */
QByteArray FileKey::rawKey() const QByteArray FileKey::rawKey() const
{ {
if (!m_key) { return QByteArray(m_key.data(), m_key.size());
return {};
}
return QByteArray::fromRawData(m_key, SHA256_SIZE);
} }
/** /**
@ -225,7 +209,7 @@ void FileKey::createXMLv2(QIODevice* device, int size)
} }
w.writeCharacters(QChar(key[i])); w.writeCharacters(QChar(key[i]));
} }
sodium_memzero(key.data(), static_cast<std::size_t>(key.capacity())); Botan::secure_scrub_memory(key.data(), static_cast<std::size_t>(key.capacity()));
w.writeCharacters("\n "); w.writeCharacters("\n ");
w.writeEndElement(); w.writeEndElement();
@ -315,12 +299,12 @@ bool FileKey::loadXml(QIODevice* device, QString* errorMsg)
while (!xmlReader.error() && xmlReader.readNextStartElement()) { while (!xmlReader.error() && xmlReader.readNextStartElement()) {
if (xmlReader.name() == "Data") { if (xmlReader.name() == "Data") {
keyFileData.hash = QByteArray::fromHex(xmlReader.attributes().value("Hash").toLatin1()); keyFileData.hash = QByteArray::fromHex(xmlReader.attributes().value("Hash").toLatin1());
QByteArray rawData = xmlReader.readElementText().simplified().replace(" ", "").toLatin1(); keyFileData.data = xmlReader.readElementText().simplified().replace(" ", "").toLatin1();
if (keyFileData.version.startsWith("1.0") && Tools::isBase64(rawData)) { if (keyFileData.version.startsWith("1.0") && Tools::isBase64(keyFileData.data)) {
keyFileData.data = QByteArray::fromBase64(rawData); keyFileData.data = QByteArray::fromBase64(keyFileData.data);
} else if (keyFileData.version == "2.0" && Tools::isHex(rawData)) { } else if (keyFileData.version == "2.0" && Tools::isHex(keyFileData.data)) {
keyFileData.data = QByteArray::fromHex(rawData); keyFileData.data = QByteArray::fromHex(keyFileData.data);
CryptoHash hash(CryptoHash::Sha256); CryptoHash hash(CryptoHash::Sha256);
hash.addData(keyFileData.data); hash.addData(keyFileData.data);
@ -337,8 +321,6 @@ bool FileKey::loadXml(QIODevice* device, QString* errorMsg)
} }
return false; return false;
} }
sodium_memzero(rawData.data(), static_cast<std::size_t>(rawData.capacity()));
} }
} }
} }
@ -346,11 +328,11 @@ bool FileKey::loadXml(QIODevice* device, QString* errorMsg)
bool ok = false; bool ok = false;
if (!xmlReader.error() && !keyFileData.data.isEmpty()) { if (!xmlReader.error() && !keyFileData.data.isEmpty()) {
std::memcpy(m_key, keyFileData.data.data(), std::min(SHA256_SIZE, keyFileData.data.size())); std::memcpy(m_key.data(), keyFileData.data.data(), std::min(SHA256_SIZE, keyFileData.data.size()));
ok = true; ok = true;
} }
sodium_memzero(keyFileData.data.data(), static_cast<std::size_t>(keyFileData.data.capacity())); Botan::secure_scrub_memory(keyFileData.data.data(), static_cast<std::size_t>(keyFileData.data.capacity()));
return ok; return ok;
} }
@ -368,13 +350,12 @@ bool FileKey::loadBinary(QIODevice* device)
return false; return false;
} }
QByteArray data; Botan::secure_vector<char> data(32);
if (!Tools::readAllFromDevice(device, data) || data.size() != 32) { if (device->read(data.data(), 32) != 32 || !device->atEnd()) {
return false; return false;
} }
std::memcpy(m_key, data.data(), std::min(SHA256_SIZE, data.size())); m_key = data;
sodium_memzero(data.data(), static_cast<std::size_t>(data.capacity()));
m_type = FixedBinary; m_type = FixedBinary;
return true; return true;
} }
@ -401,15 +382,13 @@ bool FileKey::loadHex(QIODevice* device)
return false; return false;
} }
QByteArray key = QByteArray::fromHex(data); data = QByteArray::fromHex(data);
sodium_memzero(data.data(), static_cast<std::size_t>(data.capacity())); if (data.size() != 32) {
if (key.size() != 32) {
return false; return false;
} }
std::memcpy(m_key, key.data(), std::min(SHA256_SIZE, key.size())); std::memcpy(m_key.data(), data.data(), std::min(SHA256_SIZE, data.size()));
sodium_memzero(key.data(), static_cast<std::size_t>(key.capacity())); Botan::secure_scrub_memory(data.data(), static_cast<std::size_t>(data.capacity()));
m_type = FixedBinaryHex; m_type = FixedBinaryHex;
return true; return true;
@ -433,9 +412,9 @@ bool FileKey::loadHashed(QIODevice* device)
cryptoHash.addData(buffer); cryptoHash.addData(buffer);
} while (!buffer.isEmpty()); } while (!buffer.isEmpty());
auto result = cryptoHash.result(); buffer = cryptoHash.result();
std::memcpy(m_key, result.data(), std::min(SHA256_SIZE, result.size())); std::memcpy(m_key.data(), buffer.data(), std::min(SHA256_SIZE, buffer.size()));
sodium_memzero(result.data(), static_cast<std::size_t>(result.capacity())); Botan::secure_scrub_memory(buffer.data(), static_cast<std::size_t>(buffer.capacity()));
m_type = Hashed; m_type = Hashed;
return true; return true;

View File

@ -20,6 +20,7 @@
#define KEEPASSX_FILEKEY_H #define KEEPASSX_FILEKEY_H
#include <QXmlStreamReader> #include <QXmlStreamReader>
#include <botan/secmem.h>
#include "keys/Key.h" #include "keys/Key.h"
@ -41,7 +42,7 @@ public:
}; };
FileKey(); FileKey();
~FileKey() override; ~FileKey() override = default;
bool load(QIODevice* device, QString* errorMsg = nullptr); bool load(QIODevice* device, QString* errorMsg = nullptr);
bool load(const QString& fileName, QString* errorMsg = nullptr); bool load(const QString& fileName, QString* errorMsg = nullptr);
QByteArray rawKey() const override; QByteArray rawKey() const override;
@ -58,7 +59,7 @@ private:
bool loadHex(QIODevice* device); bool loadHex(QIODevice* device);
bool loadHashed(QIODevice* device); bool loadHashed(QIODevice* device);
char* m_key = nullptr; Botan::secure_vector<char> m_key;
Type m_type = None; Type m_type = None;
}; };

View File

@ -19,9 +19,9 @@
#include "core/Tools.h" #include "core/Tools.h"
#include "crypto/CryptoHash.h" #include "crypto/CryptoHash.h"
#include <algorithm> #include <algorithm>
#include <cstring> #include <cstring>
#include <gcrypt.h>
QUuid PasswordKey::UUID("77e90411-303a-43f2-b773-853b05635ead"); QUuid PasswordKey::UUID("77e90411-303a-43f2-b773-853b05635ead");
@ -29,31 +29,23 @@ constexpr int PasswordKey::SHA256_SIZE;
PasswordKey::PasswordKey() PasswordKey::PasswordKey()
: Key(UUID) : Key(UUID)
, m_key(static_cast<char*>(gcry_malloc_secure(SHA256_SIZE))) , m_key(SHA256_SIZE)
{ {
} }
PasswordKey::PasswordKey(const QString& password) PasswordKey::PasswordKey(const QString& password)
: Key(UUID) : Key(UUID)
, m_key(static_cast<char*>(gcry_malloc_secure(SHA256_SIZE))) , m_key(SHA256_SIZE)
{ {
setPassword(password); setPassword(password);
} }
PasswordKey::~PasswordKey()
{
if (m_key) {
gcry_free(m_key);
m_key = nullptr;
}
}
QByteArray PasswordKey::rawKey() const QByteArray PasswordKey::rawKey() const
{ {
if (!m_isInitialized) { if (!m_isInitialized) {
return {}; return {};
} }
return QByteArray::fromRawData(m_key, SHA256_SIZE); return QByteArray(m_key.data(), m_key.size());
} }
void PasswordKey::setPassword(const QString& password) void PasswordKey::setPassword(const QString& password)
@ -64,7 +56,7 @@ void PasswordKey::setPassword(const QString& password)
void PasswordKey::setHash(const QByteArray& hash) void PasswordKey::setHash(const QByteArray& hash)
{ {
Q_ASSERT(hash.size() == SHA256_SIZE); Q_ASSERT(hash.size() == SHA256_SIZE);
std::memcpy(m_key, hash.data(), std::min(SHA256_SIZE, hash.size())); std::memcpy(m_key.data(), hash.data(), std::min(SHA256_SIZE, hash.size()));
m_isInitialized = true; m_isInitialized = true;
} }

View File

@ -18,6 +18,8 @@
#ifndef KEEPASSX_PASSWORDKEY_H #ifndef KEEPASSX_PASSWORDKEY_H
#define KEEPASSX_PASSWORDKEY_H #define KEEPASSX_PASSWORDKEY_H
#include <botan/secmem.h>
#include <QSharedPointer> #include <QSharedPointer>
#include <QString> #include <QString>
@ -30,7 +32,7 @@ public:
PasswordKey(); PasswordKey();
explicit PasswordKey(const QString& password); explicit PasswordKey(const QString& password);
~PasswordKey() override; ~PasswordKey() override = default;
QByteArray rawKey() const override; QByteArray rawKey() const override;
void setPassword(const QString& password); void setPassword(const QString& password);
void setHash(const QByteArray& hash); void setHash(const QByteArray& hash);
@ -40,7 +42,7 @@ public:
private: private:
static constexpr int SHA256_SIZE = 32; static constexpr int SHA256_SIZE = 32;
char* m_key = nullptr; Botan::secure_vector<char> m_key;
bool m_isInitialized = false; bool m_isInitialized = false;
}; };

View File

@ -31,10 +31,6 @@
#include <QXmlStreamReader> #include <QXmlStreamReader>
#include <QtConcurrent> #include <QtConcurrent>
#include <cstring>
#include <gcrypt.h>
#include <sodium.h>
QUuid YkChallengeResponseKey::UUID("e092495c-e77d-498b-84a1-05ae0d955508"); QUuid YkChallengeResponseKey::UUID("e092495c-e77d-498b-84a1-05ae0d955508");
YkChallengeResponseKey::YkChallengeResponseKey(YubiKeySlot keySlot) YkChallengeResponseKey::YkChallengeResponseKey(YubiKeySlot keySlot)
@ -43,37 +39,15 @@ YkChallengeResponseKey::YkChallengeResponseKey(YubiKeySlot keySlot)
{ {
} }
YkChallengeResponseKey::~YkChallengeResponseKey()
{
if (m_key) {
gcry_free(m_key);
m_keySize = 0;
m_key = nullptr;
}
}
QByteArray YkChallengeResponseKey::rawKey() const
{
return QByteArray::fromRawData(m_key, static_cast<int>(m_keySize));
}
bool YkChallengeResponseKey::challenge(const QByteArray& challenge) bool YkChallengeResponseKey::challenge(const QByteArray& challenge)
{ {
m_error.clear(); m_error.clear();
QByteArray key;
auto result = auto result =
AsyncTask::runAndWaitForFuture([&] { return YubiKey::instance()->challenge(m_keySlot, challenge, key); }); AsyncTask::runAndWaitForFuture([&] { return YubiKey::instance()->challenge(m_keySlot, challenge, m_key); });
if (result == YubiKey::SUCCESS) { 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()));
} else {
// Record the error message // Record the error message
m_key.clear();
m_error = YubiKey::instance()->errorMessage(); m_error = YubiKey::instance()->errorMessage();
} }

View File

@ -22,24 +22,17 @@
#include "keys/ChallengeResponseKey.h" #include "keys/ChallengeResponseKey.h"
#include "keys/drivers/YubiKey.h" #include "keys/drivers/YubiKey.h"
#include <QObject> class YkChallengeResponseKey : public ChallengeResponseKey
class YkChallengeResponseKey : public QObject, public ChallengeResponseKey
{ {
Q_OBJECT
public: public:
static QUuid UUID; static QUuid UUID;
explicit YkChallengeResponseKey(YubiKeySlot keySlot = {}); explicit YkChallengeResponseKey(YubiKeySlot keySlot = {});
~YkChallengeResponseKey() override; ~YkChallengeResponseKey() override = default;
QByteArray rawKey() const override;
bool challenge(const QByteArray& challenge) override; bool challenge(const QByteArray& challenge) override;
private: private:
char* m_key = nullptr;
std::size_t m_keySize = 0;
YubiKeySlot m_keySlot; YubiKeySlot m_keySlot;
}; };

View File

@ -1,52 +0,0 @@
/*
* 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 "keys/YkChallengeResponseKeyCLI.h"
#include "keys/drivers/YubiKey.h"
#include "core/Tools.h"
#include "crypto/CryptoHash.h"
#include "crypto/Random.h"
#include <QFile>
QUuid YkChallengeResponseKeyCLI::UUID("e2be77c0-c810-417a-8437-32f41d00bd1d");
YkChallengeResponseKeyCLI::YkChallengeResponseKeyCLI(YubiKeySlot keySlot, QString interactionMessage, QTextStream& out)
: ChallengeResponseKey(UUID)
, m_keySlot(keySlot)
, m_interactionMessage(interactionMessage)
, m_out(out.device())
{
connect(YubiKey::instance(), SIGNAL(userInteractionRequest()), SLOT(showInteractionMessage()));
}
void YkChallengeResponseKeyCLI::showInteractionMessage()
{
m_out << m_interactionMessage << "\n\n" << flush;
}
QByteArray YkChallengeResponseKeyCLI::rawKey() const
{
return m_key;
}
bool YkChallengeResponseKeyCLI::challenge(const QByteArray& challenge)
{
auto result = YubiKey::instance()->challenge(m_keySlot, challenge, m_key);
return result == YubiKey::SUCCESS;
}

View File

@ -1,51 +0,0 @@
/*
* 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/>.
*/
#ifndef KEEPASSX_YK_CHALLENGERESPONSEKEYCLI_H
#define KEEPASSX_YK_CHALLENGERESPONSEKEYCLI_H
#include "core/Global.h"
#include "keys/ChallengeResponseKey.h"
#include "keys/drivers/YubiKey.h"
#include <QObject>
#include <QSharedPointer>
#include <QTextStream>
class YkChallengeResponseKeyCLI : public QObject, public ChallengeResponseKey
{
Q_OBJECT
public:
static QUuid UUID;
explicit YkChallengeResponseKeyCLI(YubiKeySlot keySlot, QString interactionMessage, QTextStream& out);
QByteArray rawKey() const override;
bool challenge(const QByteArray& challenge) override;
private slots:
void showInteractionMessage();
private:
QByteArray m_key;
YubiKeySlot m_keySlot;
QString m_interactionMessage;
QTextStream m_out;
};
#endif // KEEPASSX_YK_CHALLENGERESPONSEKEYCLI_H

View File

@ -265,7 +265,7 @@ bool YubiKey::testChallenge(YubiKeySlot slot, bool* wouldBlock)
bool YubiKey::performTestChallenge(void* key, int slot, bool* wouldBlock) bool YubiKey::performTestChallenge(void* key, int slot, bool* wouldBlock)
{ {
auto chall = randomGen()->randomArray(1); auto chall = randomGen()->randomArray(1);
QByteArray resp; Botan::secure_vector<char> resp;
auto ret = performChallenge(static_cast<YK_KEY*>(key), slot, false, chall, resp); auto ret = performChallenge(static_cast<YK_KEY*>(key), slot, false, chall, resp);
if (ret == SUCCESS || ret == WOULDBLOCK) { if (ret == SUCCESS || ret == WOULDBLOCK) {
if (wouldBlock) { if (wouldBlock) {
@ -285,7 +285,8 @@ bool YubiKey::performTestChallenge(void* key, int slot, bool* wouldBlock)
* @param response response output from YubiKey * @param response response output from YubiKey
* @return challenge result * @return challenge result
*/ */
YubiKey::ChallengeResult YubiKey::challenge(YubiKeySlot slot, const QByteArray& challenge, QByteArray& response) YubiKey::ChallengeResult
YubiKey::challenge(YubiKeySlot slot, const QByteArray& challenge, Botan::secure_vector<char>& response)
{ {
m_error.clear(); m_error.clear();
if (!m_initialized) { if (!m_initialized) {
@ -318,8 +319,11 @@ YubiKey::ChallengeResult YubiKey::challenge(YubiKeySlot slot, const QByteArray&
return ret; return ret;
} }
YubiKey::ChallengeResult YubiKey::ChallengeResult YubiKey::performChallenge(void* key,
YubiKey::performChallenge(void* key, int slot, bool mayBlock, const QByteArray& challenge, QByteArray& response) int slot,
bool mayBlock,
const QByteArray& challenge,
Botan::secure_vector<char>& response)
{ {
m_error.clear(); m_error.clear();
int yk_cmd = (slot == 1) ? SLOT_CHAL_HMAC1 : SLOT_CHAL_HMAC2; int yk_cmd = (slot == 1) ? SLOT_CHAL_HMAC1 : SLOT_CHAL_HMAC2;

View File

@ -23,6 +23,7 @@
#include <QMutex> #include <QMutex>
#include <QObject> #include <QObject>
#include <QTimer> #include <QTimer>
#include <botan/secmem.h>
typedef QPair<unsigned int, int> YubiKeySlot; typedef QPair<unsigned int, int> YubiKeySlot;
Q_DECLARE_METATYPE(YubiKeySlot); Q_DECLARE_METATYPE(YubiKeySlot);
@ -50,7 +51,7 @@ public:
QList<YubiKeySlot> foundKeys(); QList<YubiKeySlot> foundKeys();
QString getDisplayName(YubiKeySlot slot); QString getDisplayName(YubiKeySlot slot);
ChallengeResult challenge(YubiKeySlot slot, const QByteArray& challenge, QByteArray& response); ChallengeResult challenge(YubiKeySlot slot, const QByteArray& challenge, Botan::secure_vector<char>& response);
bool testChallenge(YubiKeySlot slot, bool* wouldBlock = nullptr); bool testChallenge(YubiKeySlot slot, bool* wouldBlock = nullptr);
QString errorMessage(); QString errorMessage();
@ -86,8 +87,11 @@ private:
static YubiKey* m_instance; static YubiKey* m_instance;
ChallengeResult ChallengeResult performChallenge(void* key,
performChallenge(void* key, int slot, bool mayBlock, const QByteArray& challenge, QByteArray& response); int slot,
bool mayBlock,
const QByteArray& challenge,
Botan::secure_vector<char>& response);
bool performTestChallenge(void* key, int slot, bool* wouldBlock); bool performTestChallenge(void* key, int slot, bool* wouldBlock);
QHash<unsigned int, QList<QPair<int, QString>>> m_foundKeys; QHash<unsigned int, QList<QPair<int, QString>>> m_foundKeys;

View File

@ -21,7 +21,7 @@ if(WITH_XC_BROWSER)
# Alloc must be defined in a static library to prevent clashing with clang ASAN definitions # Alloc must be defined in a static library to prevent clashing with clang ASAN definitions
add_library(proxy_alloc STATIC ../core/Alloc.cpp) add_library(proxy_alloc STATIC ../core/Alloc.cpp)
target_link_libraries(proxy_alloc PRIVATE Qt5::Core ${sodium_LIBRARY_RELEASE}) target_link_libraries(proxy_alloc PRIVATE Qt5::Core ${BOTAN2_LIBRARIES})
add_executable(keepassxc-proxy ${proxy_SOURCES}) add_executable(keepassxc-proxy ${proxy_SOURCES})
target_link_libraries(keepassxc-proxy proxy_alloc Qt5::Core Qt5::Network) target_link_libraries(keepassxc-proxy proxy_alloc Qt5::Core Qt5::Network)

View File

@ -17,9 +17,7 @@
*/ */
#include "ASN1Key.h" #include "ASN1Key.h"
#include "crypto/ssh/BinaryStream.h" #include "BinaryStream.h"
#include <gcrypt.h>
namespace namespace
{ {
@ -55,16 +53,6 @@ namespace
return true; return true;
} }
bool parsePublicHeader(BinaryStream& stream)
{
quint8 tag;
quint32 len;
nextTag(stream, tag, len);
return (tag == TAG_SEQUENCE);
}
bool parsePrivateHeader(BinaryStream& stream, quint8 wantedType) bool parsePrivateHeader(BinaryStream& stream, quint8 wantedType)
{ {
quint8 tag; quint8 tag;
@ -103,27 +91,6 @@ namespace
stream.read(target); stream.read(target);
return true; return true;
} }
QByteArray calculateIqmp(QByteArray& bap, QByteArray& baq)
{
gcry_mpi_t u, p, q;
QByteArray iqmp_hex;
u = gcry_mpi_snew(bap.length() * 8);
gcry_mpi_scan(&p, GCRYMPI_FMT_HEX, bap.toHex().data(), 0, nullptr);
gcry_mpi_scan(&q, GCRYMPI_FMT_HEX, baq.toHex().data(), 0, nullptr);
mpi_invm(u, q, p);
iqmp_hex.resize(bap.length() * 2);
gcry_mpi_print(GCRYMPI_FMT_HEX, reinterpret_cast<unsigned char*>(iqmp_hex.data()), iqmp_hex.size(), nullptr, u);
gcry_mpi_release(u);
gcry_mpi_release(p);
gcry_mpi_release(q);
return QByteArray::fromHex(QString(iqmp_hex).toLatin1());
}
} // namespace } // namespace
bool ASN1Key::parseDSA(QByteArray& ba, OpenSSHKey& key) bool ASN1Key::parseDSA(QByteArray& ba, OpenSSHKey& key)
@ -161,34 +128,7 @@ bool ASN1Key::parseDSA(QByteArray& ba, OpenSSHKey& key)
return true; return true;
} }
bool ASN1Key::parsePublicRSA(QByteArray& ba, OpenSSHKey& key) bool ASN1Key::parseRSA(QByteArray& ba, OpenSSHKey& key)
{
BinaryStream stream(&ba);
if (!parsePublicHeader(stream)) {
return false;
}
QByteArray n, e;
readInt(stream, n);
readInt(stream, e);
QList<QByteArray> publicData;
publicData.append(e);
publicData.append(n);
QList<QByteArray> privateData;
privateData.append(n);
privateData.append(e);
key.setType("ssh-rsa");
key.setPublicData(publicData);
key.setPrivateData(privateData);
key.setComment("");
return true;
}
bool ASN1Key::parsePrivateRSA(QByteArray& ba, OpenSSHKey& key)
{ {
BinaryStream stream(&ba); BinaryStream stream(&ba);
@ -206,6 +146,7 @@ bool ASN1Key::parsePrivateRSA(QByteArray& ba, OpenSSHKey& key)
readInt(stream, dq); readInt(stream, dq);
readInt(stream, qinv); readInt(stream, qinv);
// Note: To properly calculate the key fingerprint, e and n are reversed per RFC 4253
QList<QByteArray> publicData; QList<QByteArray> publicData;
publicData.append(e); publicData.append(e);
publicData.append(n); publicData.append(n);
@ -214,7 +155,7 @@ bool ASN1Key::parsePrivateRSA(QByteArray& ba, OpenSSHKey& key)
privateData.append(n); privateData.append(n);
privateData.append(e); privateData.append(e);
privateData.append(d); privateData.append(d);
privateData.append(calculateIqmp(p, q)); privateData.append(qinv);
privateData.append(p); privateData.append(p);
privateData.append(q); privateData.append(q);

View File

@ -25,8 +25,7 @@
namespace ASN1Key namespace ASN1Key
{ {
bool parseDSA(QByteArray& ba, OpenSSHKey& key); bool parseDSA(QByteArray& ba, OpenSSHKey& key);
bool parsePrivateRSA(QByteArray& ba, OpenSSHKey& key); bool parseRSA(QByteArray& ba, OpenSSHKey& key);
bool parsePublicRSA(QByteArray& ba, OpenSSHKey& key);
} // namespace ASN1Key } // namespace ASN1Key
#endif // KEEPASSXC_ASN1KEY_H #endif // KEEPASSXC_ASN1KEY_H

View File

@ -4,10 +4,13 @@ if(WITH_XC_SSHAGENT)
set(sshagent_SOURCES set(sshagent_SOURCES
AgentSettingsPage.cpp AgentSettingsPage.cpp
AgentSettingsWidget.cpp AgentSettingsWidget.cpp
ASN1Key.cpp
BinaryStream.cpp
KeeAgentSettings.cpp KeeAgentSettings.cpp
OpenSSHKey.cpp
SSHAgent.cpp SSHAgent.cpp
) )
add_library(sshagent STATIC ${sshagent_SOURCES}) add_library(sshagent STATIC ${sshagent_SOURCES})
target_link_libraries(sshagent Qt5::Core Qt5::Widgets Qt5::Network ${GCRYPT_LIBRARIES} ${crypto_ssh_LIB}) target_link_libraries(sshagent Qt5::Core Qt5::Widgets Qt5::Network)
endif() endif()

View File

@ -455,7 +455,7 @@ bool KeeAgentSettings::toOpenSSHKey(const QString& username,
return false; return false;
} }
if (key.encrypted() && (decrypt || key.publicParts().isEmpty())) { if (key.encrypted() && decrypt) {
if (!key.openKey(password)) { if (!key.openKey(password)) {
m_error = key.errorString(); m_error = key.errorString();
return false; return false;

View File

@ -19,9 +19,9 @@
#ifndef KEEAGENTSETTINGS_H #ifndef KEEAGENTSETTINGS_H
#define KEEAGENTSETTINGS_H #define KEEAGENTSETTINGS_H
#include "OpenSSHKey.h"
#include "core/Entry.h" #include "core/Entry.h"
#include "core/EntryAttachments.h" #include "core/EntryAttachments.h"
#include "crypto/ssh/OpenSSHKey.h"
#include <QXmlStreamReader> #include <QXmlStreamReader>
#include <QtCore> #include <QtCore>

View File

@ -18,193 +18,21 @@
#include "OpenSSHKey.h" #include "OpenSSHKey.h"
#include "ASN1Key.h"
#include "BinaryStream.h"
#include "core/Tools.h" #include "core/Tools.h"
#include "crypto/SymmetricCipher.h" #include "crypto/SymmetricCipher.h"
#include "crypto/ssh/ASN1Key.h"
#include "crypto/ssh/BinaryStream.h"
#include <QCryptographicHash> #include <QCryptographicHash>
#include <QRegularExpression> #include <QRegularExpression>
#include <QStringList> #include <QStringList>
#include <gcrypt.h> #include <botan/pwdhash.h>
const QString OpenSSHKey::TYPE_DSA_PRIVATE = "DSA PRIVATE KEY"; const QString OpenSSHKey::TYPE_DSA_PRIVATE = "DSA PRIVATE KEY";
const QString OpenSSHKey::TYPE_RSA_PRIVATE = "RSA PRIVATE KEY"; const QString OpenSSHKey::TYPE_RSA_PRIVATE = "RSA PRIVATE KEY";
const QString OpenSSHKey::TYPE_RSA_PUBLIC = "RSA PUBLIC KEY";
const QString OpenSSHKey::TYPE_OPENSSH_PRIVATE = "OPENSSH PRIVATE KEY"; const QString OpenSSHKey::TYPE_OPENSSH_PRIVATE = "OPENSSH PRIVATE KEY";
namespace
{
QPair<QString, QList<QByteArray>> binaryDeserialize(const QByteArray& serialized)
{
if (serialized.isEmpty()) {
return {};
}
QBuffer buffer;
buffer.setData(serialized);
buffer.open(QBuffer::ReadOnly);
BinaryStream stream(&buffer);
QString type;
stream.readString(type);
QByteArray temp;
QList<QByteArray> data;
while (stream.readString(temp)) {
data << temp;
}
return ::qMakePair(type, data);
}
QByteArray binarySerialize(const QString& type, const QList<QByteArray>& data)
{
if (type.isEmpty() && data.isEmpty()) {
return {};
}
QByteArray buffer;
BinaryStream stream(&buffer);
stream.writeString(type);
for (const QByteArray& part : data) {
stream.writeString(part);
}
return buffer;
}
} // namespace
// bcrypt_pbkdf.cpp
int bcrypt_pbkdf(const QByteArray& pass, const QByteArray& salt, QByteArray& key, quint32 rounds);
OpenSSHKey OpenSSHKey::generate(bool secure)
{
enum Index
{
Params,
CombinedKey,
PrivateKey,
PublicKey,
Private_N,
Private_E,
Private_D,
Private_P,
Private_Q,
Private_U, // private key
Public_N,
Public_E,
};
Tools::Map<Index, gcry_mpi_t, &gcry_mpi_release> mpi;
Tools::Map<Index, gcry_sexp_t, &gcry_sexp_release> sexp;
gcry_error_t rc = GPG_ERR_NO_ERROR;
rc = gcry_sexp_build(&sexp[Params],
NULL,
secure ? "(genkey (rsa (nbits 4:2048)))" : "(genkey (rsa (transient-key) (nbits 4:2048)))");
if (rc != GPG_ERR_NO_ERROR) {
qWarning() << "Could not create ssh key" << gcry_err_code(rc);
return OpenSSHKey();
}
rc = gcry_pk_genkey(&sexp[CombinedKey], sexp[Params]);
if (rc != GPG_ERR_NO_ERROR) {
qWarning() << "Could not create ssh key" << gcry_err_code(rc);
return OpenSSHKey();
}
sexp[PrivateKey] = gcry_sexp_find_token(sexp[CombinedKey], "private-key", 0);
sexp[PublicKey] = gcry_sexp_find_token(sexp[CombinedKey], "public-key", 0);
sexp[Private_N] = gcry_sexp_find_token(sexp[PrivateKey], "n", 1);
mpi[Private_N] = gcry_sexp_nth_mpi(sexp[Private_N], 1, GCRYMPI_FMT_USG);
sexp[Private_E] = gcry_sexp_find_token(sexp[PrivateKey], "e", 1);
mpi[Private_E] = gcry_sexp_nth_mpi(sexp[Private_E], 1, GCRYMPI_FMT_USG);
sexp[Private_D] = gcry_sexp_find_token(sexp[PrivateKey], "d", 1);
mpi[Private_D] = gcry_sexp_nth_mpi(sexp[Private_D], 1, GCRYMPI_FMT_USG);
sexp[Private_Q] = gcry_sexp_find_token(sexp[PrivateKey], "q", 1);
mpi[Private_Q] = gcry_sexp_nth_mpi(sexp[Private_Q], 1, GCRYMPI_FMT_USG);
sexp[Private_P] = gcry_sexp_find_token(sexp[PrivateKey], "p", 1);
mpi[Private_P] = gcry_sexp_nth_mpi(sexp[Private_P], 1, GCRYMPI_FMT_USG);
sexp[Private_U] = gcry_sexp_find_token(sexp[PrivateKey], "u", 1);
mpi[Private_U] = gcry_sexp_nth_mpi(sexp[Private_U], 1, GCRYMPI_FMT_USG);
sexp[Public_N] = gcry_sexp_find_token(sexp[PublicKey], "n", 1);
mpi[Public_N] = gcry_sexp_nth_mpi(sexp[Public_N], 1, GCRYMPI_FMT_USG);
sexp[Public_E] = gcry_sexp_find_token(sexp[PublicKey], "e", 1);
mpi[Public_E] = gcry_sexp_nth_mpi(sexp[Public_E], 1, GCRYMPI_FMT_USG);
QList<QByteArray> publicParts;
QList<QByteArray> privateParts;
Tools::Buffer buffer;
gcry_mpi_format format = GCRYMPI_FMT_USG;
rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Private_N]);
if (rc != GPG_ERR_NO_ERROR) {
qWarning() << "Could not extract private key part" << gcry_err_code(rc);
return OpenSSHKey();
}
privateParts << buffer.content();
buffer.clear();
rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Private_E]);
if (rc != GPG_ERR_NO_ERROR) {
qWarning() << "Could not extract private key part" << gcry_err_code(rc);
return OpenSSHKey();
}
privateParts << buffer.content();
buffer.clear();
rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Private_D]);
if (rc != GPG_ERR_NO_ERROR) {
qWarning() << "Could not extract private key part" << gcry_err_code(rc);
return OpenSSHKey();
}
privateParts << buffer.content();
buffer.clear();
rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Private_U]);
if (rc != GPG_ERR_NO_ERROR) {
qWarning() << "Could not extract private key part" << gcry_err_code(rc);
return OpenSSHKey();
}
privateParts << buffer.content();
buffer.clear();
rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Private_P]);
if (rc != GPG_ERR_NO_ERROR) {
qWarning() << "Could not extract private key part" << gcry_err_code(rc);
return OpenSSHKey();
}
privateParts << buffer.content();
buffer.clear();
rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Private_Q]);
if (rc != GPG_ERR_NO_ERROR) {
qWarning() << "Could not extract private key part" << gcry_err_code(rc);
return OpenSSHKey();
}
privateParts << buffer.content();
buffer.clear();
rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Public_E]);
if (rc != GPG_ERR_NO_ERROR) {
qWarning() << "Could not extract public key part" << gcry_err_code(rc);
return OpenSSHKey();
}
publicParts << buffer.content();
buffer.clear();
rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Public_N]);
if (rc != GPG_ERR_NO_ERROR) {
qWarning() << "Could not extract public key part" << gcry_err_code(rc);
return OpenSSHKey();
}
publicParts << buffer.content();
OpenSSHKey key;
key.m_rawType = OpenSSHKey::TYPE_RSA_PRIVATE;
key.setType("ssh-rsa");
key.setPublicData(publicParts);
key.setPrivateData(privateParts);
key.setComment("");
return key;
}
OpenSSHKey::OpenSSHKey(QObject* parent) OpenSSHKey::OpenSSHKey(QObject* parent)
: QObject(parent) : QObject(parent)
, m_type(QString()) , m_type(QString())
@ -251,20 +79,6 @@ const QString OpenSSHKey::type() const
return m_type; return m_type;
} }
int OpenSSHKey::keyLength() const
{
if (m_type == "ssh-dss" && m_rawPublicData.length() == 4) {
return (m_rawPublicData[0].length() - 1) * 8;
} else if (m_type == "ssh-rsa" && m_rawPublicData.length() == 2) {
return (m_rawPublicData[1].length() - 1) * 8;
} else if (m_type.startsWith("ecdsa-sha2-") && m_rawPublicData.length() == 2) {
return (m_rawPublicData[1].length() - 1) * 4;
} else if (m_type == "ssh-ed25519" && m_rawPublicData.length() == 1) {
return m_rawPublicData[0].length() * 8;
}
return 0;
}
const QString OpenSSHKey::fingerprint(QCryptographicHash::Algorithm algo) const const QString OpenSSHKey::fingerprint(QCryptographicHash::Algorithm algo) const
{ {
if (m_rawPublicData.isEmpty()) { if (m_rawPublicData.isEmpty()) {
@ -301,24 +115,6 @@ const QString OpenSSHKey::comment() const
return m_comment; return m_comment;
} }
const QString OpenSSHKey::privateKey() const
{
if (m_rawPrivateData.isEmpty()) {
return {};
}
QByteArray privateKey;
BinaryStream stream(&privateKey);
stream.writeString(m_type);
for (QByteArray ba : m_rawPrivateData) {
stream.writeString(ba);
}
return m_type + " " + QString::fromLatin1(privateKey.toBase64()) + " " + m_comment;
}
const QString OpenSSHKey::publicKey() const const QString OpenSSHKey::publicKey() const
{ {
if (m_rawPublicData.isEmpty()) { if (m_rawPublicData.isEmpty()) {
@ -438,7 +234,7 @@ bool OpenSSHKey::parsePKCS1PEM(const QByteArray& in)
return false; return false;
} }
if (m_rawType == TYPE_DSA_PRIVATE || m_rawType == TYPE_RSA_PRIVATE || m_rawType == TYPE_RSA_PUBLIC) { if (m_rawType == TYPE_DSA_PRIVATE || m_rawType == TYPE_RSA_PRIVATE) {
m_rawData = data; m_rawData = data;
} else if (m_rawType == TYPE_OPENSSH_PRIVATE) { } else if (m_rawType == TYPE_OPENSSH_PRIVATE) {
BinaryStream stream(&data); BinaryStream stream(&data);
@ -508,7 +304,7 @@ bool OpenSSHKey::encrypted() const
bool OpenSSHKey::openKey(const QString& passphrase) bool OpenSSHKey::openKey(const QString& passphrase)
{ {
QScopedPointer<SymmetricCipher> cipher; QScopedPointer<SymmetricCipher> cipher(new SymmetricCipher());
if (!m_rawPrivateData.isEmpty()) { if (!m_rawPrivateData.isEmpty()) {
return true; return true;
@ -519,28 +315,26 @@ bool OpenSSHKey::openKey(const QString& passphrase)
return false; return false;
} }
if (m_cipherName.compare("aes-128-cbc", Qt::CaseInsensitive) == 0) { QByteArray rawData = m_rawData;
cipher.reset(new SymmetricCipher(SymmetricCipher::Aes128, SymmetricCipher::Cbc, SymmetricCipher::Decrypt));
} else if (m_cipherName == "aes256-cbc" || m_cipherName.compare("aes-256-cbc", Qt::CaseInsensitive) == 0) { if (m_cipherName != "none") {
cipher.reset(new SymmetricCipher(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt)); auto cipherMode = SymmetricCipher::stringToMode(m_cipherName);
} else if (m_cipherName == "aes256-ctr" || m_cipherName.compare("aes-256-ctr", Qt::CaseInsensitive) == 0) { if (cipherMode == SymmetricCipher::InvalidMode) {
cipher.reset(new SymmetricCipher(SymmetricCipher::Aes256, SymmetricCipher::Ctr, SymmetricCipher::Decrypt));
} else if (m_cipherName != "none") {
m_error = tr("Unknown cipher: %1").arg(m_cipherName); m_error = tr("Unknown cipher: %1").arg(m_cipherName);
return false; return false;
} }
if (m_kdfName == "bcrypt") { QByteArray keyData, ivData;
if (!cipher) {
m_error = tr("Trying to run KDF without cipher");
return false;
}
if (m_kdfName == "bcrypt") {
if (passphrase.isEmpty()) { if (passphrase.isEmpty()) {
m_error = tr("Passphrase is required to decrypt this key"); m_error = tr("Passphrase is required to decrypt this key");
return false; return false;
} }
int keySize = cipher->keySize(cipherMode);
int blockSize = 16;
BinaryStream optionStream(&m_kdfOptions); BinaryStream optionStream(&m_kdfOptions);
QByteArray salt; QByteArray salt;
@ -549,32 +343,31 @@ bool OpenSSHKey::openKey(const QString& passphrase)
optionStream.readString(salt); optionStream.readString(salt);
optionStream.read(rounds); optionStream.read(rounds);
QByteArray decryptKey; QByteArray decryptKey(keySize + blockSize, '\0');
decryptKey.fill(0, cipher->keySize() + cipher->blockSize()); try {
auto baPass = passphrase.toUtf8();
QByteArray phraseData = passphrase.toUtf8(); auto pwhash = Botan::PasswordHashFamily::create_or_throw("Bcrypt-PBKDF")->from_iterations(rounds);
if (bcrypt_pbkdf(phraseData, salt, decryptKey, rounds) < 0) { pwhash->derive_key(reinterpret_cast<uint8_t*>(decryptKey.data()),
m_error = tr("Key derivation failed, key file corrupted?"); decryptKey.size(),
baPass.constData(),
baPass.size(),
reinterpret_cast<const uint8_t*>(salt.constData()),
salt.size());
} catch (std::exception& e) {
m_error = tr("Key derivation failed: %1").arg(e.what());
return false; return false;
} }
QByteArray keyData, ivData; keyData = decryptKey.left(keySize);
keyData.setRawData(decryptKey.data(), cipher->keySize()); ivData = decryptKey.right(blockSize);
ivData.setRawData(decryptKey.data() + cipher->keySize(), cipher->blockSize());
cipher->init(keyData, ivData);
if (!cipher->init(keyData, ivData)) {
m_error = cipher->errorString();
return false;
}
} else if (m_kdfName == "md5") { } else if (m_kdfName == "md5") {
if (m_cipherIV.length() < 8) { if (m_cipherIV.length() < 8) {
m_error = tr("Cipher IV is too short for MD5 kdf"); m_error = tr("Cipher IV is too short for MD5 kdf");
return false; return false;
} }
QByteArray keyData; int keySize = cipher->keySize(cipherMode);
QByteArray mdBuf; QByteArray mdBuf;
do { do {
QCryptographicHash hash(QCryptographicHash::Md5); QCryptographicHash hash(QCryptographicHash::Md5);
@ -583,30 +376,28 @@ bool OpenSSHKey::openKey(const QString& passphrase)
hash.addData(m_cipherIV.data(), 8); hash.addData(m_cipherIV.data(), 8);
mdBuf = hash.result(); mdBuf = hash.result();
keyData.append(mdBuf); keyData.append(mdBuf);
} while (keyData.size() < cipher->keySize()); } while (keyData.size() < keySize);
if (keyData.size() > cipher->keySize()) { if (keyData.size() > keySize) {
// If our key size isn't a multiple of 16 (e.g. AES-192 or something), // If our key size isn't a multiple of 16 (e.g. AES-192 or something),
// then we will need to truncate it. // then we will need to truncate it.
keyData.resize(cipher->keySize()); keyData.resize(keySize);
} }
if (!cipher->init(keyData, m_cipherIV)) { ivData = m_cipherIV;
m_error = cipher->errorString();
return false;
}
} else if (m_kdfName != "none") { } else if (m_kdfName != "none") {
m_error = tr("Unknown KDF: %1").arg(m_kdfName); m_error = tr("Unknown KDF: %1").arg(m_kdfName);
return false; return false;
} }
QByteArray rawData = m_rawData; // Initialize the cipher using the processed key and iv data
if (!cipher->init(cipherMode, SymmetricCipher::Decrypt, keyData, ivData)) {
if (cipher && cipher->isInitalized()) { m_error = tr("Failed to initialize cipher: %1").arg(cipher->errorString());
bool ok = false; return false;
rawData = cipher->process(rawData, &ok); }
if (!ok) { // Decrypt the raw data, we do not use finish because padding is handled separately
m_error = tr("Decryption failed, wrong passphrase?"); if (!cipher->process(rawData)) {
m_error = tr("Decryption failed: %1").arg(cipher->errorString());
return false; return false;
} }
} }
@ -619,13 +410,7 @@ bool OpenSSHKey::openKey(const QString& passphrase)
return true; return true;
} else if (m_rawType == TYPE_RSA_PRIVATE) { } else if (m_rawType == TYPE_RSA_PRIVATE) {
if (!ASN1Key::parsePrivateRSA(rawData, *this)) { if (!ASN1Key::parseRSA(rawData, *this)) {
m_error = tr("Decryption failed, wrong passphrase?");
return false;
}
return true;
} else if (m_rawType == TYPE_RSA_PUBLIC) {
if (!ASN1Key::parsePublicRSA(rawData, *this)) {
m_error = tr("Decryption failed, wrong passphrase?"); m_error = tr("Decryption failed, wrong passphrase?");
return false; return false;
} }
@ -779,49 +564,6 @@ bool OpenSSHKey::writePrivate(BinaryStream& stream)
return true; return true;
} }
QList<QByteArray> OpenSSHKey::publicParts() const
{
return m_rawPublicData;
}
QList<QByteArray> OpenSSHKey::privateParts() const
{
return m_rawPrivateData;
}
const QString& OpenSSHKey::privateType() const
{
return m_rawType;
}
OpenSSHKey OpenSSHKey::restoreFromBinary(Type type, const QByteArray& serialized)
{
OpenSSHKey key;
auto data = binaryDeserialize(serialized);
key.setType(data.first);
switch (type) {
case Public:
key.setPublicData(data.second);
break;
case Private:
key.setPrivateData(data.second);
break;
}
return key;
}
QByteArray OpenSSHKey::serializeToBinary(Type type, const OpenSSHKey& key)
{
Q_ASSERT(!key.encrypted());
switch (type) {
case Public:
return binarySerialize(key.type(), key.publicParts());
case Private:
return binarySerialize(key.type(), key.privateParts());
}
return {};
}
uint qHash(const OpenSSHKey& key) uint qHash(const OpenSSHKey& key)
{ {
return qHash(key.fingerprint()); return qHash(key.fingerprint());

View File

@ -19,7 +19,8 @@
#ifndef KEEPASSXC_OPENSSHKEY_H #ifndef KEEPASSXC_OPENSSHKEY_H
#define KEEPASSXC_OPENSSHKEY_H #define KEEPASSXC_OPENSSHKEY_H
#include <QtCore> #include <QCryptographicHash>
#include <QObject>
class BinaryStream; class BinaryStream;
@ -27,8 +28,6 @@ class OpenSSHKey : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
static OpenSSHKey generate(bool secure = true);
explicit OpenSSHKey(QObject* parent = nullptr); explicit OpenSSHKey(QObject* parent = nullptr);
OpenSSHKey(const OpenSSHKey& other); OpenSSHKey(const OpenSSHKey& other);
bool operator==(const OpenSSHKey& other) const; bool operator==(const OpenSSHKey& other) const;
@ -39,11 +38,9 @@ public:
const QString cipherName() const; const QString cipherName() const;
const QString type() const; const QString type() const;
int keyLength() const;
const QString fingerprint(QCryptographicHash::Algorithm algo = QCryptographicHash::Sha256) const; const QString fingerprint(QCryptographicHash::Algorithm algo = QCryptographicHash::Sha256) const;
const QString comment() const; const QString comment() const;
const QString publicKey() const; const QString publicKey() const;
const QString privateKey() const;
const QString errorString() const; const QString errorString() const;
void setType(const QString& type); void setType(const QString& type);
@ -58,24 +55,10 @@ public:
bool writePublic(BinaryStream& stream); bool writePublic(BinaryStream& stream);
bool writePrivate(BinaryStream& stream); bool writePrivate(BinaryStream& stream);
QList<QByteArray> publicParts() const;
QList<QByteArray> privateParts() const;
const QString& privateType() const;
static const QString TYPE_DSA_PRIVATE; static const QString TYPE_DSA_PRIVATE;
static const QString TYPE_RSA_PRIVATE; static const QString TYPE_RSA_PRIVATE;
static const QString TYPE_RSA_PUBLIC;
static const QString TYPE_OPENSSH_PRIVATE; static const QString TYPE_OPENSSH_PRIVATE;
enum Type
{
Public,
Private
};
static OpenSSHKey restoreFromBinary(Type eType, const QByteArray& serialized);
static QByteArray serializeToBinary(Type eType, const OpenSSHKey& key);
private: private:
bool extractPEM(const QByteArray& in, QByteArray& out); bool extractPEM(const QByteArray& in, QByteArray& out);

View File

@ -18,12 +18,12 @@
#include "SSHAgent.h" #include "SSHAgent.h"
#include "BinaryStream.h"
#include "OpenSSHKey.h"
#include "core/Config.h" #include "core/Config.h"
#include "core/Database.h" #include "core/Database.h"
#include "core/Group.h" #include "core/Group.h"
#include "core/Metadata.h" #include "core/Metadata.h"
#include "crypto/ssh/BinaryStream.h"
#include "crypto/ssh/OpenSSHKey.h"
#include "sshagent/KeeAgentSettings.h" #include "sshagent/KeeAgentSettings.h"
#include <QtNetwork> #include <QtNetwork>

View File

@ -23,7 +23,7 @@
#include <QList> #include <QList>
#include <QtCore> #include <QtCore>
#include "crypto/ssh/OpenSSHKey.h" #include "OpenSSHKey.h"
#include "sshagent/KeeAgentSettings.h" #include "sshagent/KeeAgentSettings.h"
class SSHAgent : public QObject class SSHAgent : public QObject

View File

@ -17,12 +17,9 @@
#include "SymmetricCipherStream.h" #include "SymmetricCipherStream.h"
SymmetricCipherStream::SymmetricCipherStream(QIODevice* baseDevice, SymmetricCipherStream::SymmetricCipherStream(QIODevice* baseDevice)
SymmetricCipher::Algorithm algo,
SymmetricCipher::Mode mode,
SymmetricCipher::Direction direction)
: LayeredStream(baseDevice) : LayeredStream(baseDevice)
, m_cipher(new SymmetricCipher(algo, mode, direction)) , m_cipher(new SymmetricCipher())
, m_bufferPos(0) , m_bufferPos(0)
, m_bufferFilling(false) , m_bufferFilling(false)
, m_error(false) , m_error(false)
@ -37,14 +34,18 @@ SymmetricCipherStream::~SymmetricCipherStream()
close(); close();
} }
bool SymmetricCipherStream::init(const QByteArray& key, const QByteArray& iv) bool SymmetricCipherStream::init(SymmetricCipher::Mode mode,
SymmetricCipher::Direction direction,
const QByteArray& key,
const QByteArray& iv)
{ {
m_isInitialized = m_cipher->init(key, iv); m_isInitialized = m_cipher->init(mode, direction, key, iv);
if (!m_isInitialized) { if (!m_isInitialized) {
setErrorString(m_cipher->errorString()); setErrorString(m_cipher->errorString());
return false;
} }
m_streamCipher = m_cipher->blockSize() == 1; m_streamCipher = m_cipher->blockSize(m_cipher->mode()) == 1;
return m_isInitialized; return true;
} }
void SymmetricCipherStream::resetInternalState() void SymmetricCipherStream::resetInternalState()
@ -145,42 +146,24 @@ bool SymmetricCipherStream::readBlock()
m_bufferFilling = true; m_bufferFilling = true;
return false; return false;
} else { } else {
if (!m_cipher->processInPlace(m_buffer)) { m_bufferPos = 0;
m_bufferFilling = false;
if (!m_streamCipher && m_baseDevice->atEnd()) {
if (!m_cipher->finish(m_buffer)) {
m_error = true; m_error = true;
setErrorString(m_cipher->errorString()); setErrorString(m_cipher->errorString());
return false; return false;
} }
m_bufferPos = 0; } else if (m_buffer.size() > 0) {
m_bufferFilling = false; if (!m_cipher->process(m_buffer)) {
if (m_baseDevice->atEnd()) {
if (!m_streamCipher) {
// PKCS7 padding
quint8 padLength = m_buffer.at(m_buffer.size() - 1);
if (padLength == blockSize()) {
Q_ASSERT(m_buffer == QByteArray(blockSize(), blockSize()));
// full block with just padding: discard
m_buffer.clear();
return false;
} else if (padLength > blockSize()) {
// invalid padding
m_error = true; m_error = true;
setErrorString("Invalid padding."); setErrorString(m_cipher->errorString());
return false; return false;
} else {
Q_ASSERT(m_buffer.right(padLength) == QByteArray(padLength, padLength));
// resize buffer to strip padding
m_buffer.resize(blockSize() - padLength);
return true;
} }
} else { }
return m_buffer.size() > 0; return m_buffer.size() > 0;
} }
} else {
return true;
}
}
} }
qint64 SymmetricCipherStream::writeData(const char* data, qint64 maxSize) qint64 SymmetricCipherStream::writeData(const char* data, qint64 maxSize)
@ -222,14 +205,13 @@ bool SymmetricCipherStream::writeBlock(bool lastBlock)
Q_ASSERT(m_streamCipher || lastBlock || (m_buffer.size() == blockSize())); Q_ASSERT(m_streamCipher || lastBlock || (m_buffer.size() == blockSize()));
if (lastBlock && !m_streamCipher) { if (lastBlock && !m_streamCipher) {
// PKCS7 padding QByteArray end;
int padLen = blockSize() - m_buffer.size(); if (!m_cipher->finish(m_buffer)) {
for (int i = 0; i < padLen; i++) { m_error = true;
m_buffer.append(static_cast<char>(padLen)); setErrorString(m_cipher->errorString());
return false;
} }
} } else if (!m_cipher->process(m_buffer)) {
if (!m_cipher->processInPlace(m_buffer)) {
m_error = true; m_error = true;
setErrorString(m_cipher->errorString()); setErrorString(m_cipher->errorString());
return false; return false;
@ -250,5 +232,5 @@ int SymmetricCipherStream::blockSize() const
if (m_streamCipher) { if (m_streamCipher) {
return 1024; return 1024;
} }
return m_cipher->blockSize(); return m_cipher->blockSize(m_cipher->mode());
} }

View File

@ -29,12 +29,10 @@ class SymmetricCipherStream : public LayeredStream
Q_OBJECT Q_OBJECT
public: public:
SymmetricCipherStream(QIODevice* baseDevice, SymmetricCipherStream(QIODevice* baseDevice);
SymmetricCipher::Algorithm algo,
SymmetricCipher::Mode mode,
SymmetricCipher::Direction direction);
~SymmetricCipherStream(); ~SymmetricCipherStream();
bool init(const QByteArray& key, const QByteArray& iv); bool
init(SymmetricCipher::Mode mode, SymmetricCipher::Direction direction, const QByteArray& key, const QByteArray& iv);
bool open(QIODevice::OpenMode mode) override; bool open(QIODevice::OpenMode mode) override;
bool reset() override; bool reset() override;
void close() override; void close() override;

View File

@ -33,7 +33,7 @@ public:
bool storeKey(const QString& databasePath, const QByteArray& passwordKey); bool storeKey(const QString& databasePath, const QByteArray& passwordKey);
QSharedPointer<QByteArray> getKey(const QString& databasePath) const; bool getKey(const QString& databasePath, QByteArray& passwordKey) const;
bool isAvailable(); bool isAvailable();

View File

@ -57,28 +57,25 @@ bool TouchID::storeKey(const QString& databasePath, const QByteArray& passwordKe
} }
// generate random AES 256bit key and IV // generate random AES 256bit key and IV
Random* random = randomGen(); QByteArray randomKey = randomGen()->randomArray(32);
QByteArray randomKey = random->randomArray(32); QByteArray randomIV = randomGen()->randomArray(16);
QByteArray randomIV = random->randomArray(16);
bool ok; SymmetricCipher aes256Encrypt;
SymmetricCipher aes256Encrypt(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Encrypt); if (!aes256Encrypt.init(SymmetricCipher::Aes256_CBC, SymmetricCipher::Encrypt, randomKey, randomIV)) {
if (!aes256Encrypt.init(randomKey, randomIV)) {
debug("TouchID::storeKey - Error initializing encryption: %s", debug("TouchID::storeKey - Error initializing encryption: %s",
aes256Encrypt.errorString().toUtf8().constData()); aes256Encrypt.errorString().toUtf8().constData());
return false; return false;
} }
// encrypt and keep result in memory // encrypt and keep result in memory
QByteArray encryptedMasterKey = aes256Encrypt.process(passwordKey, &ok); QByteArray encryptedMasterKey = passwordKey;
if (!ok) { if (!aes256Encrypt.process(encryptedMasterKey)) {
debug("TouchID::storeKey - Error encrypting: %s", aes256Encrypt.errorString().toUtf8().constData()); debug("TouchID::storeKey - Error encrypting: %s", aes256Encrypt.errorString().toUtf8().constData());
return false; return false;
} }
// memorize which database the stored key is for // memorize which database the stored key is for
this->m_encryptedMasterKeys.insert(databasePath, encryptedMasterKey); m_encryptedMasterKeys.insert(databasePath, encryptedMasterKey);
NSString* accountName = (SECURITY_ACCOUNT_PREFIX + hash(databasePath)).toNSString(); // autoreleased NSString* accountName = (SECURITY_ACCOUNT_PREFIX + hash(databasePath)).toNSString(); // autoreleased
@ -145,18 +142,19 @@ bool TouchID::storeKey(const QString& databasePath, const QByteArray& passwordKe
* Checks if an encrypted PasswordKey is available for the given database, tries to * Checks if an encrypted PasswordKey is available for the given database, tries to
* decrypt it using the KeyChain and if successful, returns it. * decrypt it using the KeyChain and if successful, returns it.
*/ */
QSharedPointer <QByteArray> TouchID::getKey(const QString& databasePath) const bool TouchID::getKey(const QString& databasePath, QByteArray& passwordKey) const
{ {
passwordKey.clear();
if (databasePath.isEmpty()) { if (databasePath.isEmpty()) {
// illegal arguments // illegal arguments
debug("TouchID::storeKey - Illegal argument: databasePath = %s", databasePath.toUtf8().constData()); debug("TouchID::storeKey - Illegal argument: databasePath = %s", databasePath.toUtf8().constData());
return NULL; return false;
} }
// checks if encrypted PasswordKey is available and is stored for the given database // checks if encrypted PasswordKey is available and is stored for the given database
if (!this->m_encryptedMasterKeys.contains(databasePath)) { if (!this->m_encryptedMasterKeys.contains(databasePath)) {
debug("TouchID::getKey - No stored key found"); debug("TouchID::getKey - No stored key found");
return NULL; return false;
} }
// query the KeyChain for the AES key // query the KeyChain for the AES key
@ -179,12 +177,12 @@ QSharedPointer <QByteArray> TouchID::getKey(const QString& databasePath) const
CFRelease(query); CFRelease(query);
if (status == errSecUserCanceled) { if (status == errSecUserCanceled) {
// user canceled the authentication, need special return value // user canceled the authentication, return true with empty key
debug("TouchID::getKey - User canceled authentication"); debug("TouchID::getKey - User canceled authentication");
return QSharedPointer<QByteArray>::create(); return true;
} else if (status != errSecSuccess || dataTypeRef == NULL) { } else if (status != errSecSuccess || dataTypeRef == NULL) {
debug("TouchID::getKey - Error retrieving result: %d", status); debug("TouchID::getKey - Error retrieving result: %d", status);
return NULL; return false;
} }
CFDataRef valueData = static_cast<CFDataRef>(dataTypeRef); CFDataRef valueData = static_cast<CFDataRef>(dataTypeRef);
@ -196,22 +194,20 @@ QSharedPointer <QByteArray> TouchID::getKey(const QString& databasePath) const
QByteArray key = dataBytes.left(32); QByteArray key = dataBytes.left(32);
QByteArray iv = dataBytes.right(16); QByteArray iv = dataBytes.right(16);
bool ok; SymmetricCipher aes256Decrypt;
SymmetricCipher aes256Decrypt(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt); if (!aes256Decrypt.init(SymmetricCipher::Aes256_CBC, SymmetricCipher::Decrypt, key, iv)) {
if (!aes256Decrypt.init(key, iv)) {
debug("TouchID::getKey - Error initializing decryption: %s", aes256Decrypt.errorString().toUtf8().constData()); debug("TouchID::getKey - Error initializing decryption: %s", aes256Decrypt.errorString().toUtf8().constData());
return NULL; return false;
} }
// decrypt PasswordKey from memory using AES // decrypt PasswordKey from memory using AES
QByteArray result = aes256Decrypt.process(this->m_encryptedMasterKeys[databasePath], &ok); passwordKey = m_encryptedMasterKeys[databasePath];
if (!ok) { if (!aes256Decrypt.process(passwordKey)) {
debug("TouchID::getKey - Error decryption: %s", aes256Decrypt.errorString().toUtf8().constData()); debug("TouchID::getKey - Error decryption: %s", aes256Decrypt.errorString().toUtf8().constData());
return NULL; return false;
} }
return QSharedPointer<QByteArray>::create(result); return true;
} }
/** /**

View File

@ -94,8 +94,7 @@ set(testsupport_SOURCES
modeltest.cpp modeltest.cpp
FailDevice.cpp FailDevice.cpp
mock/MockClock.cpp mock/MockClock.cpp
util/TemporaryFile.cpp util/TemporaryFile.cpp)
stub/TestRandom.cpp)
add_library(testsupport STATIC ${testsupport_SOURCES}) add_library(testsupport STATIC ${testsupport_SOURCES})
target_link_libraries(testsupport Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Test) target_link_libraries(testsupport Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Test)
@ -162,9 +161,9 @@ if(WITH_XC_AUTOTYPE)
set_target_properties(testautotype PROPERTIES ENABLE_EXPORTS ON) set_target_properties(testautotype PROPERTIES ENABLE_EXPORTS ON)
endif() endif()
if(WITH_XC_CRYPTO_SSH) if(WITH_XC_SSHAGENT)
add_unit_test(NAME testopensshkey SOURCES TestOpenSSHKey.cpp add_unit_test(NAME testopensshkey SOURCES TestOpenSSHKey.cpp
LIBS ${TEST_LIBRARIES}) LIBS sshagent ${TEST_LIBRARIES})
if(NOT WIN32) if(NOT WIN32)
add_unit_test(NAME testsshagent SOURCES TestSSHAgent.cpp add_unit_test(NAME testsshagent SOURCES TestSSHAgent.cpp
LIBS ${TEST_LIBRARIES}) LIBS ${TEST_LIBRARIES})

View File

@ -21,9 +21,11 @@
#include "browser/BrowserSettings.h" #include "browser/BrowserSettings.h"
#include "core/Tools.h" #include "core/Tools.h"
#include "crypto/Crypto.h" #include "crypto/Crypto.h"
#include "sodium/crypto_box.h"
#include <QString> #include <QString>
#include <botan/sodium.h>
using namespace Botan::Sodium;
QTEST_GUILESS_MAIN(TestBrowser) QTEST_GUILESS_MAIN(TestBrowser)

Some files were not shown because too many files have changed in this diff Show More