Correct issues with TOTP Setup
* Fix #3142 - Warn user when entering invalid TOTP secret key. * Fix #773 - The TOTP dialog now listens for the copy shortcut without having to press the Copy button. * Add ability to choose hash algorithm from the TOTP setup dialog * Add upgrade to "otp" attribute when custom attributes are chosen to prevent data loss Ran make format
This commit is contained in:
@@ -23,35 +23,34 @@
|
||||
#include <QCryptographicHash>
|
||||
#include <QMessageAuthenticationCode>
|
||||
#include <QRegularExpression>
|
||||
#include <QRegularExpressionMatch>
|
||||
#include <QUrl>
|
||||
#include <QUrlQuery>
|
||||
#include <QVariant>
|
||||
#include <QtEndian>
|
||||
#include <cmath>
|
||||
|
||||
static QList<Totp::Encoder> encoders{
|
||||
static QList<Totp::Encoder> totpEncoders{
|
||||
{"", "", "0123456789", Totp::DEFAULT_DIGITS, Totp::DEFAULT_STEP, false},
|
||||
{"steam", Totp::STEAM_SHORTNAME, "23456789BCDFGHJKMNPQRTVWXY", Totp::STEAM_DIGITS, Totp::DEFAULT_STEP, true},
|
||||
};
|
||||
|
||||
static Totp::HashType getHashTypeByName(const QString& name)
|
||||
static Totp::Algorithm getHashTypeByName(const QString& name)
|
||||
{
|
||||
if (name.compare(QString("SHA512"), Qt::CaseInsensitive) == 0) {
|
||||
return Totp::HashType::Sha512;
|
||||
return Totp::Algorithm::Sha512;
|
||||
}
|
||||
if (name.compare(QString("SHA256"), Qt::CaseInsensitive) == 0) {
|
||||
return Totp::HashType::Sha256;
|
||||
return Totp::Algorithm::Sha256;
|
||||
}
|
||||
return Totp::HashType::Sha1;
|
||||
return Totp::Algorithm::Sha1;
|
||||
}
|
||||
|
||||
static QString getNameForHashType(const Totp::HashType hashType)
|
||||
static QString getNameForHashType(const Totp::Algorithm hashType)
|
||||
{
|
||||
switch (hashType) {
|
||||
case Totp::HashType::Sha512:
|
||||
case Totp::Algorithm::Sha512:
|
||||
return QString("SHA512");
|
||||
case Totp::HashType::Sha256:
|
||||
case Totp::Algorithm::Sha256:
|
||||
return QString("SHA256");
|
||||
default:
|
||||
return QString("SHA1");
|
||||
@@ -67,24 +66,26 @@ QSharedPointer<Totp::Settings> Totp::parseSettings(const QString& rawSettings, c
|
||||
if (url.isValid() && url.scheme() == "otpauth") {
|
||||
// Default OTP url format
|
||||
QUrlQuery query(url);
|
||||
settings->otpUrl = true;
|
||||
settings->format = StorageFormat::OTPURL;
|
||||
settings->key = query.queryItemValue("secret");
|
||||
settings->digits = query.queryItemValue("digits").toUInt();
|
||||
settings->step = query.queryItemValue("period").toUInt();
|
||||
if (query.hasQueryItem("digits")) {
|
||||
settings->digits = query.queryItemValue("digits").toUInt();
|
||||
}
|
||||
if (query.hasQueryItem("period")) {
|
||||
settings->step = query.queryItemValue("period").toUInt();
|
||||
}
|
||||
if (query.hasQueryItem("encoder")) {
|
||||
settings->encoder = getEncoderByName(query.queryItemValue("encoder"));
|
||||
}
|
||||
if (query.hasQueryItem("algorithm")) {
|
||||
settings->hashType = getHashTypeByName(query.queryItemValue("algorithm"));
|
||||
settings->algorithm = getHashTypeByName(query.queryItemValue("algorithm"));
|
||||
}
|
||||
} else {
|
||||
QUrlQuery query(rawSettings);
|
||||
if (query.hasQueryItem("key")) {
|
||||
// Compatibility with "KeeOtp" plugin
|
||||
settings->keeOtp = true;
|
||||
settings->format = StorageFormat::KEEOTP;
|
||||
settings->key = query.queryItemValue("key");
|
||||
settings->digits = DEFAULT_DIGITS;
|
||||
settings->step = DEFAULT_STEP;
|
||||
if (query.hasQueryItem("size")) {
|
||||
settings->digits = query.queryItemValue("size").toUInt();
|
||||
}
|
||||
@@ -92,10 +93,11 @@ QSharedPointer<Totp::Settings> Totp::parseSettings(const QString& rawSettings, c
|
||||
settings->step = query.queryItemValue("step").toUInt();
|
||||
}
|
||||
if (query.hasQueryItem("otpHashMode")) {
|
||||
settings->hashType = getHashTypeByName(query.queryItemValue("otpHashMode"));
|
||||
settings->algorithm = getHashTypeByName(query.queryItemValue("otpHashMode"));
|
||||
}
|
||||
} else {
|
||||
// Parse semi-colon separated values ([step];[digits|S])
|
||||
settings->format = StorageFormat::LEGACY;
|
||||
auto vars = rawSettings.split(";");
|
||||
if (vars.size() >= 2) {
|
||||
if (vars[1] == STEAM_SHORTNAME) {
|
||||
@@ -116,7 +118,8 @@ QSharedPointer<Totp::Settings> Totp::parseSettings(const QString& rawSettings, c
|
||||
|
||||
// Detect custom settings, used by setup GUI
|
||||
if (settings->encoder.shortName.isEmpty()
|
||||
&& (settings->digits != DEFAULT_DIGITS || settings->step != DEFAULT_STEP)) {
|
||||
&& (settings->digits != DEFAULT_DIGITS || settings->step != DEFAULT_STEP
|
||||
|| settings->algorithm != DEFAULT_ALGORITHM)) {
|
||||
settings->custom = true;
|
||||
}
|
||||
|
||||
@@ -126,23 +129,13 @@ QSharedPointer<Totp::Settings> Totp::parseSettings(const QString& rawSettings, c
|
||||
QSharedPointer<Totp::Settings> Totp::createSettings(const QString& key,
|
||||
const uint digits,
|
||||
const uint step,
|
||||
const Totp::StorageFormat format,
|
||||
const QString& encoderShortName,
|
||||
const Totp::HashType hashType,
|
||||
QSharedPointer<Totp::Settings> prevSettings)
|
||||
const Totp::Algorithm algorithm)
|
||||
{
|
||||
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), hashType, key, false, false, isCustom, digits, step});
|
||||
}
|
||||
bool isCustom = digits != DEFAULT_DIGITS || step != DEFAULT_STEP || algorithm != DEFAULT_ALGORITHM;
|
||||
return QSharedPointer<Totp::Settings>(
|
||||
new Totp::Settings{format, getEncoderByShortName(encoderShortName), algorithm, key, isCustom, digits, step});
|
||||
}
|
||||
|
||||
QString Totp::writeSettings(const QSharedPointer<Totp::Settings>& settings,
|
||||
@@ -155,7 +148,7 @@ QString Totp::writeSettings(const QSharedPointer<Totp::Settings>& settings,
|
||||
}
|
||||
|
||||
// OTP Url output
|
||||
if (settings->otpUrl || forceOtp) {
|
||||
if (settings->format == StorageFormat::OTPURL || forceOtp) {
|
||||
auto urlstring = QString("otpauth://totp/%1:%2?secret=%3&period=%4&digits=%5&issuer=%1")
|
||||
.arg(title.isEmpty() ? "KeePassXC" : QString(QUrl::toPercentEncoding(title)),
|
||||
username.isEmpty() ? "none" : QString(QUrl::toPercentEncoding(username)),
|
||||
@@ -166,18 +159,18 @@ 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));
|
||||
if (settings->algorithm != Totp::DEFAULT_ALGORITHM) {
|
||||
urlstring.append("&algorithm=").append(getNameForHashType(settings->algorithm));
|
||||
}
|
||||
return urlstring;
|
||||
} else if (settings->keeOtp) {
|
||||
} else if (settings->format == StorageFormat::KEEOTP) {
|
||||
// KeeOtp output
|
||||
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));
|
||||
if (settings->algorithm != Totp::DEFAULT_ALGORITHM) {
|
||||
keyString.append("&otpHashMode=").append(getNameForHashType(settings->algorithm));
|
||||
}
|
||||
return keyString;
|
||||
} else if (!settings->encoder.shortName.isEmpty()) {
|
||||
@@ -213,11 +206,11 @@ QString Totp::generateTotp(const QSharedPointer<Totp::Settings>& settings, const
|
||||
}
|
||||
|
||||
QCryptographicHash::Algorithm cryptoHash;
|
||||
switch (settings->hashType) {
|
||||
case Totp::HashType::Sha512:
|
||||
switch (settings->algorithm) {
|
||||
case Totp::Algorithm::Sha512:
|
||||
cryptoHash = QCryptographicHash::Sha512;
|
||||
break;
|
||||
case Totp::HashType::Sha256:
|
||||
case Totp::Algorithm::Sha256:
|
||||
cryptoHash = QCryptographicHash::Sha256;
|
||||
break;
|
||||
default:
|
||||
@@ -256,11 +249,29 @@ QString Totp::generateTotp(const QSharedPointer<Totp::Settings>& settings, const
|
||||
return retval;
|
||||
}
|
||||
|
||||
QList<QPair<QString, QString>> Totp::supportedEncoders()
|
||||
{
|
||||
QList<QPair<QString, QString>> encoders;
|
||||
for (auto& encoder : totpEncoders) {
|
||||
encoders << QPair<QString, QString>(encoder.name, encoder.shortName);
|
||||
}
|
||||
return encoders;
|
||||
}
|
||||
|
||||
QList<QPair<QString, Totp::Algorithm>> Totp::supportedAlgorithms()
|
||||
{
|
||||
QList<QPair<QString, Algorithm>> algorithms;
|
||||
algorithms << QPair<QString, Algorithm>(QStringLiteral("SHA-1"), Algorithm::Sha1);
|
||||
algorithms << QPair<QString, Algorithm>(QStringLiteral("SHA-256"), Algorithm::Sha256);
|
||||
algorithms << QPair<QString, Algorithm>(QStringLiteral("SHA-512"), Algorithm::Sha512);
|
||||
return algorithms;
|
||||
}
|
||||
|
||||
Totp::Encoder& Totp::defaultEncoder()
|
||||
{
|
||||
// The first encoder is always the default
|
||||
Q_ASSERT(!encoders.empty());
|
||||
return encoders[0];
|
||||
Q_ASSERT(!totpEncoders.empty());
|
||||
return totpEncoders[0];
|
||||
}
|
||||
|
||||
Totp::Encoder& Totp::steamEncoder()
|
||||
@@ -270,7 +281,7 @@ Totp::Encoder& Totp::steamEncoder()
|
||||
|
||||
Totp::Encoder& Totp::getEncoderByShortName(const QString& shortName)
|
||||
{
|
||||
for (auto& encoder : encoders) {
|
||||
for (auto& encoder : totpEncoders) {
|
||||
if (encoder.shortName == shortName) {
|
||||
return encoder;
|
||||
}
|
||||
@@ -280,7 +291,7 @@ Totp::Encoder& Totp::getEncoderByShortName(const QString& shortName)
|
||||
|
||||
Totp::Encoder& Totp::getEncoderByName(const QString& name)
|
||||
{
|
||||
for (auto& encoder : encoders) {
|
||||
for (auto& encoder : totpEncoders) {
|
||||
if (encoder.name == name) {
|
||||
return encoder;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ class QUrl;
|
||||
|
||||
namespace Totp
|
||||
{
|
||||
|
||||
struct Encoder
|
||||
{
|
||||
QString name;
|
||||
@@ -39,20 +38,26 @@ namespace Totp
|
||||
bool reverse;
|
||||
};
|
||||
|
||||
enum HashType
|
||||
enum Algorithm
|
||||
{
|
||||
Sha1,
|
||||
Sha256,
|
||||
Sha512,
|
||||
};
|
||||
|
||||
enum StorageFormat
|
||||
{
|
||||
OTPURL,
|
||||
KEEOTP,
|
||||
LEGACY,
|
||||
};
|
||||
|
||||
struct Settings
|
||||
{
|
||||
Totp::StorageFormat format;
|
||||
Totp::Encoder encoder;
|
||||
Totp::HashType hashType;
|
||||
Totp::Algorithm algorithm;
|
||||
QString key;
|
||||
bool otpUrl;
|
||||
bool keeOtp;
|
||||
bool custom;
|
||||
uint digits;
|
||||
uint step;
|
||||
@@ -61,7 +66,8 @@ namespace Totp
|
||||
constexpr uint DEFAULT_STEP = 30u;
|
||||
constexpr uint DEFAULT_DIGITS = 6u;
|
||||
constexpr uint STEAM_DIGITS = 5u;
|
||||
constexpr Totp::HashType DEFAULT_HASHTYPE = Sha1;
|
||||
constexpr Totp::Algorithm DEFAULT_ALGORITHM = Sha1;
|
||||
constexpr Totp::StorageFormat DEFAULT_FORMAT = OTPURL;
|
||||
static const QString STEAM_SHORTNAME = "S";
|
||||
|
||||
static const QString ATTRIBUTE_OTP = "otp";
|
||||
@@ -72,9 +78,9 @@ namespace Totp
|
||||
QSharedPointer<Totp::Settings> createSettings(const QString& key,
|
||||
const uint digits,
|
||||
const uint step,
|
||||
const Totp::StorageFormat format = DEFAULT_FORMAT,
|
||||
const QString& encoderShortName = {},
|
||||
const Totp::HashType hashType = DEFAULT_HASHTYPE,
|
||||
QSharedPointer<Totp::Settings> prevSettings = {});
|
||||
const Totp::Algorithm algorithm = DEFAULT_ALGORITHM);
|
||||
QString writeSettings(const QSharedPointer<Totp::Settings>& settings,
|
||||
const QString& title = {},
|
||||
const QString& username = {},
|
||||
@@ -82,6 +88,9 @@ namespace Totp
|
||||
|
||||
QString generateTotp(const QSharedPointer<Totp::Settings>& settings, const quint64 time = 0ull);
|
||||
|
||||
QList<QPair<QString, QString>> supportedEncoders();
|
||||
QList<QPair<QString, Algorithm>> supportedAlgorithms();
|
||||
|
||||
Encoder& defaultEncoder();
|
||||
Encoder& steamEncoder();
|
||||
Encoder& getEncoderByShortName(const QString& shortName);
|
||||
|
||||
Reference in New Issue
Block a user