diff options
Diffstat (limited to 'src/totp/totp.cpp')
-rw-r--r-- | src/totp/totp.cpp | 72 |
1 files changed, 61 insertions, 11 deletions
diff --git a/src/totp/totp.cpp b/src/totp/totp.cpp index 7a584def2..f102335aa 100644 --- a/src/totp/totp.cpp +++ b/src/totp/totp.cpp @@ -28,14 +28,47 @@ #include <QtEndian> #include <cmath> -const quint8 QTotp::defaultStep = 30; -const quint8 QTotp::defaultDigits = 6; +const quint8 Totp::defaultStep = 30; +const quint8 Totp::defaultDigits = 6; -QTotp::QTotp() +/** + * 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 Totp::ENCODER_STEAM = 254; + +const Totp::Encoder Totp::defaultEncoder = { "", "", "0123456789", 0, 0, false }; +const QMap<quint8, Totp::Encoder> Totp::encoders{ + { Totp::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> Totp::shortNameToEncoder{ + { "S", Totp::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> Totp::nameToEncoder{ + { "steam", Totp::ENCODER_STEAM }, +}; + +Totp::Totp() { } -QString QTotp::parseOtpString(QString key, quint8& digits, quint8& step) +QString Totp::parseOtpString(QString key, quint8& digits, quint8& step) { QUrl url(key); @@ -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); @@ -92,7 +128,7 @@ QString QTotp::parseOtpString(QString key, quint8& digits, quint8& step) return seed; } -QString QTotp::generateTotp(const QByteArray key, +QString Totp::generateTotp(const QByteArray key, quint64 time, const quint8 numDigits = defaultDigits, const quint8 step = defaultStep) @@ -119,20 +155,34 @@ 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 -QUrl QTotp::generateOtpString(const QString& secret, +QUrl Totp::generateOtpString(const QString& secret, const QString& type, const QString& issuer, const QString& username, const QString& algorithm, - const quint8& digits, - const quint8& step) + quint8 digits, + quint8 step) { QUrl keyUri; keyUri.setScheme("otpauth"); |