mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-12-25 23:39:45 -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);
|
m_entry->setTotp(settings);
|
||||||
emit totpUpdated();
|
emit totpUpdated();
|
||||||
close();
|
close();
|
||||||
|
@ -35,6 +35,29 @@ static QList<Totp::Encoder> encoders{
|
|||||||
{"steam", Totp::STEAM_SHORTNAME, "23456789BCDFGHJKMNPQRTVWXY", Totp::STEAM_DIGITS, Totp::DEFAULT_STEP, true},
|
{"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)
|
QSharedPointer<Totp::Settings> Totp::parseSettings(const QString& rawSettings, const QString& key)
|
||||||
{
|
{
|
||||||
// Create default settings
|
// Create default settings
|
||||||
@ -51,6 +74,9 @@ QSharedPointer<Totp::Settings> Totp::parseSettings(const QString& rawSettings, c
|
|||||||
if (query.hasQueryItem("encoder")) {
|
if (query.hasQueryItem("encoder")) {
|
||||||
settings->encoder = getEncoderByName(query.queryItemValue("encoder"));
|
settings->encoder = getEncoderByName(query.queryItemValue("encoder"));
|
||||||
}
|
}
|
||||||
|
if (query.hasQueryItem("algorithm")) {
|
||||||
|
settings->hashType = getHashTypeByName(query.queryItemValue("algorithm"));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
QUrlQuery query(rawSettings);
|
QUrlQuery query(rawSettings);
|
||||||
if (query.hasQueryItem("key")) {
|
if (query.hasQueryItem("key")) {
|
||||||
@ -65,6 +91,9 @@ QSharedPointer<Totp::Settings> Totp::parseSettings(const QString& rawSettings, c
|
|||||||
if (query.hasQueryItem("step")) {
|
if (query.hasQueryItem("step")) {
|
||||||
settings->step = query.queryItemValue("step").toUInt();
|
settings->step = query.queryItemValue("step").toUInt();
|
||||||
}
|
}
|
||||||
|
if (query.hasQueryItem("otpHashMode")) {
|
||||||
|
settings->hashType = getHashTypeByName(query.queryItemValue("otpHashMode"));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Parse semi-colon separated values ([step];[digits|S])
|
// Parse semi-colon separated values ([step];[digits|S])
|
||||||
auto vars = rawSettings.split(";");
|
auto vars = rawSettings.split(";");
|
||||||
@ -98,19 +127,21 @@ QSharedPointer<Totp::Settings> Totp::createSettings(const QString& key,
|
|||||||
const uint digits,
|
const uint digits,
|
||||||
const uint step,
|
const uint step,
|
||||||
const QString& encoderShortName,
|
const QString& encoderShortName,
|
||||||
|
const Totp::HashType hashType,
|
||||||
QSharedPointer<Totp::Settings> prevSettings)
|
QSharedPointer<Totp::Settings> prevSettings)
|
||||||
{
|
{
|
||||||
bool isCustom = digits != DEFAULT_DIGITS || step != DEFAULT_STEP;
|
bool isCustom = digits != DEFAULT_DIGITS || step != DEFAULT_STEP;
|
||||||
if (prevSettings) {
|
if (prevSettings) {
|
||||||
prevSettings->key = key;
|
prevSettings->key = key;
|
||||||
|
prevSettings->hashType = hashType;
|
||||||
prevSettings->digits = digits;
|
prevSettings->digits = digits;
|
||||||
prevSettings->step = step;
|
prevSettings->step = step;
|
||||||
prevSettings->encoder = Totp::getEncoderByShortName(encoderShortName);
|
prevSettings->encoder = Totp::getEncoderByShortName(encoderShortName);
|
||||||
prevSettings->custom = isCustom;
|
prevSettings->custom = isCustom;
|
||||||
return prevSettings;
|
return prevSettings;
|
||||||
} else {
|
} else {
|
||||||
return QSharedPointer<Totp::Settings>(
|
return QSharedPointer<Totp::Settings>(new Totp::Settings{
|
||||||
new Totp::Settings{getEncoderByShortName(encoderShortName), key, false, false, isCustom, digits, step});
|
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()) {
|
if (!settings->encoder.name.isEmpty()) {
|
||||||
urlstring.append("&encoder=").append(settings->encoder.name);
|
urlstring.append("&encoder=").append(settings->encoder.name);
|
||||||
}
|
}
|
||||||
|
if (settings->hashType != Totp::DEFAULT_HASHTYPE) {
|
||||||
|
urlstring.append("&algorithm=").append(getNameForHashType(settings->hashType));
|
||||||
|
}
|
||||||
return urlstring;
|
return urlstring;
|
||||||
} else if (settings->keeOtp) {
|
} else if (settings->keeOtp) {
|
||||||
// KeeOtp output
|
// 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(QString(Base32::sanitizeInput(settings->key.toLatin1())))
|
||||||
.arg(settings->digits)
|
.arg(settings->digits)
|
||||||
.arg(settings->step);
|
.arg(settings->step);
|
||||||
|
if (settings->hashType != Totp::DEFAULT_HASHTYPE) {
|
||||||
|
keyString.append("&otpHashMode=").append(getNameForHashType(settings->hashType));
|
||||||
|
}
|
||||||
|
return keyString;
|
||||||
} else if (!settings->encoder.shortName.isEmpty()) {
|
} else if (!settings->encoder.shortName.isEmpty()) {
|
||||||
// Semicolon output [step];[encoder]
|
// Semicolon output [step];[encoder]
|
||||||
return QString("%1;%2").arg(settings->step).arg(settings->encoder.shortName);
|
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");
|
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.setKey(secret.toByteArray());
|
||||||
code.addData(QByteArray(reinterpret_cast<char*>(¤t), sizeof(current)));
|
code.addData(QByteArray(reinterpret_cast<char*>(¤t), sizeof(current)));
|
||||||
QByteArray hmac = code.result();
|
QByteArray hmac = code.result();
|
||||||
|
@ -39,9 +39,17 @@ namespace Totp
|
|||||||
bool reverse;
|
bool reverse;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum HashType
|
||||||
|
{
|
||||||
|
Sha1,
|
||||||
|
Sha256,
|
||||||
|
Sha512,
|
||||||
|
};
|
||||||
|
|
||||||
struct Settings
|
struct Settings
|
||||||
{
|
{
|
||||||
Totp::Encoder encoder;
|
Totp::Encoder encoder;
|
||||||
|
Totp::HashType hashType;
|
||||||
QString key;
|
QString key;
|
||||||
bool otpUrl;
|
bool otpUrl;
|
||||||
bool keeOtp;
|
bool keeOtp;
|
||||||
@ -53,6 +61,7 @@ namespace Totp
|
|||||||
constexpr uint DEFAULT_STEP = 30u;
|
constexpr uint DEFAULT_STEP = 30u;
|
||||||
constexpr uint DEFAULT_DIGITS = 6u;
|
constexpr uint DEFAULT_DIGITS = 6u;
|
||||||
constexpr uint STEAM_DIGITS = 5u;
|
constexpr uint STEAM_DIGITS = 5u;
|
||||||
|
constexpr Totp::HashType DEFAULT_HASHTYPE = Sha1;
|
||||||
static const QString STEAM_SHORTNAME = "S";
|
static const QString STEAM_SHORTNAME = "S";
|
||||||
|
|
||||||
static const QString ATTRIBUTE_OTP = "otp";
|
static const QString ATTRIBUTE_OTP = "otp";
|
||||||
@ -64,6 +73,7 @@ namespace Totp
|
|||||||
const uint digits,
|
const uint digits,
|
||||||
const uint step,
|
const uint step,
|
||||||
const QString& encoderShortName = {},
|
const QString& encoderShortName = {},
|
||||||
|
const Totp::HashType hashType = DEFAULT_HASHTYPE,
|
||||||
QSharedPointer<Totp::Settings> prevSettings = {});
|
QSharedPointer<Totp::Settings> prevSettings = {});
|
||||||
QString writeSettings(const QSharedPointer<Totp::Settings>& settings,
|
QString writeSettings(const QSharedPointer<Totp::Settings>& settings,
|
||||||
const QString& title = {},
|
const QString& title = {},
|
||||||
|
@ -41,15 +41,29 @@ void TestTotp::testParseSecret()
|
|||||||
QCOMPARE(settings->custom, false);
|
QCOMPARE(settings->custom, false);
|
||||||
QCOMPARE(settings->digits, 6u);
|
QCOMPARE(settings->digits, 6u);
|
||||||
QCOMPARE(settings->step, 30u);
|
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
|
// KeeOTP Parsing
|
||||||
secret = "key=HXDMVJECJJWSRBY%3d&step=25&size=8";
|
secret = "key=HXDMVJECJJWSRBY%3d&step=25&size=8&otpHashMode=Sha256";
|
||||||
settings = Totp::parseSettings(secret);
|
settings = Totp::parseSettings(secret);
|
||||||
QVERIFY(!settings.isNull());
|
QVERIFY(!settings.isNull());
|
||||||
QCOMPARE(settings->key, QString("HXDMVJECJJWSRBY="));
|
QCOMPARE(settings->key, QString("HXDMVJECJJWSRBY="));
|
||||||
QCOMPARE(settings->custom, true);
|
QCOMPARE(settings->custom, true);
|
||||||
QCOMPARE(settings->digits, 8u);
|
QCOMPARE(settings->digits, 8u);
|
||||||
QCOMPARE(settings->step, 25u);
|
QCOMPARE(settings->step, 25u);
|
||||||
|
QCOMPARE(settings->hashType, Totp::HashType::Sha256);
|
||||||
|
|
||||||
// Semi-colon delineated "TOTP Settings"
|
// Semi-colon delineated "TOTP Settings"
|
||||||
secret = "gezdgnbvgy3tqojqgezdgnbvgy3tqojq";
|
secret = "gezdgnbvgy3tqojqgezdgnbvgy3tqojq";
|
||||||
@ -59,6 +73,7 @@ void TestTotp::testParseSecret()
|
|||||||
QCOMPARE(settings->custom, true);
|
QCOMPARE(settings->custom, true);
|
||||||
QCOMPARE(settings->digits, 8u);
|
QCOMPARE(settings->digits, 8u);
|
||||||
QCOMPARE(settings->step, 30u);
|
QCOMPARE(settings->step, 30u);
|
||||||
|
QCOMPARE(settings->hashType, Totp::HashType::Sha1);
|
||||||
|
|
||||||
// Bare secret (no "TOTP Settings" attribute)
|
// Bare secret (no "TOTP Settings" attribute)
|
||||||
secret = "gezdgnbvgy3tqojqgezdgnbvgy3tqojq";
|
secret = "gezdgnbvgy3tqojqgezdgnbvgy3tqojq";
|
||||||
@ -68,6 +83,7 @@ void TestTotp::testParseSecret()
|
|||||||
QCOMPARE(settings->custom, false);
|
QCOMPARE(settings->custom, false);
|
||||||
QCOMPARE(settings->digits, 6u);
|
QCOMPARE(settings->digits, 6u);
|
||||||
QCOMPARE(settings->step, 30u);
|
QCOMPARE(settings->step, 30u);
|
||||||
|
QCOMPARE(settings->hashType, Totp::HashType::Sha1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestTotp::testTotpCode()
|
void TestTotp::testTotpCode()
|
||||||
|
Loading…
Reference in New Issue
Block a user