Skip to content
Merged
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
72 changes: 45 additions & 27 deletions src/scep/scep_server.c
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,18 @@ static int issue_and_reply(WolfCertServer* s, int fd,
return rc;
}

/* Answer a rejected pkiMessage with a signed CertRep carrying pkiStatus
* FAILURE and failInfo, per RFC 8894 section 3.2.1. */
static int send_pki_failure(WolfCertServer* s, int fd,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The helper clears keep_alive on its 400 fallback but leaves it to the caller on the CertRep path, and the callers are split: handle_pki_op (:802, :816, :832) and handle_enroll (:688, :696) set it, while handle_enroll's queue-full and handle_get_cert_initial's badCertId branches deliberately do not. Every combination is coherent today, but split ownership means a future caller that returns non-OK without the assignment emits Connection: keep-alive on a socket we then close.

The parse-failure branch five lines up (:796) already does exactly that, as do issue_and_reply()'s "Bad CSR" 400 and the 500 sites. All pre-existing, but this PR introduces the invariant and leaves its siblings out of it. Setting keep_alive = 0 inside send_text() for status >= 400 would make it unforgettable. Note also that nothing asserts the header - WolfCertHttpResponse exposes none - so a branch answering with a CertRep and keep_alive still 1 would pass the suite.

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.

Not changed, and I do not think send_text() can carry it: these failure replies go out as HTTP 200 CertReps through send_bin, so a "status >= 400" rule inside send_text never sees them. The call-site assignment is the only thing that makes that header truthful.

I did measure it. On the same rejection over a keep-alive socket: with the assignment the reply says Connection: close, without it Connection: keep-alive — and the server closes either way, because the non-OK return breaks the serve loop. So the flag is redundant for closing and load-bearing for the header, which is a distinction worth a comment; I have not added one yet.

There is now a test that sends over a keep-alive socket and requires the reply plus a clean close. It does not assert the header itself — raw_http_req discards headers — so that gap is real, as you said.

The pre-existing siblings (parse-failure 400, "Bad CSR", the 500s) are untouched. Happy to take them in a follow-up.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Agreed on the mechanism - send_text() can't carry it, since these go out as HTTP 200 through send_bin(). But the round you added doesn't cover the assignment, and I think the PR body currently reads as if it does.

I deleted s->keep_alive = 0 from each new branch in turn and rebuilt: 27/27 green both times. The reason is the one you measured - src/server.c:351 breaks the keep-alive loop on any non-OK serve_fd() return, so the socket closes whether or not the flag is clear, and raw_http_req() discards headers. So the round asserts the return-code path; the header stays untested, exactly as you said in your last paragraph.

Nothing to change in the code. Two small things: the PR body's "Both branches clear keep_alive so the Connection: header matches the close" is worth rewording so it doesn't imply the new test proves it, and the comment you mentioned but haven't added is worth adding - "redundant for closing, load-bearing for the header" is precisely the thing a future editor would delete.

If you want it actually pinned, raw_http_req() returning the Connection: value alongside the status would do it in a couple of lines and would also cover the pre-existing 400/500 siblings once you get to them. Happy for that to be the follow-up rather than this PR.

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.

Both done. The comment is on each branch — "Closed by the non-OK return; the flag is for the header" — which is the sentence a future editor needs before deleting the line. The PR body no longer implies the new round covers the assignment; it now says the tests cover the close and not the header.

Taking the raw_http_req() change as the follow-up, as you suggest.

const uint8_t* tid, size_t tid_len,
const uint8_t* snonce, size_t snonce_len,
const char* fail_info)
{
/* A FAILURE CertRep carries no messageData, hence no envelope target. */

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The parser takes the transactionID attribute's inner value whatever its tag, so a peer can send an OCTET STRING or a UTF8String of arbitrary bytes and we echo them back inside a PrintableString - enc_printable_n() (scep_msg.c:100) does no charset check. The result isn't valid DER and a strict client decoder rejects it.

The encoder is pre-existing; what changes here is reachability. Before, these three cases got a text/plain 400 and the transactionID was never re-encoded; now it is, on a message that has passed nothing but the CMS signature check. If these paths stay as CertReps, worth validating the recovered transactionID against the PrintableString repertoire and taking the 400 fallback when it doesn't conform.

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.

Acknowledged, not fixed. enc_printable_n() writes tag 0x13 with no charset check, and #22's guard tests that the transactionID is present, not what bytes it holds, so an OCTET STRING or UTF8String value still round-trips into a PrintableString.

It reaches the two surviving branches the same way it already reaches the enroll and badCertId replies, so this is a pre-existing encoder issue that this PR widens rather than introduces. I would rather fix it once for every site than only for these — validate against the PrintableString repertoire in the encoder, or reject at parse. Want it in this PR, or as a follow-up?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Follow-up, and fix it in the encoder rather than at these sites - your instinct is right.

Confirming it still stands at 76a0273b, since the reachability is what changed: I sent a transactionID of 00 FF 7F 40 5F 24 21 0A on the unrecognized-messageType path and got it back echoed under tag 0x13. The parser unwraps by length and never looks at the inner tag (scep_msg.c:788-820), and enc_printable_n() writes 0x13 unconditionally.

Per-site validation would leave the enroll and badCertId replies broken anyway, so one check in enc_printable_n() - or a repertoire check at parse, which also stops us storing it - covers every site at once. Not a blocker here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Confirming the scope for that follow-up, because there is a second value the repertoire check would catch and a narrow fix would miss: a messageType with an embedded NUL dispatches on its prefix.

scep_msg.c:862 copies vlen bytes and appends its own terminator, and dispatch is strcmp(mt, "19"), so a value of 31 39 00 78 ("19\0x") compares equal to "19". Probed against 74643ed7 by patching enc_printable() to map '@' to a NUL and sending the control round of check_required_attrs as "19@x": it passed the control's own assertions - 200, pkiStatus 0, enveloped messageData - so the server issued a certificate for a messageType that is not 19.

NUL is not in the PrintableString repertoire either, so one check at parse closes this and the tag hole together, whereas a check that only inspects what the encoder writes would leave it. Pre-existing, not a blocker here, and this PR does not widen it - "19\0x" reached handle_enroll before the change too. Just worth pinning to the follow-up so it does not get written narrowly.

It also means the new mt[0] == '\0' guard covers the empty case but not this one, so the four attributes are not quite treated the same way yet.

return send_cert_rep(s, fd, NULL, 0, NULL, 0,
tid, tid_len, snonce, snonce_len, "2", fail_info);
}

