/* * SPDX-License-Identifier: GPL-3.0-or-later * SPDX-FileCopyrightText: 2020 Johan Ouwerkerk */ #include "qr.h" #include "../base32/base32.h" #include static std::optional parseUnsigned(const QString &value, const std::function &check) { bool ok = false; quint64 v = value.toULongLong(&ok, 10); return ok && check(v) ? std::optional(v) : std::nullopt; } static std::optional parseUnsigned(const QString &value, uint valueIfEmpty, const std::function &check) { if (value.isEmpty()) { return std::optional(valueIfEmpty); } const auto r = parseUnsigned(value, check); return r ? std::optional((uint)*r) : std::nullopt; } static std::function positiveUpTo(quint64 max) { return std::function([max](quint64 v) -> bool { return v >= 1ULL && v <= max; }); } static std::optional checkNonEmptyString(const QString &value, const std::function &check) { QString v(value); return v.isEmpty() || !check(v) ? std::nullopt : std::optional(v); } static std::function usableName(const QChar reserved = QLatin1Char('\0')) { return std::function([reserved](QString &v) -> bool { for (const auto c : v) { if (!c.isPrint() || c == reserved) { return false; } } return true; }); } namespace model { static std::optional convertType(uri::QrParts::Type type) { switch (type) { case uri::QrParts::Type::Hotp: return std::optional(AccountInput::TokenType::Hotp); case uri::QrParts::Type::Totp: return std::optional(AccountInput::TokenType::Totp); default: Q_ASSERT_X(false, Q_FUNC_INFO, "Unknown/unsupported otpauth token type?"); return std::nullopt; } } static std::optional convertAlgorithm(const QString &algorithm) { static const QString sha1 = QStringLiteral("sha1"); static const QString sha256 = QStringLiteral("sha256"); static const QString sha512 = QStringLiteral("sha512"); if (algorithm.isEmpty()) { return std::optional(AccountInput::TOTPAlgorithm::Sha1); } const auto lower = algorithm.toLower(); if (lower == sha1) { return std::optional(AccountInput::TOTPAlgorithm::Sha1); } if (lower == sha256) { return std::optional(AccountInput::TOTPAlgorithm::Sha256); } if (lower == sha512) { return std::optional(AccountInput::TOTPAlgorithm::Sha512); } return std::nullopt; } QrParameters::QrParameters(AccountInput::TokenType type, const QString &name, const QString &issuer, const QString &secret, uint tokenLength, quint64 counter, uint timeStep, AccountInput::TOTPAlgorithm algorithm) : m_type(type) , m_name(name) , m_issuer(issuer) , m_secret(secret) , m_tokenLength(tokenLength) , m_counter(counter) , m_timeStep(timeStep) , m_algorithm(algorithm) { } std::optional QrParameters::parse(const QByteArray &qrCode) { const auto parts = uri::QrParts::parse(qrCode); return parts ? from(*parts) : std::nullopt; } std::optional QrParameters::parse(const QString &qrCode) { const auto parts = uri::QrParts::parse(qrCode); return parts ? from(*parts) : std::nullopt; } std::optional QrParameters::from(const uri::QrParts &parts) { const auto type = convertType(parts.type()); const auto algorithm = convertAlgorithm(parts.algorithm()); const auto timeStep = parseUnsigned(parts.timeStep(), 30U, positiveUpTo(std::numeric_limits::max())); const auto tokenLength = parseUnsigned(parts.tokenLength(), 6U, [](qulonglong v) -> bool { return v >= 6ULL && v <= 10ULL; }); const auto counter = parts.counter().isEmpty() ? std::optional(0ULL) : parseUnsigned(parts.counter(), positiveUpTo(std::numeric_limits::max())); const auto secret = checkNonEmptyString(parts.secret(), [](QString &v) -> bool { while ((v.size() % 8) != 0) { v += QLatin1Char('='); } return (bool)base32::validate(v); }); const auto name = parts.name().isEmpty() ? std::optional(QString()) : checkNonEmptyString(parts.name(), usableName()); const auto issuer = parts.issuer().isEmpty() ? std::optional(QString()) : checkNonEmptyString(parts.issuer(), usableName(QLatin1Char(':'))); return type && algorithm && timeStep && tokenLength && counter && secret && name && issuer ? std::optional(QrParameters(*type, *name, *issuer, *secret, *tokenLength, *counter, *timeStep, *algorithm)) : std::nullopt; } void QrParameters::populate(AccountInput *input) const { if (!input) { Q_ASSERT_X(input, Q_FUNC_INFO, "Input must be provided"); return; } input->setType(m_type); input->setName(m_name); input->setIssuer(m_issuer); input->setSecret(m_secret); input->setTokenLength(m_tokenLength); input->setCounter(m_counter); input->setTimeStep(m_timeStep); input->setAlgorithm(m_algorithm); } }