diff --git a/src/est/est_server.c b/src/est/est_server.c index e587411..2fc8ce0 100644 --- a/src/est/est_server.c +++ b/src/est/est_server.c @@ -109,7 +109,7 @@ typedef struct { size_t content_length; uint8_t* body; size_t body_len; - int connection_close; /* 1 when client sent Connection: close */ + int connection_close; /* 1 to close after the response */ void* heap; } EstRequest; @@ -139,94 +139,6 @@ static int read_line(const char** p, const char* end, char** ls, size_t* ll) return 0; } -/* Parse the chunk-size line at raw[ri..]. The 8 hex digit cap keeps csz within - * 32 bits. Returns 0 ok, 1 if the CRLF has not arrived, -1 if malformed. */ -static int read_chunk_size(const uint8_t* raw, size_t raw_len, size_t ri, - size_t* csz, size_t* next) -{ - size_t he = ri; - size_t hex_digits = 0; - size_t v = 0; - int parsed = 0; - - while (he + 1 < raw_len && !(raw[he] == '\r' && raw[he+1] == '\n')) { - ++he; - } - - if (he + 1 >= raw_len) - return 1; - - for (size_t k = ri; k < he; ++k) { - char c = (char)raw[k]; - if (c == ';') - break; /* chunk-ext */ - - int d = (c >= '0' && c <= '9') ? c - '0' - : (c >= 'a' && c <= 'f') ? c - 'a' + 10 - : (c >= 'A' && c <= 'F') ? c - 'A' + 10 : -1; - if (d < 0 || ++hex_digits > 8) - return -1; - - v = (v << 4) | (size_t)d; - parsed = 1; - } - if (parsed == 0) - return -1; - - *csz = v; - *next = he + 2; - - return 0; -} - -/* Walks the framing, since "0\r\n" also occurs in chunk sizes and data. - * Returns 1 once the last chunk and its trailers have arrived or the framing - * is malformed (the decode pass rejects it), 0 when more bytes are needed. */ -static int chunked_body_complete(const uint8_t* raw, size_t raw_len) -{ - size_t ri = 0; - size_t ls = 0; - - while (ri < raw_len) { - size_t csz = 0; - size_t next = 0; - int r = read_chunk_size(raw, raw_len, ri, &csz, &next); - if (r > 0) - return 0; - if (r < 0) - return 1; - - ri = next; - if (csz == 0) { - /* RFC 7230 section 4.1: consume trailers through the blank - * line, or the leftover CRLF corrupts the next request. */ - while (ri < raw_len) { - ls = ri; - while (ri + 1 < raw_len && - !(raw[ri] == '\r' && raw[ri + 1] == '\n')) { - ++ri; - } - - if (ri + 1 >= raw_len) - return 0; - if (ri == ls) - return 1; /* blank line terminates the trailers */ - - ri += 2; - } - - return 0; - } - - if (raw_len - ri < 2 || csz > raw_len - ri - 2) - return 0; - - ri += csz + 2; - } - - return 0; -} - static int parse_request_in(WolfCertServer* s, int fd, EstRequest* out, void* heap, char* buf, size_t buf_sz) { @@ -266,6 +178,9 @@ static int parse_request_in(WolfCertServer* s, int fd, EstRequest* out, if (sp2 == NULL) return WOLFCERT_ERR_PROTOCOL; + WolfCertReqFraming fr; + wolfcert_server_framing_init(&fr, sp2 + 1, (size_t)(line + llen - sp2 - 1)); + size_t plen = (size_t)(sp2 - sp1 - 1); if (plen >= sizeof(out->path)) return WOLFCERT_ERR_PROTOCOL; @@ -273,21 +188,18 @@ static int parse_request_in(WolfCertServer* s, int fd, EstRequest* out, memcpy(out->path, sp1 + 1, plen); out->path[plen] = '\0'; - int chunked = 0; while (read_line(&p, end, &line, &llen) == 0 && llen > 0) { const char* v; size_t vlen; + int rc; if (wolfcert_http_hdr_split(line, llen, &v, &vlen) != 0) return WOLFCERT_ERR_PROTOCOL; - if (wolfcert_http_hdr_is(line, llen, "Content-Length")) { - out->content_length = (size_t)strtoul(v, NULL, 10); - } - else if (wolfcert_http_hdr_is(line, llen, "Transfer-Encoding")) { - if (vlen >= 7 && wolfcert_ascii_ncasecmp(v, "chunked", 7) == 0) - chunked = 1; - } - else if (wolfcert_http_hdr_is(line, llen, "Authorization")) { + + rc = wolfcert_server_framing_hdr(&fr, line, llen, v, vlen); + if (rc < 0) + return rc; + if (rc == 0 && wolfcert_http_hdr_is(line, llen, "Authorization")) { if (out->auth_header != NULL) return WOLFCERT_ERR_PROTOCOL; @@ -297,103 +209,17 @@ static int parse_request_in(WolfCertServer* s, int fd, EstRequest* out, out->auth_header[vlen] = '\0'; } } - else if (wolfcert_http_hdr_is(line, llen, "Connection")) { - if (vlen >= 5 && wolfcert_ascii_ncasecmp(v, "close", 5) == 0) - out->connection_close = 1; - } } + out->content_length = fr.content_length; + out->connection_close = wolfcert_server_framing_close(&fr); + size_t body_have = (size_t)(end - p); - if (chunked) { + if (fr.chunked) { /* globalsign's estclient always sends chunked request bodies. */ - static const size_t BODY_CAP = 1 * 1024 * 1024; - uint8_t* raw = NULL; - size_t raw_len = 0, raw_cap = 0; - if (body_have > 0) { - raw = (uint8_t*)WOLFCERT_XMALLOC(body_have, heap); - if (raw == NULL) - return WOLFCERT_ERR_MEMORY; - - memcpy(raw, p, body_have); - raw_len = body_have; - raw_cap = body_have; - } - - while (!chunked_body_complete(raw, raw_len)) { - size_t grow = raw_len < 2048 ? 2048 : raw_len; - if (raw_len + grow > BODY_CAP + 64 * 1024) { - WOLFCERT_XFREE(raw, heap); - return WOLFCERT_ERR_PROTOCOL; - } - - uint8_t* nb = (uint8_t*)WOLFCERT_XREALLOC(raw, raw_len + grow, heap); - if (nb == NULL) { - WOLFCERT_XFREE(raw, heap); - return WOLFCERT_ERR_MEMORY; - } - - raw = nb; - raw_cap = raw_len + grow; - ssize_t r = wolfcert_io_recv(s, fd, raw + raw_len, raw_cap - raw_len); - if (r <= 0) { - WOLFCERT_XFREE(raw, heap); - return WOLFCERT_ERR_IO; - } - - raw_len += (size_t)r; - } - - uint8_t* body = (uint8_t*)WOLFCERT_XMALLOC(raw_len + 1, heap); - if (body == NULL) { - WOLFCERT_XFREE(raw, heap); - return WOLFCERT_ERR_MEMORY; - } - - size_t body_sz = 0, ri = 0; - while (ri < raw_len) { - size_t csz = 0; - size_t next = 0; - int r = read_chunk_size(raw, raw_len, ri, &csz, &next); - if (r > 0) - break; - if (r < 0) { - WOLFCERT_XFREE(body, heap); - WOLFCERT_XFREE(raw, heap); - return WOLFCERT_ERR_PROTOCOL; - } - - ri = next; - if (csz == 0) - break; - - if (csz > raw_len - ri || csz > BODY_CAP - body_sz) { - WOLFCERT_XFREE(body, heap); - WOLFCERT_XFREE(raw, heap); - return WOLFCERT_ERR_PROTOCOL; - } - - memcpy(body + body_sz, raw + ri, csz); - body_sz += csz; - ri += csz; - if (ri == raw_len) - break; - - if (ri + 2 > raw_len || - raw[ri] != '\r' || raw[ri + 1] != '\n') { - WOLFCERT_XFREE(body, heap); - WOLFCERT_XFREE(raw, heap); - return WOLFCERT_ERR_PROTOCOL; - } - - ri += 2; - } - - body[body_sz] = '\0'; - WOLFCERT_XFREE(raw, heap); - out->body = body; - out->body_len = body_sz; - - return WOLFCERT_OK; + return wolfcert_server_read_chunked(s, fd, p, body_have, + 1 * 1024 * 1024, &out->body, + &out->body_len, heap); } if (out->content_length > 0) { diff --git a/src/http.c b/src/http.c index 1df6214..b427340 100644 --- a/src/http.c +++ b/src/http.c @@ -859,21 +859,26 @@ static int read_headers(WolfCertConn* c, DynBuf* rx) } } -/* Parse the chunk-size line at raw[ri..]; *next lands past its CRLF. Returns 0 - * ok, 1 if the CRLF has not arrived, -1 if malformed or over 0xFFFFFFFF. */ -static int read_chunk_size(const uint8_t* raw, size_t raw_len, size_t ri, - size_t* csz, size_t* next) +/* Advance *at to the next CRLF; 1 if none yet, with *at where to resume. */ +static int find_crlf(const uint8_t* raw, size_t raw_len, size_t* at) { - size_t he = ri; - size_t v = 0; - int parsed = 0; + size_t i = *at; - while (he + 1 < raw_len && !(raw[he] == '\r' && raw[he+1] == '\n')) { - ++he; + while (i + 1 < raw_len && !(raw[i] == '\r' && raw[i + 1] == '\n')) { + ++i; } + *at = i; + + return i + 1 < raw_len ? 0 : 1; +} - if (he + 1 >= raw_len) - return 1; +/* Parse the chunk-size line raw[ri..he). Returns 0 ok, -1 if malformed or over + * 0xFFFFFFFF. */ +static int parse_chunk_size(const uint8_t* raw, size_t ri, size_t he, + size_t* csz) +{ + size_t v = 0; + int parsed = 0; for (size_t k = ri; k < he; ++k) { char c = (char)raw[k]; @@ -904,58 +909,54 @@ static int read_chunk_size(const uint8_t* raw, size_t raw_len, size_t ri, if (parsed == 0) return -1; /* empty chunk-size line */ - *csz = v; - *next = he + 2; + *csz = v; return 0; } -/* Walk chunk framing over the bytes received so far. Returns 1 at a - * zero-length chunk parsed on a chunk-header boundary whose trailer - * section is closed, 0 when more bytes are needed, -1 when malformed. */ -static int chunked_body_complete(const uint8_t* raw, size_t raw_len) +WOLFCERT_TEST_VIS int wolfcert_http_chunked_complete(const uint8_t* raw, + size_t raw_len, + WolfCertChunkScan* st) { - size_t ri = 0; - size_t ls = 0; + while (st->pos < raw_len) { + size_t ri = st->pos; + size_t csz = st->chunk; - while (ri < raw_len) { - size_t csz = 0; - size_t next = 0; - int r = read_chunk_size(raw, raw_len, ri, &csz, &next); - if (r > 0) - return 0; - if (r < 0) - return -1; + if (csz > 0) { + if (raw_len - ri < 2 || csz > raw_len - ri - 2) + return 0; /* payload and its CRLF not yet received */ - ri = next; - if (csz == 0) { - /* Consume the trailer section through its blank line; stopping - * at "0\r\n" desyncs keep-alive. */ - while (ri < raw_len) { - ls = ri; - while (ri + 1 < raw_len && - !(raw[ri] == '\r' && raw[ri + 1] == '\n')) { - ++ri; - } + if (raw[ri + csz] != '\r' || raw[ri + csz + 1] != '\n') + return -1; /* no CRLF closing the chunk payload */ - if (ri + 1 >= raw_len) - return 0; /* trailer line CRLF not fully received */ - if (ri == ls) - return 1; /* blank line terminates the trailers */ - - ri += 2; - } - - return 0; + st->pos = ri + csz + 2; + st->chunk = 0; + continue; } - if (raw_len - ri < 2 || csz > raw_len - ri - 2) - return 0; /* chunk payload plus trailing CRLF not yet received */ + if (st->crlf < ri) + st->crlf = ri; + if (find_crlf(raw, raw_len, &st->crlf) != 0) + return 0; - if (raw[ri + csz] != '\r' || raw[ri + csz + 1] != '\n') - return -1; /* no CRLF closing the chunk payload */ + if (st->in_trailers) { + if (st->crlf == ri) + return 1; /* blank line terminates the trailers */ + } + else if (parse_chunk_size(raw, ri, st->crlf, &st->chunk) != 0 || + (st->max != 0 && st->chunk > st->max - st->total)) { + return -1; + } + else if (st->chunk == 0) { + /* Consume the trailer section through its blank line; stopping + * at "0\r\n" desyncs keep-alive. */ + st->in_trailers = 1; + } + else { + st->total += st->chunk; + } - ri += csz + 2; + st->pos = st->crlf + 2; } return 0; @@ -999,9 +1000,9 @@ static int check_trailers(const uint8_t* raw, size_t raw_len) return WOLFCERT_ERR_PROTOCOL; } -static int decode_chunked(const uint8_t* in, size_t in_len, - uint8_t** out, size_t* out_len, - size_t max_bytes, void* heap) +int wolfcert_http_chunked_decode(const uint8_t* in, size_t in_len, + uint8_t** out, size_t* out_len, + size_t max_bytes, void* heap) { DynBuf body = { .heap = heap, .max = max_bytes }; size_t p = 0; @@ -1009,14 +1010,15 @@ static int decode_chunked(const uint8_t* in, size_t in_len, while (p < in_len) { size_t clen = 0; - size_t next = 0; + size_t he = p; - if (read_chunk_size(in, in_len, p, &clen, &next) != 0) { + if (find_crlf(in, in_len, &he) != 0 || + parse_chunk_size(in, p, he, &clen) != 0) { WOLFCERT_XFREE(body.buf, heap); return WOLFCERT_ERR_PROTOCOL; } - p = next; + p = he + 2; if (clen == 0) { rc = check_trailers(in + p, in_len - p); if (rc != WOLFCERT_OK) { @@ -1074,11 +1076,13 @@ static int read_body(WolfCertConn* c, DynBuf* rx, size_t body_start, return WOLFCERT_ERR_PROTOCOL; if (chunked) { + WolfCertChunkScan scan = { .max = max_bytes }; int framed; for (;;) { - framed = chunked_body_complete(rx->buf + body_start, - rx->len - body_start); + framed = wolfcert_http_chunked_complete(rx->buf + body_start, + rx->len - body_start, + &scan); if (framed != 0) break; @@ -1095,8 +1099,9 @@ static int read_body(WolfCertConn* c, DynBuf* rx, size_t body_start, if (framed < 0) return WOLFCERT_ERR_PROTOCOL; - return decode_chunked(rx->buf + body_start, rx->len - body_start, - out, out_len, max_bytes, heap); + return wolfcert_http_chunked_decode(rx->buf + body_start, + rx->len - body_start, + out, out_len, max_bytes, heap); } if (length >= 0) { diff --git a/src/internal.h b/src/internal.h index 9a3d0d8..52a46c5 100644 --- a/src/internal.h +++ b/src/internal.h @@ -153,6 +153,32 @@ WOLFCERT_TEST_VIS void wolfcert_sock_nosigpipe(int fd); WOLFCERT_TEST_VIS int64_t wolfcert_mono_ms(void); +/* Message framing and persistence of one request to the test servers. */ +typedef struct { + size_t content_length; + int http10; + int chunked; + int cl_seen; + int keep_alive; + int close; +} WolfCertReqFraming; + +void wolfcert_server_framing_init(WolfCertReqFraming* f, const char* ver, + size_t ver_len); +/* 1 when the line is Content-Length, Transfer-Encoding or Connection, 0 for + * any other field, WOLFCERT_ERR_PROTOCOL for a coding other than chunked. */ +int wolfcert_server_framing_hdr(WolfCertReqFraming* f, const char* line, + size_t llen, const char* v, size_t vlen); +/* 1 when the connection closes after the response. */ +int wolfcert_server_framing_close(const WolfCertReqFraming* f); + +/* Receive the rest of a chunked request body that starts with have[0..have_len) + * and decode it, rejecting a body over max_bytes. */ +WOLFCERT_TEST_VIS int wolfcert_server_read_chunked(WolfCertServer* srv, int fd, + const void* have, size_t have_len, + size_t max_bytes, uint8_t** body, + size_t* body_len, void* heap); + WOLFCERT_API const WolfCertServerOps* wolfcert_est_server_ops(void); WOLFCERT_API const WolfCertServerOps* wolfcert_scep_server_ops(void); @@ -226,6 +252,26 @@ WOLFCERT_TEST_VIS void wolfcert_http_url_free (WolfCertUrl* u); WOLFCERT_TEST_VIS int wolfcert_http_url_origin(const WolfCertUrl* u, void* heap, char** out_origin); +/* Resume point for wolfcert_http_chunked_complete(); zero it before the first + * call, setting max to cap the decoded body (0 for no cap). */ +typedef struct { + size_t pos; + size_t crlf; + size_t chunk; + size_t max; + size_t total; + int in_trailers; +} WolfCertChunkScan; + +/* 1 once the last chunk and its trailer section have arrived, 0 when more + * bytes are needed, -1 when the framing is malformed. */ +WOLFCERT_TEST_VIS int wolfcert_http_chunked_complete(const uint8_t* raw, + size_t raw_len, + WolfCertChunkScan* st); +int wolfcert_http_chunked_decode(const uint8_t* in, size_t in_len, + uint8_t** out, size_t* out_len, + size_t max_bytes, void* heap); + /* strncasecmp() with ASCII-only case folding */ int wolfcert_ascii_ncasecmp(const char* a, const char* b, size_t n); /* 1 when the header line's field name is `name`, followed directly by ':'. */ diff --git a/src/scep/scep_server.c b/src/scep/scep_server.c index 7fa50d1..cd7471e 100644 --- a/src/scep/scep_server.c +++ b/src/scep/scep_server.c @@ -198,6 +198,9 @@ static int read_request(WolfCertServer* s, int fd, ScepRequest* out, void* heap) if (sp2 == NULL) return WOLFCERT_ERR_PROTOCOL; + WolfCertReqFraming fr; + wolfcert_server_framing_init(&fr, sp2 + 1, (size_t)(line + llen - sp2 - 1)); + /* Split the request-target in place inside rawbuf. */ *sp2 = '\0'; out->path = sp1 + 1; @@ -214,18 +217,20 @@ static int read_request(WolfCertServer* s, int fd, ScepRequest* out, void* heap) const char* v; size_t vlen; - if (wolfcert_http_hdr_split(line, llen, &v, &vlen) != 0) + if (wolfcert_http_hdr_split(line, llen, &v, &vlen) != 0 || + wolfcert_server_framing_hdr(&fr, line, llen, v, vlen) < 0) return WOLFCERT_ERR_PROTOCOL; - if (wolfcert_http_hdr_is(line, llen, "Content-Length")) { - out->content_length = (size_t)strtoul(v, NULL, 10); - } - else if (wolfcert_http_hdr_is(line, llen, "Connection")) { - if (vlen >= 5 && wolfcert_ascii_ncasecmp(v, "close", 5) == 0) - out->connection_close = 1; - } } + out->content_length = fr.content_length; + out->connection_close = wolfcert_server_framing_close(&fr); + size_t have = (size_t)(end - p); + if (fr.chunked) { + return wolfcert_server_read_chunked(s, fd, p, have, 1 * 1024 * 1024, + &out->body, &out->body_len, heap); + } + if (out->content_length > 0) { if (out->content_length > 1 * 1024 * 1024) return WOLFCERT_ERR_PROTOCOL; @@ -1095,7 +1100,7 @@ static int handle_pki_op_get(WolfCertServer* s, int fd, ScepRequest* req) return rc; } - /* Free any body a bogus Content-Length made read_request allocate. */ + /* Free any body sent with a GET. */ WOLFCERT_XFREE(req->body, req->heap); req->body = der.data; req->body_len = der.len; diff --git a/src/server.c b/src/server.c index 9a3c6d1..c04c6f0 100644 --- a/src/server.c +++ b/src/server.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -156,6 +157,157 @@ ssize_t wolfcert_io_send(WolfCertServer* srv, int fd, const void* buf, size_t le return r; } +static void ows_trim(const char** v, size_t* vlen) +{ + while (*vlen > 0 && (**v == ' ' || **v == '\t')) { + (*v)++; + (*vlen)--; + } + while (*vlen > 0 && ((*v)[*vlen - 1] == ' ' || (*v)[*vlen - 1] == '\t')) + (*vlen)--; +} + +static int te_chunked(const char* v, size_t vlen) +{ + ows_trim(&v, &vlen); + return vlen == 7 && wolfcert_ascii_ncasecmp(v, "chunked", 7) == 0; +} + +/* Connection is a comma-separated option list (RFC 9110 section 7.6.1). */ +static void conn_options(WolfCertReqFraming* f, const char* v, size_t vlen) +{ + const char* end = v + vlen; + + while (v < end) { + const char* t = v; + size_t tlen; + + while (v < end && *v != ',') + v++; + tlen = (size_t)(v - t); + ows_trim(&t, &tlen); + + if (tlen == 5 && wolfcert_ascii_ncasecmp(t, "close", 5) == 0) + f->close = 1; + else if (tlen == 10 && + wolfcert_ascii_ncasecmp(t, "keep-alive", 10) == 0) + f->keep_alive = 1; + + if (v < end) + v++; + } +} + +void wolfcert_server_framing_init(WolfCertReqFraming* f, const char* ver, + size_t ver_len) +{ + memset(f, 0, sizeof(*f)); + f->http10 = ver_len == 8 && memcmp(ver, "HTTP/1.0", 8) == 0; +} + +int wolfcert_server_framing_hdr(WolfCertReqFraming* f, const char* line, + size_t llen, const char* v, size_t vlen) +{ + if (wolfcert_http_hdr_is(line, llen, "Content-Length")) { + f->content_length = (size_t)strtoul(v, NULL, 10); + f->cl_seen = 1; + } + else if (wolfcert_http_hdr_is(line, llen, "Transfer-Encoding")) { + /* Any coding but a lone chunked is refused with 400. */ + if (f->chunked || !te_chunked(v, vlen)) + return WOLFCERT_ERR_PROTOCOL; + + f->chunked = 1; + } + else if (wolfcert_http_hdr_is(line, llen, "Connection")) { + conn_options(f, v, vlen); + } + else { + return 0; + } + + return 1; +} + +int wolfcert_server_framing_close(const WolfCertReqFraming* f) +{ + /* RFC 9112 section 9.3: HTTP/1.0 persists only with keep-alive. Section + * 6.1: close after TE with Content-Length, or TE on HTTP/1.0. */ + return f->close || (f->http10 && !f->keep_alive) || + (f->chunked && (f->cl_seen || f->http10)); +} + +WOLFCERT_TEST_VIS int wolfcert_server_read_chunked(WolfCertServer* srv, int fd, + const void* have, size_t have_len, + size_t max_bytes, uint8_t** body, + size_t* body_len, void* heap) +{ + size_t raw_max = max_bytes + 64 * 1024; /* room for the framing */ + uint8_t* raw = NULL; + size_t raw_len = 0; + size_t raw_cap = 0; + WolfCertChunkScan scan = { .max = max_bytes }; + int framed; + int rc; + + if (have_len > raw_max) + return WOLFCERT_ERR_PROTOCOL; + + if (have_len > 0) { + raw = (uint8_t*)WOLFCERT_XMALLOC(have_len, heap); + if (raw == NULL) + return WOLFCERT_ERR_MEMORY; + + memcpy(raw, have, have_len); + raw_len = have_len; + raw_cap = have_len; + } + + while ((framed = wolfcert_http_chunked_complete(raw, raw_len, + &scan)) == 0) { + ssize_t r; + + if (raw_len == raw_cap) { + size_t grow = raw_cap < 2048 ? 2048 : raw_cap; + uint8_t* nb; + + if (grow > raw_max - raw_cap) + grow = raw_max - raw_cap; + if (grow == 0) { + WOLFCERT_XFREE(raw, heap); + return WOLFCERT_ERR_PROTOCOL; + } + + nb = (uint8_t*)WOLFCERT_XREALLOC(raw, raw_cap + grow, heap); + if (nb == NULL) { + WOLFCERT_XFREE(raw, heap); + return WOLFCERT_ERR_MEMORY; + } + + raw = nb; + raw_cap += grow; + } + + r = wolfcert_io_recv(srv, fd, raw + raw_len, raw_cap - raw_len); + if (r <= 0) { + WOLFCERT_XFREE(raw, heap); + return WOLFCERT_ERR_IO; + } + + raw_len += (size_t)r; + } + + if (framed < 0) + rc = WOLFCERT_ERR_PROTOCOL; + else + rc = wolfcert_http_chunked_decode(raw, raw_len, body, body_len, + max_bytes, heap); + + WOLFCERT_XFREE(raw, heap); + + return rc; +} + static int tls_setup(WolfCertServer* s, const WolfCertServerCfgSrv* cfg) { int rc = WOLFCERT_OK; diff --git a/tests/integration/test_est_chunked_robustness.c b/tests/integration/test_est_chunked_robustness.c index e48008d..12ffa35 100644 --- a/tests/integration/test_est_chunked_robustness.c +++ b/tests/integration/test_est_chunked_robustness.c @@ -185,6 +185,159 @@ static int accept_wellformed_chunks(uint16_t port) return 0; } +/* RFC 9112 section 7.1: legal framings reach the CSR check, malformed ones + * fail at the framing. */ +static int check_chunk_framing(uint16_t port) +{ + static const struct { + const char* framing; + const char* want; + } cases[] = { + { "000000004\r\nAAAA\r\n0\r\n\r\n", "Bad CSR" }, + { "4 ;n=v\r\nAAAA\r\n0\r\n\r\n", "Bad CSR" }, + { "4\t;n=v\r\nAAAA\r\n0\r\n\r\n", "Bad CSR" }, + { "4\r\nAAAA\r\n0\r\nX-T: 1\r\n\r\n", "Bad CSR" }, + { "4 5\r\nAAAA\r\n0\r\n\r\n", "Bad Request" }, + { "4\r\nAAAA\r\n0\r\ngarbage\r\n\r\n", "Bad Request" }, + { "4\r\nAAAA\r\n0\r\n: v\r\n\r\n", "Bad Request" }, + }; + static const char hdr[] = + "POST /.well-known/est/simpleenroll HTTP/1.1\r\n" + "Host: 127.0.0.1\r\n" + "Content-Type: application/pkcs10\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n"; + char req[256]; + char status[128]; + int failed = 0; + size_t i; + + for (i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { + int n = snprintf(req, sizeof(req), "%s%s", hdr, cases[i].framing); + REQUIRE(n > 0 && (size_t)n < sizeof(req)); + memset(status, 0, sizeof(status)); + send_and_read_status(port, req, (size_t)n, status, sizeof(status)); + if (strstr(status, cases[i].want) == NULL) { + fprintf(stderr, "FAIL framing case %zu: want '%s', got '%.40s'\n", + i, cases[i].want, status); + failed = 1; + } + } + return failed; +} + +/* Send req over a new TLS connection and read the response head into resp. */ +static int read_head(uint16_t port, const char* req, size_t len, char* resp, + size_t cap) +{ + TestTlsConn c; + size_t n = 0; + + resp[0] = '\0'; + REQUIRE(test_tls_connect(&c, port, g_tls_cert, g_tls_cert_len) == 0); + REQUIRE(test_tls_write(&c, req, len) == 0); + while (n + 1 < cap) { + int r = test_tls_read(&c, resp + n, cap - 1 - n); + if (r <= 0) + break; + n += (size_t)r; + resp[n] = '\0'; + if (strstr(resp, "\r\n\r\n") != NULL) + break; + } + resp[n] = '\0'; + test_tls_close(&c); + return 0; +} + +/* Only an exact "chunked" coding is decoded, and a request framed by both + * Transfer-Encoding and Content-Length, or sent as HTTP/1.0, closes. */ +static int check_transfer_encoding(uint16_t port) +{ + static const struct { + const char* headers; + const char* want; + const char* conn; + const char* version; + } cases[] = { + { "Transfer-Encoding: chunked \r\n", + "Bad CSR", "keep-alive", "1.1" }, + { "Transfer-Encoding: chunkedX\r\n", + "Bad Request", "close", "1.1" }, + { "Transfer-Encoding: gzip, chunked\r\n", + "Bad Request", "close", "1.1" }, + { "Transfer-Encoding: chunked\r\n" + "Transfer-Encoding: chunked\r\n", + "Bad Request", "close", "1.1" }, + { "Transfer-Encoding: chunked\r\n" + "Content-Length: 4\r\n", + "Bad CSR", "close", "1.1" }, + { "Transfer-Encoding: chunked\r\n", + "Bad CSR", "close", "1.0" }, + { "Connection: keep-alive\r\n" + "Transfer-Encoding: chunked\r\n", + "Bad CSR", "close", "1.0" }, + }; + char req[512]; + char resp[1024]; + char conn[32]; + size_t i; + int failed = 0; + + for (i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { + int len = snprintf(req, sizeof(req), + "POST /.well-known/est/simpleenroll HTTP/%s\r\n" + "Host: 127.0.0.1\r\n" + "Content-Type: application/pkcs10\r\n" + "%s\r\n4\r\nAAAA\r\n0\r\n\r\n", + cases[i].version, cases[i].headers); + REQUIRE(len > 0 && (size_t)len < sizeof(req)); + REQUIRE(read_head(port, req, (size_t)len, resp, sizeof(resp)) == 0); + snprintf(conn, sizeof(conn), "Connection: %s\r\n", cases[i].conn); + if (strstr(resp, cases[i].want) == NULL || + strstr(resp, conn) == NULL) { + fprintf(stderr, "FAIL coding case %zu: want '%s' and '%s', " + "got '%.60s'\n", i, cases[i].want, cases[i].conn, + resp); + failed = 1; + } + } + return failed; +} + +/* An HTTP/1.0 request closes the connection unless it asks for keep-alive. */ +static int check_http10_persistence(uint16_t port) +{ + static const struct { + const char* headers; + const char* want; + } cases[] = { + { "", "Connection: close" }, + { "Connection: keep-alive\r\n", "Connection: keep-alive" }, + { "Connection: TE, keep-alive\r\n", "Connection: keep-alive" }, + { "Connection: keep-alive, close\r\n", "Connection: close" }, + { "Connection: keep-alivex\r\n", "Connection: close" }, + }; + char req[256]; + char resp[1024]; + size_t i; + int failed = 0; + + for (i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { + int len = snprintf(req, sizeof(req), + "GET /.well-known/est/cacerts HTTP/1.0\r\n" + "Host: 127.0.0.1\r\n%s\r\n", cases[i].headers); + REQUIRE(len > 0 && (size_t)len < sizeof(req)); + REQUIRE(read_head(port, req, (size_t)len, resp, sizeof(resp)) == 0); + if (strstr(resp, cases[i].want) == NULL) { + fprintf(stderr, "FAIL HTTP/1.0 case %zu: want '%s', got '%.40s'\n", + i, cases[i].want, resp); + failed = 1; + } + } + return failed; +} + /* The chunk-size line "10\r\n" contains "0\r\n", so a framer that scans for * the terminator stops early and answers "Bad Request" instead of "Bad CSR". */ static int accept_multisegment_chunked_body(uint16_t port) @@ -481,6 +634,12 @@ int main(void) rc = reject_corrupt_chunk_trailer(port); if (rc == 0) rc = accept_wellformed_chunks(port); + if (rc == 0) + rc = check_chunk_framing(port); + if (rc == 0) + rc = check_transfer_encoding(port); + if (rc == 0) + rc = check_http10_persistence(port); if (rc == 0) rc = accept_multisegment_chunked_body(port); if (rc == 0) diff --git a/tests/integration/test_scep_roundtrip.c b/tests/integration/test_scep_roundtrip.c index 890cda4..0c0c3d9 100644 --- a/tests/integration/test_scep_roundtrip.c +++ b/tests/integration/test_scep_roundtrip.c @@ -74,10 +74,40 @@ static int write_all_fd(int fd, const uint8_t* buf, size_t len) return 0; } +#define RAW_KEEP_ALIVE 0x1 +#define RAW_CHUNKED 0x2 + +/* Up to two chunks with a zero-padded size and a chunk-ext, then a trailer. */ +static int write_chunked_fd(int fd, const uint8_t* body, size_t body_len) +{ + static const char TRAILER[] = "0\r\nX-T: 1\r\n\r\n"; + size_t half = body_len >= 2 ? body_len / 2 : body_len; + char line[32]; + int n; + + if (half > 0) { + n = snprintf(line, sizeof(line), "%09zx ;x=1\r\n", half); + if (write_all_fd(fd, (const uint8_t*)line, (size_t)n) != 0 || + write_all_fd(fd, body, half) != 0 || + write_all_fd(fd, (const uint8_t*)"\r\n", 2) != 0) + return -1; + } + + if (body_len > half) { + n = snprintf(line, sizeof(line), "%zx\r\n", body_len - half); + if (write_all_fd(fd, (const uint8_t*)line, (size_t)n) != 0 || + write_all_fd(fd, body + half, body_len - half) != 0 || + write_all_fd(fd, (const uint8_t*)"\r\n", 2) != 0) + return -1; + } + + return write_all_fd(fd, (const uint8_t*)TRAILER, sizeof(TRAILER) - 1); +} + /* Sends hdr and body to the loopback server and reads the reply to EOF. * Returns the status code, or -1 on no reply. out_body is caller-freed. */ static int raw_exchange(uint16_t port, const char* hdr, size_t hdr_len, - const uint8_t* body, size_t body_len, + const uint8_t* body, size_t body_len, int chunked, uint8_t** out_body, size_t* out_body_len) { struct sockaddr_in addr; @@ -115,7 +145,9 @@ static int raw_exchange(uint16_t port, const char* hdr, size_t hdr_len, } if (write_all_fd(fd, (const uint8_t*)hdr, hdr_len) != 0 || - (body_len > 0 && write_all_fd(fd, body, body_len) != 0)) { + (chunked && write_chunked_fd(fd, body, body_len) != 0) || + (!chunked && body_len > 0 && + write_all_fd(fd, body, body_len) != 0)) { close(fd); return -1; } @@ -171,17 +203,27 @@ static int raw_exchange(uint16_t port, const char* hdr, size_t hdr_len, * branches the client API cannot produce. out_body, when set, is caller-freed. */ static int raw_http_req(uint16_t port, const char* method, const char* target, const char* content_type, - const uint8_t* body, size_t body_len, int persistent, + const uint8_t* body, size_t body_len, int flags, uint8_t** out_body, size_t* out_body_len) { + const char* conn = (flags & RAW_KEEP_ALIVE) ? "keep-alive" : "close"; + int chunked = body != NULL && (flags & RAW_CHUNKED); char hdr[512]; int n; - if (body != NULL) + if (chunked) + n = snprintf(hdr, sizeof(hdr), + "%s %s HTTP/1.1\r\nHost: 127.0.0.1\r\nConnection: %s\r\n" + "%s%s%sTransfer-Encoding: chunked\r\n\r\n", + method, target, conn, + content_type != NULL ? "Content-Type: " : "", + content_type != NULL ? content_type : "", + content_type != NULL ? "\r\n" : ""); + else if (body != NULL) n = snprintf(hdr, sizeof(hdr), "%s %s HTTP/1.1\r\nHost: 127.0.0.1\r\nConnection: %s\r\n" "%s%s%sContent-Length: %zu\r\n\r\n", - method, target, persistent ? "keep-alive" : "close", + method, target, conn, content_type != NULL ? "Content-Type: " : "", content_type != NULL ? content_type : "", content_type != NULL ? "\r\n" : "", @@ -189,7 +231,7 @@ static int raw_http_req(uint16_t port, const char* method, const char* target, else n = snprintf(hdr, sizeof(hdr), "%s %s HTTP/1.1\r\nHost: 127.0.0.1\r\nConnection: %s\r\n\r\n", - method, target, persistent ? "keep-alive" : "close"); + method, target, conn); if (n < 0 || (size_t)n >= sizeof(hdr)) { if (out_body != NULL && out_body_len != NULL) { *out_body = NULL; @@ -198,14 +240,14 @@ static int raw_http_req(uint16_t port, const char* method, const char* target, return -1; } - return raw_exchange(port, hdr, (size_t)n, body, body_len, + return raw_exchange(port, hdr, (size_t)n, body, body_len, chunked, out_body, out_body_len); } /* Status code of the reply to a hand-written request, or -1 on no reply. */ static int raw_request_status(uint16_t port, const char* req) { - return raw_exchange(port, req, strlen(req), NULL, 0, NULL, NULL); + return raw_exchange(port, req, strlen(req), NULL, 0, 0, NULL, NULL); } static int raw_http_status(uint16_t port, const char* target, const char* body) @@ -1165,7 +1207,8 @@ static int check_required_attrs(WolfCertServer* s, const WolfCertKeyCfg* kcfg, if (raw_http_req(wolfcert_server_port(s), "POST", "/scep?operation=PKIOperation", "application/x-pki-message", - msg.data, msg.len, 1, NULL, NULL) != 400) { + msg.data, msg.len, RAW_KEEP_ALIVE, + NULL, NULL) != 400) { fprintf(stderr, "FAIL %s:%d required-attrs did not reject " "and close ahead of the messageType check\n", __FILE__, __LINE__); @@ -1187,7 +1230,7 @@ static int check_required_attrs(WolfCertServer* s, const WolfCertKeyCfg* kcfg, /* POST a signed PKCSReq whose transactionID `tid` is tagged PrintableString * whatever its bytes, which wolfcert_scep_build_pki_message will not encode. */ static int post_raw_tid(uint16_t port, const uint8_t* signer, size_t signer_len, - const WolfCertBuffer* kder, const char* tid) + const WolfCertBuffer* kder, const char* tid, int flags) { static const byte oid_msg_type[] = { 0x06,0x0A,0x60,0x86,0x48,0x01,0x86,0xF8,0x45,0x01,0x09,0x02 }; @@ -1250,7 +1293,7 @@ static int post_raw_tid(uint16_t port, const uint8_t* signer, size_t signer_len, if (n > 0) st = raw_http_req(port, "POST", "/scep?operation=PKIOperation", - "application/x-pki-message", buf, (size_t)n, 0, + "application/x-pki-message", buf, (size_t)n, flags, &rsp, &rsp_len); free(rsp); @@ -1261,6 +1304,137 @@ static int post_raw_tid(uint16_t port, const uint8_t* signer, size_t signer_len, return st; } +static int connect_loopback(uint16_t port) +{ + struct sockaddr_in addr; + struct timeval tv = { .tv_sec = 5 }; + int fd = socket(AF_INET, SOCK_STREAM, 0); + + if (fd < 0) + return -1; + setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) != 0) { + close(fd); + return -1; + } + return fd; +} + +/* Send req on fd and read one response into buf; returns its status or -1. */ +static int exchange(int fd, const char* req, char* buf, size_t cap) +{ + const char* eoh = NULL; + const char* cl; + size_t need = 0; + size_t n = 0; + ssize_t r; + + if (write_all_fd(fd, (const uint8_t*)req, strlen(req)) != 0) + return -1; + + while (n + 1 < cap && (eoh == NULL || n < need)) { + r = read(fd, buf + n, cap - 1 - n); + if (r <= 0) + break; + n += (size_t)r; + buf[n] = '\0'; + if (eoh == NULL && (eoh = strstr(buf, "\r\n\r\n")) != NULL) { + cl = strstr(buf, "Content-Length:"); + need = (size_t)(eoh + 4 - buf) + + (cl != NULL && cl < eoh ? strtoul(cl + 15, NULL, 10) : 0); + } + } + buf[n] = '\0'; + + if (eoh == NULL || n < need || strncmp(buf, "HTTP/1.1 ", 9) != 0) + return -1; + return atoi(buf + 9); +} + +#define CAPS_REQ "GET /scep?operation=GetCACaps HTTP/1.1\r\nHost: 127.0.0.1\r\n" +#define CAPS_REQ_10 \ + "GET /scep?operation=GetCACaps HTTP/1.0\r\nHost: 127.0.0.1\r\n" + +/* Chunked framing is consumed exactly, bad framing gets 400, and TE with + * Content-Length or HTTP/1.0 without keep-alive closes the connection. */ +static int check_scep_chunked_framing(uint16_t port) +{ + static const char* const kept[] = { + CAPS_REQ "Transfer-Encoding: chunked\r\nContent-Length-Hint: 0\r\n" + "\r\n4 ;x=1\r\nAAAA\r\n0\r\nX-T: 1\r\n\r\n", + CAPS_REQ_10 "Connection: keep-alive\r\n\r\n", + CAPS_REQ_10 "Connection: TE, keep-alive\r\n\r\n", + }; + static const char plain[] = CAPS_REQ "\r\n"; + static const char* const closing[] = { + CAPS_REQ "Transfer-Encoding: chunked\r\nContent-Length: 4\r\n\r\n" + "4\r\nAAAA\r\n0\r\n\r\n", + CAPS_REQ_10 "Transfer-Encoding: chunked\r\n\r\n4\r\nAAAA\r\n0\r\n\r\n", + CAPS_REQ_10 "\r\n", + CAPS_REQ_10 "Connection: keep-alive, close\r\n\r\n", + CAPS_REQ_10 "Connection: keep-alive\r\nTransfer-Encoding: chunked\r\n" + "\r\n4\r\nAAAA\r\n0\r\n\r\n", + }; + static const char* const bad[] = { + CAPS_REQ "Transfer-Encoding: chunked\r\n\r\n4 5\r\nAAAA\r\n0\r\n\r\n", + CAPS_REQ "Transfer-Encoding: chunkedX\r\n\r\n4\r\nAAAA\r\n0\r\n\r\n", + CAPS_REQ "Transfer-Encoding: gzip, chunked\r\n\r\n" + "4\r\nAAAA\r\n0\r\n\r\n", + CAPS_REQ "Transfer-Encoding: chunked\r\nTransfer-Encoding: chunked\r\n" + "\r\n4\r\nAAAA\r\n0\r\n\r\n", + CAPS_REQ "Transfer-Encoding : chunked\r\n\r\n4\r\nAAAA\r\n0\r\n\r\n", + }; + char buf[2048]; + size_t i; + int fails = 0; + int fd; + int st1, st2; + + for (i = 0; i < sizeof(kept) / sizeof(kept[0]); i++) { + fd = connect_loopback(port); + REQUIRE(fd >= 0); + st1 = exchange(fd, kept[i], buf, sizeof(buf)); + st2 = exchange(fd, plain, buf, sizeof(buf)); + close(fd); + if (st1 != 200 || st2 != 200) { + fprintf(stderr, "FAIL keep-alive case %zu: %d then %d\n", i, st1, + st2); + fails++; + } + } + + for (i = 0; i < sizeof(bad) / sizeof(bad[0]); i++) { + fd = connect_loopback(port); + REQUIRE(fd >= 0); + st1 = exchange(fd, bad[i], buf, sizeof(buf)); + close(fd); + if (st1 != 400) { + fprintf(stderr, "FAIL bad framing %zu got %d\n", i, st1); + fails++; + } + } + + for (i = 0; i < sizeof(closing) / sizeof(closing[0]); i++) { + fd = connect_loopback(port); + REQUIRE(fd >= 0); + st1 = exchange(fd, closing[i], buf, sizeof(buf)); + st2 = st1 == 200 && strstr(buf, "Connection: close") != NULL && + read(fd, buf, sizeof(buf)) == 0; + close(fd); + if (!st2) { + fprintf(stderr, "FAIL closing case %zu kept the connection " + "(status %d)\n", i, st1); + fails++; + } + } + + return fails; +} + /* A transactionID that is not a PrintableString gets 400. */ static int check_unprintable_tid(uint16_t port, const WolfCertKeyCfg* kcfg) { @@ -1283,7 +1457,7 @@ static int check_unprintable_tid(uint16_t port, const WolfCertKeyCfg* kcfg) csr.len, &signer, &signer_len, NULL); if (rc == WOLFCERT_OK) { - st = post_raw_tid(port, signer, signer_len, &kder, "tid@1"); + st = post_raw_tid(port, signer, signer_len, &kder, "tid@1", 0); if (st != 400) { fprintf(stderr, "FAIL %s:%d '@' transactionID got status %d\n", __FILE__, __LINE__, st); @@ -1294,7 +1468,7 @@ static int check_unprintable_tid(uint16_t port, const WolfCertKeyCfg* kcfg) /* The control reaches de-enveloping, which answers the junk content with * a signed FAILURE. */ if (rc == WOLFCERT_OK) { - st = post_raw_tid(port, signer, signer_len, &kder, "tid-1"); + st = post_raw_tid(port, signer, signer_len, &kder, "tid-1", 0); if (st != 200) { fprintf(stderr, "FAIL %s:%d control transactionID got status %d\n", __FILE__, __LINE__, st); @@ -1302,6 +1476,16 @@ static int check_unprintable_tid(uint16_t port, const WolfCertKeyCfg* kcfg) } } + if (rc == WOLFCERT_OK) { + st = post_raw_tid(port, signer, signer_len, &kder, "tid-2", + RAW_CHUNKED); + if (st != 200) { + fprintf(stderr, "FAIL %s:%d chunked control got status %d\n", + __FILE__, __LINE__, st); + rc = -1; + } + } + WOLFCERT_XFREE(signer, NULL); wolfcert_buffer_free(&kder); wolfcert_buffer_free(&csr); @@ -1455,7 +1639,8 @@ static int check_malformed_dispatch(uint16_t port, const WolfCertKeyCfg* kcfg, if (rc == WOLFCERT_OK) { if (raw_http_req(port, "POST", "/scep?operation=PKIOperation", "application/x-pki-message", - msg.data, msg.len, 1, NULL, NULL) != 200) { + msg.data, msg.len, RAW_KEEP_ALIVE, + NULL, NULL) != 200) { fprintf(stderr, "FAIL %s:%d dispatch failure did not answer " "and close\n", __FILE__, __LINE__); rc = -1; @@ -1705,6 +1890,7 @@ int main(void) REQUIRE(check_unprintable_tid(wolfcert_server_port(s), &kcfg) == WOLFCERT_OK); + REQUIRE(check_scep_chunked_framing(wolfcert_server_port(s)) == 0); REQUIRE(check_malformed_dispatch(wolfcert_server_port(s), &kcfg, ca_der->buffer, ca_der->length) diff --git a/tests/unit/test_http.c b/tests/unit/test_http.c index 798ef71..235264d 100644 --- a/tests/unit/test_http.c +++ b/tests/unit/test_http.c @@ -1180,6 +1180,77 @@ static int test_chunked_over_max_accumulated(void) return oversize_chunk_case(body); } +#ifdef WOLFCERT_HAVE_SERVER +/* The server's chunked reader decodes a buffered body and enforces its decoded + * cap at the size line, and its raw cap, before it would read the socket. */ +static int test_server_read_chunked_caps(void) +{ + static const char ok[] = "4\r\nabcd\r\n0\r\n\r\n"; + static const char big[] = "11\r\n"; + static const char sum[] = "8\r\n01234567\r\n9\r\n"; + const size_t raw_max = 16 + 64 * 1024; + uint8_t* body = NULL; + size_t body_len = 0; + uint8_t* raw; + int ok_rc, big_rc, sum_rc, full_rc, over_rc, ok_body; + + ok_rc = wolfcert_server_read_chunked(NULL, -1, ok, sizeof(ok) - 1, 16, + &body, &body_len, NULL); + ok_body = body != NULL && body_len == 4 && memcmp(body, "abcd", 4) == 0; + WOLFCERT_XFREE(body, NULL); + body = NULL; + + big_rc = wolfcert_server_read_chunked(NULL, -1, big, sizeof(big) - 1, 16, + &body, &body_len, NULL); + sum_rc = wolfcert_server_read_chunked(NULL, -1, sum, sizeof(sum) - 1, 16, + &body, &body_len, NULL); + + raw = (uint8_t*)malloc(raw_max + 1); + REQUIRE(raw != NULL); + memcpy(raw, "1;", 2); + memset(raw + 2, 'A', raw_max + 1 - 2); + full_rc = wolfcert_server_read_chunked(NULL, -1, raw, raw_max, 16, + &body, &body_len, NULL); + over_rc = wolfcert_server_read_chunked(NULL, -1, raw, raw_max + 1, 16, + &body, &body_len, NULL); + free(raw); + + REQUIRE(ok_rc == WOLFCERT_OK && ok_body); + REQUIRE(big_rc == WOLFCERT_ERR_PROTOCOL); + REQUIRE(sum_rc == WOLFCERT_ERR_PROTOCOL); + REQUIRE(full_rc == WOLFCERT_ERR_PROTOCOL); + REQUIRE(over_rc == WOLFCERT_ERR_PROTOCOL); + REQUIRE(body == NULL); + return 0; +} +#endif + +/* Feeding a chunked body a byte at a time gives the same verdict as whole. */ +static int test_chunked_complete_resume(void) +{ + static const char ok[] = "4;x=1\r\nabcd\r\n10\r\n0123456789abcdef\r\n" + "0\r\nX-A: 1\r\nX-B: 2\r\n\r\n"; + static const char bad[] = "4\r\nabcdX\r\n0\r\n\r\n"; + WolfCertChunkScan st = { 0 }; + size_t n; + int r = 0; + + for (n = 1; n < sizeof(ok) - 1; n++) { + r = wolfcert_http_chunked_complete((const uint8_t*)ok, n, &st); + REQUIRE(r == 0); + } + REQUIRE(wolfcert_http_chunked_complete((const uint8_t*)ok, n, &st) == 1); + + memset(&st, 0, sizeof(st)); + for (n = 1; n < sizeof(bad); n++) { + r = wolfcert_http_chunked_complete((const uint8_t*)bad, n, &st); + if (r != 0) + break; + } + REQUIRE(r == -1 && n == 9); + return 0; +} + int main(void) { /* make check has no per-test timeout, and a framing bug shows as a hang. */ @@ -1188,8 +1259,14 @@ int main(void) REQUIRE(wolfcert_init(NULL) == WOLFCERT_OK); if (test_url_parser()) return 1; +#ifdef WOLFCERT_HAVE_SERVER + if (test_server_read_chunked_caps()) + return 1; +#endif if (test_url_origin()) return 1; + if (test_chunked_complete_resume()) + return 1; if (test_loopback_http()) return 1; if (test_session_retry_after_reset())