Replace all crypto libraries with Botan

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

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

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

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

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

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

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

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

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

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

View File

@ -86,12 +86,6 @@ if(WITH_XC_ALL)
endif()
endif()
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 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)
find_package(QREncode REQUIRED)
find_package(sodium 1.0.12 REQUIRED)
set(CMAKE_REQUIRED_INCLUDES ${ZLIB_INCLUDE_DIR})
if(ZLIB_VERSION_STRING VERSION_LESS "1.2.0")
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)

View File

@ -46,6 +46,10 @@ Files: cmake/FindYubiKey.cmake
Copyright: 2014 Kyle Manna <kyle@kylemanna.com>
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 <akharlov@gmail.com>
License: MIT

121
cmake/FindBotan2.cmake Normal file
View File

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

View File

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

View File

@ -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 ".")

View File

@ -24,9 +24,9 @@
#include <QJsonDocument>
#include <QJsonParseError>
#include <sodium.h>
#include <sodium/crypto_box.h>
#include <sodium/randombytes.h>
#include <botan/sodium.h>
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()) {

View File

@ -25,7 +25,6 @@
#include <QMutexLocker>
#include <QtNetwork>
#include "sodium.h"
#include <iostream>
#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());
}

View File

@ -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()

View File

@ -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

View File

@ -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<YkChallengeResponseKeyCLI>(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<YkChallengeResponseKey>(new YkChallengeResponseKey({serial, slot}));
compositeKey->addChallengeResponseKey(key);
QObject::disconnect(conn);
}
#else
Q_UNUSED(yubiKeySlot);

View File

@ -16,9 +16,9 @@
*/
#include <QtGlobal>
#include <botan/mem_ops.h>
#include <cstdint>
#include <cstdlib>
#include <sodium.h>
#if defined(Q_OS_MACOS)
#include <malloc/malloc.h>
#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);
}

View File

@ -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<char*>(raw), size);
}
} // namespace Tools

View File

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

View File

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

View File

