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:
Jonathan White
2019-10-14 09:25:45 -04:00
parent 71085838db
commit 99a2d66086
9 changed files with 211 additions and 122 deletions

View File

@@ -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;
}

View File

@@ -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);