mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-03-30 09:58:15 -04:00

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.
356 lines
10 KiB
C++
356 lines
10 KiB
C++
/*
|
|
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
|
* Copyright (C) 2017 Lennart Glauer <mail@lennart-glauer.de>
|
|
* 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 "Tools.h"
|
|
|
|
#include "config-keepassx.h"
|
|
#include "core/Config.h"
|
|
#include "core/Translator.h"
|
|
|
|
#include "git-info.h"
|
|
#include <QCoreApplication>
|
|
#include <QElapsedTimer>
|
|
#include <QIODevice>
|
|
#include <QImageReader>
|
|
#include <QLocale>
|
|
#include <QRegularExpression>
|
|
#include <QStringList>
|
|
#include <QSysInfo>
|
|
#include <QUrl>
|
|
#include <QUuid>
|
|
#include <cctype>
|
|
|
|
#ifdef Q_OS_WIN
|
|
#include <windows.h> // for Sleep()
|
|
#endif
|
|
|
|
#ifdef Q_OS_UNIX
|
|
#include <time.h> // for nanosleep()
|
|
#endif
|
|
|
|
namespace Tools
|
|
{
|
|
QString debugInfo()
|
|
{
|
|
QString debugInfo = "KeePassXC - ";
|
|
debugInfo.append(QObject::tr("Version %1").arg(KEEPASSXC_VERSION).append("\n"));
|
|
#ifndef KEEPASSXC_BUILD_TYPE_RELEASE
|
|
debugInfo.append(QObject::tr("Build Type: %1").arg(KEEPASSXC_BUILD_TYPE).append("\n"));
|
|
#endif
|
|
|
|
QString commitHash;
|
|
if (!QString(GIT_HEAD).isEmpty()) {
|
|
commitHash = GIT_HEAD;
|
|
}
|
|
if (!commitHash.isEmpty()) {
|
|
debugInfo.append(QObject::tr("Revision: %1").arg(commitHash.left(7)).append("\n"));
|
|
}
|
|
|
|
#ifdef KEEPASSXC_DIST
|
|
debugInfo.append(QObject::tr("Distribution: %1").arg(KEEPASSXC_DIST_TYPE).append("\n"));
|
|
#endif
|
|
|
|
// Qt related debugging information.
|
|
debugInfo.append("\n");
|
|
debugInfo.append("Qt ").append(QString::fromLocal8Bit(qVersion())).append("\n");
|
|
#ifdef QT_NO_DEBUG
|
|
debugInfo.append(QObject::tr("Debugging mode is disabled.").append("\n"));
|
|
#else
|
|
debugInfo.append(QObject::tr("Debugging mode is enabled.").append("\n"));
|
|
#endif
|
|
debugInfo.append("\n");
|
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)
|
|
debugInfo.append(QObject::tr("Operating system: %1\nCPU architecture: %2\nKernel: %3 %4")
|
|
.arg(QSysInfo::prettyProductName(),
|
|
QSysInfo::currentCpuArchitecture(),
|
|
QSysInfo::kernelType(),
|
|
QSysInfo::kernelVersion()));
|
|
|
|
debugInfo.append("\n\n");
|
|
#endif
|
|
|
|
QString extensions;
|
|
#ifdef WITH_XC_AUTOTYPE
|
|
extensions += "\n- " + QObject::tr("Auto-Type");
|
|
#endif
|
|
#ifdef WITH_XC_BROWSER
|
|
extensions += "\n- " + QObject::tr("Browser Integration");
|
|
#endif
|
|
#ifdef WITH_XC_SSHAGENT
|
|
extensions += "\n- " + QObject::tr("SSH Agent");
|
|
#endif
|
|
#if defined(WITH_XC_KEESHARE_SECURE) && defined(WITH_XC_KEESHARE_INSECURE)
|
|
extensions += "\n- " + QObject::tr("KeeShare (signed and unsigned sharing)");
|
|
#elif defined(WITH_XC_KEESHARE_SECURE)
|
|
extensions += "\n- " + QObject::tr("KeeShare (only signed sharing)");
|
|
#elif defined(WITH_XC_KEESHARE_INSECURE)
|
|
extensions += "\n- " + QObject::tr("KeeShare (only unsigned sharing)");
|
|
#endif
|
|
#ifdef WITH_XC_YUBIKEY
|
|
extensions += "\n- " + QObject::tr("YubiKey");
|
|
#endif
|
|
#ifdef WITH_XC_TOUCHID
|
|
extensions += "\n- " + QObject::tr("TouchID");
|
|
#endif
|
|
#ifdef WITH_XC_FDOSECRETS
|
|
extensions += "\n- " + QObject::tr("Secret Service Integration");
|
|
#endif
|
|
|
|
if (extensions.isEmpty()) {
|
|
extensions = " " + QObject::tr("None");
|
|
}
|
|
|
|
debugInfo.append(QObject::tr("Enabled extensions:").append(extensions).append("\n"));
|
|
return debugInfo;
|
|
}
|
|
|
|
QString humanReadableFileSize(qint64 bytes, quint32 precision)
|
|
{
|
|
constexpr auto kibibyte = 1024;
|
|
double size = bytes;
|
|
|
|
QStringList units = QStringList() << "B"
|
|
<< "KiB"
|
|
<< "MiB"
|
|
<< "GiB";
|
|
int i = 0;
|
|
int maxI = units.size() - 1;
|
|
|
|
while ((size >= kibibyte) && (i < maxI)) {
|
|
size /= kibibyte;
|
|
i++;
|
|
}
|
|
|
|
return QString("%1 %2").arg(QLocale().toString(size, 'f', precision), units.at(i));
|
|
}
|
|
|
|
bool readFromDevice(QIODevice* device, QByteArray& data, int size)
|
|
{
|
|
QByteArray buffer;
|
|
buffer.resize(size);
|
|
|
|
qint64 readResult = device->read(buffer.data(), size);
|
|
if (readResult == -1) {
|
|
return false;
|
|
} else {
|
|
buffer.resize(readResult);
|
|
data = buffer;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool readAllFromDevice(QIODevice* device, QByteArray& data)
|
|
{
|
|
QByteArray result;
|
|
qint64 readBytes = 0;
|
|
qint64 readResult;
|
|
do {
|
|
result.resize(result.size() + 16384);
|
|
readResult = device->read(result.data() + readBytes, result.size() - readBytes);
|
|
if (readResult > 0) {
|
|
readBytes += readResult;
|
|
}
|
|
} while (readResult > 0);
|
|
|
|
if (readResult == -1) {
|
|
return false;
|
|
} else {
|
|
result.resize(static_cast<int>(readBytes));
|
|
data = result;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
QString imageReaderFilter()
|
|
{
|
|
const QList<QByteArray> formats = QImageReader::supportedImageFormats();
|
|
QStringList formatsStringList;
|
|
|
|
for (const QByteArray& format : formats) {
|
|
for (char codePoint : format) {
|
|
if (!QChar(codePoint).isLetterOrNumber()) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
formatsStringList.append("*." + QString::fromLatin1(format).toLower());
|
|
}
|
|
|
|
return formatsStringList.join(" ");
|
|
}
|
|
|
|
bool isHex(const QByteArray& ba)
|
|
{
|
|
for (const uchar c : ba) {
|
|
if (!std::isxdigit(c)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool isBase64(const QByteArray& ba)
|
|
{
|
|
constexpr auto pattern = R"(^(?:[a-z0-9+/]{4})*(?:[a-z0-9+/]{3}=|[a-z0-9+/]{2}==)?$)";
|
|
QRegExp regexp(pattern, Qt::CaseInsensitive, QRegExp::RegExp2);
|
|
|
|
QString base64 = QString::fromLatin1(ba.constData(), ba.size());
|
|
|
|
return regexp.exactMatch(base64);
|
|
}
|
|
|
|
void sleep(int ms)
|
|
{
|
|
Q_ASSERT(ms >= 0);
|
|
|
|
if (ms == 0) {
|
|
return;
|
|
}
|
|
|
|
#ifdef Q_OS_WIN
|
|
Sleep(uint(ms));
|
|
#else
|
|
timespec ts;
|
|
ts.tv_sec = ms / 1000;
|
|
ts.tv_nsec = (ms % 1000) * 1000 * 1000;
|
|
nanosleep(&ts, nullptr);
|
|
#endif
|
|
}
|
|
|
|
void wait(int ms)
|
|
{
|
|
Q_ASSERT(ms >= 0);
|
|
|
|
if (ms == 0) {
|
|
return;
|
|
}
|
|
|
|
QElapsedTimer timer;
|
|
timer.start();
|
|
|
|
if (ms <= 50) {
|
|
QCoreApplication::processEvents(QEventLoop::AllEvents, ms);
|
|
sleep(qMax(ms - static_cast<int>(timer.elapsed()), 0));
|
|
} else {
|
|
int timeLeft;
|
|
do {
|
|
timeLeft = ms - timer.elapsed();
|
|
if (timeLeft > 0) {
|
|
QCoreApplication::processEvents(QEventLoop::AllEvents, timeLeft);
|
|
sleep(10);
|
|
}
|
|
} while (!timer.hasExpired(ms));
|
|
}
|
|
}
|
|
|
|
bool checkUrlValid(const QString& urlField)
|
|
{
|
|
if (urlField.isEmpty() || urlField.startsWith("cmd://", Qt::CaseInsensitive)
|
|
|| urlField.startsWith("{REF:A", Qt::CaseInsensitive)) {
|
|
return true;
|
|
}
|
|
|
|
QUrl url;
|
|
if (urlField.contains("://")) {
|
|
url = urlField;
|
|
} else {
|
|
url = QUrl::fromUserInput(urlField);
|
|
}
|
|
|
|
if (url.scheme() != "file" && url.host().isEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
// Check for illegal characters. Adds also the wildcard * to the list
|
|
QRegularExpression re("[<>\\^`{|}\\*]");
|
|
auto match = re.match(urlField);
|
|
if (match.hasMatch()) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Escape common regex symbols except for *, ?, and |
|
|
auto regexEscape = QRegularExpression(R"re(([-[\]{}()+.,\\\/^$#]))re");
|
|
|
|
QRegularExpression convertToRegex(const QString& string, bool useWildcards, bool exactMatch, bool caseSensitive)
|
|
{
|
|
QString pattern = string;
|
|
|
|
// Wildcard support (*, ?, |)
|
|
if (useWildcards) {
|
|
pattern.replace(regexEscape, "\\\\1");
|
|
pattern.replace("*", ".*");
|
|
pattern.replace("?", ".");
|
|
}
|
|
|
|
// Exact modifier
|
|
if (exactMatch) {
|
|
pattern = "^" + pattern + "$";
|
|
}
|
|
|
|
auto regex = QRegularExpression(pattern);
|
|
if (!caseSensitive) {
|
|
regex.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
|
|
}
|
|
|
|
return regex;
|
|
}
|
|
|
|
QString uuidToHex(const QUuid& uuid)
|
|
{
|
|
return QString::fromLatin1(uuid.toRfc4122().toHex());
|
|
}
|
|
|
|
QUuid hexToUuid(const QString& uuid)
|
|
{
|
|
return QUuid::fromRfc4122(QByteArray::fromHex(uuid.toLatin1()));
|
|
}
|
|
|
|
QString envSubstitute(const QString& filepath, QProcessEnvironment environment)
|
|
{
|
|
QString subbed = filepath;
|
|
|
|
#if defined(Q_OS_WIN)
|
|
QRegularExpression varRe("\\%([A-Za-z][A-Za-z0-9_]*)\\%");
|
|
QString homeEnv = "USERPROFILE";
|
|
#else
|
|
QRegularExpression varRe("\\$([A-Za-z][A-Za-z0-9_]*)");
|
|
QString homeEnv = "HOME";
|
|
#endif
|
|
|
|
if (subbed.startsWith("~/") || subbed.startsWith("~\\"))
|
|
subbed.replace(0, 1, environment.value(homeEnv));
|
|
|
|
QRegularExpressionMatch match;
|
|
|
|
do {
|
|
match = varRe.match(subbed);
|
|
if (match.hasMatch()) {
|
|
subbed.replace(match.capturedStart(), match.capturedLength(), environment.value(match.captured(1)));
|
|
}
|
|
} while (match.hasMatch());
|
|
|
|
return subbed;
|
|
}
|
|
} // namespace Tools
|