@ -1,4 +1,5 @@
/*
* Copyright (C) 2021 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
*
* This program is free software: you can redistribute it and/or modify
@ -15,36 +16,16 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSX_CRYPTO_H
#define KEEPASSX_CRYPTO_H
#ifndef KEEPASSXC_CRYPTO_H
#define KEEPASSXC_CRYPTO_H
#include <QString>
class Crypto
namespace Crypto
{
public:
static bool init();
static bool initialized();
static bool backendSelfTest();
static QString errorString();
static QString debugInfo();
bool init();
QString errorString();
QString debugInfo();
}; // namespace Crypto
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;
};
#endif // KEEPASSX_CRYPTO_H
#endif // KEEPASSXC_CRYPTO_H

View File

@ -1,4 +1,5 @@
/*
* Copyright (C) 2021 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
*
* This program is free software: you can redistribute it and/or modify
@ -17,15 +18,17 @@
#include "CryptoHash.h"
#include <gcrypt.h>
#include "crypto/Crypto.h"
#include <QScopedPointer>
#include <botan/hash.h>
#include <botan/mac.h>
class CryptoHashPrivate
{
public:
gcry_md_hd_t ctx;
int hashLen;
QScopedPointer<Botan::HashFunction> hashFunction;
QScopedPointer<Botan::MessageAuthenticationCode> hmacFunction;
};
CryptoHash::CryptoHash(Algorithm algo, bool hmac)
@ -33,45 +36,31 @@ CryptoHash::CryptoHash(Algorithm algo, bool hmac)
{
Q_D(CryptoHash);
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,47 @@ void CryptoHash::addData(const QByteArray& data)
return;
}
gcry_md_write(d->ctx, data.constData(), static_cast<size_t>(data.size()));
try {
if (d->hmacFunction) {
d->hmacFunction->update(reinterpret_cast<const uint8_t*>(data.data()), data.size());
} else if (d->hashFunction) {
d->hashFunction->update(reinterpret_cast<const uint8_t*>(data.data()), data.size());
}
} catch (std::exception& e) {
qWarning("CryptoHash::update failed to add data: %s", e.what());
Q_ASSERT(false);
}
}
void CryptoHash::setKey(const QByteArray& data)
{
Q_D(CryptoHash);
gcry_error_t error = gcry_md_setkey(d->ctx, data.constData(), static_cast<size_t>(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<const uint8_t*>(data.data()), data.size());
} catch (std::exception& e) {
qWarning("CryptoHash::setKey failed to set HMAC key: %s", e.what());
Q_ASSERT(false);
}
}
Q_ASSERT(error == 0);
}
QByteArray CryptoHash::result() const
{
Q_D(const CryptoHash);
const auto result = reinterpret_cast<const char*>(gcry_md_read(d->ctx, 0));
return QByteArray(result, d->hashLen);
Botan::secure_vector<uint8_t> result;
if (d->hmacFunction) {
result = d->hmacFunction->final();
} else if (d->hashFunction) {
result = d->hashFunction->final();
}
return QByteArray(reinterpret_cast<const char*>(result.data()), result.size());
}
QByteArray CryptoHash::hash(const QByteArray& data, Algorithm algo)
{
// replace with gcry_md_hash_buffer()?
CryptoHash cryptoHash(algo);
cryptoHash.addData(data);
return cryptoHash.result();
@ -114,7 +119,6 @@ QByteArray CryptoHash::hash(const QByteArray& data, Algorithm algo)
QByteArray CryptoHash::hmac(const QByteArray& data, const QByteArray& key, Algorithm algo)
{
// replace with gcry_md_hash_buffer()?
CryptoHash cryptoHash(algo, true);
cryptoHash.setKey(key);
cryptoHash.addData(data);

View File

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

View File

@ -17,46 +17,64 @@
#include "Random.h"
#include <gcrypt.h>
#include "core/Global.h"
#include "crypto/Crypto.h"
class RandomBackendGcrypt : public RandomBackend
{
public:
void randomize(void* data, int len) override;
};
#include <QPointer>
#include <QSharedPointer>
#include <botan/auto_rng.h>
#include <botan/system_rng.h>
QSharedPointer<Random> Random::m_instance;
QSharedPointer<Random> Random::instance()
{
if (!m_instance) {
m_instance.reset(new Random());
}
return m_instance;
}
Random::Random()
{
#ifdef BOTAN_HAS_SYSTEM_RNG
m_rng.reset(new Botan::System_RNG);
#else
m_rng.reset(new Botan::Autoseeded_RNG);
#endif
}
QSharedPointer<Botan::RandomNumberGenerator> Random::getRng()
{
return m_rng;
}
void Random::randomize(QByteArray& ba)
{
m_backend->randomize(ba.data(), ba.size());
m_rng->randomize(reinterpret_cast<uint8_t*>(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<uint8_t*>(&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()
{
}

View File

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

View File

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

View File

@ -23,29 +23,26 @@
#include <QString>
#include <QUuid>
#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 @@ public:
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);
}
static bool aesKdf(const QByteArray& key, int rounds, QByteArray& data);
Q_REQUIRED_RESULT inline bool processInPlace(QByteArray& data)
{
return m_backend->processInPlace(data);
}
void reset();
Mode mode();
Q_REQUIRED_RESULT inline bool processInPlace(QByteArray& data, quint64 rounds)
{
Q_ASSERT(rounds > 0);
return m_backend->processInPlace(data, rounds);
}
bool reset();
int keySize() const;
int blockSize() const;
QString errorString() const;
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<SymmetricCipherBackend> m_backend;
bool m_initialized;
Algorithm m_algo;
QString m_error;
Mode m_mode;
QSharedPointer<Botan::Cipher_Mode> m_cipher;
Q_DISABLE_COPY(SymmetricCipher)
};
#endif // KEEPASSX_SYMMETRICCIPHER_H

View File

@ -1,44 +0,0 @@
/*
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSX_SYMMETRICCIPHERBACKEND_H
#define KEEPASSX_SYMMETRICCIPHERBACKEND_H
#include <QByteArray>
class SymmetricCipherBackend
{
public:
virtual ~SymmetricCipherBackend()
{
}
virtual bool init() = 0;
virtual bool setKey(const QByteArray& key) = 0;
virtual bool setIv(const QByteArray& iv) = 0;
virtual QByteArray process(const QByteArray& data, bool* ok) = 0;
Q_REQUIRED_RESULT virtual bool processInPlace(QByteArray& data) = 0;
Q_REQUIRED_RESULT virtual bool processInPlace(QByteArray& data, quint64 rounds) = 0;
virtual bool reset() = 0;
virtual int keySize() const = 0;
virtual int blockSize() const = 0;
virtual QString error() const = 0;
};
#endif // KEEPASSX_SYMMETRICCIPHERBACKEND_H

View File

@ -1,261 +0,0 @@
/*
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "SymmetricCipherGcrypt.h"
#include "config-keepassx.h"
#include "crypto/Crypto.h"
SymmetricCipherGcrypt::SymmetricCipherGcrypt(SymmetricCipher::Algorithm algo,
SymmetricCipher::Mode mode,
SymmetricCipher::Direction direction)
: m_ctx(nullptr)
, m_algo(gcryptAlgo(algo))
, m_mode(gcryptMode(mode))
, m_direction(direction)
{
}
SymmetricCipherGcrypt::~SymmetricCipherGcrypt()
{
gcry_cipher_close(m_ctx);
}
int SymmetricCipherGcrypt::gcryptAlgo(SymmetricCipher::Algorithm algo)
{
switch (algo) {
case SymmetricCipher::Aes128:
return GCRY_CIPHER_AES128;
case SymmetricCipher::Aes256:
return GCRY_CIPHER_AES256;
case SymmetricCipher::Twofish:
return GCRY_CIPHER_TWOFISH;
case SymmetricCipher::Salsa20:
return GCRY_CIPHER_SALSA20;
case SymmetricCipher::ChaCha20:
return GCRY_CIPHER_CHACHA20;
default:
Q_ASSERT(false);
return -1;
}
}
int SymmetricCipherGcrypt::gcryptMode(SymmetricCipher::Mode mode)
{
switch (mode) {
case SymmetricCipher::Ecb:
return GCRY_CIPHER_MODE_ECB;
case SymmetricCipher::Cbc:
return GCRY_CIPHER_MODE_CBC;
case SymmetricCipher::Ctr:
return GCRY_CIPHER_MODE_CTR;
case SymmetricCipher::Stream:
return GCRY_CIPHER_MODE_STREAM;
default:
Q_ASSERT(false);
return -1;
}
}
void SymmetricCipherGcrypt::setError(const gcry_error_t& err)
{
const char* gcryptError = gcry_strerror(err);
const char* gcryptErrorSource = gcry_strsource(err);
m_error = QString("%1/%2").arg(QString::fromLocal8Bit(gcryptErrorSource), QString::fromLocal8Bit(gcryptError));
}
bool SymmetricCipherGcrypt::init()
{
Q_ASSERT(Crypto::initialized());
gcry_error_t error;
if (m_ctx != nullptr)
gcry_cipher_close(m_ctx);
error = gcry_cipher_open(&m_ctx, m_algo, m_mode, 0);
if (error != 0) {
setError(error);
return false;
}
return true;
}
bool SymmetricCipherGcrypt::setKey(const QByteArray& key)
{
m_key = key;
gcry_error_t error = gcry_cipher_setkey(m_ctx, m_key.constData(), m_key.size());
if (error != 0) {
setError(error);
return false;
}
return true;
}
bool SymmetricCipherGcrypt::setIv(const QByteArray& iv)
{
m_iv = iv;
gcry_error_t error;
if (m_mode == GCRY_CIPHER_MODE_CTR) {
error = gcry_cipher_setctr(m_ctx, m_iv.constData(), m_iv.size());
} else {
error = gcry_cipher_setiv(m_ctx, m_iv.constData(), m_iv.size());
}
if (error != 0) {
setError(error);
return false;
}
return true;
}
QByteArray SymmetricCipherGcrypt::process(const QByteArray& data, bool* ok)
{
// TODO: check block size
QByteArray result;
result.resize(data.size());
gcry_error_t error;
if (m_direction == SymmetricCipher::Decrypt) {
error = gcry_cipher_decrypt(m_ctx, result.data(), data.size(), data.constData(), data.size());
} else {
error = gcry_cipher_encrypt(m_ctx, result.data(), data.size(), data.constData(), data.size());
}
if (error != 0) {
setError(error);
*ok = false;
} else {
*ok = true;
}
return result;
}
bool SymmetricCipherGcrypt::processInPlace(QByteArray& data)
{
// TODO: check block size
gcry_error_t error;
if (m_direction == SymmetricCipher::Decrypt) {
error = gcry_cipher_decrypt(m_ctx, data.data(), data.size(), nullptr, 0);
} else {
error = gcry_cipher_encrypt(m_ctx, data.data(), data.size(), nullptr, 0);
}
if (error != 0) {
setError(error);
return false;
}
return true;
}
bool SymmetricCipherGcrypt::processInPlace(QByteArray& data, quint64 rounds)
{
gcry_error_t error;
char* rawData = data.data();
int size = data.size();
if (m_direction == SymmetricCipher::Decrypt) {
for (quint64 i = 0; i != rounds; ++i) {
error = gcry_cipher_decrypt(m_ctx, rawData, size, nullptr, 0);
if (error != 0) {
setError(error);
return false;
}
}
} else {
for (quint64 i = 0; i != rounds; ++i) {
error = gcry_cipher_encrypt(m_ctx, rawData, size, nullptr, 0);
if (error != 0) {
setError(error);
return false;
}
}
}
return true;
}
bool SymmetricCipherGcrypt::reset()
{
gcry_error_t error;
error = gcry_cipher_reset(m_ctx);
if (error != 0) {
setError(error);
return false;
}
error = gcry_cipher_setiv(m_ctx, m_iv.constData(), m_iv.size());
if (error != 0) {
setError(error);
return false;
}
return true;
}
int SymmetricCipherGcrypt::keySize() const
{
gcry_error_t error;
size_t keySizeT;
error = gcry_cipher_algo_info(m_algo, GCRYCTL_GET_KEYLEN, nullptr, &keySizeT);
if (error != 0)
return -1;
return keySizeT;
}
int SymmetricCipherGcrypt::blockSize() const
{
gcry_error_t error;
size_t blockSizeT;
error = gcry_cipher_algo_info(m_algo, GCRYCTL_GET_BLKLEN, nullptr, &blockSizeT);
if (error != 0)
return -1;
return blockSizeT;
}
QString SymmetricCipherGcrypt::error() const
{
return m_error;
}

View File

@ -1,62 +0,0 @@
/*
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSX_SYMMETRICCIPHERGCRYPT_H
#define KEEPASSX_SYMMETRICCIPHERGCRYPT_H
#include <gcrypt.h>
#include "crypto/SymmetricCipher.h"
#include "crypto/SymmetricCipherBackend.h"
class SymmetricCipherGcrypt : public SymmetricCipherBackend
{
public:
SymmetricCipherGcrypt(SymmetricCipher::Algorithm algo,
SymmetricCipher::Mode mode,
SymmetricCipher::Direction direction);
~SymmetricCipherGcrypt();
bool init();
bool setKey(const QByteArray& key);
bool setIv(const QByteArray& iv);
QByteArray process(const QByteArray& data, bool* ok);
Q_REQUIRED_RESULT bool processInPlace(QByteArray& data);
Q_REQUIRED_RESULT bool processInPlace(QByteArray& data, quint64 rounds);
bool reset();
int keySize() const;
int blockSize() const;
QString error() const;
private:
static int gcryptAlgo(SymmetricCipher::Algorithm algo);
static int gcryptMode(SymmetricCipher::Mode mode);
void setError(const gcry_error_t& err);
gcry_cipher_hd_t m_ctx;
const int m_algo;
const int m_mode;
const SymmetricCipher::Direction m_direction;
QByteArray m_key;
QByteArray m_iv;
QString m_error;
};
#endif // KEEPASSX_SYMMETRICCIPHERGCRYPT_H

View File

@ -1,34 +0,0 @@
/*
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSXC_CRYPTO_ARGON2_H
#define KEEPASSXC_CRYPTO_ARGON2_H
/*
Argon2 wrapper header with redefined symbols to be used with the
patched libargon2 binary which is generated by the build system.
This is to avoid link-time definition clashes with libsodium on Windows.
*/
#ifdef Q_OS_WIN
#define argon2_hash libargon2_argon2_hash
#define argon2_error_message libargon2_argon2_error_message
#endif
#include <argon2.h>
#endif // KEEPASSXC_CRYPTO_ARGON2_H

