Add initial Steam TOTP support
* Add the concept of custom TOTP encoders, each with potential for custom code alphabet, length, step interval and code direction (i.e. reversed) * Select custom encoder via overload of the digits field of a loaded entry * Allow selection of custom encoders via the "TOTP Settings" field's size, as currently done by KeeTrayTOTP for Steam. Use "S" for the short name of the Steam custom encoder * Allow selection of custom encoders via the "otp" field by appending a "&encoder=<name>" field to the URL query. For example, "&encoder=steam" * Update TOTP set-up dialog to permit selection between (default, steam, custom) settings.
This commit is contained in:
@@ -31,6 +31,39 @@
|
||||
const quint8 QTotp::defaultStep = 30;
|
||||
const quint8 QTotp::defaultDigits = 6;
|
||||
|
||||
/**
|
||||
* Custom encoder types. Each should be unique and >= 128 and < 255
|
||||
* Values have no meaning outside of keepassxc
|
||||
*/
|
||||
/**
|
||||
* Encoder for Steam Guard TOTP
|
||||
*/
|
||||
const quint8 QTotp::ENCODER_STEAM = 254;
|
||||
|
||||
const QTotp::Encoder QTotp::defaultEncoder = { "", "", "0123456789", 0, 0, false };
|
||||
const QMap<quint8, QTotp::Encoder> QTotp::encoders{
|
||||
{ QTotp::ENCODER_STEAM, { "steam", "S", "23456789BCDFGHJKMNPQRTVWXY", 5, 30, true } },
|
||||
};
|
||||
|
||||
/**
|
||||
* These map the second field of the "TOTP Settings" field to our internal encoder number
|
||||
* that overloads the digits field. Make sure that the key matches the shortName value
|
||||
* in the corresponding Encoder
|
||||
* NOTE: when updating this map, a corresponding edit to the settings regex must be made
|
||||
* in Entry::totpSeed()
|
||||
*/
|
||||
const QMap<QString, quint8> QTotp::shortNameToEncoder{
|
||||
{ "S", QTotp::ENCODER_STEAM },
|
||||
};
|
||||
/**
|
||||
* These map the "encoder=" URL parameter of the "otp" field to our internal encoder number
|
||||
* that overloads the digits field. Make sure that the key matches the name value
|
||||
* in the corresponding Encoder
|
||||
*/
|
||||
const QMap<QString, quint8> QTotp::nameToEncoder{
|
||||
{ "steam", QTotp::ENCODER_STEAM },
|
||||
};
|
||||
|
||||
QTotp::QTotp()
|
||||
{
|
||||
}
|
||||
@@ -57,7 +90,10 @@ QString QTotp::parseOtpString(QString key, quint8& digits, quint8& step)
|
||||
if (q_step > 0 && q_step <= 60) {
|
||||
step = q_step;
|
||||
}
|
||||
|
||||
QString encName = query.queryItemValue("encoder");
|
||||
if (!encName.isEmpty() && nameToEncoder.contains(encName)) {
|
||||
digits = nameToEncoder[encName];
|
||||
}
|
||||
} else {
|
||||
// Compatibility with "KeeOtp" plugin string format
|
||||
QRegExp rx("key=(.+)", Qt::CaseInsensitive, QRegExp::RegExp);
|
||||
@@ -119,10 +155,24 @@ QString QTotp::generateTotp(const QByteArray key,
|
||||
| (hmac[offset + 3] & 0xff);
|
||||
// clang-format on
|
||||
|
||||
quint32 digitsPower = pow(10, numDigits);
|
||||
const Encoder& encoder = encoders.value(numDigits, defaultEncoder);
|
||||
// if encoder.digits is 0, we need to use the passed-in number of digits (default encoder)
|
||||
quint8 digits = encoder.digits == 0 ? numDigits : encoder.digits;
|
||||
int direction = -1;
|
||||
int startpos = digits - 1;
|
||||
if (encoder.reverse) {
|
||||
direction = 1;
|
||||
startpos = 0;
|
||||
}
|
||||
quint32 digitsPower = pow(encoder.alphabet.size(), digits);
|
||||
|
||||
quint64 password = binary % digitsPower;
|
||||
return QString("%1").arg(password, numDigits, 10, QChar('0'));
|
||||
QString retval(int(digits), encoder.alphabet[0]);
|
||||
for (quint8 pos = startpos; password > 0; pos += direction) {
|
||||
retval[pos] = encoder.alphabet[int(password % encoder.alphabet.size())];
|
||||
password /= encoder.alphabet.size();
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
// See: https://github.com/google/google-authenticator/wiki/Key-Uri-Format
|
||||
@@ -131,8 +181,8 @@ QUrl QTotp::generateOtpString(const QString& secret,
|
||||
const QString& issuer,
|
||||
const QString& username,
|
||||
const QString& algorithm,
|
||||
const quint8& digits,
|
||||
const quint8& step)
|
||||
quint8 digits,
|
||||
quint8 step)
|
||||
{
|
||||
QUrl keyUri;
|
||||
keyUri.setScheme("otpauth");
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
#define QTOTP_H
|
||||
|
||||
#include <QtCore/qglobal.h>
|
||||
#include <QString>
|
||||
#include <QMap>
|
||||
|
||||
class QUrl;
|
||||
|
||||
@@ -34,10 +36,25 @@ public:
|
||||
const QString& issuer,
|
||||
const QString& username,
|
||||
const QString& algorithm,
|
||||
const quint8& digits,
|
||||
const quint8& step);
|
||||
quint8 digits,
|
||||
quint8 step);
|
||||
static const quint8 defaultStep;
|
||||
static const quint8 defaultDigits;
|
||||
struct Encoder
|
||||
{
|
||||
QString name;
|
||||
QString shortName;
|
||||
QString alphabet;
|
||||
quint8 digits;
|
||||
quint8 step;
|
||||
bool reverse;
|
||||
};
|
||||
static const Encoder defaultEncoder;
|
||||
// custom encoder values that overload the digits field
|
||||
static const quint8 ENCODER_STEAM;
|
||||
static const QMap<quint8, Encoder> encoders;
|
||||
static const QMap<QString, quint8> shortNameToEncoder;
|
||||
static const QMap<QString, quint8> nameToEncoder;
|
||||
};
|
||||
|
||||
#endif // QTOTP_H
|
||||
|
||||
Reference in New Issue
Block a user