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 }