View File

@ -20,6 +20,7 @@
#include <QtConcurrent>
#include "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<Kdf> AesKdf::clone() const
@ -106,24 +93,24 @@ QSharedPointer<Kdf> AesKdf::clone() const
return QSharedPointer<AesKdf>::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);
QByteArray key(16, '\x7E');
QByteArray seed(32, '\x4B');
SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Ecb, SymmetricCipher::Encrypt);
cipher.init(seed, iv);
int trials = 3;
int rounds = 1000000;
quint64 rounds = 1000000;
QElapsedTimer timer;
timer.start();
if (!cipher.processInPlace(key, rounds)) {
return -1;
for (int i = 0; i < trials; ++i) {
QByteArray result;
if (!transformKeyRaw(key, seed, rounds, &result)) {
return rounds;
}
}
return static_cast<int>(rounds * (static_cast<float>(msec) / timer.elapsed()));
return static_cast<int>(rounds * trials * static_cast<float>(msec) / timer.elapsed());
}
QString AesKdf::toString() const

View File

@ -32,8 +32,7 @@ public:
QSharedPointer<Kdf> 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

View File

@ -18,8 +18,8 @@
#include "Argon2Kdf.h"
#include <QtConcurrent>
#include <botan/pwdhash.h>
#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<uint8_t*>(result.data()),
result.size(),
raw.constData(),
raw.size(),
reinterpret_cast<const uint8_t*>(seed().constData()),
seed().size());
return true;
} catch (std::exception& e) {
qWarning("Argon2 error: %s", e.what());
return false;
}
return true;
}
QSharedPointer<Kdf> Argon2Kdf::clone() const
@ -203,20 +184,16 @@ QSharedPointer<Kdf> Argon2Kdf::clone() const
return QSharedPointer<Argon2Kdf>::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<int>(rounds * (static_cast<float>(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<size_t>(1), pwhash->iterations());
} catch (std::exception& e) {
return 1;
}
return 1;
}
QString Argon2Kdf::toString() const

View File

@ -45,22 +45,11 @@ public:
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

View File

@ -16,7 +16,6 @@
*/
#include "Kdf.h"
#include "Kdf_p.h"
#include <QtConcurrent>
@ -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);
}

View File

@ -46,7 +46,7 @@ public:
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 @@ public:
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

View File

@ -1,43 +0,0 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Kdf.h"
#include <QThread>
#ifndef KEEPASSXC_KDF_P_H
#define KEEPASSXC_KDF_P_H
class Kdf::BenchmarkThread : public QThread
{
Q_OBJECT
public:
explicit BenchmarkThread(int msec, const Kdf* kdf);
int rounds();
protected:
void run();
private:
int m_rounds;
int m_msec;
const Kdf* m_kdf;
};
#endif // KEEPASSXC_KDF_P_H

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,9 +18,6 @@ if(WITH_XC_FDOSECRETS)
# setting storage
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()

View File

