From 3929675f82f320abad0f103204a9de7bc0c0e52f Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Sun, 17 Jan 2021 18:35:48 -0500 Subject: [PATCH] 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. --- CMakeLists.txt | 32 +- COPYING | 4 + cmake/FindBotan2.cmake | 121 +++ share/translations/keepassx_en_US.ts | 2 +- src/CMakeLists.txt | 13 +- src/browser/BrowserAction.cpp | 8 +- src/browser/BrowserHost.cpp | 6 - src/browser/CMakeLists.txt | 2 +- src/cli/CMakeLists.txt | 8 +- src/cli/Utils.cpp | 11 +- src/core/Alloc.cpp | 4 +- src/core/Tools.cpp | 26 - src/core/Tools.h | 28 - src/crypto/Crypto.cpp | 522 ++++++------- src/crypto/Crypto.h | 31 +- src/crypto/CryptoHash.cpp | 72 +- src/crypto/Random.cpp | 85 +-- src/crypto/Random.h | 27 +- src/crypto/SymmetricCipher.cpp | 246 +++++-- src/crypto/SymmetricCipher.h | 79 +- src/crypto/SymmetricCipherBackend.h | 44 -- src/crypto/SymmetricCipherGcrypt.cpp | 261 ------- src/crypto/SymmetricCipherGcrypt.h | 62 -- src/crypto/argon2/argon2.h | 34 - src/crypto/kdf/AesKdf.cpp | 26 +- src/crypto/kdf/AesKdf.h | 3 +- src/crypto/kdf/Argon2Kdf.cpp | 65 +- src/crypto/kdf/Argon2Kdf.h | 13 +- src/crypto/kdf/Kdf.cpp | 35 - src/crypto/kdf/Kdf.h | 5 +- src/crypto/kdf/Kdf_p.h | 43 -- src/crypto/ssh/CMakeLists.txt | 14 - src/crypto/ssh/bcrypt_pbkdf.cpp | 172 ----- src/crypto/ssh/blf.h | 88 --- src/crypto/ssh/blowfish.c | 696 ------------------ src/crypto/ssh/includes.h | 19 - src/fdosecrets/CMakeLists.txt | 5 +- src/fdosecrets/GcryptMPI.cpp | 63 -- src/fdosecrets/GcryptMPI.h | 64 -- src/fdosecrets/objects/SessionCipher.cpp | 199 +---- src/fdosecrets/objects/SessionCipher.h | 73 +- src/format/Kdbx3Reader.cpp | 11 +- src/format/Kdbx3Writer.cpp | 18 +- src/format/Kdbx4Reader.cpp | 24 +- src/format/Kdbx4Writer.cpp | 21 +- src/format/KdbxReader.cpp | 2 +- src/format/KeePass1Reader.cpp | 21 +- src/format/KeePass2RandomStream.cpp | 38 +- src/format/KeePass2RandomStream.h | 8 +- src/format/OpData01.cpp | 8 +- src/format/OpVaultReader.cpp | 69 +- src/format/OpVaultReaderBandEntry.cpp | 6 +- src/gui/DatabaseOpenWidget.cpp | 9 +- src/gui/entry/EditEntryWidget.cpp | 2 +- src/keeshare/CMakeLists.txt | 2 +- src/keeshare/KeeShare.cpp | 1 - src/keeshare/KeeShare.h | 1 + src/keeshare/KeeShareSettings.cpp | 153 ++-- src/keeshare/KeeShareSettings.h | 14 +- src/keeshare/SettingsWidgetKeeShare.cpp | 60 +- src/keeshare/SettingsWidgetKeeShare.ui | 118 +-- src/keeshare/ShareExport.cpp | 8 +- src/keeshare/ShareExport.h | 2 + src/keeshare/ShareImport.cpp | 54 +- src/keeshare/ShareImport.h | 2 + src/keeshare/ShareObserver.cpp | 2 + src/keeshare/Signature.cpp | 258 +------ src/keeshare/Signature.h | 15 +- .../group/EditGroupWidgetKeeShare.cpp | 1 - src/keys/ChallengeResponseKey.h | 10 +- src/keys/CompositeKey.cpp | 2 +- src/keys/FileKey.cpp | 41 +- src/keys/FileKey.h | 5 +- src/keys/PasswordKey.cpp | 18 +- src/keys/PasswordKey.h | 6 +- src/keys/YkChallengeResponseKey.cpp | 32 +- src/keys/YkChallengeResponseKey.h | 11 +- src/keys/YkChallengeResponseKeyCLI.cpp | 52 -- src/keys/YkChallengeResponseKeyCLI.h | 51 -- src/main.cpp | 33 +- src/proxy/CMakeLists.txt | 2 +- src/{crypto/ssh => sshagent}/ASN1Key.cpp | 67 +- src/{crypto/ssh => sshagent}/ASN1Key.h | 3 +- src/{crypto/ssh => sshagent}/BinaryStream.cpp | 0 src/{crypto/ssh => sshagent}/BinaryStream.h | 0 src/sshagent/CMakeLists.txt | 5 +- src/sshagent/KeeAgentSettings.cpp | 2 +- src/sshagent/KeeAgentSettings.h | 2 +- src/{crypto/ssh => sshagent}/OpenSSHKey.cpp | 408 ++-------- src/{crypto/ssh => sshagent}/OpenSSHKey.h | 21 +- src/sshagent/SSHAgent.cpp | 4 +- src/sshagent/SSHAgent.h | 2 +- src/streams/SymmetricCipherStream.cpp | 76 +- src/streams/SymmetricCipherStream.h | 8 +- src/touchid/TouchID.h | 2 +- src/touchid/TouchID.mm | 46 +- tests/CMakeLists.txt | 7 +- tests/TestBrowser.cpp | 4 +- tests/TestFdoSecrets.cpp | 65 +- tests/TestFdoSecrets.h | 4 - tests/TestKeePass2RandomStream.cpp | 24 +- tests/TestOpenSSHKey.cpp | 13 +- tests/TestOpenSSHKey.h | 1 - tests/TestRandomGenerator.cpp | 66 +- tests/TestRandomGenerator.h | 10 +- tests/TestSSHAgent.h | 3 +- tests/TestSharing.cpp | 111 +-- tests/TestSharing.h | 10 +- tests/TestSignature.cpp | 105 +-- tests/TestSymmetricCipher.cpp | 258 +++---- tests/TestSymmetricCipher.h | 5 +- tests/gui/TestGuiFdoSecrets.cpp | 73 +- tests/gui/TestGuiFdoSecrets.h | 6 +- tests/mock/MockChallengeResponseKey.cpp | 10 +- tests/mock/MockChallengeResponseKey.h | 1 - tests/stub/TestRandom.cpp | 62 -- tests/stub/TestRandom.h | 48 -- 117 files changed, 1567 insertions(+), 4467 deletions(-) create mode 100644 cmake/FindBotan2.cmake delete mode 100644 src/crypto/SymmetricCipherBackend.h delete mode 100644 src/crypto/SymmetricCipherGcrypt.cpp delete mode 100644 src/crypto/SymmetricCipherGcrypt.h delete mode 100644 src/crypto/argon2/argon2.h delete mode 100644 src/crypto/kdf/Kdf_p.h delete mode 100644 src/crypto/ssh/CMakeLists.txt delete mode 100644 src/crypto/ssh/bcrypt_pbkdf.cpp delete mode 100644 src/crypto/ssh/blf.h delete mode 100644 src/crypto/ssh/blowfish.c delete mode 100644 src/crypto/ssh/includes.h delete mode 100644 src/fdosecrets/GcryptMPI.cpp delete mode 100644 src/fdosecrets/GcryptMPI.h delete mode 100644 src/keys/YkChallengeResponseKeyCLI.cpp delete mode 100644 src/keys/YkChallengeResponseKeyCLI.h rename src/{crypto/ssh => sshagent}/ASN1Key.cpp (71%) rename src/{crypto/ssh => sshagent}/ASN1Key.h (89%) rename src/{crypto/ssh => sshagent}/BinaryStream.cpp (100%) rename src/{crypto/ssh => sshagent}/BinaryStream.h (100%) rename src/{crypto/ssh => sshagent}/OpenSSHKey.cpp (51%) rename src/{crypto/ssh => sshagent}/OpenSSHKey.h (82%) delete mode 100644 tests/stub/TestRandom.cpp delete mode 100644 tests/stub/TestRandom.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b679cb2549..729aa41407 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -86,12 +86,6 @@ if(WITH_XC_ALL) 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 if(NOT WITH_XC_NETWORKING AND WITH_XC_UPDATECHECK) message(STATUS "Disabling WITH_XC_UPDATECHECK because WITH_XC_NETWORKING is disabled") @@ -250,6 +244,7 @@ if(WITH_APP_BUNDLE) endif() 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("-Wformat=2 -Wmissing-format-attribute") add_gcc_compiler_flags("-fvisibility=hidden") @@ -269,7 +264,6 @@ else() 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_cflags("-Wchar-subscripts -Wwrite-strings") @@ -324,7 +318,7 @@ if(APPLE AND CMAKE_COMPILER_IS_CLANGXX) endif() if(WITH_DEV_BUILD) - add_definitions(-DQT_DEPRECATED_WARNINGS -DGCRYPT_NO_DEPRECATED) + add_definitions(-DQT_DEPRECATED_WARNINGS) else() add_definitions(-DQT_NO_DEPRECATED_WARNINGS) add_gcc_compiler_cxxflags("-Wno-deprecated-declarations") @@ -447,20 +441,22 @@ endif() # Make sure we don't enable asserts there. set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS_NONE QT_NO_DEBUG) -find_package(LibGPGError REQUIRED) -find_package(Gcrypt 1.7.0 REQUIRED) -find_package(Argon2 REQUIRED) -find_package(ZLIB REQUIRED) -find_package(QREncode REQUIRED) -find_package(sodium 1.0.12 REQUIRED) - -set(CMAKE_REQUIRED_INCLUDES ${ZLIB_INCLUDE_DIR}) +# Find Botan2 +find_package(Botan2 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) if(ZLIB_VERSION_STRING VERSION_LESS "1.2.0") message(FATAL_ERROR "zlib 1.2.0 or higher is required to use the gzip format") endif() +include_directories(SYSTEM ${ZLIB_INCLUDE_DIR}) -include_directories(SYSTEM ${ARGON2_INCLUDE_DIR} ${sodium_INCLUDE_DIR}) +# QREncode required for TOTP +find_package(QREncode REQUIRED) # Optional if(WITH_XC_YUBIKEY) @@ -499,7 +495,7 @@ if(UNIX) endif() endif() -include_directories(SYSTEM ${GCRYPT_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR}) +include_directories(SYSTEM ${ZLIB_INCLUDE_DIR}) add_subdirectory(src) add_subdirectory(share) diff --git a/COPYING b/COPYING index c18b82201d..1ec6980e10 100644 --- a/COPYING +++ b/COPYING @@ -46,6 +46,10 @@ Files: cmake/FindYubiKey.cmake Copyright: 2014 Kyle Manna License: GPL-2 or GPL-3 +Files: cmake/FindBotan2.cmake +Copyright: 2018 Ribose Inc. +License: BSD-2-clause + Files: cmake/GenerateProductVersion.cmake Copyright: 2015 halex2005 License: MIT diff --git a/cmake/FindBotan2.cmake b/cmake/FindBotan2.cmake new file mode 100644 index 0000000000..a7564f542e --- /dev/null +++ b/cmake/FindBotan2.cmake @@ -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) diff --git a/share/translations/keepassx_en_US.ts b/share/translations/keepassx_en_US.ts index 7fb1fed781..9daa322719 100644 --- a/share/translations/keepassx_en_US.ts +++ b/share/translations/keepassx_en_US.ts @@ -7477,7 +7477,7 @@ Kernel: %3 %4 Do you want to trust %1 with the fingerprint of %2 from %3? - Do you want to trust %1 with the fingerprint of %2 from %3? {1 ?} {2 ?} + Do you want to trust %1 with the fingerprint of %2 from %3? Not this time diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e3c764e0cb..8b98f21c33 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -67,7 +67,6 @@ set(keepassx_SOURCES crypto/CryptoHash.cpp crypto/Random.cpp crypto/SymmetricCipher.cpp - crypto/SymmetricCipherGcrypt.cpp crypto/kdf/Kdf.cpp crypto/kdf/AesKdf.cpp crypto/kdf/Argon2Kdf.cpp @@ -182,7 +181,6 @@ set(keepassx_SOURCES keys/FileKey.cpp keys/PasswordKey.cpp keys/YkChallengeResponseKey.cpp - keys/YkChallengeResponseKeyCLI.cpp streams/HashedBlockStream.cpp streams/HmacBlockStream.cpp streams/LayeredStream.cpp @@ -247,11 +245,6 @@ add_subdirectory(cli) add_subdirectory(qrcode) set(qrcode_LIB qrcode) -add_subdirectory(crypto/ssh) -if(WITH_XC_CRYPTO_SSH) - set(crypto_ssh_LIB crypto_ssh) -endif() - add_subdirectory(keeshare) if(WITH_XC_KEESHARE) set(keeshare_LIB keeshare) @@ -322,10 +315,9 @@ target_link_libraries(keepassx_core Qt5::Concurrent Qt5::Network Qt5::Widgets - ${sodium_LIBRARY_RELEASE} + ${BOTAN2_LIBRARIES} ${YUBIKEY_LIBRARIES} ${ZXCVBN_LIBRARIES} - ${ARGON2_LIBRARIES} ${ZLIB_LIBRARIES} ) @@ -370,7 +362,6 @@ if(MINGW) endif() add_executable(${PROGNAME} WIN32 ${keepassx_SOURCES_MAINEXE} ${WIN32_ProductVersionFiles}) -target_link_libraries(keepassx_core ${GCRYPT_LIBRARIES} ${GPGERROR_LIBRARIES}) target_link_libraries(${PROGNAME} keepassx_core) 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) 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() install(FILES ${OPENSSL_DLL} ${CRYPTO_DLL} DESTINATION ".") diff --git a/src/browser/BrowserAction.cpp b/src/browser/BrowserAction.cpp index 65fe4cb7b6..aa8fafa1f4 100644 --- a/src/browser/BrowserAction.cpp +++ b/src/browser/BrowserAction.cpp @@ -24,9 +24,9 @@ #include #include -#include -#include -#include +#include + +using namespace Botan::Sodium; namespace { @@ -283,7 +283,7 @@ QJsonObject BrowserAction::handleGetLogins(const QJsonObject& json, const QStrin const QString id = decrypted.value("id").toString(); const QString formUrl = decrypted.value("submitUrl").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); if (users.isEmpty()) { diff --git a/src/browser/BrowserHost.cpp b/src/browser/BrowserHost.cpp index ac4e996c3b..ee3bc5c57e 100644 --- a/src/browser/BrowserHost.cpp +++ b/src/browser/BrowserHost.cpp @@ -25,7 +25,6 @@ #include #include -#include "sodium.h" #include #ifdef Q_OS_WIN @@ -53,11 +52,6 @@ BrowserHost::~BrowserHost() void BrowserHost::start() { - if (sodium_init() == -1) { - qWarning() << "Failed to start browser service: libsodium failed to initialize!"; - return; - } - if (!m_localServer->isListening()) { m_localServer->listen(BrowserShared::localServerPath()); } diff --git a/src/browser/CMakeLists.txt b/src/browser/CMakeLists.txt index bb92511bc5..d5833cfc4f 100755 --- a/src/browser/CMakeLists.txt +++ b/src/browser/CMakeLists.txt @@ -32,5 +32,5 @@ if(WITH_XC_BROWSER) Variant.cpp) add_library(keepassxcbrowser STATIC ${keepassxcbrowser_SOURCES}) - target_link_libraries(keepassxcbrowser Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Network ${sodium_LIBRARY_RELEASE}) + target_link_libraries(keepassxcbrowser Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Network ${BOTAN2_LIBRARIES}) endif() diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index 10507b0ace..7f058312df 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -54,13 +54,7 @@ add_executable(keepassxc-cli keepassxc-cli.cpp) target_link_libraries(keepassxc-cli ${GPGERROR_LIBRARIES} cli - keepassx_core - Qt5::Core - ${GCRYPT_LIBRARIES} - ${sodium_LIBRARY_RELEASE} - ${ARGON2_LIBRARIES} - ${ZLIB_LIBRARIES} - ${ZXCVBN_LIBRARIES}) + keepassx_core) install(TARGETS keepassxc-cli BUNDLE DESTINATION . COMPONENT Runtime diff --git a/src/cli/Utils.cpp b/src/cli/Utils.cpp index ca440a3931..a416498730 100644 --- a/src/cli/Utils.cpp +++ b/src/cli/Utils.cpp @@ -18,7 +18,7 @@ #include "Utils.h" #ifdef WITH_XC_YUBIKEY -#include "keys/YkChallengeResponseKeyCLI.h" +#include "keys/YkChallengeResponseKey.h" #endif #ifdef Q_OS_WIN @@ -165,9 +165,14 @@ namespace Utils } } - auto key = QSharedPointer(new YkChallengeResponseKeyCLI( - {serial, slot}, QObject::tr("Please touch the button on your YubiKey to continue…"), err)); + auto conn = QObject::connect(YubiKey::instance(), &YubiKey::userInteractionRequest, [&] { + err << QObject::tr("Please touch the button on your YubiKey to continue…") << "\n\n" << flush; + }); + + auto key = QSharedPointer(new YkChallengeResponseKey({serial, slot})); compositeKey->addChallengeResponseKey(key); + + QObject::disconnect(conn); } #else Q_UNUSED(yubiKeySlot); diff --git a/src/core/Alloc.cpp b/src/core/Alloc.cpp index 6c798e728f..525a7e5c65 100644 --- a/src/core/Alloc.cpp +++ b/src/core/Alloc.cpp @@ -16,9 +16,9 @@ */ #include +#include #include #include -#include #if defined(Q_OS_MACOS) #include #elif defined(Q_OS_FREEBSD) @@ -43,7 +43,7 @@ void operator delete(void* ptr, std::size_t size) noexcept return; } - sodium_memzero(ptr, size); + Botan::secure_scrub_memory(ptr, size); std::free(ptr); } diff --git a/src/core/Tools.cpp b/src/core/Tools.cpp index 7e2b65bcdf..d92d1b4a8a 100644 --- a/src/core/Tools.cpp +++ b/src/core/Tools.cpp @@ -352,30 +352,4 @@ namespace Tools 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(raw), size); - } - } // namespace Tools diff --git a/src/core/Tools.h b/src/core/Tools.h index e56a25189e..c7bf5093fd 100644 --- a/src/core/Tools.h +++ b/src/core/Tools.h @@ -64,34 +64,6 @@ namespace Tools } } - template struct Map - { - QMap 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() { // Cache the result since the Qt version can't change during diff --git a/src/crypto/Crypto.cpp b/src/crypto/Crypto.cpp index fb9f28bd9d..64a108af9e 100644 --- a/src/crypto/Crypto.cpp +++ b/src/crypto/Crypto.cpp @@ -19,326 +19,288 @@ #include -#include +#include +#include #include "config-keepassx.h" #include "crypto/CryptoHash.h" #include "crypto/SymmetricCipher.h" -bool Crypto::m_initialized(false); -QString Crypto::m_errorStr; -QString Crypto::m_backendVersion; - -Crypto::Crypto() +namespace Crypto { -} + bool checkAlgorithms(); + bool selfTest(); + + void raiseError(const QString& str); + bool testSha256(); + bool testSha512(); + bool testAes256Cbc(); + bool testAes256Ecb(); + bool testTwofish(); + bool testSalsa20(); + bool testChaCha20(); + + bool m_initialized = false; + QString m_error; + + bool init() + { + if (m_initialized) { + qWarning("Crypto::init: already initialized"); + return true; + } + + if (Botan::version_major() != 2) { + m_error = QObject::tr("Botan library must be major version 2, found %1.%2.%3") + .arg(Botan::version_major()) + .arg(Botan::version_minor()) + .arg(Botan::version_patch()); + return false; + } + if (Botan::version_minor() < 11) { + m_error = QObject::tr("Botan library version 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; + } + + // has to be set before testing Crypto classes + m_initialized = true; + + if (!selfTest()) { + m_initialized = false; + return false; + } -bool Crypto::init() -{ - if (m_initialized) { - qWarning("Crypto::init: already initialized"); return true; } - m_backendVersion = QString::fromLocal8Bit(gcry_check_version(0)); - gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); - - if (!checkAlgorithms()) { - return false; + bool initialized() + { + return m_initialized; } - // has to be set before testing Crypto classes - m_initialized = true; - - if (!backendSelfTest() || !selfTest()) { - m_initialized = false; - return false; + QString errorString() + { + return m_error; } - return true; -} - -bool Crypto::initialized() -{ - return m_initialized; -} - -QString Crypto::errorString() -{ - 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; + 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; } - 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; + + bool selfTest() + { + return testSha256() && testSha512() && testAes256Cbc() && testAes256Ecb() && testTwofish() && testSalsa20() + && testChaCha20(); } - return true; -} + void raiseError(const QString& str) + { + m_error = str; + qWarning("Crypto::selfTest: %s", qPrintable(m_error)); + } -bool Crypto::selfTest() -{ - return testSha256() && testSha512() && testAes256Cbc() && testAes256Ecb() && testTwofish() && testSalsa20() - && testChaCha20(); -} + bool testSha256() + { + QByteArray sha256Test = + CryptoHash::hash("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", CryptoHash::Sha256); -void Crypto::raiseError(const QString& str) -{ - m_errorStr = str; - qWarning("Crypto::selfTest: %s", qPrintable(m_errorStr)); -} + if (sha256Test != QByteArray::fromHex("248D6A61D20638B8E5C026930C3E6039A33CE45964FF2167F6ECEDD419DB06C1")) { + raiseError("SHA-256 mismatch."); + return false; + } -bool Crypto::testSha256() -{ - QByteArray sha256Test = - CryptoHash::hash("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", CryptoHash::Sha256); - - if (sha256Test != QByteArray::fromHex("248D6A61D20638B8E5C026930C3E6039A33CE45964FF2167F6ECEDD419DB06C1")) { - raiseError("SHA-256 mismatch."); - return false; + return true; } - return true; -} + bool testSha512() + { + QByteArray sha512Test = + CryptoHash::hash("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", CryptoHash::Sha512); -bool Crypto::testSha512() -{ - QByteArray sha512Test = - CryptoHash::hash("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", CryptoHash::Sha512); - - if (sha512Test - != QByteArray::fromHex("204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b" - "07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445")) { - raiseError("SHA-512 mismatch."); - return false; + if (sha512Test + != QByteArray::fromHex("204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b" + "07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445")) { + raiseError("SHA-512 mismatch."); + return false; + } + + return true; } - return true; -} + bool testAes256Cbc() + { + QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"); + QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f"); + QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a"); + plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51")); + QByteArray cipherText = QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd6"); + cipherText.append(QByteArray::fromHex("9cfc4e967edb808d679f777bc6702c7d")); + + QByteArray data = plainText; + SymmetricCipher aes256; + if (!aes256.init(SymmetricCipher::Aes256_CBC, SymmetricCipher::Encrypt, key, iv)) { + raiseError(aes256.errorString()); + return false; + } + if (!aes256.process(data)) { + raiseError(aes256.errorString()); + return false; + } + if (data != cipherText) { + raiseError("AES-256 CBC encryption mismatch."); + return false; + } + + if (!aes256.init(SymmetricCipher::Aes256_CBC, SymmetricCipher::Decrypt, key, iv)) { + raiseError(aes256.errorString()); + return false; + } + if (!aes256.process(data)) { + raiseError(aes256.errorString()); + return false; + } + if (data != plainText) { + raiseError("AES-256 CBC decryption mismatch."); + return false; + } -bool Crypto::testAes256Cbc() -{ - QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"); - QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f"); - QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a"); - plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51")); - QByteArray cipherText = QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd6"); - cipherText.append(QByteArray::fromHex("9cfc4e967edb808d679f777bc6702c7d")); - bool ok; - - SymmetricCipher aes256Encrypt(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Encrypt); - if (!aes256Encrypt.init(key, iv)) { - raiseError(aes256Encrypt.errorString()); - return false; - } - QByteArray encryptedText = aes256Encrypt.process(plainText, &ok); - if (!ok) { - raiseError(aes256Encrypt.errorString()); - return false; - } - if (encryptedText != cipherText) { - raiseError("AES-256 CBC encryption mismatch."); - return false; + return true; } - SymmetricCipher aes256Decrypt(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt); - if (!aes256Decrypt.init(key, iv)) { - raiseError(aes256Decrypt.errorString()); - return false; - } - QByteArray decryptedText = aes256Decrypt.process(cipherText, &ok); - if (!ok) { - raiseError(aes256Decrypt.errorString()); - return false; - } - if (decryptedText != plainText) { - raiseError("AES-256 CBC decryption mismatch."); - return false; - } + bool testAes256Ecb() + { + QByteArray key = QByteArray::fromHex("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F"); + QByteArray iv = QByteArray::fromHex("00000000000000000000000000000000"); + QByteArray plainText = QByteArray::fromHex("00112233445566778899AABBCCDDEEFF"); + plainText.append(QByteArray::fromHex("00112233445566778899AABBCCDDEEFF")); + QByteArray cipherText = QByteArray::fromHex("8EA2B7CA516745BFEAFC49904B496089"); + cipherText.append(QByteArray::fromHex("8EA2B7CA516745BFEAFC49904B496089")); - return true; -} + std::unique_ptr cipher(Botan::BlockCipher::create("AES-256")); + cipher->set_key(reinterpret_cast(key.data()), key.size()); -bool Crypto::testAes256Ecb() -{ - QByteArray key = QByteArray::fromHex("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F"); - QByteArray iv = QByteArray::fromHex("00000000000000000000000000000000"); - QByteArray plainText = QByteArray::fromHex("00112233445566778899AABBCCDDEEFF"); - plainText.append(QByteArray::fromHex("00112233445566778899AABBCCDDEEFF")); - QByteArray cipherText = QByteArray::fromHex("8EA2B7CA516745BFEAFC49904B496089"); - cipherText.append(QByteArray::fromHex("8EA2B7CA516745BFEAFC49904B496089")); - bool ok; - - SymmetricCipher aes256Encrypt(SymmetricCipher::Aes256, SymmetricCipher::Ecb, SymmetricCipher::Encrypt); - if (!aes256Encrypt.init(key, iv)) { - 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; - } + Botan::secure_vector out(plainText.size()); + cipher->encrypt_n( + reinterpret_cast(plainText.data()), out.data(), plainText.size() / cipher->block_size()); - SymmetricCipher aes256Decrypt(SymmetricCipher::Aes256, SymmetricCipher::Ecb, SymmetricCipher::Decrypt); - if (!aes256Decrypt.init(key, iv)) { - 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; - } + QByteArray encryptedText(reinterpret_cast(out.data()), out.size()); + if (encryptedText != cipherText) { + raiseError("AES-256 ECB encryption mismatch."); + return false; + } - return true; -} + cipher->decrypt_n(out.data(), out.data(), out.size() / cipher->block_size()); -bool Crypto::testTwofish() -{ - QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"); - QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f"); - QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a"); - plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51")); - QByteArray cipherText = QByteArray::fromHex("e0227c3cc80f3cb1b2ed847cc6f57d3c"); - cipherText.append(QByteArray::fromHex("657b1e7960b30fb7c8d62e72ae37c3a0")); - bool ok; - - SymmetricCipher twofishEncrypt(SymmetricCipher::Twofish, SymmetricCipher::Cbc, SymmetricCipher::Encrypt); - if (!twofishEncrypt.init(key, iv)) { - raiseError(twofishEncrypt.errorString()); - return false; - } - QByteArray encryptedText = twofishEncrypt.process(plainText, &ok); - if (!ok) { - raiseError(twofishEncrypt.errorString()); - return false; - } - if (encryptedText != cipherText) { - raiseError("Twofish encryption mismatch."); - return false; - } + QByteArray decryptedText(reinterpret_cast(out.data()), out.size()); + if (decryptedText != plainText) { + raiseError("AES-256 ECB decryption mismatch."); + return false; + } - SymmetricCipher twofishDecrypt(SymmetricCipher::Twofish, SymmetricCipher::Cbc, SymmetricCipher::Decrypt); - if (!twofishDecrypt.init(key, iv)) { - raiseError(twofishEncrypt.errorString()); - return false; - } - QByteArray decryptedText = twofishDecrypt.process(cipherText, &ok); - if (!ok) { - raiseError(twofishDecrypt.errorString()); - return false; - } - if (decryptedText != plainText) { - raiseError("Twofish encryption mismatch."); - return false; + return true; } - return true; -} + bool testTwofish() + { + QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"); + QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f"); + QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a"); + plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51")); + QByteArray cipherText = QByteArray::fromHex("e0227c3cc80f3cb1b2ed847cc6f57d3c"); + cipherText.append(QByteArray::fromHex("657b1e7960b30fb7c8d62e72ae37c3a0")); + + QByteArray data = plainText; + SymmetricCipher twofish; + if (!twofish.init(SymmetricCipher::Twofish_CBC, SymmetricCipher::Encrypt, key, iv)) { + raiseError(twofish.errorString()); + return false; + } + if (!twofish.process(data)) { + raiseError(twofish.errorString()); + return false; + } + if (data != cipherText) { + raiseError("Twofish encryption mismatch."); + return false; + } + + if (!twofish.init(SymmetricCipher::Twofish_CBC, SymmetricCipher::Decrypt, key, iv)) { + raiseError(twofish.errorString()); + return false; + } + if (!twofish.process(data)) { + raiseError(twofish.errorString()); + return false; + } + if (data != plainText) { + raiseError("Twofish encryption mismatch."); + return false; + } -bool Crypto::testSalsa20() -{ - QByteArray salsa20Key = QByteArray::fromHex("F3F4F5F6F7F8F9FAFBFCFDFEFF000102030405060708090A0B0C0D0E0F101112"); - QByteArray salsa20iv = QByteArray::fromHex("0000000000000000"); - QByteArray salsa20Plain = QByteArray::fromHex("00000000000000000000000000000000"); - QByteArray salsa20Cipher = QByteArray::fromHex("B4C0AFA503BE7FC29A62058166D56F8F"); - bool ok; - - SymmetricCipher salsa20Stream(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt); - if (!salsa20Stream.init(salsa20Key, salsa20iv)) { - raiseError(salsa20Stream.errorString()); - return false; - } - - QByteArray salsaProcessed = salsa20Stream.process(salsa20Plain, &ok); - if (!ok) { - raiseError(salsa20Stream.errorString()); - return false; - } - if (salsaProcessed != salsa20Cipher) { - raiseError("Salsa20 stream cipher mismatch."); - return false; + return true; } - return true; -} + bool testSalsa20() + { + QByteArray salsa20Key = QByteArray::fromHex("F3F4F5F6F7F8F9FAFBFCFDFEFF000102030405060708090A0B0C0D0E0F101112"); + QByteArray salsa20iv = QByteArray::fromHex("0000000000000000"); + QByteArray salsa20Plain = QByteArray::fromHex("00000000000000000000000000000000"); + QByteArray salsa20Cipher = QByteArray::fromHex("B4C0AFA503BE7FC29A62058166D56F8F"); + + QByteArray data = salsa20Plain; + SymmetricCipher salsa20Stream; + if (!salsa20Stream.init(SymmetricCipher::Salsa20, SymmetricCipher::Encrypt, salsa20Key, salsa20iv)) { + raiseError(salsa20Stream.errorString()); + return false; + } + if (!salsa20Stream.process(data)) { + raiseError(salsa20Stream.errorString()); + return false; + } + if (data != salsa20Cipher) { + raiseError("Salsa20 stream cipher mismatch."); + return false; + } -bool Crypto::testChaCha20() -{ - QByteArray chacha20Key = QByteArray::fromHex("0000000000000000000000000000000000000000000000000000000000000000"); - QByteArray chacha20iv = QByteArray::fromHex("0000000000000000"); - QByteArray chacha20Plain = QByteArray::fromHex("0000000000000000000000000000000000000000000000000000000000000000000" - "0000000000000000000000000000000000000000000000000000000000000"); - QByteArray chacha20Cipher = QByteArray::fromHex("76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da" - "41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586"); - bool ok; - - SymmetricCipher chacha20Stream(SymmetricCipher::ChaCha20, SymmetricCipher::Stream, SymmetricCipher::Encrypt); - if (!chacha20Stream.init(chacha20Key, chacha20iv)) { - raiseError(chacha20Stream.errorString()); - return false; + return true; } - QByteArray chacha20Processed = chacha20Stream.process(chacha20Plain, &ok); - if (!ok) { - raiseError(chacha20Stream.errorString()); - return false; - } - if (chacha20Processed != chacha20Cipher) { - raiseError("ChaCha20 stream cipher mismatch."); - return false; - } + bool testChaCha20() + { + QByteArray chacha20Key = + QByteArray::fromHex("0000000000000000000000000000000000000000000000000000000000000000"); + QByteArray chacha20iv = QByteArray::fromHex("0000000000000000"); + QByteArray chacha20Plain = + QByteArray::fromHex("0000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000"); + QByteArray chacha20Cipher = + QByteArray::fromHex("76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da" + "41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586"); + + QByteArray data = chacha20Plain; + SymmetricCipher chacha20Stream; + if (!chacha20Stream.init(SymmetricCipher::ChaCha20, SymmetricCipher::Encrypt, chacha20Key, chacha20iv)) { + raiseError(chacha20Stream.errorString()); + return false; + } + if (!chacha20Stream.process(data)) { + raiseError(chacha20Stream.errorString()); + return false; + } + if (data != chacha20Cipher) { + raiseError("ChaCha20 stream cipher mismatch."); + return false; + } - return true; -} + return true; + } +} // namespace Crypto diff --git a/src/crypto/Crypto.h b/src/crypto/Crypto.h index 4346f5055d..af209a74b9 100644 --- a/src/crypto/Crypto.h +++ b/src/crypto/Crypto.h @@ -20,31 +20,12 @@ #include -class Crypto +namespace Crypto { -public: - static bool init(); - static bool initialized(); - static bool backendSelfTest(); - static QString errorString(); - static QString debugInfo(); - -private: - 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; -}; + bool init(); + bool initialized(); + QString errorString(); + QString debugInfo(); +}; // namespace Crypto #endif // KEEPASSX_CRYPTO_H diff --git a/src/crypto/CryptoHash.cpp b/src/crypto/CryptoHash.cpp index 5eab8567a3..ed8523b9cf 100644 --- a/src/crypto/CryptoHash.cpp +++ b/src/crypto/CryptoHash.cpp @@ -17,15 +17,16 @@ #include "CryptoHash.h" -#include +#include +#include #include "crypto/Crypto.h" class CryptoHashPrivate { public: - gcry_md_hd_t ctx; - int hashLen; + std::unique_ptr hashFunction; + std::unique_ptr hmacFunction; }; CryptoHash::CryptoHash(Algorithm algo, bool hmac) @@ -35,43 +36,31 @@ CryptoHash::CryptoHash(Algorithm algo, bool hmac) Q_ASSERT(Crypto::initialized()); - int algoGcrypt = -1; - unsigned int flagsGcrypt = GCRY_MD_FLAG_SECURE; - switch (algo) { 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; - 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; - default: Q_ASSERT(false); 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() { Q_D(CryptoHash); - - gcry_md_close(d->ctx); - - delete d_ptr; + delete d; } void CryptoHash::addData(const QByteArray& data) @@ -82,31 +71,45 @@ void CryptoHash::addData(const QByteArray& data) return; } - gcry_md_write(d->ctx, data.constData(), static_cast(data.size())); + try { + if (d->hmacFunction) { + d->hmacFunction->update(reinterpret_cast(data.data()), data.size()); + } else if (d->hashFunction) { + d->hashFunction->update(reinterpret_cast(data.data()), data.size()); + } + } catch (std::exception& e) { + qWarning("CryptoHash::update failed to add data: %s", e.what()); + } } void CryptoHash::setKey(const QByteArray& data) { Q_D(CryptoHash); - gcry_error_t error = gcry_md_setkey(d->ctx, data.constData(), static_cast(data.size())); - if (error) { - qWarning("Gcrypt error (setKey): %s\n %s", gcry_strerror(error), gcry_strsource(error)); + if (d->hmacFunction) { + try { + d->hmacFunction->set_key(reinterpret_cast(data.data()), data.size()); + } catch (std::exception& e) { + qWarning("CryptoHash::setKey failed to set HMAC key: %s", e.what()); + } } - Q_ASSERT(error == 0); } QByteArray CryptoHash::result() const { Q_D(const CryptoHash); - const auto result = reinterpret_cast(gcry_md_read(d->ctx, 0)); - return QByteArray(result, d->hashLen); + Botan::secure_vector result; + if (d->hmacFunction) { + result = d->hmacFunction->final(); + } else if (d->hashFunction) { + result = d->hashFunction->final(); + } + return QByteArray(reinterpret_cast(result.data()), result.size()); } QByteArray CryptoHash::hash(const QByteArray& data, Algorithm algo) { - // replace with gcry_md_hash_buffer()? CryptoHash cryptoHash(algo); cryptoHash.addData(data); return cryptoHash.result(); @@ -114,7 +117,6 @@ QByteArray CryptoHash::hash(const QByteArray& data, Algorithm algo) QByteArray CryptoHash::hmac(const QByteArray& data, const QByteArray& key, Algorithm algo) { - // replace with gcry_md_hash_buffer()? CryptoHash cryptoHash(algo, true); cryptoHash.setKey(key); cryptoHash.addData(data); diff --git a/src/crypto/Random.cpp b/src/crypto/Random.cpp index 024a82f914..430a413d13 100644 --- a/src/crypto/Random.cpp +++ b/src/crypto/Random.cpp @@ -17,46 +17,64 @@ #include "Random.h" -#include - #include "core/Global.h" #include "crypto/Crypto.h" -class RandomBackendGcrypt : public RandomBackend -{ -public: - void randomize(void* data, int len) override; -}; +#include +#include + +#include +#include QSharedPointer Random::m_instance; +QSharedPointer 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 Random::getRng() +{ + return m_rng; +} + void Random::randomize(QByteArray& ba) { - m_backend->randomize(ba.data(), ba.size()); + m_rng->randomize(reinterpret_cast(ba.data()), ba.size()); } QByteArray Random::randomArray(int len) { - QByteArray ba; - ba.resize(len); - + QByteArray ba(len, '\0'); randomize(ba); - return ba; } quint32 Random::randomUInt(quint32 limit) { - Q_ASSERT(limit != 0); Q_ASSERT(limit <= QUINT32_MAX); + if (limit == 0) { + return 0; + } quint32 rand; const quint32 ceil = QUINT32_MAX - (QUINT32_MAX % limit) - 1; - // To avoid modulo bias: - // Make sure rand is below the largest number where rand%limit==0 + // To avoid modulo bias make sure rand is below the largest number where rand%limit==0 do { - m_backend->randomize(&rand, 4); + m_rng->randomize(reinterpret_cast(&rand), 4); } while (rand > ceil); return (rand % limit); @@ -66,38 +84,3 @@ quint32 Random::randomUIntRange(quint32 min, quint32 max) { 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() -{ -} diff --git a/src/crypto/Random.h b/src/crypto/Random.h index bdf7b9acaf..2a9445a92e 100644 --- a/src/crypto/Random.h +++ b/src/crypto/Random.h @@ -22,16 +22,13 @@ #include #include -class RandomBackend -{ -public: - virtual void randomize(void* data, int len) = 0; - virtual ~RandomBackend(); -}; +#include class Random { public: + static QSharedPointer instance(); + void randomize(QByteArray& ba); QByteArray randomArray(int len); @@ -45,23 +42,17 @@ class Random */ quint32 randomUIntRange(quint32 min, quint32 max); - static Random* instance(); - -protected: - static void resetInstance(); - static void setInstance(RandomBackend* backend); + QSharedPointer getRng(); private: - static QSharedPointer m_instance; - - explicit Random(RandomBackend* backend); + explicit Random(); + Q_DISABLE_COPY(Random); - QScopedPointer m_backend; - - Q_DISABLE_COPY(Random) + static QSharedPointer m_instance; + QSharedPointer m_rng; }; -inline Random* randomGen() +static inline QSharedPointer randomGen() { return Random::instance(); } diff --git a/src/crypto/SymmetricCipher.cpp b/src/crypto/SymmetricCipher.cpp index ee4295eec5..c894ca3058 100644 --- a/src/crypto/SymmetricCipher.cpp +++ b/src/crypto/SymmetricCipher.cpp @@ -16,143 +16,247 @@ */ #include "SymmetricCipher.h" +#include #include "config-keepassx.h" -#include "crypto/SymmetricCipherGcrypt.h" -SymmetricCipher::SymmetricCipher(Algorithm algo, Mode mode, Direction direction) - : m_backend(createBackend(algo, mode, direction)) - , m_initialized(false) - , m_algo(algo) +#include +#include + +bool SymmetricCipher::init(Mode mode, Direction direction, const QByteArray& key, const QByteArray& iv) { + m_mode = mode; + if (mode == InvalidMode) { + m_error = QObject::tr("SymmetricCipher::init: Invalid cipher mode."); + return false; + } + + 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(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(iv.data()), iv.size()); + } catch (std::exception& e) { + m_mode = InvalidMode; + m_cipher.reset(); + + m_error = e.what(); + reset(); + return false; + } + + return true; } -SymmetricCipher::~SymmetricCipher() +bool SymmetricCipher::isInitalized() const { + return m_cipher; } -bool SymmetricCipher::init(const QByteArray& key, const QByteArray& iv) +bool SymmetricCipher::process(char* data, int len) { - if (!m_backend->init()) { + Q_ASSERT(isInitalized()); + if (!isInitalized()) { + m_error = QObject::tr("Cipher not initialized prior to use."); return false; } - - if (!m_backend->setKey(key)) { + if (len == 0) { + m_error = QObject::tr("Cannot process 0 length data."); return false; } - if (!m_backend->setIv(iv)) { + try { + // Block size is checked by Botan, an exception is thrown if invalid + m_cipher->process(reinterpret_cast(data), len); + return true; + } catch (std::exception& e) { + m_error = e.what(); return false; } - - m_initialized = true; - return true; } -bool SymmetricCipher::isInitalized() const +bool SymmetricCipher::process(QByteArray& data) { - return m_initialized; + return process(data.data(), data.size()); } -SymmetricCipherBackend* SymmetricCipher::createBackend(Algorithm algo, Mode mode, Direction direction) +bool SymmetricCipher::finish(QByteArray& data) { - switch (algo) { - case Aes128: - case Aes256: - case Twofish: - case Salsa20: - case ChaCha20: - return new SymmetricCipherGcrypt(algo, mode, direction); + Q_ASSERT(isInitalized()); + if (!isInitalized()) { + m_error = QObject::tr("Cipher not initialized prior to use."); + return false; + } - default: - Q_ASSERT(false); - return nullptr; + try { + // Error checking is done by Botan, an exception is thrown if invalid + Botan::secure_vector 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; } } -bool SymmetricCipher::reset() +void SymmetricCipher::reset() { - return m_backend->reset(); + m_error.clear(); + if (isInitalized()) { + m_cipher.reset(); + } } -int SymmetricCipher::keySize() const +SymmetricCipher::Mode SymmetricCipher::mode() { - return m_backend->keySize(); + return m_mode; } -int SymmetricCipher::blockSize() const +bool SymmetricCipher::aesKdf(const QByteArray& key, int rounds, QByteArray& data) { - return m_backend->blockSize(); + try { + std::unique_ptr cipher(Botan::BlockCipher::create("AES-256")); + cipher->set_key(reinterpret_cast(key.data()), key.size()); + + Botan::secure_vector 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 { - 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) { - return Aes256; - } else if (cipher == KeePass2::CIPHER_CHACHA20) { + if (uuid == KeePass2::CIPHER_AES128) { + return Aes128_CBC; + } else if (uuid == KeePass2::CIPHER_AES256) { + return Aes256_CBC; + } else if (uuid == KeePass2::CIPHER_CHACHA20) { return ChaCha20; - } else if (cipher == KeePass2::CIPHER_TWOFISH) { - return Twofish; + } else if (uuid == KeePass2::CIPHER_TWOFISH) { + return Twofish_CBC; } - qWarning("SymmetricCipher::cipherToAlgorithm: invalid UUID %s", cipher.toString().toLatin1().data()); - return InvalidAlgorithm; + qWarning("SymmetricCipher: Invalid KeePass2 Cipher UUID %s", uuid.toString().toLatin1().data()); + return InvalidMode; +} + +SymmetricCipher::Mode SymmetricCipher::stringToMode(const QString& cipher) +{ + auto cs = Qt::CaseInsensitive; + if (cipher.compare("aes-128-cbc", cs) == 0 || cipher.compare("aes128-cbc", cs) == 0) { + return Aes128_CBC; + } else if (cipher.compare("aes-256-cbc", cs) == 0 || cipher.compare("aes256-cbc", cs) == 0) { + return Aes256_CBC; + } else if (cipher.compare("aes-128-ctr", cs) == 0 || cipher.compare("aes128-ctr", cs) == 0) { + return Aes128_CTR; + } else if (cipher.compare("aes-256-ctr", cs) == 0 || cipher.compare("aes256-ctr", cs) == 0) { + return Aes256_CTR; + } else if (cipher.startsWith("twofish", cs)) { + return Twofish_CBC; + } else if (cipher.startsWith("salsa", cs)) { + return Salsa20; + } else if (cipher.startsWith("chacha", cs)) { + return ChaCha20; + } else { + return InvalidMode; + } } -QUuid SymmetricCipher::algorithmToCipher(Algorithm algo) +QString SymmetricCipher::modeToString(const Mode mode) { - switch (algo) { - case Aes128: - return KeePass2::CIPHER_AES128; - case Aes256: - return KeePass2::CIPHER_AES256; + 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 KeePass2::CIPHER_CHACHA20; - case Twofish: - return KeePass2::CIPHER_TWOFISH; + return QStringLiteral("ChaCha20"); default: - qWarning("SymmetricCipher::algorithmToCipher: invalid algorithm %d", algo); + Q_ASSERT_X(false, "SymmetricCipher::modeToString", "Invalid Mode Specified"); return {}; } } -int SymmetricCipher::algorithmIvSize(Algorithm algo) +int SymmetricCipher::defaultIvSize(Mode mode) { - switch (algo) { + 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; - 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) +int SymmetricCipher::keySize(Mode mode) { - switch (algo) { + switch (mode) { + case Aes128_CBC: + case Aes128_CTR: + return 16; + case Aes256_CBC: + case Aes256_CTR: + case Twofish_CBC: + case Salsa20: case ChaCha20: - return Stream; - case Aes256: - case Twofish: - return Cbc; + return 32; default: - qWarning("SymmetricCipher::algorithmMode: invalid algorithm %d", algo); - return InvalidMode; + return 0; } } -SymmetricCipher::Algorithm SymmetricCipher::algorithm() const +int SymmetricCipher::blockSize(Mode mode) { - return m_algo; + 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; + } } diff --git a/src/crypto/SymmetricCipher.h b/src/crypto/SymmetricCipher.h index e0e91aff12..5bc2caab9f 100644 --- a/src/crypto/SymmetricCipher.h +++ b/src/crypto/SymmetricCipher.h @@ -23,29 +23,26 @@ #include #include -#include "crypto/SymmetricCipherBackend.h" #include "format/KeePass2.h" +namespace Botan +{ + class Cipher_Mode; +} + class SymmetricCipher { public: - enum Algorithm - { - Aes128, - Aes256, - Twofish, - Salsa20, - ChaCha20, - InvalidAlgorithm = -1 - }; - enum Mode { - Cbc, - Ctr, - Ecb, - Stream, - InvalidMode = -1 + Aes128_CBC, + Aes256_CBC, + Aes128_CTR, + Aes256_CTR, + Twofish_CBC, + ChaCha20, + Salsa20, + InvalidMode = -1, }; enum Direction @@ -54,46 +51,36 @@ class SymmetricCipher Encrypt }; - SymmetricCipher(Algorithm algo, Mode mode, Direction direction); - ~SymmetricCipher(); - Q_DISABLE_COPY(SymmetricCipher) + explicit SymmetricCipher() = default; + ~SymmetricCipher() = default; - bool init(const QByteArray& key, const QByteArray& iv); 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) - { - return m_backend->process(data, ok); - } - - Q_REQUIRED_RESULT inline bool processInPlace(QByteArray& data) - { - return m_backend->processInPlace(data); - } + static bool aesKdf(const QByteArray& key, int rounds, QByteArray& data); - Q_REQUIRED_RESULT inline bool processInPlace(QByteArray& data, quint64 rounds) - { - Q_ASSERT(rounds > 0); - return m_backend->processInPlace(data, rounds); - } + void reset(); + Mode mode(); - bool reset(); - int keySize() const; - int blockSize() const; QString errorString() const; - Algorithm algorithm() const; - static Algorithm cipherToAlgorithm(const QUuid& cipher); - static QUuid algorithmToCipher(Algorithm algo); - static int algorithmIvSize(Algorithm algo); - static Mode algorithmMode(Algorithm algo); + static Mode cipherUuidToMode(const QUuid& uuid); + static Mode stringToMode(const QString& cipher); + static int defaultIvSize(Mode mode); + static int keySize(Mode mode); + static int blockSize(Mode mode); private: - static SymmetricCipherBackend* createBackend(Algorithm algo, Mode mode, Direction direction); + static QString modeToString(const Mode mode); - const QScopedPointer m_backend; - bool m_initialized; - Algorithm m_algo; + QString m_error; + Mode m_mode; + QSharedPointer m_cipher; + + Q_DISABLE_COPY(SymmetricCipher) }; #endif // KEEPASSX_SYMMETRICCIPHER_H diff --git a/src/crypto/SymmetricCipherBackend.h b/src/crypto/SymmetricCipherBackend.h deleted file mode 100644 index c8e98cfa42..0000000000 --- a/src/crypto/SymmetricCipherBackend.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2010 Felix Geyer - * - * 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 . - */ - -#ifndef KEEPASSX_SYMMETRICCIPHERBACKEND_H -#define KEEPASSX_SYMMETRICCIPHERBACKEND_H - -#include - -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 diff --git a/src/crypto/SymmetricCipherGcrypt.cpp b/src/crypto/SymmetricCipherGcrypt.cpp deleted file mode 100644 index 4d12d25a9c..0000000000 --- a/src/crypto/SymmetricCipherGcrypt.cpp +++ /dev/null @@ -1,261 +0,0 @@ -/* - * Copyright (C) 2010 Felix Geyer - * - * 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 . - */ - -#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; -} diff --git a/src/crypto/SymmetricCipherGcrypt.h b/src/crypto/SymmetricCipherGcrypt.h deleted file mode 100644 index 2f886999c3..0000000000 --- a/src/crypto/SymmetricCipherGcrypt.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2010 Felix Geyer - * - * 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 . - */ - -#ifndef KEEPASSX_SYMMETRICCIPHERGCRYPT_H -#define KEEPASSX_SYMMETRICCIPHERGCRYPT_H - -#include - -#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 diff --git a/src/crypto/argon2/argon2.h b/src/crypto/argon2/argon2.h deleted file mode 100644 index 8eba85a6f9..0000000000 --- a/src/crypto/argon2/argon2.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2018 KeePassXC Team - * - * 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 . - */ - -#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 - -#endif // KEEPASSXC_CRYPTO_ARGON2_H diff --git a/src/crypto/kdf/AesKdf.cpp b/src/crypto/kdf/AesKdf.cpp index d1daf1e5d0..8821ac1ef3 100644 --- a/src/crypto/kdf/AesKdf.cpp +++ b/src/crypto/kdf/AesKdf.cpp @@ -20,6 +20,7 @@ #include #include "crypto/CryptoHash.h" +#include "crypto/SymmetricCipher.h" #include "format/KeePass2.h" 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) { - 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; - - if (!cipher.processInPlace(*result, rounds)) { - qWarning("AesKdf::transformKeyRaw: error in SymmetricCipher::processInPlace: %s", - cipher.errorString().toUtf8().data()); - return false; - } - - return true; + return SymmetricCipher::aesKdf(seed, rounds, *result); } QSharedPointer AesKdf::clone() const @@ -106,20 +93,17 @@ QSharedPointer AesKdf::clone() const return QSharedPointer::create(*this); } -int AesKdf::benchmarkImpl(int msec) const +int AesKdf::benchmark(int msec) const { QByteArray key = QByteArray(16, '\x7E'); QByteArray seed = QByteArray(32, '\x4B'); - QByteArray iv(16, 0); - - SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Ecb, SymmetricCipher::Encrypt); - cipher.init(seed, iv); quint64 rounds = 1000000; QElapsedTimer timer; timer.start(); - if (!cipher.processInPlace(key, rounds)) { + QByteArray result; + if (!transformKeyRaw(key, seed, rounds, &result)) { return -1; } diff --git a/src/crypto/kdf/AesKdf.h b/src/crypto/kdf/AesKdf.h index d71fbb1d14..072dc95270 100644 --- a/src/crypto/kdf/AesKdf.h +++ b/src/crypto/kdf/AesKdf.h @@ -32,8 +32,7 @@ class AesKdf : public Kdf QSharedPointer clone() const override; QString toString() const override; -protected: - int benchmarkImpl(int msec) const override; + int benchmark(int msec) const override; private: Q_REQUIRED_RESULT static bool diff --git a/src/crypto/kdf/Argon2Kdf.cpp b/src/crypto/kdf/Argon2Kdf.cpp index a477a038a3..979a6825cc 100644 --- a/src/crypto/kdf/Argon2Kdf.cpp +++ b/src/crypto/kdf/Argon2Kdf.cpp @@ -18,8 +18,8 @@ #include "Argon2Kdf.h" #include +#include -#include "crypto/argon2/argon2.h" #include "format/KeePass2.h" /** @@ -163,39 +163,20 @@ bool Argon2Kdf::transform(const QByteArray& raw, QByteArray& result) const { result.clear(); result.resize(32); - return transformKeyRaw(raw, seed(), version(), type(), rounds(), memory(), parallelism(), result); -} - -bool Argon2Kdf::transformKeyRaw(const QByteArray& key, - 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(), - nullptr, - 0, - type == Type::Argon2d ? Argon2_d : Argon2_id, - version); - if (rc != ARGON2_OK) { - qWarning("Argon2 error: %s", argon2_error_message(rc)); + try { + auto algo = type() == Type::Argon2d ? "Argon2d" : "Argon2id"; + auto pwhash = Botan::PasswordHashFamily::create_or_throw(algo)->from_params(memory(), rounds(), parallelism()); + pwhash->derive_key(reinterpret_cast(result.data()), + result.size(), + raw.constData(), + raw.size(), + reinterpret_cast(seed().constData()), + seed().size()); + return true; + } catch (std::exception& e) { + qWarning("Argon2 error: %s", e.what()); return false; } - - return true; } QSharedPointer Argon2Kdf::clone() const @@ -203,20 +184,16 @@ QSharedPointer Argon2Kdf::clone() const return QSharedPointer::create(*this); } -int Argon2Kdf::benchmarkImpl(int msec) const +int Argon2Kdf::benchmark(int msec) const { - QByteArray key = QByteArray(16, '\x7E'); - QByteArray seed = QByteArray(32, '\x4B'); - - QElapsedTimer timer; - timer.start(); - - int rounds = 4; - if (transformKeyRaw(key, seed, version(), type(), rounds, memory(), parallelism(), key)) { - return static_cast(rounds * (static_cast(msec) / timer.elapsed())); + try { + auto algo = type() == Type::Argon2d ? "Argon2d" : "Argon2id"; + auto pwhash = Botan::PasswordHashFamily::create_or_throw(algo)->tune( + 32, std::chrono::milliseconds(msec), memory() / 1024); + return qMax(static_cast(1), pwhash->iterations()); + } catch (std::exception& e) { + return 1; } - - return 1; } QString Argon2Kdf::toString() const diff --git a/src/crypto/kdf/Argon2Kdf.h b/src/crypto/kdf/Argon2Kdf.h index cc8e5f07f9..b5881b45b0 100644 --- a/src/crypto/kdf/Argon2Kdf.h +++ b/src/crypto/kdf/Argon2Kdf.h @@ -45,22 +45,11 @@ class Argon2Kdf : public Kdf bool setParallelism(quint32 threads); QString toString() const override; -protected: - int benchmarkImpl(int msec) const override; + int benchmark(int msec) const override; quint32 m_version; quint64 m_memory; 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 diff --git a/src/crypto/kdf/Kdf.cpp b/src/crypto/kdf/Kdf.cpp index 6a4c7fc622..7c803a12fc 100644 --- a/src/crypto/kdf/Kdf.cpp +++ b/src/crypto/kdf/Kdf.cpp @@ -16,7 +16,6 @@ */ #include "Kdf.h" -#include "Kdf_p.h" #include @@ -68,37 +67,3 @@ void Kdf::randomizeSeed() { 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); -} diff --git a/src/crypto/kdf/Kdf.h b/src/crypto/kdf/Kdf.h index da9a2526cb..3acfc2c19f 100644 --- a/src/crypto/kdf/Kdf.h +++ b/src/crypto/kdf/Kdf.h @@ -46,7 +46,7 @@ class Kdf virtual QString toString() const = 0; - int benchmark(int msec) const; + virtual int benchmark(int msec) const = 0; /* * Default target encryption time, in MS. @@ -62,13 +62,10 @@ class Kdf static const int MAX_ENCRYPTION_TIME = 5000; protected: - virtual int benchmarkImpl(int msec) const = 0; - int m_rounds; QByteArray m_seed; private: - class BenchmarkThread; const QUuid m_uuid; }; #endif // KEEPASSX_KDF_H diff --git a/src/crypto/kdf/Kdf_p.h b/src/crypto/kdf/Kdf_p.h deleted file mode 100644 index 55ad2401b7..0000000000 --- a/src/crypto/kdf/Kdf_p.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2017 KeePassXC Team - * - * 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 . - */ - -#include "Kdf.h" - -#include - -#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 diff --git a/src/crypto/ssh/CMakeLists.txt b/src/crypto/ssh/CMakeLists.txt deleted file mode 100644 index fdaf68e411..0000000000 --- a/src/crypto/ssh/CMakeLists.txt +++ /dev/null @@ -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() diff --git a/src/crypto/ssh/bcrypt_pbkdf.cpp b/src/crypto/ssh/bcrypt_pbkdf.cpp deleted file mode 100644 index a57e2dab17..0000000000 --- a/src/crypto/ssh/bcrypt_pbkdf.cpp +++ /dev/null @@ -1,172 +0,0 @@ -/* $OpenBSD: bcrypt_pbkdf.c,v 1.13 2015/01/12 03:20:04 tedu Exp $ */ -/* - * Copyright (c) 2013 Ted Unangst - * - * 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 - -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(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(countsalt), sizeof(countsalt)); - sha2salt = ctx.result(); - - bcrypt_hash(reinterpret_cast(sha2pass.data()), reinterpret_cast(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(tmpout), sizeof(tmpout)); - sha2salt = ctx.result(); - bcrypt_hash(reinterpret_cast(sha2pass.data()), reinterpret_cast(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; -} diff --git a/src/crypto/ssh/blf.h b/src/crypto/ssh/blf.h deleted file mode 100644 index f1ac5a5c2b..0000000000 --- a/src/crypto/ssh/blf.h +++ /dev/null @@ -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 - * 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 */ - diff --git a/src/crypto/ssh/blowfish.c b/src/crypto/ssh/blowfish.c deleted file mode 100644 index e10f7e7d92..0000000000 --- a/src/crypto/ssh/blowfish.c +++ /dev/null @@ -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 - * All rights reserved. - * - * Implementation advice by David Mazieres . - * - * 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 /* used for debugging */ -#include -#endif - -#include -#ifdef HAVE_BLF_H -#include -#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)) */ - diff --git a/src/crypto/ssh/includes.h b/src/crypto/ssh/includes.h deleted file mode 100644 index ab29d77dd6..0000000000 --- a/src/crypto/ssh/includes.h +++ /dev/null @@ -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 - -#if defined(_WIN32) || defined(__HAIKU__) -#include - -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 diff --git a/src/fdosecrets/CMakeLists.txt b/src/fdosecrets/CMakeLists.txt index 35728a1798..fec3f99d0e 100644 --- a/src/fdosecrets/CMakeLists.txt +++ b/src/fdosecrets/CMakeLists.txt @@ -18,9 +18,6 @@ if(WITH_XC_FDOSECRETS) # setting storage FdoSecretsSettings.cpp - # gcrypt MPI wrapper - GcryptMPI.cpp - # dbus objects dbus/DBusClient.cpp dbus/DBusMgr.cpp @@ -34,5 +31,5 @@ if(WITH_XC_FDOSECRETS) objects/Prompt.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() diff --git a/src/fdosecrets/GcryptMPI.cpp b/src/fdosecrets/GcryptMPI.cpp deleted file mode 100644 index 4fc792deba..0000000000 --- a/src/fdosecrets/GcryptMPI.cpp +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2019 Aetf - * - * 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 . - */ - -#include "GcryptMPI.h" - -GcryptMPI MpiFromBytes(const QByteArray& bytes, bool secure, gcry_mpi_format format) -{ - auto bufLen = static_cast(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(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(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(buf), static_cast(buflen)); - - gcry_free(buf); - - return bytes; -} diff --git a/src/fdosecrets/GcryptMPI.h b/src/fdosecrets/GcryptMPI.h deleted file mode 100644 index bbc3d04397..0000000000 --- a/src/fdosecrets/GcryptMPI.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2019 Aetf - * - * 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 . - */ - -#ifndef KEEPASSXC_GCRYPTMPI_H -#define KEEPASSXC_GCRYPTMPI_H - -#include - -#include - -#include -#include - -template using deleter_from_fn = std::integral_constant; - -/** - * A simple RAII wrapper for gcry_mpi_t - */ -using GcryptMPI = std::unique_ptr>; - -/** - * A simple RAII wrapper for libgcrypt allocated memory - */ -using GcryptMemPtr = std::unique_ptr>; - -/** - * 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 diff --git a/src/fdosecrets/objects/SessionCipher.cpp b/src/fdosecrets/objects/SessionCipher.cpp index efb6da1fe2..b7129911b8 100644 --- a/src/fdosecrets/objects/SessionCipher.cpp +++ b/src/fdosecrets/objects/SessionCipher.cpp @@ -17,29 +17,11 @@ #include "SessionCipher.h" -#include "crypto/CryptoHash.h" #include "crypto/Random.h" #include "crypto/SymmetricCipher.h" -#include - -#include - -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 +#include +#include namespace FdoSecrets { @@ -47,128 +29,56 @@ namespace FdoSecrets constexpr char PlainCipher::Algorithm[]; constexpr char DhIetf1024Sha256Aes128CbcPkcs7::Algorithm[]; - DhIetf1024Sha256Aes128CbcPkcs7::DhIetf1024Sha256Aes128CbcPkcs7(const QByteArray& clientPublicKeyBytes) - : m_valid(false) + DhIetf1024Sha256Aes128CbcPkcs7::DhIetf1024Sha256Aes128CbcPkcs7(const QByteArray& clientPublicKey) { - // read client public key - auto clientPub = MpiFromBytes(clientPublicKeyBytes, false); - - // generate server side private, 128 bytes - GcryptMPI serverPrivate = nullptr; - if (NextPrivKey) { - serverPrivate = std::move(NextPrivKey); - } 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 - 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()); + try { + m_privateKey.reset(new Botan::DH_PrivateKey(*randomGen()->getRng(), Botan::DL_Group("modp/ietf/1024"))); + m_valid = updateClientPublicKey(clientPublicKey); + } catch (std::exception& e) { + qCritical("Failed to derive DH key: %s", e.what()); + m_privateKey.reset(); + m_valid = false; } - - initialize(std::move(clientPub), std::move(serverPublic), std::move(serverPrivate)); } - bool - DhIetf1024Sha256Aes128CbcPkcs7::initialize(GcryptMPI clientPublic, GcryptMPI serverPublic, GcryptMPI serverPrivate) + bool DhIetf1024Sha256Aes128CbcPkcs7::updateClientPublicKey(const QByteArray& clientPublicKey) { - QByteArray commonSecretBytes; - if (!diffieHullman(clientPublic, serverPrivate, commonSecretBytes)) { + if (!m_privateKey) { return false; } - m_privateKey = MpiToBytes(serverPrivate); - m_publicKey = MpiToBytes(serverPublic); - - m_aesKey = hkdf(commonSecretBytes); - - m_valid = true; - return true; - } - - bool DhIetf1024Sha256Aes128CbcPkcs7::diffieHullman(const GcryptMPI& clientPub, - const GcryptMPI& serverPrivate, - QByteArray& commonSecretBytes) - { - if (!clientPub || !serverPrivate) { + try { + Botan::secure_vector salt(32, '\0'); + auto dhka = m_privateKey->create_key_agreement_op(*randomGen()->getRng(), "HKDF(SHA-256)", ""); + auto aesKey = dhka->agree(16, + reinterpret_cast(clientPublicKey.constData()), + clientPublicKey.size(), + salt.data(), + salt.size()); + m_aesKey = QByteArray(reinterpret_cast(aesKey.data()), aesKey.size()); + return true; + } catch (std::exception& e) { + qCritical("Failed to update client public key: %s", e.what()); 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 output = input; - output.value.clear(); output.parameters.clear(); + output.value.clear(); - SymmetricCipher encrypter(SymmetricCipher::Aes128, SymmetricCipher::Cbc, SymmetricCipher::Encrypt); - - auto IV = randomGen()->randomArray(SymmetricCipher::algorithmIvSize(SymmetricCipher::Aes128)); - if (!encrypter.init(m_aesKey, IV)) { + SymmetricCipher encrypter; + auto IV = randomGen()->randomArray(SymmetricCipher::defaultIvSize(SymmetricCipher::Aes128_CBC)); + if (!encrypter.init(SymmetricCipher::Aes128_CBC, SymmetricCipher::Encrypt, m_aesKey, IV)) { qWarning() << "Error encrypt: " << encrypter.errorString(); return output; } output.parameters = IV; - - bool ok; output.value = input.value; - output.value = encrypter.process(padPkcs7(output.value, encrypter.blockSize()), &ok); - if (!ok) { + if (!encrypter.finish(output.value)) { qWarning() << "Error encrypt: " << encrypter.errorString(); return output; } @@ -176,50 +86,23 @@ namespace FdoSecrets 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(padLen))); - return input; - } - Secret DhIetf1024Sha256Aes128CbcPkcs7::decrypt(const Secret& input) { - auto IV = input.parameters; - SymmetricCipher decrypter(SymmetricCipher::Aes128, SymmetricCipher::Cbc, SymmetricCipher::Decrypt); - if (!decrypter.init(m_aesKey, IV)) { + SymmetricCipher decrypter; + if (!decrypter.init(SymmetricCipher::Aes128_CBC, SymmetricCipher::Decrypt, m_aesKey, input.parameters)) { qWarning() << "Error decoding: " << decrypter.errorString(); return input; } - bool ok; + Secret output = input; output.parameters.clear(); - output.value = decrypter.process(input.value, &ok); - - if (!ok) { + if (!decrypter.finish(output.value)) { qWarning() << "Error decoding: " << decrypter.errorString(); return input; } - - unpadPkcs7(output.value); 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 { return m_valid; @@ -227,16 +110,10 @@ namespace FdoSecrets QVariant DhIetf1024Sha256Aes128CbcPkcs7::negotiationOutput() const { - return m_publicKey; - } - - void DhIetf1024Sha256Aes128CbcPkcs7::fixNextServerKeys(GcryptMPI priv, GcryptMPI pub) - { - NextPrivKey = std::move(priv); - NextPubKey = std::move(pub); + if (m_valid) { + auto pubkey = m_privateKey->public_value(); + return QByteArray(reinterpret_cast(pubkey.data()), pubkey.size()); + } + return {}; } - - GcryptMPI DhIetf1024Sha256Aes128CbcPkcs7::NextPrivKey = nullptr; - GcryptMPI DhIetf1024Sha256Aes128CbcPkcs7::NextPubKey = nullptr; - } // namespace FdoSecrets diff --git a/src/fdosecrets/objects/SessionCipher.h b/src/fdosecrets/objects/SessionCipher.h index e1450784b7..e9b6262e5f 100644 --- a/src/fdosecrets/objects/SessionCipher.h +++ b/src/fdosecrets/objects/SessionCipher.h @@ -18,15 +18,15 @@ #ifndef KEEPASSXC_FDOSECRETS_SESSIONCIPHER_H #define KEEPASSXC_FDOSECRETS_SESSIONCIPHER_H -#include "fdosecrets/GcryptMPI.h" #include "fdosecrets/objects/Session.h" -class TestFdoSecrets; -class TestGuiFdoSecrets; +namespace Botan +{ + class DH_PrivateKey; +} namespace FdoSecrets { - class CipherPair { Q_DISABLE_COPY(CipherPair) @@ -69,77 +69,24 @@ namespace FdoSecrets 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: 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 decrypt(const Secret& input) override; - bool isValid() const override; - QVariant negotiationOutput() const override; - private: - /** - * For test only, fix the server side private and public key. - */ - static void fixNextServerKeys(GcryptMPI priv, GcryptMPI pub); - static GcryptMPI NextPrivKey; - static GcryptMPI NextPubKey; + bool updateClientPublicKey(const QByteArray& clientPublicKey); private: Q_DISABLE_COPY(DhIetf1024Sha256Aes128CbcPkcs7); - friend class ::TestFdoSecrets; - friend class ::TestGuiFdoSecrets; + + bool m_valid = false; + QSharedPointer m_privateKey; + QByteArray m_aesKey; }; } // namespace FdoSecrets diff --git a/src/format/Kdbx3Reader.cpp b/src/format/Kdbx3Reader.cpp index 35244f618a..752e3943d5 100644 --- a/src/format/Kdbx3Reader.cpp +++ b/src/format/Kdbx3Reader.cpp @@ -65,10 +65,9 @@ bool Kdbx3Reader::readDatabaseImpl(QIODevice* device, hash.addData(db->transformedDatabaseKey()); QByteArray finalKey = hash.result(); - SymmetricCipher::Algorithm cipher = SymmetricCipher::cipherToAlgorithm(db->cipher()); - SymmetricCipherStream cipherStream( - device, cipher, SymmetricCipher::algorithmMode(cipher), SymmetricCipher::Decrypt); - if (!cipherStream.init(finalKey, m_encryptionIV)) { + auto mode = SymmetricCipher::cipherUuidToMode(db->cipher()); + SymmetricCipherStream cipherStream(device); + if (!cipherStream.init(mode, SymmetricCipher::Decrypt, finalKey, m_encryptionIV)) { raiseError(cipherStream.errorString()); return false; } @@ -106,8 +105,8 @@ bool Kdbx3Reader::readDatabaseImpl(QIODevice* device, xmlDevice = ioCompressor.data(); } - KeePass2RandomStream randomStream(KeePass2::ProtectedStreamAlgo::Salsa20); - if (!randomStream.init(m_protectedStreamKey)) { + KeePass2RandomStream randomStream; + if (!randomStream.init(SymmetricCipher::Salsa20, m_protectedStreamKey)) { raiseError(randomStream.errorString()); return false; } diff --git a/src/format/Kdbx3Writer.cpp b/src/format/Kdbx3Writer.cpp index b1c1b820a2..fae2132349 100644 --- a/src/format/Kdbx3Writer.cpp +++ b/src/format/Kdbx3Writer.cpp @@ -35,8 +35,15 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db) m_error = false; 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 encryptionIV = randomGen()->randomArray(16); + QByteArray encryptionIV = randomGen()->randomArray(ivSize); QByteArray protectedStreamKey = randomGen()->randomArray(32); QByteArray startBytes = randomGen()->randomArray(32); 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); // write cipher stream - SymmetricCipher::Algorithm algo = SymmetricCipher::cipherToAlgorithm(db->cipher()); - SymmetricCipherStream cipherStream(device, algo, SymmetricCipher::algorithmMode(algo), SymmetricCipher::Encrypt); - cipherStream.init(finalKey, encryptionIV); + SymmetricCipherStream cipherStream(device); + cipherStream.init(mode, SymmetricCipher::Encrypt, finalKey, encryptionIV); if (!cipherStream.open(QIODevice::WriteOnly)) { raiseError(cipherStream.errorString()); return false; @@ -127,8 +133,8 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db) Q_ASSERT(outputDevice); - KeePass2RandomStream randomStream(KeePass2::ProtectedStreamAlgo::Salsa20); - if (!randomStream.init(protectedStreamKey)) { + KeePass2RandomStream randomStream; + if (!randomStream.init(SymmetricCipher::Salsa20, protectedStreamKey)) { raiseError(randomStream.errorString()); return false; } diff --git a/src/format/Kdbx4Reader.cpp b/src/format/Kdbx4Reader.cpp index baeab903c4..7d583f9fbf 100644 --- a/src/format/Kdbx4Reader.cpp +++ b/src/format/Kdbx4Reader.cpp @@ -83,13 +83,13 @@ bool Kdbx4Reader::readDatabaseImpl(QIODevice* device, return false; } - SymmetricCipher::Algorithm cipher = SymmetricCipher::cipherToAlgorithm(db->cipher()); - if (cipher == SymmetricCipher::InvalidAlgorithm) { + auto mode = SymmetricCipher::cipherUuidToMode(db->cipher()); + if (mode == SymmetricCipher::InvalidMode) { raiseError(tr("Unknown cipher")); return false; } - SymmetricCipherStream cipherStream(&hmacStream, cipher, SymmetricCipher::algorithmMode(cipher), SymmetricCipher::Decrypt); - if (!cipherStream.init(finalKey, m_encryptionIV)) { + SymmetricCipherStream cipherStream(&hmacStream); + if (!cipherStream.init(mode, SymmetricCipher::Decrypt, finalKey, m_encryptionIV)) { raiseError(cipherStream.errorString()); return false; } @@ -121,8 +121,20 @@ bool Kdbx4Reader::readDatabaseImpl(QIODevice* device, return false; } - KeePass2RandomStream randomStream(m_irsAlgo); - if (!randomStream.init(m_protectedStreamKey)) { + // TODO: Convert m_irsAlgo to Mode + 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()); return false; } diff --git a/src/format/Kdbx4Writer.cpp b/src/format/Kdbx4Writer.cpp index 1810d9aa78..9961e021d7 100644 --- a/src/format/Kdbx4Writer.cpp +++ b/src/format/Kdbx4Writer.cpp @@ -36,12 +36,12 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db) m_error = false; m_errorStr.clear(); - SymmetricCipher::Algorithm algo = SymmetricCipher::cipherToAlgorithm(db->cipher()); - if (algo == SymmetricCipher::InvalidAlgorithm) { + auto mode = SymmetricCipher::cipherUuidToMode(db->cipher()); + if (mode == SymmetricCipher::InvalidMode) { raiseError(tr("Invalid symmetric cipher algorithm.")); return false; } - int ivSize = SymmetricCipher::algorithmIvSize(algo); + int ivSize = SymmetricCipher::defaultIvSize(mode); if (ivSize < 0) { raiseError(tr("Invalid symmetric cipher IV size.", "IV = Initialization Vector for symmetric cipher")); return false; @@ -124,10 +124,9 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db) return false; } - cipherStream.reset(new SymmetricCipherStream( - hmacBlockStream.data(), algo, SymmetricCipher::algorithmMode(algo), SymmetricCipher::Encrypt)); + cipherStream.reset(new SymmetricCipherStream(hmacBlockStream.data())); - if (!cipherStream->init(finalKey, encryptionIV)) { + if (!cipherStream->init(mode, SymmetricCipher::Encrypt, finalKey, encryptionIV)) { raiseError(cipherStream->errorString()); return false; } @@ -165,8 +164,8 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db) CHECK_RETURN_FALSE(writeInnerHeaderField(outputDevice, KeePass2::InnerHeaderFieldID::End, QByteArray())); - KeePass2RandomStream randomStream(KeePass2::ProtectedStreamAlgo::ChaCha20); - if (!randomStream.init(protectedStreamKey)) { + KeePass2RandomStream randomStream; + if (!randomStream.init(SymmetricCipher::ChaCha20, protectedStreamKey)) { raiseError(randomStream.errorString()); return false; } @@ -207,7 +206,7 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db) bool Kdbx4Writer::writeInnerHeaderField(QIODevice* device, KeePass2::InnerHeaderFieldID fieldId, const QByteArray& data) { QByteArray fieldIdArr; - fieldIdArr[0] = static_cast(fieldId); + fieldIdArr.append(static_cast(fieldId)); CHECK_RETURN_FALSE(writeData(device, fieldIdArr)); CHECK_RETURN_FALSE( writeData(device, Endian::sizedIntToBytes(static_cast(data.size()), KeePass2::BYTEORDER))); @@ -294,7 +293,7 @@ bool Kdbx4Writer::serializeVariantMap(const QVariantMap& map, QByteArray& output return false; } QByteArray typeBytes; - typeBytes[0] = static_cast(fieldType); + typeBytes.append(static_cast(fieldType)); QByteArray nameBytes = k.toUtf8(); QByteArray nameLenBytes = Endian::sizedIntToBytes(nameBytes.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; - endBytes[0] = static_cast(KeePass2::VariantMapFieldType::End); + endBytes.append(static_cast(KeePass2::VariantMapFieldType::End)); CHECK_RETURN_FALSE(buf.write(endBytes) == 1); return true; } diff --git a/src/format/KdbxReader.cpp b/src/format/KdbxReader.cpp index 6ae77662c8..14966bcd23 100644 --- a/src/format/KdbxReader.cpp +++ b/src/format/KdbxReader.cpp @@ -129,7 +129,7 @@ void KdbxReader::setCipher(const QByteArray& data) return; } - if (SymmetricCipher::cipherToAlgorithm(uuid) == SymmetricCipher::InvalidAlgorithm) { + if (SymmetricCipher::cipherUuidToMode(uuid) == SymmetricCipher::InvalidMode) { raiseError(tr("Unsupported cipher")); return; } diff --git a/src/format/KeePass1Reader.cpp b/src/format/KeePass1Reader.cpp index 96e9c19149..4d372b575b 100644 --- a/src/format/KeePass1Reader.cpp +++ b/src/format/KeePass1Reader.cpp @@ -332,15 +332,14 @@ KeePass1Reader::testKeys(const QString& password, const QByteArray& keyfileData, if (finalKey.isEmpty()) { 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()); return nullptr; } @@ -362,9 +361,13 @@ KeePass1Reader::testKeys(const QString& password, const QByteArray& keyfileData, return nullptr; } - cipherStream->open(QIODevice::ReadOnly); if (success) { + if (!cipherStream->init(mode, SymmetricCipher::Decrypt, finalKey, m_encryptionIV)) { + raiseError(cipherStream->errorString()); + return nullptr; + } + cipherStream->open(QIODevice::ReadOnly); break; } else { cipherStream.reset(); diff --git a/src/format/KeePass2RandomStream.cpp b/src/format/KeePass2RandomStream.cpp index 2f8c8340d6..509e325136 100644 --- a/src/format/KeePass2RandomStream.cpp +++ b/src/format/KeePass2RandomStream.cpp @@ -20,23 +20,21 @@ #include "crypto/CryptoHash.h" #include "format/KeePass2.h" -KeePass2RandomStream::KeePass2RandomStream(KeePass2::ProtectedStreamAlgo algo) - : m_cipher(mapAlgo(algo), SymmetricCipher::Stream, SymmetricCipher::Encrypt) - , m_offset(0) +bool KeePass2RandomStream::init(SymmetricCipher::Mode mode, const QByteArray& key) { -} - -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); + switch (mode) { + case SymmetricCipher::Salsa20: { + return m_cipher.init(mode, + SymmetricCipher::Encrypt, + CryptoHash::hash(key, CryptoHash::Sha256), + KeePass2::INNER_STREAM_SALSA20_IV); + } case SymmetricCipher::ChaCha20: { 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: - qWarning("Invalid stream algorithm (%d)", m_cipher.algorithm()); + qWarning("Invalid stream cipher mode (%d)", mode); break; } return false; @@ -111,23 +109,11 @@ bool KeePass2RandomStream::loadBlock() { Q_ASSERT(m_offset == m_buffer.size()); - m_buffer.fill('\0', m_cipher.blockSize()); - if (!m_cipher.processInPlace(m_buffer)) { + m_buffer.fill('\0', m_cipher.blockSize(m_cipher.mode())); + if (!m_cipher.process(m_buffer)) { return false; } m_offset = 0; 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; - } -} \ No newline at end of file diff --git a/src/format/KeePass2RandomStream.h b/src/format/KeePass2RandomStream.h index e41f5a5d6c..d8d8dd0f9d 100644 --- a/src/format/KeePass2RandomStream.h +++ b/src/format/KeePass2RandomStream.h @@ -26,9 +26,9 @@ class KeePass2RandomStream { 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 process(const QByteArray& data, bool* ok); Q_REQUIRED_RESULT bool processInPlace(QByteArray& data); @@ -39,9 +39,7 @@ class KeePass2RandomStream SymmetricCipher m_cipher; QByteArray m_buffer; - int m_offset; - - static SymmetricCipher::Algorithm mapAlgo(KeePass2::ProtectedStreamAlgo algo); + int m_offset = 0; }; #endif // KEEPASSX_KEEPASS2RANDOMSTREAM_H diff --git a/src/format/OpData01.cpp b/src/format/OpData01.cpp index d373e58142..76e729d662 100644 --- a/src/format/OpData01.cpp +++ b/src/format/OpData01.cpp @@ -69,8 +69,8 @@ bool OpData01::decode(const QByteArray& data, const QByteArray& key, const QByte return false; } - SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt); - if (!cipher.init(key, iv)) { + SymmetricCipher cipher; + if (!cipher.init(SymmetricCipher::Aes256_CBC, SymmetricCipher::Decrypt, key, iv)) { m_errorStr = tr("Unable to init cipher for opdata01: %1").arg(cipher.errorString()); 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 achieve an even multiple of blocks. */ - const int blockSize = cipher.blockSize(); + const int blockSize = cipher.blockSize(cipher.mode()); int randomBytes = blockSize - (len % blockSize); if (randomBytes == 0) { // add random block @@ -111,7 +111,7 @@ bool OpData01::decode(const QByteArray& data, const QByteArray& key, const QByte return false; } - if (!cipher.processInPlace(qbaCT)) { + if (!cipher.process(qbaCT)) { m_errorStr = tr("Unable to process clearText in place"); return false; } diff --git a/src/format/OpVaultReader.cpp b/src/format/OpVaultReader.cpp index dab08bd822..c2337fdaf7 100644 --- a/src/format/OpVaultReader.cpp +++ b/src/format/OpVaultReader.cpp @@ -28,7 +28,8 @@ #include #include #include -#include + +#include OpVaultReader::OpVaultReader(QObject* parent) : QObject(parent) @@ -366,30 +367,12 @@ OpVaultReader::decodeB64CompositeKeys(const QString& b64, const QByteArray& encK */ 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; result->error = false; - result->encrypt = QByteArray(encKeySize, '\0'); - result->hmac = QByteArray(hmacKeySize, '\0'); - - 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++); - } + auto digest = CryptoHash::hash(keyKey, CryptoHash::Sha512); + result->encrypt = digest.left(32); + result->hmac = digest.right(32); return result; } @@ -403,43 +386,27 @@ OpVaultReader::DerivedKeyHMAC* OpVaultReader::decodeCompositeKeys(const QByteArr OpVaultReader::DerivedKeyHMAC* 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; result->error = false; - QByteArray keybuffer(keysize, '\0'); - auto err = gcry_kdf_derive(password.toUtf8().constData(), - password.size(), - GCRY_KDF_PBKDF2, - GCRY_MD_SHA512, - salt.constData(), - salt.size(), - iterations, - keysize, - keybuffer.data()); - if (err != 0) { + QByteArray out(64, '\0'); + try { + auto pwhash = Botan::PasswordHashFamily::create_or_throw("PBKDF2(SHA-512)")->from_iterations(iterations); + pwhash->derive_key(reinterpret_cast(out.data()), + out.size(), + password.toUtf8().constData(), + password.size(), + reinterpret_cast(salt.constData()), + salt.size()); + } catch (std::exception& e) { 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; } - 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; } diff --git a/src/format/OpVaultReaderBandEntry.cpp b/src/format/OpVaultReaderBandEntry.cpp index f684561b88..c9f2045b38 100644 --- a/src/format/OpVaultReaderBandEntry.cpp +++ b/src/format/OpVaultReaderBandEntry.cpp @@ -80,12 +80,12 @@ bool OpVaultReader::decryptBandEntry(const QJsonObject& bandEntry, QByteArray iv = kBA.mid(0, 16); QByteArray keyAndMacKey = kBA.mid(iv.size(), 64); - SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt); - if (!cipher.init(m_masterKey, iv)) { + SymmetricCipher cipher; + if (!cipher.init(SymmetricCipher::Aes256_CBC, SymmetricCipher::Decrypt, m_masterKey, iv)) { qCritical() << "Unable to init cipher using masterKey in UUID " << uuid; return false; } - if (!cipher.processInPlace(keyAndMacKey)) { + if (!cipher.process(keyAndMacKey)) { qCritical() << "Unable to decipher \"k\"(key+hmac) in UUID " << uuid; return false; } diff --git a/src/gui/DatabaseOpenWidget.cpp b/src/gui/DatabaseOpenWidget.cpp index 8ab2237f99..f65de6f5e6 100644 --- a/src/gui/DatabaseOpenWidget.cpp +++ b/src/gui/DatabaseOpenWidget.cpp @@ -280,13 +280,14 @@ QSharedPointer DatabaseOpenWidget::buildDatabaseKey() databaseKey->clear(); // try to get, decrypt and use PasswordKey - QSharedPointer passwordKey = TouchID::getInstance().getKey(m_filename); - if (passwordKey != NULL) { + QByteArray passwordKey; + if (TouchID::getInstance().getKey(m_filename, passwordKey)) { // check if the user cancelled the operation - if (passwordKey.isNull()) + if (passwordKey.isNull()) { return QSharedPointer(); + } - databaseKey->addKey(PasswordKey::fromRawKey(*passwordKey)); + databaseKey->addKey(PasswordKey::fromRawKey(passwordKey)); } } #endif diff --git a/src/gui/entry/EditEntryWidget.cpp b/src/gui/entry/EditEntryWidget.cpp index b4339034ae..b474ebaca0 100644 --- a/src/gui/entry/EditEntryWidget.cpp +++ b/src/gui/entry/EditEntryWidget.cpp @@ -46,8 +46,8 @@ #include "core/TimeDelta.h" #include "core/Tools.h" #ifdef WITH_XC_SSHAGENT -#include "crypto/ssh/OpenSSHKey.h" #include "sshagent/KeeAgentSettings.h" +#include "sshagent/OpenSSHKey.h" #include "sshagent/SSHAgent.h" #endif #ifdef WITH_XC_BROWSER diff --git a/src/keeshare/CMakeLists.txt b/src/keeshare/CMakeLists.txt index 333f273a3b..af4bc61a8d 100644 --- a/src/keeshare/CMakeLists.txt +++ b/src/keeshare/CMakeLists.txt @@ -19,7 +19,7 @@ if(WITH_XC_KEESHARE) ) 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 find_package(QuaZip) diff --git a/src/keeshare/KeeShare.cpp b/src/keeshare/KeeShare.cpp index 31136a5c4d..1ae19c9daf 100644 --- a/src/keeshare/KeeShare.cpp +++ b/src/keeshare/KeeShare.cpp @@ -21,7 +21,6 @@ #include "core/DatabaseIcons.h" #include "core/Group.h" #include "core/Metadata.h" -#include "crypto/ssh/OpenSSHKey.h" #include "keeshare/ShareObserver.h" #include "keeshare/Signature.h" diff --git a/src/keeshare/KeeShare.h b/src/keeshare/KeeShare.h index 952711dbfd..3a847fd0e1 100644 --- a/src/keeshare/KeeShare.h +++ b/src/keeshare/KeeShare.h @@ -18,6 +18,7 @@ #ifndef KEEPASSXC_KEESHARE_H #define KEEPASSXC_KEESHARE_H +#include #include #include diff --git a/src/keeshare/KeeShareSettings.cpp b/src/keeshare/KeeShareSettings.cpp index a640cdaed5..918743dcb9 100644 --- a/src/keeshare/KeeShareSettings.cpp +++ b/src/keeshare/KeeShareSettings.cpp @@ -16,77 +16,41 @@ */ #include "KeeShareSettings.h" + #include "core/CustomData.h" #include "core/Database.h" #include "core/DatabaseIcons.h" #include "core/Group.h" #include "core/Metadata.h" -#include "crypto/ssh/OpenSSHKey.h" +#include "crypto/Random.h" #include "keeshare/Signature.h" #include #include #include +#include +#include +#include +#include +#include +#include #include namespace KeeShareSettings { 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 specific) { QString buffer; QXmlStreamWriter writer(&buffer); writer.setCodec(QTextCodec::codecForName("UTF-8")); - writer.setAutoFormatting(true); - writer.setAutoFormattingIndent(2); - writer.writeStartDocument(); 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); writer.writeEndElement(); - writer.writeEndElement(); writer.writeEndDocument(); return buffer; } @@ -106,17 +70,25 @@ namespace KeeShareSettings if (certificate.isNull()) { return; } + auto berKey = Botan::PKCS8::BER_encode(*certificate.key); + auto baKey = QByteArray::fromRawData(reinterpret_cast(berKey.data()), berKey.size()); + writer.writeStartElement("Signer"); writer.writeCharacters(certificate.signer); writer.writeEndElement(); writer.writeStartElement("Key"); - writer.writeCharacters(certificate.key.toBase64()); + writer.writeCharacters(baKey.toBase64()); writer.writeEndElement(); } bool Certificate::operator==(const Certificate& other) const { - return key == other.key && signer == other.signer; + if (isNull() && other.isNull()) { + return true; + } else if (isNull() || other.isNull()) { + return false; + } + return key->private_key_bits() == other.key->private_key_bits() && signer == other.signer; } bool Certificate::operator!=(const Certificate& other) const @@ -126,7 +98,7 @@ namespace KeeShareSettings bool Certificate::isNull() const { - return key.isEmpty() && signer.isEmpty(); + return !key || signer.isEmpty(); } QString Certificate::fingerprint() const @@ -134,20 +106,7 @@ namespace KeeShareSettings if (isNull()) { return {}; } - return sshKey().fingerprint(); - } - - OpenSSHKey Certificate::sshKey() const - { - return unpackCertificate(*this); - } - - QString Certificate::publicKey() const - { - if (isNull()) { - return {}; - } - return sshKey().publicKey(); + return QString::fromStdString(key->fingerprint_public()); } Certificate Certificate::deserialize(QXmlStreamReader& reader) @@ -157,7 +116,16 @@ namespace KeeShareSettings if (reader.name() == "Signer") { certificate.signer = reader.readElementText(); } 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(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; @@ -165,7 +133,12 @@ namespace KeeShareSettings bool Key::operator==(const Key& other) const { - return key == other.key; + if (isNull() && other.isNull()) { + return true; + } else if (isNull() || other.isNull()) { + return false; + } + return key->private_key_bits() == other.key->private_key_bits(); } bool Key::operator!=(const Key& other) const @@ -175,20 +148,7 @@ namespace KeeShareSettings bool Key::isNull() const { - return key.isEmpty(); - } - - QString Key::privateKey() const - { - if (isNull()) { - return {}; - } - return sshKey().privateKey(); - } - - OpenSSHKey Key::sshKey() const - { - return unpackKey(*this); + return !key; } void Key::serialize(QXmlStreamWriter& writer, const Key& key) @@ -196,24 +156,37 @@ namespace KeeShareSettings if (key.isNull()) { return; } - writer.writeCharacters(key.key.toBase64()); + auto berKey = Botan::PKCS8::BER_encode(*key.key); + auto baKey = QByteArray::fromRawData(reinterpret_cast(berKey.data()), berKey.size()); + writer.writeCharacters(baKey.toBase64()); } Key Key::deserialize(QXmlStreamReader& reader) { 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(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; } Own Own::generate() { - OpenSSHKey key = OpenSSHKey::generate(false); - key.openKey(QString()); Own own; - own.key = packKey(key); - const QString name = qgetenv("USER"); // + "@" + QHostInfo::localHostName(); - own.certificate = packCertificate(key, name); + own.key.key.reset(new Botan::RSA_PrivateKey(*randomGen()->getRng(), 2048)); + auto name = qgetenv("USER"); + if (name.isEmpty()) { + name = qgetenv("USERNAME"); + } + own.certificate.signer = name; + own.certificate.key = own.key.key; return own; } @@ -249,7 +222,7 @@ namespace KeeShareSettings } } } else { - ::qWarning() << "Unknown KeeShareSettings element" << reader.name(); + qWarning("Unknown KeeShareSettings element %s", qPrintable(reader.name().toString())); reader.skipCurrentElement(); } } @@ -289,7 +262,7 @@ namespace KeeShareSettings } else if (reader.name() == "PublicKey") { own.certificate = Certificate::deserialize(reader); } else { - ::qWarning() << "Unknown KeeShareSettings element" << reader.name(); + qWarning("Unknown KeeShareSettings element %s", qPrintable(reader.name().toString())); reader.skipCurrentElement(); } } @@ -360,12 +333,12 @@ namespace KeeShareSettings if (reader.name() == "Certificate") { foreign.certificates << ScopedCertificate::deserialize(reader); } else { - ::qWarning() << "Unknown Cerificates element" << reader.name(); + qWarning("Unknown Certificates element %s", qPrintable(reader.name().toString())); reader.skipCurrentElement(); } } } else { - ::qWarning() << "Unknown KeeShareSettings element" << reader.name(); + qWarning("Unknown KeeShareSettings element %s", qPrintable(reader.name().toString())); reader.skipCurrentElement(); } } @@ -459,7 +432,7 @@ namespace KeeShareSettings } else if (reader.name() == "Password") { reference.password = QString::fromUtf8(QByteArray::fromBase64(reader.readElementText().toLatin1())); } else { - ::qWarning() << "Unknown Reference element" << reader.name(); + qWarning("Unknown Reference element %s", qPrintable(reader.name().toString())); reader.skipCurrentElement(); } } @@ -489,7 +462,7 @@ namespace KeeShareSettings } else if (reader.name() == "Certificate") { sign.certificate = KeeShareSettings::Certificate::deserialize(reader); } else { - ::qWarning() << "Unknown Sign element" << reader.name(); + qWarning("Unknown Sign element %s", qPrintable(reader.name().toString())); reader.skipCurrentElement(); } } diff --git a/src/keeshare/KeeShareSettings.h b/src/keeshare/KeeShareSettings.h index 4605d24aa6..80500fdf2f 100644 --- a/src/keeshare/KeeShareSettings.h +++ b/src/keeshare/KeeShareSettings.h @@ -20,8 +20,13 @@ #include #include +#include +#include -#include "crypto/ssh/OpenSSHKey.h" +namespace Botan +{ + class Private_Key; +} class CustomData; class QXmlStreamWriter; @@ -31,7 +36,7 @@ namespace KeeShareSettings { struct Certificate { - QByteArray key; + QSharedPointer key; QString signer; bool operator==(const Certificate& other) const; @@ -39,8 +44,6 @@ namespace KeeShareSettings bool isNull() const; QString fingerprint() const; - QString publicKey() const; - OpenSSHKey sshKey() const; static void serialize(QXmlStreamWriter& writer, const Certificate& certificate); static Certificate deserialize(QXmlStreamReader& reader); @@ -48,14 +51,13 @@ namespace KeeShareSettings struct Key { - QByteArray key; + QSharedPointer key; bool operator==(const Key& other) const; bool operator!=(const Key& other) const; bool isNull() const; QString privateKey() const; - OpenSSHKey sshKey() const; static void serialize(QXmlStreamWriter& writer, const Key& key); static Key deserialize(QXmlStreamReader& reader); diff --git a/src/keeshare/SettingsWidgetKeeShare.cpp b/src/keeshare/SettingsWidgetKeeShare.cpp index 424e78c4ba..429655b5b3 100644 --- a/src/keeshare/SettingsWidgetKeeShare.cpp +++ b/src/keeshare/SettingsWidgetKeeShare.cpp @@ -22,11 +22,13 @@ #include "core/Group.h" #include "core/Metadata.h" #include "gui/FileDialog.h" +#include "gui/MessageBox.h" #include "keeshare/KeeShare.h" #include "keeshare/KeeShareSettings.h" -#include #include +#include +#include SettingsWidgetKeeShare::SettingsWidgetKeeShare(QWidget* parent) : QWidget(parent) @@ -72,27 +74,32 @@ void SettingsWidgetKeeShare::updateForeignCertificates() { auto headers = QStringList() << tr("Path") << tr("Status"); #if defined(WITH_XC_KEESHARE_SECURE) - headers << tr("Signer") << tr("Fingerprint") << tr("Certificate"); + headers << tr("Signer") << tr("Fingerprint"); #endif m_importedCertificateModel.reset(new QStandardItemModel()); m_importedCertificateModel->setHorizontalHeaderLabels(headers); for (const auto& scopedCertificate : m_foreign.certificates) { - const auto items = QList() - << new QStandardItem(scopedCertificate.path) - << new QStandardItem(scopedCertificate.trust == KeeShareSettings::Trust::Ask - ? tr("Ask") - : (scopedCertificate.trust == KeeShareSettings::Trust::Trusted - ? tr("Trusted") - : tr("Untrusted"))) + QList items; + items << new QStandardItem(scopedCertificate.path); + + switch (scopedCertificate.trust) { + case KeeShareSettings::Trust::Ask: + items << new QStandardItem(tr("Ask")); + 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) - << new QStandardItem(scopedCertificate.isKnown() ? scopedCertificate.certificate.signer - : tr("Unknown")) - << new QStandardItem(scopedCertificate.certificate.fingerprint()) - << new QStandardItem(scopedCertificate.certificate.publicKey()) + items << new QStandardItem(scopedCertificate.isKnown() ? scopedCertificate.certificate.signer : tr("Unknown")); + items << new QStandardItem(scopedCertificate.certificate.fingerprint()); #endif - ; m_importedCertificateModel->appendRow(items); } @@ -103,8 +110,6 @@ void SettingsWidgetKeeShare::updateForeignCertificates() void SettingsWidgetKeeShare::updateOwnCertificate() { 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()); } @@ -133,8 +138,6 @@ void SettingsWidgetKeeShare::generateCertificate() { m_own = KeeShareSettings::Own::generate(); 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()); } @@ -165,16 +168,14 @@ void SettingsWidgetKeeShare::importCertificate() void SettingsWidgetKeeShare::exportCertificate() { if (KeeShare::own() != m_own) { - QMessageBox warning; - warning.setIcon(QMessageBox::Warning); - warning.setWindowTitle(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 " - "current certificate?")); - auto yes = warning.addButton(QMessageBox::StandardButton::Yes); - auto no = warning.addButton(QMessageBox::StandardButton::No); - warning.setDefaultButton(no); - warning.exec(); - if (warning.clickedButton() != yes) { + auto ans = MessageBox::warning( + this, + tr("Exporting changed certificate"), + tr("The exported certificate is not the same as the one in use. Do you want to export the " + "current certificate?"), + MessageBox::Yes | MessageBox::No, + MessageBox::No); + if (ans != MessageBox::Yes) { return; } } @@ -186,8 +187,7 @@ void SettingsWidgetKeeShare::exportCertificate() 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")); QString filename = QString("%1.%2").arg(m_own.certificate.signer).arg(filetype); - filename = fileDialog()->getSaveFileName( - this, tr("Select path"), defaultDirPath, filters, nullptr, QFileDialog::Options(0)); + filename = fileDialog()->getSaveFileName(this, tr("Select path"), defaultDirPath, filters); if (filename.isEmpty()) { return; } diff --git a/src/keeshare/SettingsWidgetKeeShare.ui b/src/keeshare/SettingsWidgetKeeShare.ui index 44fb9f1dab..bd0b98a856 100644 --- a/src/keeshare/SettingsWidgetKeeShare.ui +++ b/src/keeshare/SettingsWidgetKeeShare.ui @@ -79,87 +79,10 @@ Own certificate - + 10 - - - - Fingerprint: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Key - - - true - - - - - - - Certificate: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Signer: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Key: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Certificate - - - true - - - - - - - Signer name field - - - - - - - Fingerprint - - - true - - - @@ -207,6 +130,43 @@ + + + + Fingerprint + + + true + + + + + + + Signer name field + + + + + + + Signer: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Fingerprint: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + @@ -333,8 +293,6 @@ importOwnCertificateButton exportOwnCertificateButton ownCertificateSignerEdit - ownCertificatePrivateKeyEdit - ownCertificatePublicKeyEdit ownCertificateFingerprintEdit trustImportedCertificateButton askImportedCertificateButton diff --git a/src/keeshare/ShareExport.cpp b/src/keeshare/ShareExport.cpp index aedbc04af3..fc34dff9fa 100644 --- a/src/keeshare/ShareExport.cpp +++ b/src/keeshare/ShareExport.cpp @@ -23,6 +23,9 @@ #include "keeshare/Signature.h" #include "keys/PasswordKey.h" +#include +#include + #if defined(WITH_XC_KEESHARE_SECURE) #include #include @@ -147,9 +150,8 @@ namespace } QTextStream stream(&file); KeeShareSettings::Sign sign; - auto sshKey = own.key.sshKey(); - sshKey.openKey(QString()); - sign.signature = Signature::create(bytes, sshKey); + // TODO: check for false return + Signature::create(bytes, own.key.key, sign.signature); sign.certificate = own.certificate; stream << KeeShareSettings::Sign::serialize(sign); stream.flush(); diff --git a/src/keeshare/ShareExport.h b/src/keeshare/ShareExport.h index ff2abdc81a..db54b2258a 100644 --- a/src/keeshare/ShareExport.h +++ b/src/keeshare/ShareExport.h @@ -17,6 +17,8 @@ #ifndef KEEPASSXC_SHAREEXPORT_H #define KEEPASSXC_SHAREEXPORT_H +#include + #include "keeshare/KeeShareSettings.h" #include "keeshare/ShareObserver.h" diff --git a/src/keeshare/ShareImport.cpp b/src/keeshare/ShareImport.cpp index 38a477aaf6..88ef8374cd 100644 --- a/src/keeshare/ShareImport.cpp +++ b/src/keeshare/ShareImport.cpp @@ -22,8 +22,10 @@ #include "keeshare/Signature.h" #include "keys/PasswordKey.h" +#include #include #include +#include #if defined(WITH_XC_KEESHARE_SECURE) #include @@ -52,55 +54,43 @@ namespace KeeShareSettings::Certificate certificate; if (!sign.signature.isEmpty()) { certificate = sign.certificate; - auto key = sign.certificate.sshKey(); - key.openKey(QString()); - const auto signer = Signature(); - if (!signer.verify(data, sign.signature, key)) { + if (!Signature::verify(data, sign.certificate.key, sign.signature)) { qCritical("Invalid signature for shared container %s.", qPrintable(reference.path)); return {Invalid, KeeShareSettings::Certificate()}; } - if (ownCertificate.key == sign.certificate.key) { + // Automatically trust your own certificate + if (ownCertificate == sign.certificate) { return {Own, ownCertificate}; } } - enum Scope - { - Invalid, - Global, - Local - }; - Scope scope = Invalid; - KeeShareSettings::Trust trusted = KeeShareSettings::Trust::Ask; + for (const auto& scopedCertificate : knownCertificates) { - if (scopedCertificate.certificate.key == certificate.key && scopedCertificate.path == reference.path) { - // Global scope is overwritten by local scope - scope = Global; - trusted = scopedCertificate.trust; - } - if (scopedCertificate.certificate.key == certificate.key && scopedCertificate.path == reference.path) { - scope = Local; - trusted = scopedCertificate.trust; + if (scopedCertificate.certificate == certificate && scopedCertificate.path == reference.path) { + if (scopedCertificate.trust == KeeShareSettings::Trust::Trusted) { + return {TrustedForever, certificate}; + } else if (scopedCertificate.trust == KeeShareSettings::Trust::Untrusted) { + return {UntrustedForever, certificate}; + } + // Default to ask 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; + warning.setWindowTitle(ShareImport::tr("KeeShare Import")); if (sign.signature.isEmpty()) { warning.setIcon(QMessageBox::Warning); - warning.setWindowTitle(ShareImport::tr("Import from container without signature")); - warning.setText(ShareImport::tr("We cannot verify the source of the shared container because it is not " + warning.setText(ShareImport::tr("The source of the shared container cannot be verified because it is not " "signed. Do you really want to import from %1?") .arg(reference.path)); } else { warning.setIcon(QMessageBox::Question); - warning.setWindowTitle(ShareImport::tr("Import from container with certificate")); - warning.setText(ShareImport::tr("Do you want to trust %1 with the fingerprint of %2 from %3?") - .arg(certificate.signer, certificate.fingerprint(), reference.path)); + warning.setText(ShareImport::tr("Do you want to trust %1 with certificate fingerprint:\n%2\n%3") + .arg(reference.path) + .arg(certificate.signer) + .arg(certificate.fingerprint())); } auto untrustedOnce = warning.addButton(ShareImport::tr("Not this time"), QMessageBox::ButtonRole::NoRole); auto untrustedForever = warning.addButton(ShareImport::tr("Never"), QMessageBox::ButtonRole::NoRole); @@ -188,7 +178,7 @@ namespace const auto trusted = trust.first == TrustedForever ? KeeShareSettings::Trust::Trusted : KeeShareSettings::Trust::Untrusted; 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.path = reference.path; scopedCertificate.trust = trusted; @@ -279,7 +269,7 @@ namespace const auto trusted = trust.first == TrustedForever ? KeeShareSettings::Trust::Trusted : KeeShareSettings::Trust::Untrusted; 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.path = reference.path; scopedCertificate.trust = trusted; diff --git a/src/keeshare/ShareImport.h b/src/keeshare/ShareImport.h index 96cb67c690..739e2979ef 100644 --- a/src/keeshare/ShareImport.h +++ b/src/keeshare/ShareImport.h @@ -17,6 +17,8 @@ #ifndef KEEPASSXC_SHAREIMPORT_H #define KEEPASSXC_SHAREIMPORT_H +#include + #include "keeshare/ShareObserver.h" class ShareImport diff --git a/src/keeshare/ShareObserver.cpp b/src/keeshare/ShareObserver.cpp index f74e800a05..5d5bc7e226 100644 --- a/src/keeshare/ShareObserver.cpp +++ b/src/keeshare/ShareObserver.cpp @@ -25,6 +25,8 @@ #include "keeshare/ShareExport.h" #include "keeshare/ShareImport.h" +#include + namespace { QString resolvePath(const QString& path, QSharedPointer database) diff --git a/src/keeshare/Signature.cpp b/src/keeshare/Signature.cpp index fdc0481fba..8143d943f7 100644 --- a/src/keeshare/Signature.cpp +++ b/src/keeshare/Signature.cpp @@ -16,245 +16,53 @@ */ #include "Signature.h" -#include "core/Tools.h" -#include "crypto/Crypto.h" -#include "crypto/CryptoHash.h" -#include "crypto/ssh/OpenSSHKey.h" -#include -#include +#include "crypto/Random.h" -struct RSASigner -{ - 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 parts = key.privateParts(); - if (parts.count() != 6) { - raiseError("Unsupported signing key"); - return QString(); - } - - const QByteArray block = CryptoHash::hash(data, CryptoHash::Sha256); - - Tools::Map mpi; - Tools::Map 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 parts = key.publicParts(); - if (parts.count() != 2) { - raiseError("Unsupported verification key"); - return false; - } - - const QByteArray block = CryptoHash::hash(data, CryptoHash::Sha256); - - Tools::Map mpi; - Tools::Map 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) +bool Signature::create(const QByteArray& data, QSharedPointer key, QString& signature) { // 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 // when we integrate a proper library) // Even more, we could publish standard self signed certificates with the container // instead of the custom certificates - if (key.type() == "ssh-rsa") { - RSASigner signer; - QString result = signer.sign(data, key); - if (signer.rc != GPG_ERR_NO_ERROR) { - ::qWarning() << signer.error; + if (key->algo_name() == "RSA") { + try { + Botan::PK_Signer signer(*key, "EMSA3(SHA-256)"); + signer.update(reinterpret_cast(data.constData()), data.size()); + auto s = signer.signature(*randomGen()->getRng()); + + auto hex = QByteArray(reinterpret_cast(s.data()), s.size()).toHex(); + signature = QString("rsa|%1").arg(QString::fromLatin1(hex)); + return true; + } catch (std::exception& e) { + qWarning("KeeShare: Failed to sign data: %s", e.what()); + return false; } - return result; } - ::qWarning() << "Unsupported Public/Private key format"; - return QString(); + qWarning("Unsupported Public/Private key format"); + return false; } -bool Signature::verify(const QByteArray& data, const QString& signature, const OpenSSHKey& key) +bool Signature::verify(const QByteArray& data, QSharedPointer key, const QString& signature) { - if (key.type() == "ssh-rsa") { - RSASigner signer; - bool result = signer.verify(data, key, signature); - if (signer.rc != GPG_ERR_NO_ERROR) { - ::qWarning() << signer.error; + 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(data.constData()), data.size()); + return verifier.check_signature(reinterpret_cast(sig_s.constData()), sig_s.size()); + } catch (std::exception& e) { + qWarning("KeeShare: Failed to verify signature: %s", e.what()); + return false; } - return result; } - ::qWarning() << "Unsupported Public/Private key format"; + qWarning("Unsupported Public/Private key format"); return false; } diff --git a/src/keeshare/Signature.h b/src/keeshare/Signature.h index 59c32339ff..9c4f83bbae 100644 --- a/src/keeshare/Signature.h +++ b/src/keeshare/Signature.h @@ -18,17 +18,14 @@ #ifndef KEEPASSXC_SIGNATURE_H #define KEEPASSXC_SIGNATURE_H +#include #include -#include +#include -class QByteArray; -class OpenSSHKey; - -class Signature +namespace Signature { -public: - static QString create(const QByteArray& data, const OpenSSHKey& key); - static bool verify(const QByteArray& data, const QString& signature, const OpenSSHKey& key); -}; + bool create(const QByteArray& data, QSharedPointer key, QString& signature); + bool verify(const QByteArray& data, QSharedPointer key, const QString& signature); +}; // namespace Signature #endif // KEEPASSXC_SIGNATURE_H diff --git a/src/keeshare/group/EditGroupWidgetKeeShare.cpp b/src/keeshare/group/EditGroupWidgetKeeShare.cpp index a3b71220f8..4e2e6eeb27 100644 --- a/src/keeshare/group/EditGroupWidgetKeeShare.cpp +++ b/src/keeshare/group/EditGroupWidgetKeeShare.cpp @@ -23,7 +23,6 @@ #include "core/Group.h" #include "core/Metadata.h" #include "core/Resources.h" -#include "crypto/ssh/OpenSSHKey.h" #include "gui/FileDialog.h" #include "keeshare/KeeShare.h" diff --git a/src/keys/ChallengeResponseKey.h b/src/keys/ChallengeResponseKey.h index 263b507e0e..d772ce7d4e 100644 --- a/src/keys/ChallengeResponseKey.h +++ b/src/keys/ChallengeResponseKey.h @@ -21,6 +21,7 @@ #include #include +#include class ChallengeResponseKey { @@ -31,9 +32,13 @@ class ChallengeResponseKey } virtual ~ChallengeResponseKey() = default; - virtual QByteArray rawKey() const = 0; virtual bool challenge(const QByteArray& challenge) = 0; - virtual QUuid uuid() const + + Botan::secure_vector& rawKey() + { + return m_key; + } + QUuid uuid() const { return m_uuid; } @@ -44,6 +49,7 @@ class ChallengeResponseKey protected: QString m_error; + Botan::secure_vector m_key; private: Q_DISABLE_COPY(ChallengeResponseKey); diff --git a/src/keys/CompositeKey.cpp b/src/keys/CompositeKey.cpp index 23c830f9fa..10aef6c9f4 100644 --- a/src/keys/CompositeKey.cpp +++ b/src/keys/CompositeKey.cpp @@ -143,7 +143,7 @@ bool CompositeKey::challenge(const QByteArray& seed, QByteArray& result, QString qWarning() << "Failed to issue challenge: " << key->error(); return false; } - cryptoHash.addData(key->rawKey()); + cryptoHash.addData(key->rawKey().data()); } result = cryptoHash.result(); diff --git a/src/keys/FileKey.cpp b/src/keys/FileKey.cpp index 2ac52ae69a..1207889115 100644 --- a/src/keys/FileKey.cpp +++ b/src/keys/FileKey.cpp @@ -24,10 +24,8 @@ #include -#include +#include #include -#include -#include QUuid FileKey::UUID("a584cbc4-c9b4-437e-81bb-362ca9709273"); @@ -35,18 +33,10 @@ constexpr int FileKey::SHA256_SIZE; FileKey::FileKey() : Key(UUID) - , m_key(static_cast(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. * @@ -169,10 +159,7 @@ bool FileKey::load(const QString& fileName, QString* errorMsg) */ QByteArray FileKey::rawKey() const { - if (!m_key) { - return {}; - } - return QByteArray::fromRawData(m_key, SHA256_SIZE); + return QByteArray(m_key.data(), m_key.size()); } /** @@ -225,7 +212,7 @@ void FileKey::createXMLv2(QIODevice* device, int size) } w.writeCharacters(QChar(key[i])); } - sodium_memzero(key.data(), static_cast(key.capacity())); + Botan::secure_scrub_memory(key.data(), static_cast(key.capacity())); w.writeCharacters("\n "); w.writeEndElement(); @@ -338,7 +325,7 @@ bool FileKey::loadXml(QIODevice* device, QString* errorMsg) return false; } - sodium_memzero(rawData.data(), static_cast(rawData.capacity())); + Botan::secure_scrub_memory(rawData.data(), static_cast(rawData.capacity())); } } } @@ -346,11 +333,11 @@ bool FileKey::loadXml(QIODevice* device, QString* errorMsg) bool ok = false; 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; } - sodium_memzero(keyFileData.data.data(), static_cast(keyFileData.data.capacity())); + Botan::secure_scrub_memory(keyFileData.data.data(), static_cast(keyFileData.data.capacity())); return ok; } @@ -373,8 +360,8 @@ bool FileKey::loadBinary(QIODevice* device) return false; } - std::memcpy(m_key, data.data(), std::min(SHA256_SIZE, data.size())); - sodium_memzero(data.data(), static_cast(data.capacity())); + std::memcpy(m_key.data(), data.data(), std::min(SHA256_SIZE, data.size())); + Botan::secure_scrub_memory(data.data(), static_cast(data.capacity())); m_type = FixedBinary; return true; } @@ -402,14 +389,14 @@ bool FileKey::loadHex(QIODevice* device) } QByteArray key = QByteArray::fromHex(data); - sodium_memzero(data.data(), static_cast(data.capacity())); + Botan::secure_scrub_memory(data.data(), static_cast(data.capacity())); if (key.size() != 32) { return false; } - std::memcpy(m_key, key.data(), std::min(SHA256_SIZE, key.size())); - sodium_memzero(key.data(), static_cast(key.capacity())); + std::memcpy(m_key.data(), key.data(), std::min(SHA256_SIZE, key.size())); + Botan::secure_scrub_memory(key.data(), static_cast(key.capacity())); m_type = FixedBinaryHex; return true; @@ -434,8 +421,8 @@ bool FileKey::loadHashed(QIODevice* device) } while (!buffer.isEmpty()); auto result = cryptoHash.result(); - std::memcpy(m_key, result.data(), std::min(SHA256_SIZE, result.size())); - sodium_memzero(result.data(), static_cast(result.capacity())); + std::memcpy(m_key.data(), result.data(), std::min(SHA256_SIZE, result.size())); + Botan::secure_scrub_memory(result.data(), static_cast(result.capacity())); m_type = Hashed; return true; diff --git a/src/keys/FileKey.h b/src/keys/FileKey.h index 540dde7d23..2e104ba5ae 100644 --- a/src/keys/FileKey.h +++ b/src/keys/FileKey.h @@ -20,6 +20,7 @@ #define KEEPASSX_FILEKEY_H #include +#include #include "keys/Key.h" @@ -41,7 +42,7 @@ class FileKey : public Key }; FileKey(); - ~FileKey() override; + ~FileKey() override = default; bool load(QIODevice* device, QString* errorMsg = nullptr); bool load(const QString& fileName, QString* errorMsg = nullptr); QByteArray rawKey() const override; @@ -58,7 +59,7 @@ class FileKey : public Key bool loadHex(QIODevice* device); bool loadHashed(QIODevice* device); - char* m_key = nullptr; + Botan::secure_vector m_key; Type m_type = None; }; diff --git a/src/keys/PasswordKey.cpp b/src/keys/PasswordKey.cpp index 4393a17807..ae60786ef6 100644 --- a/src/keys/PasswordKey.cpp +++ b/src/keys/PasswordKey.cpp @@ -19,9 +19,9 @@ #include "core/Tools.h" #include "crypto/CryptoHash.h" + #include #include -#include QUuid PasswordKey::UUID("77e90411-303a-43f2-b773-853b05635ead"); @@ -29,31 +29,23 @@ constexpr int PasswordKey::SHA256_SIZE; PasswordKey::PasswordKey() : Key(UUID) - , m_key(static_cast(gcry_malloc_secure(SHA256_SIZE))) + , m_key(SHA256_SIZE) { } PasswordKey::PasswordKey(const QString& password) : Key(UUID) - , m_key(static_cast(gcry_malloc_secure(SHA256_SIZE))) + , m_key(SHA256_SIZE) { setPassword(password); } -PasswordKey::~PasswordKey() -{ - if (m_key) { - gcry_free(m_key); - m_key = nullptr; - } -} - QByteArray PasswordKey::rawKey() const { if (!m_isInitialized) { return {}; } - return QByteArray::fromRawData(m_key, SHA256_SIZE); + return QByteArray(m_key.data(), m_key.size()); } void PasswordKey::setPassword(const QString& password) @@ -64,7 +56,7 @@ void PasswordKey::setPassword(const QString& password) void PasswordKey::setHash(const QByteArray& hash) { 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; } diff --git a/src/keys/PasswordKey.h b/src/keys/PasswordKey.h index b84506673a..e8542d88b9 100644 --- a/src/keys/PasswordKey.h +++ b/src/keys/PasswordKey.h @@ -18,6 +18,8 @@ #ifndef KEEPASSX_PASSWORDKEY_H #define KEEPASSX_PASSWORDKEY_H +#include + #include #include @@ -30,7 +32,7 @@ class PasswordKey : public Key PasswordKey(); explicit PasswordKey(const QString& password); - ~PasswordKey() override; + ~PasswordKey() override = default; QByteArray rawKey() const override; void setPassword(const QString& password); void setHash(const QByteArray& hash); @@ -40,7 +42,7 @@ class PasswordKey : public Key private: static constexpr int SHA256_SIZE = 32; - char* m_key = nullptr; + Botan::secure_vector m_key; bool m_isInitialized = false; }; diff --git a/src/keys/YkChallengeResponseKey.cpp b/src/keys/YkChallengeResponseKey.cpp index 4bf2ab196b..a729474dd0 100644 --- a/src/keys/YkChallengeResponseKey.cpp +++ b/src/keys/YkChallengeResponseKey.cpp @@ -31,10 +31,6 @@ #include #include -#include -#include -#include - QUuid YkChallengeResponseKey::UUID("e092495c-e77d-498b-84a1-05ae0d955508"); YkChallengeResponseKey::YkChallengeResponseKey(YubiKeySlot keySlot) @@ -43,35 +39,17 @@ 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(m_keySize)); -} - bool YkChallengeResponseKey::challenge(const QByteArray& challenge) { m_error.clear(); - QByteArray key; + QByteArray response; auto result = - AsyncTask::runAndWaitForFuture([&] { return YubiKey::instance()->challenge(m_keySlot, challenge, key); }); + AsyncTask::runAndWaitForFuture([&] { return YubiKey::instance()->challenge(m_keySlot, challenge, response); }); if (result == YubiKey::SUCCESS) { - if (m_key) { - gcry_free(m_key); - } - m_keySize = static_cast(key.size()); - m_key = static_cast(gcry_malloc_secure(m_keySize)); - std::memcpy(m_key, key.data(), m_keySize); - sodium_memzero(key.data(), static_cast(key.capacity())); + m_key.resize(response.size()); + Botan::copy_mem(m_key.data(), response.constData(), response.size()); + Botan::secure_scrub_memory(response.data(), response.capacity()); } else { // Record the error message m_error = YubiKey::instance()->errorMessage(); diff --git a/src/keys/YkChallengeResponseKey.h b/src/keys/YkChallengeResponseKey.h index ba213f4891..5ea38603c0 100644 --- a/src/keys/YkChallengeResponseKey.h +++ b/src/keys/YkChallengeResponseKey.h @@ -22,24 +22,17 @@ #include "keys/ChallengeResponseKey.h" #include "keys/drivers/YubiKey.h" -#include - -class YkChallengeResponseKey : public QObject, public ChallengeResponseKey +class YkChallengeResponseKey : public ChallengeResponseKey { - Q_OBJECT - public: static QUuid UUID; explicit YkChallengeResponseKey(YubiKeySlot keySlot = {}); - ~YkChallengeResponseKey() override; + ~YkChallengeResponseKey() override = default; - QByteArray rawKey() const override; bool challenge(const QByteArray& challenge) override; private: - char* m_key = nullptr; - std::size_t m_keySize = 0; YubiKeySlot m_keySlot; }; diff --git a/src/keys/YkChallengeResponseKeyCLI.cpp b/src/keys/YkChallengeResponseKeyCLI.cpp deleted file mode 100644 index 4c78e4d0a8..0000000000 --- a/src/keys/YkChallengeResponseKeyCLI.cpp +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2019 KeePassXC Team - * - * 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 . - */ - -#include "keys/YkChallengeResponseKeyCLI.h" -#include "keys/drivers/YubiKey.h" - -#include "core/Tools.h" -#include "crypto/CryptoHash.h" -#include "crypto/Random.h" - -#include - -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; -} diff --git a/src/keys/YkChallengeResponseKeyCLI.h b/src/keys/YkChallengeResponseKeyCLI.h deleted file mode 100644 index 56025e7e1b..0000000000 --- a/src/keys/YkChallengeResponseKeyCLI.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2019 KeePassXC Team - * - * 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 . - */ - -#ifndef KEEPASSX_YK_CHALLENGERESPONSEKEYCLI_H -#define KEEPASSX_YK_CHALLENGERESPONSEKEYCLI_H - -#include "core/Global.h" -#include "keys/ChallengeResponseKey.h" -#include "keys/drivers/YubiKey.h" - -#include -#include -#include - -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 diff --git a/src/main.cpp b/src/main.cpp index 20359b1af8..5f175173e6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -96,6 +96,14 @@ int main(int argc, char** argv) return EXIT_SUCCESS; } + // Show debug information and then exit + if (parser.isSet(debugInfoOption)) { + QTextStream out(stdout, QIODevice::WriteOnly); + QString debugInfo = Tools::debugInfo().append("\n").append(Crypto::debugInfo()); + out << debugInfo << endl; + return EXIT_SUCCESS; + } + // Process config file options early if (parser.isSet(configOption) || parser.isSet(localConfigOption)) { Config::createConfigFromFile(parser.value(configOption), parser.value(localConfigOption)); @@ -111,15 +119,6 @@ int main(int argc, char** argv) return EXIT_SUCCESS; } - // Apply the configured theme before creating any GUI elements - app.applyTheme(); - -#if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) - QGuiApplication::setDesktopFileName(app.property("KPXC_QUALIFIED_APPNAME").toString() + QStringLiteral(".desktop")); -#endif - - Application::bootstrap(); - if (!Crypto::init()) { QString error = QObject::tr("Fatal error while testing the cryptographic functions."); error.append("\n"); @@ -128,14 +127,14 @@ int main(int argc, char** argv) return EXIT_FAILURE; } - // Displaying the debugging informations must be done after Crypto::init, - // to make sure we know which libgcrypt version is used. - if (parser.isSet(debugInfoOption)) { - QTextStream out(stdout, QIODevice::WriteOnly); - QString debugInfo = Tools::debugInfo().append("\n").append(Crypto::debugInfo()); - out << debugInfo << endl; - return EXIT_SUCCESS; - } + // Apply the configured theme before creating any GUI elements + app.applyTheme(); + +#if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) + QGuiApplication::setDesktopFileName(app.property("KPXC_QUALIFIED_APPNAME").toString() + QStringLiteral(".desktop")); +#endif + + Application::bootstrap(); MainWindow mainWindow; diff --git a/src/proxy/CMakeLists.txt b/src/proxy/CMakeLists.txt index 86c6af964b..d013b53077 100755 --- a/src/proxy/CMakeLists.txt +++ b/src/proxy/CMakeLists.txt @@ -21,7 +21,7 @@ if(WITH_XC_BROWSER) # Alloc must be defined in a static library to prevent clashing with clang ASAN definitions 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}) target_link_libraries(keepassxc-proxy proxy_alloc Qt5::Core Qt5::Network) diff --git a/src/crypto/ssh/ASN1Key.cpp b/src/sshagent/ASN1Key.cpp similarity index 71% rename from src/crypto/ssh/ASN1Key.cpp rename to src/sshagent/ASN1Key.cpp index b585d3abda..7fafbeb2b6 100644 --- a/src/crypto/ssh/ASN1Key.cpp +++ b/src/sshagent/ASN1Key.cpp @@ -17,9 +17,7 @@ */ #include "ASN1Key.h" -#include "crypto/ssh/BinaryStream.h" - -#include +#include "BinaryStream.h" namespace { @@ -55,16 +53,6 @@ namespace return true; } - bool parsePublicHeader(BinaryStream& stream) - { - quint8 tag; - quint32 len; - - nextTag(stream, tag, len); - - return (tag == TAG_SEQUENCE); - } - bool parsePrivateHeader(BinaryStream& stream, quint8 wantedType) { quint8 tag; @@ -103,27 +91,6 @@ namespace stream.read(target); 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(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 bool ASN1Key::parseDSA(QByteArray& ba, OpenSSHKey& key) @@ -161,34 +128,7 @@ bool ASN1Key::parseDSA(QByteArray& ba, OpenSSHKey& key) return true; } -bool ASN1Key::parsePublicRSA(QByteArray& ba, OpenSSHKey& key) -{ - BinaryStream stream(&ba); - - if (!parsePublicHeader(stream)) { - return false; - } - - QByteArray n, e; - readInt(stream, n); - readInt(stream, e); - - QList publicData; - publicData.append(e); - publicData.append(n); - - QList 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) +bool ASN1Key::parseRSA(QByteArray& ba, OpenSSHKey& key) { BinaryStream stream(&ba); @@ -206,6 +146,7 @@ bool ASN1Key::parsePrivateRSA(QByteArray& ba, OpenSSHKey& key) readInt(stream, dq); readInt(stream, qinv); + // Note: To properly calculate the key fingerprint, e and n are reversed per RFC 4253 QList publicData; publicData.append(e); publicData.append(n); @@ -214,7 +155,7 @@ bool ASN1Key::parsePrivateRSA(QByteArray& ba, OpenSSHKey& key) privateData.append(n); privateData.append(e); privateData.append(d); - privateData.append(calculateIqmp(p, q)); + privateData.append(qinv); privateData.append(p); privateData.append(q); diff --git a/src/crypto/ssh/ASN1Key.h b/src/sshagent/ASN1Key.h similarity index 89% rename from src/crypto/ssh/ASN1Key.h rename to src/sshagent/ASN1Key.h index 0a199d3576..58dbcd4df5 100644 --- a/src/crypto/ssh/ASN1Key.h +++ b/src/sshagent/ASN1Key.h @@ -25,8 +25,7 @@ namespace ASN1Key { bool parseDSA(QByteArray& ba, OpenSSHKey& key); - bool parsePrivateRSA(QByteArray& ba, OpenSSHKey& key); - bool parsePublicRSA(QByteArray& ba, OpenSSHKey& key); + bool parseRSA(QByteArray& ba, OpenSSHKey& key); } // namespace ASN1Key #endif // KEEPASSXC_ASN1KEY_H diff --git a/src/crypto/ssh/BinaryStream.cpp b/src/sshagent/BinaryStream.cpp similarity index 100% rename from src/crypto/ssh/BinaryStream.cpp rename to src/sshagent/BinaryStream.cpp diff --git a/src/crypto/ssh/BinaryStream.h b/src/sshagent/BinaryStream.h similarity index 100% rename from src/crypto/ssh/BinaryStream.h rename to src/sshagent/BinaryStream.h diff --git a/src/sshagent/CMakeLists.txt b/src/sshagent/CMakeLists.txt index d42059a5d0..9694674158 100644 --- a/src/sshagent/CMakeLists.txt +++ b/src/sshagent/CMakeLists.txt @@ -4,10 +4,13 @@ if(WITH_XC_SSHAGENT) set(sshagent_SOURCES AgentSettingsPage.cpp AgentSettingsWidget.cpp + ASN1Key.cpp + BinaryStream.cpp KeeAgentSettings.cpp + OpenSSHKey.cpp SSHAgent.cpp ) 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() diff --git a/src/sshagent/KeeAgentSettings.cpp b/src/sshagent/KeeAgentSettings.cpp index e9f6e4bdd4..b5cf0f4266 100644 --- a/src/sshagent/KeeAgentSettings.cpp +++ b/src/sshagent/KeeAgentSettings.cpp @@ -455,7 +455,7 @@ bool KeeAgentSettings::toOpenSSHKey(const QString& username, return false; } - if (key.encrypted() && (decrypt || key.publicParts().isEmpty())) { + if (key.encrypted() && decrypt) { if (!key.openKey(password)) { m_error = key.errorString(); return false; diff --git a/src/sshagent/KeeAgentSettings.h b/src/sshagent/KeeAgentSettings.h index 3cc7016661..c76ac3b164 100644 --- a/src/sshagent/KeeAgentSettings.h +++ b/src/sshagent/KeeAgentSettings.h @@ -19,9 +19,9 @@ #ifndef KEEAGENTSETTINGS_H #define KEEAGENTSETTINGS_H +#include "OpenSSHKey.h" #include "core/Entry.h" #include "core/EntryAttachments.h" -#include "crypto/ssh/OpenSSHKey.h" #include #include diff --git a/src/crypto/ssh/OpenSSHKey.cpp b/src/sshagent/OpenSSHKey.cpp similarity index 51% rename from src/crypto/ssh/OpenSSHKey.cpp rename to src/sshagent/OpenSSHKey.cpp index 7cabf38c6a..4160bf7e7e 100644 --- a/src/crypto/ssh/OpenSSHKey.cpp +++ b/src/sshagent/OpenSSHKey.cpp @@ -18,193 +18,21 @@ #include "OpenSSHKey.h" +#include "ASN1Key.h" +#include "BinaryStream.h" #include "core/Tools.h" #include "crypto/SymmetricCipher.h" -#include "crypto/ssh/ASN1Key.h" -#include "crypto/ssh/BinaryStream.h" #include #include #include -#include +#include const QString OpenSSHKey::TYPE_DSA_PRIVATE = "DSA 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"; -namespace -{ - QPair> 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 data; - while (stream.readString(temp)) { - data << temp; - } - return ::qMakePair(type, data); - } - - QByteArray binarySerialize(const QString& type, const QList& 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 mpi; - Tools::Map 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 publicParts; - QList 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) : QObject(parent) , m_type(QString()) @@ -251,20 +79,6 @@ const QString OpenSSHKey::type() const 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 { if (m_rawPublicData.isEmpty()) { @@ -301,24 +115,6 @@ const QString OpenSSHKey::comment() const 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 { if (m_rawPublicData.isEmpty()) { @@ -438,7 +234,7 @@ bool OpenSSHKey::parsePKCS1PEM(const QByteArray& in) 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; } else if (m_rawType == TYPE_OPENSSH_PRIVATE) { BinaryStream stream(&data); @@ -508,7 +304,7 @@ bool OpenSSHKey::encrypted() const bool OpenSSHKey::openKey(const QString& passphrase) { - QScopedPointer cipher; + QScopedPointer cipher(new SymmetricCipher()); if (!m_rawPrivateData.isEmpty()) { return true; @@ -519,94 +315,89 @@ bool OpenSSHKey::openKey(const QString& passphrase) return false; } - if (m_cipherName.compare("aes-128-cbc", Qt::CaseInsensitive) == 0) { - 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) { - cipher.reset(new SymmetricCipher(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt)); - } else if (m_cipherName == "aes256-ctr" || m_cipherName.compare("aes-256-ctr", Qt::CaseInsensitive) == 0) { - cipher.reset(new SymmetricCipher(SymmetricCipher::Aes256, SymmetricCipher::Ctr, SymmetricCipher::Decrypt)); - } else if (m_cipherName != "none") { - m_error = tr("Unknown cipher: %1").arg(m_cipherName); - return false; - } + QByteArray rawData = m_rawData; - if (m_kdfName == "bcrypt") { - if (!cipher) { - m_error = tr("Trying to run KDF without cipher"); + if (m_cipherName != "none") { + auto cipherMode = SymmetricCipher::stringToMode(m_cipherName); + if (cipherMode == SymmetricCipher::InvalidMode) { + m_error = tr("Unknown cipher: %1").arg(m_cipherName); return false; } - if (passphrase.isEmpty()) { - m_error = tr("Passphrase is required to decrypt this key"); - return false; - } + QByteArray keyData, ivData; - BinaryStream optionStream(&m_kdfOptions); + if (m_kdfName == "bcrypt") { + if (passphrase.isEmpty()) { + m_error = tr("Passphrase is required to decrypt this key"); + return false; + } - QByteArray salt; - quint32 rounds; + int keySize = cipher->keySize(cipherMode); + int blockSize = 16; + + BinaryStream optionStream(&m_kdfOptions); + + QByteArray salt; + quint32 rounds; + + optionStream.readString(salt); + optionStream.read(rounds); + + QByteArray decryptKey(keySize + blockSize, '\0'); + try { + auto baPass = passphrase.toUtf8(); + auto pwhash = Botan::PasswordHashFamily::create_or_throw("Bcrypt-PBKDF")->from_iterations(rounds); + pwhash->derive_key(reinterpret_cast(decryptKey.data()), + decryptKey.size(), + baPass.constData(), + baPass.size(), + reinterpret_cast(salt.constData()), + salt.size()); + } catch (std::exception& e) { + m_error = tr("Key derivation failed: %1").arg(e.what()); + return false; + } - optionStream.readString(salt); - optionStream.read(rounds); + keyData = decryptKey.left(keySize); + ivData = decryptKey.right(blockSize); + } else if (m_kdfName == "md5") { + if (m_cipherIV.length() < 8) { + m_error = tr("Cipher IV is too short for MD5 kdf"); + return false; + } - QByteArray decryptKey; - decryptKey.fill(0, cipher->keySize() + cipher->blockSize()); + int keySize = cipher->keySize(cipherMode); + + QByteArray mdBuf; + do { + QCryptographicHash hash(QCryptographicHash::Md5); + hash.addData(mdBuf); + hash.addData(passphrase.toUtf8()); + hash.addData(m_cipherIV.data(), 8); + mdBuf = hash.result(); + keyData.append(mdBuf); + } while (keyData.size() < keySize); + + if (keyData.size() > keySize) { + // If our key size isn't a multiple of 16 (e.g. AES-192 or something), + // then we will need to truncate it. + keyData.resize(keySize); + } - QByteArray phraseData = passphrase.toUtf8(); - if (bcrypt_pbkdf(phraseData, salt, decryptKey, rounds) < 0) { - m_error = tr("Key derivation failed, key file corrupted?"); + ivData = m_cipherIV; + } else if (m_kdfName != "none") { + m_error = tr("Unknown KDF: %1").arg(m_kdfName); return false; } - QByteArray keyData, ivData; - keyData.setRawData(decryptKey.data(), cipher->keySize()); - 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") { - if (m_cipherIV.length() < 8) { - m_error = tr("Cipher IV is too short for MD5 kdf"); + // Initialize the cipher using the processed key and iv data + if (!cipher->init(cipherMode, SymmetricCipher::Decrypt, keyData, ivData)) { + m_error = tr("Failed to initialize cipher: %1").arg(cipher->errorString()); return false; } - - QByteArray keyData; - QByteArray mdBuf; - do { - QCryptographicHash hash(QCryptographicHash::Md5); - hash.addData(mdBuf); - hash.addData(passphrase.toUtf8()); - hash.addData(m_cipherIV.data(), 8); - mdBuf = hash.result(); - keyData.append(mdBuf); - } while (keyData.size() < cipher->keySize()); - - if (keyData.size() > cipher->keySize()) { - // If our key size isn't a multiple of 16 (e.g. AES-192 or something), - // then we will need to truncate it. - keyData.resize(cipher->keySize()); - } - - if (!cipher->init(keyData, m_cipherIV)) { - m_error = cipher->errorString(); - return false; - } - } else if (m_kdfName != "none") { - m_error = tr("Unknown KDF: %1").arg(m_kdfName); - return false; - } - - QByteArray rawData = m_rawData; - - if (cipher && cipher->isInitalized()) { - bool ok = false; - rawData = cipher->process(rawData, &ok); - if (!ok) { - m_error = tr("Decryption failed, wrong passphrase?"); + // Decrypt the raw data, we do not use finish because padding is handled separately + if (!cipher->process(rawData)) { + m_error = tr("Decryption failed: %1").arg(cipher->errorString()); return false; } } @@ -619,13 +410,7 @@ bool OpenSSHKey::openKey(const QString& passphrase) return true; } else if (m_rawType == TYPE_RSA_PRIVATE) { - if (!ASN1Key::parsePrivateRSA(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)) { + if (!ASN1Key::parseRSA(rawData, *this)) { m_error = tr("Decryption failed, wrong passphrase?"); return false; } @@ -779,49 +564,6 @@ bool OpenSSHKey::writePrivate(BinaryStream& stream) return true; } -QList OpenSSHKey::publicParts() const -{ - return m_rawPublicData; -} - -QList 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) { return qHash(key.fingerprint()); diff --git a/src/crypto/ssh/OpenSSHKey.h b/src/sshagent/OpenSSHKey.h similarity index 82% rename from src/crypto/ssh/OpenSSHKey.h rename to src/sshagent/OpenSSHKey.h index 85c288b9f9..99689da5b9 100644 --- a/src/crypto/ssh/OpenSSHKey.h +++ b/src/sshagent/OpenSSHKey.h @@ -19,7 +19,8 @@ #ifndef KEEPASSXC_OPENSSHKEY_H #define KEEPASSXC_OPENSSHKEY_H -#include +#include +#include class BinaryStream; @@ -27,8 +28,6 @@ class OpenSSHKey : public QObject { Q_OBJECT public: - static OpenSSHKey generate(bool secure = true); - explicit OpenSSHKey(QObject* parent = nullptr); OpenSSHKey(const OpenSSHKey& other); bool operator==(const OpenSSHKey& other) const; @@ -39,11 +38,9 @@ class OpenSSHKey : public QObject const QString cipherName() const; const QString type() const; - int keyLength() const; const QString fingerprint(QCryptographicHash::Algorithm algo = QCryptographicHash::Sha256) const; const QString comment() const; const QString publicKey() const; - const QString privateKey() const; const QString errorString() const; void setType(const QString& type); @@ -58,24 +55,10 @@ class OpenSSHKey : public QObject bool writePublic(BinaryStream& stream); bool writePrivate(BinaryStream& stream); - QList publicParts() const; - QList privateParts() const; - const QString& privateType() const; - static const QString TYPE_DSA_PRIVATE; static const QString TYPE_RSA_PRIVATE; - static const QString TYPE_RSA_PUBLIC; 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: bool extractPEM(const QByteArray& in, QByteArray& out); diff --git a/src/sshagent/SSHAgent.cpp b/src/sshagent/SSHAgent.cpp index 1e67ae3c8f..81610e7d43 100644 --- a/src/sshagent/SSHAgent.cpp +++ b/src/sshagent/SSHAgent.cpp @@ -18,12 +18,12 @@ #include "SSHAgent.h" +#include "BinaryStream.h" +#include "OpenSSHKey.h" #include "core/Config.h" #include "core/Database.h" #include "core/Group.h" #include "core/Metadata.h" -#include "crypto/ssh/BinaryStream.h" -#include "crypto/ssh/OpenSSHKey.h" #include "sshagent/KeeAgentSettings.h" #include diff --git a/src/sshagent/SSHAgent.h b/src/sshagent/SSHAgent.h index d18cd47704..2318fdffd4 100644 --- a/src/sshagent/SSHAgent.h +++ b/src/sshagent/SSHAgent.h @@ -23,7 +23,7 @@ #include #include -#include "crypto/ssh/OpenSSHKey.h" +#include "OpenSSHKey.h" #include "sshagent/KeeAgentSettings.h" class SSHAgent : public QObject diff --git a/src/streams/SymmetricCipherStream.cpp b/src/streams/SymmetricCipherStream.cpp index b930d8023b..afe6c1b53a 100644 --- a/src/streams/SymmetricCipherStream.cpp +++ b/src/streams/SymmetricCipherStream.cpp @@ -17,12 +17,9 @@ #include "SymmetricCipherStream.h" -SymmetricCipherStream::SymmetricCipherStream(QIODevice* baseDevice, - SymmetricCipher::Algorithm algo, - SymmetricCipher::Mode mode, - SymmetricCipher::Direction direction) +SymmetricCipherStream::SymmetricCipherStream(QIODevice* baseDevice) : LayeredStream(baseDevice) - , m_cipher(new SymmetricCipher(algo, mode, direction)) + , m_cipher(new SymmetricCipher()) , m_bufferPos(0) , m_bufferFilling(false) , m_error(false) @@ -37,14 +34,18 @@ SymmetricCipherStream::~SymmetricCipherStream() 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) { setErrorString(m_cipher->errorString()); + return false; } - m_streamCipher = m_cipher->blockSize() == 1; - return m_isInitialized; + m_streamCipher = m_cipher->blockSize(m_cipher->mode()) == 1; + return true; } void SymmetricCipherStream::resetInternalState() @@ -145,41 +146,23 @@ bool SymmetricCipherStream::readBlock() m_bufferFilling = true; return false; } else { - if (!m_cipher->processInPlace(m_buffer)) { - m_error = true; - setErrorString(m_cipher->errorString()); - return false; - } m_bufferPos = 0; m_bufferFilling = false; - 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; - setErrorString("Invalid padding."); - 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; + if (!m_streamCipher && m_baseDevice->atEnd()) { + if (!m_cipher->finish(m_buffer)) { + m_error = true; + setErrorString(m_cipher->errorString()); + return false; + } + } else if (m_buffer.size() > 0) { + if (!m_cipher->process(m_buffer)) { + m_error = true; + setErrorString(m_cipher->errorString()); + return false; } - } else { - return true; } + return m_buffer.size() > 0; } } @@ -222,14 +205,13 @@ bool SymmetricCipherStream::writeBlock(bool lastBlock) Q_ASSERT(m_streamCipher || lastBlock || (m_buffer.size() == blockSize())); if (lastBlock && !m_streamCipher) { - // PKCS7 padding - int padLen = blockSize() - m_buffer.size(); - for (int i = 0; i < padLen; i++) { - m_buffer.append(static_cast(padLen)); + QByteArray end; + if (!m_cipher->finish(m_buffer)) { + m_error = true; + setErrorString(m_cipher->errorString()); + return false; } - } - - if (!m_cipher->processInPlace(m_buffer)) { + } else if (!m_cipher->process(m_buffer)) { m_error = true; setErrorString(m_cipher->errorString()); return false; @@ -250,5 +232,5 @@ int SymmetricCipherStream::blockSize() const if (m_streamCipher) { return 1024; } - return m_cipher->blockSize(); + return m_cipher->blockSize(m_cipher->mode()); } diff --git a/src/streams/SymmetricCipherStream.h b/src/streams/SymmetricCipherStream.h index 4f8feaa332..3f11dc8bdd 100644 --- a/src/streams/SymmetricCipherStream.h +++ b/src/streams/SymmetricCipherStream.h @@ -29,12 +29,10 @@ class SymmetricCipherStream : public LayeredStream Q_OBJECT public: - SymmetricCipherStream(QIODevice* baseDevice, - SymmetricCipher::Algorithm algo, - SymmetricCipher::Mode mode, - SymmetricCipher::Direction direction); + SymmetricCipherStream(QIODevice* baseDevice); ~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 reset() override; void close() override; diff --git a/src/touchid/TouchID.h b/src/touchid/TouchID.h index d375220548..b330758860 100644 --- a/src/touchid/TouchID.h +++ b/src/touchid/TouchID.h @@ -33,7 +33,7 @@ class TouchID bool storeKey(const QString& databasePath, const QByteArray& passwordKey); - QSharedPointer getKey(const QString& databasePath) const; + bool getKey(const QString& databasePath, QByteArray& passwordKey) const; bool isAvailable(); diff --git a/src/touchid/TouchID.mm b/src/touchid/TouchID.mm index 7df5ad556b..3502761310 100644 --- a/src/touchid/TouchID.mm +++ b/src/touchid/TouchID.mm @@ -57,28 +57,25 @@ inline QString hash(const QString& value) } // generate random AES 256bit key and IV - Random* random = randomGen(); - QByteArray randomKey = random->randomArray(32); - QByteArray randomIV = random->randomArray(16); + QByteArray randomKey = randomGen()->randomArray(32); + QByteArray randomIV = randomGen()->randomArray(16); - bool ok; - SymmetricCipher aes256Encrypt(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Encrypt); - - if (!aes256Encrypt.init(randomKey, randomIV)) { + SymmetricCipher aes256Encrypt; + if (!aes256Encrypt.init(SymmetricCipher::Aes256_CBC, SymmetricCipher::Encrypt, randomKey, randomIV)) { debug("TouchID::storeKey - Error initializing encryption: %s", aes256Encrypt.errorString().toUtf8().constData()); return false; } // encrypt and keep result in memory - QByteArray encryptedMasterKey = aes256Encrypt.process(passwordKey, &ok); - if (!ok) { + QByteArray encryptedMasterKey = passwordKey; + if (!aes256Encrypt.process(encryptedMasterKey)) { debug("TouchID::storeKey - Error encrypting: %s", aes256Encrypt.errorString().toUtf8().constData()); return false; } // 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 @@ -145,18 +142,19 @@ inline QString hash(const QString& value) * Checks if an encrypted PasswordKey is available for the given database, tries to * decrypt it using the KeyChain and if successful, returns it. */ -QSharedPointer TouchID::getKey(const QString& databasePath) const +bool TouchID::getKey(const QString& databasePath, QByteArray& passwordKey) const { + passwordKey.clear(); if (databasePath.isEmpty()) { // illegal arguments debug("TouchID::storeKey - Illegal argument: databasePath = %s", databasePath.toUtf8().constData()); - return NULL; + return false; } // checks if encrypted PasswordKey is available and is stored for the given database if (!this->m_encryptedMasterKeys.contains(databasePath)) { debug("TouchID::getKey - No stored key found"); - return NULL; + return false; } // query the KeyChain for the AES key @@ -179,12 +177,12 @@ inline QString hash(const QString& value) CFRelease(query); 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"); - return QSharedPointer::create(); + return true; } else if (status != errSecSuccess || dataTypeRef == NULL) { debug("TouchID::getKey - Error retrieving result: %d", status); - return NULL; + return false; } CFDataRef valueData = static_cast(dataTypeRef); @@ -196,22 +194,20 @@ inline QString hash(const QString& value) QByteArray key = dataBytes.left(32); QByteArray iv = dataBytes.right(16); - bool ok; - SymmetricCipher aes256Decrypt(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt); - - if (!aes256Decrypt.init(key, iv)) { + SymmetricCipher aes256Decrypt; + if (!aes256Decrypt.init(SymmetricCipher::Aes256_CBC, SymmetricCipher::Decrypt, key, iv)) { debug("TouchID::getKey - Error initializing decryption: %s", aes256Decrypt.errorString().toUtf8().constData()); - return NULL; + return false; } // decrypt PasswordKey from memory using AES - QByteArray result = aes256Decrypt.process(this->m_encryptedMasterKeys[databasePath], &ok); - if (!ok) { + passwordKey = m_encryptedMasterKeys[databasePath]; + if (!aes256Decrypt.process(passwordKey)) { debug("TouchID::getKey - Error decryption: %s", aes256Decrypt.errorString().toUtf8().constData()); - return NULL; + return false; } - return QSharedPointer::create(result); + return true; } /** diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b532acba94..6abb58bc7f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -94,8 +94,7 @@ set(testsupport_SOURCES modeltest.cpp FailDevice.cpp mock/MockClock.cpp - util/TemporaryFile.cpp - stub/TestRandom.cpp) + util/TemporaryFile.cpp) add_library(testsupport STATIC ${testsupport_SOURCES}) target_link_libraries(testsupport Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Test) @@ -160,9 +159,9 @@ if(WITH_XC_AUTOTYPE) set_target_properties(testautotype PROPERTIES ENABLE_EXPORTS ON) endif() -if(WITH_XC_CRYPTO_SSH) +if(WITH_XC_SSHAGENT) add_unit_test(NAME testopensshkey SOURCES TestOpenSSHKey.cpp - LIBS ${TEST_LIBRARIES}) + LIBS sshagent ${TEST_LIBRARIES}) if(NOT WIN32) add_unit_test(NAME testsshagent SOURCES TestSSHAgent.cpp LIBS ${TEST_LIBRARIES}) diff --git a/tests/TestBrowser.cpp b/tests/TestBrowser.cpp index a82ed6ef80..8699da7f68 100644 --- a/tests/TestBrowser.cpp +++ b/tests/TestBrowser.cpp @@ -21,9 +21,11 @@ #include "browser/BrowserSettings.h" #include "core/Tools.h" #include "crypto/Crypto.h" -#include "sodium/crypto_box.h" #include +#include + +using namespace Botan::Sodium; QTEST_GUILESS_MAIN(TestBrowser) diff --git a/tests/TestFdoSecrets.cpp b/tests/TestFdoSecrets.cpp index eba97f67c0..a7528f6abe 100644 --- a/tests/TestFdoSecrets.cpp +++ b/tests/TestFdoSecrets.cpp @@ -21,7 +21,7 @@ #include "core/EntrySearcher.h" #include "crypto/Crypto.h" -#include "fdosecrets/GcryptMPI.h" +#include "crypto/Random.h" #include "fdosecrets/dbus/DBusMgr.h" #include "fdosecrets/objects/Collection.h" #include "fdosecrets/objects/Item.h" @@ -29,69 +29,10 @@ QTEST_GUILESS_MAIN(TestFdoSecrets) -void TestFdoSecrets::initTestCase() -{ - QVERIFY(Crypto::init()); -} - -void TestFdoSecrets::cleanupTestCase() -{ -} - -void TestFdoSecrets::testGcryptMPI() -{ - auto bytes = QByteArray::fromHex(QByteArrayLiteral("DEADBEEF")); - - auto mpi = MpiFromBytes(bytes); - auto another = MpiFromHex("DEADBEEF"); - - // verify it can parse the bytes in USG mode - QVERIFY(mpi.get()); - QVERIFY(another.get()); - - // verify the number is of the correct value - QCOMPARE(gcry_mpi_cmp_ui(mpi.get(), 0xdeadbeef), 0); - QCOMPARE(gcry_mpi_cmp_ui(another.get(), 0xdeadbeef), 0); - - // verify it can convert back - QCOMPARE(MpiToBytes(mpi), bytes); - QCOMPARE(MpiToBytes(another), bytes); -} - void TestFdoSecrets::testDhIetf1024Sha256Aes128CbcPkcs7() { - auto clientPublic = MpiFromHex("40a0c8d27012c651bf270ebd96890a538" - "396fae3852aef69c0c19bae420d667577" - "ed471cd8ba5a49ef0ec91b568b95f87f0" - "9ec31d271f1699ed140c5b38644c42f60" - "ef84b5a6c406e17c07cd3208e5a605626" - "a5266153b447529946be2394dd43e5638" - "5ffbc4322902c2942391d1a36e8d125dc" - "809e3e406a2f5c2dcf39d3da2"); - auto serverPublic = MpiFromHex("e407997e8b918419cf851cf3345358fdf" - "ffb9564a220ac9c3934efd277cea20d17" - "467ecdc56e817f75ac39501f38a4a04ff" - "64d627e16c09981c7ad876da255b61c8e" - "6a8408236c2a4523cfe6961c26dbdfc77" - "c1a27a5b425ca71a019e829fae32c0b42" - "0e1b3096b48bc2ce9ccab1d1ff13a5eb4" - "b263cee30bdb1a57af9bfa93f"); - auto serverPrivate = MpiFromHex("013f4f3381ef0ca11c4c7363079577b56" - "99b238644e0aba47e24bdba6173590216" - "4f1e12dd0944800a373e090e63192f53b" - "93583e9a9e50bb9d792aafaa3a0f5ae77" - "de0c3423f5820848d88ee3bdd01c889f2" - "7af58a02f5b6693d422b9d189b300d7b1" - "be5076b5795cf8808c31e2e2898368d18" - "ab5c26b0ea3480c9aba8154cf"); - - std::unique_ptr cipher{new FdoSecrets::DhIetf1024Sha256Aes128CbcPkcs7}; - - cipher->initialize(std::move(clientPublic), std::move(serverPublic), std::move(serverPrivate)); - - QVERIFY(cipher->isValid()); - - QCOMPARE(cipher->m_aesKey.toHex(), QByteArrayLiteral("6b8f5ee55138eac37118508be21e7834")); + FdoSecrets::DhIetf1024Sha256Aes128CbcPkcs7 cipher(randomGen()->randomArray(128)); + QVERIFY(cipher.isValid()); } void TestFdoSecrets::testCrazyAttributeKey() diff --git a/tests/TestFdoSecrets.h b/tests/TestFdoSecrets.h index c41a6578f4..05caf588c1 100644 --- a/tests/TestFdoSecrets.h +++ b/tests/TestFdoSecrets.h @@ -25,10 +25,6 @@ class TestFdoSecrets : public QObject Q_OBJECT private slots: - void initTestCase(); - void cleanupTestCase(); - - void testGcryptMPI(); void testDhIetf1024Sha256Aes128CbcPkcs7(); void testCrazyAttributeKey(); void testSpecialCharsInAttributeValue(); diff --git a/tests/TestKeePass2RandomStream.cpp b/tests/TestKeePass2RandomStream.cpp index 3f54435327..bc7e16b999 100644 --- a/tests/TestKeePass2RandomStream.cpp +++ b/tests/TestKeePass2RandomStream.cpp @@ -35,8 +35,11 @@ void TestKeePass2RandomStream::test() const QByteArray key("\x11\x22\x33\x44\x55\x66\x77\x88"); const int Size = 128; - SymmetricCipher cipher(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt); - QVERIFY(cipher.init(CryptoHash::hash(key, CryptoHash::Sha256), KeePass2::INNER_STREAM_SALSA20_IV)); + SymmetricCipher cipher; + QVERIFY(cipher.init(SymmetricCipher::Salsa20, + SymmetricCipher::Encrypt, + CryptoHash::hash(key, CryptoHash::Sha256), + KeePass2::INNER_STREAM_SALSA20_IV)); const QByteArray data(QByteArray::fromHex("601ec313775789a5b7a7f504bbf3d228f443e3ca4d62b59aca84e990cacaf5c5" "2b0930daa23de94ce87017ba2d84988ddfc9c58db67aada613c2dd08457941a6" @@ -45,7 +48,7 @@ void TestKeePass2RandomStream::test() QByteArray cipherPad; cipherPad.fill('\0', Size); - QVERIFY(cipher.processInPlace(cipherPad)); + QVERIFY(cipher.process(cipherPad)); QByteArray cipherData; cipherData.resize(Size); @@ -54,9 +57,9 @@ void TestKeePass2RandomStream::test() cipherData[i] = data[i] ^ cipherPad[i]; } - KeePass2RandomStream randomStream(KeePass2::ProtectedStreamAlgo::Salsa20); + KeePass2RandomStream randomStream; bool ok; - QVERIFY(randomStream.init(key)); + QVERIFY(randomStream.init(SymmetricCipher::Salsa20, key)); QByteArray randomStreamData; randomStreamData.append(randomStream.process(data.mid(0, 7), &ok)); QVERIFY(ok); @@ -70,10 +73,13 @@ void TestKeePass2RandomStream::test() randomStreamData.append(randomStream.process(data.mid(64, 64), &ok)); QVERIFY(ok); - SymmetricCipher cipherEncrypt(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt); - QVERIFY(cipherEncrypt.init(CryptoHash::hash(key, CryptoHash::Sha256), KeePass2::INNER_STREAM_SALSA20_IV)); - QByteArray cipherDataEncrypt = cipherEncrypt.process(data, &ok); - QVERIFY(ok); + SymmetricCipher cipherEncrypt; + QVERIFY(cipherEncrypt.init(SymmetricCipher::Salsa20, + SymmetricCipher::Encrypt, + CryptoHash::hash(key, CryptoHash::Sha256), + KeePass2::INNER_STREAM_SALSA20_IV)); + QByteArray cipherDataEncrypt = data; + QVERIFY(cipherEncrypt.process(cipherDataEncrypt)); QCOMPARE(randomStreamData.size(), Size); QCOMPARE(cipherData, cipherDataEncrypt); diff --git a/tests/TestOpenSSHKey.cpp b/tests/TestOpenSSHKey.cpp index 956dfc64aa..2a880ab1cf 100644 --- a/tests/TestOpenSSHKey.cpp +++ b/tests/TestOpenSSHKey.cpp @@ -18,8 +18,8 @@ #include "TestOpenSSHKey.h" #include "TestGlobal.h" #include "crypto/Crypto.h" -#include "crypto/ssh/BinaryStream.h" -#include "crypto/ssh/OpenSSHKey.h" +#include "sshagent/BinaryStream.h" +#include "sshagent/OpenSSHKey.h" QTEST_GUILESS_MAIN(TestOpenSSHKey) @@ -434,12 +434,3 @@ void TestOpenSSHKey::testDecryptUTF8() QCOMPARE(key.type(), QString("ssh-ed25519")); QCOMPARE(key.comment(), QString("opensshkey-test-utf8@keepassxc")); } - -void TestOpenSSHKey::testGenerateRSA() -{ - OpenSSHKey key = OpenSSHKey::generate(false); - QVERIFY(!key.encrypted()); - QCOMPARE(key.cipherName(), QString("none")); - QCOMPARE(key.type(), QString("ssh-rsa")); - QCOMPARE(key.comment(), QString("")); -} diff --git a/tests/TestOpenSSHKey.h b/tests/TestOpenSSHKey.h index 64516b2041..214de89425 100644 --- a/tests/TestOpenSSHKey.h +++ b/tests/TestOpenSSHKey.h @@ -38,7 +38,6 @@ private slots: void testDecryptOpenSSHAES256CTR(); void testDecryptRSAAES256CTR(); void testDecryptUTF8(); - void testGenerateRSA(); }; #endif // TESTOPENSSHKEY_H diff --git a/tests/TestRandomGenerator.cpp b/tests/TestRandomGenerator.cpp index 02f183d07d..ae6dfb4e8f 100644 --- a/tests/TestRandomGenerator.cpp +++ b/tests/TestRandomGenerator.cpp @@ -17,64 +17,46 @@ #include "TestRandomGenerator.h" #include "TestGlobal.h" + #include "core/Endian.h" #include "core/Global.h" -#include "stub/TestRandom.h" +#include "crypto/Random.h" #include QTEST_GUILESS_MAIN(TestRandomGenerator) -void TestRandomGenerator::initTestCase() -{ - m_backend = new RandomBackendPreset(); - - TestRandom::setup(m_backend); -} - -void TestRandomGenerator::cleanupTestCase() +void TestRandomGenerator::testArray() { - TestRandom::teardown(); + auto ba = randomGen()->randomArray(10); + QCOMPARE(ba.size(), 10); + QVERIFY(ba != QByteArray(10, '\0')); - m_backend = nullptr; + auto ba2 = ba; + randomGen()->randomize(ba2); + QVERIFY(ba2 != ba); } void TestRandomGenerator::testUInt() { - QByteArray nextBytes; - - nextBytes = Endian::sizedIntToBytes(42, QSysInfo::ByteOrder); - m_backend->setNextBytes(nextBytes); - QCOMPARE(randomGen()->randomUInt(100), 42U); - - nextBytes = Endian::sizedIntToBytes(117, QSysInfo::ByteOrder); - m_backend->setNextBytes(nextBytes); - QCOMPARE(randomGen()->randomUInt(100), 17U); - - nextBytes = Endian::sizedIntToBytes(1001, QSysInfo::ByteOrder); - m_backend->setNextBytes(nextBytes); - QCOMPARE(randomGen()->randomUInt(1), 0U); - - nextBytes.clear(); - nextBytes.append(Endian::sizedIntToBytes(QUINT32_MAX, QSysInfo::ByteOrder)); - nextBytes.append(Endian::sizedIntToBytes(QUINT32_MAX - 70000U, QSysInfo::ByteOrder)); - m_backend->setNextBytes(nextBytes); - QCOMPARE(randomGen()->randomUInt(100000U), (QUINT32_MAX - 70000U) % 100000U); - - nextBytes.clear(); - for (int i = 0; i < 10000; i++) { - nextBytes.append(Endian::sizedIntToBytes((QUINT32_MAX / 2U) + 1U + i, QSysInfo::ByteOrder)); + QVERIFY(randomGen()->randomUInt(0) == 0); + QVERIFY(randomGen()->randomUInt(1) == 0); + + // Run a bunch of trials creating random numbers to ensure we meet the standard + for (int i = 0; i < 100; ++i) { + QVERIFY(randomGen()->randomUInt(5) < 5); + QVERIFY(randomGen()->randomUInt(100) < 100); + QVERIFY(randomGen()->randomUInt(100000U) < 100000U); + QVERIFY(randomGen()->randomUInt((QUINT32_MAX / 2U) + 1U) < QUINT32_MAX / 2U + 1U); } - nextBytes.append(Endian::sizedIntToBytes(QUINT32_MAX / 2U, QSysInfo::ByteOrder)); - m_backend->setNextBytes(nextBytes); - QCOMPARE(randomGen()->randomUInt((QUINT32_MAX / 2U) + 1U), QUINT32_MAX / 2U); } void TestRandomGenerator::testUIntRange() { - QByteArray nextBytes; - - nextBytes = Endian::sizedIntToBytes(42, QSysInfo::ByteOrder); - m_backend->setNextBytes(nextBytes); - QCOMPARE(randomGen()->randomUIntRange(100, 200), 142U); + // Run a bunch of trials to ensure we stay within the range + for (int i = 0; i < 100; ++i) { + auto rand = randomGen()->randomUIntRange(100, 200); + QVERIFY(rand >= 100); + QVERIFY(rand < 200); + } } diff --git a/tests/TestRandomGenerator.h b/tests/TestRandomGenerator.h index addcb0250e..6f9c335e41 100644 --- a/tests/TestRandomGenerator.h +++ b/tests/TestRandomGenerator.h @@ -18,24 +18,16 @@ #ifndef KEEPASSX_TESTRANDOMGENERATOR_H #define KEEPASSX_TESTRANDOMGENERATOR_H -#include "crypto/Random.h" - #include -class RandomBackendPreset; - class TestRandomGenerator : public QObject { Q_OBJECT private slots: - void initTestCase(); - void cleanupTestCase(); + void testArray(); void testUInt(); void testUIntRange(); - -private: - RandomBackendPreset* m_backend; }; #endif // KEEPASSX_TESTRANDOMGENERATOR_H diff --git a/tests/TestSSHAgent.h b/tests/TestSSHAgent.h index 13e8076e71..bb9fa54596 100644 --- a/tests/TestSSHAgent.h +++ b/tests/TestSSHAgent.h @@ -18,10 +18,11 @@ #ifndef TESTSSHAGENT_H #define TESTSSHAGENT_H -#include "crypto/ssh/OpenSSHKey.h" +#include "sshagent/OpenSSHKey.h" #include #include #include +#include class TestSSHAgent : public QObject { diff --git a/tests/TestSharing.cpp b/tests/TestSharing.cpp index c1188ed142..b54076ed5b 100644 --- a/tests/TestSharing.cpp +++ b/tests/TestSharing.cpp @@ -17,7 +17,6 @@ #include "TestSharing.h" #include "TestGlobal.h" -#include "stub/TestRandom.h" #include #include @@ -29,11 +28,14 @@ #include "core/Metadata.h" #include "crypto/Crypto.h" #include "crypto/Random.h" -#include "crypto/ssh/OpenSSHKey.h" #include "format/KeePass2Writer.h" #include "keeshare/KeeShareSettings.h" #include "keys/PasswordKey.h" +#include +#include +#include + #include QTEST_GUILESS_MAIN(TestSharing) @@ -50,59 +52,6 @@ void TestSharing::initTestCase() QVERIFY(Crypto::init()); } -void TestSharing::cleanupTestCase() -{ - TestRandom::teardown(); -} - -void TestSharing::testIdempotentDatabaseWriting() -{ - QScopedPointer db(new Database()); - auto key = QSharedPointer::create(); - key->addKey(QSharedPointer::create("password")); - db->setKey(key); - - Group* sharingGroup = new Group(); - sharingGroup->setName("SharingGroup"); - sharingGroup->setUuid(QUuid::createUuid()); - sharingGroup->setParent(db->rootGroup()); - - Entry* entry1 = new Entry(); - entry1->setUuid(QUuid::createUuid()); - entry1->beginUpdate(); - entry1->setTitle("Entry1"); - entry1->endUpdate(); - entry1->setGroup(sharingGroup); - - Entry* entry2 = new Entry(); - entry2->setUuid(QUuid::createUuid()); - entry2->beginUpdate(); - entry2->setTitle("Entry2"); - entry2->endUpdate(); - entry2->setGroup(sharingGroup); - - // prevent from changes introduced by randomization - TestRandom::setup(new RandomBackendNull()); - - QByteArray bufferOriginal; - { - QBuffer device(&bufferOriginal); - device.open(QIODevice::ReadWrite); - KeePass2Writer writer; - writer.writeDatabase(&device, db.data()); - } - - QByteArray bufferCopy; - { - QBuffer device(&bufferCopy); - device.open(QIODevice::ReadWrite); - KeePass2Writer writer; - writer.writeDatabase(&device, db.data()); - } - - QCOMPARE(bufferCopy, bufferOriginal); -} - void TestSharing::testNullObjects() { const QString empty; @@ -142,11 +91,10 @@ void TestSharing::testNullObjects() void TestSharing::testCertificateSerialization() { QFETCH(KeeShareSettings::Trust, trusted); - const OpenSSHKey& key = stubkey(); + auto key = stubkey(); KeeShareSettings::ScopedCertificate original; original.path = "/path"; - original.certificate = KeeShareSettings::Certificate{OpenSSHKey::serializeToBinary(OpenSSHKey::Public, key), - "Some &#_\"\" weird string"}; + original.certificate = KeeShareSettings::Certificate{key, "Some &#_\"\" weird string"}; original.trust = trusted; QString buffer; @@ -156,17 +104,18 @@ void TestSharing::testCertificateSerialization() KeeShareSettings::ScopedCertificate::serialize(writer, original); writer.writeEndElement(); writer.writeEndDocument(); + QXmlStreamReader reader(buffer); reader.readNextStartElement(); QVERIFY(reader.name() == "Certificate"); KeeShareSettings::ScopedCertificate restored = KeeShareSettings::ScopedCertificate::deserialize(reader); - QCOMPARE(restored.certificate.key, original.certificate.key); + QCOMPARE(restored.certificate.key->private_key_bits(), original.certificate.key->private_key_bits()); QCOMPARE(restored.certificate.signer, original.certificate.signer); QCOMPARE(restored.trust, original.trust); QCOMPARE(restored.path, original.path); - QCOMPARE(restored.certificate.sshKey().publicParts(), key.publicParts()); + QCOMPARE(restored.certificate.key->public_key_bits(), key->public_key_bits()); } void TestSharing::testCertificateSerialization_data() @@ -179,9 +128,9 @@ void TestSharing::testCertificateSerialization_data() void TestSharing::testKeySerialization() { - const OpenSSHKey& key = stubkey(); + auto key = stubkey(); KeeShareSettings::Key original; - original.key = OpenSSHKey::serializeToBinary(OpenSSHKey::Private, key); + original.key = key; QString buffer; QXmlStreamWriter writer(&buffer); @@ -195,9 +144,8 @@ void TestSharing::testKeySerialization() QVERIFY(reader.name() == "Key"); KeeShareSettings::Key restored = KeeShareSettings::Key::deserialize(reader); - QCOMPARE(restored.key, original.key); - QCOMPARE(restored.sshKey().privateParts(), key.privateParts()); - QCOMPARE(restored.sshKey().type(), key.type()); + QCOMPARE(restored.key->private_key_bits(), original.key->private_key_bits()); + QCOMPARE(restored.key->algo_name(), original.key->algo_name()); } void TestSharing::testReferenceSerialization() @@ -263,31 +211,33 @@ void TestSharing::testSettingsSerialization() QCOMPARE(restoredActive.in, importing); QCOMPARE(restoredActive.out, exporting); - QCOMPARE(restoredOwn.certificate.key, ownCertificate.key); - QCOMPARE(restoredOwn.key.key, ownKey.key); + if (ownCertificate.key) { + QCOMPARE(restoredOwn.certificate, ownCertificate); + } + if (ownKey.key) { + QCOMPARE(restoredOwn.key, ownKey); + } QCOMPARE(restoredForeign.certificates.count(), foreignCertificates.count()); for (int i = 0; i < foreignCertificates.count(); ++i) { - QCOMPARE(restoredForeign.certificates[i].certificate.key, foreignCertificates[i].certificate.key); + QCOMPARE(restoredForeign.certificates[i].certificate, foreignCertificates[i].certificate); } } void TestSharing::testSettingsSerialization_data() { - const OpenSSHKey& sshKey0 = stubkey(0); + auto sshKey0 = stubkey(0); KeeShareSettings::ScopedCertificate certificate0; certificate0.path = "/path/0"; - certificate0.certificate = KeeShareSettings::Certificate{OpenSSHKey::serializeToBinary(OpenSSHKey::Public, sshKey0), - "Some &#_\"\" weird string"}; + certificate0.certificate = KeeShareSettings::Certificate{sshKey0, "Some &#_\"\" weird string"}; certificate0.trust = KeeShareSettings::Trust::Trusted; KeeShareSettings::Key key0; - key0.key = OpenSSHKey::serializeToBinary(OpenSSHKey::Private, sshKey0); + key0.key = sshKey0; - const OpenSSHKey& sshKey1 = stubkey(1); + auto sshKey1 = stubkey(1); KeeShareSettings::ScopedCertificate certificate1; certificate1.path = "/path/1"; - certificate1.certificate = - KeeShareSettings::Certificate{OpenSSHKey::serializeToBinary(OpenSSHKey::Public, sshKey1), "Another "}; + certificate1.certificate = KeeShareSettings::Certificate{sshKey1, "Another "}; certificate1.trust = KeeShareSettings::Trust::Untrusted; QTest::addColumn("importing"); @@ -307,13 +257,12 @@ void TestSharing::testSettingsSerialization_data() << QList({certificate1}); } -const OpenSSHKey& TestSharing::stubkey(int index) +const QSharedPointer TestSharing::stubkey(int index) { - static QMap keys; + static QMap> keys; if (!keys.contains(index)) { - OpenSSHKey* key = new OpenSSHKey(OpenSSHKey::generate(false)); - key->setParent(this); - keys[index] = key; + keys.insert(index, + QSharedPointer(new Botan::RSA_PrivateKey(*randomGen()->getRng(), 2048))); } - return *keys[index]; + return keys[index]; } diff --git a/tests/TestSharing.h b/tests/TestSharing.h index ebf85fd4e3..f3e45faf2d 100644 --- a/tests/TestSharing.h +++ b/tests/TestSharing.h @@ -21,16 +21,16 @@ #include #include -class OpenSSHKey; - +namespace Botan +{ + class RSA_PrivateKey; +} class TestSharing : public QObject { Q_OBJECT private slots: void initTestCase(); - void cleanupTestCase(); - void testIdempotentDatabaseWriting(); void testNullObjects(); void testCertificateSerialization(); void testCertificateSerialization_data(); @@ -41,7 +41,7 @@ private slots: void testSettingsSerialization_data(); private: - const OpenSSHKey& stubkey(int iIndex = 0); + const QSharedPointer stubkey(int index = 0); }; #endif // KEEPASSXC_TESTSHARING_H diff --git a/tests/TestSignature.cpp b/tests/TestSignature.cpp index 887143a85d..5997827d5e 100644 --- a/tests/TestSignature.cpp +++ b/tests/TestSignature.cpp @@ -22,9 +22,13 @@ #include #include "crypto/Crypto.h" -#include "crypto/ssh/OpenSSHKey.h" +#include "crypto/Random.h" #include "keeshare/Signature.h" +#include +#include +#include + QTEST_GUILESS_MAIN(TestSignature) static const char* rsa_2_private = "-----BEGIN RSA PRIVATE KEY-----\n" "MIIEowIBAAKCAQEAwGdladnqFfcDy02Gubx4sdBT8NYEg2YKXfcKLSwca5gV4X7I\n" @@ -107,6 +111,35 @@ static const char* rsa_1_public = "-----BEGIN RSA PUBLIC KEY-----\n" static QByteArray data("Some trivial test with a longer .... ................................. longer text"); +QSharedPointer loadPrivateKey(const QString& pem) +{ + try { + std::string label; + auto der = Botan::PEM_Code::decode(pem.toStdString(), label); + auto key = new Botan::RSA_PrivateKey( + Botan::AlgorithmIdentifier("RSA", Botan::AlgorithmIdentifier::USE_NULL_PARAM), der); + return QSharedPointer(key); + } catch (std::exception& e) { + qWarning("Failed to load key: %s", e.what()); + return {}; + } +} + +QSharedPointer loadPublicKey(const QString& pem) +{ + try { + std::string label; + auto der = Botan::PEM_Code::decode(pem.toStdString(), label); + auto key = + new Botan::RSA_PublicKey(Botan::AlgorithmIdentifier("RSA", Botan::AlgorithmIdentifier::USE_NULL_PARAM), + std::vector(der.begin(), der.end())); + return QSharedPointer(key); + } catch (std::exception& e) { + qWarning("Failed to load key: %s", e.what()); + return {}; + } +} + void TestSignature::initTestCase() { QVERIFY(Crypto::init()); @@ -114,85 +147,59 @@ void TestSignature::initTestCase() void TestSignature::testSigningOpenSSH_RSA_PrivateOnly() { - OpenSSHKey privateKey; - privateKey.parsePKCS1PEM(rsa_2_private); - privateKey.openKey(QString()); - QCOMPARE(privateKey.fingerprint(), QString("SHA256:ZAQ/W1QdW59OaIh/0hs3ePl2og5TjXnGX5L0iN7WtNA")); - Signature signer; - const QString sign = signer.create(data, privateKey); - QVERIFY(!sign.isEmpty()); + auto rsaKey = loadPrivateKey(rsa_2_private); + QString sign; + Signature::create(data, rsaKey, sign); QCOMPARE(sign, QString("rsa|%1").arg(QString::fromLatin1(rsa_2_sign))); - Signature verifier; - const bool verified = verifier.verify(data, sign, privateKey); + const bool verified = Signature::verify(data, rsaKey, sign); QCOMPARE(verified, true); } void TestSignature::testSigningOpenSSH_RSA() { - OpenSSHKey privateKey; - privateKey.parsePKCS1PEM(rsa_2_private); - privateKey.openKey(QString()); - QCOMPARE(privateKey.fingerprint(), QString("SHA256:ZAQ/W1QdW59OaIh/0hs3ePl2og5TjXnGX5L0iN7WtNA")); - Signature signer; - const QString sign = signer.create(data, privateKey); + auto privateKey = loadPrivateKey(rsa_2_private); + QString sign; + Signature::create(data, privateKey, sign); QVERIFY(!sign.isEmpty()); - OpenSSHKey publicKey; - publicKey.parsePKCS1PEM(rsa_2_public); - publicKey.openKey(QString()); - QCOMPARE(publicKey.fingerprint(), QString("SHA256:ZAQ/W1QdW59OaIh/0hs3ePl2og5TjXnGX5L0iN7WtNA")); - - Signature verifier; - const bool verified = verifier.verify(data, sign, publicKey); + auto publicKey = loadPublicKey(rsa_2_public); + const bool verified = Signature::verify(data, publicKey, sign); QCOMPARE(verified, true); } void TestSignature::testSigningGenerated_RSA_PrivateOnly() { - OpenSSHKey privateKey = OpenSSHKey::generate(false); - privateKey.openKey(QString()); + QSharedPointer key(new Botan::RSA_PrivateKey(*randomGen()->getRng(), 2048)); - Signature signer; - const QString sign = signer.create(data, privateKey); + QString sign; + Signature::create(data, key, sign); QVERIFY(!sign.isEmpty()); - Signature verifier; - const bool verified = verifier.verify(data, sign, privateKey); + const bool verified = Signature::verify(data, key, sign); QCOMPARE(verified, true); } void TestSignature::testSigningTest_RSA_PrivateOnly() { - OpenSSHKey privateKey; - privateKey.parsePKCS1PEM(rsa_1_private); - privateKey.openKey(QString()); - QCOMPARE(privateKey.fingerprint(), QString("SHA256:DYdaZciYNxCejr+/8x+OKYxeTU1D5UsuIFUG4PWRFkk")); - Signature signer; - const QString sign = signer.create(data, privateKey); + auto rsaKey = loadPrivateKey(rsa_2_private); + QString sign; + Signature::create(data, rsaKey, sign); QVERIFY(!sign.isEmpty()); - Signature verifier; - const bool verified = verifier.verify(data, sign, privateKey); + const bool verified = Signature::verify(data, rsaKey, sign); QCOMPARE(verified, true); } void TestSignature::testSigningTest_RSA() { - OpenSSHKey privateKey; - privateKey.parsePKCS1PEM(rsa_1_private); - privateKey.openKey(QString()); - QCOMPARE(privateKey.fingerprint(), QString("SHA256:DYdaZciYNxCejr+/8x+OKYxeTU1D5UsuIFUG4PWRFkk")); - Signature signer; - const QString sign = signer.create(data, privateKey); + auto privateKey = loadPrivateKey(rsa_1_private); + QString sign; + Signature::create(data, privateKey, sign); QVERIFY(!sign.isEmpty()); - OpenSSHKey publicKey; - publicKey.parsePKCS1PEM(rsa_1_public); - publicKey.openKey(QString()); - QCOMPARE(publicKey.fingerprint(), QString("SHA256:DYdaZciYNxCejr+/8x+OKYxeTU1D5UsuIFUG4PWRFkk")); - Signature verifier; - const bool verified = verifier.verify(data, sign, publicKey); + auto publicKey = loadPublicKey(rsa_1_public); + const bool verified = Signature::verify(data, publicKey, sign); QCOMPARE(verified, true); } diff --git a/tests/TestSymmetricCipher.cpp b/tests/TestSymmetricCipher.cpp index bc872a5101..de6b2c1c0f 100644 --- a/tests/TestSymmetricCipher.cpp +++ b/tests/TestSymmetricCipher.cpp @@ -26,7 +26,6 @@ #include "streams/SymmetricCipherStream.h" QTEST_GUILESS_MAIN(TestSymmetricCipher) -Q_DECLARE_METATYPE(SymmetricCipher::Algorithm); Q_DECLARE_METATYPE(SymmetricCipher::Mode); Q_DECLARE_METATYPE(SymmetricCipher::Direction); @@ -35,19 +34,18 @@ void TestSymmetricCipher::initTestCase() QVERIFY(Crypto::init()); } -void TestSymmetricCipher::testAlgorithmToCipher() +void TestSymmetricCipher::testCipherUuidToMode() { - QCOMPARE(SymmetricCipher::algorithmToCipher(SymmetricCipher::Algorithm::Aes128), KeePass2::CIPHER_AES128); - QCOMPARE(SymmetricCipher::algorithmToCipher(SymmetricCipher::Algorithm::Aes256), KeePass2::CIPHER_AES256); - QCOMPARE(SymmetricCipher::algorithmToCipher(SymmetricCipher::Algorithm::Twofish), KeePass2::CIPHER_TWOFISH); - QCOMPARE(SymmetricCipher::algorithmToCipher(SymmetricCipher::Algorithm::ChaCha20), KeePass2::CIPHER_CHACHA20); - QCOMPARE(SymmetricCipher::algorithmToCipher(SymmetricCipher::Algorithm::InvalidAlgorithm), QUuid()); + QCOMPARE(SymmetricCipher::cipherUuidToMode(KeePass2::CIPHER_AES128), SymmetricCipher::Aes128_CBC); + QCOMPARE(SymmetricCipher::cipherUuidToMode(KeePass2::CIPHER_AES256), SymmetricCipher::Aes256_CBC); + QCOMPARE(SymmetricCipher::cipherUuidToMode(KeePass2::CIPHER_TWOFISH), SymmetricCipher::Twofish_CBC); + QCOMPARE(SymmetricCipher::cipherUuidToMode(KeePass2::CIPHER_CHACHA20), SymmetricCipher::ChaCha20); + QCOMPARE(SymmetricCipher::cipherUuidToMode(QUuid()), SymmetricCipher::InvalidMode); } // clang-format off void TestSymmetricCipher::testEncryptionDecryption_data() { - QTest::addColumn("algorithm"); QTest::addColumn("mode"); QTest::addColumn("direction"); QTest::addColumn("key"); @@ -57,64 +55,57 @@ void TestSymmetricCipher::testEncryptionDecryption_data() // http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf QTest::newRow("AES128-CBC Encryption") - << SymmetricCipher::Aes128 - << SymmetricCipher::Cbc + << SymmetricCipher::Aes128_CBC << SymmetricCipher::Encrypt << QByteArray::fromHex("2b7e151628aed2a6abf7158809cf4f3c") << QByteArray::fromHex("000102030405060708090a0b0c0d0e0f") - << QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51") + << QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a") << QByteArray::fromHex("7649abac8119b246cee98e9b12e9197d5086cb9b507219ee95db113a917678b2"); QTest::newRow("AES128-CBC Decryption") - << SymmetricCipher::Aes128 - << SymmetricCipher::Cbc + << SymmetricCipher::Aes128_CBC << SymmetricCipher::Decrypt << QByteArray::fromHex("2b7e151628aed2a6abf7158809cf4f3c") << QByteArray::fromHex("000102030405060708090a0b0c0d0e0f") << QByteArray::fromHex("7649abac8119b246cee98e9b12e9197d5086cb9b507219ee95db113a917678b2") - << QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51"); + << QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a"); QTest::newRow("AES256-CBC Encryption") - << SymmetricCipher::Aes256 - << SymmetricCipher::Cbc + << SymmetricCipher::Aes256_CBC << SymmetricCipher::Encrypt << QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4") << QByteArray::fromHex("000102030405060708090a0b0c0d0e0f") - << QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51") + << QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a") << QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd69cfc4e967edb808d679f777bc6702c7d"); QTest::newRow("AES256-CBC Decryption") - << SymmetricCipher::Aes256 - << SymmetricCipher::Cbc + << SymmetricCipher::Aes256_CBC << SymmetricCipher::Decrypt << QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4") << QByteArray::fromHex("000102030405060708090a0b0c0d0e0f") << QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd69cfc4e967edb808d679f777bc6702c7d") - << QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51"); + << QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a"); QTest::newRow("AES256-CTR Encryption") - << SymmetricCipher::Aes256 - << SymmetricCipher::Ctr + << SymmetricCipher::Aes256_CTR << SymmetricCipher::Encrypt << QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4") << QByteArray::fromHex("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff") - << QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51") + << QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a") << QByteArray::fromHex("601ec313775789a5b7a7f504bbf3d228f443e3ca4d62b59aca84e990cacaf5c5"); QTest::newRow("AES256-CTR Decryption") - << SymmetricCipher::Aes256 - << SymmetricCipher::Ctr + << SymmetricCipher::Aes256_CTR << SymmetricCipher::Decrypt << QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4") << QByteArray::fromHex("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff") << QByteArray::fromHex("601ec313775789a5b7a7f504bbf3d228f443e3ca4d62b59aca84e990cacaf5c5") - << QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51"); + << QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a"); } // clang-format on void TestSymmetricCipher::testEncryptionDecryption() { - QFETCH(SymmetricCipher::Algorithm, algorithm); QFETCH(SymmetricCipher::Mode, mode); QFETCH(SymmetricCipher::Direction, direction); QFETCH(QByteArray, key); @@ -122,66 +113,51 @@ void TestSymmetricCipher::testEncryptionDecryption() QFETCH(QByteArray, plainText); QFETCH(QByteArray, cipherText); - bool ok; - SymmetricCipher cipher(algorithm, mode, direction); - QVERIFY(cipher.init(key, iv)); - QCOMPARE(cipher.blockSize(), 16); - QCOMPARE(cipher.process(plainText, &ok), cipherText); - QVERIFY(ok); - - if (mode == SymmetricCipher::Cbc) { - QBuffer buffer; - SymmetricCipherStream stream(&buffer, algorithm, mode, direction); - QVERIFY(stream.init(key, iv)); - buffer.open(QIODevice::WriteOnly); - QVERIFY(stream.open(QIODevice::WriteOnly)); - QVERIFY(stream.reset()); - - buffer.reset(); - buffer.buffer().clear(); - QCOMPARE(stream.write(plainText.left(16)), qint64(16)); - QCOMPARE(buffer.data(), cipherText.left(16)); - QVERIFY(stream.reset()); - // make sure padding is written - QCOMPARE(buffer.data().size(), 32); - - buffer.reset(); - buffer.buffer().clear(); - QCOMPARE(stream.write(plainText.left(10)), qint64(10)); - QVERIFY(buffer.data().isEmpty()); - - QVERIFY(stream.reset()); - buffer.reset(); - buffer.buffer().clear(); - QCOMPARE(stream.write(plainText.left(10)), qint64(10)); - stream.close(); - QCOMPARE(buffer.data().size(), 16); - } + QByteArray data = plainText; + SymmetricCipher cipher; + QVERIFY(cipher.init(mode, direction, key, iv)); + QVERIFY(cipher.process(data)); + QCOMPARE(data.left(16), cipherText.left(16)); + + QBuffer buffer; + buffer.open(QIODevice::WriteOnly); + SymmetricCipherStream stream(&buffer); + QVERIFY(stream.init(mode, direction, key, iv)); + QVERIFY(stream.open(QIODevice::WriteOnly)); + QCOMPARE(stream.write(plainText.left(16)), qint64(16)); + stream.close(); + QCOMPARE(buffer.data().left(16), cipherText.left(16)); } void TestSymmetricCipher::testAesCbcPadding_data() { + QTest::addColumn("mode"); QTest::addColumn("key"); QTest::addColumn("iv"); QTest::addColumn("cipherText"); QTest::addColumn("plainText"); QTest::addColumn("padding"); - QTest::newRow("AES128") << QByteArray::fromHex("2b7e151628aed2a6abf7158809cf4f3c") + // clang-format off + QTest::newRow("AES128") << SymmetricCipher::Aes128_CBC + << QByteArray::fromHex("2b7e151628aed2a6abf7158809cf4f3c") << QByteArray::fromHex("000102030405060708090a0b0c0d0e0f") << QByteArray::fromHex("7649abac8119b246cee98e9b12e9197d5086cb9b507219ee95db113a917678b2") << QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51") << QByteArray::fromHex("55e21d7100b988ffec32feeafaf23538"); - QTest::newRow("AES256") << QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4") + QTest::newRow("AES256") << SymmetricCipher::Aes256_CBC + << QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4") << QByteArray::fromHex("000102030405060708090a0b0c0d0e0f") << QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd69cfc4e967edb808d679f777bc6702c7d") << QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51") << QByteArray::fromHex("3a3aa5e0213db1a9901f9036cf5102d2"); + // clang-format on } void TestSymmetricCipher::testAesCbcPadding() { + QFETCH(SymmetricCipher::Mode, mode); QFETCH(QByteArray, key); QFETCH(QByteArray, iv); QFETCH(QByteArray, cipherText); @@ -192,64 +168,36 @@ void TestSymmetricCipher::testAesCbcPadding() QByteArray cipherTextPadded = cipherText + padding; QBuffer buffer(&cipherTextPadded); - SymmetricCipherStream stream(&buffer, SymmetricCipher::Aes128, SymmetricCipher::Cbc, SymmetricCipher::Decrypt); - QVERIFY(stream.init(key, iv)); + SymmetricCipherStream stream(&buffer); + QVERIFY(stream.init(mode, SymmetricCipher::Decrypt, key, iv)); buffer.open(QIODevice::ReadOnly); QVERIFY(stream.open(QIODevice::ReadOnly)); QCOMPARE(stream.read(10), plainText.left(10)); buffer.reset(); QVERIFY(stream.reset()); + QVERIFY(stream.init(mode, SymmetricCipher::Decrypt, key, iv)); QCOMPARE(stream.read(20), plainText.left(20)); buffer.reset(); QVERIFY(stream.reset()); + QVERIFY(stream.init(mode, SymmetricCipher::Decrypt, key, iv)); QCOMPARE(stream.read(16), plainText.left(16)); buffer.reset(); QVERIFY(stream.reset()); + QVERIFY(stream.init(mode, SymmetricCipher::Decrypt, key, iv)); QCOMPARE(stream.read(100), plainText); } -void TestSymmetricCipher::testInplaceEcb_data() +void TestSymmetricCipher::testAesKdf() { - QTest::addColumn("key"); - QTest::addColumn("plainText"); - QTest::addColumn("cipherText"); + auto key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"); + auto data = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a"); + auto result = QByteArray::fromHex("f3eed1bdb5d2a03c064b5a7e3db181f8"); - QTest::newRow("AES128") << QByteArray::fromHex("2b7e151628aed2a6abf7158809cf4f3c") - << QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a") - << QByteArray::fromHex("3ad77bb40d7a3660a89ecaf32466ef97"); -} + QVERIFY(SymmetricCipher::aesKdf(key, 1, data)); + QCOMPARE(data, result); -void TestSymmetricCipher::testInplaceEcb() -{ - QFETCH(QByteArray, key); - QFETCH(QByteArray, plainText); - QFETCH(QByteArray, cipherText); - - SymmetricCipher cipherInPlaceEnc(SymmetricCipher::Aes128, SymmetricCipher::Ecb, SymmetricCipher::Encrypt); - QVERIFY(cipherInPlaceEnc.init(key, QByteArray(16, 0))); - QCOMPARE(cipherInPlaceEnc.blockSize(), 16); - auto data = QByteArray(plainText); - QVERIFY(cipherInPlaceEnc.processInPlace(data)); - QCOMPARE(data, cipherText); - - SymmetricCipher cipherInPlaceDec(SymmetricCipher::Aes128, SymmetricCipher::Ecb, SymmetricCipher::Decrypt); - QVERIFY(cipherInPlaceDec.init(key, QByteArray(16, 0))); - QCOMPARE(cipherInPlaceDec.blockSize(), 16); - QVERIFY(cipherInPlaceDec.processInPlace(data)); - QCOMPARE(data, plainText); - - SymmetricCipher cipherInPlaceEnc2(SymmetricCipher::Aes128, SymmetricCipher::Ecb, SymmetricCipher::Encrypt); - QVERIFY(cipherInPlaceEnc2.init(key, QByteArray(16, 0))); - QCOMPARE(cipherInPlaceEnc2.blockSize(), 16); - data = QByteArray(plainText); - QVERIFY(cipherInPlaceEnc2.processInPlace(data, 100)); - - SymmetricCipher cipherInPlaceDec2(SymmetricCipher::Aes128, SymmetricCipher::Ecb, SymmetricCipher::Decrypt); - QVERIFY(cipherInPlaceDec2.init(key, QByteArray(16, 0))); - QCOMPARE(cipherInPlaceDec2.blockSize(), 16); - QVERIFY(cipherInPlaceDec2.processInPlace(data, 100)); - QCOMPARE(data, plainText); + // TODO: Test multiple rounds of AES KDF } void TestSymmetricCipher::testTwofish256CbcEncryption() @@ -278,32 +226,30 @@ void TestSymmetricCipher::testTwofish256CbcEncryption() QByteArray::fromHex("957108025BFD57125B40057BC2DE4FE2"), QByteArray::fromHex("6F725C5950133F82EF021A94CADC8508")}; - SymmetricCipher cipher(SymmetricCipher::Twofish, SymmetricCipher::Cbc, SymmetricCipher::Encrypt); + SymmetricCipher cipher; for (int i = 0; i < keys.size(); ++i) { - QVERIFY(cipher.init(keys[i], ivs[i])); + QVERIFY(cipher.init(SymmetricCipher::Twofish_CBC, SymmetricCipher::Encrypt, keys[i], ivs[i])); QByteArray ptNext = plainTexts[i]; QByteArray ctPrev = ivs[i]; QByteArray ctCur; - QCOMPARE(cipher.blockSize(), 16); - bool ok = false; + QCOMPARE(cipher.blockSize(cipher.mode()), 16); for (int j = 0; j < 5000; ++j) { - ctCur = cipher.process(ptNext, &ok); - if (!ok) { + if (!cipher.process(ptNext)) { break; } + ctCur = ptNext; ptNext = ctPrev; ctPrev = ctCur; - ctCur = cipher.process(ptNext, &ok); - if (!ok) { + if (!cipher.process(ptNext)) { break; } + ctCur = ptNext; ptNext = ctPrev; ctPrev = ctCur; } - QVERIFY(ok); QCOMPARE(ctCur, cipherTexts[i]); } } @@ -334,30 +280,24 @@ void TestSymmetricCipher::testTwofish256CbcDecryption() QByteArray::fromHex("A792AC61E7110C434BC2BBCAB6E53CAE"), QByteArray::fromHex("4C81F5BDC1081170FF96F50B1F76A566")}; - SymmetricCipher cipher(SymmetricCipher::Twofish, SymmetricCipher::Cbc, SymmetricCipher::Decrypt); + SymmetricCipher cipher; for (int i = 0; i < keys.size(); ++i) { - cipher.init(keys[i], ivs[i]); + QVERIFY(cipher.init(SymmetricCipher::Twofish_CBC, SymmetricCipher::Decrypt, keys[i], ivs[i])); QByteArray ctNext = cipherTexts[i]; QByteArray ptCur; - QCOMPARE(cipher.blockSize(), 16); - bool ok = false; + QCOMPARE(cipher.blockSize(cipher.mode()), 16); for (int j = 0; j < 5000; ++j) { - ptCur = cipher.process(ctNext, &ok); - if (!ok) { + if (!cipher.process(ctNext)) { break; } - ctNext = ptCur; - ptCur = cipher.process(ctNext, &ok); - if (!ok) { + if (!cipher.process(ctNext)) { break; } - ctNext = ptCur; } - QVERIFY(ok); - QCOMPARE(ptCur, plainTexts[i]); + QCOMPARE(ctNext, plainTexts[i]); } } @@ -367,20 +307,22 @@ void TestSymmetricCipher::testSalsa20() QByteArray key = QByteArray::fromHex("F3F4F5F6F7F8F9FAFBFCFDFEFF000102030405060708090A0B0C0D0E0F101112"); QByteArray iv = QByteArray::fromHex("0000000000000000"); - bool ok; - SymmetricCipher cipher(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt); - QVERIFY(cipher.init(key, iv)); + SymmetricCipher cipher; + QVERIFY(cipher.init(SymmetricCipher::Salsa20, SymmetricCipher::Encrypt, key, iv)); QByteArray cipherTextA; for (int i = 0; i < 8; i++) { - cipherTextA.append(cipher.process(QByteArray(64, '\0'), &ok)); - QVERIFY(ok); + QByteArray data(64, '\0'); + QVERIFY(cipher.process(data)); + cipherTextA.append(data); } - cipher.reset(); - QByteArray cipherTextB = cipher.process(QByteArray(512, '\0'), &ok); - QVERIFY(ok); + // Re-initialize + QVERIFY(cipher.init(SymmetricCipher::Salsa20, SymmetricCipher::Encrypt, key, iv)); + + QByteArray cipherTextB(512, '\0'); + QVERIFY(cipher.process(cipherTextB)); cipher.reset(); QByteArray expectedCipherText1; @@ -421,50 +363,52 @@ void TestSymmetricCipher::testSalsa20() void TestSymmetricCipher::testChaCha20() { // https://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-04#section-7 - bool ok; - { QByteArray key = QByteArray::fromHex("0000000000000000000000000000000000000000000000000000000000000000"); QByteArray iv = QByteArray::fromHex("0000000000000000"); - SymmetricCipher cipher(SymmetricCipher::ChaCha20, SymmetricCipher::Stream, SymmetricCipher::Encrypt); - QVERIFY(cipher.init(key, iv)); - QCOMPARE(cipher.process(QByteArray(64, 0), &ok), + SymmetricCipher cipher; + QByteArray data(64, 0); + QVERIFY(cipher.init(SymmetricCipher::ChaCha20, SymmetricCipher::Encrypt, key, iv)); + QVERIFY(cipher.process(data)); + QCOMPARE(data, QByteArray::fromHex("76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7" "724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586")); - QVERIFY(ok); } { QByteArray key = QByteArray::fromHex("0000000000000000000000000000000000000000000000000000000000000001"); QByteArray iv = QByteArray::fromHex("0000000000000000"); - SymmetricCipher cipher(SymmetricCipher::ChaCha20, SymmetricCipher::Stream, SymmetricCipher::Encrypt); - QVERIFY(cipher.init(key, iv)); - QCOMPARE(cipher.process(QByteArray(64, 0), &ok), + SymmetricCipher cipher; + QByteArray data(64, 0); + QVERIFY(cipher.init(SymmetricCipher::ChaCha20, SymmetricCipher::Encrypt, key, iv)); + QVERIFY(cipher.process(data)); + QCOMPARE(data, QByteArray::fromHex("4540f05a9f1fb296d7736e7b208e3c96eb4fe1834688d2604f450952ed432d41bbe2a0b6ea7566d2a" "5d1e7e20d42af2c53d792b1c43fea817e9ad275ae546963")); - QVERIFY(ok); } { QByteArray key = QByteArray::fromHex("0000000000000000000000000000000000000000000000000000000000000000"); QByteArray iv = QByteArray::fromHex("0000000000000001"); - SymmetricCipher cipher(SymmetricCipher::ChaCha20, SymmetricCipher::Stream, SymmetricCipher::Encrypt); - QVERIFY(cipher.init(key, iv)); - QCOMPARE(cipher.process(QByteArray(60, 0), &ok), + SymmetricCipher cipher; + QByteArray data(60, 0); + QVERIFY(cipher.init(SymmetricCipher::ChaCha20, SymmetricCipher::Encrypt, key, iv)); + QVERIFY(cipher.process(data)); + QCOMPARE(data, QByteArray::fromHex("de9cba7bf3d69ef5e786dc63973f653a0b49e015adbff7134fcb7df137821031e85a050278a708452" "7214f73efc7fa5b5277062eb7a0433e445f41e3")); - QVERIFY(ok); } { QByteArray key = QByteArray::fromHex("0000000000000000000000000000000000000000000000000000000000000000"); QByteArray iv = QByteArray::fromHex("0100000000000000"); - SymmetricCipher cipher(SymmetricCipher::ChaCha20, SymmetricCipher::Stream, SymmetricCipher::Encrypt); - QVERIFY(cipher.init(key, iv)); - QCOMPARE(cipher.process(QByteArray(64, 0), &ok), + SymmetricCipher cipher; + QByteArray data(64, 0); + QVERIFY(cipher.init(SymmetricCipher::ChaCha20, SymmetricCipher::Encrypt, key, iv)); + QVERIFY(cipher.process(data)); + QCOMPARE(data, QByteArray::fromHex("ef3fdfd6c61578fbf5cf35bd3dd33b8009631634d21e42ac33960bd138e50d32111e4caf237ee53ca" "8ad6426194a88545ddc497a0b466e7d6bbdb0041b2f586b")); - QVERIFY(ok); } } @@ -477,8 +421,8 @@ void TestSymmetricCipher::testPadding() QBuffer buffer; buffer.open(QIODevice::ReadWrite); - SymmetricCipherStream streamEnc(&buffer, SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Encrypt); - QVERIFY(streamEnc.init(key, iv)); + SymmetricCipherStream streamEnc(&buffer); + QVERIFY(streamEnc.init(SymmetricCipher::Aes256_CBC, SymmetricCipher::Encrypt, key, iv)); streamEnc.open(QIODevice::WriteOnly); streamEnc.write(plainText); streamEnc.close(); @@ -486,8 +430,8 @@ void TestSymmetricCipher::testPadding() // make sure padding is written QCOMPARE(buffer.buffer().size(), 16); - SymmetricCipherStream streamDec(&buffer, SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt); - QVERIFY(streamDec.init(key, iv)); + SymmetricCipherStream streamDec(&buffer); + QVERIFY(streamDec.init(SymmetricCipher::Aes256_CBC, SymmetricCipher::Decrypt, key, iv)); streamDec.open(QIODevice::ReadOnly); QByteArray decrypted = streamDec.readAll(); QCOMPARE(decrypted, plainText); @@ -500,8 +444,8 @@ void TestSymmetricCipher::testStreamReset() QBuffer buffer; QVERIFY(buffer.open(QIODevice::WriteOnly)); - SymmetricCipherStream writer(&buffer, SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Encrypt); - QVERIFY(writer.init(key, iv)); + SymmetricCipherStream writer(&buffer); + QVERIFY(writer.init(SymmetricCipher::Aes256_CBC, SymmetricCipher::Encrypt, key, iv)); QVERIFY(writer.open(QIODevice::WriteOnly)); QCOMPARE(writer.write(QByteArray(4, 'Z')), qint64(4)); // test if reset() and close() write only one block diff --git a/tests/TestSymmetricCipher.h b/tests/TestSymmetricCipher.h index 5eede0953a..28ec432b64 100644 --- a/tests/TestSymmetricCipher.h +++ b/tests/TestSymmetricCipher.h @@ -27,13 +27,12 @@ class TestSymmetricCipher : public QObject private slots: void initTestCase(); - void testAlgorithmToCipher(); + void testCipherUuidToMode(); void testEncryptionDecryption_data(); void testEncryptionDecryption(); void testAesCbcPadding_data(); void testAesCbcPadding(); - void testInplaceEcb_data(); - void testInplaceEcb(); + void testAesKdf(); void testTwofish256CbcEncryption(); void testTwofish256CbcDecryption(); void testSalsa20(); diff --git a/tests/gui/TestGuiFdoSecrets.cpp b/tests/gui/TestGuiFdoSecrets.cpp index 5550212743..1e8834e973 100644 --- a/tests/gui/TestGuiFdoSecrets.cpp +++ b/tests/gui/TestGuiFdoSecrets.cpp @@ -32,6 +32,7 @@ #include "core/Config.h" #include "core/Tools.h" #include "crypto/Crypto.h" +#include "crypto/Random.h" #include "gui/Application.h" #include "gui/DatabaseTabWidget.h" #include "gui/DatabaseWidget.h" @@ -160,49 +161,22 @@ void TestGuiFdoSecrets::initTestCase() VERIFY(m_plugin); m_mainWindow->show(); + auto key = QByteArray::fromHex("e407997e8b918419cf851cf3345358fdf" + "ffb9564a220ac9c3934efd277cea20d17" + "467ecdc56e817f75ac39501f38a4a04ff" + "64d627e16c09981c7ad876da255b61c8e" + "6a8408236c2a4523cfe6961c26dbdfc77" + "c1a27a5b425ca71a019e829fae32c0b42" + "0e1b3096b48bc2ce9ccab1d1ff13a5eb4" + "b263cee30bdb1a57af9bfa93f"); + m_clientCipher.reset(new DhIetf1024Sha256Aes128CbcPkcs7(key)); + // Load the NewDatabase.kdbx file into temporary storage QFile sourceDbFile(QStringLiteral(KEEPASSX_TEST_DATA_DIR "/NewDatabase.kdbx")); VERIFY(sourceDbFile.open(QIODevice::ReadOnly)); VERIFY(Tools::readAllFromDevice(&sourceDbFile, m_dbData)); sourceDbFile.close(); - // set keys for session encryption - m_serverPublic = MpiFromHex("e407997e8b918419cf851cf3345358fdf" - "ffb9564a220ac9c3934efd277cea20d17" - "467ecdc56e817f75ac39501f38a4a04ff" - "64d627e16c09981c7ad876da255b61c8e" - "6a8408236c2a4523cfe6961c26dbdfc77" - "c1a27a5b425ca71a019e829fae32c0b42" - "0e1b3096b48bc2ce9ccab1d1ff13a5eb4" - "b263cee30bdb1a57af9bfa93f"); - m_serverPrivate = MpiFromHex("013f4f3381ef0ca11c4c7363079577b56" - "99b238644e0aba47e24bdba6173590216" - "4f1e12dd0944800a373e090e63192f53b" - "93583e9a9e50bb9d792aafaa3a0f5ae77" - "de0c3423f5820848d88ee3bdd01c889f2" - "7af58a02f5b6693d422b9d189b300d7b1" - "be5076b5795cf8808c31e2e2898368d18" - "ab5c26b0ea3480c9aba8154cf"); - // use the same cipher to do the client side encryption, but exchange the position of client/server keys - m_cipher.reset(new DhIetf1024Sha256Aes128CbcPkcs7); - VERIFY(m_cipher->initialize(MpiFromBytes(MpiToBytes(m_serverPublic)), - MpiFromHex("30d18c6b328bac970c05bda6af2e708b9" - "d6bbbb6dc136c1a2d96e870fabc86ad74" - "1846a26a4197f32f65ea2e7580ad2afe3" - "dd5d6c1224b8368b0df2cd75d520a9ff9" - "7fe894cc7da71b7bd285b4633359c16c8" - "d341f822fa4f0fdf59b5d3448658c46a2" - "a86dbb14ff85823873f8a259ccc52bbb8" - "2b5a4c2a75447982553b42221"), - MpiFromHex("84aafe9c9f356f7762307f4d791acb59e" - "8e3fd562abdbb481d0587f8400ad6c51d" - "af561a1beb9a22c8cd4d2807367c5787b" - "2e06d631ccbb5194b6bb32211583ce688" - "f9c2cebc22a9e4d494d12ebdd570c61a1" - "62a94e88561d25ccd0415339d1f59e1b0" - "6bc6b6b5fde46e23b2410eb034be390d3" - "2407ec7ae90f0831f24afd5ac"))); - // set a fake dbus client all the time so we can freely access DBusMgr anywhere m_client.reset(new FakeClient(m_plugin->dbus().data())); m_plugin->dbus()->overrideClient(m_client); @@ -875,7 +849,7 @@ void TestGuiFdoSecrets::testItemCreate() // secrets { DBUS_GET(ss, item->GetSecret(QDBusObjectPath(sess->path()))); - auto decrypted = m_cipher->decrypt(ss.unmarshal(m_plugin->dbus())); + auto decrypted = m_clientCipher->decrypt(ss.unmarshal(m_plugin->dbus())); COMPARE(decrypted.value, QByteArrayLiteral("Password")); } @@ -1075,7 +1049,7 @@ void TestGuiFdoSecrets::testItemSecret() // plain text secret { DBUS_GET(encrypted, item->GetSecret(QDBusObjectPath(sess->path()))); - auto ss = m_cipher->decrypt(encrypted.unmarshal(m_plugin->dbus())); + auto ss = m_clientCipher->decrypt(encrypted.unmarshal(m_plugin->dbus())); COMPARE(ss.contentType, TEXT_PLAIN); COMPARE(ss.value, entry->password().toUtf8()); } @@ -1087,7 +1061,7 @@ void TestGuiFdoSecrets::testItemSecret() VERIFY(spyShowNotification.isValid()); DBUS_GET(encrypted, item->GetSecret(QDBusObjectPath(sess->path()))); - auto ss = m_cipher->decrypt(encrypted.unmarshal(m_plugin->dbus())); + auto ss = m_clientCipher->decrypt(encrypted.unmarshal(m_plugin->dbus())); COMPARE(ss.contentType, TEXT_PLAIN); COMPARE(ss.value, entry->password().toUtf8()); @@ -1107,7 +1081,7 @@ void TestGuiFdoSecrets::testItemSecret() ss.contentType = TEXT_PLAIN; ss.value = "NewPassword"; ss.session = QDBusObjectPath(sess->path()); - auto encrypted = m_cipher->encrypt(ss.unmarshal(m_plugin->dbus())); + auto encrypted = m_clientCipher->encrypt(ss.unmarshal(m_plugin->dbus())); DBUS_VERIFY(item->SetSecret(encrypted.marshal())); COMPARE(entry->password().toUtf8(), ss.value); @@ -1119,12 +1093,12 @@ void TestGuiFdoSecrets::testItemSecret() expected.contentType = APPLICATION_OCTET_STREAM; expected.value = QByteArrayLiteral("NewPasswordBinary"); expected.session = QDBusObjectPath(sess->path()); - DBUS_VERIFY(item->SetSecret(m_cipher->encrypt(expected.unmarshal(m_plugin->dbus())).marshal())); + DBUS_VERIFY(item->SetSecret(m_clientCipher->encrypt(expected.unmarshal(m_plugin->dbus())).marshal())); COMPARE(entry->password(), QStringLiteral("")); DBUS_GET(encrypted, item->GetSecret(QDBusObjectPath(sess->path()))); - auto ss = m_cipher->decrypt(encrypted.unmarshal(m_plugin->dbus())); + auto ss = m_clientCipher->decrypt(encrypted.unmarshal(m_plugin->dbus())); COMPARE(ss.contentType, expected.contentType); COMPARE(ss.value, expected.value); } @@ -1195,7 +1169,7 @@ void TestGuiFdoSecrets::testItemLockState() "text/plain", } .unmarshal(m_plugin->dbus()); - auto encrypted = m_cipher->encrypt(secret).marshal(); + auto encrypted = m_clientCipher->encrypt(secret).marshal(); // when access confirmation is disabled, item is unlocked when the collection is unlocked FdoSecrets::settings()->setConfirmAccessItem(false); @@ -1364,13 +1338,8 @@ QSharedPointer TestGuiFdoSecrets::openSession(const QSharedPointer return getProxy(sessPath); } else if (algo == DhIetf1024Sha256Aes128CbcPkcs7::Algorithm) { - - DhIetf1024Sha256Aes128CbcPkcs7::fixNextServerKeys(MpiFromBytes(MpiToBytes(m_serverPrivate)), - MpiFromBytes(MpiToBytes(m_serverPublic))); - - DBUS_GET2(output, sessPath, service->OpenSession(algo, QDBusVariant(m_cipher->m_publicKey))); - - COMPARE(qvariant_cast(output.variant()), MpiToBytes(m_serverPublic)); + DBUS_GET2(output, sessPath, service->OpenSession(algo, QDBusVariant(m_clientCipher->negotiationOutput()))); + m_clientCipher->updateClientPublicKey(output.variant().toByteArray()); return getProxy(sessPath); } QTest::qFail("Unsupported algorithm", __FILE__, __LINE__); @@ -1412,7 +1381,7 @@ QSharedPointer TestGuiFdoSecrets::createItem(const QSharedPointerpath()); ss.value = pass.toLocal8Bit(); ss.contentType = "plain/text"; - auto encrypted = m_cipher->encrypt(ss.unmarshal(m_plugin->dbus())).marshal(); + auto encrypted = m_clientCipher->encrypt(ss.unmarshal(m_plugin->dbus())).marshal(); DBUS_GET2(itemPath, promptPath, coll->CreateItem(properties, encrypted, replace)); diff --git a/tests/gui/TestGuiFdoSecrets.h b/tests/gui/TestGuiFdoSecrets.h index 8ded865869..e25472ae0f 100644 --- a/tests/gui/TestGuiFdoSecrets.h +++ b/tests/gui/TestGuiFdoSecrets.h @@ -25,7 +25,6 @@ #include #include -#include "fdosecrets/GcryptMPI.h" #include "fdosecrets/dbus/DBusTypes.h" class MainWindow; @@ -139,10 +138,7 @@ protected slots: QPointer m_plugin; QSharedPointer m_client; - // For DH session tests - GcryptMPI m_serverPrivate; - GcryptMPI m_serverPublic; - std::unique_ptr m_cipher; + QScopedPointer m_clientCipher; QByteArray m_dbData; QScopedPointer m_dbFile; diff --git a/tests/mock/MockChallengeResponseKey.cpp b/tests/mock/MockChallengeResponseKey.cpp index 1fa79dc844..3ca387bb38 100644 --- a/tests/mock/MockChallengeResponseKey.cpp +++ b/tests/mock/MockChallengeResponseKey.cpp @@ -27,13 +27,13 @@ MockChallengeResponseKey::~MockChallengeResponseKey() { } -QByteArray MockChallengeResponseKey::rawKey() const -{ - return m_challenge + m_secret; -} - bool MockChallengeResponseKey::challenge(const QByteArray& challenge) { m_challenge = challenge; + + auto response = m_challenge + m_secret; + m_key.resize(response.size()); + std::copy(response.begin(), response.end(), m_key.data()); + return true; } diff --git a/tests/mock/MockChallengeResponseKey.h b/tests/mock/MockChallengeResponseKey.h index a9aeeb412d..0a3656bb54 100644 --- a/tests/mock/MockChallengeResponseKey.h +++ b/tests/mock/MockChallengeResponseKey.h @@ -30,7 +30,6 @@ class MockChallengeResponseKey : public ChallengeResponseKey explicit MockChallengeResponseKey(const QByteArray& secret); Q_DISABLE_COPY(MockChallengeResponseKey); ~MockChallengeResponseKey() override; - QByteArray rawKey() const override; bool challenge(const QByteArray& challenge) override; private: diff --git a/tests/stub/TestRandom.cpp b/tests/stub/TestRandom.cpp deleted file mode 100644 index d33b1c9b8f..0000000000 --- a/tests/stub/TestRandom.cpp +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2018 KeePassXC Team - * - * 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 . - */ - -#include "TestRandom.h" -#include "TestGlobal.h" - -RandomBackendPreset::RandomBackendPreset() - : m_bytesIndex(0) -{ -} - -void RandomBackendPreset::randomize(void* data, int len) -{ - QVERIFY(len <= (m_nextBytes.size() - m_bytesIndex)); - - char* charData = reinterpret_cast(data); - - for (int i = 0; i < len; i++) { - charData[i] = m_nextBytes[m_bytesIndex + i]; - } - - m_bytesIndex += len; -} - -void RandomBackendPreset::setNextBytes(const QByteArray& nextBytes) -{ - m_nextBytes = nextBytes; - m_bytesIndex = 0; -} - -void TestRandom::setup(RandomBackend* backend) -{ - Random::setInstance(backend); -} - -void TestRandom::teardown() -{ - Random::resetInstance(); -} - -void RandomBackendNull::randomize(void* data, int len) -{ - char* charData = reinterpret_cast(data); - - for (int i = 0; i < len; i++) { - charData[i] = '\0'; - } -} diff --git a/tests/stub/TestRandom.h b/tests/stub/TestRandom.h deleted file mode 100644 index ec5afb59d9..0000000000 --- a/tests/stub/TestRandom.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2018 KeePassXC Team - * - * 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 . - */ - -#ifndef KEEPASSXC_TESTRANDOM_H -#define KEEPASSXC_TESTRANDOM_H - -#include "crypto/Random.h" - -class RandomBackendPreset : public RandomBackend -{ -public: - RandomBackendPreset(); - void randomize(void* data, int len) override; - void setNextBytes(const QByteArray& nextBytes); - -private: - QByteArray m_nextBytes; - int m_bytesIndex; -}; - -class RandomBackendNull : public RandomBackend -{ -public: - void randomize(void* data, int len) override; -}; - -class TestRandom : public Random -{ -public: - static void setup(RandomBackend* backend); - static void teardown(); -}; - -#endif // KEEPASSXC_TESTRANDOM_H