Skip to content

Commit

Permalink
Fix Base32
Browse files Browse the repository at this point in the history
  • Loading branch information
varjolintu committed Feb 4, 2024
1 parent df361e8 commit 8a19281
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 75 deletions.
9 changes: 4 additions & 5 deletions src/core/Base32.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2017 KeePassXC Team <[email protected]>
* Copyright (C) 2024 KeePassXC Team <[email protected]>
*
* 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
Expand All @@ -24,7 +24,6 @@
#include "Base32.h"

#include <QHash>
#include <QVariant>

constexpr quint64 MASK_40BIT = quint64(0xF8) << 32;
constexpr quint64 MASK_35BIT = quint64(0x7C0000000);
Expand All @@ -43,10 +42,10 @@ constexpr quint8 ASCII_a = static_cast<quint8>('a');
constexpr quint8 ASCII_z = static_cast<quint8>('z');
constexpr quint8 ASCII_EQ = static_cast<quint8>('=');

QVariant Base32::decode(const QByteArray& encodedData)
QByteArray Base32::decode(const QByteArray& encodedData)
{
if (encodedData.size() <= 0) {
return QVariant::fromValue(QByteArray(""));
return {};
}

if (encodedData.size() % 8 != 0) {
Expand Down Expand Up @@ -139,7 +138,7 @@ QVariant Base32::decode(const QByteArray& encodedData)
Q_ASSERT(encodedData.size() == i);
Q_ASSERT(nBytes == o);

return QVariant::fromValue(data);
return data;
}

QByteArray Base32::encode(const QByteArray& data)
Expand Down
5 changes: 2 additions & 3 deletions src/core/Base32.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2017 KeePassXC Team <[email protected]>
* Copyright (C) 2024 KeePassXC Team <[email protected]>
*
* 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
Expand All @@ -25,13 +25,12 @@
#define BASE32_H

#include <QByteArray>
#include <QVariant>

class Base32
{
public:
Base32() = default;
Q_REQUIRED_RESULT static QVariant decode(const QByteArray&);
Q_REQUIRED_RESULT static QByteArray decode(const QByteArray&);
Q_REQUIRED_RESULT static QByteArray encode(const QByteArray&);
Q_REQUIRED_RESULT static QByteArray addPadding(const QByteArray&);
Q_REQUIRED_RESULT static QByteArray removePadding(const QByteArray&);
Expand Down
6 changes: 3 additions & 3 deletions src/core/Entry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1312,7 +1312,7 @@ Database* Entry::database()
QString Entry::maskPasswordPlaceholders(const QString& str) const
{
QString result = str;
result.replace(QRegularExpression("(\\{PASSWORD\\})", QRegularExpression::CaseInsensitiveOption), "******");
result.replace(QRegularExpression(R"(\\{PASSWORD\\})", QRegularExpression::CaseInsensitiveOption), "******");
return result;
}

Expand Down Expand Up @@ -1431,8 +1431,8 @@ QString Entry::resolveUrl(const QString& url) const
{
QString newUrl = url;

QRegularExpression fileRegEx("^([a-z]:)?[\\\\/]", QRegularExpression::CaseInsensitiveOption);
if (!fileRegEx.match(newUrl).hasMatch()) {
QRegularExpression fileRegEx(R"(^([a-z]:)?[\\\\/])", QRegularExpression::CaseInsensitiveOption);
if (fileRegEx.match(newUrl).hasMatch()) {
// Match possible file paths without the scheme and convert it to a file URL
newUrl = QDir::fromNativeSeparators(newUrl);
newUrl = QUrl::fromLocalFile(newUrl).toString();
Expand Down
8 changes: 4 additions & 4 deletions src/totp/totp.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2024 KeePassXC Team <[email protected]>
* Copyright (C) 2017 Weslly Honorato <[email protected]>
* Copyright (C) 2017 KeePassXC Team <[email protected]>
*
* 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
Expand Down Expand Up @@ -200,8 +200,8 @@ QString Totp::generateTotp(const QSharedPointer<Totp::Settings>& settings, const
current = qToBigEndian(time / step);
}

QVariant secret = Base32::decode(Base32::sanitizeInput(settings->key.toLatin1()));
if (secret.isNull()) {
const auto secret = Base32::decode(Base32::sanitizeInput(settings->key.toLatin1()));
if (secret.isEmpty()) {
return QObject::tr("Invalid Key", "TOTP");
}

Expand All @@ -218,7 +218,7 @@ QString Totp::generateTotp(const QSharedPointer<Totp::Settings>& settings, const
break;
}
QMessageAuthenticationCode code(cryptoHash);
code.setKey(secret.toByteArray());
code.setKey(secret);
code.addData(QByteArray(reinterpret_cast<char*>(&current), sizeof(current)));
QByteArray hmac = code.result();

Expand Down
120 changes: 60 additions & 60 deletions tests/TestBase32.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2017 KeePassXC Team <[email protected]>
* Copyright (C) 2024 KeePassXC Team <[email protected]>
*
* 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
Expand All @@ -26,134 +26,134 @@ void TestBase32::testDecode()
{
// 3 quanta, all upper case + padding
QByteArray encodedData = "JBSWY3DPEB3W64TMMQXC4LQ=";
QVariant data = Base32::decode(encodedData);
QByteArray data = Base32::decode(encodedData);
QString expectedData = "Hello world...";
QVERIFY(!data.isNull());
QCOMPARE(data.toString(), expectedData);
QVERIFY(data.value<QByteArray>().size() == expectedData.size());
QVERIFY(!data.isEmpty());
QCOMPARE(QString(data.toStdString().c_str()), expectedData);
QVERIFY(data.size() == expectedData.size());

// 4 quanta, all upper case
encodedData = "GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ";
data = Base32::decode(encodedData);
expectedData = "12345678901234567890";
QVERIFY(!data.isNull());
QCOMPARE(data.toString(), expectedData);
QVERIFY(data.value<QByteArray>().size() == expectedData.size());
QVERIFY(!data.isEmpty());
QCOMPARE(QString(data.toStdString().c_str()), expectedData);
QVERIFY(data.size() == expectedData.size());

// 4 quanta, all lower case
encodedData = "gezdgnbvgy3tqojqgezdgnbvgy3tqojq";
data = Base32::decode(encodedData);
expectedData = "12345678901234567890";
QVERIFY(!data.isNull());
QCOMPARE(data.toString(), expectedData);
QVERIFY(data.value<QByteArray>().size() == expectedData.size());
QVERIFY(!data.isEmpty());
QCOMPARE(QString(data.toStdString().c_str()), expectedData);
QVERIFY(data.size() == expectedData.size());

// 4 quanta, mixed upper and lower case
encodedData = "Gezdgnbvgy3tQojqgezdGnbvgy3tQojQ";
data = Base32::decode(encodedData);
expectedData = "12345678901234567890";
QVERIFY(!data.isNull());
QCOMPARE(data.toString(), expectedData);
QVERIFY(data.value<QByteArray>().size() == expectedData.size());
QVERIFY(!data.isEmpty());
QCOMPARE(QString(data.toStdString().c_str()), expectedData);
QVERIFY(data.size() == expectedData.size());

// 1 pad characters
encodedData = "ORSXG5A=";
data = Base32::decode(encodedData);
expectedData = "test";
QVERIFY(!data.isNull());
QCOMPARE(data.toString(), expectedData);
QVERIFY(data.value<QByteArray>().size() == expectedData.size());
QVERIFY(!data.isEmpty());
QCOMPARE(QString(data.toStdString().c_str()), expectedData);
QVERIFY(data.size() == expectedData.size());

// 3 pad characters
encodedData = "L5PV6===";
data = Base32::decode(encodedData);
expectedData = "___";
QVERIFY(!data.isNull());
QCOMPARE(data.toString(), expectedData);
QVERIFY(data.value<QByteArray>().size() == expectedData.size());
QVERIFY(!data.isEmpty());
QCOMPARE(QString(data.toStdString().c_str()), expectedData);
QVERIFY(data.size() == expectedData.size());

// 4 pad characters
encodedData = "MZXW6IDCMFZA====";
data = Base32::decode(encodedData);
expectedData = "foo bar";
QVERIFY(!data.isNull());
QCOMPARE(data.toString(), expectedData);
QVERIFY(data.value<QByteArray>().size() == expectedData.size());
QVERIFY(!data.isEmpty());
QCOMPARE(QString(data.toStdString().c_str()), expectedData);
QVERIFY(data.size() == expectedData.size());

// six pad characters
encodedData = "MZXW6YTBOI======";
data = Base32::decode(encodedData);
expectedData = "foobar";
QVERIFY(!data.isNull());
QCOMPARE(data.toString(), expectedData);
QVERIFY(data.value<QByteArray>().size() == expectedData.size());
QVERIFY(!data.isEmpty());
QCOMPARE(QString(data.toStdString().c_str()), expectedData);
QVERIFY(data.size() == expectedData.size());

encodedData = "IA======";
data = Base32::decode(encodedData);
expectedData = "@";
QVERIFY(!data.isNull());
QCOMPARE(data.toString(), expectedData);
QVERIFY(data.value<QByteArray>().size() == expectedData.size());
QVERIFY(!data.isEmpty());
QCOMPARE(QString(data.toStdString().c_str()), expectedData);
QVERIFY(data.size() == expectedData.size());

// error: illegal character
encodedData = "1MZXW6YTBOI=====";
data = Base32::decode(encodedData);
QVERIFY(data.isNull());
QVERIFY(data.isEmpty());

// error: missing pad character
encodedData = "MZXW6YTBOI=====";
data = Base32::decode(encodedData);
QVERIFY(data.isNull());
QVERIFY(data.isEmpty());

// RFC 4648 test vectors
encodedData = "";
data = Base32::decode(encodedData);
expectedData = "";
QVERIFY(!data.isNull());
QCOMPARE(data.toString(), expectedData);
QVERIFY(data.value<QByteArray>().size() == expectedData.size());
QVERIFY(data.isEmpty());
QCOMPARE(QString(data.toStdString().c_str()), expectedData);
QVERIFY(data.size() == expectedData.size());

encodedData = "MY======";
data = Base32::decode(encodedData);
expectedData = "f";
QVERIFY(!data.isNull());
QCOMPARE(data.toString(), expectedData);
QVERIFY(data.value<QByteArray>().size() == expectedData.size());
QVERIFY(!data.isEmpty());
QCOMPARE(QString(data.toStdString().c_str()), expectedData);
QVERIFY(data.size() == expectedData.size());

encodedData = "MZXQ====";
data = Base32::decode(encodedData);
expectedData = "fo";
QVERIFY(!data.isNull());
QCOMPARE(data.toString(), expectedData);
QVERIFY(data.value<QByteArray>().size() == expectedData.size());
QVERIFY(!data.isEmpty());
QCOMPARE(QString(data.toStdString().c_str()), expectedData);
QVERIFY(data.size() == expectedData.size());

encodedData = "MZXW6===";
data = Base32::decode(encodedData);
QVERIFY(!data.isNull());
QVERIFY(!data.isEmpty());
expectedData = "foo";
QCOMPARE(data.toString(), expectedData);
QVERIFY(data.value<QByteArray>().size() == expectedData.size());
QCOMPARE(QString(data.toStdString().c_str()), expectedData);
QVERIFY(data.size() == expectedData.size());

encodedData = "MZXW6YQ=";
data = Base32::decode(encodedData);
expectedData = "foob";
QVERIFY(!data.isNull());
QCOMPARE(data.toString(), expectedData);
QVERIFY(data.value<QByteArray>().size() == expectedData.size());
QVERIFY(!data.isEmpty());
QCOMPARE(QString(data.toStdString().c_str()), expectedData);
QVERIFY(data.size() == expectedData.size());

encodedData = "MZXW6YTB";
expectedData = "fooba";
data = Base32::decode(encodedData);
QVERIFY(!data.isNull());
QCOMPARE(data.toString(), expectedData);
QVERIFY(data.value<QByteArray>().size() == expectedData.size());
QVERIFY(!data.isEmpty());
QCOMPARE(QString(data.toStdString().c_str()), expectedData);
QVERIFY(data.size() == expectedData.size());

encodedData = "MZXW6YTBOI======";
data = Base32::decode(encodedData);
expectedData = "foobar";
QVERIFY(!data.isNull());
QCOMPARE(data.toString(), expectedData);
QVERIFY(data.value<QByteArray>().size() == expectedData.size());
QVERIFY(!data.isEmpty());
QCOMPARE(QString(data.toStdString().c_str()), expectedData);
QVERIFY(data.size() == expectedData.size());
}

void TestBase32::testEncode()
Expand Down Expand Up @@ -307,25 +307,25 @@ void TestBase32::testSanitizeInput()
// sanitize input (white space + missing padding)
QByteArray encodedData = "JBSW Y3DP EB3W 64TM MQXC 4LQA";
auto data = Base32::decode(Base32::sanitizeInput(encodedData));
QVERIFY(!data.isNull());
QCOMPARE(data.toString(), QString("Hello world..."));
QVERIFY(!data.isEmpty());
QCOMPARE(QString(data.toStdString().c_str()), QString("Hello world..."));

// sanitize input (typo + missing padding)
encodedData = "J8SWY3DPE83W64TMMQXC4LQA";
data = Base32::decode(Base32::sanitizeInput(encodedData));
QVERIFY(!data.isNull());
QCOMPARE(data.toString(), QString("Hello world..."));
QVERIFY(!data.isEmpty());
QCOMPARE(QString(data.toStdString().c_str()), QString("Hello world..."));

// sanitize input (other illegal characters)
encodedData = "J8SWY3D[PE83W64TMMQ]XC!4LQA";
data = Base32::decode(Base32::sanitizeInput(encodedData));
QVERIFY(!data.isNull());
QCOMPARE(data.toString(), QString("Hello world..."));
QVERIFY(!data.isEmpty());
QCOMPARE(QString(data.toStdString().c_str()), QString("Hello world..."));

// sanitize input (NUL character)
encodedData = "J8SWY3DPE83W64TMMQXC4LQA";
encodedData.insert(3, '\0');
data = Base32::decode(Base32::sanitizeInput(encodedData));
QVERIFY(!data.isNull());
QCOMPARE(data.toString(), QString("Hello world..."));
QVERIFY(!data.isEmpty());
QCOMPARE(QString(data.toStdString().c_str()), QString("Hello world..."));
}

0 comments on commit 8a19281

Please sign in to comment.