@ -1,63 +0,0 @@
/*
* Copyright (C) 2019 Aetf <aetf@unlimitedcodeworks.xyz>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "GcryptMPI.h"
GcryptMPI MpiFromBytes(const QByteArray& bytes, bool secure, gcry_mpi_format format)
{
auto bufLen = static_cast<size_t>(bytes.size());
const char* buf = nullptr;
// gcry_mpi_scan uses secure memory only if input buffer is secure memory, so we have to make a copy
GcryptMemPtr secureBuf;
if (secure) {
secureBuf.reset(static_cast<char*>(gcry_malloc_secure(bufLen)));
memcpy(secureBuf.get(), bytes.data(), bufLen);
buf = secureBuf.get();
} else {
buf = bytes.data();
}
gcry_mpi_t rawMpi;
auto err = gcry_mpi_scan(&rawMpi, format, buf, format == GCRYMPI_FMT_HEX ? 0 : bufLen, nullptr);
GcryptMPI mpi(rawMpi);
if (gcry_err_code(err) != GPG_ERR_NO_ERROR) {
mpi.reset();
}
return mpi;
}
GcryptMPI MpiFromHex(const char* hex, bool secure)
{
return MpiFromBytes(QByteArray::fromRawData(hex, static_cast<int>(strlen(hex) + 1)), secure, GCRYMPI_FMT_HEX);
}
QByteArray MpiToBytes(const GcryptMPI& mpi)
{
unsigned char* buf = nullptr;
size_t buflen = 0;
gcry_mpi_aprint(GCRYMPI_FMT_USG, &buf, &buflen, mpi.get());
QByteArray bytes(reinterpret_cast<char*>(buf), static_cast<int>(buflen));
gcry_free(buf);
return bytes;
}

View File

@ -1,64 +0,0 @@
/*
* Copyright (C) 2019 Aetf <aetf@unlimitedcodeworks.xyz>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSXC_GCRYPTMPI_H
#define KEEPASSXC_GCRYPTMPI_H
#include <QByteArray>
#include <gcrypt.h>
#include <memory>
#include <type_traits>
template <typename D, D fn> using deleter_from_fn = std::integral_constant<D, fn>;
/**
* A simple RAII wrapper for gcry_mpi_t
*/
using GcryptMPI = std::unique_ptr<gcry_mpi, deleter_from_fn<decltype(&gcry_mpi_release), &gcry_mpi_release>>;
/**
* A simple RAII wrapper for libgcrypt allocated memory
*/
using GcryptMemPtr = std::unique_ptr<char, deleter_from_fn<decltype(&gcry_free), &gcry_free>>;
/**
* Parse a QByteArray as MPI
* @param bytes
* @param secure
* @param format
* @return
*/
GcryptMPI MpiFromBytes(const QByteArray& bytes, bool secure = true, gcry_mpi_format format = GCRYMPI_FMT_USG);
/**
* Parse MPI from hex data
* @param hex
* @param secure
* @return
*/
GcryptMPI MpiFromHex(const char* hex, bool secure = true);
/**
* Dump MPI to bytes in USG format
* @param mpi
* @return
*/
QByteArray MpiToBytes(const GcryptMPI& mpi);
#endif // KEEPASSXC_GCRYPTMPI_H

View File

@ -17,29 +17,11 @@
#include "SessionCipher.h"
#include "crypto/CryptoHash.h"
#include "crypto/Random.h"
#include "crypto/SymmetricCipher.h"
#include <gcrypt.h>
#include <memory>
namespace
{
constexpr const auto IETF1024_SECOND_OAKLEY_GROUP_P_HEX = "FFFFFFFFFFFFFFFFC90FDAA22168C234"
"C4C6628B80DC1CD129024E088A67CC74"
"020BBEA63B139B22514A08798E3404DD"
"EF9519B3CD3A431B302B0A6DF25F1437"
"4FE1356D6D51C245E485B576625E7EC6"
"F44C42E9A637ED6B0BFF5CB6F406B7ED"
"EE386BFB5A899FA5AE9F24117C4B1FE6"
"49286651ECE65381FFFFFFFFFFFFFFFF";
constexpr const size_t KEY_SIZE_BYTES = 128;
constexpr const int AES_KEY_LEN = 16; // 128 bits
const auto IETF1024_SECOND_OAKLEY_GROUP_P = MpiFromHex(IETF1024_SECOND_OAKLEY_GROUP_P_HEX, false);
} // namespace
#include <botan/dh.h>
#include <botan/pk_ops.h>
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);
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;
}
// 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());
}
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<uint8_t> salt(32, '\0');
auto dhka = m_privateKey->create_key_agreement_op(*randomGen()->getRng(), "HKDF(SHA-256)", "");
auto aesKey = dhka->agree(16,
reinterpret_cast<const uint8_t*>(clientPublicKey.constData()),
clientPublicKey.size(),
salt.data(),
salt.size());
m_aesKey = QByteArray(reinterpret_cast<char*>(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<char>(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;
if (m_valid) {
auto pubkey = m_privateKey->public_value();
return QByteArray(reinterpret_cast<char*>(pubkey.data()), pubkey.size());
}
return {};
}
void DhIetf1024Sha256Aes128CbcPkcs7::fixNextServerKeys(GcryptMPI priv, GcryptMPI pub)
{
NextPrivKey = std::move(priv);
NextPubKey = std::move(pub);
}
GcryptMPI DhIetf1024Sha256Aes128CbcPkcs7::NextPrivKey = nullptr;
GcryptMPI DhIetf1024Sha256Aes128CbcPkcs7::NextPubKey = nullptr;
} // namespace FdoSecrets

View File

@ -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<Botan::DH_PrivateKey> m_privateKey;
QByteArray m_aesKey;
};
} // namespace FdoSecrets

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<char>(fieldId);
fieldIdArr.append(static_cast<char>(fieldId));
CHECK_RETURN_FALSE(writeData(device, fieldIdArr));
CHECK_RETURN_FALSE(
writeData(device, Endian::sizedIntToBytes(static_cast<quint32>(data.size()), KeePass2::BYTEORDER)));
@ -294,7 +293,7 @@ bool Kdbx4Writer::serializeVariantMap(const QVariantMap& map, QByteArray& output
return false;
}
QByteArray typeBytes;
typeBytes[0] = static_cast<char>(fieldType);
typeBytes.append(static_cast<char>(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<char>(KeePass2::VariantMapFieldType::End);
endBytes.append(static_cast<char>(KeePass2::VariantMapFieldType::End));
CHECK_RETURN_FALSE(buf.write(endBytes) == 1);
return true;
}

View File

@ -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;
}

View File

@ -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();

View File

@ -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;
}
}

View File

@ -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 @@ private:
SymmetricCipher m_cipher;
QByteArray m_buffer;
int m_offset;
static SymmetricCipher::Algorithm mapAlgo(KeePass2::ProtectedStreamAlgo algo);
int m_offset = 0;
};
#endif // KEEPASSX_KEEPASS2RANDOMSTREAM_H

View File

@ -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;
}

View File

@ -28,7 +28,8 @@
#include <QJsonDocument>
#include <QJsonObject>
#include <QUuid>
#include <gcrypt.h>
#include <botan/pwdhash.h>
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<uint8_t*>(out.data()),
out.size(),
password.toUtf8().constData(),
password.size(),
reinterpret_cast<const uint8_t*>(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;
}

View File

@ -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;
}

View File

@ -280,13 +280,14 @@ QSharedPointer<CompositeKey> DatabaseOpenWidget::buildDatabaseKey()
databaseKey->clear();
// try to get, decrypt and use PasswordKey
QSharedPointer<QByteArray> 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<CompositeKey>();
}
databaseKey->addKey(PasswordKey::fromRawKey(*passwordKey));
databaseKey->addKey(PasswordKey::fromRawKey(passwordKey));
}
}
#endif

View File

@ -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

View File

@ -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)

View File

@ -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"

View File

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

View File

