diff --git a/lib/misc.c b/lib/misc.c index f9cacd9..c8707ee 100644 --- a/lib/misc.c +++ b/lib/misc.c @@ -15,8 +15,10 @@ * limitations under the License. */ +#define _GNU_SOURCE #include "misc.h" #include +#include #include #include "hooks.h" @@ -43,6 +45,51 @@ zero(void *mem, size_t len) memset(mem, 0, len); } +/* Decode the base64url-encoded protected header and load it as JSON only + * when it can contain a "zip" key. The full JSON parse is the expensive + * step (the base64url decode is comparatively cheap), so we gate it behind + * a substring scan for "zip" over the *decoded* bytes: a "zip" key is only + * possible if the literal bytes "zip" appear in the decoded JSON text. + * + * The scan must run on the decoded bytes, NOT on the base64url-encoded + * string: a "zip" key does not survive base64url encoding as the literal + * substring "zip", so scanning the encoded form yields false negatives. + * + * When "zip" is definitely absent, *no_zip is set to true and NULL is + * returned without parsing. On decode error, *no_zip stays false and NULL + * is returned (callers then treat it the same as a header with no zip). */ +static json_t * +load_protected_check_zip(const json_t *prt, bool *no_zip) +{ + uint8_t *buf = NULL; + json_t *out = NULL; + size_t size = 0; + + *no_zip = false; + + size = jose_b64_dec(prt, NULL, 0); + if (size == SIZE_MAX) + return NULL; + + buf = jose_calloc(1, size); + if (!buf) + return NULL; + + if (jose_b64_dec(prt, buf, size) != size) { + zero(buf, size); + jose_free(buf); + return NULL; + } + + if (memmem(buf, size, "zip", 3)) + out = json_loadb((char *) buf, size, JSON_DECODE_ANY, NULL); + else + *no_zip = true; + + zero(buf, size); + jose_free(buf); + return out; +} bool handle_zip_enc(json_t *json, const void *in, size_t len, void **data, size_t *datalen) @@ -54,8 +101,19 @@ handle_zip_enc(json_t *json, const void *in, size_t len, void **data, size_t *da jose_io_auto_t *zipdata = NULL; prt = json_object_get(json, "protected"); - if (prt && json_is_string(prt)) - prt = jose_b64_dec_load(prt); + if (prt && json_is_string(prt)) { + bool no_zip = false; + json_t *loaded = load_protected_check_zip(prt, &no_zip); + if (no_zip) { + /* No zip; skip the full parse. (prt is a borrowed reference, + * so clear it before returning to avoid a spurious decref.) */ + prt = NULL; + *data = (void*)in; + *datalen = len; + return true; + } + prt = loaded; + } /* Check if we have "zip" in the protected header. */ if (json_unpack(prt, "{s:s}", "zip", &z) == -1) { @@ -89,8 +147,17 @@ zip_in_protected_header(json_t *json) char *z = NULL; prt = json_object_get(json, "protected"); - if (prt && json_is_string(prt)) - prt = jose_b64_dec_load(prt); + if (prt && json_is_string(prt)) { + bool no_zip = false; + json_t *loaded = load_protected_check_zip(prt, &no_zip); + if (no_zip) { + /* No zip; skip the full parse. (prt is a borrowed reference, + * so clear it before returning to avoid a spurious decref.) */ + prt = NULL; + return false; + } + prt = loaded; + } /* Check if we have "zip" in the protected header. */ if (json_unpack(prt, "{s:s}", "zip", &z) == -1)