diff --git a/.github/actions/zephyr-workspace/action.yml b/.github/actions/zephyr-workspace/action.yml index 39d5776..8663e67 100644 --- a/.github/actions/zephyr-workspace/action.yml +++ b/.github/actions/zephyr-workspace/action.yml @@ -40,9 +40,12 @@ runs: - name: Workspace cache key id: wskey shell: bash + env: + ZEPHYR_REF: ${{ inputs.zephyr-ref }} + WOLFSSL_REF: ${{ inputs.wolfssl-ref }} + SCRIPT_HASH: ${{ hashFiles('scripts/ci/zephyr-add-wolfssl.py') }} run: | - h="${{ hashFiles('scripts/ci/zephyr-add-wolfssl.py') }}" - echo "key=zephyr-ws-${RUNNER_OS}-${{ inputs.zephyr-ref }}-${{ inputs.wolfssl-ref }}-${h:0:12}-$(python3 -V | tr -d ' ')" \ + echo "key=zephyr-ws-${RUNNER_OS}-${ZEPHYR_REF}-${WOLFSSL_REF}-${SCRIPT_HASH:0:12}-$(python3 -V | tr -d ' ')" \ >> "$GITHUB_OUTPUT" - name: Restore the Zephyr workspace @@ -56,6 +59,8 @@ runs: - name: Initialise the west workspace shell: bash + env: + ZEPHYR_REF: ${{ inputs.zephyr-ref }} run: | set -euo pipefail if [ ! -x "$HOME/zephyr-venv/bin/west" ]; then @@ -64,14 +69,16 @@ runs: fi if [ ! -d "$HOME/zephyrproject/.west" ]; then "$HOME/zephyr-venv/bin/west" init -m https://github.com/zephyrproject-rtos/zephyr \ - --mr "${{ inputs.zephyr-ref }}" "$HOME/zephyrproject" + --mr "$ZEPHYR_REF" "$HOME/zephyrproject" fi - name: Add wolfSSL to the manifest shell: bash + env: + WOLFSSL_REF: ${{ inputs.wolfssl-ref }} run: | ./scripts/ci/zephyr-add-wolfssl.py \ - "$HOME/zephyrproject/zephyr/west.yml" "${{ inputs.wolfssl-ref }}" + "$HOME/zephyrproject/zephyr/west.yml" "$WOLFSSL_REF" - name: Update west modules shell: bash @@ -91,12 +98,16 @@ runs: - name: Install the Zephyr SDK shell: bash + env: + SDK_VERSION: ${{ inputs.sdk-version }} + TOOLCHAIN: ${{ inputs.toolchain }} run: | set -euo pipefail - v="${{ inputs.sdk-version }}" + v="$SDK_VERSION" cd "$HOME" - wget -q "https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v${v}/zephyr-sdk-${v}_linux-x86_64_minimal.tar.xz" + wget -q --tries=3 --retry-on-http-error=429,500,502,503,504 \ + "https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v${v}/zephyr-sdk-${v}_linux-x86_64_minimal.tar.xz" tar xf "zephyr-sdk-${v}_linux-x86_64_minimal.tar.xz" cd "zephyr-sdk-${v}" - ./setup.sh -h -c -t "${{ inputs.toolchain }}" + ./setup.sh -h -c -t "$TOOLCHAIN" echo "ZEPHYR_SDK_INSTALL_DIR=$HOME/zephyr-sdk-${v}" >> "$GITHUB_ENV" diff --git a/cli/wolfcert_client.c b/cli/wolfcert_client.c index bcb9e2d..71a48c0 100644 --- a/cli/wolfcert_client.c +++ b/cli/wolfcert_client.c @@ -1011,6 +1011,23 @@ static int scep_resolve_ca(const WolfCertServerCfg* srv, const CaPin* pin, return rc; } +/* Carry on without capabilities on a non-200 GetCACaps reply. */ +static int scep_fetch_caps(const WolfCertServerCfg* srv, WolfCertScepCaps* caps, + const char* cmd) +{ + int rc = wolfcert_scep_get_ca_caps(srv, caps); + + if (rc == WOLFCERT_ERR_HTTP) { + fprintf(stderr, "%s: GetCACaps failed, assuming a CA without " + "capabilities (3DES unless --content-cipher)\n", cmd); + rc = WOLFCERT_OK; + } + else if (rc != WOLFCERT_OK) { + fprintf(stderr, "%s: GetCACaps: %s\n", cmd, wolfcert_strerror(rc)); + } + return rc; +} + /* Fetch the CA, resolve any pin against it, then run PKCSReq and any polling. */ static int scep_enroll(const Opts* opts, const WolfCertServerCfg* srv, const CaPin* pin, const WolfCertKey* key, @@ -1029,8 +1046,9 @@ static int scep_enroll(const Opts* opts, const WolfCertServerCfg* srv, rc = scep_resolve_ca(srv, pin, "enroll", &ca_pem, &ca_bundle, &ca_der, &bundle, &bundle_len); + if (rc == WOLFCERT_OK) + rc = scep_fetch_caps(srv, &caps, "enroll"); if (rc == WOLFCERT_OK) { - wolfcert_scep_get_ca_caps(srv, &caps); rc = wolfcert_scep_pkcs_req_ex(srv, &caps, ca_der->buffer, ca_der->length, bundle, bundle_len, key, csr->data, csr->len, &scep_result); @@ -1699,7 +1717,12 @@ static int cmd_getcert(int argc, char** argv) } if (ret == 0) { - wolfcert_scep_get_ca_caps(&srv, &caps); + rc = scep_fetch_caps(&srv, &caps, "getcert"); + if (rc != WOLFCERT_OK) + ret = 2; + } + + if (ret == 0) { rc = wolfcert_scep_get_cert(&srv, &caps, ra_der->buffer, ra_der->length, bundle, bundle_len, signer_der->buffer, signer_der->length, diff --git a/examples/enroll_scep.c b/examples/enroll_scep.c index c002761..def2712 100644 --- a/examples/enroll_scep.c +++ b/examples/enroll_scep.c @@ -101,9 +101,13 @@ int main(int argc, char** argv) return 2; } - /* 2) Optionally query capabilities. */ + /* 2) Query capabilities; carry on without them on a non-200. */ WolfCertScepCaps caps = { 0 }; - wolfcert_scep_get_ca_caps(&srv, &caps); + rc = wolfcert_scep_get_ca_caps(&srv, &caps); + if (rc != WOLFCERT_OK && rc != WOLFCERT_ERR_HTTP) { + fprintf(stderr, "GetCACaps failed: %s\n", wolfcert_strerror(rc)); + return 2; + } /* 3) Generate RSA key + CSR. */ WolfCertKeyCfg kcfg = { .type = WOLFCERT_KEY_RSA, .param = 2048, diff --git a/src/csr.c b/src/csr.c index 5b95afa..6e6a376 100644 --- a/src/csr.c +++ b/src/csr.c @@ -69,6 +69,20 @@ static int assign_rdn(CertName* subject, const char* key, size_t klen, return WOLFCERT_ERR(WOLFCERT_ERR_BAD_ARG, "csr", "subject %.*s is %zu bytes, limit %zu", (int)klen, key, vlen, rdn_fields[i].cap - 1); + /* wolfSSL encodes these two as PrintableString. */ + if ((rdn_fields[i].off == offsetof(CertName, country) || + rdn_fields[i].off == offsetof(CertName, serialDev)) && + !wolfcert_is_printable_string((const uint8_t*)val, vlen)) + return WOLFCERT_ERR(WOLFCERT_ERR_BAD_ARG, "csr", + "subject %.*s is not a PrintableString", (int)klen, key); + /* And emailAddress as IA5String, which is 7-bit. */ + if (rdn_fields[i].off == offsetof(CertName, email)) { + for (size_t j = 0; j < vlen; ++j) { + if ((unsigned char)val[j] >= 0x80) + return WOLFCERT_ERR(WOLFCERT_ERR_BAD_ARG, "csr", + "subject %.*s is not an IA5String", (int)klen, key); + } + } memcpy(dst, val, vlen); dst[vlen] = '\0'; return WOLFCERT_OK; @@ -167,20 +181,37 @@ static int build_san_seq(const WolfCertCertMeta* meta, Cert* cert, void* heap) return WOLFCERT_OK; } -/* RFC 5480 pairs P-256, P-384 and P-521 with SHA-256, SHA-384 and SHA-512. */ -#ifdef WOLFCERT_HAVE_ECC -static int ecdsa_sig_for_curve(int curve_id) +/* 1 when the wolfSSL build has SHA-. */ +static int sha2_in_build(int bits) { - switch (curve_id) { - case ECC_SECP384R1: - return CTC_SHA384wECDSA; - case ECC_SECP521R1: - return CTC_SHA512wECDSA; - case ECC_SECP256R1: + switch (bits) { + case 256: + return 1; +#ifdef WOLFSSL_SHA384 + case 384: + return 1; +#endif +#ifdef WOLFSSL_SHA512 + case 512: + return 1; +#endif default: - return CTC_SHA256wECDSA; + return 0; } } + +/* RFC 5480 pairs P-256, P-384 and P-521 with SHA-256, SHA-384 and SHA-512; + * a hash wolfSSL lacks steps down to the next one. */ +#ifdef WOLFCERT_HAVE_ECC +static int ecdsa_sig_for_curve(int curve_id) +{ + if (curve_id == ECC_SECP521R1 && sha2_in_build(512)) + return CTC_SHA512wECDSA; + if ((curve_id == ECC_SECP521R1 || curve_id == ECC_SECP384R1) && + sha2_in_build(384)) + return CTC_SHA384wECDSA; + return CTC_SHA256wECDSA; +} #endif /* CTC_* signature type for a hash size in bits, or 0 when the key's family @@ -225,6 +256,10 @@ static int choose_sig_type(const WolfCertKey* key, const WolfCertKeyAlg* alg, { if (meta != NULL && meta->preferred_hash != 0) { int sig = sig_type_for_hash(key->type, meta->preferred_hash); + if (sig != 0 && !sha2_in_build(meta->preferred_hash)) + return WOLFCERT_ERR(WOLFCERT_ERR_UNSUPPORTED, "csr", + "preferred_hash %d: wolfSSL lacks SHA-%d", + meta->preferred_hash, meta->preferred_hash); if (sig != 0) return sig; } @@ -408,7 +443,12 @@ int wolfcert_csr_build_ex(const WolfCertKey* key, const WolfCertCertMeta* meta, wc_CertFree(cert); return WOLFCERT_ERR_UNSUPPORTED; } - cert->sigType = choose_sig_type(key, alg, meta); + int sig_type = choose_sig_type(key, alg, meta); + if (sig_type < 0) { + wc_CertFree(cert); + return sig_type; + } + cert->sigType = sig_type; size_t der_cap = alg->der_cap_hint + 1024; if (key->type == WOLFCERT_KEY_RSA) { diff --git a/src/est/est_server.c b/src/est/est_server.c index 0946de2..e587411 100644 --- a/src/est/est_server.c +++ b/src/est/est_server.c @@ -39,7 +39,6 @@ #include #include #include -#include #include #include #include @@ -228,15 +227,6 @@ static int chunked_body_complete(const uint8_t* raw, size_t raw_len) return 0; } -/* 1 when the line's field name is `name`, followed directly by ':'. */ -static int hdr_is(const char* line, size_t llen, const char* name) -{ - size_t n = strlen(name); - - return llen > n && line[n] == ':' && - wolfcert_ascii_ncasecmp(line, name, n) == 0; -} - static int parse_request_in(WolfCertServer* s, int fd, EstRequest* out, void* heap, char* buf, size_t buf_sz) { @@ -285,47 +275,29 @@ static int parse_request_in(WolfCertServer* s, int fd, EstRequest* out, int chunked = 0; while (read_line(&p, end, &line, &llen) == 0 && llen > 0) { - const char* hc = memchr(line, ':', llen); + const char* v; + size_t vlen; - /* RFC 9112 section 5.1: whitespace before the colon is a 400. */ - if (hc != NULL && hc > line && (hc[-1] == ' ' || hc[-1] == '\t')) + if (wolfcert_http_hdr_split(line, llen, &v, &vlen) != 0) return WOLFCERT_ERR_PROTOCOL; - if (hdr_is(line, llen, "Content-Length")) { - out->content_length = (size_t)strtoul(hc + 1, NULL, 10); + if (wolfcert_http_hdr_is(line, llen, "Content-Length")) { + out->content_length = (size_t)strtoul(v, NULL, 10); } - else if (hdr_is(line, llen, "Transfer-Encoding")) { - const char* v = hc + 1; - while (v < line + llen && (*v == ' ' || *v == '\t')) { - ++v; - } - - size_t vlen = (size_t)(line + llen - v); + else if (wolfcert_http_hdr_is(line, llen, "Transfer-Encoding")) { if (vlen >= 7 && wolfcert_ascii_ncasecmp(v, "chunked", 7) == 0) chunked = 1; } - else if (hdr_is(line, llen, "Authorization")) { + else if (wolfcert_http_hdr_is(line, llen, "Authorization")) { if (out->auth_header != NULL) return WOLFCERT_ERR_PROTOCOL; - const char* val = hc + 1; - while (val < line + llen && (*val == ' ' || *val == '\t')) { - ++val; - } - - size_t vlen = (size_t)(line + llen - val); out->auth_header = (char*)WOLFCERT_XMALLOC(vlen + 1, heap); if (out->auth_header) { - memcpy(out->auth_header, val, vlen); + memcpy(out->auth_header, v, vlen); out->auth_header[vlen] = '\0'; } } - else if (hdr_is(line, llen, "Connection")) { - const char* v = hc + 1; - while (v < line + llen && (*v == ' ' || *v == '\t')) { - ++v; - } - - size_t vlen = (size_t)(line + llen - v); + else if (wolfcert_http_hdr_is(line, llen, "Connection")) { if (vlen >= 5 && wolfcert_ascii_ncasecmp(v, "close", 5) == 0) out->connection_close = 1; } @@ -542,8 +514,8 @@ static int check_basic_auth(const WolfCertServer* s, const char* auth_header) if (auth_header == NULL) return 0; - if (strncasecmp(auth_header, EST_BASIC_AUTH_SCHEME, - EST_BASIC_AUTH_SCHEME_LEN) != 0) + if (wolfcert_ascii_ncasecmp(auth_header, EST_BASIC_AUTH_SCHEME, + EST_BASIC_AUTH_SCHEME_LEN) != 0) return 0; size_t ul = strlen(s->cfg_basic_user); @@ -639,7 +611,7 @@ static int ensure_post_handshake_auth(WolfCertServer* s) size_t fin_len; size_t cur_len; char probe; - long deadline; + int64_t deadline; int r; int err; diff --git a/src/http.c b/src/http.c index db22f02..1df6214 100644 --- a/src/http.c +++ b/src/http.c @@ -79,6 +79,36 @@ int wolfcert_ascii_ncasecmp(const char* a, const char* b, size_t n) return 0; } +int wolfcert_http_hdr_is(const char* line, size_t llen, const char* name) +{ + size_t n = strlen(name); + + return llen > n && line[n] == ':' && + wolfcert_ascii_ncasecmp(line, name, n) == 0; +} + +int wolfcert_http_hdr_split(const char* line, size_t llen, const char** val, + size_t* vlen) +{ + const char* end = line + llen; + const char* v = (const char*)memchr(line, ':', llen); + + *val = NULL; + *vlen = 0; + if (v == NULL) + return 0; + if (v > line && (v[-1] == ' ' || v[-1] == '\t')) + return -1; + + ++v; + while (v < end && (*v == ' ' || *v == '\t')) + ++v; + + *val = v; + *vlen = (size_t)(end - v); + return 0; +} + WOLFCERT_TEST_VIS void wolfcert_http_url_free(WolfCertUrl* u) { if (u == NULL) @@ -566,21 +596,18 @@ static int dyn_append(DynBuf* d, const void* data, size_t len) static char* find_header(const char* headers, const char* name, void* heap) { - size_t nlen = strlen(name); const char* p = headers; while (*p) { - if (wolfcert_ascii_ncasecmp(p, name, nlen) == 0 && p[nlen] == ':') { - const char* v = p + nlen + 1; - while (*v == ' ' || *v == '\t') { - ++v; - } - - const char* e = strstr(v, "\r\n"); - if (e == NULL) - return NULL; + const char* nl = strstr(p, "\r\n"); + if (nl == NULL) + break; - size_t n = (size_t)(e - v); + size_t llen = (size_t)(nl - p); + const char* v; + size_t n; + if (wolfcert_http_hdr_is(p, llen, name) && + wolfcert_http_hdr_split(p, llen, &v, &n) == 0) { char* s = (char*)WOLFCERT_XMALLOC(n + 1, heap); if (s == NULL) return NULL; @@ -591,10 +618,6 @@ static char* find_header(const char* headers, const char* name, void* heap) return s; } - const char* nl = strstr(p, "\r\n"); - if (nl == NULL) - break; - p = nl + 2; } return NULL; diff --git a/src/internal.h b/src/internal.h index 018c137..9a3d0d8 100644 --- a/src/internal.h +++ b/src/internal.h @@ -141,7 +141,7 @@ struct WolfCertServer { * retries in wolfcert_io_{recv,send} are allowed only then. */ int poll_timeouts_armed; void* heap; - long deadline_ms; /* 0 when none is armed */ + int64_t deadline_ms; /* 0 when none is armed */ }; /* wolfSSL_read/write on a TLS connection, else recv()/send(). */ @@ -151,7 +151,7 @@ ssize_t wolfcert_io_send(WolfCertServer* srv, int fd, const void* buf, size_t le /* Best-effort SO_NOSIGPIPE; a no-op where unsupported or fd is not a socket. */ WOLFCERT_TEST_VIS void wolfcert_sock_nosigpipe(int fd); -long wolfcert_mono_ms(void); +WOLFCERT_TEST_VIS int64_t wolfcert_mono_ms(void); WOLFCERT_API const WolfCertServerOps* wolfcert_est_server_ops(void); WOLFCERT_API const WolfCertServerOps* wolfcert_scep_server_ops(void); @@ -228,6 +228,12 @@ WOLFCERT_TEST_VIS int wolfcert_http_url_origin(const WolfCertUrl* u, 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 ':'. */ +int wolfcert_http_hdr_is(const char* line, size_t llen, const char* name); +/* -1 on whitespace before the colon (RFC 9112 section 5.1), else 0 with the + * value after leading whitespace, or *val NULL when the line has no colon. */ +int wolfcert_http_hdr_split(const char* line, size_t llen, const char** val, + size_t* vlen); /* `_encode` emits one line; `_encode_mime` wraps at 64 columns, which libest's * BIO_f_base64 body parser requires. `_decode` accepts both. */ diff --git a/src/net_posix.c b/src/net_posix.c index f128dca..16fb6c7 100644 --- a/src/net_posix.c +++ b/src/net_posix.c @@ -56,11 +56,11 @@ void wolfcert_sock_nosigpipe(int fd) #endif } -long wolfcert_mono_ms(void) +int64_t wolfcert_mono_ms(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); - return (long)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; + return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; } /* timeout_ms <= 0 blocks. Returns 0 on success, -1 on error or timeout. */ @@ -133,7 +133,7 @@ int wolfcert_posix_connect(const char* host, int port, int timeout_ms, void* ctx return -1; /* timeout_ms bounds the whole connect across all candidate addresses. */ - long deadline = (timeout_ms > 0) ? wolfcert_mono_ms() + timeout_ms : 0; + int64_t deadline = (timeout_ms > 0) ? wolfcert_mono_ms() + timeout_ms : 0; int fd = -1; for (rp = res; rp != NULL; rp = rp->ai_next) { diff --git a/src/scep/scep_client.c b/src/scep/scep_client.c index 091d8bd..906bc72 100644 --- a/src/scep/scep_client.c +++ b/src/scep/scep_client.c @@ -41,7 +41,6 @@ #include #include -#include static char* append_query(const char* base, const char* op, void* heap) { @@ -95,7 +94,7 @@ static int has_cap(const char* body, size_t len, const char* needle) if (tlen > 0 && body[start + tlen - 1] == '\r') --tlen; - if (tlen == nl && strncasecmp(body + start, needle, nl) == 0) + if (tlen == nl && wolfcert_ascii_ncasecmp(body + start, needle, nl) == 0) return 1; if (i < len) diff --git a/src/scep/scep_msg.c b/src/scep/scep_msg.c index acdca77..55b88e2 100644 --- a/src/scep/scep_msg.c +++ b/src/scep/scep_msg.c @@ -729,6 +729,8 @@ WOLFCERT_TEST_VIS int wolfcert_scep_parse_pki_message(const uint8_t* pki_der, char* s; uint8_t* b; size_t off, vlen, voff; + byte tag; + int hdr; int seen = 0; int bit; int rc; @@ -775,6 +777,9 @@ WOLFCERT_TEST_VIS int wolfcert_scep_parse_pki_message(const uint8_t* pki_der, *out_signer_cert = sc; *out_signer_cert_len = p7->verifyCertSz; } + else { + rc = WOLFCERT_ERR_MEMORY; + } } } @@ -802,7 +807,7 @@ WOLFCERT_TEST_VIS int wolfcert_scep_parse_pki_message(const uint8_t* pki_der, if (out_fail_info) *out_fail_info = NULL; - for (a = p7->decodedAttrib; a != NULL; a = a->next) { + for (a = p7->decodedAttrib; rc == WOLFCERT_OK && a != NULL; a = a->next) { bit = scep_attr_bit(a); if (bit == 0) continue; @@ -814,48 +819,32 @@ WOLFCERT_TEST_VIS int wolfcert_scep_parse_pki_message(const uint8_t* pki_der, } seen |= bit; - /* Depending on the wolfSSL version, value is either the outer SET OF - * AttributeValue or the bare first AttributeValue. */ - if (a->valueSz < 2 || a->value == NULL) - continue; - v = a->value; off = 0; - - if (v[0] == 0x31) { - off = 2; - if (v[1] == 0x81) { - if (a->valueSz < 3) - continue; - off = 3; - } - else if (v[1] == 0x82) { - if (a->valueSz < 4) - continue; - off = 4; + hdr = 0; + vlen = 0; + if (v != NULL && a->valueSz > 0) { + hdr = der_read_tlv(v, a->valueSz, &tag, &vlen); + /* Depending on the wolfSSL version, value is either the outer SET + * OF AttributeValue or the bare first AttributeValue. */ + if (hdr > 0 && tag == 0x31 && vlen > 0) { + off = (size_t)hdr; + hdr = der_read_tlv(v + off, vlen, &tag, &vlen); } } - - if (off + 2 > a->valueSz) - continue; - - vlen = v[off + 1]; - voff = off + 2; - if (v[off + 1] == 0x81) { - if (off + 3 > a->valueSz) - continue; - - vlen = v[off + 2]; - voff = off + 3; - } - else if (v[off + 1] == 0x82) { - if (off + 4 > a->valueSz) - continue; - vlen = ((size_t)v[off + 2] << 8) | v[off + 3]; - voff = off + 4; + if (hdr < 0) { + rc = WOLFCERT_ERR(WOLFCERT_ERR_PROTOCOL, "scep", + "truncated signed attribute in pkiMessage"); + break; } + voff = off + (size_t)hdr; - if (voff + vlen != a->valueSz) { + if (vlen == 0) { + rc = WOLFCERT_ERR(WOLFCERT_ERR_PROTOCOL, "scep", + "empty signed attribute in pkiMessage"); + break; + } + if (voff + vlen < a->valueSz) { rc = WOLFCERT_ERR(WOLFCERT_ERR_PROTOCOL, "scep", "multi-valued signed attribute in pkiMessage"); break; @@ -865,7 +854,7 @@ WOLFCERT_TEST_VIS int wolfcert_scep_parse_pki_message(const uint8_t* pki_der, * which hold no NUL. */ if ((bit & (SCEP_ATTR_MSG_TYPE | SCEP_ATTR_PKI_STATUS | SCEP_ATTR_FAIL_INFO)) != 0 && - (v[off] != 0x13 || memchr(v + voff, 0, vlen) != NULL)) { + (tag != 0x13 || memchr(v + voff, 0, vlen) != NULL)) { rc = WOLFCERT_ERR(WOLFCERT_ERR_PROTOCOL, "scep", "signed attribute is not a PrintableString"); break; @@ -905,19 +894,23 @@ WOLFCERT_TEST_VIS int wolfcert_scep_parse_pki_message(const uint8_t* pki_der, if (out_str != NULL) { s = (char*)WOLFCERT_XMALLOC(vlen + 1, heap); - if (s != NULL) { - memcpy(s, v + off, vlen); - s[vlen] = '\0'; - *out_str = s; + if (s == NULL) { + rc = WOLFCERT_ERR_MEMORY; + break; } + memcpy(s, v + off, vlen); + s[vlen] = '\0'; + *out_str = s; } else if (out_bin != NULL) { b = (uint8_t*)WOLFCERT_XMALLOC(vlen, heap); - if (b != NULL) { - memcpy(b, v + off, vlen); - *out_bin = b; - *out_bin_len = vlen; + if (b == NULL) { + rc = WOLFCERT_ERR_MEMORY; + break; } + memcpy(b, v + off, vlen); + *out_bin = b; + *out_bin_len = vlen; } } diff --git a/src/scep/scep_server.c b/src/scep/scep_server.c index 40ad6b5..7fa50d1 100644 --- a/src/scep/scep_server.c +++ b/src/scep/scep_server.c @@ -37,7 +37,6 @@ #include #include #include -#include #include #include @@ -212,21 +211,17 @@ static int read_request(WolfCertServer* s, int fd, ScepRequest* out, void* heap) } while (read_line(&p, end, &line, &llen) == 0 && llen > 0) { - if (llen > 14 && strncasecmp(line, "Content-Length", 14) == 0) { - char* c = memchr(line, ':', llen); - if (c) - out->content_length = (size_t)strtoul(c + 1, NULL, 10); + const char* v; + size_t vlen; + + 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 (llen > 10 && strncasecmp(line, "Connection", 10) == 0) { - char* colon = memchr(line, ':', llen); - if (colon != NULL) { - const char* v = colon + 1; - while (v < line + llen && (*v == ' ' || *v == '\t')) - ++v; - size_t vlen = (size_t)(line + llen - v); - if (vlen >= 5 && strncasecmp(v, "close", 5) == 0) - out->connection_close = 1; - } + else if (wolfcert_http_hdr_is(line, llen, "Connection")) { + if (vlen >= 5 && wolfcert_ascii_ncasecmp(v, "close", 5) == 0) + out->connection_close = 1; } } @@ -948,6 +943,10 @@ static int handle_pki_op(WolfCertServer* s, int fd, const ScepRequest* req) int rc = wolfcert_scep_parse_pki_message(req->body, req->body_len, &env, &tid, &tid_len, &snonce, &snonce_len, &rnonce, &rnonce_len, &mt, &ps, &signer_cert, &signer_cert_len, NULL, s->heap); + if (rc == WOLFCERT_ERR_MEMORY) { + send_text(s, fd, 500, "Server Error", "text/plain", ""); + goto out; + } if (rc != WOLFCERT_OK) { send_text(s, fd, 400, "Bad Request", "text/plain", ""); goto out; diff --git a/tests/integration/test_est_csr_attrs_apply_roundtrip.c b/tests/integration/test_est_csr_attrs_apply_roundtrip.c index 6373b9f..8536076 100644 --- a/tests/integration/test_est_csr_attrs_apply_roundtrip.c +++ b/tests/integration/test_est_csr_attrs_apply_roundtrip.c @@ -152,6 +152,13 @@ static int auto_apply_pins_everything(WolfCertServer* s) WolfCertBuffer cert_pem = { 0 }; int rc = wolfcert_client_enroll(cli, &srv, &key_cfg, &meta, &key, &cert_pem); +#ifndef WOLFSSL_SHA384 + REQUIRE(rc == WOLFCERT_ERR_UNSUPPORTED); + wolfcert_buffer_free(&cert_pem); + wolfcert_key_free(key); + wolfcert_client_free(cli); + return 0; +#endif REQUIRE(rc == WOLFCERT_OK); REQUIRE(key != NULL); REQUIRE(wolfcert_key_type(key) == WOLFCERT_KEY_ECC); @@ -197,6 +204,13 @@ static int explicit_caller_wins(WolfCertServer* s) WolfCertBuffer cert_pem = { 0 }; int rc = wolfcert_client_enroll(cli, &srv, &key_cfg, &meta, &key, &cert_pem); +#ifndef WOLFSSL_SHA384 + REQUIRE(rc == WOLFCERT_ERR_UNSUPPORTED); + wolfcert_buffer_free(&cert_pem); + wolfcert_key_free(key); + wolfcert_client_free(cli); + return 0; +#endif REQUIRE(rc == WOLFCERT_OK); REQUIRE(wolfcert_key_type(key) == WOLFCERT_KEY_RSA); diff --git a/tests/integration/test_scep_roundtrip.c b/tests/integration/test_scep_roundtrip.c index 79abbc6..890cda4 100644 --- a/tests/integration/test_scep_roundtrip.c +++ b/tests/integration/test_scep_roundtrip.c @@ -74,23 +74,20 @@ static int write_all_fd(int fd, const uint8_t* buf, size_t len) return 0; } -/* Raw HTTP/1.1 request to the loopback SCEP server, for malformed-request - * 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, +/* 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, uint8_t** out_body, size_t* out_body_len) { struct sockaddr_in addr; struct timeval tv; - char hdr[512]; uint8_t* resp = NULL; size_t resp_len = 0; size_t resp_cap = 0; const uint8_t* sep; int eof = 0; int fd; - int n; ssize_t r; int status = -1; @@ -117,24 +114,7 @@ static int raw_http_req(uint16_t port, const char* method, const char* target, return -1; } - 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", - content_type != NULL ? "Content-Type: " : "", - content_type != NULL ? content_type : "", - content_type != NULL ? "\r\n" : "", - body_len); - 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"); - if (n < 0 || (size_t)n >= sizeof(hdr)) { - close(fd); - return -1; - } - if (write_all_fd(fd, (const uint8_t*)hdr, (size_t)n) != 0 || + if (write_all_fd(fd, (const uint8_t*)hdr, hdr_len) != 0 || (body_len > 0 && write_all_fd(fd, body, body_len) != 0)) { close(fd); return -1; @@ -187,6 +167,47 @@ static int raw_http_req(uint16_t port, const char* method, const char* target, return status; } +/* Raw HTTP/1.1 request to the loopback SCEP server, for malformed-request + * 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, + uint8_t** out_body, size_t* out_body_len) +{ + char hdr[512]; + int n; + + 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", + content_type != NULL ? "Content-Type: " : "", + content_type != NULL ? content_type : "", + content_type != NULL ? "\r\n" : "", + body_len); + 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"); + if (n < 0 || (size_t)n >= sizeof(hdr)) { + if (out_body != NULL && out_body_len != NULL) { + *out_body = NULL; + *out_body_len = 0; + } + return -1; + } + + return raw_exchange(port, hdr, (size_t)n, body, body_len, + 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); +} + static int raw_http_status(uint16_t port, const char* target, const char* body) { return raw_http_req(port, "GET", target, NULL, @@ -1657,6 +1678,15 @@ int main(void) REQUIRE(wolfcert_scep_get_ca_caps(&tls_cli, &tls_caps) != WOLFCERT_ERR_TLS); } + /* A longer field name that starts with Content-Length is not one. */ + REQUIRE(raw_request_status(wolfcert_server_port(s), + "GET /scep?operation=GetCACaps HTTP/1.1\r\n" + "Host: 127.0.0.1\r\nContent-Length-Foo: 4\r\n" + "Connection: close\r\n\r\n") == 200); + REQUIRE(raw_request_status(wolfcert_server_port(s), + "GET /scep?operation=GetCACaps HTTP/1.1\r\n" + "Host : 127.0.0.1\r\nConnection: close\r\n\r\n") == 400); + /* Malformed GET PKIOperation requests get 400 (RFC 8894 section 4.1). */ REQUIRE(raw_http_status(wolfcert_server_port(s), "/scep?operation=PKIOperation", NULL) == 400); /* no message= */ diff --git a/tests/unit/test_csr.c b/tests/unit/test_csr.c index 02df008..ea088f7 100644 --- a/tests/unit/test_csr.c +++ b/tests/unit/test_csr.c @@ -84,6 +84,107 @@ static int build_and_reparse(WolfCertKeyType kt, int param) return 0; } +#ifdef WOLFCERT_HAVE_ECC +/* An ECC CSR uses its curve's hash, or the strongest one wolfSSL has. */ +static int ecc_sig_follows_curve(void) +{ + static const struct { int param; int sig; } want[] = { + { 256, CTC_SHA256wECDSA }, +#if defined(HAVE_ECC384) || defined(HAVE_ALL_CURVES) +#ifdef WOLFSSL_SHA384 + { 384, CTC_SHA384wECDSA }, +#else + { 384, CTC_SHA256wECDSA }, +#endif +#endif +#if defined(HAVE_ECC521) || defined(HAVE_ALL_CURVES) +#if defined(WOLFSSL_SHA512) + { 521, CTC_SHA512wECDSA }, +#elif defined(WOLFSSL_SHA384) + { 521, CTC_SHA384wECDSA }, +#else + { 521, CTC_SHA256wECDSA }, +#endif +#endif + }; + WolfCertCertMeta meta = { .subject_dn = "CN=device-1" }; + + for (size_t i = 0; i < sizeof(want) / sizeof(want[0]); i++) { + WolfCertKeyCfg cfg = { .type = WOLFCERT_KEY_ECC, + .param = want[i].param, + .dev_id = WOLFCERT_DEVID_SOFTWARE }; + WolfCertKey* key = NULL; + WolfCertBuffer der = { 0 }; + DecodedCert dc; + int sig; + + REQUIRE(wolfcert_key_generate(&cfg, &key) == WOLFCERT_OK); + REQUIRE(wolfcert_csr_build(key, &meta, &der) == WOLFCERT_OK); + wc_InitDecodedCert(&dc, der.data, (word32)der.len, NULL); + REQUIRE(wc_ParseCert(&dc, CERTREQ_TYPE, NO_VERIFY, NULL) == 0); + sig = (int)dc.signatureOID; + wc_FreeDecodedCert(&dc); + wolfcert_buffer_free(&der); + wolfcert_key_free(key); + if (sig != want[i].sig) { + fprintf(stderr, "FAIL P-%d signed with %d, want %d\n", + want[i].param, sig, want[i].sig); + return 1; + } + } + return 0; +} + +/* An ECC CSR signs with preferred_hash, or fails when wolfSSL lacks it. */ +static int ecc_sig_follows_preferred_hash(void) +{ + static const struct { int hash; int sig; } want[] = { + { 256, CTC_SHA256wECDSA }, +#ifdef WOLFSSL_SHA384 + { 384, CTC_SHA384wECDSA }, +#else + { 384, 0 }, +#endif +#ifdef WOLFSSL_SHA512 + { 512, CTC_SHA512wECDSA }, +#else + { 512, 0 }, +#endif + }; + WolfCertKeyCfg cfg = { .type = WOLFCERT_KEY_ECC, .param = 256, + .dev_id = WOLFCERT_DEVID_SOFTWARE }; + WolfCertKey* key = NULL; + int ret = 0; + + REQUIRE(wolfcert_key_generate(&cfg, &key) == WOLFCERT_OK); + for (size_t i = 0; i < sizeof(want) / sizeof(want[0]) && ret == 0; i++) { + WolfCertCertMeta meta = { .subject_dn = "CN=device-1", + .preferred_hash = want[i].hash }; + WolfCertBuffer der = { 0 }; + DecodedCert dc; + int sig = 0; + int rc = wolfcert_csr_build(key, &meta, &der); + + if (rc == WOLFCERT_OK) { + wc_InitDecodedCert(&dc, der.data, (word32)der.len, NULL); + if (wc_ParseCert(&dc, CERTREQ_TYPE, NO_VERIFY, NULL) == 0) + sig = (int)dc.signatureOID; + wc_FreeDecodedCert(&dc); + } + if (want[i].sig == 0) + ret = rc != WOLFCERT_ERR_UNSUPPORTED || der.data != NULL; + else + ret = sig != want[i].sig; + if (ret != 0) + fprintf(stderr, "FAIL SHA-%d gave rc %d sig %d, want sig %d\n", + want[i].hash, rc, sig, want[i].sig); + wolfcert_buffer_free(&der); + } + wolfcert_key_free(key); + return ret; +} +#endif + /* UID, rfc822Name and iPAddress meta fields reach the built CSR. */ static int build_with_extras(void) { @@ -204,6 +305,38 @@ static int csr_build_rejects_oversized_rdn(void) #endif #ifdef WOLFCERT_HAVE_ECC +/* C and serialNumber outside the PrintableString set, and a non-ASCII + * emailAddress, are BAD_ARG. */ +static int csr_build_rejects_unprintable_rdn(void) +{ + static const char* const bad[] = { + "C=U_", "CN=dev,serialNumber=ab_c@1", + "CN=dev,emailAddress=j\xc3\xbcrgen@example.de" + }; + WolfCertKeyCfg cfg = { .type = WOLFCERT_KEY_ECC, .param = 256, + .dev_id = WOLFCERT_DEVID_SOFTWARE }; + WolfCertKey* key = NULL; + WolfCertCertMeta meta = { 0 }; + WolfCertBuffer der = { 0 }; + size_t i; + + REQUIRE(wolfcert_key_generate(&cfg, &key) == WOLFCERT_OK); + + for (i = 0; i < sizeof(bad) / sizeof(bad[0]); i++) { + meta.subject_dn = bad[i]; + REQUIRE(wolfcert_csr_build(key, &meta, &der) == WOLFCERT_ERR_BAD_ARG); + REQUIRE(der.data == NULL && der.len == 0); + } + + meta.subject_dn = "C=US,serialNumber=AB-12 (x),emailAddress=dev@example.org"; + REQUIRE(wolfcert_csr_build(key, &meta, &der) == WOLFCERT_OK); + REQUIRE(der.len > 0); + + wolfcert_buffer_free(&der); + wolfcert_key_free(key); + return 0; +} + /* Self-sign the subject and SAN set in c with the test's ECC key, then free * c; returns the DER length. */ static int make_self_cert(Cert* c, const WolfCertKey* key, byte* out, int cap) @@ -851,6 +984,8 @@ int main(void) return 1; #endif #ifdef WOLFCERT_HAVE_ECC + if (csr_build_rejects_unprintable_rdn()) + return 1; if (csr_build_rejects_oversized_rdn()) return 1; if (renewal_keeps_empty_subject(1) || renewal_keeps_empty_subject(0)) @@ -873,6 +1008,10 @@ int main(void) return 1; if (build_and_reparse(WOLFCERT_KEY_ECC, 256)) return 1; + if (ecc_sig_follows_curve()) + return 1; + if (ecc_sig_follows_preferred_hash()) + return 1; #endif #ifdef WOLFCERT_HAVE_RSA if (build_and_reparse(WOLFCERT_KEY_RSA, 2048)) diff --git a/tests/unit/test_net.c b/tests/unit/test_net.c index af4fba7..ebc9a58 100644 --- a/tests/unit/test_net.c +++ b/tests/unit/test_net.c @@ -34,7 +34,6 @@ #include #include #include -#include #include #define REQUIRE(cond) \ @@ -45,13 +44,6 @@ } \ } while (0) -static long mono_ms(void) -{ - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return (long)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; -} - static volatile sig_atomic_t g_sigpipe_raised; static void note_sigpipe(int sig) @@ -151,9 +143,9 @@ int main(void) close(ls); /* An unroutable address must fail well under the OS default (~75s). */ - long t0 = mono_ms(); + int64_t t0 = wolfcert_mono_ms(); int fd2 = wolfcert_posix_connect("10.255.255.1", 9, 250, NULL); - long elapsed = mono_ms() - t0; + int64_t elapsed = wolfcert_mono_ms() - t0; REQUIRE(fd2 < 0); REQUIRE(elapsed < 3000); @@ -170,6 +162,6 @@ int main(void) return 1; #endif - printf("OK (unreachable connect returned in %ldms)\n", elapsed); + printf("OK (unreachable connect returned in %ldms)\n", (long)elapsed); return 0; } diff --git a/tests/unit/test_scep_msg.c b/tests/unit/test_scep_msg.c index d7b8c55..f073bae 100644 --- a/tests/unit/test_scep_msg.c +++ b/tests/unit/test_scep_msg.c @@ -764,11 +764,17 @@ static int check_result_defined(const char* what, int rc, const WolfCertScepResu #ifdef TEST_ALLOC_FAILURES /* Allocations left before failing_malloc() returns NULL; -1 never fails. */ static int g_allocs_left = -1; +static int g_alloc_failed; +static wolfSSL_Malloc_cb g_saved_malloc; +static wolfSSL_Free_cb g_saved_free; +static wolfSSL_Realloc_cb g_saved_realloc; static void* failing_malloc(size_t sz) { - if (g_allocs_left == 0) + if (g_allocs_left == 0) { + g_alloc_failed = 1; return NULL; + } if (g_allocs_left > 0) g_allocs_left--; return malloc(sz); @@ -781,35 +787,53 @@ static void failing_free(void* ptr) static void* failing_realloc(void* ptr, size_t sz) { - if (g_allocs_left == 0) + if (g_allocs_left == 0) { + g_alloc_failed = 1; return NULL; + } if (g_allocs_left > 0) g_allocs_left--; return realloc(ptr, sz); } +/* Lets `n` wolfSSL allocations succeed and fails the rest. */ +static int oom_arm(int n) +{ + if (wolfSSL_GetAllocators(&g_saved_malloc, &g_saved_free, + &g_saved_realloc) != 0) + return -1; + g_allocs_left = n; + g_alloc_failed = 0; + return wolfSSL_SetAllocators(failing_malloc, failing_free, + failing_realloc); +} + +/* Restores the allocators; 1 when an allocation was failed, 0 if not, or -1. */ +static int oom_disarm(void) +{ + g_allocs_left = -1; + if (wolfSSL_SetAllocators(g_saved_malloc, g_saved_free, + g_saved_realloc) != 0) + return -1; + return g_alloc_failed; +} + /* Failing each allocation in turn must give WOLFCERT_ERR_MEMORY or a match. */ static int pem_has_cert_reports_oom(const char* pem, size_t pem_len, const DecodedCert* lc) { - wolfSSL_Malloc_cb mf; - wolfSSL_Free_cb ff; - wolfSSL_Realloc_cb rf; int fails; + int failed; int rc = 0; - REQUIRE(wolfSSL_GetAllocators(&mf, &ff, &rf) == 0); for (fails = 0; rc != 1; fails++) { - g_allocs_left = fails; - REQUIRE(wolfSSL_SetAllocators(failing_malloc, failing_free, - failing_realloc) == 0); + REQUIRE(oom_arm(fails) == 0); rc = wolfcert_scep_pem_has_cert((const uint8_t*)pem, pem_len, lc->issuerRaw, (size_t)lc->issuerRawLen, lc->serial, (size_t)lc->serialSz, NULL); - REQUIRE(wolfSSL_SetAllocators(mf, ff, rf) == 0); - g_allocs_left = -1; - if (rc != 1 && rc != WOLFCERT_ERR_MEMORY) { + REQUIRE((failed = oom_disarm()) >= 0); + if (rc != 1 && (rc != WOLFCERT_ERR_MEMORY || !failed)) { fprintf(stderr, "FAIL pem_has_cert with allocation %d failing: " "%d\n", fails, rc); return 1; @@ -822,22 +846,16 @@ static int pem_has_cert_reports_oom(const char* pem, size_t pem_len, static int rep_signer_reports_oom(const uint8_t* signer, size_t signer_len, const uint8_t* bundle, size_t bundle_len) { - wolfSSL_Malloc_cb mf; - wolfSSL_Free_cb ff; - wolfSSL_Realloc_cb rf; int fails; + int failed; int rc = WOLFCERT_ERR_MEMORY; - REQUIRE(wolfSSL_GetAllocators(&mf, &ff, &rf) == 0); for (fails = 0; rc != WOLFCERT_OK; fails++) { - g_allocs_left = fails; - REQUIRE(wolfSSL_SetAllocators(failing_malloc, failing_free, - failing_realloc) == 0); + REQUIRE(oom_arm(fails) == 0); rc = wolfcert_scep_verify_rep_signer(signer, signer_len, bundle, bundle_len, NULL); - REQUIRE(wolfSSL_SetAllocators(mf, ff, rf) == 0); - g_allocs_left = -1; - if (rc != WOLFCERT_OK && rc != WOLFCERT_ERR_MEMORY) { + REQUIRE((failed = oom_disarm()) >= 0); + if (rc != WOLFCERT_OK && (rc != WOLFCERT_ERR_MEMORY || !failed)) { fprintf(stderr, "FAIL verify_rep_signer with allocation %d " "failing: %d\n", fails, rc); return 1; @@ -2244,6 +2262,7 @@ static int check_text_attrib(const uint8_t* ca_der, size_t ca_len, REQUIRE(make_signed_with_attribs(ca_der, ca_len, ca_key, ca_key_len, &attrib, 1, &msg, &msg_len) == 0); + wolfcert_clear_error(); rc = wolfcert_scep_parse_pki_message(msg, msg_len, &env, NULL, NULL, NULL, NULL, NULL, NULL, &mt, &ps, NULL, NULL, &fi, NULL); @@ -2272,6 +2291,8 @@ static int test_text_attrib_printable(void) static const byte ps_nul[] = { 0x13, 0x02, '2', 0x00 }; static const byte fi_nul[] = { 0x13, 0x03, '0', 0x00, 'x' }; static const byte mt_utf8[] = { 0x0C, 0x01, '3' }; + static const byte ps_short[] = { 0x13, 0x05, '2' }; + static const byte ps_empty[] = { 0x13, 0x00 }; uint8_t* ca_der = NULL; size_t ca_len = 0; uint8_t* ca_key = NULL; @@ -2294,7 +2315,22 @@ static int test_text_attrib_printable(void) check_text_attrib(ca_der, ca_len, ca_key, ca_key_len, scep_oid_msg_type, sizeof(scep_oid_msg_type), mt_utf8, sizeof(mt_utf8), - WOLFCERT_ERR_PROTOCOL, NULL) != 0) { + WOLFCERT_ERR_PROTOCOL, NULL) != 0 || + check_text_attrib(ca_der, ca_len, ca_key, ca_key_len, + scep_oid_pki_status, sizeof(scep_oid_pki_status), + ps_short, sizeof(ps_short), + WOLFCERT_ERR_PROTOCOL, NULL) != 0 || + strstr(wolfcert_last_error_message(), "truncated") == NULL || + check_text_attrib(ca_der, ca_len, ca_key, ca_key_len, + scep_oid_pki_status, sizeof(scep_oid_pki_status), + ps_empty, sizeof(ps_empty), + WOLFCERT_ERR_PROTOCOL, NULL) != 0 || + strstr(wolfcert_last_error_message(), "empty") == NULL || + check_text_attrib(ca_der, ca_len, ca_key, ca_key_len, + scep_oid_pki_status, sizeof(scep_oid_pki_status), + ps_empty, 0, WOLFCERT_ERR_PROTOCOL, NULL) != 0 || + strstr(wolfcert_last_error_message(), "empty") == NULL) { + fprintf(stderr, "text attribute: %s\n", wolfcert_last_error_message()); ret = 1; } @@ -2303,6 +2339,124 @@ static int test_text_attrib_printable(void) return ret; } +/* A tag or length cut short, or a value overrunning its SET, is truncated. */ +static int test_text_attrib_truncated(void) +{ + static const byte cut_tag[] = { 0x13 }; + static const byte cut_len81[] = { 0x13, 0x81 }; + static const byte cut_len82[] = { 0x13, 0x82, 0x00 }; + static const byte indef[] = { 0x13, 0x80, '2', 0x00, 0x00 }; + static const byte cut_set[] = { 0x31, 0x81 }; + static const byte over_set[] = { 0x31, 0x03, 0x13, 0x05, '2' }; + static const struct { + const byte* v; + word32 len; + } cases[] = { + { cut_tag, sizeof(cut_tag) }, + { cut_len81, sizeof(cut_len81) }, + { cut_len82, sizeof(cut_len82) }, + { indef, sizeof(indef) }, + { cut_set, sizeof(cut_set) }, + { over_set, sizeof(over_set) }, + }; + uint8_t* ca_der = NULL; + size_t ca_len = 0; + uint8_t* ca_key = NULL; + size_t ca_key_len = 0; + size_t i; + int ret = 0; + + REQUIRE(make_ca(&ca_der, &ca_len, &ca_key, &ca_key_len) == 0); + + for (i = 0; ret == 0 && i < sizeof(cases) / sizeof(cases[0]); i++) { + if (check_text_attrib(ca_der, ca_len, ca_key, ca_key_len, + scep_oid_pki_status, sizeof(scep_oid_pki_status), + cases[i].v, cases[i].len, + WOLFCERT_ERR_PROTOCOL, NULL) != 0 || + strstr(wolfcert_last_error_message(), "truncated") == NULL) { + fprintf(stderr, "truncated case %zu: %s\n", i, + wolfcert_last_error_message()); + ret = 1; + } + } + + free(ca_der); + free(ca_key); + return ret; +} + +#ifdef TEST_ALLOC_FAILURES +/* Failing each allocation in turn must give an error or a parse with every + * attribute present. */ +static int test_parse_reports_oom(void) +{ + static const byte msg_type[] = { 0x13, 0x02, '1', '9' }; + static const byte tid[] = { 0x13, 0x01, 'A' }; + PKCS7Attrib attribs[2]; + uint8_t* ca_der = NULL; + size_t ca_len = 0; + uint8_t* ca_key = NULL; + size_t ca_key_len = 0; + uint8_t* msg = NULL; + size_t msg_len = 0; + int fails; + int failed; + int rc = WOLFCERT_ERR_MEMORY; + int ret = 0; + + attribs[0].oid = scep_oid_msg_type; + attribs[0].oidSz = sizeof(scep_oid_msg_type); + attribs[0].value = msg_type; + attribs[0].valueSz = sizeof(msg_type); + attribs[1].oid = scep_oid_trans_id; + attribs[1].oidSz = sizeof(scep_oid_trans_id); + attribs[1].value = tid; + attribs[1].valueSz = sizeof(tid); + REQUIRE(make_ca(&ca_der, &ca_len, &ca_key, &ca_key_len) == 0); + REQUIRE(make_signed_with_attribs(ca_der, ca_len, ca_key, ca_key_len, + attribs, 2, &msg, &msg_len) == 0); + + for (fails = 0; ret == 0 && rc != WOLFCERT_OK; fails++) { + WolfCertBuffer env = { 0 }; + uint8_t* t = NULL; + size_t t_len = 0; + uint8_t* signer = NULL; + size_t signer_len = 0; + char* mt = NULL; + + wolfcert_clear_error(); + REQUIRE(oom_arm(fails) == 0); + rc = wolfcert_scep_parse_pki_message(msg, msg_len, &env, &t, &t_len, + NULL, NULL, NULL, NULL, &mt, NULL, &signer, &signer_len, + NULL, NULL); + REQUIRE((failed = oom_disarm()) >= 0); + /* wolfSSL's PKCS#7 verify can report a failed allocation as a parse + * error, so only wolfCert's own allocations must give ERR_MEMORY. */ + if (rc == WOLFCERT_OK ? + (t == NULL || mt == NULL || signer == NULL) : + (!failed || + (rc != WOLFCERT_ERR_MEMORY && + wolfcert_last_wolfssl_err() == 0) || + t != NULL || mt != NULL || signer != NULL || + env.data != NULL)) { + fprintf(stderr, "FAIL parse_pki_message with allocation %d " + "failing: %d\n", fails, rc); + ret = 1; + } + WOLFCERT_XFREE(t, NULL); + WOLFCERT_XFREE(signer, NULL); + WOLFCERT_XFREE(mt, NULL); + wolfcert_buffer_free(&env); + } + + free(msg); + free(ca_der); + free(ca_key); + REQUIRE(fails > 1); + return ret; +} +#endif + int main(void) { REQUIRE(test_static_mem_init() == 0); @@ -2353,8 +2507,14 @@ int main(void) return 1; if (test_multi_value_signed_attrib()) return 1; +#ifdef TEST_ALLOC_FAILURES + if (test_parse_reports_oom()) + return 1; +#endif if (test_text_attrib_printable()) return 1; + if (test_text_attrib_truncated()) + return 1; #ifdef HAVE_ECC if (test_envelop_rejects_ecc_ra()) return 1; diff --git a/tests/unit/test_server_ca_store.c b/tests/unit/test_server_ca_store.c index 27a1312..3a6bd8b 100644 --- a/tests/unit/test_server_ca_store.c +++ b/tests/unit/test_server_ca_store.c @@ -17,6 +17,8 @@ * along with wolfCert. If not, see . */ +#define _POSIX_C_SOURCE 200809L + #include #include #include "internal.h" diff --git a/wolfcert/scep.h b/wolfcert/scep.h index c0c8a27..e996e5a 100644 --- a/wolfcert/scep.h +++ b/wolfcert/scep.h @@ -41,6 +41,8 @@ typedef struct { int get_next_ca_cert; /* GetCACaps: GetNextCACert */ } WolfCertScepCaps; +/* GET ?operation=GetCACaps. A non-200 reply returns WOLFCERT_ERR_HTTP with + * out_caps all zero. */ WOLFCERT_API int wolfcert_scep_get_ca_caps(const WolfCertServerCfg* srv, WolfCertScepCaps* out_caps); diff --git a/wolfcert/types.h b/wolfcert/types.h index 26e8c63..b348347 100644 --- a/wolfcert/types.h +++ b/wolfcert/types.h @@ -100,7 +100,10 @@ typedef struct { * Values cannot hold ','; * customize can set one. A * CTC_NAME_SIZE-byte or longer - * value is WOLFCERT_ERR_BAD_ARG. */ + * value, a C or serialNumber + * that is not a PrintableString, + * or a non-ASCII emailAddress is + * WOLFCERT_ERR_BAD_ARG. */ const char* const* san_dns; /* array of length san_dns_len */ size_t san_dns_len; const char* const* san_ip; /* textual IPv4/IPv6 */ @@ -117,7 +120,8 @@ typedef struct { const char* challenge_password; /* Optional signature hash size in bits (256 / 384 / 512); 0 uses the key - * type's default. Ed25519, Ed448 and ML-DSA ignore it. */ + * type's default. A hash the wolfSSL build lacks is + * WOLFCERT_ERR_UNSUPPORTED. Ed25519, Ed448 and ML-DSA ignore it. */ int preferred_hash; /* Optional: invoked after standard fields are set, before signing. */