Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/est/csr_attrs.c
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ static int der_take_tl(const uint8_t* p, size_t avail,
hdr = 2 + n;
}

if (hdr + len > avail)
if (len > avail - hdr)
return WOLFCERT_ERR_PARSE;

*out_len = len;
Expand Down
30 changes: 18 additions & 12 deletions src/est/est_client.c
Original file line number Diff line number Diff line change
Expand Up @@ -592,12 +592,15 @@ int wolfcert_est_session_simple_enroll_nb(WolfCertEstSession* s,
}

s->in_req = (WolfCertHttpRequest){
.method = "POST", .url = s->in_url,
.content_type = "application/pkcs10",
.accept = "application/pkcs7-mime",
.body = s->in_body.data, .body_len = s->in_body.len,
.max_response_bytes = s->max_body,
.heap = s->heap,
.method = "POST",
.url = s->in_url,
.content_type = "application/pkcs10",
.content_transfer_encoding = "base64",
.accept = "application/pkcs7-mime",
.body = s->in_body.data,
.body_len = s->in_body.len,
.max_response_bytes = s->max_body,
.heap = s->heap,
};
s->in_out = out_cert_pem;
s->in_op = EST_OP_SIMPLE_ENROLL;
Expand Down Expand Up @@ -696,12 +699,15 @@ int wolfcert_est_session_simple_enroll(WolfCertEstSession* s,
}

WolfCertHttpRequest req = {
.method = "POST", .url = url,
.content_type = "application/pkcs10",
.accept = "application/pkcs7-mime",
.body = b64.data, .body_len = b64.len,
.max_response_bytes = s->max_body,
.heap = s->heap,
Comment thread
philljj marked this conversation as resolved.
.method = "POST",
.url = url,
.content_type = "application/pkcs10",
.content_transfer_encoding = "base64",
.accept = "application/pkcs7-mime",
.body = b64.data,
.body_len = b64.len,
.max_response_bytes = s->max_body,
.heap = s->heap,
};
WolfCertHttpResponse resp = { 0 };
rc = wolfcert_http_session_request(s->http, &req, &resp);
Expand Down
171 changes: 116 additions & 55 deletions src/est/est_server.c
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,113 @@ 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..]. On success store the chunk
* length in *csz and the offset just past its terminating CRLF in
* *next. The hex-digit count is capped at 8 so a crafted long size line
* cannot wrap the value past a later bounds check; 8 digits cover every
* chunk we accept (BODY_CAP is 1 MiB). Shared by the completeness scan
* and the decode pass so the two cannot drift apart.
*
* returns 0 on success
* returns 1 when the CRLF ending the size line has not arrived yet
* (need more bytes)
* returns -1 when the line is 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; /* size line not fully received yet */

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'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion, feel free to ignore: this ascii math block is now in 3 separate places in different forms. Might be tidier to refactor to avoid a copy paste mistake or related.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be fixed by the read_chunk_size refactoring

: (c >= 'a' && c <= 'f') ? c - 'a' + 10
: (c >= 'A' && c <= 'F') ? c - 'A' + 10 : -1;
if (d < 0 || ++hex_digits > 8)
return -1; /* bad hex digit or oversized length */

v = (v << 4) | (size_t)d;
parsed = 1;
}
if (parsed == 0)
return -1; /* empty chunk-size line */

*csz = v;
*next = he + 2;

return 0;
}

/* Walk chunk framing over the bytes received so far and report whether
* the message is complete, meaning a genuine zero-length chunk has been
* parsed at a chunk-header boundary. Returns 1 when reading can stop
* (complete, or the framing is malformed and the decode pass below will
* reject it) and 0 when more bytes are still needed. A substring scan
* for "0\r\n" cannot decide this: a chunk-size line that is a multiple
* of 16 and chunk data both contain those bytes. */
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; /* chunk-size line not fully received yet */
if (r < 0)
return 1; /* malformed line - the decode pass rejects it */

/* read_chunk_size guarantees next <= raw_len, so ri stays
* <= raw_len and the raw_len - ri math below cannot underflow. */
ri = next;
if (csz == 0) {
/* Last-chunk marker. RFC 7230: the message ends only after
* the CRLF terminating the trailer section (minimum
* "0\r\n\r\n"). Stopping at "0\r\n" would leave that CRLF
* unread on the socket and corrupt the next request on a
* keep-alive connection. Consume any trailer field lines up
* to the terminating blank line before declaring complete. */
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; /* trailer line CRLF not fully received */
if (ri == ls)
return 1; /* blank line terminates the trailers */

ri += 2; /* skip this trailer field line, scan the next */
}

return 0; /* trailer terminator not yet received */
}

if (raw_len - ri < 2 || csz > raw_len - ri - 2)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the raw_len - ri math need underflow protection?

The while loop checks ri < raw_len, but ri is updated ri = next; mid while loop.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No underflow possible: read_chunk_size guarantees next <= raw_len, so ri <= raw_len always holds, and the raw_len - ri < 2 check short-circuits before the - 2. But I added a comment noting this.

return 0; /* chunk payload plus trailing CRLF not yet received */

ri += csz + 2;
}

return 0;
}

static int parse_request(WolfCertServer* s, int fd, EstRequest* out, void* heap)
{
memset(out, 0, sizeof(*out));
Expand Down Expand Up @@ -266,20 +373,7 @@ static int parse_request(WolfCertServer* s, int fd, EstRequest* out, void* heap)
raw_cap = body_have;
}

