File indexing completed on 2024-04-28 05:50:08

0001 /*
0002  * SPDX-License-Identifier: GPL-3.0-or-later
0003  * SPDX-FileCopyrightText: 2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
0004  */
0005 
0006 #include "qr.h"
0007 
0008 #include "../base32/base32.h"
0009 
0010 #include <limits>
0011 
0012 static std::optional<quint64> parseUnsigned(const QString &value, const std::function<bool(qulonglong)> &check)
0013 {
0014     bool ok = false;
0015     quint64 v = value.toULongLong(&ok, 10);
0016 
0017     return ok && check(v) ? std::optional<quint64>(v) : std::nullopt;
0018 }
0019 
0020 static std::optional<uint> parseUnsigned(const QString &value, uint valueIfEmpty, const std::function<bool(quint64)> &check)
0021 {
0022     if (value.isEmpty()) {
0023         return std::optional<uint>(valueIfEmpty);
0024     }
0025 
0026     const auto r = parseUnsigned(value, check);
0027     return r ? std::optional<uint>((uint) *r) : std::nullopt;
0028 }
0029 
0030 static std::function<bool(quint64)> positiveUpTo(quint64 max)
0031 {
0032     return std::function<bool(quint64)>([max](quint64 v) -> bool
0033     {
0034         return v >= 1ULL && v <= max;
0035     });
0036 }
0037 
0038 static std::optional<QString> checkNonEmptyString(const QString &value, const std::function<bool(QString&)> &check)
0039 {
0040     QString v(value);
0041     return v.isEmpty() || !check(v) ? std::nullopt : std::optional<QString>(v);
0042 }
0043 
0044 static std::function<bool(QString&)> usableName(const QChar reserved = QLatin1Char('\0'))
0045 {
0046     return std::function<bool(QString &)>([reserved](QString &v) -> bool
0047     {
0048         for (const auto c : v) {
0049             if (!c.isPrint() || c == reserved) {
0050                 return false;
0051             }
0052         }
0053 
0054         return true;
0055     });
0056 }
0057 
0058 namespace model
0059 {
0060 
0061     static std::optional<AccountInput::TokenType> convertType(uri::QrParts::Type type)
0062     {
0063         switch (type) {
0064         case uri::QrParts::Type::Hotp:
0065             return std::optional<AccountInput::TokenType>(AccountInput::TokenType::Hotp);
0066         case uri::QrParts::Type::Totp:
0067             return std::optional<AccountInput::TokenType>(AccountInput::TokenType::Totp);
0068         default:
0069             Q_ASSERT_X(false, Q_FUNC_INFO, "Unknown/unsupported otpauth token type?");
0070             return std::nullopt;
0071         }
0072     }
0073 
0074     static std::optional<AccountInput::TOTPAlgorithm> convertAlgorithm(const QString &algorithm)
0075     {
0076         static const QString sha1 = QStringLiteral("sha1");
0077         static const QString sha256 = QStringLiteral("sha256");
0078         static const QString sha512 = QStringLiteral("sha512");
0079 
0080         if (algorithm.isEmpty()) {
0081             return std::optional<AccountInput::TOTPAlgorithm>(AccountInput::TOTPAlgorithm::Sha1);
0082         }
0083 
0084         const auto lower = algorithm.toLower();
0085         if (lower == sha1) {
0086             return std::optional<AccountInput::TOTPAlgorithm>(AccountInput::TOTPAlgorithm::Sha1);
0087         }
0088         if (lower == sha256) {
0089             return std::optional<AccountInput::TOTPAlgorithm>(AccountInput::TOTPAlgorithm::Sha256);
0090         }
0091         if (lower == sha512) {
0092             return std::optional<AccountInput::TOTPAlgorithm>(AccountInput::TOTPAlgorithm::Sha512);
0093         }
0094 
0095         return std::nullopt;
0096     }
0097 
0098     QrParameters::QrParameters(AccountInput::TokenType type, const QString &name, const QString &issuer,
0099                                const QString &secret, uint tokenLength, quint64 counter, uint timeStep,
0100                                AccountInput::TOTPAlgorithm algorithm) :
0101         m_type(type), m_name(name), m_issuer(issuer), m_secret(secret), m_tokenLength(tokenLength),
0102         m_counter(counter), m_timeStep(timeStep), m_algorithm(algorithm)
0103     {
0104     }
0105 
0106     std::optional<QrParameters> QrParameters::parse(const QByteArray &qrCode)
0107     {
0108         const auto parts = uri::QrParts::parse(qrCode);
0109         return parts ? from(*parts) : std::nullopt;
0110     }
0111 
0112     std::optional<QrParameters> QrParameters::parse(const QString &qrCode)
0113     {
0114         const auto parts = uri::QrParts::parse(qrCode);
0115         return parts ? from(*parts) : std::nullopt;
0116     }
0117 
0118     std::optional<QrParameters> QrParameters::from(const uri::QrParts &parts)
0119     {
0120         const auto type = convertType(parts.type());
0121         const auto algorithm = convertAlgorithm(parts.algorithm());
0122         const auto timeStep = parseUnsigned(parts.timeStep(), 30U, positiveUpTo(std::numeric_limits<uint>::max()));
0123 
0124         const auto tokenLength = parseUnsigned(parts.tokenLength(), 6U, [](qulonglong v) -> bool
0125         {
0126             return v >= 6ULL && v <= 10ULL;
0127         });
0128 
0129         const auto counter = parts.counter().isEmpty()
0130             ? std::optional<quint64>(0ULL)
0131             : parseUnsigned(parts.counter(),positiveUpTo(std::numeric_limits<quint64>::max()));
0132 
0133         const auto secret = checkNonEmptyString(parts.secret(), [](QString &v) -> bool
0134         {
0135             while ((v.size() % 8) != 0) {
0136                 v += QLatin1Char('=');
0137             }
0138 
0139             return (bool) base32::validate(v);
0140         });
0141 
0142         const auto name = parts.name().isEmpty()
0143             ? std::optional<QString>(QString())
0144             : checkNonEmptyString(parts.name(), usableName());
0145 
0146         const auto issuer = parts.issuer().isEmpty()
0147             ? std::optional<QString>(QString())
0148             : checkNonEmptyString(parts.issuer(), usableName(QLatin1Char(':')));
0149 
0150         return type && algorithm && timeStep && tokenLength && counter && secret && name && issuer
0151             ? std::optional<QrParameters>(QrParameters(*type, *name, *issuer, *secret, *tokenLength, *counter, *timeStep, *algorithm))
0152             : std::nullopt;
0153     }
0154 
0155     void QrParameters::populate(AccountInput *input) const
0156     {
0157         if (!input) {
0158             Q_ASSERT_X(input, Q_FUNC_INFO, "Input must be provided");
0159             return;
0160         }
0161 
0162         input->setType(m_type);
0163         input->setName(m_name);
0164         input->setIssuer(m_issuer);
0165         input->setSecret(m_secret);
0166         input->setTokenLength(m_tokenLength);
0167         input->setCounter(m_counter);
0168         input->setTimeStep(m_timeStep);
0169         input->setAlgorithm(m_algorithm);
0170     }
0171 }