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
7 changes: 4 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <scenario>`.
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`.
2 changes: 1 addition & 1 deletion Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
11 changes: 7 additions & 4 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand All @@ -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

Expand Down
10 changes: 7 additions & 3 deletions docs/EMBEDDED.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions docs/MIGRATING-FROM-WOLFSCEP.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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.

Expand Down
52 changes: 45 additions & 7 deletions examples/enroll_scep.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@
* Minimal SCEP enrollment example.
*
* Usage:
* enroll_scep <server_url> <subject_dn>
* enroll_scep <server_url> <subject_dn> <ca_sha256_hex>
*
* 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 <wolfcert/wolfcert.h>
Expand All @@ -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;
}

Expand All @@ -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);
Expand All @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions examples/user_settings.h.example
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
3 changes: 2 additions & 1 deletion scripts/ci/build-wolfssl.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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)}"
Expand Down
28 changes: 21 additions & 7 deletions src/est/est_server.c
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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. */
Expand All @@ -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);
Comment thread
Frauschi marked this conversation as resolved.

return ok;
}
Expand Down
10 changes: 1 addition & 9 deletions src/internal.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,14 @@ 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;
char module[32];
char message[256];
} WolfCertErrState;

static WOLFCERT_TLS WolfCertErrState g_err;
static THREAD_LS_T WolfCertErrState g_err;

void wolfcert_set_default_heap(void* heap)
{
Expand Down
4 changes: 2 additions & 2 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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)
Expand Down
Loading
Loading