int saw_end = 0;
while (!saw_end) {
/* Any "0\r\n" at a chunk-header position marks the end. */
for (size_t i = 0; i + 2 < raw_len; ++i) {
if (raw[i] == '0' && raw[i+1] == '\r' && raw[i+2] == '\n') {
/* Cheap heuristic: treat the last occurrence as the
* terminator. The decode pass below revalidates. */
saw_end = 1;
}
}

if (saw_end)
break;

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);
Expand Down Expand Up @@ -312,51 +406,18 @@ static int parse_request(WolfCertServer* s, int fd, EstRequest* out, void* heap)

size_t body_sz = 0, ri = 0;
while (ri < raw_len) {
/* Locate the end of the chunk-size line. */
size_t he = ri;
while (he + 1 < raw_len && !(raw[he] == '\r' && raw[he+1] == '\n')) {
++he;
}

if (he + 1 >= raw_len)
break;

unsigned long csz = 0;
int parsed = 0;
size_t hex_digits = 0;
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) {
parsed = -1;
break;
}

/* Cap the hex-digit count so a crafted long chunk-size
* line can't silently wrap `csz` to a small value that
* then sneaks past the `ri + csz > raw_len` check. 8
* digits cover every legitimate chunk we'd ever accept
* (BODY_CAP is 1 MiB). */
if (++hex_digits > 8) {
parsed = -1;
break;
}

csz = (csz << 4) | (unsigned long)d;
parsed = 1;
}
if (parsed <= 0) {
size_t csz = 0;
size_t next = 0;
int r = read_chunk_size(raw, raw_len, ri, &csz, &next);
if (r > 0)
break; /* chunk-size line not fully received */
if (r < 0) {
WOLFCERT_XFREE(body, heap);
WOLFCERT_XFREE(raw, heap);
return WOLFCERT_ERR_PROTOCOL;
}

ri = he + 2;
ri = next;
if (csz == 0)
break;

Expand Down Expand Up @@ -663,7 +724,7 @@ static int csr__take_tl(const uint8_t* p, size_t avail,
h = 2 + n;
}

if (h + l > avail)
if (l > avail - h)
return -1;

*len = l;
Expand Down
9 changes: 5 additions & 4 deletions src/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -213,11 +213,12 @@ WOLFCERT_API const WolfCertServerOps* wolfcert_scep_server_ops(void);

#if defined(WOLFCERT_BUILD_TESTING)
/* Test-only fault injection for a started SCEP server: make it emit a CertRep
* without a recipientNonce, and/or sign the CertRep with a throwaway key
* instead of the CA key. Used to drive the client-side rejection paths. Call
* after wolfcert_server_start and before the client request. */
* without a recipientNonce, sign the CertRep with a throwaway key instead of
* the CA key, and/or force the CertRep senderNonce RNG draw to fail. Used to
* drive the client-side rejection and server error paths. Call after
* wolfcert_server_start and before the client request. */
WOLFCERT_TEST_VIS void wolfcert_scep_server_set_faults(WolfCertServer* s,
int omit_recipient_nonce, int sign_with_wrong_key);
int omit_recipient_nonce, int sign_with_wrong_key, int rng_fail);
#endif

/* ---- error reporting --------------------------------------------------- */
Expand Down
50 changes: 43 additions & 7 deletions src/scep/scep_client.c
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,40 @@ static void fill_common(const WolfCertServerCfg* srv, WolfCertHttpRequest* req)

/* ---- GetCACaps ---------------------------------------------------------- */

/* RFC 8894 section 3.5.2: GetCACaps is a newline-delimited list of exact
* capability tokens. Match a whole line, not a substring, so an unknown
* token that merely contains a known one is not mistaken for it. */
static int has_cap(const char* body, size_t len, const char* needle)
{
size_t nl = strlen(needle);
for (size_t i = 0; i + nl <= len; ++i) {
if (strncasecmp(body + i, needle, nl) == 0) {
size_t nl;
size_t i = 0;

/* Guard both pointers before dereferencing either: strlen(needle)
* below and body[] indexing in the scan. `len` needs no separate
* bound - every body[] access is gated by `i < len`, and the
* strncasecmp only fires when the matched token length equals nl,
* so it never reads past body + len. */
if (body == NULL || needle == NULL)
return 0;

nl = strlen(needle);

while (i < len) {
size_t start = i;
size_t tlen;

while (i < len && body[i] != '\n')
++i;

tlen = i - start;
if (tlen > 0 && body[start + tlen - 1] == '\r')
--tlen;

if (tlen == nl && strncasecmp(body + start, needle, nl) == 0)
return 1;
}

if (i < len)
++i;
}

return 0;
Expand Down Expand Up @@ -487,11 +514,20 @@ static int do_scep_round_trip(const WolfCertServerCfg* srv,
return rc;

WC_RNG rng;
wc_InitRng_ex(&rng, heap, WOLFCERT_DEVID_SOFTWARE);
if (wc_InitRng_ex(&rng, heap, WOLFCERT_DEVID_SOFTWARE) != 0) {
wolfcert_buffer_free(&env);
return WOLFCERT_ERR(WOLFCERT_ERR_CRYPTO, "scep",
"RNG init failed for transactionID/nonce");
}

uint8_t txid_gen[16], nonce[16];
wc_RNG_GenerateBlock(&rng, txid_gen, sizeof(txid_gen));
wc_RNG_GenerateBlock(&rng, nonce, sizeof(nonce));
if (wc_RNG_GenerateBlock(&rng, txid_gen, sizeof(txid_gen)) != 0 ||
wc_RNG_GenerateBlock(&rng, nonce, sizeof(nonce)) != 0) {
wc_FreeRng(&rng);
wolfcert_buffer_free(&env);
return WOLFCERT_ERR(WOLFCERT_ERR_CRYPTO, "scep",
"RNG failed generating transactionID/nonce");
}

wc_FreeRng(&rng);

Expand Down
Loading
Loading