diff --git a/src/gui/TotpSetupDialog.cpp b/src/gui/TotpSetupDialog.cpp index 0e2c8da5a..4710824e0 100644 --- a/src/gui/TotpSetupDialog.cpp +++ b/src/gui/TotpSetupDialog.cpp @@ -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(); diff --git a/src/totp/totp.cpp b/src/totp/totp.cpp index 7188288a1..14ae994b1 100644 --- a/src/totp/totp.cpp +++ b/src/totp/totp.cpp @@ -35,6 +35,29 @@ static QList 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::parseSettings(const QString& rawSettings, const QString& key) { // Create default settings @@ -51,6 +74,9 @@ QSharedPointer 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::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::createSettings(const QString& key, const uint digits, const uint step, const QString& encoderShortName, + const Totp::HashType hashType, QSharedPointer 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( - new Totp::Settings{getEncoderByShortName(encoderShortName), key, false, false, isCustom, digits, step}); + return QSharedPointer(new Totp::Settings{ + getEncoderByShortName(encoderShortName), hashType, key, false, false, isCustom, digits, step}); } } @@ -135,13 +166,20 @@ QString Totp::writeSettings(const QSharedPointer& 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") - .arg(QString(Base32::sanitizeInput(settings->key.toLatin1()))) - .arg(settings->digits) - .arg(settings->step); + 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& 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(¤t), sizeof(current))); QByteArray hmac = code.result(); diff --git a/src/totp/totp.h b/src/totp/totp.h index 499973bf9..ccece84fe 100644 --- a/src/totp/totp.h +++ b/src/totp/totp.h @@ -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 prevSettings = {}); QString writeSettings(const QSharedPointer& settings, const QString& title = {}, diff --git a/tests/TestTotp.cpp b/tests/TestTotp.cpp index f4de2c6ad..c44cd4e8c 100644 --- a/tests/TestTotp.cpp +++ b/tests/TestTotp.cpp @@ -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()