@ -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 <QMessageBox>
#include <QPainter>
#include <QPushButton>
#include <QTextCodec>
#include <QXmlStreamWriter>
#include <botan/ber_dec.h>
#include <botan/pk_keys.h>
#include <botan/pkcs8.h>
#include <botan/rsa.h>
#include <functional>
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<void(QXmlStreamWriter& writer)> 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,23 @@ namespace KeeShareSettings
if (certificate.isNull()) {
return;
}
auto berKey = Botan::PKCS8::BER_encode(*certificate.key);
auto baKey = QByteArray::fromRawData(reinterpret_cast<const char*>(berKey.data()), berKey.size());
writer.writeStartElement("Signer");
writer.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 isNull() == other.isNull();
}
return key->private_key_bits() == other.key->private_key_bits() && signer == other.signer;
}
bool Certificate::operator!=(const Certificate& other) const
@ -126,7 +96,7 @@ namespace KeeShareSettings
bool Certificate::isNull() const
{
return key.isEmpty() && signer.isEmpty();
return !key || signer.isEmpty();
}
QString Certificate::fingerprint() const
@ -134,20 +104,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 +114,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<const uint8_t*>(rawKey.constData()),
rawKey.size());
certificate.key.reset(Botan::PKCS8::load_key(dataSource).release());
} catch (std::exception& e) {
qWarning("KeeShare: Failed to deserialize key data: %s", e.what());
}
}
}
}
return certificate;
@ -165,7 +131,10 @@ namespace KeeShareSettings
bool Key::operator==(const Key& other) const
{
return key == other.key;
if (isNull() || other.isNull()) {
return isNull() == other.isNull();
}
return key->private_key_bits() == other.key->private_key_bits();
}
bool Key::operator!=(const Key& other) const
@ -175,20 +144,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 +152,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<const char*>(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<const uint8_t*>(rawKey.constData()),
rawKey.size());
key.key.reset(Botan::PKCS8::load_key(dataSource).release());
} catch (std::exception& e) {
qWarning("KeeShare: Failed to deserialize key data: %s", e.what());
}
}
return key;
}
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 +218,7 @@ namespace KeeShareSettings
}
}
} else {
::qWarning() << "Unknown KeeShareSettings element" << reader.name();
qWarning("Unknown KeeShareSettings element %s", qPrintable(reader.name().toString()));
reader.skipCurrentElement();
}
}
@ -289,7 +258,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 +329,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 +428,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 +458,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();
}
}

View File

@ -20,8 +20,13 @@
#include <QMap>
#include <QObject>
#include <QSharedPointer>
#include <QUuid>
#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<Botan::Private_Key> 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<Botan::Private_Key> 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);

View File

@ -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 <QMessageBox>
#include <QStandardItemModel>
#include <QStandardPaths>
#include <QTextStream>
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<QStandardItem*>()
<< new QStandardItem(scopedCertificate.path)
<< new QStandardItem(scopedCertificate.trust == KeeShareSettings::Trust::Ask
? tr("Ask")
: (scopedCertificate.trust == KeeShareSettings::Trust::Trusted
? tr("Trusted")
: tr("Untrusted")))
QList<QStandardItem*> 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;
}

View File

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

View File

@ -23,6 +23,9 @@
#include "keeshare/Signature.h"
#include "keys/PasswordKey.h"
#include <QBuffer>
#include <QTextStream>
#if defined(WITH_XC_KEESHARE_SECURE)
#include <quazip.h>
#include <quazipfile.h>
@ -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();

View File

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

View File

@ -22,8 +22,10 @@
#include "keeshare/Signature.h"
#include "keys/PasswordKey.h"
#include <QBuffer>
#include <QMessageBox>
#include <QPushButton>
#include <QTextStream>
#if defined(WITH_XC_KEESHARE_SECURE)
#include <quazip.h>
@ -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;

View File

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

View File

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

View File

