From 6fc06a1a4651ca10acfd0ba4229e4e84a703129b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Tue, 6 Oct 2026 10:47:00 +0200 Subject: [PATCH 1/3] Correct stale interop and client API claims in the docs The README's Interoperability section still said the interop scripts are run by hand and that wolfSSL rejects micromdm's and step-ca's SCEP. The nightly Interop workflow runs them, and every peer passes: micromdm in both directions on a wolfSSL built with --enable-des3, and step-ca SCEP once its CA chain is RSA. The old step-ca failure was BAD_KEYWRAP_ALG_E from its default ECDSA RA, not a key-wrap algorithm wolfSSL refuses. The section now lists the caveats that still apply and points at docs/INTEROP.md, whose introduction also called the scripts hand-run. docs/ARCHITECTURE.md and CLAUDE.md named the CryptoCb registration call wolfCrypt_CryptoCb_RegisterDevice, which does not exist; it is wc_CryptoCb_RegisterDevice. ARCHITECTURE.md said wolfcert_client_enroll and _reenroll route to EST or SCEP, and client.h said fetch_meta queries SCEP capabilities. Only wolfcert_client_get_ca handles SCEP; fetch_meta, enroll and reenroll return WOLFCERT_ERR_UNSUPPORTED for it. wolfcert/types.h described verify_server 0 as an explicit-trust-anchor bootstrap. EST refuses it on every call and SCEP refuses it on an https:// URL, so the field has to be 1 wherever TLS is in use. EXTRA_DIST left out docs/CI.md, docs/INTEROP.md and docs/MIGRATING-FROM-WOLFSCEP.md, so a make dist tarball lacked files the README and the other docs point at. --- CLAUDE.md | 2 +- Makefile.am | 3 +++ README.md | 22 ++++++++++++---------- docs/ARCHITECTURE.md | 18 ++++++++++-------- docs/INTEROP.md | 2 +- wolfcert/client.h | 9 ++++----- wolfcert/types.h | 2 +- 7 files changed, 32 insertions(+), 26 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 597f220..91c82da 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -154,7 +154,7 @@ Minimal orientation: or via a config struct - an optional `heap` field; fall back to `wolfcert_default_heap()` when it's NULL. - **CryptoCb devId, never direct registration.** wolfCert never calls - `wolfCrypt_CryptoCb_RegisterDevice`. The application registers its + `wc_CryptoCb_RegisterDevice`. The application registers its backend and passes the resulting `dev_id` via `WolfCertKeyCfg.dev_id`; wolfCert threads it into every `wc_*_init_ex` call. - **Error codes are small and closed.** See `wolfcert/errors.h`. diff --git a/Makefile.am b/Makefile.am index a97c2c4..78e8680 100644 --- a/Makefile.am +++ b/Makefile.am @@ -244,7 +244,10 @@ EXTRA_DIST = \ README.md \ LICENSE \ docs/ARCHITECTURE.md \ + docs/CI.md \ docs/EMBEDDED.md \ + docs/INTEROP.md \ + docs/MIGRATING-FROM-WOLFSCEP.md \ autogen.sh \ CMakeLists.txt \ wolfcert/options.h.in \ diff --git a/README.md b/README.md index 23ba912..65f5b71 100644 --- a/README.md +++ b/README.md @@ -215,16 +215,18 @@ All `WolfCertBuffer` outputs remember which heap they came from, so the same ## Interoperability -Beyond its own client↔server test suite, wolfCert is checked by hand against -third-party EST and SCEP implementations using the scripts under -`tests/interop/`. EST interop passes — Cisco libest, globalsign/est, and -OpenSSL `cms`/`pkcs7` cross-verification of wolfCert's PKCS#7 output. A few -caveats apply to SCEP and to some servers: - -- On wolfSSL ≥ 5.9, micromdm's `scepserver` (signs its CertRep with SHA-1) - and Smallstep step-ca's SCEP (EnvelopedData key-wrap algorithm) are - rejected by wolfSSL's stricter PKCS#7 verification. -- step-ca's EST endpoints are only in the commercial build, not open-source. +Beyond its own client↔server test suite, wolfCert is checked against +third-party EST and SCEP implementations by the scripts under +`tests/interop/`, which the nightly `Interop` workflow runs. All of them +pass: Cisco libest, globalsign/est, micromdm/scep in both directions, +step-ca SCEP, and OpenSSL `cms`/`pkcs7` cross-verification of wolfCert's +PKCS#7 output. `docs/INTEROP.md` has the details. A few caveats apply: + +- micromdm content-encrypts with single DES, so it needs a wolfSSL built + with `--enable-des3`. +- step-ca's SCEP needs an RSA CA chain in place of its default ECDSA one, + since SCEP is RSA-only. +- Open-source step-ca has no EST endpoints, so only its SCEP is tested. - When serving strict third-party SCEP *clients*, wolfCert's CertRep carries the full RFC 8894 signed-attribute set including `recipientNonce`. This works on any malloc-enabled wolfSSL (its PKCS#7 encoder grows the diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 543abcc..de4ea50 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -106,12 +106,14 @@ The layering rules that matter to an integrator: pulls the protocol headers based on what was compiled in, so an application can include it without caring whether EST or SCEP is present. - **`wolfcert_client_*` is the high-level orchestrator.** - `wolfcert_client_enroll` / `_reenroll` route to EST or SCEP based on - `WolfCertServerCfg.protocol`. `_reenroll` copies the Subject and SAN of the - certificate being renewed into its CSR byte for byte (RFC 7030 section - 4.2.2) and refuses a `WolfCertCertMeta` that sets either. Callers that want - finer control reach directly into the `wolfcert_est_*` / `wolfcert_scep_*` - primitives. + `wolfcert_client_get_ca` routes to EST or SCEP based on + `WolfCertServerCfg.protocol`; `_fetch_meta`, `_enroll` and `_reenroll` are + EST-only and return `WOLFCERT_ERR_UNSUPPORTED` for SCEP, which enrolls + through the `wolfcert_scep_*` primitives. `_reenroll` copies the Subject + and SAN of the certificate being renewed into its CSR byte for byte (RFC + 7030 section 4.2.2) and refuses a `WolfCertCertMeta` that sets either. + Callers that want finer control reach directly into the `wolfcert_est_*` / + `wolfcert_scep_*` primitives. - **Protocol modules depend on subsystems, never the reverse**, and the test server lives below the public API — an embedder can hand it an already-accepted socket via `wolfcert_server_serve_fd()` instead of using @@ -320,7 +322,7 @@ registers the backend; wolfCert threads the devId through every crypto call: ```c /* Application startup: register the CryptoCb with wolfSSL. */ -wolfCrypt_CryptoCb_RegisterDevice(MY_DEVID, my_callback, my_ctx); +wc_CryptoCb_RegisterDevice(MY_DEVID, my_callback, my_ctx); /* Generate a key that lives behind the CryptoCb. */ WolfCertKeyCfg cfg = { @@ -518,7 +520,7 @@ that then leaves `transport` zeroed fails with `WOLFCERT_ERR_BAD_ARG`. ```c /* 1. Startup */ -wolfCrypt_CryptoCb_RegisterDevice(MY_DEVID, my_callback, my_ctx); +wc_CryptoCb_RegisterDevice(MY_DEVID, my_callback, my_ctx); wolfcert_init(my_static_heap_hint); wolfcert_set_log_cb(my_log, NULL); diff --git a/docs/INTEROP.md b/docs/INTEROP.md index 0da90ee..a1fcd9f 100644 --- a/docs/INTEROP.md +++ b/docs/INTEROP.md @@ -1,6 +1,6 @@ # Third-party interoperability -wolfCert's EST (RFC 7030) and SCEP (RFC 8894) clients and test server are exercised against independent implementations under `tests/interop/`. These scripts are **hand-run and best-effort** - they are driven by the nightly `Interop` GitHub workflow (`.github/workflows/interop.yml`) but are not part of `ctest`. +wolfCert's EST (RFC 7030) and SCEP (RFC 8894) clients and test server are exercised against independent implementations under `tests/interop/`. The nightly `Interop` GitHub workflow (`.github/workflows/interop.yml`) runs these scripts; they are not part of `ctest`. ## Best-effort / skip convention diff --git a/wolfcert/client.h b/wolfcert/client.h index 9cf3b95..48aef15 100644 --- a/wolfcert/client.h +++ b/wolfcert/client.h @@ -27,9 +27,8 @@ extern "C" { #endif -/* Protocol-agnostic orchestration. srv->protocol selects EST vs SCEP. - * These are the functions an application will usually call; they internally - * invoke the lower-level est_* / scep_* primitives. */ +/* High-level orchestration over the est_* / scep_* primitives. Only + * wolfcert_client_get_ca supports SCEP; the rest are EST-only. */ typedef struct WolfCertClient WolfCertClient; @@ -51,8 +50,8 @@ WOLFCERT_API int wolfcert_client_get_ca(WolfCertClient* client, WolfCertEncoding encoding, WolfCertBuffer* out_ca); -/* Query server for CSR attributes / SCEP capabilities. Fields already set - * in meta are preserved; unset ones may be filled from the server. */ +/* Query the EST server's CSR attributes. Fields already set in meta are + * preserved; unset ones may be filled from the server. */ WOLFCERT_API int wolfcert_client_fetch_meta(WolfCertClient* client, const WolfCertServerCfg* srv, WolfCertCertMeta* meta); diff --git a/wolfcert/types.h b/wolfcert/types.h index 2bcada6..972fe49 100644 --- a/wolfcert/types.h +++ b/wolfcert/types.h @@ -251,7 +251,7 @@ typedef struct { const char* server_url; /* e.g. https://ca.example/.well-known/est */ const uint8_t* trust_anchors; /* bootstrap trust for TLS; PEM or DER; optional */ size_t trust_anchors_len; - int verify_server; /* 0 = explicit-TA bootstrap, 1 = full verify */ + int verify_server; /* must be 1 for EST and https:// SCEP */ int timeout_ms; /* per-request timeout; 0 = default */ /* Optional client identity for mutual TLS. Set both `client_cert` From 1502c8995429975b3f0d2aa998b5000f0f205022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Tue, 6 Oct 2026 11:08:28 +0200 Subject: [PATCH 2/3] Turn CLAUDE.md into an integrator-focused AGENTS.md CLAUDE.md was written for people working on wolfCert itself: the dev build, ctest, internal error macros, symbol visibility, license headers, the steps for adding a key algorithm or a Zephyr board. An agent helping someone build wolfCert into a product needs something else, and other agents than Claude Code read AGENTS.md rather than CLAUDE.md. AGENTS.md now follows the layout of wolfSSL's: which integration path to pick, the wolfSSL features wolfCert requires and the canonical configure line, building and installing with the CMake and autoconf options side by side, the configuration model, when to pick EST or SCEP, a minimal enrollment flow, the public headers, the porting hooks (heap hints, transport, store, CryptoCb dev_id, non-blocking sessions, logging), checking an integration against wolfcert-server, and the gotchas, including that verify_server must be set to 1, since EST and HTTPS SCEP refuse a zero-initialized config. CLAUDE.md is reduced to an import of AGENTS.md. Contributor notes move to AGENTS.local.md / CLAUDE.local.md, which .gitignore now lists, as wolfSSL does. docs/EMBEDDED.md and scripts/ci/build-wolfssl.sh pointed at CLAUDE.md for the build requirements and key algorithm gating and now point at AGENTS.md. EXTRA_DIST now ships AGENTS.md and CLAUDE.md. --- .gitignore | 4 + AGENTS.md | 272 ++++++++++++++++++++++++++++++++++++ CLAUDE.md | 228 +----------------------------- Makefile.am | 2 + docs/EMBEDDED.md | 4 +- scripts/ci/build-wolfssl.sh | 2 +- 6 files changed, 282 insertions(+), 230 deletions(-) create mode 100644 AGENTS.md diff --git a/.gitignore b/.gitignore index e1c158d..1f01ada 100644 --- a/.gitignore +++ b/.gitignore @@ -85,3 +85,7 @@ cmake/wolfCertTargets.cmake *~ .DS_Store .cache + +# Local (untracked) agent instruction files +AGENTS.local.md +CLAUDE.local.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..339e7fa --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,272 @@ +# wolfCert Integration Guide for Agents + +wolfCert is a C library for client-side certificate lifecycle management on +top of wolfSSL: fetch the CA chain, generate a key, build a CSR, enroll and +re-enroll, persist the result. It speaks EST (RFC 7030) and SCEP (RFC 8894) +over HTTP/S and targets everything from Linux hosts to RTOS and bare-metal +devices. Current version is v0.1.0 (`wolfcert/version.h`). Dual licensed: +GPL-3.0-or-later or a commercial license from wolfSSL Inc. + +This file is for agents helping users *integrate wolfCert into their own +projects*. If `AGENTS.local.md` or `CLAUDE.local.md` exists in the repository +root, read it before starting work. Those files are gitignored, carry +maintainer- and machine-specific instructions, and take precedence over this +file. + +## Choose an integration path + +| Situation | Path | +|---|---| +| CMake-based project | CMake: `find_package(wolfCert)` after install, or `add_subdirectory()`; link `wolfCert::wolfcert` | +| Unix-like host or cross-compile with autotools | Autoconf (`./configure`), consume via `pkg-config wolfcert` | +| IDE, RTOS or bare-metal build with no configure step | `user_settings.h` + `-DWOLFCERT_USER_SETTINGS` (`docs/EMBEDDED.md`) | +| Zephyr | `zephyr/` module (`module.yml`, Kconfig), see `zephyr/README.md` | +| Moving off wolfSCEP | `docs/MIGRATING-FROM-WOLFSCEP.md` | + +## wolfSSL comes first + +Most integration failures are a wolfSSL built without what wolfCert needs. +wolfCert requires **wolfSSL >= 5.9.4** and **hard-fails at configure time** +if the wolfSSL it finds lacks any of `HAVE_PKCS7`, `WOLFSSL_CERT_GEN`, +`WOLFSSL_CERT_REQ`, `WOLFSSL_CERT_EXT`, `WOLFSSL_KEY_GEN`, `WOLF_CRYPTO_CB`, +`WOLFSSL_BASE64_ENCODE`, `WOLFSSL_ALT_NAMES` or `WOLFSSL_CERT_NAME_ALL`, was +built with `NO_AES` / `NO_SHA256`, or provides neither TLS 1.2 nor TLS 1.3. + +- **SCEP** additionally needs RSA and AES-128-CBC encrypt and decrypt + (`NO_RSA`, `NO_AES_128`, `NO_AES_CBC` or `NO_AES_DECRYPT` hard-fail unless + SCEP is disabled), since RFC 8894 is RSA-only and makes AES-128-CBC + mandatory. +- **Shared libwolfssl** must export the `WOLFSSL_ASN_API` helpers wolfCert + calls, which needs one of `WOLFSSL_PUBLIC_ASN` (the lean choice), + `OPENSSL_EXTRA`, `OPENSSL_EXTRA_X509_SMALL` or `WOLFSSL_TEST_CERT`. A static + libwolfssl links them regardless. The OpenSSL compatibility layer itself is + not required. +- **ML-DSA** additionally needs `WOLFSSL_MLDSA_CHECK_KEY`, which + `--enable-mldsa` gives by default; it is lost with + `WOLFSSL_DILITHIUM_NO_CHECK_KEY` or `WOLFSSL_MLDSA_VERIFY_ONLY`. + +The canonical wolfSSL configure line satisfies every requirement and enables +every optional key type: + +```sh +./configure --enable-pkcs7 --enable-certgen --enable-certreq \ + --enable-certext --enable-keygen --enable-ecc --enable-cryptocb \ + --enable-base64encode --enable-ed25519 --enable-ed448 \ + --enable-mldsa --enable-postauth --enable-ip-alt-name \ + --enable-des3 --enable-sni \ + CPPFLAGS="-DWOLFSSL_ALT_NAMES -DWOLFSSL_CERT_NAME_ALL \ + -DKEEP_PEER_CERT -DWOLFSSL_PUBLIC_ASN \ + -DWOLFSSL_HAVE_TLS_UNIQUE" +``` + +`KEEP_PEER_CERT` and `WOLFSSL_HAVE_TLS_UNIQUE` are needed only by the test +server (post-handshake auth and `/simplereenroll`), and `--enable-des3` only +to talk to SCEP peers that still use DES, such as micromdm. A device-side +client can leave all three out. + +## Build and install + +```sh +# CMake (primary) +cmake -S . -B build -DWITH_WOLFSSL=/path/to/wolfssl/prefix +cmake --build build -j +cmake --install build + +# Autoconf (kept at parity) +./autogen.sh # git checkouts only +./configure --with-wolfssl=/path/to/wolfssl/prefix +make -j && make install +``` + +Without an explicit path, CMake looks for wolfSSL's CMake package and then +`pkg-config`; autoconf uses `pkg-config`. The +install provides the library, the public headers, a generated +`wolfcert/options.h`, `wolfcert.pc`, the CMake package files and the +`wolfcert-client` / `wolfcert-server` CLIs. + +| CMake | Autoconf | Default | Effect | +|---|---|---|---| +| `WOLFCERT_ENABLE_EST` | `--enable-est` | on | EST client | +| `WOLFCERT_ENABLE_SCEP` | `--enable-scep` | on | SCEP client; pulls in RSA | +| `WOLFCERT_ENABLE_SERVER` | `--enable-server` | on | Minimal EST/SCEP test server | +| `WOLFCERT_ENABLE_CLI` | `--enable-cli` | on | `wolfcert-client`, `wolfcert-server` | +| `WOLFCERT_ENABLE_BUILTIN_TRANSPORT` | `--enable-builtin-transport` | on | POSIX socket transport; turn off on targets with no sockets | +| `WOLFCERT_ENABLE_POSIX_STORE` | `--enable-posix-store` | on | File-based cert/key store | +| `WOLFCERT_BUILD_SHARED` | (libtool `--enable-shared`) | on | Shared vs static library | +| `WOLFCERT_USER_SETTINGS` | `--enable-user-settings` | off | Read features from `user_settings.h` instead of the generated `options.h` | + +A device build usually turns off the server and CLI, and the built-in +transport and POSIX store where the target has no sockets or filesystem. + +## Configuration model + +wolfCert records its resolved features (`WOLFCERT_HAVE_EST`, +`WOLFCERT_HAVE_SCEP`, `WOLFCERT_HAVE_` for RSA, ECC, ED25519, ED448 and +MLDSA, ...) in a generated `wolfcert/options.h`, the way wolfSSL does with +`wolfssl/options.h`. `wolfcert/types.h` includes it, so applications only +include ``, which pulls in the EST and SCEP headers that +were compiled in. + +- **Key algorithms follow wolfSSL.** Each one wolfSSL lacks is dropped with a + warning and returns `WOLFCERT_ERR_UNSUPPORTED` at run time. At least one + must remain. +- **No build system:** define `WOLFCERT_USER_SETTINGS` for the library and + the application, and supply a `user_settings.h` started from + `examples/user_settings.h.example`. `docs/EMBEDDED.md` explains the file + and how to share the name with wolfSSL's own `user_settings.h`. +- `wolfcert/check_config.h` validates the resolved feature set at compile + time, so a broken combination fails the build with a specific `#error`. + +## EST or SCEP + +| | EST (RFC 7030) | SCEP (RFC 8894) | +|---|---|---| +| Transport | HTTPS, server authentication mandatory | HTTP or HTTPS (HTTPS needs `verify_server`); security is in the PKCS#7 messages | +| Client authentication | mTLS (e.g. factory cert), HTTP Basic, TLS 1.3 post-handshake auth | `challengePassword` | +| Key types | RSA, ECC P-256/384/521, Ed25519, Ed448, ML-DSA-44/65/87 | RSA only | +| Typical peers | IoT and industrial PKI, Cisco libest, GlobalSign | MDM and network gear, micromdm, step-ca | +| High-level API | `wolfcert_client_*` | `wolfcert_scep_*` | + +Pick EST unless the CA only speaks SCEP. + +## Minimal enrollment flow + +EST through the high-level client, with a TLS trust anchor for the server: + +```c +#include + +wolfcert_init(NULL); /* NULL: system allocator */ +WolfCertClient* client = NULL; +wolfcert_client_new(&client); + +WolfCertServerCfg srv = { + .protocol = WOLFCERT_PROTO_EST, + .server_url = "https://ca.example/.well-known/est", + .trust_anchors = ca_pem, /* PEM or DER */ + .trust_anchors_len = ca_pem_len, + .verify_server = 1, +}; +WolfCertKeyCfg kcfg = { .type = WOLFCERT_KEY_ECC, .param = 256, + .dev_id = WOLFCERT_DEVID_SOFTWARE }; +WolfCertCertMeta meta = { .subject_dn = "CN=device-0001" }; +WolfCertKey* key = NULL; +WolfCertBuffer cert = { 0 }; + +int rc = wolfcert_client_enroll(client, &srv, &kcfg, &meta, &key, &cert); +if (rc != WOLFCERT_OK) + printf("%s: %s\n", wolfcert_strerror(rc), wolfcert_last_error_message()); + +wolfcert_buffer_free(&cert); +wolfcert_key_free(key); +wolfcert_client_free(client); +wolfcert_cleanup(); +``` + +- **SCEP:** `wolfcert_scep_get_ca_cert`, check it against an out-of-band + fingerprint with `wolfcert_scep_verify_ca_fingerprint`, then + `wolfcert_scep_get_ca_caps` and `wolfcert_scep_pkcs_req` with an RSA key. + Set `meta.challenge_password` if the CA requires one. + `examples/enroll_scep.c` is the full flow. +- **Step by step:** `wolfcert_key_generate`, `wolfcert_csr_build`, then + `wolfcert_est_simple_enroll` (`examples/enroll_est.c`). +- **Pending approval:** the `_ex` variants report a status of PENDING; poll + again later (EST: re-POST after `retry_after_sec`; SCEP: + `wolfcert_scep_get_cert_initial`). The plain variants return + `WOLFCERT_ERR_PENDING`. +- **Renewal:** `wolfcert_client_reenroll` (EST, authenticates with the + certificate being renewed) or `wolfcert_scep_renewal_req`. +- **Hardware keys:** `examples/enroll_cryptocb.c`. + +## Where to look + +| Path | Contents | +|---|---| +| `wolfcert/wolfcert.h` | Umbrella header, `wolfcert_init` / `wolfcert_cleanup` | +| `wolfcert/client.h` | High-level `wolfcert_client_*` API | +| `wolfcert/est.h`, `wolfcert/scep.h` | Protocol one-shots, `_ex` PENDING variants, sessions, non-blocking `_nb` sessions | +| `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/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 | +| `zephyr/samples/wolfcert_est_client/` | EST client on Zephyr (qemu_x86, FRDM-MCXN947) | +| `src/` | Implementation; `src/internal.h` is not for applications | + +The headers are the API reference: their comments document every field and +function contract. + +## Porting hooks + +| Need | Hook | Where | +|---|---|---| +| Static memory / custom heap | `wolfcert_init(heap)` sets the default heap hint; per-call `heap` fields override it, and the hint reaches every wolfSSL call | `wolfcert/memory.h`, README "Integrating with a wolfSSL static-memory pool" | +| Network stack without BSD sockets (lwIP, NetX, FreeRTOS+TCP, wolfIP) | Fill `WolfCertServerCfg.transport` (`WolfCertTransport`); TLS records go through it too | `wolfcert/types.h`, `docs/EMBEDDED.md` section 7 | +| Persistence in flash / NVM | Implement `WolfCertStoreOps` (`read` / `write` / `remove`) | `wolfcert/store.h` | +| Keys in a TPM, HSM, PKCS#11 token or secure element | Register a backend with `wc_CryptoCb_RegisterDevice`, pass its id as `WolfCertKeyCfg.dev_id` | `docs/ARCHITECTURE.md` section 4.2, `examples/enroll_cryptocb.c` | +| Event loop / no blocking | `wolfcert_est_session_open_async` / `wolfcert_scep_session_open_async`, then the `_nb` calls until they stop returning `WOLFCERT_ERR_WANT_READ` / `_WANT_WRITE`; `*_session_fd` gives the descriptor to poll | `wolfcert/est.h`, `wolfcert/scep.h` | +| Logging | `wolfcert_set_log_cb` (e.g. to a UART) | `wolfcert/log.h` | +| RAM budget | `max_response_bytes`, HTTP stack buffers, wolfSSL `Cert` / `CertName` sizing | `docs/EMBEDDED.md` | + +## Verifying an integration + +- The test server and CLI make a loopback peer for any client code: + + ```sh + wolfcert-server --proto est --listen 127.0.0.1:8443 \ + --tls-cert server.crt --tls-key server.key --est-allow-anonymous + wolfcert-client enroll --proto est \ + --url https://127.0.0.1:8443/.well-known/est --trust server.crt \ + --key-type ecc:256 --subject "CN=dev" \ + --out-key dev.key --out-cert dev.crt + ``` + + README "Quick start" has the SCEP and CA-pinning variants; `--help` on + either tool lists the client authentication and approval options. +- `wolfcert-client` also covers `getcacerts`, `reenroll` (EST) and + `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. + +## Gotchas + +- **Set `verify_server` to 1.** A zero-initialized config leaves it at 0, + which every EST call and SCEP over `https://` refuse with + `WOLFCERT_ERR_TLS`. Plain-HTTP SCEP ignores it and relies on the CA + fingerprint check instead. +- **The `wolfcert_client_*` calls are EST-only except `get_ca`.** For SCEP + they return `WOLFCERT_ERR_UNSUPPORTED`; use `wolfcert_scep_*`. +- **SCEP rejects non-RSA keys** with `WOLFCERT_ERR_UNSUPPORTED`, and an ECC + RA/CA certificate as well. Use EST for ECC, EdDSA or ML-DSA device keys. +- **Set `protocol` on every config**, not only for the `wolfcert_client_*` + calls: every `wolfcert_est_*` / `wolfcert_scep_*` entry point returns + `WOLFCERT_ERR_BAD_ARG` on a mismatch. +- **`max_response_bytes` defaults to 64 KiB**, sized for hosts. Set it on an + MCU. +- **The HTTPS transport needs TLS 1.2 or newer**, or TLS 1.3 when wolfSSL + is built with `WOLFSSL_NO_TLS12`. A server limited to older versions + fails the handshake. +- **Non-blocking mode still blocks for DNS and the initial TCP connect.** +- **The test server is for development and interop only**, not a production + CA. +- **SCEP over plain HTTP is downgradable:** an attacker who strips `AES` from + GetCACaps forces 3DES. Pin the CA fingerprint and force AES with + `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. + +## Further documentation + +- `README.md`: quick start, capabilities and limitations, interoperability. +- `docs/ARCHITECTURE.md`: design, protocol flows with sequence diagrams, and + the MCU / CryptoCb integration guide (section 4). +- `docs/EMBEDDED.md`: RAM sizing, `user_settings.h` builds, targets without + sockets or a filesystem, Zephyr. +- `docs/INTEROP.md`: tested third-party EST and SCEP peers. +- `docs/MIGRATING-FROM-WOLFSCEP.md`: call mapping from wolfSCEP. +- Support and commercial licensing: support@wolfssl.com, + licensing@wolfssl.com. diff --git a/CLAUDE.md b/CLAUDE.md index 91c82da..43c994c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,227 +1 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## What this is - -wolfCert is a C library for client-side certificate lifecycle management -built on top of wolfSSL. It implements EST (RFC 7030) and SCEP (RFC 8894) -for certificate enrollment over HTTP/S. Current version is v0.1.0 -(`wolfcert/version.h`). License is GPL-3.0-or-later. - -## Build - -CMake is the primary build system; autoconf is kept at parity. - -```sh -# Dev build: everything enabled, tests + examples on -cmake -S . -B build \ - -DWOLFCERT_ENABLE_TESTS=ON \ - -DWOLFCERT_ENABLE_EXAMPLES=ON -cmake --build build -j - -# Autoconf equivalent -./autogen.sh -./configure --enable-tests --enable-examples -make -j -``` - -wolfCert targets **wolfSSL >= 5.9.4**. The build **hard-fails** at -configure time if the installed wolfSSL lacks any of `HAVE_PKCS7`, -`WOLFSSL_CERT_GEN`, `WOLFSSL_CERT_REQ`, `WOLFSSL_CERT_EXT`, -`WOLFSSL_KEY_GEN`, `WOLF_CRYPTO_CB`, `WOLFSSL_BASE64_ENCODE`, -`WOLFSSL_ALT_NAMES`, or `WOLFSSL_CERT_NAME_ALL`, or if it was built with -`NO_AES` / `NO_SHA256`, or if it provides neither TLS 1.2 nor TLS 1.3. -With SCEP enabled it also needs AES-128-CBC encrypt and decrypt -(`NO_AES_128`, `NO_AES_CBC` or `NO_AES_DECRYPT` hard-fail), since RFC 8894 -makes it mandatory-to-implement. -It also link-probes the `WOLFSSL_ASN_API` helpers it calls, which a shared -libwolfssl exports only under one of `WOLFSSL_PUBLIC_ASN` (the lean choice), -`OPENSSL_EXTRA`, `OPENSSL_EXTRA_X509_SMALL` or `WOLFSSL_TEST_CERT`; a static -one links them regardless. The OpenSSL compatibility layer -itself is not required. With ML-DSA enabled it additionally needs -`WOLFSSL_MLDSA_CHECK_KEY` (`wc_MlDsaKey_CheckKey()`), which reloading an -ML-DSA CA from a store calls -- checked when `src/key_algs.c` compiles, -since only `dilithium.h` resolves that macro. `--enable-mldsa` gives it by -default; it is lost only if wolfSSL is built with -`WOLFSSL_DILITHIUM_NO_CHECK_KEY` or `WOLFSSL_MLDSA_VERIFY_ONLY`. - -The in-tree test server needs `KEEP_PEER_CERT` for post-handshake auth and `/simplereenroll`, and `WOLFSSL_HAVE_TLS_UNIQUE` for post-handshake auth; `README.md` has the details below its configure line. - -**Key algorithms are gated** by `WOLFCERT_HAVE_` (RSA, ECC, -ED25519, ED448, MLDSA). RSA, ECC, Ed25519, Ed448 and ML-DSA are each -*optional* (absent => warning, that key type returns -`WOLFCERT_ERR_UNSUPPORTED`), with two constraints: at least one key -algorithm must be present, and **SCEP requires RSA** (RFC 8894 is -RSA-only) so a `NO_RSA` wolfSSL hard-fails unless SCEP is disabled. SCEP -content encryption follows the `content_cipher` entry in -`docs/ARCHITECTURE.md`. A caller can override it per-connection via -`WolfCertServerCfg.proto_opts.scep.content_cipher` (e.g. force AES-256-CBC -for a peer that requires it). TLS: -the HTTPS transport pins its floor to TLS 1.2, or TLS 1.3 when wolfSSL -is built `WOLFSSL_NO_TLS12`. - -All of these resolved feature flags are written into a **generated -`wolfcert/options.h`** (from `wolfcert/options.h.in`, by both CMake's -`configure_file` and autoconf's `config.status`, like -`wolfssl/options.h`) which `wolfcert/types.h` includes -- the sources no -longer receive `WOLFCERT_HAVE_*` via `-D`. Alternatively, define -**`WOLFCERT_USER_SETTINGS`** and supply a `user_settings.h` on the include -path (the wolfSSL `WOLFSSL_USER_SETTINGS` analogue, for header-only builds with -no configure step); `types.h` then includes it instead of `options.h`. Either -way `wolfcert/check_config.h` validates the resolved feature set at compile -time. See `docs/EMBEDDED.md` and `examples/user_settings.h.example`. Canonical -wolfSSL configure: - -```sh -./configure --enable-pkcs7 --enable-certgen --enable-certreq \ - --enable-certext --enable-keygen --enable-ecc --enable-cryptocb \ - --enable-base64encode --enable-ed25519 --enable-ed448 \ - --enable-mldsa --enable-postauth --enable-ip-alt-name \ - --enable-des3 --enable-sni \ - CPPFLAGS="-DWOLFSSL_ALT_NAMES -DWOLFSSL_CERT_NAME_ALL \ - -DKEEP_PEER_CERT -DWOLFSSL_PUBLIC_ASN \ - -DWOLFSSL_HAVE_TLS_UNIQUE" -``` - -CMake options live at the top of `CMakeLists.txt`; the matching autoconf -flags are in `configure.ac`. The SCEP server emits the full RFC 8894 -signed-attribute set (including `recipientNonce`) on any malloc-enabled -wolfSSL — its PKCS#7 encoder grows the signed-attribute array on the heap -past the inline `MAX_SIGNED_ATTRIBS_SZ`. Only a `WOLFSSL_NO_MALLOC` build -needs wolfSSL rebuilt with `-DMAX_SIGNED_ATTRIBS_SZ>=9` to carry it. - -## Test - -```sh -ctest --test-dir build --output-on-failure # everything -ctest --test-dir build -R est_pha_roundtrip # single test by name -ctest --test-dir build -R roundtrip --output-on-failure # regex -``` - -Unit tests live in `tests/unit/`; end-to-end flows in -`tests/integration/` drive the in-tree test server via -`WOLFCERT_ENABLE_SERVER=ON`. Third-party interop scripts under -`tests/interop/` are hand-run and not part of `ctest`. - -## Run the CLIs - -After a build with `-DWOLFCERT_ENABLE_CLI=ON` (the default): - -```sh -build/wolfcert-server --proto est --listen 127.0.0.1:8443 \ - --tls-cert server.crt --tls-key server.key --est-allow-anonymous -build/wolfcert-client enroll --proto est \ - --url https://127.0.0.1:8443/.well-known/est --trust server.crt \ - --key-type ecc:256 --subject "CN=dev" \ - --out-key dev.key --out-cert dev.crt -``` - -See `README.md` for TLS / mTLS / PHA / SCEP pending-queue variants. - -## Architecture at a glance - -The authoritative internal-architecture document is **`docs/ARCHITECTURE.md`** - -it covers module decomposition, data structures, subsystems, protocol -layers, end-to-end request walkthroughs with ASCII sequence diagrams, the -MCU / CryptoCb integration guide, and conventions. Read it before making -non-trivial changes. - -Minimal orientation: - -- **Public API** lives in `wolfcert/`; `wolfcert.h` is the - umbrella header. Public headers never include internal headers. -- **Four layers**: application -> `wolfcert/*.h` -> protocol - (`src/est/`, `src/scep/`) -> subsystems (`src/keygen.c`, `src/csr.c`, - `src/http.c`, `src/store.c`, `src/pkcs7_util.c`, `src/server.c`) -> - wolfSSL. Protocol modules depend on subsystem modules, never the - reverse. -- **Three vtables** carry all the pluggability: `WolfCertKeyAlg` - (algorithm dispatch in `src/key_algs.c`), `WolfCertStoreOps` - (storage backends in `src/store.c`), `WolfCertServerOps` (test-server - protocol dispatch in `src/internal.h`, factories in - `src/est/est_server.c` and `src/scep/scep_server.c`). -- **Adding a key algorithm** = one `WolfCertKeyAlg` struct literal in - `src/key_algs.c` plus (if applicable) a `WOLFCERT_HAVE_` compile - guard. No edits to `keygen.c`, `csr.c`, or `ca_issue.c`. - -## Non-obvious conventions - -- **Every allocation takes a heap hint.** Use - `WOLFCERT_XMALLOC` / `XFREE` / `XREALLOC` macros from - `wolfcert/memory.h`. The hint rides through to wolfSSL so - static-memory pools are honoured. Every public API accepts - directly - or via a config struct - an optional `heap` field; fall back to - `wolfcert_default_heap()` when it's NULL. -- **CryptoCb devId, never direct registration.** wolfCert never calls - `wc_CryptoCb_RegisterDevice`. The application registers its - backend and passes the resulting `dev_id` via `WolfCertKeyCfg.dev_id`; - wolfCert threads it into every `wc_*_init_ex` call. -- **Error codes are small and closed.** See `wolfcert/errors.h`. - Extended per-thread diagnostics live behind - `wolfcert_last_error_message()` / `wolfcert_last_wolfssl_err()`. Use - the `WOLFCERT_ERR(rc, module, fmt, ...)` and - `WOLFCERT_ERR_WC(wc_rc, module, fmt, ...)` macros from - `src/internal.h` at every error site; `wolfcert_strerror` must cover - every code. -- **Naming.** Public and internal functions both use the `wolfcert_*` - prefix; public types are `WolfCert*` and macros / enums `WOLFCERT_*`. - The public/private boundary is enforced by visibility, not by name: - the library builds with hidden default visibility, public prototypes - in `wolfcert/*.h` are decorated `WOLFCERT_API`, and internal helpers - declared in `src/internal.h` stay hidden. Internal symbols that - in-tree tests need to link against are tagged `WOLFCERT_TEST_VIS`, - which becomes a default-visibility export only when - `WOLFCERT_BUILD_TESTING` is defined. -- **License header.** Every `.c` / `.h` source file opens with the full - GPL copyright block (`Copyright (C) 2026 wolfSSL Inc. ...`) - copy it - verbatim from any existing source file. The - `SPDX-License-Identifier: GPL-3.0-or-later` one-liner is used *only* on - build files (`CMakeLists.txt`, `configure.ac`, `Makefile.am`), never on - C sources. Include order within a `.c`/`.h`: the module's own headers - (`` and `"internal.h"`) -> wolfSSL (``) -> - system headers. -- **Session vs one-shot APIs.** The non-blocking session variants - (`wolfcert_http_session_request_nb`, `wolfcert_est_session_*_nb`, - `wolfcert_scep_session_*_nb`) are the only non-blocking entry points; - one-shot `wolfcert_http_request` / `wolfcert_est_*` / `wolfcert_scep_*` - calls are blocking by design. DNS + initial TCP connect remain - synchronous even in non-blocking mode. The SCEP session (unlike EST) - does not require TLS, since SCEP authenticates at the pkiMessage layer. -- **SCEP is RSA-only** (per RFC 8894). The SCEP entry points reject - non-RSA keys with `WOLFCERT_ERR_UNSUPPORTED`. EST is the right - protocol for Ed25519 / Ed448 / ML-DSA. - -## Adding a Zephyr board to the EST sample - -Supporting a new board in `zephyr/samples/wolfcert_est_client/` takes all of: - -1. `boards/.conf`, plus a `.overlay` if the devicetree needs changing. - What the board must supply is in - [`zephyr/README.md` → "Adding a board"](zephyr/README.md#adding-a-board). -2. A `build_only: true` scenario in the sample's `sample.yaml` with - `platform_allow: `. -3. `EXTRA_DIST` lines for the new files in `zephyr/include.am`. -4. A CI job in `.github/workflows/zephyr.yml` modelled on the `mcxn` job: - `.github/actions/zephyr-workspace` with the board's SDK toolchain, twister - `--build-only` for the board, then `scripts/ci/twister-assert-ran.py - --built-only `. -5. A row in both board tables of - [`zephyr/README.md` → "EST client sample"](zephyr/README.md#est-client-sample). -6. A run 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 - ([`zephyr/README.md` → "Sizing"](zephyr/README.md#sizing)). - -## Pointers to the rest of the docs - -- `README.md` - user-facing quick start. -- `docs/ARCHITECTURE.md` - design overview, protocol flows, and the - MCU / CryptoCb integration guide (start here for non-trivial changes). -- `docs/EMBEDDED.md` - RAM-sizing knobs for constrained targets - (`Cert`/`CertName` shrinking, tunable HTTP stack buffers). -- `docs/MIGRATING-FROM-WOLFSCEP.md` - call mapping and behavioural - differences for an existing wolfSCEP integration, including which - messageType a renewal should carry. -- `wolfcert/*.h` - authoritative API reference (inline - comments document every field and function contract). +@AGENTS.md diff --git a/Makefile.am b/Makefile.am index 78e8680..146e381 100644 --- a/Makefile.am +++ b/Makefile.am @@ -242,6 +242,8 @@ AM_TESTS_ENVIRONMENT = WOLFCERT_CLI=$(abs_builddir)/wolfcert-client; \ EXTRA_DIST = \ README.md \ + AGENTS.md \ + CLAUDE.md \ LICENSE \ docs/ARCHITECTURE.md \ docs/CI.md \ diff --git a/docs/EMBEDDED.md b/docs/EMBEDDED.md index d67e2ba..b025f6f 100644 --- a/docs/EMBEDDED.md +++ b/docs/EMBEDDED.md @@ -149,7 +149,7 @@ Trade-offs: fails with `WOLFCERT_ERR_UNSUPPORTED`. - Disabling `WOLFSSL_CERT_NAME_ALL` and/or `WOLFSSL_CERT_EXT` in wolfSSL removes the less-common `CertName` fields entirely - but wolfCert's - build requires both (see `CLAUDE.md` / `CMakeLists.txt`), so prefer + build requires both (see `AGENTS.md` / `CMakeLists.txt`), so prefer shrinking `WC_CTC_NAME_SIZE` over dropping these. ## 2. wolfCert HTTP stack buffers @@ -256,7 +256,7 @@ wolfCert holds no locks of its own (init/cleanup delegate refcounting to Strip unused key algorithms and protocols at configure time so their code and tables drop out entirely - see the `WOLFCERT_HAVE_*` / `WOLFCERT_ENABLE_*` options in `CMakeLists.txt` / `configure.ac` and the -gating discussion in `CLAUDE.md`. SCEP is RSA-only; if you only need EST +gating discussion in `AGENTS.md`. SCEP is RSA-only; if you only need EST with ECC or a PQC algorithm, disabling SCEP avoids pulling in RSA. ## 7. Targets without BSD sockets or a filesystem diff --git a/scripts/ci/build-wolfssl.sh b/scripts/ci/build-wolfssl.sh index 1749317..5e8de79 100755 --- a/scripts/ci/build-wolfssl.sh +++ b/scripts/ci/build-wolfssl.sh @@ -26,7 +26,7 @@ WOLFSSL_REPO="${WOLFSSL_REPO:-https://github.com/wolfSSL/wolfssl.git}" # Config-name -> wolfSSL ./configure argument array. # # The canonical base (satisfies every hard wolfCert requirement plus all -# optional key algorithms) mirrors README.md / CLAUDE.md. Each variant layers +# optional key algorithms) mirrors README.md / AGENTS.md. Each variant layers # a delta onto that base. VAR=VALUE assignments are passed to configure as # single argv elements so embedded spaces survive word-splitting. # ---------------------------------------------------------------------------- From de0f4ceb47a21d1bf2e5631652a02e72a40e61cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Wed, 7 Oct 2026 07:29:12 +0200 Subject: [PATCH 3/3] Add CONTRIBUTING.md The previous commit moved the contributor rules CLAUDE.md held (error macros, symbol visibility, license headers, include order, the Zephyr board checklist) into untracked local files, which left other contributors and review agents without them. CONTRIBUTING.md starts from the guide shared across the wolfSSL repositories, without the Jenkins and wolfSSL-Bot paragraphs, since wolfCert's CI runs only on GitHub Actions. Its declaration and line- length rules are relaxed to match wolfCert, which builds as C11 and does not check line length in CI, and two rules on keeping comments short are added. A wolfCert section after it carries the project rules and the full checklist for adding a board to the Zephyr EST sample. AGENTS.md points contributors at it, and EXTRA_DIST ships it. --- AGENTS.md | 2 +- CONTRIBUTING.md | 119 ++++++++++++++++++++++++++++++++++++++++++++++++ Makefile.am | 1 + 3 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 CONTRIBUTING.md diff --git a/AGENTS.md b/AGENTS.md index 339e7fa..5129d6b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,7 +11,7 @@ This file is for agents helping users *integrate wolfCert into their own projects*. If `AGENTS.local.md` or `CLAUDE.local.md` exists in the repository root, read it before starting work. Those files are gitignored, carry maintainer- and machine-specific instructions, and take precedence over this -file. +file. Changes to wolfCert itself follow `CONTRIBUTING.md`. ## Choose an integration path diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..32b6666 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,119 @@ +# Contributing + +Thank you for your interest in contributing. This guide is shared across the wolfSSL Inc. repositories (wolfSSL, wolfSSH, wolfTPM, wolfBoot, wolfMQTT, wolfCLU, wolfPKCS11, wolfHSM, wolfProvider, wolfSentry, and the rest), so it covers what is common to all of them rather than anything specific to this one. Please read the "Contributor Agreement" section below - it is the one requirement that surprises people, and we would rather you know about it up front. + +## Ways to Contribute + +Pick whichever fits you best. All three are welcome: + +1. **Open a pull request.** This is the preferred route and the easiest for us to review, test, and give you credit for. +2. **Email the patch to support@wolfssl.com.** If you would rather not work through GitHub, send us the diff directly and we will take it from there. +3. **File an issue.** Report a defect, ask a question, or propose a change. Use an issue template if the repository offers one. Tell us what the problem is, how to reproduce it, and what you expected instead, and we can take it from there. You do not need to set labels - a maintainer applies those during triage. This is also the route to use if you are unable to submit code at all (see "If You Cannot Sign the Contributor Agreement" below). + +## Contributor Agreement (Required) + +wolfSSL Inc. dual licenses its software: an open source license - GPLv3 for most projects, see that repository's `LICENSING`, `COPYING`, or `LICENSE` file - and a commercial license for everyone else. To be able to ship your contribution under both, we need to hold the rights to relicense it. That means **we cannot merge a contribution until a signed contributor agreement is on file** for you (and, where applicable, your employer). + +**You can open your pull request first and in parallel**: + +1. Email **support@wolfssl.com** and ask for the contributor agreement. +2. Include a **reference to your pull request** - a link or the PR number - so we can tie the agreement to the contribution. If you are not going through GitHub, attach the patch you would like to submit instead. +3. Include your **location** (country, and state or region), and **details about your project and how you are using the library**. +4. We will send you the agreement to review and sign. + +The review and the agreement proceed independently, so starting both at once is the fastest path to a merge. The signed agreement needs to be on file before we can merge. + +The agreement is a short document that grants wolfSSL Inc. a copyright and patent license to your contribution; you otherwise keep all right, title, and interest in your own work, and anything we make available under any license also stays available under an FSF- or OSI-approved open source license. You sign either as an individual or on behalf of your employer, and we send you the full text to read before anything is signed. + +The agreement covers all contributions, not just code - documentation changes and one-line typo fixes need one too. There is no size threshold below which it is waived. + +## If You Cannot Sign the Contributor Agreement + +Some employers do not permit signing third-party agreements, and we understand that. You can still get the change in: + +- Open a GitHub issue in the affected repository. +- Describe the problem, how to reproduce it, and the change you believe is correct - in prose, at whatever level of detail you can share. +- We can then implement and test the change ourselves. + +If you go this route, please do **not** attach or paste code that you want kept out of the agreement. A clear description of the defect and the intended behavior is enough for us to work from, and it keeps the provenance of the fix unambiguous. + +## Before You Start + +- **Check the default branch first.** The fix may already be in, or the surrounding code may have moved. Please base your work on the current default branch - `master` in most wolfSSL repositories, `main` in a few. +- **Open an issue for anything large.** New features, API changes, new hardware ports, and behavior changes are much easier to land if we agree on the approach before the code is written. +- **Know the target.** Most wolfSSL projects are portable C (C90 / ANSI C) and run on everything from bare-metal microcontrollers and RTOSes to desktops and servers. Changes that are fine on a modern Linux host can break a build with no filesystem, no heap, no threads, or a different word size. Portability constraints are real and are the most common reason a change needs rework. +- **Build and test details** for each project live in that repository's `README` and `INSTALL` files. This guide deliberately does not duplicate them. + +## Submitting a Pull Request + +1. Fork the repository and create a topic branch off the default branch. +2. Keep commits focused; one logical change per commit, with a clear message. +3. Open the pull request against that same default branch. +4. Fill out the pull request template if the repository has one - especially the description of what changed and how you tested it. +5. Make sure CI is green - see "Continuous Integration" below. +6. Expect review comments. Most contributions go through at least one round. + +## Continuous Integration + +Pull requests are checked by GitHub Actions, and the jobs are public. Open the "Checks" tab on your pull request, click into any failing job, and read the full log yourself. Please do this first - most failures are a build break or a test regression from the change itself, and you can usually reproduce them locally. + +CI is broad and occasionally flaky. If a failure looks unrelated to your change, say so in a comment and we will re-run the job. + +## What We Look For + +- **Match the surrounding style.** Indentation, brace placement, and naming should look like the file you are editing. +- **C11.** wolfCert builds as C11 (`CMAKE_C_STANDARD 11`). Follow the declaration style of the file you are editing. +- **`/* ... */` comments only.** No `//` comments in C sources. +- **Comments name what the code cannot show.** Most code needs none. When one is needed, keep it to one or two lines: a spec or hardware constraint, a workaround for another project's bug, or a trap a later edit would reintroduce. Do not restate the code, narrate the steps, or explain what a called function does. Comments on public types and functions in `wolfcert/*.h` are the API reference and may run longer to document the contract. +- **Long explanations go in the commit message.** Design reasoning, alternatives not taken, performance figures and history go stale in the source. No banner blocks or theory-of-operation headers. +- **Avoid `goto`.** We discourage it in new code. Some existing cleanup paths use it, but prefer a single exit point with a return-code variable over adding more. +- **Check every return code.** Do not ignore an error return, and do not add always-succeeds stubs - return a "not implemented" error instead. +- **Free what you allocate,** on every return path. +- **Tests.** Behavior changes and bug fixes should come with a test that fails before the change and passes after. +- **No new compiler warnings.** +- **Aim for 80 columns.** Keep new C code within 80 columns where practical. Some existing code is wider, and CI does not check it. This does not apply to Markdown or other documentation, which is not hard-wrapped. +- **Clean source text.** 7-bit ASCII only, no trailing whitespace, and a newline at end of file. Several repositories enforce this in CI. +- **Keep the diff to the point.** Unrelated reformatting makes review harder and is usually asked to be removed. + +## Licensing of Contributions + +Contributions are accepted under the license of the project you are contributing to - GPLv3 for most wolfSSL repositories, with per-project exceptions in a few. See that repository's `LICENSING`, `COPYING`, or `LICENSE` file for the specifics, including any exceptions. The contributor agreement is what additionally allows wolfSSL Inc. to offer your contribution under a commercial license. + +Please only submit code that you have the right to contribute. Do not paste in code taken from another project unless you are certain the license is compatible and you say so in the pull request. + +## Reporting Security Issues + +**Do not open a public issue or pull request for a security vulnerability.** + +Email **support@wolfssl.com** instead, and please keep the issue private until a fix has been released. See `SECURITY.md` in the repository if it has one, or the published policy at +. + +## Getting Help + +- **Support and licensing questions:** support@wolfssl.com +- **Bugs and feature requests:** GitHub issues on the relevant repository +- **Documentation:** +- **Community:** + +## wolfCert Specifics + +`README.md` has the build and test steps, `docs/ARCHITECTURE.md` the module layout, and `AGENTS.md` the wolfSSL features wolfCert needs. On top of the rules above, wolfCert asks for: + +- **Heap hints on every allocation.** Allocate through `WOLFCERT_XMALLOC`, `WOLFCERT_XREALLOC` and `WOLFCERT_XFREE` from `wolfcert/memory.h` and pass the hint on to wolfSSL. A public API takes an optional `heap`, directly or in its config struct, and falls back to `wolfcert_default_heap()` when it is NULL. +- **No CryptoCb registration.** wolfCert never calls `wc_CryptoCb_RegisterDevice`; the application registers its backend and passes the `dev_id` in `WolfCertKeyCfg`. +- **A closed set of error codes.** Report every error with `WOLFCERT_ERR` or `WOLFCERT_ERR_WC` from `src/internal.h`, and keep `wolfcert_strerror` covering every code in `wolfcert/errors.h`. +- **Visibility.** Public and internal functions both use the `wolfcert_` prefix. The CMake build hides every symbol by default, so public prototypes in `wolfcert/*.h` carry `WOLFCERT_API`. Mark an internal function that the in-tree tests call `WOLFCERT_TEST_VIS`, which exports it only in a `WOLFCERT_BUILD_TESTING` build. +- **Append-only public structs.** The v0.1.0 ABI is released, so a new field in a public struct goes at its end. +- **License header.** Every `.c` and `.h` file opens with the full GPL copyright block, copied from an existing source file; the Lint workflow checks it. Build files (`CMakeLists.txt`, `configure.ac`, `Makefile.am`) use the `SPDX-License-Identifier: GPL-3.0-or-later` line instead. +- **Include order.** The module's own headers (``, `"internal.h"`) first, then wolfSSL, then system headers. +- **Both build systems.** CMake and autoconf are kept at parity. A new source file goes into `CMakeLists.txt` and `Makefile.am` (or `zephyr/include.am` for Zephyr files), and a new non-source file into `EXTRA_DIST`. `scripts/ci/check-buildsystem-parity.sh` checks the library source lists. +- **A new key algorithm** is one `WolfCertKeyAlg` entry in `src/key_algs.c`, plus a `WOLFCERT_HAVE_` guard if wolfSSL can build without it. + +### Adding a Zephyr board to the EST sample + +1. Add `boards/.conf` to `zephyr/samples/wolfcert_est_client/`, plus a `.overlay` if the devicetree needs changing. `zephyr/README.md` "Adding a board" lists what the board must supply. +2. Add a `build_only: true` scenario to the sample's `sample.yaml` with `platform_allow` set to the board target. +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`). diff --git a/Makefile.am b/Makefile.am index 146e381..29b2b33 100644 --- a/Makefile.am +++ b/Makefile.am @@ -244,6 +244,7 @@ EXTRA_DIST = \ README.md \ AGENTS.md \ CLAUDE.md \ + CONTRIBUTING.md \ LICENSE \ docs/ARCHITECTURE.md \ docs/CI.md \