/* SPDX-License-Identifier: GPL-3.0-only */ #include "web_auth_parse.h" #include static void wipe(void *buffer, size_t length) { volatile uint8_t *p = buffer; while (length--) *p++ = 0; } static bool alnum_ascii(unsigned char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'); } static bool authority(const char *text, size_t length, char *out) { if (!text || !length || length > WEB_AUTH_ORIGIN_CAPACITY - 5U) return false; if (length >= 4U && memcmp(text + length - 4U, ":443", 4U) == 0) length -= 4U; if (!length || length > WEB_AUTH_ORIGIN_CAPACITY - 9U) return false; size_t label = 0; for (size_t i = 0; i < length; ++i) { unsigned char c = (unsigned char)text[i]; if (c == '.') { if (!label || text[i - 1U] == '-') return false; label = 0; } else { if (!alnum_ascii(c) && c != '-') return false; if ((!label && c == '-') || ++label > 63U) return false; } out[i] = c >= 'A' && c <= 'Z' ? (char)(c + ('a' - 'A')) : (char)c; } if (!label || text[length - 1U] == '-') return false; out[length] = 0; return true; } bool web_auth_parse_origin(const char *host, size_t host_length, const char *origin, size_t origin_length, char canonical[WEB_AUTH_ORIGIN_CAPACITY]) { if (!canonical) return false; memset(canonical, 0, WEB_AUTH_ORIGIN_CAPACITY); char other[WEB_AUTH_ORIGIN_CAPACITY] = {0}; if (!origin || origin_length < 9U || origin_length > WEB_AUTH_ORIGIN_CAPACITY + 3U || memcmp(origin, "https://", 8U) != 0 || !authority(host, host_length, canonical + 8U) || !authority(origin + 8U, origin_length - 8U, other) || strcmp(canonical + 8U, other) != 0) { memset(canonical, 0, WEB_AUTH_ORIGIN_CAPACITY); return false; } memcpy(canonical, "https://", 8U); return true; } static bool cookie_name_char(unsigned char c) { return alnum_ascii(c) || (c && strchr("!#$%&'*+-.^_`|~", c)); } bool web_auth_parse_optional_cookie(const char *header, size_t length, const char *name, char token[WEB_AUTH_TOKEN_LENGTH + 1U], bool *present) { if (!present) return false; *present = false; if (!token) return false; memset(token, 0, WEB_AUTH_TOKEN_LENGTH + 1U); if (!header || !name || !*name || !length || length > WEB_AUTH_COOKIE_HEADER_MAX) return false; size_t pos = 0, selected = 0, name_length = strlen(name); bool found = false; while (pos < length) { while (pos < length && header[pos] == ' ') ++pos; size_t start = pos; while (pos < length && cookie_name_char((unsigned char)header[pos])) ++pos; size_t key_length = pos - start; if (!key_length || pos == length || header[pos++] != '=') return false; size_t value = pos; while (pos < length && header[pos] != ';') { unsigned char c = (unsigned char)header[pos++]; if (c < 0x21 || c > 0x7e || c == '"' || c == ',' || c == '\\') return false; } if (key_length == name_length && memcmp(header + start, name, key_length) == 0) { if (found || pos - value != WEB_AUTH_TOKEN_LENGTH) return false; for (size_t i = value; i < pos; ++i) if (!((header[i] >= '0' && header[i] <= '9') || (header[i] >= 'a' && header[i] <= 'f'))) return false; found = true; selected = value; } if (pos < length && ++pos == length) return false; } if (found) memcpy(token, header + selected, WEB_AUTH_TOKEN_LENGTH); *present = found; return true; } bool web_auth_parse_cookie(const char *header, size_t length, const char *name, char token[WEB_AUTH_TOKEN_LENGTH + 1U]) { bool present = false; return web_auth_parse_optional_cookie(header, length, name, token, &present) && present; } typedef struct { const uint8_t *data; size_t length; size_t pos; } json_cursor_t; static void whitespace(json_cursor_t *c) { while (c->pos < c->length) { uint8_t b = c->data[c->pos]; if (b != ' ' && b != '\t' && b != '\r' && b != '\n') break; ++c->pos; } } static bool take(json_cursor_t *c, uint8_t byte) { whitespace(c); if (c->pos == c->length || c->data[c->pos] != byte) return false; ++c->pos; return true; } static bool hex4(json_cursor_t *c, uint32_t *value) { *value = 0; for (unsigned i = 0; i < 4; ++i) { if (c->pos == c->length) return false; uint8_t b = c->data[c->pos++]; unsigned digit; if (b >= '0' && b <= '9') digit = b - '0'; else if (b >= 'a' && b <= 'f') digit = b - 'a' + 10U; else if (b >= 'A' && b <= 'F') digit = b - 'A' + 10U; else return false; *value = (*value << 4) | digit; } return true; } static bool codepoint(json_cursor_t *c, uint32_t *value) { if (c->pos == c->length) return false; uint8_t b = c->data[c->pos++]; if (b == '\\') { if (c->pos == c->length) return false; b = c->data[c->pos++]; switch (b) { case '"': case '\\': case '/': *value = b; return true; case 'b': *value = 8; return true; case 'f': *value = 12; return true; case 'n': *value = 10; return true; case 'r': *value = 13; return true; case 't': *value = 9; return true; case 'u': break; default: return false; } if (!hex4(c, value)) return false; if (*value >= 0xd800 && *value <= 0xdbff) { uint32_t low; if (c->length - c->pos < 2U || c->data[c->pos++] != '\\' || c->data[c->pos++] != 'u' || !hex4(c, &low) || low < 0xdc00 || low > 0xdfff) return false; *value = 0x10000 + ((*value - 0xd800) << 10) + low - 0xdc00; } return *value && !(*value >= 0xd800 && *value <= 0xdfff); } if (b < 0x20) return false; if (b < 0x80) { *value = b; return true; } unsigned extra; uint32_t minimum; if (b >= 0xc2 && b <= 0xdf) { extra = 1; minimum = 0x80; *value = b & 0x1f; } else if (b >= 0xe0 && b <= 0xef) { extra = 2; minimum = 0x800; *value = b & 0x0f; } else if (b >= 0xf0 && b <= 0xf4) { extra = 3; minimum = 0x10000; *value = b & 7; } else return false; while (extra--) { if (c->pos == c->length) return false; b = c->data[c->pos++]; if ((b & 0xc0) != 0x80) return false; *value = (*value << 6) | (b & 0x3f); } return *value >= minimum && *value <= 0x10ffff && !(*value >= 0xd800 && *value <= 0xdfff); } static bool string(json_cursor_t *c, uint8_t *out, size_t capacity, size_t *length) { *length = 0; if (!take(c, '"')) return false; while (c->pos < c->length && c->data[c->pos] != '"') { uint32_t cp; if (!codepoint(c, &cp)) return false; size_t bytes = cp < 0x80 ? 1U : cp < 0x800 ? 2U : cp < 0x10000 ? 3U : 4U; if (bytes > capacity - *length) return false; if (bytes == 1U) out[(*length)++] = (uint8_t)cp; else { out[(*length)++] = (uint8_t)((bytes == 2U ? 0xc0 : bytes == 3U ? 0xe0 : 0xf0) | (cp >> (6U * (bytes - 1U)))); for (size_t i = bytes - 1U; i > 0; --i) out[(*length)++] = (uint8_t)(0x80 | ((cp >> (6U * (i - 1U))) & 0x3f)); } } if (c->pos == c->length) return false; ++c->pos; out[*length] = 0; return true; } bool web_auth_parse_json_string(const char *body, size_t length, size_t *position, uint8_t *output, size_t capacity, size_t *decoded_length) { if (output && capacity) wipe(output, capacity); if (decoded_length) *decoded_length = 0; if (!body || !position || *position > length || !output || !capacity || !decoded_length) return false; json_cursor_t c = { (const uint8_t *)body, length, *position }; if (!string(&c, output, capacity - 1U, decoded_length)) { wipe(output, capacity); *decoded_length = 0; return false; } *position = c.pos; return true; } bool web_auth_parse_login(const char *body, size_t length, web_auth_credentials_t *credentials) { if (!credentials) return false; wipe(credentials, sizeof(*credentials)); if (!body || !length || length > WEB_AUTH_LOGIN_BODY_MAX) return false; json_cursor_t c = { (const uint8_t *)body, length, 0 }; unsigned seen = 0; if (!take(&c, '{')) return false; for (unsigned field = 0; field < 2; ++field) { uint8_t key[9] = {0}; size_t key_length; if ((field && !take(&c, ',')) || !string(&c, key, 8U, &key_length) || !take(&c, ':')) goto invalid; unsigned bit; uint8_t *output; size_t *output_length, capacity; if (key_length == 8U && memcmp(key, "username", 8U) == 0) { bit = 1; output = credentials->username; output_length = &credentials->username_length; capacity = WEB_AUTH_USERNAME_MAX; } else if (key_length == 8U && memcmp(key, "password", 8U) == 0) { bit = 2; output = credentials->password; output_length = &credentials->password_length; capacity = WEB_AUTH_PASSWORD_MAX; } else goto invalid; if ((seen & bit) || !string(&c, output, capacity, output_length)) goto invalid; seen |= bit; } if (!take(&c, '}')) goto invalid; whitespace(&c); if (c.pos == c.length && seen == 3U) return true; invalid: wipe(credentials, sizeof(*credentials)); return false; }