/* Handle messageType=19 (PKCSReq) or 17 (RenewalReq) freshly arrived. */
static int handle_enroll(WolfCertServer* s, int fd, const char* mt,
const WolfCertBuffer* csr,
Expand All @@ -668,18 +680,16 @@ static int handle_enroll(WolfCertServer* s, int fd, const char* mt,
csr->data, csr->len, s->heap) != WOLFCERT_OK) {
/* Report the failure as a CertRep, then close the connection. */
s->keep_alive = 0;
return send_cert_rep(s, fd, NULL, 0, env_target, env_target_len,
tid, tid_len, snonce, snonce_len,
"2", "2" /* badRequest */);
return send_pki_failure(s, fd, tid, tid_len, snonce, snonce_len,
"2" /* badRequest */);
}

if (check_challenge(csr->data, csr->len, s->cfg_challenge,
s->heap) != WOLFCERT_OK) {
/* Report the failure as a CertRep, then close the connection. */
s->keep_alive = 0;
return send_cert_rep(s, fd, NULL, 0, env_target, env_target_len,
tid, tid_len, snonce, snonce_len,
"2", "2" /* badRequest */);
return send_pki_failure(s, fd, tid, tid_len, snonce, snonce_len,
"2" /* badRequest */);
}

if (s->cfg.scep_require_approval) {
Expand All @@ -694,9 +704,8 @@ static int handle_enroll(WolfCertServer* s, int fd, const char* mt,

if (add != WOLFCERT_OK) {
/* Queue full - fail rather than silently losing requests. */
return send_cert_rep(s, fd, NULL, 0, env_target, env_target_len,
tid, tid_len, snonce, snonce_len,
"2", "2" /* badRequest */);
return send_pki_failure(s, fd, tid, tid_len, snonce, snonce_len,
"2" /* badRequest */);
}
}

Expand All @@ -716,18 +725,13 @@ static int handle_enroll(WolfCertServer* s, int fd, const char* mt,
* to be pending forever. */
static int handle_get_cert_initial(WolfCertServer* s, int fd,
const uint8_t* tid, size_t tid_len,
const uint8_t* snonce, size_t snonce_len,
const uint8_t* signer_cert, size_t signer_cert_len)
const uint8_t* snonce, size_t snonce_len)
{
ScepPriv* p = (ScepPriv*)s->priv;
ScepPending* e = pending_find(p, tid, tid_len);
if (e == NULL) {
const uint8_t* env_target = signer_cert ? signer_cert : s->ca.cert_der;
size_t env_target_len = signer_cert ? signer_cert_len : s->ca.cert_der_len;

return send_cert_rep(s, fd, NULL, 0, env_target, env_target_len,
tid, tid_len, snonce, snonce_len,
"2", "4" /* badCertId: no such transaction */);
return send_pki_failure(s, fd, tid, tid_len, snonce, snonce_len,
"4" /* badCertId: no such transaction */);
}

/* Approve on first poll. A production implementation would hold
Expand Down Expand Up @@ -778,6 +782,7 @@ static int handle_pki_op(WolfCertServer* s, int fd, const ScepRequest* req)
uint8_t* signer_cert = NULL;
size_t signer_cert_len = 0;
WolfCertBuffer csr = { 0 };
int send_rc = WOLFCERT_OK;

int rc = wolfcert_scep_parse_pki_message(req->body, req->body_len, &env,
&tid, &tid_len, &snonce, &snonce_len, &rnonce, &rnonce_len,
Expand All @@ -787,25 +792,33 @@ static int handle_pki_op(WolfCertServer* s, int fd, const ScepRequest* req)
goto out;
}

if (tid == NULL || tid_len == 0 || snonce == NULL || snonce_len == 0) {
/* RFC 8894 section 3.2.1 requires all three in every message, so one that
* omits any of them is not a pkiMessage a CertRep could answer. */
if (tid == NULL || tid_len == 0 || snonce == NULL || snonce_len == 0 ||
mt == NULL || mt[0] == '\0') {
s->keep_alive = 0;
send_text(s, fd, 400, "Bad Message", "text/plain", "");
rc = WOLFCERT_ERR_PROTOCOL;
goto out;
}

Comment thread
Frauschi marked this conversation as resolved.
if (mt == NULL) {
send_text(s, fd, 400, "Bad Message", "text/plain", "");
goto out;
}

rc = wolfcert_scep_deenvelop(s->ca.cert_der, s->ca.cert_der_len,
s->ca.key_der, s->ca.key_der_len,
env.data, env.len, &csr, s->heap);
if (rc != WOLFCERT_OK && strcmp(mt, "20") != 0) {
/* Decryption matters for 19/17 (CSR inside); for 20 the payload
* is IssuerAndSubject which the server matches by txid anyway. */
send_text(s, fd, 400, "Cannot Decrypt", "text/plain", "");
const char* fail_info = rc == WOLFCERT_ERR_UNSUPPORTED
? "0" /* badAlg */
: "2" /* badRequest */;

/* Closed by the non-OK return; the flag is for the header. */
s->keep_alive = 0;
send_rc = send_pki_failure(s, fd, tid, tid_len, snonce, snonce_len,
fail_info);
if (send_rc != WOLFCERT_OK)
WOLFCERT_LOG_DBG("scep", "CertRep send failed: %d", send_rc);

goto out;
}

Expand All @@ -814,11 +827,16 @@ static int handle_pki_op(WolfCertServer* s, int fd, const ScepRequest* req)
tid, tid_len, snonce, snonce_len);
}
else if (strcmp(mt, "20") == 0) {
rc = handle_get_cert_initial(s, fd, tid, tid_len, snonce, snonce_len,
signer_cert, signer_cert_len);
rc = handle_get_cert_initial(s, fd, tid, tid_len, snonce, snonce_len);
}
else {
send_text(s, fd, 400, "Bad Message", "text/plain", "");
/* Closed by the non-OK return; the flag is for the header. */
s->keep_alive = 0;
send_rc = send_pki_failure(s, fd, tid, tid_len, snonce, snonce_len,
"2" /* badRequest */);
if (send_rc != WOLFCERT_OK)
WOLFCERT_LOG_DBG("scep", "CertRep send failed: %d", send_rc);

rc = WOLFCERT_ERR_PROTOCOL;
}

