diff --git a/AGENTS.md b/AGENTS.md index fb2cd70..bdcd01d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -188,7 +188,7 @@ wolfcert_cleanup(); | `wolfcert/types.h` | `WolfCertServerCfg`, `WolfCertKeyCfg`, `WolfCertCertMeta`, `WolfCertTransport`, `wolfcert_buffer_free` | | `wolfcert/keygen.h`, `wolfcert/csr.h` | Key generation, import / export, CSR building | | `wolfcert/store.h` | `WolfCertStoreOps` storage vtable, POSIX and in-memory backends | -| `wolfcert/errors.h`, `wolfcert/status.h`, `wolfcert/log.h` | Error codes, per-thread error detail, log callback | +| `wolfcert/errors.h`, `wolfcert/status.h`, `wolfcert/log.h` | Error codes, last-error detail, log callback | | `wolfcert/memory.h` | Default heap hint, allocation macros | | `examples/` | `enroll_est.c`, `enroll_scep.c`, `enroll_cryptocb.c`, `user_settings.h.example` | | `examples/certs/` | Test credentials; never ship them | @@ -229,7 +229,7 @@ contract, alone or in a group comment. `getnextca` / `getcert` (SCEP), which is handy for checking a production CA before writing code against it. - On Zephyr, the EST client sample runs against a host `wolfcert-server`; - `zephyr/README.md` has the steps and the per-thread stack sizing. + `zephyr/README.md` has the steps and the stack sizing. ## Gotchas @@ -257,7 +257,8 @@ contract, alone or in a group comment. `proto_opts.scep.content_cipher` where the network is untrusted. - **Error detail:** `wolfcert_strerror(rc)` names the code; `wolfcert_last_error_message()` and `wolfcert_last_wolfssl_err()` give the - per-thread detail behind it. + detail behind it, per thread when wolfSSL's `THREAD_LS_T` is thread-local + (`HAVE_THREAD_LS`, outside FreeRTOS and Zephyr). ## Further documentation diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d46b007..a04f4ec 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -118,4 +118,4 @@ Email **support@wolfssl.com** instead, and please keep the issue private until a 3. Add the new files to `EXTRA_DIST` in `zephyr/include.am`. 4. Add a job to `.github/workflows/zephyr.yml` modelled on the `mcxn` job: the `zephyr-workspace` action with the board's SDK toolchain, twister `--build-only` for the board, then `scripts/ci/twister-assert-ran.py --built-only `. 5. Add a row to both board tables under "EST client sample" in `zephyr/README.md`. -6. Run it on the hardware: enroll against a host `wolfcert-server`, and check every thread's stack headroom with `CONFIG_THREAD_ANALYZER`, since the thread-local data takes room on each one ("Sizing" in `zephyr/README.md`). +6. Run it on the hardware: enroll against a host `wolfcert-server`, and check every thread's stack headroom with `CONFIG_THREAD_ANALYZER`. diff --git a/Makefile.am b/Makefile.am index 2bf6f70..395c68e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -112,7 +112,7 @@ check_PROGRAMS += test_net endif test_smoke_SOURCES = tests/unit/test_smoke.c -test_smoke_LDADD = libwolfcert.la $(WOLFSSL_LIBS) +test_smoke_LDADD = libwolfcert.la $(WOLFSSL_LIBS) -lpthread test_keygen_SOURCES = tests/unit/test_keygen.c test_keygen_LDADD = libwolfcert.la $(WOLFSSL_LIBS) test_csr_SOURCES = tests/unit/test_csr.c diff --git a/README.md b/README.md index 65f5b71..5465fe3 100644 --- a/README.md +++ b/README.md @@ -197,7 +197,8 @@ All `WolfCertBuffer` outputs remember which heap they came from, so the same build). Keys run in software or through any wolfSSL CryptoCb backend. - **SCEP is RSA-only** (per RFC 8894); use EST for the other key types. - **EST endpoints:** `/cacerts`, `/simpleenroll`, `/simplereenroll`, - `/csrattrs`. + `/csrattrs`. The RFC 7030 section 3.5 tls-unique binding is neither sent + nor checked; TLS 1.3 does not define tls-unique. - **SCEP operations:** `GetCACaps`, `GetCACert`, `GetNextCACert`, `PKCSReq`, `RenewalReq`, `GetCert`, plus `pkiStatus=PENDING` + `GetCertInitial` polling. - **Transport:** HTTP/1.1 over wolfSSL TLS 1.3/1.2 (trust anchors, SNI, diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index cdea1f5..eb75cf7 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -55,7 +55,7 @@ A few constraints shape the whole library: disabled subsystems disappear from the link entirely. 7. **Minimal, explicit error reporting.** Every function returns a small negative `WOLFCERT_ERR_*` code; extended diagnostics (module, underlying - wolfSSL error, message) are recorded in thread-local state and retrieved + wolfSSL error, message) are recorded in last-error state and retrieved via `wolfcert_last_error_message()` / `wolfcert_last_wolfssl_err()`. ### What wolfCert is not @@ -418,8 +418,11 @@ wolfcert_set_log_cb(my_log, NULL); wolfcert_set_log_level(WOLFCERT_LOG_INFO); /* default is WARN */ ``` -Extended error state is thread-local. On metal without TLS support, define -`WOLFCERT_NO_THREAD_LOCAL` and it degrades to a global. After any failing call: +Extended error state is per-thread when wolfSSL's `THREAD_LS_T` is +thread-local: `HAVE_THREAD_LS`, which its configure enables where the compiler +supports it, without `NO_THREAD_LS`, and not on FreeRTOS or Zephyr, where +wolfSSL leaves `THREAD_LS_T` empty. Otherwise (RTOS and bare-metal builds) the +state is one global, so keep wolfCert calls on one thread. After any failing call: ```c int rc = wolfcert_est_simple_enroll(&srv, csr, csr_len, &cert); @@ -431,7 +434,7 @@ if (rc != WOLFCERT_OK) { } ``` -The message string is valid until the next wolfCert call on the same thread. +The message string is valid until the error state next changes. ### 4.6 Pluggable transport diff --git a/docs/EMBEDDED.md b/docs/EMBEDDED.md index 5840812..e05cb21 100644 --- a/docs/EMBEDDED.md +++ b/docs/EMBEDDED.md @@ -252,6 +252,12 @@ If your target is single-threaded, build wolfSSL with `SINGLE_THREADED`. wolfCert holds no locks of its own (init/cleanup delegate refcounting to `wolfSSL_Init`/`wolfSSL_Cleanup`), so there is nothing extra to configure. +The error state behind `wolfcert_last_error_message()` is per-thread only +when wolfSSL's `THREAD_LS_T` is thread-local, which takes `HAVE_THREAD_LS` +and is never the case on FreeRTOS or Zephyr. Otherwise, as in most RTOS and +`user_settings.h` builds, it is one global, so make the wolfCert calls whose +errors you read from one thread. + ## 6. Algorithms & protocols Strip unused key algorithms and protocols at configure time so their code @@ -313,9 +319,7 @@ overwrites the idle thread and the image dies later in the interrupt handler rather than reporting a stack overflow. Build with `CONFIG_HW_STACK_PROTECTION=y` while tuning `CONFIG_MAIN_STACK_SIZE` so an overflow names itself. The x86 default `CONFIG_ISR_STACK_SIZE` (2048) is also -thin once the networking stack is in the image. Every thread's stack also -holds the thread-local data; -[`zephyr/README.md` (Sizing)](../zephyr/README.md#sizing) covers how much. +thin once the networking stack is in the image. **A clock is not optional.** Without a set wall clock every certificate looks not-yet-valid and the failure surfaces as a trust-anchor error, not a clock diff --git a/docs/MIGRATING-FROM-WOLFSCEP.md b/docs/MIGRATING-FROM-WOLFSCEP.md index b4dad5b..27a1116 100644 --- a/docs/MIGRATING-FROM-WOLFSCEP.md +++ b/docs/MIGRATING-FROM-WOLFSCEP.md @@ -75,7 +75,7 @@ wolfSCEP_response(scep, answer, &answerSz, 0); if (wolfSCEP_reply_status(scep) == WS_PKI_SUCCESS) { /* ... */ } ``` -The same enrollment in wolfCert, which is `examples/enroll_scep.c` with the fingerprint check added: +The same enrollment in wolfCert; `examples/enroll_scep.c` is a runnable version of it: ```c WolfCertServerCfg srv = { @@ -152,7 +152,7 @@ srv.proto_opts.scep.content_cipher = WOLFCERT_SCEP_CIPHER_AES256; **Fingerprint verification is now the library's job.** Delete your helper and call `wolfcert_scep_verify_ca_fingerprint`, which hashes the DER certificate and compares in constant time. -**Error codes are a different, smaller set.** wolfSCEP's `WS_*` codes map onto `wolfcert/errors.h`: `WS_BAD_ARGUMENT` to `WOLFCERT_ERR_BAD_ARG`, `WS_MEMORY_E` to `WOLFCERT_ERR_MEMORY`, `WS_PARSE_E` to `WOLFCERT_ERR_PARSE`, the `WS_CBIO_ERR_*` family to `WOLFCERT_ERR_IO` and, in non-blocking mode only, to `WOLFCERT_ERR_WANT_READ` and `WOLFCERT_ERR_WANT_WRITE`. There is no per-call error string; use `wolfcert_strerror` for the code and `wolfcert_last_error_message` for the per-thread detail. +**Error codes are a different, smaller set.** wolfSCEP's `WS_*` codes map onto `wolfcert/errors.h`: `WS_BAD_ARGUMENT` to `WOLFCERT_ERR_BAD_ARG`, `WS_MEMORY_E` to `WOLFCERT_ERR_MEMORY`, `WS_PARSE_E` to `WOLFCERT_ERR_PARSE`, the `WS_CBIO_ERR_*` family to `WOLFCERT_ERR_IO` and, in non-blocking mode only, to `WOLFCERT_ERR_WANT_READ` and `WOLFCERT_ERR_WANT_WRITE`. There is no per-call error string; use `wolfcert_strerror` for the code and `wolfcert_last_error_message` for the detail. **TLS is optional for SCEP and mandatory for EST.** SCEP authenticates inside the pkiMessage, so a plaintext `http://` endpoint is accepted. An `https://` endpoint requires `srv.verify_server`: every SCEP entry point, one-shot and session alike, refuses to run unverified rather than completing a silent anonymous handshake, because `verify_server` is the only peer-verification switch in the transport. diff --git a/examples/enroll_scep.c b/examples/enroll_scep.c index 521e953..c002761 100644 --- a/examples/enroll_scep.c +++ b/examples/enroll_scep.c @@ -21,10 +21,11 @@ * Minimal SCEP enrollment example. * * Usage: - * enroll_scep + * enroll_scep * - * Fetches the CA cert via GetCACert, then PKCSReq-enrolls a fresh 2048-bit - * RSA key. Writes the issued cert in PEM to stdout. + * Fetches the CA cert via GetCACert, checks it against the SHA-256 + * fingerprint, then PKCSReq-enrolls a fresh 2048-bit RSA key. Writes the + * issued cert in PEM to stdout. */ #include @@ -41,10 +42,38 @@ /* SCEP is RSA-only (RFC 8894); other key types enroll over EST. */ +static int hex_decode(const char* hex, uint8_t* out, size_t out_len) +{ + size_t i; + + if (strlen(hex) != 2 * out_len) + return -1; + + for (i = 0; i < 2 * out_len; i++) { + char c = hex[i]; + int v = (c >= '0' && c <= '9') ? c - '0' + : (c >= 'a' && c <= 'f') ? c - 'a' + 10 + : (c >= 'A' && c <= 'F') ? c - 'A' + 10 : -1; + if (v < 0) + return -1; + if (i % 2 == 0) + out[i / 2] = (uint8_t)(v << 4); + else + out[i / 2] |= (uint8_t)v; + } + return 0; +} + int main(int argc, char** argv) { - if (argc < 3) { - fprintf(stderr, "usage: %s URL SUBJECT\n", argv[0]); + uint8_t fp[32]; + + if (argc < 4) { + fprintf(stderr, "usage: %s URL SUBJECT CA_SHA256_HEX\n", argv[0]); + return 1; + } + if (hex_decode(argv[3], fp, sizeof(fp)) != 0) { + fprintf(stderr, "CA fingerprint must be 64 hex digits\n"); return 1; } @@ -63,6 +92,15 @@ int main(int argc, char** argv) if (wc_PemToDer(ca_pem.data, (long)ca_pem.len, CERT_TYPE, &ca_der, NULL, NULL, NULL) != 0) return 2; + /* GetCACert is unauthenticated over plain HTTP, so pin the CA. */ + int rc = wolfcert_scep_verify_ca_fingerprint(ca_der->buffer, + ca_der->length, fp, sizeof(fp), WOLFCERT_SCEP_FP_SHA256); + if (rc != WOLFCERT_OK) { + fprintf(stderr, "CA fingerprint check failed: %s\n", + wolfcert_strerror(rc)); + return 2; + } + /* 2) Optionally query capabilities. */ WolfCertScepCaps caps = { 0 }; wolfcert_scep_get_ca_caps(&srv, &caps); @@ -81,8 +119,8 @@ int main(int argc, char** argv) /* 4) PKCSReq. */ WolfCertBuffer cert = { 0 }; - int rc = wolfcert_scep_pkcs_req(&srv, &caps, ca_der->buffer, ca_der->length, - key, csr.data, csr.len, &cert); + rc = wolfcert_scep_pkcs_req(&srv, &caps, ca_der->buffer, ca_der->length, + key, csr.data, csr.len, &cert); if (rc != WOLFCERT_OK) { fprintf(stderr, "PKCSReq failed: %s\n", wolfcert_strerror(rc)); return 2; diff --git a/examples/user_settings.h.example b/examples/user_settings.h.example index 0cb0e72..abbf035 100644 --- a/examples/user_settings.h.example +++ b/examples/user_settings.h.example @@ -50,4 +50,8 @@ #define WOLFCERT_HAVE_ED448 1 #define WOLFCERT_HAVE_MLDSA 1 +/* wolfSSL setting for a hosted target sharing this file: makes the error + * state per-thread (wolfSSL ignores it on FreeRTOS and Zephyr). */ +/* #define HAVE_THREAD_LS */ + #endif /* WOLFCERT_USER_SETTINGS_H */ diff --git a/scripts/ci/build-wolfssl.sh b/scripts/ci/build-wolfssl.sh index abcab84..1072bd6 100755 --- a/scripts/ci/build-wolfssl.sh +++ b/scripts/ci/build-wolfssl.sh @@ -182,10 +182,11 @@ done # One flag per line keeps the spaces inside CPPFLAGS. No mapfile: macOS # runners run this under /bin/bash 3.2 on a cache miss. +_flags=$(resolve_flags "$CONFIG") || exit 2 CONFIGURE_FLAGS=() while IFS= read -r _flag; do CONFIGURE_FLAGS+=("$_flag") -done < <(resolve_flags "$CONFIG") +done <<< "$_flags" : "${PREFIX:=$PWD/.wolfssl-install/$CONFIG}" : "${JOBS:=$( (command -v nproc >/dev/null && nproc) || sysctl -n hw.ncpu 2>/dev/null || echo 2)}" diff --git a/src/est/est_server.c b/src/est/est_server.c index b6aee7d..0946de2 100644 --- a/src/est/est_server.c +++ b/src/est/est_server.c @@ -116,6 +116,8 @@ typedef struct { static void free_req(EstRequest* r) { + if (r->auth_header != NULL) + wc_ForceZero(r->auth_header, (word32)strlen(r->auth_header)); WOLFCERT_XFREE(r->auth_header, r->heap); WOLFCERT_XFREE(r->body, r->heap); memset(r, 0, sizeof(*r)); @@ -235,14 +237,14 @@ static int hdr_is(const char* line, size_t llen, const char* name) wolfcert_ascii_ncasecmp(line, name, n) == 0; } -static int parse_request(WolfCertServer* s, int fd, EstRequest* out, void* heap) +static int parse_request_in(WolfCertServer* s, int fd, EstRequest* out, + void* heap, char* buf, size_t buf_sz) { memset(out, 0, sizeof(*out)); out->heap = heap; - char buf[WOLFCERT_HTTP_REQ_BUF_SZ]; size_t n = 0; - while (n < sizeof(buf) - 1) { - ssize_t r = wolfcert_io_recv(s, fd, buf + n, sizeof(buf) - 1 - n); + while (n < buf_sz - 1) { + ssize_t r = wolfcert_io_recv(s, fd, buf + n, buf_sz - 1 - n); if (r <= 0) return WOLFCERT_ERR_IO; @@ -392,7 +394,7 @@ static int parse_request(WolfCertServer* s, int fd, EstRequest* out, void* heap) if (csz == 0) break; - if (ri + csz > raw_len || body_sz + csz > BODY_CAP) { + if (csz > raw_len - ri || csz > BODY_CAP - body_sz) { WOLFCERT_XFREE(body, heap); WOLFCERT_XFREE(raw, heap); return WOLFCERT_ERR_PROTOCOL; @@ -454,6 +456,16 @@ static int parse_request(WolfCertServer* s, int fd, EstRequest* out, void* heap) return WOLFCERT_OK; } +/* The header block can carry Basic credentials. */ +static int parse_request(WolfCertServer* s, int fd, EstRequest* out, void* heap) +{ + char buf[WOLFCERT_HTTP_REQ_BUF_SZ]; + int rc = parse_request_in(s, fd, out, heap, buf, sizeof(buf)); + + wc_ForceZero(buf, (word32)sizeof(buf)); + return rc; +} + static void send_all(WolfCertServer* s, int fd, const void* buf, size_t len) { const uint8_t* p = buf; @@ -546,7 +558,9 @@ static int check_basic_auth(const WolfCertServer* s, const char* auth_header) memcpy(raw + ul + 1, s->cfg_basic_pass, pl); WolfCertBuffer enc = { 0 }; - if (wolfcert_base64_encode(raw, ul + 1 + pl, &enc, s->heap) != WOLFCERT_OK) + int erc = wolfcert_base64_encode(raw, ul + 1 + pl, &enc, s->heap); + wc_ForceZero(raw, (word32)sizeof(raw)); + if (erc != WOLFCERT_OK) return 0; /* Constant-time compare of the base64 credential. */ @@ -555,7 +569,7 @@ static int check_basic_auth(const WolfCertServer* s, const char* auth_header) if (ok) ok = (wc_ConstantCompare((const byte*)tok, enc.data, (int)enc.len) == 0); - wolfcert_buffer_free(&enc); + wolfcert_buffer_free_secure(&enc); return ok; } diff --git a/src/internal.c b/src/internal.c index fe4268e..ed1d1e6 100644 --- a/src/internal.c +++ b/src/internal.c @@ -32,14 +32,6 @@ static WolfCertLogCb g_log_cb = NULL; static void* g_log_ctx = NULL; static WolfCertLogLevel g_log_level = WOLFCERT_LOG_WARN; -#if defined(__STDC_NO_THREADS__) || defined(WOLFCERT_NO_THREAD_LOCAL) -# define WOLFCERT_TLS /* empty */ -#elif defined(__GNUC__) || defined(__clang__) -# define WOLFCERT_TLS __thread -#else -# define WOLFCERT_TLS /* empty */ -#endif - typedef struct { int wolfcert_rc; int wolfssl_rc; @@ -47,7 +39,7 @@ typedef struct { char message[256]; } WolfCertErrState; -static WOLFCERT_TLS WolfCertErrState g_err; +static THREAD_LS_T WolfCertErrState g_err; void wolfcert_set_default_heap(void* heap) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8edf576..567debc 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,7 +1,8 @@ # SPDX-License-Identifier: GPL-3.0-or-later +find_package(Threads REQUIRED) add_executable(test_smoke unit/test_smoke.c) -target_link_libraries(test_smoke PRIVATE wolfcert) +target_link_libraries(test_smoke PRIVATE wolfcert Threads::Threads) add_test(NAME smoke COMMAND test_smoke) add_executable(test_keygen unit/test_keygen.c) @@ -24,7 +25,6 @@ if(WOLFCERT_ENABLE_BUILTIN_TRANSPORT) add_test(NAME net COMMAND test_net) endif() -find_package(Threads REQUIRED) add_executable(test_transport unit/test_transport.c) target_include_directories(test_transport PRIVATE ${CMAKE_SOURCE_DIR}/src) target_link_libraries(test_transport PRIVATE wolfcert) diff --git a/tests/integration/test_est_tls_roundtrip.c b/tests/integration/test_est_tls_roundtrip.c index 992a296..51c8483 100644 --- a/tests/integration/test_est_tls_roundtrip.c +++ b/tests/integration/test_est_tls_roundtrip.c @@ -55,6 +55,32 @@ static void* server_thread(void* arg) { wolfcert_server_run((WolfCertServer*)arg); return NULL; } +static int cn_is(const DecodedCert* dc, const char* cn) +{ + return dc->subjectCN != NULL && dc->subjectCNLen == (int)strlen(cn) && + memcmp(dc->subjectCN, cn, strlen(cn)) == 0; +} + +/* The issued cert's subject CN must equal cn. */ +static int check_subject_cn(const WolfCertBuffer* issued, const char* cn) +{ + uint8_t der[4096]; + DecodedCert dc; + int der_len; + int match; + + der_len = wc_CertPemToDer(issued->data, (int)issued->len, der, + (int)sizeof(der), CERT_TYPE); + REQUIRE(der_len > 0); + + wc_InitDecodedCert(&dc, der, (word32)der_len, NULL); + match = wc_ParseCert(&dc, CERT_TYPE, NO_VERIFY, NULL) == 0 && + cn_is(&dc, cn); + wc_FreeDecodedCert(&dc); + REQUIRE(match); + return 0; +} + #ifdef KEEP_PEER_CERT static int impostor_customize(void* wolfssl_cert, void* ctx) { @@ -80,12 +106,9 @@ static int check_renewed_identity(const WolfCertBuffer* issued) REQUIRE(der_len > 0); wc_InitDecodedCert(&dc, der, (word32)der_len, NULL); - REQUIRE(wc_ParseCert(&dc, CERT_TYPE, NO_VERIFY, NULL) == 0); - REQUIRE(dc.subjectCN != NULL); - REQUIRE(dc.subjectCNLen == (int)strlen("reenroll-device")); - REQUIRE(memcmp(dc.subjectCN, "reenroll-device", - strlen("reenroll-device")) == 0); - found = has_alt(dc.altNames, ASN_IP_TYPE, (const char*)ip, + found = wc_ParseCert(&dc, CERT_TYPE, NO_VERIFY, NULL) == 0 && + cn_is(&dc, "reenroll-device") && + has_alt(dc.altNames, ASN_IP_TYPE, (const char*)ip, (int)sizeof(ip)); wc_FreeDecodedCert(&dc); REQUIRE(found); @@ -375,8 +398,7 @@ int main(void) fprintf(stderr, "enroll rc=%d (%s)\n", rc, wolfcert_strerror(rc)); REQUIRE(rc == WOLFCERT_OK); REQUIRE(memmem(issued.data, issued.len, "BEGIN CERTIFICATE", 17) != NULL); - REQUIRE(memmem(issued.data, issued.len, "tls-est-client", 14) != NULL || - issued.len > 0); /* PEM carries subject in DER, not ASCII */ + REQUIRE(check_subject_cn(&issued, "tls-est-client") == 0); /* The same trust anchor in DER form. */ DerBuffer* ta_der = NULL; diff --git a/tests/test_static_mem.h b/tests/test_static_mem.h index ae23ff7..40ceacf 100644 --- a/tests/test_static_mem.h +++ b/tests/test_static_mem.h @@ -41,13 +41,18 @@ static inline int test_static_mem_init(void) sizeof(g_test_static_pool), WOLFMEM_GENERAL, 1) != 0) return -1; wolfSSL_SetGlobalHeapHint(g_test_heap_hint); - wolfcert_set_default_heap(g_test_heap_hint); return 0; } +static inline void* test_heap_hint(void) +{ + return g_test_heap_hint; +} + #else static inline int test_static_mem_init(void) { return 0; } +static inline void* test_heap_hint(void) { return NULL; } #endif /* WOLFSSL_STATIC_MEMORY && WOLFSSL_NO_MALLOC */ diff --git a/tests/unit/test_csr.c b/tests/unit/test_csr.c index b130d93..02df008 100644 --- a/tests/unit/test_csr.c +++ b/tests/unit/test_csr.c @@ -844,7 +844,7 @@ static int renewal_size_limits(void) int main(void) { REQUIRE(test_static_mem_init() == 0); - REQUIRE(wolfcert_init(NULL) == WOLFCERT_OK); + REQUIRE(wolfcert_init(test_heap_hint()) == WOLFCERT_OK); #ifdef WOLFCERT_HAVE_SERVER if (subject_copy_rejects_oversized_rdn()) diff --git a/tests/unit/test_csr_attrs.c b/tests/unit/test_csr_attrs.c index b2bd61b..bd1d22c 100644 --- a/tests/unit/test_csr_attrs.c +++ b/tests/unit/test_csr_attrs.c @@ -64,7 +64,7 @@ static const uint8_t OID_SECP384R1[] = { 0x2B, 0x81, 0x04, 0x00, 0x22 }; int main(void) { REQUIRE(test_static_mem_init() == 0); - REQUIRE(wolfcert_init(NULL) == WOLFCERT_OK); + REQUIRE(wolfcert_init(test_heap_hint()) == WOLFCERT_OK); /* Empty input gives no items and zero hints. */ { diff --git a/tests/unit/test_est.c b/tests/unit/test_est.c index d7104e5..bed2567 100644 --- a/tests/unit/test_est.c +++ b/tests/unit/test_est.c @@ -564,7 +564,7 @@ int main(void) signal(SIGPIPE, SIG_IGN); REQUIRE(test_static_mem_init() == 0); - REQUIRE(wolfcert_init(NULL) == WOLFCERT_OK); + REQUIRE(wolfcert_init(test_heap_hint()) == WOLFCERT_OK); if (test_oid_to_dotted()) return 1; diff --git a/tests/unit/test_keygen.c b/tests/unit/test_keygen.c index b700cc7..91bd68a 100644 --- a/tests/unit/test_keygen.c +++ b/tests/unit/test_keygen.c @@ -69,7 +69,7 @@ static int roundtrip(WolfCertKeyType type, int param) int main(void) { REQUIRE(test_static_mem_init() == 0); - REQUIRE(wolfcert_init(NULL) == WOLFCERT_OK); + REQUIRE(wolfcert_init(test_heap_hint()) == WOLFCERT_OK); #ifdef WOLFCERT_HAVE_ECC if (roundtrip(WOLFCERT_KEY_ECC, 256)) return 1; diff --git a/tests/unit/test_parse_negative.c b/tests/unit/test_parse_negative.c index 06a6ce3..e4c4352 100644 --- a/tests/unit/test_parse_negative.c +++ b/tests/unit/test_parse_negative.c @@ -243,7 +243,7 @@ static int test_csr_pem(void) int main(void) { REQUIRE(test_static_mem_init() == 0); - REQUIRE(wolfcert_init(NULL) == WOLFCERT_OK); + REQUIRE(wolfcert_init(test_heap_hint()) == WOLFCERT_OK); if (test_url()) return 1; if (test_base64()) diff --git a/tests/unit/test_scep_msg.c b/tests/unit/test_scep_msg.c index e0fdf3c..d7b8c55 100644 --- a/tests/unit/test_scep_msg.c +++ b/tests/unit/test_scep_msg.c @@ -2306,7 +2306,7 @@ static int test_text_attrib_printable(void) int main(void) { REQUIRE(test_static_mem_init() == 0); - REQUIRE(wolfcert_init(NULL) == WOLFCERT_OK); + REQUIRE(wolfcert_init(test_heap_hint()) == WOLFCERT_OK); if (test_getca_url()) return 1; if (test_scep_rejects_est_cfg()) diff --git a/tests/unit/test_server_ca_store.c b/tests/unit/test_server_ca_store.c index 6ef4f91..27a1312 100644 --- a/tests/unit/test_server_ca_store.c +++ b/tests/unit/test_server_ca_store.c @@ -955,7 +955,7 @@ static int test_bad_bind_host_rejected(void) int main(void) { REQUIRE(test_static_mem_init() == 0); - REQUIRE(wolfcert_init(NULL) == WOLFCERT_OK); + REQUIRE(wolfcert_init(test_heap_hint()) == WOLFCERT_OK); #if CA_STORE_NEEDS_TLS REQUIRE(gen_server_identity(&srv_cert_pem, &srv_cert_pem_len, diff --git a/tests/unit/test_smoke.c b/tests/unit/test_smoke.c index d1eff5a..af6317a 100644 --- a/tests/unit/test_smoke.c +++ b/tests/unit/test_smoke.c @@ -23,13 +23,52 @@ #include #include +/* wolfSSL leaves THREAD_LS_T empty on FreeRTOS and Zephyr. */ +#if defined(HAVE_THREAD_LS) && !defined(NO_THREAD_LS) && \ + !defined(FREERTOS) && !defined(FREERTOS_TCP) && !defined(WOLFSSL_ZEPHYR) +#define TEST_ERR_PER_THREAD +#endif + +#ifdef TEST_ERR_PER_THREAD +#include + +static void* fail_keygen(void* arg) +{ + WolfCertKeyCfg cfg = { .type = (WolfCertKeyType)999 }; + WolfCertKey* key = NULL; + int* ok = (int*)arg; + + *ok = wolfcert_key_generate(&cfg, &key) == WOLFCERT_ERR_UNSUPPORTED && + wolfcert_last_error_message()[0] != '\0'; + return NULL; +} + +/* An error on another thread leaves this thread's error state alone. */ +static int error_state_is_per_thread(void) +{ + pthread_t t; + int ok = 0; + + wolfcert_clear_error(); + if (pthread_create(&t, NULL, fail_keygen, &ok) != 0 || + pthread_join(t, NULL) != 0 || !ok) + return 1; + if (wolfcert_last_error_message()[0] != '\0') { + fprintf(stderr, "error state leaked across threads: %s\n", + wolfcert_last_error_message()); + return 1; + } + return 0; +} +#endif + int main(void) { if (test_static_mem_init() != 0) { fprintf(stderr, "static mem init failed\n"); return 1; } - if (wolfcert_init(NULL) != WOLFCERT_OK) { + if (wolfcert_init(test_heap_hint()) != WOLFCERT_OK) { fprintf(stderr, "wolfcert_init failed\n"); return 1; } @@ -42,6 +81,12 @@ int main(void) wolfcert_cleanup(); return 1; } +#ifdef TEST_ERR_PER_THREAD + if (error_state_is_per_thread() != 0) { + wolfcert_cleanup(); + return 1; + } +#endif printf("wolfCert %s\n", v); wolfcert_cleanup(); return 0; diff --git a/tests/unit/test_store.c b/tests/unit/test_store.c index 10b59f6..2b513a7 100644 --- a/tests/unit/test_store.c +++ b/tests/unit/test_store.c @@ -109,7 +109,7 @@ static int test_memory(void) int main(void) { REQUIRE(test_static_mem_init() == 0); - REQUIRE(wolfcert_init(NULL) == WOLFCERT_OK); + REQUIRE(wolfcert_init(test_heap_hint()) == WOLFCERT_OK); #ifdef WOLFCERT_HAVE_POSIX_STORE if (test_posix()) return 1; diff --git a/tests/unit/test_transport.c b/tests/unit/test_transport.c index df5d34b..d425ca8 100644 --- a/tests/unit/test_transport.c +++ b/tests/unit/test_transport.c @@ -1075,7 +1075,7 @@ static int test_no_builtin_transport(void) int main(void) { REQUIRE(test_static_mem_init() == 0); - REQUIRE(wolfcert_init(NULL) == WOLFCERT_OK); + REQUIRE(wolfcert_init(test_heap_hint()) == WOLFCERT_OK); if (test_roundtrip()) return 1; if (test_byte_at_a_time()) diff --git a/wolfcert/status.h b/wolfcert/status.h index 8207242..a7ca001 100644 --- a/wolfcert/status.h +++ b/wolfcert/status.h @@ -19,9 +19,9 @@ /** * @file status.h - * Detail behind the last WOLFCERT_ERR_* code. The state is per-thread on GCC - * and Clang unless __STDC_NO_THREADS__ or WOLFCERT_NO_THREAD_LOCAL is - * defined, and global otherwise. + * Detail behind the last WOLFCERT_ERR_* code. The state is per-thread when + * wolfSSL's THREAD_LS_T is thread-local (HAVE_THREAD_LS without NO_THREAD_LS, + * outside FreeRTOS and Zephyr), and global otherwise. */ #ifndef WOLFCERT_STATUS_H diff --git a/wolfcert/types.h b/wolfcert/types.h index cde97c4..26e8c63 100644 --- a/wolfcert/types.h +++ b/wolfcert/types.h @@ -83,7 +83,7 @@ typedef enum { typedef struct { WolfCertKeyType type; - int param; /* RSA bits (2048/3072/4096) or ECC curve id */ + int param; /* RSA 2048/3072/4096 or ECC 256/384/521 bits */ int dev_id; /* wolfSSL CryptoCb devId; -1 for software */ const char* key_label; /* optional backend identifier for persistent keys */ void* heap; /* optional heap hint; NULL = default */ @@ -112,12 +112,6 @@ typedef struct { const char* key_usage; /* comma-separated */ const char* extended_key_usage; /* comma-separated EKU names or OIDs */ - /* Raw CSR attribute DER. wolfcert_csr_build does not add it to the CSR - * yet; customize can set other attributes on the wolfSSL Cert, and - * wolfcert_csr_attrs_apply maps the typed /csrattrs hints. */ - const uint8_t* csr_attributes_der; - size_t csr_attributes_der_len; - /* Optional PKCS#9 challengePassword CSR attribute, used by SCEP (RFC 8894 * section 2.4); NULL emits none. At most CTC_NAME_SIZE - 1 chars. */ const char* challenge_password; diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index 77b9d8b..3c048ff 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -82,11 +82,6 @@ if(CONFIG_WOLFCERT) # CONFIG_APP_LINK_WITH_WOLFSSL being left at its default. target_link_libraries(wolfCert INTERFACE wolfSSL) - # Without thread-local storage the last-error slot is shared by all threads. - if(NOT CONFIG_THREAD_LOCAL_STORAGE) - zephyr_library_compile_definitions(WOLFCERT_NO_THREAD_LOCAL) - endif() - if(CONFIG_WOLFCERT_BUILD_TESTING) target_compile_definitions(wolfCert INTERFACE WOLFCERT_BUILD_TESTING) zephyr_library_compile_definitions(WOLFCERT_BUILD_TESTING) diff --git a/zephyr/Kconfig b/zephyr/Kconfig index c53394b..67f2b6e 100644 --- a/zephyr/Kconfig +++ b/zephyr/Kconfig @@ -15,7 +15,6 @@ menuconfig WOLFCERT depends on WOLFSSL select NETWORKING select POSIX_API - imply THREAD_LOCAL_STORAGE help Build wolfCert as a Zephyr module. See the top-level README.md for what the library does and zephyr/README.md for the integration. diff --git a/zephyr/README.md b/zephyr/README.md index af1dc98..ada0aed 100644 --- a/zephyr/README.md +++ b/zephyr/README.md @@ -79,15 +79,6 @@ See `docs/EMBEDDED.md` section 8. The short version: build with `CONFIG_HW_STACK_PROTECTION=y` while tuning `CONFIG_MAIN_STACK_SIZE`, because a stack overflow here is reported as something else entirely. -**Every thread's stack also holds the thread-local data.** Zephyr copies all -of the image's `__thread` variables onto the top of each thread's stack when -the thread starts, and does not check that they fit. wolfCert's per-thread -error state brings that copy to ~340 bytes, more than the default 320-byte -`CONFIG_IDLE_STACK_SIZE`, and the image hard-faults before the boot banner. -Raise the idle stack to at least 1024 and leave the same ~340 bytes of room on -every small-stack thread — for example `CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE`, -and `CONFIG_LOG_PROCESS_THREAD_STACK_SIZE` in a `CONFIG_LOG` build. - ## Networking on qemu_x86 A networked QEMU image defaults to SLIP and waits forever on `/tmp/slip.sock`. @@ -204,14 +195,13 @@ needs changing. `prj.conf` leaves these to the board: - **An entropy source.** The devicetree's `zephyr,entropy` usually provides one, as the SoC's TRNG does on FRDM-MCXN947. `CONFIG_TEST_RANDOM_GENERATOR` is for emulators only: keys generated from it are predictable. -- **Stack and heap sizes.** Start from the FRDM-MCXN947 file, and leave room on - every thread's stack for the thread-local data (see [Sizing](#sizing)). +- **Stack and heap sizes.** Start from the FRDM-MCXN947 file (see + [Sizing](#sizing)). ### Troubleshooting | Symptom | Cause | |---|---| -| `HARD FAULT` repeated before the boot banner | A thread's stack cannot hold the thread-local data; see [Sizing](#sizing) | | `peer ip address mismatch` or `peer subject name mismatch` | The server certificate's SAN does not name the host in the URL; issue one with `gen-server-cert.sh` | | `enroll failed: TLS error ()`, or `ASN date error, current date is before start of validity` | The device clock is before a certificate's start date; the clock is the build time, so rebuild with `-p` | | `ASN no signer error to confirm failure` | `CONFIG_WOLFCERT_SAMPLE_CA_CERT` is not the CA that issued the server's certificate; see [step 2](#2-configure-the-sample) | diff --git a/zephyr/samples/wolfcert_est_client/boards/frdm_mcxn947_mcxn947_cpu0.conf b/zephyr/samples/wolfcert_est_client/boards/frdm_mcxn947_mcxn947_cpu0.conf index 347ba3b..4275b29 100644 --- a/zephyr/samples/wolfcert_est_client/boards/frdm_mcxn947_mcxn947_cpu0.conf +++ b/zephyr/samples/wolfcert_est_client/boards/frdm_mcxn947_mcxn947_cpu0.conf @@ -5,6 +5,4 @@ CONFIG_NET_DHCPV4=y CONFIG_MAIN_STACK_SIZE=8192 CONFIG_HEAP_MEM_POOL_SIZE=16384 -# Room for the thread-local data; see zephyr/README.md, Sizing. -CONFIG_IDLE_STACK_SIZE=1024 CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048