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

0001 /*
0002  * SPDX-License-Identifier: GPL-3.0-or-later
0003  * SPDX-FileCopyrightText: 2019-2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
0004  */
0005 #include "base32.h"
0006 #include "../logging_p.h"
0007 
0008 KEYSMITH_LOGGER(logger, ".base32")
0009 
0010 static const QChar alphaMinLowerCase(QLatin1Char('a'));
0011 static const QChar alphaMaxLowerCase(QLatin1Char('z'));
0012 static const QChar alphaMinUpperCase(QLatin1Char('A'));
0013 static const QChar alphaMaxUpperCase(QLatin1Char('Z'));
0014 static const QChar numMin(QLatin1Char('2'));
0015 static const QChar numMax(QLatin1Char('7'));
0016 static const QChar pad(QLatin1Char('='));
0017 
0018 static inline bool checkInputRange(const QString &encoded, int from, int until)
0019 {
0020     /*
0021      * from should be between 0 (inclusive) and size (exclusive)
0022      * until should be between from (inclusive) and size (inclusive)
0023      * total range size (until - from) should be a multiple of 8 or it is not valid base32
0024      */
0025     int size = encoded.size();
0026     return from >= 0 && from <= size && until >= from && until <= size && ((until - from) % 8) == 0;
0027 }
0028 
0029 static std::optional<int> decode(const QChar &chr)
0030 {
0031     if (chr >= alphaMinLowerCase && chr <= alphaMaxLowerCase) {
0032         return std::optional<int>(chr.toLatin1() - alphaMinLowerCase.toLatin1());
0033     }
0034     if (chr >= alphaMinUpperCase && chr <= alphaMaxUpperCase) {
0035         return std::optional<int>(chr.toLatin1() - alphaMinUpperCase.toLatin1());
0036     }
0037     if (chr >= numMin && chr <= numMax) {
0038         return std::optional<int>(26 + chr.toLatin1() - numMin.toLatin1());
0039     }
0040 
0041     if (chr >= pad) {
0042         return std::optional<int>(0);
0043     }
0044 
0045     return std::nullopt;
0046 }
0047 
0048 static std::optional<quint64> decode(const QString &encoded, int index)
0049 {
0050     quint64 result = 0ULL;
0051 
0052     for (int i = 0; i < 8; ++i) {
0053         const QChar inputChar = encoded[index + i];
0054         std::optional<int> v = decode(inputChar);
0055         if (v) {
0056             result = (result << 5) | *v;
0057         } else {
0058             qCDebug(logger) << "Not a valid base32 character:" << inputChar;
0059             return std::nullopt;
0060         }
0061     }
0062 
0063     return std::optional<quint64>(result);
0064 }
0065 
0066 static std::optional<size_t> decode(const QString &encoded, int index, int end, int padding, size_t offset, size_t capacity, char * const output)
0067 {
0068     Q_ASSERT_X(offset <= capacity, Q_FUNC_INFO, "invalid offset into output buffer");
0069     Q_ASSERT_X(end >= 0 && end <= encoded.size(), Q_FUNC_INFO, "end of encoded data should be valid");
0070     Q_ASSERT_X(padding >= 0 && padding <= end, Q_FUNC_INFO, "padding index should be valid");
0071     Q_ASSERT_X(index >= 0 && index <= padding && ((end - index) % 8) == 0, Q_FUNC_INFO, "index should be valid");
0072 
0073     size_t group;
0074 
0075     switch ((index + 8) - padding)
0076     {
0077         case 2:
0078         case 5:
0079         case 7:
0080             Q_ASSERT_X(false, Q_FUNC_INFO, "invalid amount of padding should have been caught by previous validation");
0081             return std::nullopt;
0082         case 1:
0083             group = 4;
0084             break;
0085         case 3:
0086             group = 3;
0087             break;
0088         case 4:
0089             group = 2;
0090             break;
0091         case 6:
0092             group = 1;
0093             break;
0094         default: // no padding (yet) for the group at the given index: there are 8 or more bytes left
0095             group = 5;
0096             break;
0097     }
0098 
0099     Q_ASSERT_X((capacity - offset) >= group, Q_FUNC_INFO, "offset/output group too big for output buffer size");
0100 
0101     std::optional<quint64> bits = decode(encoded, index);
0102 
0103     Q_ASSERT_X(bits, Q_FUNC_INFO, "invalid input should have been caught by prior validation");
0104 
0105     quint64 value = *bits;
0106     for (size_t i = 0; i < group; ++i) {
0107         output[offset + i] = (char) ((value >> (32ULL - i * 8ULL)) & 0xFFULL);
0108     }
0109 
0110     return std::optional<size_t>(group);
0111 }
0112 
0113 static inline bool isBase32(const QChar &c)
0114 {
0115     return (c >= alphaMinLowerCase && c <= alphaMaxLowerCase) || (c >= alphaMinUpperCase && c <= alphaMaxUpperCase) || (c >= numMin && c <= numMax);
0116 }
0117 
0118 static bool isPaddingValid(const QString &encoded, int paddingIndex, int amount)
0119 {
0120     static const int padMasks[7] = {
0121         0x7, // 8 - 1 padding -> 7 * 5 - 32 bits -> 3 trailing bits: mask 0x7
0122         0x0, // 8 - 2 padding -> invalid
0123         0x1, // 8 - 3 padding -> 5 * 5 - 24 bits -> 1 trailing bit : mask 0x1
0124         0xF, // 8 - 4 padding -> 4 * 5 - 16 bits -> 4 trailing bits: mask 0xF
0125         0x0, // 8 - 5 padding -> invalid
0126         0x3, // 8 - 6 padding -> 2 * 5 -  8 bits -> 2 trailing bits: mask 0x3
0127         0x0  // 8 - 7 padding -> invalid
0128     };
0129 
0130     if (amount == 0) {
0131         return true;
0132     }
0133 
0134     if (amount >= 8) {
0135         return false;
0136     }
0137 
0138     Q_ASSERT_X(paddingIndex >= 0, Q_FUNC_INFO, "invalid amount of padding should have been caught by previous validation");
0139 
0140     const QChar c = encoded[paddingIndex - 1];
0141     Q_ASSERT_X(c != pad, Q_FUNC_INFO, "invalid amount of padding should have been caught by previous validation");
0142 
0143     /*
0144      * Check if the amount of padding corresponds to a known (valid) input 'group' size
0145      * by looking up the mask for the last character before padding (0 = invalid)
0146      */
0147     int p = padMasks[amount - 1];
0148     if (p == 0) {
0149         return false;
0150     }
0151 
0152     std::optional<int> d = decode(c);
0153     Q_ASSERT_X(d, Q_FUNC_INFO, "invalid input should have been caught by prior validation");
0154 
0155     /*
0156      * check if there are no trailing bits,
0157      * i.e. the last character before padding does not encode bits that are not whitelisted by the mask
0158      */
0159     return ((*d) & p) == 0;
0160 
0161 }
0162 
0163 static std::optional<int> isBase32(const QString &encoded, int from, int until)
0164 {
0165     if (!checkInputRange(encoded, from, until)) {
0166         return std::nullopt;
0167     }
0168 
0169     int paddingIndex = until;
0170     for (int i = from; i < until; ++i) {
0171         const QChar at = encoded[i];
0172         if (at == pad) {
0173             if (paddingIndex == until) {
0174                 paddingIndex = i;
0175             }
0176         } else {
0177             /*
0178              * Reject input if:
0179              * - padding has 'started' but the current character is not the padding character
0180              * - the current character is not a (valid) value character
0181              */
0182             if (paddingIndex < until || !isBase32(at)) {
0183                 return std::nullopt;
0184             }
0185         }
0186     }
0187 
0188     int amount = until - paddingIndex;
0189 
0190     return isPaddingValid(encoded, paddingIndex, amount) ? std::optional<int>(paddingIndex) : std::nullopt;
0191 }
0192 
0193 static inline size_t determineCapacity(size_t encodedBytes, size_t accountFor, size_t lastBytes)
0194 {
0195     return 5 * (encodedBytes - accountFor) / 8  + lastBytes;
0196 }
0197 
0198 static size_t requiredCapacity(int paddingIndex, int from, int until)
0199 {
0200     // based on the amount of padding, determine the exact size of the encoded data
0201     int size = paddingIndex - from;
0202     switch (until - paddingIndex) {
0203     case 0:
0204         return determineCapacity(size, 0, 0);
0205     case 1:
0206         return determineCapacity(size, 7, 4);
0207     case 3:
0208         return determineCapacity(size, 5, 3);
0209     case 4:
0210         return determineCapacity(size, 4, 2);
0211     case 6:
0212         return determineCapacity(size, 2, 1);
0213     default:
0214         Q_ASSERT_X(false, Q_FUNC_INFO, "invalid input size/amount of padding should have been caught by previous validation");
0215         return 0;
0216     }
0217 }
0218 
0219 namespace base32
0220 {
0221     std::optional<size_t> validate(const QString &encoded, int from, int until)
0222     {
0223         int max = until == -1 ? encoded.size() : until;
0224         if (!checkInputRange(encoded, from, max)) {
0225             return std::nullopt;
0226         }
0227 
0228         std::optional<int> padding = isBase32(encoded, from, max);
0229         return padding ? std::optional<size_t>(requiredCapacity(*padding, from, max)) : std::nullopt;
0230     }
0231 
0232     std::optional<size_t> decode(const QString &encoded, char * const out, size_t outlen, int from, int until)
0233     {
0234         int max = until == -1 ? encoded.size() : until;
0235         if (!checkInputRange(encoded, from, max)) {
0236             qCDebug(logger) << "Invalid input range from:" << from << "until:" << until << "implied limit:" << max;
0237             return std::nullopt;
0238         }
0239 
0240         std::optional<int> padding = isBase32(encoded, from, max);
0241         if (!padding) {
0242             qCDebug(logger) << "Unable to decode: input range is not valid base32";
0243             return std::nullopt;
0244         }
0245 
0246         size_t needed = requiredCapacity(*padding, from, max);
0247         if (outlen < needed) {
0248             qCDebug(logger) << "Unable to decode: required capacity:" << needed << "exceeds allocated output buffer size:" << outlen;
0249             return std::nullopt;
0250         }
0251 
0252         int index;
0253         size_t decoded = 0;
0254         for(index = from; index < max && decoded < needed; index += 8) {
0255             std::optional<size_t> group = decode(encoded, index, max, *padding, decoded, needed, out);
0256             Q_ASSERT_X(group, Q_FUNC_INFO, "input should have been fully validated; decoding should succeed");
0257             decoded += *group;
0258         }
0259 
0260         Q_ASSERT_X(decoded == needed, Q_FUNC_INFO, "number of bytes decoded should match expected output capacity required");
0261         Q_ASSERT_X(index == max, Q_FUNC_INFO, "number of characters decoded should match end of the input range exactly");
0262 
0263         return std::optional<size_t>(decoded);
0264     }
0265 
0266     std::optional<QByteArray> decode(const QString &encoded)
0267     {
0268         std::optional<QByteArray> result = std::nullopt;
0269         std::optional<size_t> capacity = validate(encoded);
0270 
0271         if (!capacity) {
0272             qCDebug(logger) << "Unable to decode input: invalid base32";
0273             return std::nullopt;
0274         }
0275 
0276         QByteArray decoded;
0277         decoded.reserve((int) *capacity);
0278         decoded.resize((int) *capacity);
0279         if (decode(encoded, decoded.data(), *capacity)) {
0280             result.emplace(decoded);
0281         } else {
0282             qCDebug(logger) << "Failed to decode base32";
0283         }
0284 
0285         return result;
0286     }
0287 }