Expand Down
194 changes: 189 additions & 5 deletions tests/integration/test_scep_roundtrip.c
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,7 @@ static int check_getnextca_ca_id(const uint8_t* ca_der_buf, size_t ca_der_len)
return 0;
}

#if defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_128)
/* RFC 8894 section 3.2.1 requires transactionID and a fresh senderNonce in every
* pkiMessage; the client always sends both, so POST hand-built ones instead. */
static int check_required_attrs(WolfCertServer* s, const WolfCertKeyCfg* kcfg,
Expand Down Expand Up @@ -830,8 +831,9 @@ static int check_required_attrs(WolfCertServer* s, const WolfCertKeyCfg* kcfg,

/* Each round omits one required attribute; the last is the control that
* proves this raw-POST harness reaches the issuance path at all. */
for (i = 0; rc == WOLFCERT_OK && i < 5; ++i) {
WolfCertScepAttrs a = { .message_type = "19" };
for (i = 0; rc == WOLFCERT_OK && i < 7; ++i) {
WolfCertScepAttrs a = { .message_type = i == 4 ? NULL :
i == 5 ? "" : "19" };
WolfCertBuffer msg = { 0 };
WolfCertBuffer renv = { 0 };
uint8_t *r_tid = NULL, *r_sn = NULL, *r_rn = NULL, *r_sc = NULL;
Expand All @@ -856,7 +858,11 @@ static int check_required_attrs(WolfCertServer* s, const WolfCertKeyCfg* kcfg,
a.transaction_id = tid; a.transaction_id_len = sizeof(tid);
a.sender_nonce = snonce; a.sender_nonce_len = 0;
}
else { /* control: both present */
else if (i == 4 || i == 5) { /* no / zero-length messageType */
a.transaction_id = tid; a.transaction_id_len = sizeof(tid);
a.sender_nonce = snonce; a.sender_nonce_len = sizeof(snonce);
}
else { /* control: all three present */
a.transaction_id = tid; a.transaction_id_len = sizeof(tid);
a.sender_nonce = snonce; a.sender_nonce_len = sizeof(snonce);
}
Expand All @@ -873,8 +879,8 @@ static int check_required_attrs(WolfCertServer* s, const WolfCertKeyCfg* kcfg,
msg.data, msg.len, 0, &rsp, &rsp_len);
wolfcert_buffer_free(&msg);

if (i < 4) {
/* Nothing to echo, so no conforming CertRep exists. */
if (i < 6) {
/* An attribute that is absent or empty is not a pkiMessage. */
ok = (st == 400);
}
else {
Expand All @@ -886,6 +892,7 @@ static int check_required_attrs(WolfCertServer* s, const WolfCertKeyCfg* kcfg,
ok = ok && r_tid != NULL && r_tidl == sizeof(tid) &&
memcmp(r_tid, tid, sizeof(tid)) == 0 &&
r_st != NULL && strcmp(r_st, "0") == 0 && renv.len > 0 &&
r_sn != NULL && r_snl == sizeof(snonce) &&
r_rn != NULL && r_rnl == sizeof(snonce) &&
memcmp(r_rn, snonce, sizeof(snonce)) == 0;

Expand Down Expand Up @@ -937,6 +944,174 @@ static int check_required_attrs(WolfCertServer* s, const WolfCertKeyCfg* kcfg,
return rc;
}

/* handle_pki_op's dispatch failures answer with a signed CertRep FAILURE, not
* a bare HTTP error. The client cannot produce these messages, so POST
* hand-built ones. Owns and frees everything it makes. */
static int check_malformed_dispatch(uint16_t port, const WolfCertKeyCfg* kcfg,
Comment thread
Frauschi marked this conversation as resolved.
const uint8_t* ca_der_buf, size_t ca_der_len)
{
static const uint8_t junk[4] = { 0x04, 0x02, 0xAB, 0xCD };
static const char* const msg_type[3] = { "19", "99", "19" };
static const char* const want_fi[3] = { "2", "2", "0" };

WolfCertCertMeta meta = { .subject_dn = "CN=scep-dispatch" };
WolfCertKey* key = NULL;
WolfCertBuffer csr = { 0 };
WolfCertBuffer kder = { 0 };
WolfCertBuffer env = { 0 };
uint8_t* signer = NULL;
size_t signer_len = 0;
uint8_t tid[16], snonce[16];
uint8_t prev_sn[16];
uint8_t* bad_env = NULL;
int have_prev = 0;
char url[160];
size_t i;
int rc;

memset(tid, 0x33, sizeof(tid));
memset(snonce, 0x44, sizeof(snonce));
snprintf(url, sizeof(url),
"http://127.0.0.1:%u/scep?operation=PKIOperation", port);

rc = wolfcert_key_generate(kcfg, &key);
if (rc == WOLFCERT_OK)
rc = wolfcert_csr_build(key, &meta, &csr);
if (rc == WOLFCERT_OK)
rc = wolfcert_key_to_der(key, &kder);
if (rc == WOLFCERT_OK)
rc = wolfcert_scep_self_signed_rsa((RsaKey*)key->impl, csr.data,
csr.len, &signer, &signer_len, NULL);
if (rc == WOLFCERT_OK)
rc = wolfcert_scep_envelop(ca_der_buf, ca_der_len, csr.data, csr.len,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is the only unguarded AES-128 use in the file. SCEP_SRV_ENC_OID (scep_server.c:52-57) falls back to DES3b when WOLFSSL_AES_128/HAVE_AES_CBC are absent, and every other AES-CBC use here sits behind WOLFCERT_TEST_HAVE_CIPHER_OVERRIDE (:266) or an equivalent guard. On such a build wolfcert_scep_envelop() fails before the loop, all four rounds are skipped, and the REQUIRE() in main() takes the whole integration test down even though the server is correct.

Only round 2 needs a decryptable envelope and the server de-envelops whatever OID arrives, so a file-level TEST_SCEP_ENC_OID chosen the same way the server chooses SCEP_SRV_ENC_OID covers it.

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.

Fixed, and it applied to check_required_attrs too, so the guard covers both helpers and both call sites:

#if defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_128)

Note it names WOLFSSL_AES_128 rather than (AES_128 || AES_256) — both helpers hardcode AES128CBCb, so the wider condition would let an AES-256-only build through and fail exactly as before.

The trade-off is that both tests now compile out on a 3DES-only build instead of adapting to it, which is the config where the server's DES3b fallback runs. A file-level TEST_SCEP_ENC_OID picked the way the server picks SCEP_SRV_ENC_OID would keep the coverage; happy to switch if you prefer that.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Keep the guard. The coverage it costs is a config we don't build in CI, and TEST_SCEP_ENC_OID would buy that back at the price of a second place where the test has to track how the server picks its cipher. I forced the guard off and the file still compiles clean - raw_http_req() survives via raw_http_status() - so nothing else was leaning on those two helpers.

One thing to add: both groups now vanish with no notice, so a 3DES-only build reports a clean full run having skipped them. test_est_mldsa_roundtrip.c:59 prints a SKIP (...) line for exactly this and is worth matching.

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.

Keeping the guard then, and the notice is in, matching test_est_mldsa_roundtrip.c:59:

    printf("SKIP required-attrs and malformed-dispatch "
           "(wolfSSL built without AES-128-CBC)\n");

So a 3DES-only build now says which two groups it skipped rather than reporting a clean run.

AES128CBCb, &env, NULL);

/* The last round needs a cipher the CA cannot run: copy the envelope and
* point its algorithm at an unassigned OID under the same arc. */
if (rc == WOLFCERT_OK) {
static const uint8_t aes128_cbc[] =
{ 0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x01,0x02 };
uint8_t* at = NULL;

bad_env = (uint8_t*)WOLFCERT_XMALLOC(env.len, NULL);
if (bad_env == NULL)
rc = WOLFCERT_ERR_MEMORY;
if (rc == WOLFCERT_OK) {
memcpy(bad_env, env.data, env.len);
at = (uint8_t*)memmem(bad_env, env.len, aes128_cbc,
sizeof(aes128_cbc));
if (at == NULL)
rc = -1;
else
at[sizeof(aes128_cbc) - 1] = 0x63;
}
}

for (i = 0; rc == WOLFCERT_OK && i < 3; ++i) {
WolfCertScepAttrs a = {
.transaction_id = tid, .transaction_id_len = sizeof(tid),
.sender_nonce = snonce, .sender_nonce_len = sizeof(snonce),
.message_type = msg_type[i],
};
/* Round 1 needs an envelope the CA can open, or it trips the
* decrypt branch first; round 2 needs the unrunnable one. */
const uint8_t* content = i == 1 ? env.data :
i == 2 ? bad_env : junk;
size_t content_len = i >= 1 ? env.len : sizeof(junk);
WolfCertBuffer msg = { 0 };
WolfCertBuffer renv = { 0 };
uint8_t *r_tid = NULL, *r_sn = NULL, *r_rn = NULL, *r_sc = NULL;
size_t r_tidl = 0, r_snl = 0, r_rnl = 0, r_scl = 0;
char *r_mt = NULL, *r_st = NULL, *r_fi = NULL;
WolfCertHttpResponse resp = { 0 };

rc = wolfcert_scep_build_pki_message(content, content_len,
signer, signer_len, kder.data, kder.len,
SHA256h, &a, &msg, NULL);
if (rc == WOLFCERT_OK) {
WolfCertHttpRequest req = {
.method = "POST",
.url = url,
.content_type = "application/x-pki-message",
.body = msg.data,
.body_len = msg.len,
};
int ok = wolfcert_http_request(&req, &resp) == WOLFCERT_OK &&
resp.status_code == 200 && resp.body != NULL;

ok = ok && wolfcert_scep_parse_pki_message(resp.body,
resp.body_len, &renv, &r_tid, &r_tidl, &r_sn,
&r_snl, &r_rn, &r_rnl, &r_mt, &r_st, &r_sc,
&r_scl, &r_fi, NULL) == WOLFCERT_OK;

ok = ok && r_mt != NULL && strcmp(r_mt, "3") == 0 &&
r_st != NULL && strcmp(r_st, "2") == 0 &&
r_fi != NULL && strcmp(r_fi, want_fi[i]) == 0 &&
r_tid != NULL && r_tidl == sizeof(tid) &&
memcmp(r_tid, tid, sizeof(tid)) == 0 &&
r_rn != NULL && r_rnl == sizeof(snonce) &&
memcmp(r_rn, snonce, sizeof(snonce)) == 0 &&
renv.len == 0;

/* Each reply carries a fresh senderNonce of its own. */
ok = ok && r_sn != NULL && r_snl == sizeof(prev_sn) &&
(!have_prev ||
memcmp(r_sn, prev_sn, sizeof(prev_sn)) != 0);
if (ok) {
memcpy(prev_sn, r_sn, sizeof(prev_sn));
have_prev = 1;
}

WOLFCERT_XFREE(r_tid, NULL); WOLFCERT_XFREE(r_sn, NULL);
WOLFCERT_XFREE(r_rn, NULL); WOLFCERT_XFREE(r_sc, NULL);
WOLFCERT_XFREE(r_mt, NULL); WOLFCERT_XFREE(r_st, NULL);
WOLFCERT_XFREE(r_fi, NULL);
wolfcert_buffer_free(&renv);
wolfcert_http_response_free(&resp);
if (!ok)
rc = -1;
}

wolfcert_buffer_free(&msg);
}

/* Ask to keep the connection alive: the CertRep must come back and the
* server must then hang up. Holding the socket open instead times the
* read out, which raw_http_req reports as -1 rather than 200. */
if (rc == WOLFCERT_OK) {
WolfCertScepAttrs a = {
.transaction_id = tid, .transaction_id_len = sizeof(tid),
.sender_nonce = snonce, .sender_nonce_len = sizeof(snonce),
.message_type = "99",
};
WolfCertBuffer msg = { 0 };

rc = wolfcert_scep_build_pki_message(env.data, env.len, signer,
signer_len, kder.data, kder.len, SHA256h, &a, &msg, NULL);
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) {
fprintf(stderr, "FAIL %s:%d dispatch failure did not answer "
"and close\n", __FILE__, __LINE__);
rc = -1;
}
wolfcert_buffer_free(&msg);
}
}

WOLFCERT_XFREE(bad_env, NULL);
WOLFCERT_XFREE(signer, NULL);
wolfcert_buffer_free(&env);
wolfcert_buffer_free(&kder);
wolfcert_buffer_free(&csr);
wolfcert_key_free(key);

return rc;
}

#endif /* HAVE_AES_CBC && WOLFSSL_AES_128 */

int main(void)
{
REQUIRE(wolfcert_init(NULL) == WOLFCERT_OK);
Expand Down Expand Up @@ -1098,9 +1273,18 @@ int main(void)
REQUIRE(raw_http_status(wolfcert_server_port(s),
"/scep?operation=PKIOperation&message=QUJD", "XYZ") == 400); /* body freed */

#if defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_128)
REQUIRE(check_required_attrs(s, &kcfg, ca_der->buffer,
ca_der->length) == WOLFCERT_OK);

REQUIRE(check_malformed_dispatch(wolfcert_server_port(s), &kcfg,
ca_der->buffer, ca_der->length)
== WOLFCERT_OK);
#else
printf("SKIP required-attrs and malformed-dispatch "
"(wolfSSL built without AES-128-CBC)\n");
#endif

#ifdef WOLFCERT_HAVE_ED25519
/* Ed25519 signer must be rejected cleanly (RFC 8894 requires RSA). */
WolfCertKeyCfg edcfg = { .type = WOLFCERT_KEY_ED25519, .param = 0,
Expand Down
Loading