@ -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 <QByteArray>
#include <gcrypt.h>
#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<QByteArray> parts = key.privateParts();
if (parts.count() != 6) {
raiseError("Unsupported signing key");
return QString();
}
const QByteArray block = CryptoHash::hash(data, CryptoHash::Sha256);
Tools::Map<Index, gcry_mpi_t, &gcry_mpi_release> mpi;
Tools::Map<Index, gcry_sexp_t, &gcry_sexp_release> sexp;
const gcry_mpi_format format = GCRYMPI_FMT_USG;
rc = gcry_mpi_scan(&mpi[N], format, parts[0].data(), parts[0].size(), nullptr);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return QString();
}
rc = gcry_mpi_scan(&mpi[E], format, parts[1].data(), parts[1].size(), nullptr);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return QString();
}
rc = gcry_mpi_scan(&mpi[D], format, parts[2].data(), parts[2].size(), nullptr);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return QString();
}
rc = gcry_mpi_scan(&mpi[U], format, parts[3].data(), parts[3].size(), nullptr);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return QString();
}
rc = gcry_mpi_scan(&mpi[P], format, parts[4].data(), parts[4].size(), nullptr);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return QString();
}
rc = gcry_mpi_scan(&mpi[Q], format, parts[5].data(), parts[5].size(), nullptr);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return QString();
}
if (gcry_mpi_cmp(mpi[P], mpi[Q]) > 0) {
// see https://www.gnupg.org/documentation/manuals/gcrypt/RSA-key-parameters.html#RSA-key-parameters
gcry_mpi_swap(mpi[P], mpi[Q]);
gcry_mpi_invm(mpi[U], mpi[P], mpi[Q]);
}
rc = gcry_sexp_build(&sexp[Key],
NULL,
"(private-key (rsa (n %m) (e %m) (d %m) (p %m) (q %m) (u %m)))",
mpi[N],
mpi[E],
mpi[D],
mpi[P],
mpi[Q],
mpi[U]);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return QString();
}
rc = gcry_pk_testkey(sexp[Key]);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return QString();
}
rc = gcry_sexp_build(&sexp[Data], NULL, "(data (flags pkcs1) (hash sha256 %b))", block.size(), block.data());
// rc = gcry_sexp_build(&sexp[Data], NULL, "(data (flags raw) (value %b))", data.size(), data.data());
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return QString();
}
rc = gcry_pk_sign(&sexp[Sig], sexp[Data], sexp[Key]);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return QString();
}
sexp[S] = gcry_sexp_find_token(sexp[Sig], "s", 1);
mpi[S] = gcry_sexp_nth_mpi(sexp[S], 1, GCRYMPI_FMT_USG);
Tools::Buffer buffer;
rc = gcry_mpi_aprint(GCRYMPI_FMT_STD, &buffer.raw, &buffer.size, mpi[S]);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return QString();
}
return QString("rsa|%1").arg(QString::fromLatin1(buffer.content().toHex()));
}
bool verify(const QByteArray& data, const OpenSSHKey& key, const QString& signature)
{
const gcry_mpi_format format = GCRYMPI_FMT_USG;
enum MPI
{
N,
E, // public key
R,
S // signature
};
enum SEXP
{
Data,
Key,
Sig
};
const QList<QByteArray> parts = key.publicParts();
if (parts.count() != 2) {
raiseError("Unsupported verification key");
return false;
}
const QByteArray block = CryptoHash::hash(data, CryptoHash::Sha256);
Tools::Map<MPI, gcry_mpi_t, &gcry_mpi_release> mpi;
Tools::Map<SEXP, gcry_sexp_t, &gcry_sexp_release> sexp;
rc = gcry_mpi_scan(&mpi[E], format, parts[0].data(), parts[0].size(), nullptr);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return false;
}
rc = gcry_mpi_scan(&mpi[N], format, parts[1].data(), parts[1].size(), nullptr);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return false;
}
rc = gcry_sexp_build(&sexp[Key], NULL, "(public-key (rsa (n %m) (e %m)))", mpi[N], mpi[E]);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return false;
}
QRegExp extractor("rsa\\|([a-f0-9]+)", Qt::CaseInsensitive);
if (!extractor.exactMatch(signature) || extractor.captureCount() != 1) {
raiseError("Could not unpack signature parts");
return false;
}
const QByteArray sig_s = QByteArray::fromHex(extractor.cap(1).toLatin1());
rc = gcry_mpi_scan(&mpi[S], GCRYMPI_FMT_STD, sig_s.data(), sig_s.size(), nullptr);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return false;
}
rc = gcry_sexp_build(&sexp[Sig], NULL, "(sig-val (rsa (s %m)))", mpi[S]);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return false;
}
rc = gcry_sexp_build(&sexp[Data], NULL, "(data (flags pkcs1) (hash sha256 %b))", block.size(), block.data());
// rc = gcry_sexp_build(&sexp[Data], NULL, "(data (flags raw) (value %b))", data.size(), data.data());
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return false;
}
rc = gcry_pk_verify(sexp[Sig], sexp[Data], sexp[Key]);
if (rc != GPG_ERR_NO_ERROR && rc != GPG_ERR_BAD_SIGNATURE) {
raiseError();
return false;
}
return rc != GPG_ERR_BAD_SIGNATURE;
}
};
QString Signature::create(const QByteArray& data, const OpenSSHKey& key)
bool Signature::create(const QByteArray& data, QSharedPointer<Botan::Private_Key> 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;
}
return result;
}
::qWarning() << "Unsupported Public/Private key format";
return QString();
}
if (key->algo_name() == "RSA") {
try {
Botan::PK_Signer signer(*key, "EMSA3(SHA-256)");
signer.update(reinterpret_cast<const uint8_t*>(data.constData()), data.size());
auto s = signer.signature(*randomGen()->getRng());
bool Signature::verify(const QByteArray& data, const QString& signature, const OpenSSHKey& key)
{
if (key.type() == "ssh-rsa") {
RSASigner signer;
bool result = signer.verify(data, key, signature);
if (signer.rc != GPG_ERR_NO_ERROR) {
::qWarning() << signer.error;
auto hex = QByteArray(reinterpret_cast<char*>(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";
qWarning("Unsupported Public/Private key format");
return false;
}
bool Signature::verify(const QByteArray& data, QSharedPointer<Botan::Public_Key> key, const QString& signature)
{
if (key && key->algo_name() == "RSA") {
QRegExp extractor("rsa\\|([a-f0-9]+)", Qt::CaseInsensitive);
if (!extractor.exactMatch(signature) || extractor.captureCount() != 1) {
qWarning("Could not unpack signature parts");
return false;
}
const QByteArray sig_s = QByteArray::fromHex(extractor.cap(1).toLatin1());
try {
Botan::PK_Verifier verifier(*key, "EMSA3(SHA-256)");
verifier.update(reinterpret_cast<const uint8_t*>(data.constData()), data.size());
return verifier.check_signature(reinterpret_cast<const uint8_t*>(sig_s.constData()), sig_s.size());
} catch (std::exception& e) {
qWarning("KeeShare: Failed to verify signature: %s", e.what());
return false;
}
}
qWarning("Unsupported Public/Private key format");
return false;
}

View File

@ -18,17 +18,14 @@
#ifndef KEEPASSXC_SIGNATURE_H
#define KEEPASSXC_SIGNATURE_H
#include <QSharedPointer>
#include <QString>
#include <gcrypt.h>
#include <botan/pubkey.h>
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<Botan::Private_Key> key, QString& signature);
bool verify(const QByteArray& data, QSharedPointer<Botan::Public_Key> key, const QString& signature);
}; // namespace Signature
#endif // KEEPASSXC_SIGNATURE_H

View File

@ -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"

View File

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

View File

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

View File

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

View File

@ -20,6 +20,7 @@
#define KEEPASSX_FILEKEY_H
#include <QXmlStreamReader>
#include <botan/secmem.h>
#include "keys/Key.h"
@ -41,7 +42,7 @@ public:
};
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 @@ private:
bool loadHex(QIODevice* device);
bool loadHashed(QIODevice* device);
char* m_key = nullptr;
Botan::secure_vector<char> m_key;
Type m_type = None;
};

View File

@ -19,9 +19,9 @@
#include "core/Tools.h"
#include "crypto/CryptoHash.h"
#include <algorithm>
#include <cstring>
#include <gcrypt.h>
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<char*>(gcry_malloc_secure(SHA256_SIZE)))
, m_key(SHA256_SIZE)
{
}
PasswordKey::PasswordKey(const QString& password)
: Key(UUID)
, m_key(static_cast<char*>(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;
}

View File

@ -18,6 +18,8 @@
#ifndef KEEPASSX_PASSWORDKEY_H
#define KEEPASSX_PASSWORDKEY_H
#include <botan/secmem.h>
#include <QSharedPointer>
#include <QString>
@ -30,7 +32,7 @@ public:
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 @@ public:
private:
static constexpr int SHA256_SIZE = 32;
char* m_key = nullptr;
Botan::secure_vector<char> m_key;
bool m_isInitialized = false;
};

View File

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

View File

@ -22,24 +22,17 @@
#include "keys/ChallengeResponseKey.h"
#include "keys/drivers/YubiKey.h"
#include <QObject>
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;
};

View File

