mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-12-25 07:19:42 -05:00
This implements support for SHA-256 and SHA-512 hash algorithms when generating TOTP codes. These algorithms are specified by RFC6238. The implementation is compatible with Google's OTP URL format, as well as with the KeeOTP plugin for KeePass. The implementation is not wired into the GUI, as the main project developer expressed strong negative sentiment about adding more options there. It is possible to configure codes by putting the appropriate string into the entry's otp property, or using another program with a less opinionated UI and a compatible on-disk format.
This commit is contained in:
parent
61b1f8c966
commit
04983ce4cd
@ -59,7 +59,8 @@ void TotpSetupDialog::saveSettings()
|
||||
}
|
||||
}
|
||||
|
||||
auto settings = Totp::createSettings(m_ui->seedEdit->text(), digits, step, encShortName, m_entry->totpSettings());
|
||||
auto settings = Totp::createSettings(
|
||||
m_ui->seedEdit->text(), digits, step, encShortName, Totp::HashType::Sha1, m_entry->totpSettings());
|
||||
m_entry->setTotp(settings);
|
||||
emit totpUpdated();
|
||||
close();
|
||||
|
@ -35,6 +35,29 @@ static QList<Totp::Encoder> encoders{
|
||||
{"steam", Totp::STEAM_SHORTNAME, "23456789BCDFGHJKMNPQRTVWXY", Totp::STEAM_DIGITS, Totp::DEFAULT_STEP, true},
|
||||
};
|
||||
|
||||
static Totp::HashType getHashTypeByName(const QString& name)
|
||||
{
|
||||
if (name.compare(QString("SHA512"), Qt::CaseInsensitive) == 0) {
|
||||
return Totp::HashType::Sha512;
|
||||
}
|
||||
if (name.compare(QString("SHA256"), Qt::CaseInsensitive) == 0) {
|
||||
return Totp::HashType::Sha256;
|
||||
}
|
||||
return Totp::HashType::Sha1;
|
||||
}
|
||||
|
||||
static QString getNameForHashType(const Totp::HashType hashType)
|
||||
{
|
||||
switch (hashType) {
|
||||
case Totp::HashType::Sha512:
|
||||
return QString("SHA512");
|
||||
case Totp::HashType::Sha256:
|
||||
return QString("SHA256");
|
||||
default:
|
||||
return QString("SHA1");
|
||||
}
|
||||
}
|
||||
|
||||
QSharedPointer<Totp::Settings> Totp::parseSettings(const QString& rawSettings, const QString& key)
|
||||
{
|
||||
// Create default settings
|
||||
@ -51,6 +74,9 @@ QSharedPointer<Totp::Settings> Totp::parseSettings(const QString& rawSettings, c
|
||||
if (query.hasQueryItem("encoder")) {
|
||||
settings->encoder = getEncoderByName(query.queryItemValue("encoder"));
|
||||
}
|
||||
if (query.hasQueryItem("algorithm")) {
|
||||
settings->hashType = getHashTypeByName(query.queryItemValue("algorithm"));
|
||||
}
|
||||
} else {
|
||||
QUrlQuery query(rawSettings);
|
||||
if (query.hasQueryItem("key")) {
|
||||
@ -65,6 +91,9 @@ QSharedPointer<Totp::Settings> Totp::parseSettings(const QString& rawSettings, c
|
||||
if (query.hasQueryItem("step")) {
|
||||
settings->step = query.queryItemValue("step").toUInt();
|
||||
}
|
||||
if (query.hasQueryItem("otpHashMode")) {
|
||||
settings->hashType = getHashTypeByName(query.queryItemValue("otpHashMode"));
|
||||
}
|
||||
} else {
|
||||
// Parse semi-colon separated values ([step];[digits|S])
|
||||
auto vars = rawSettings.split(";");
|
||||
@ -98,19 +127,21 @@ QSharedPointer<Totp::Settings> Totp::createSettings(const QString& key,
|
||||
const uint digits,
|
||||
const uint step,
|
||||
const QString& encoderShortName,
|
||||
const Totp::HashType hashType,
|
||||
QSharedPointer<Totp::Settings> prevSettings)
|
||||
{
|
||||
bool isCustom = digits != DEFAULT_DIGITS || step != DEFAULT_STEP;
|
||||
if (prevSettings) {
|
||||
prevSettings->key = key;
|
||||
prevSettings->hashType = hashType;
|
||||
prevSettings->digits = digits;
|
||||
prevSettings->step = step;
|
||||
prevSettings->encoder = Totp::getEncoderByShortName(encoderShortName);
|
||||
prevSettings->custom = isCustom;
|
||||
return prevSettings;
|
||||
} else {
|
||||
return QSharedPointer<Totp::Settings>(
|
||||
new Totp::Settings{getEncoderByShortName(encoderShortName), key, false, false, isCustom, digits, step});
|
||||
return QSharedPointer<Totp::Settings>(new Totp::Settings{
|
||||
getEncoderByShortName(encoderShortName), hashType, key, false, false, isCustom, digits, step});
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,13 +166,20 @@ QString Totp::writeSettings(const QSharedPointer<Totp::Settings>& settings,
|
||||
if (!settings->encoder.name.isEmpty()) {
|
||||
urlstring.append("&encoder=").append(settings->encoder.name);
|
||||
}
|
||||
if (settings->hashType != Totp::DEFAULT_HASHTYPE) {
|
||||
urlstring.append("&algorithm=").append(getNameForHashType(settings->hashType));
|
||||
}
|
||||
return urlstring;
|
||||
} else if (settings->keeOtp) {
|
||||
// KeeOtp output
|
||||
return QString("key=%1&size=%2&step=%3")
|
||||
auto keyString = QString("key=%1&size=%2&step=%3")
|
||||
.arg(QString(Base32::sanitizeInput(settings->key.toLatin1())))
|
||||
.arg(settings->digits)
|
||||
.arg(settings->step);
|
||||
if (settings->hashType != Totp::DEFAULT_HASHTYPE) {
|
||||
keyString.append("&otpHashMode=").append(getNameForHashType(settings->hashType));
|
||||
}
|
||||
return keyString;
|
||||
} else if (!settings->encoder.shortName.isEmpty()) {
|
||||
// Semicolon output [step];[encoder]
|
||||
return QString("%1;%2").arg(settings->step).arg(settings->encoder.shortName);
|
||||
@ -174,7 +212,19 @@ QString Totp::generateTotp(const QSharedPointer<Totp::Settings>& settings, const
|
||||
return QObject::tr("Invalid Key", "TOTP");
|
||||
}
|
||||
|
||||
QMessageAuthenticationCode code(QCryptographicHash::Sha1);
|
||||
QCryptographicHash::Algorithm cryptoHash;
|
||||
switch (settings->hashType) {
|
||||
case Totp::HashType::Sha512:
|
||||
cryptoHash = QCryptographicHash::Sha512;
|
||||
break;
|
||||
case Totp::HashType::Sha256:
|
||||
cryptoHash = QCryptographicHash::Sha256;
|
||||
break;
|
||||
default:
|
||||
cryptoHash = QCryptographicHash::Sha1;
|
||||
break;
|
||||
}
|
||||
QMessageAuthenticationCode code(cryptoHash);
|
||||
code.setKey(secret.toByteArray());
|
||||
code.addData(QByteArray(reinterpret_cast<char*>(¤t), sizeof(current)));
|
||||
QByteArray hmac = code.result();
|
||||
|
@ -39,9 +39,17 @@ namespace Totp
|
||||
bool reverse;
|
||||
};
|
||||
|
||||
enum HashType
|
||||
{
|
||||
Sha1,
|
||||
Sha256,
|
||||
Sha512,
|
||||
};
|
||||
|
||||
struct Settings
|
||||
{
|
||||
Totp::Encoder encoder;
|
||||
Totp::HashType hashType;
|
||||
QString key;
|
||||
bool otpUrl;
|
||||
bool keeOtp;
|
||||
@ -53,6 +61,7 @@ namespace Totp
|
||||
constexpr uint DEFAULT_STEP = 30u;
|
||||
constexpr uint DEFAULT_DIGITS = 6u;
|
||||
constexpr uint STEAM_DIGITS = 5u;
|
||||
constexpr Totp::HashType DEFAULT_HASHTYPE = Sha1;
|
||||
static const QString STEAM_SHORTNAME = "S";
|
||||
|
||||
static const QString ATTRIBUTE_OTP = "otp";
|
||||
@ -64,6 +73,7 @@ namespace Totp
|
||||
const uint digits,
|
||||
const uint step,
|
||||
const QString& encoderShortName = {},
|
||||
const Totp::HashType hashType = DEFAULT_HASHTYPE,
|
||||
QSharedPointer<Totp::Settings> prevSettings = {});
|
||||
QString writeSettings(const QSharedPointer<Totp::Settings>& settings,
|
||||
const QString& title = {},
|
||||
|
@ -41,15 +41,29 @@ void TestTotp::testParseSecret()
|
||||
QCOMPARE(settings->custom, false);
|
||||
QCOMPARE(settings->digits, 6u);
|
||||
QCOMPARE(settings->step, 30u);
|
||||
QCOMPARE(settings->hashType, Totp::HashType::Sha1);
|
||||
|
||||
// OTP URL with non-default hash type
|
||||
secret = "otpauth://totp/"
|
||||
"ACME%20Co:john@example.com?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&issuer=ACME%20Co&algorithm="
|
||||
"SHA512&digits=6&period=30";
|
||||
settings = Totp::parseSettings(secret);
|
||||
QVERIFY(!settings.isNull());
|
||||
QCOMPARE(settings->key, QString("HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ"));
|
||||
QCOMPARE(settings->custom, false);
|
||||
QCOMPARE(settings->digits, 6u);
|
||||
QCOMPARE(settings->step, 30u);
|
||||
QCOMPARE(settings->hashType, Totp::HashType::Sha512);
|
||||
|
||||
// KeeOTP Parsing
|
||||
secret = "key=HXDMVJECJJWSRBY%3d&step=25&size=8";
|
||||
secret = "key=HXDMVJECJJWSRBY%3d&step=25&size=8&otpHashMode=Sha256";
|
||||
settings = Totp::parseSettings(secret);
|
||||
QVERIFY(!settings.isNull());
|
||||
QCOMPARE(settings->key, QString("HXDMVJECJJWSRBY="));
|
||||
QCOMPARE(settings->custom, true);
|
||||
QCOMPARE(settings->digits, 8u);
|
||||
QCOMPARE(settings->step, 25u);
|
||||
QCOMPARE(settings->hashType, Totp::HashType::Sha256);
|
||||
|
||||
// Semi-colon delineated "TOTP Settings"
|
||||
secret = "gezdgnbvgy3tqojqgezdgnbvgy3tqojq";
|
||||
@ -59,6 +73,7 @@ void TestTotp::testParseSecret()
|
||||
QCOMPARE(settings->custom, true);
|
||||
QCOMPARE(settings->digits, 8u);
|
||||
QCOMPARE(settings->step, 30u);
|
||||
QCOMPARE(settings->hashType, Totp::HashType::Sha1);
|
||||
|
||||
// Bare secret (no "TOTP Settings" attribute)
|
||||
secret = "gezdgnbvgy3tqojqgezdgnbvgy3tqojq";
|
||||
@ -68,6 +83,7 @@ void TestTotp::testParseSecret()
|
||||
QCOMPARE(settings->custom, false);
|
||||
QCOMPARE(settings->digits, 6u);
|
||||
QCOMPARE(settings->step, 30u);
|
||||
QCOMPARE(settings->hashType, Totp::HashType::Sha1);
|
||||
}
|
||||
|
||||
void TestTotp::testTotpCode()
|
||||
|
Loading…
Reference in New Issue
Block a user