@ -1,52 +0,0 @@
/*
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "keys/YkChallengeResponseKeyCLI.h"
#include "keys/drivers/YubiKey.h"
#include "core/Tools.h"
#include "crypto/CryptoHash.h"
#include "crypto/Random.h"
#include <QFile>
QUuid YkChallengeResponseKeyCLI::UUID("e2be77c0-c810-417a-8437-32f41d00bd1d");
YkChallengeResponseKeyCLI::YkChallengeResponseKeyCLI(YubiKeySlot keySlot, QString interactionMessage, QTextStream& out)
: ChallengeResponseKey(UUID)
, m_keySlot(keySlot)
, m_interactionMessage(interactionMessage)
, m_out(out.device())
{
connect(YubiKey::instance(), SIGNAL(userInteractionRequest()), SLOT(showInteractionMessage()));
}
void YkChallengeResponseKeyCLI::showInteractionMessage()
{
m_out << m_interactionMessage << "\n\n" << flush;
}
QByteArray YkChallengeResponseKeyCLI::rawKey() const
{
return m_key;
}
bool YkChallengeResponseKeyCLI::challenge(const QByteArray& challenge)
{
auto result = YubiKey::instance()->challenge(m_keySlot, challenge, m_key);
return result == YubiKey::SUCCESS;
}

View File

@ -1,51 +0,0 @@
/*
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSX_YK_CHALLENGERESPONSEKEYCLI_H
#define KEEPASSX_YK_CHALLENGERESPONSEKEYCLI_H
#include "core/Global.h"
#include "keys/ChallengeResponseKey.h"
#include "keys/drivers/YubiKey.h"
#include <QObject>
#include <QSharedPointer>
#include <QTextStream>
class YkChallengeResponseKeyCLI : public QObject, public ChallengeResponseKey
{
Q_OBJECT
public:
static QUuid UUID;
explicit YkChallengeResponseKeyCLI(YubiKeySlot keySlot, QString interactionMessage, QTextStream& out);
QByteArray rawKey() const override;
bool challenge(const QByteArray& challenge) override;
private slots:
void showInteractionMessage();
private:
QByteArray m_key;
YubiKeySlot m_keySlot;
QString m_interactionMessage;
QTextStream m_out;
};
#endif // KEEPASSX_YK_CHALLENGERESPONSEKEYCLI_H

View File

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

View File

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

View File

@ -21,7 +21,7 @@ if(WITH_XC_BROWSER)
# Alloc must be defined in a static library to prevent clashing with clang ASAN definitions
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)

View File

@ -17,9 +17,7 @@
*/
#include "ASN1Key.h"
#include "crypto/ssh/BinaryStream.h"
#include <gcrypt.h>
#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<unsigned char*>(iqmp_hex.data()), iqmp_hex.size(), nullptr, u);
gcry_mpi_release(u);
gcry_mpi_release(p);
gcry_mpi_release(q);
return QByteArray::fromHex(QString(iqmp_hex).toLatin1());
}
} // namespace
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<QByteArray> publicData;
publicData.append(e);
publicData.append(n);
QList<QByteArray> privateData;
privateData.append(n);
privateData.append(e);
key.setType("ssh-rsa");
key.setPublicData(publicData);
key.setPrivateData(privateData);
key.setComment("");
return true;
}
bool ASN1Key::parsePrivateRSA(QByteArray& ba, OpenSSHKey& key)
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<QByteArray> 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);

View File

@ -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

View File

@ -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()

View File

@ -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;

View File

@ -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 <QXmlStreamReader>
#include <QtCore>

View File

@ -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 <QCryptographicHash>
#include <QRegularExpression>
#include <QStringList>
#include <gcrypt.h>
#include <botan/pwdhash.h>
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<QString, QList<QByteArray>> binaryDeserialize(const QByteArray& serialized)
{
if (serialized.isEmpty()) {
return {};
}
QBuffer buffer;
buffer.setData(serialized);
buffer.open(QBuffer::ReadOnly);
BinaryStream stream(&buffer);
QString type;
stream.readString(type);
QByteArray temp;
QList<QByteArray> data;
while (stream.readString(temp)) {
data << temp;
}
return ::qMakePair(type, data);
}
QByteArray binarySerialize(const QString& type, const QList<QByteArray>& data)
{
if (type.isEmpty() && data.isEmpty()) {
return {};
}
QByteArray buffer;
BinaryStream stream(&buffer);
stream.writeString(type);
for (const QByteArray& part : data) {
stream.writeString(part);
}
return buffer;
}
} // namespace
// bcrypt_pbkdf.cpp
int bcrypt_pbkdf(const QByteArray& pass, const QByteArray& salt, QByteArray& key, quint32 rounds);
OpenSSHKey OpenSSHKey::generate(bool secure)
{
enum Index
{
Params,
CombinedKey,
PrivateKey,
PublicKey,
Private_N,
Private_E,
Private_D,
Private_P,
Private_Q,
Private_U, // private key
Public_N,
Public_E,
};
Tools::Map<Index, gcry_mpi_t, &gcry_mpi_release> mpi;
Tools::Map<Index, gcry_sexp_t, &gcry_sexp_release> sexp;
gcry_error_t rc = GPG_ERR_NO_ERROR;
rc = gcry_sexp_build(&sexp[Params],
NULL,
secure ? "(genkey (rsa (nbits 4:2048)))" : "(genkey (rsa (transient-key) (nbits 4:2048)))");
if (rc != GPG_ERR_NO_ERROR) {
qWarning() << "Could not create ssh key" << gcry_err_code(rc);
return OpenSSHKey();
}
rc = gcry_pk_genkey(&sexp[CombinedKey], sexp[Params]);
if (rc != GPG_ERR_NO_ERROR) {
qWarning() << "Could not create ssh key" << gcry_err_code(rc);
return OpenSSHKey();
}
sexp[PrivateKey] = gcry_sexp_find_token(sexp[CombinedKey], "private-key", 0);
sexp[PublicKey] = gcry_sexp_find_token(sexp[CombinedKey], "public-key", 0);
sexp[Private_N] = gcry_sexp_find_token(sexp[PrivateKey], "n", 1);
mpi[Private_N] = gcry_sexp_nth_mpi(sexp[Private_N], 1, GCRYMPI_FMT_USG);
sexp[Private_E] = gcry_sexp_find_token(sexp[PrivateKey], "e", 1);
mpi[Private_E] = gcry_sexp_nth_mpi(sexp[Private_E], 1, GCRYMPI_FMT_USG);
sexp[Private_D] = gcry_sexp_find_token(sexp[PrivateKey], "d", 1);
mpi[Private_D] = gcry_sexp_nth_mpi(sexp[Private_D], 1, GCRYMPI_FMT_USG);
sexp[Private_Q] = gcry_sexp_find_token(sexp[PrivateKey], "q", 1);
mpi[Private_Q] = gcry_sexp_nth_mpi(sexp[Private_Q], 1, GCRYMPI_FMT_USG);
sexp[Private_P] = gcry_sexp_find_token(sexp[PrivateKey], "p", 1);
mpi[Private_P] = gcry_sexp_nth_mpi(sexp[Private_P], 1, GCRYMPI_FMT_USG);
sexp[Private_U] = gcry_sexp_find_token(sexp[PrivateKey], "u", 1);
mpi[Private_U] = gcry_sexp_nth_mpi(sexp[Private_U], 1, GCRYMPI_FMT_USG);
sexp[Public_N] = gcry_sexp_find_token(sexp[PublicKey], "n", 1);
mpi[Public_N] = gcry_sexp_nth_mpi(sexp[Public_N], 1, GCRYMPI_FMT_USG);
sexp[Public_E] = gcry_sexp_find_token(sexp[PublicKey], "e", 1);
mpi[Public_E] = gcry_sexp_nth_mpi(sexp[Public_E], 1, GCRYMPI_FMT_USG);
QList<QByteArray> publicParts;
QList<QByteArray> privateParts;
Tools::Buffer buffer;
gcry_mpi_format format = GCRYMPI_FMT_USG;
rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Private_N]);
if (rc != GPG_ERR_NO_ERROR) {
qWarning() << "Could not extract private key part" << gcry_err_code(rc);
return OpenSSHKey();
}
privateParts << buffer.content();
buffer.clear();
rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Private_E]);
if (rc != GPG_ERR_NO_ERROR) {
qWarning() << "Could not extract private key part" << gcry_err_code(rc);
return OpenSSHKey();
}
privateParts << buffer.content();
buffer.clear();
rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Private_D]);
if (rc != GPG_ERR_NO_ERROR) {
qWarning() << "Could not extract private key part" << gcry_err_code(rc);
return OpenSSHKey();
}
privateParts << buffer.content();
buffer.clear();
rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Private_U]);
if (rc != GPG_ERR_NO_ERROR) {
qWarning() << "Could not extract private key part" << gcry_err_code(rc);
return OpenSSHKey();
}
privateParts << buffer.content();
buffer.clear();
rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Private_P]);
if (rc != GPG_ERR_NO_ERROR) {
qWarning() << "Could not extract private key part" << gcry_err_code(rc);
return OpenSSHKey();
}
privateParts << buffer.content();
buffer.clear();
rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Private_Q]);
if (rc != GPG_ERR_NO_ERROR) {
qWarning() << "Could not extract private key part" << gcry_err_code(rc);
return OpenSSHKey();
}
privateParts << buffer.content();
buffer.clear();
rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Public_E]);
if (rc != GPG_ERR_NO_ERROR) {
qWarning() << "Could not extract public key part" << gcry_err_code(rc);
return OpenSSHKey();
}
publicParts << buffer.content();
buffer.clear();
rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Public_N]);
if (rc != GPG_ERR_NO_ERROR) {
qWarning() << "Could not extract public key part" << gcry_err_code(rc);
return OpenSSHKey();
}
publicParts << buffer.content();
OpenSSHKey key;
key.m_rawType = OpenSSHKey::TYPE_RSA_PRIVATE;
key.setType("ssh-rsa");
key.setPublicData(publicParts);
key.setPrivateData(privateParts);
key.setComment("");
return key;
}
OpenSSHKey::OpenSSHKey(QObject* parent)
: 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<SymmetricCipher> cipher;
QScopedPointer<SymmetricCipher> 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");
return false;
}
if (passphrase.isEmpty()) {
m_error = tr("Passphrase is required to decrypt this key");
return false;
}
BinaryStream optionStream(&m_kdfOptions);
QByteArray salt;
quint32 rounds;
optionStream.readString(salt);
optionStream.read(rounds);
QByteArray decryptKey;
decryptKey.fill(0, cipher->keySize() + cipher->blockSize());
QByteArray phraseData = passphrase.toUtf8();
if (bcrypt_pbkdf(phraseData, salt, decryptKey, rounds) < 0) {
m_error = tr("Key derivation failed, key file corrupted?");
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;
}
QByteArray keyData, ivData;
keyData.setRawData(decryptKey.data(), cipher->keySize());
ivData.setRawData(decryptKey.data() + cipher->keySize(), cipher->blockSize());
cipher->init(keyData, ivData);
if (m_kdfName == "bcrypt") {
if (passphrase.isEmpty()) {
m_error = tr("Passphrase is required to decrypt this key");
return false;
}
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");
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<uint8_t*>(decryptKey.data()),
decryptKey.size(),
baPass.constData(),
baPass.size(),
reinterpret_cast<const uint8_t*>(salt.constData()),
salt.size());
} catch (std::exception& e) {
m_error = tr("Key derivation failed: %1").arg(e.what());
return false;
}
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;
}
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);
}
ivData = m_cipherIV;
} else if (m_kdfName != "none") {
m_error = tr("Unknown KDF: %1").arg(m_kdfName);
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();
// 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;
}
} 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<QByteArray> OpenSSHKey::publicParts() const
{
return m_rawPublicData;
}
QList<QByteArray> OpenSSHKey::privateParts() const
{
return m_rawPrivateData;
}
const QString& OpenSSHKey::privateType() const
{
return m_rawType;
}
OpenSSHKey OpenSSHKey::restoreFromBinary(Type type, const QByteArray& serialized)
{
OpenSSHKey key;
auto data = binaryDeserialize(serialized);
key.setType(data.first);
switch (type) {
case Public:
key.setPublicData(data.second);
break;
case Private:
key.setPrivateData(data.second);
break;
}
return key;
}
QByteArray OpenSSHKey::serializeToBinary(Type type, const OpenSSHKey& key)
{
Q_ASSERT(!key.encrypted());
switch (type) {
case Public:
return binarySerialize(key.type(), key.publicParts());
case Private:
return binarySerialize(key.type(), key.privateParts());
}
return {};
}
uint qHash(const OpenSSHKey& key)
{
return qHash(key.fingerprint());

View File

@ -19,7 +19,8 @@
#ifndef KEEPASSXC_OPENSSHKEY_H
#define KEEPASSXC_OPENSSHKEY_H
#include <QtCore>
#include <QCryptographicHash>
#include <QObject>
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 @@ public:
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 @@ public:
bool writePublic(BinaryStream& stream);
bool writePrivate(BinaryStream& stream);
QList<QByteArray> publicParts() const;
QList<QByteArray> privateParts() const;
const QString& privateType() const;
static const QString TYPE_DSA_PRIVATE;
static const QString TYPE_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);

View File

@ -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 <QtNetwork>

View File

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

View File

@ -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<char>(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());
}

View File

@ -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;

View File

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

View File

@ -57,28 +57,25 @@ bool TouchID::storeKey(const QString& databasePath, const QByteArray& passwordKe
}
// 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 @@ bool TouchID::storeKey(const QString& databasePath, const QByteArray& passwordKe
* Checks if an encrypted PasswordKey is available for the given database, tries to
* decrypt it using the KeyChain and if successful, returns it.
*/
QSharedPointer <QByteArray> TouchID::getKey(const QString& databasePath) const
bool TouchID::getKey(const QString& databasePath, QByteArray& passwordKey) const
{
passwordKey.clear();
if (databasePath.isEmpty()) {
// 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 @@ QSharedPointer <QByteArray> TouchID::getKey(const QString& databasePath) const
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<QByteArray>::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<CFDataRef>(dataTypeRef);
@ -196,22 +194,20 @@ QSharedPointer <QByteArray> TouchID::getKey(const QString& databasePath) const
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<QByteArray>::create(result);
return true;
}
/**

View File

@ -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)
@ -162,9 +161,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})

View File

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

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