diff --git a/.github/workflows/zephyr.yml b/.github/workflows/zephyr.yml new file mode 100644 index 0000000..75e6e61 --- /dev/null +++ b/.github/workflows/zephyr.yml @@ -0,0 +1,176 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +name: Zephyr + +# Builds wolfCert as a Zephyr module and runs the on-target tests under +# qemu_x86. +on: + pull_request: + push: + branches: + - main + - master + +concurrency: + group: zephyr-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +env: + WOLFSSL_REF: master + ZEPHYR_REF: v4.4.2 + ZEPHYR_SDK: 1.0.1 + +jobs: + qemu-x86: + name: qemu_x86 + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - uses: actions/checkout@v5 + + # Host side: wolfcert-server for the EST gate. + - name: Build wolfCert CLIs + uses: ./.github/actions/build-wolfcert-cli + with: + wolfssl-ref: ${{ env.WOLFSSL_REF }} + + - name: Install Zephyr build dependencies + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + ninja-build gperf ccache dfu-util device-tree-compiler wget \ + python3-dev python3-pip python3-setuptools python3-wheel \ + xz-utils file make gcc libsdl2-dev libmagic1 qemu-system-x86 + + # Keyed on everything baked into the restored workspace: the refs and the + # manifest script that wrote them, plus the interpreter the venv records. + # west update below still moves wolfSSL to the current tip of WOLFSSL_REF. + - name: Workspace cache key + id: wskey + run: | + h="${{ hashFiles('scripts/ci/zephyr-add-wolfssl.py') }}" + echo "key=zephyr-ws-${RUNNER_OS}-${ZEPHYR_REF}-${WOLFSSL_REF}-${h:0:12}-$(python3 -V | tr -d ' ')" \ + >> "$GITHUB_OUTPUT" + + - name: Restore the Zephyr workspace + id: ws + uses: actions/cache/restore@v4 + with: + path: | + ~/zephyrproject + ~/zephyr-venv + key: ${{ steps.wskey.outputs.key }} + + - name: Initialise the west workspace + run: | + set -euo pipefail + if [ ! -x "$HOME/zephyr-venv/bin/west" ]; then + python3 -m venv "$HOME/zephyr-venv" + "$HOME/zephyr-venv/bin/pip" install -q --upgrade pip west + fi + if [ ! -d "$HOME/zephyrproject/.west" ]; then + "$HOME/zephyr-venv/bin/west" init -m https://github.com/zephyrproject-rtos/zephyr \ + --mr "$ZEPHYR_REF" "$HOME/zephyrproject" + fi + + - name: Add wolfSSL to the manifest + run: | + ./scripts/ci/zephyr-add-wolfssl.py \ + "$HOME/zephyrproject/zephyr/west.yml" "$WOLFSSL_REF" + + - name: Update west modules + run: | + cd "$HOME/zephyrproject" + "$HOME/zephyr-venv/bin/west" update --narrow -o=--depth=1 + "$HOME/zephyr-venv/bin/pip" install -q -r zephyr/scripts/requirements.txt + + # Only the push-to-main run seeds the key, so PR runs cannot race on it. + - name: Save the Zephyr workspace + if: github.event_name == 'push' && steps.ws.outputs.cache-hit != 'true' + uses: actions/cache/save@v4 + with: + path: | + ~/zephyrproject + ~/zephyr-venv + key: ${{ steps.wskey.outputs.key }} + + - name: Install the Zephyr SDK + run: | + set -euo pipefail + cd "$HOME" + wget -q "https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v${ZEPHYR_SDK}/zephyr-sdk-${ZEPHYR_SDK}_linux-x86_64_minimal.tar.xz" + tar xf "zephyr-sdk-${ZEPHYR_SDK}_linux-x86_64_minimal.tar.xz" + cd "zephyr-sdk-${ZEPHYR_SDK}" + ./setup.sh -h -c -t x86_64-zephyr-elf + echo "ZEPHYR_SDK_INSTALL_DIR=$HOME/zephyr-sdk-${ZEPHYR_SDK}" >> "$GITHUB_ENV" + + # The SDK's QEMU lacks SLIRP; see zephyr/README.md. + - name: Select a QEMU with SLIRP + run: | + set -euo pipefail + if ! qemu-system-i386 -netdev help 2>&1 | grep -qx user; then + echo "::error::distro qemu-system-i386 has no 'user' netdev" + exit 1 + fi + echo "QEMU_BIN_PATH=/usr/bin" >> "$GITHUB_ENV" + + - name: Unit tests + run: | + cd "$HOME/zephyrproject" + "$HOME/zephyr-venv/bin/python" zephyr/scripts/twister \ + -T "${{ github.workspace }}/zephyr/tests" \ + -T "${{ github.workspace }}/zephyr/samples" \ + -p qemu_x86 \ + -x=EXTRA_ZEPHYR_MODULES=${{ github.workspace }} \ + --outdir "$HOME/twister-unit" -vv + # twister exits 0 when every configuration is filtered out, so name + # the suites that have to have run. + "$HOME/zephyr-venv/bin/python" \ + "${{ github.workspace }}/scripts/ci/twister-assert-ran.py" \ + "$HOME/twister-unit/twister.json" \ + wolfcert.unit.smoke wolfcert.unit.keygen wolfcert.unit.csr \ + wolfcert.unit.csr_attrs wolfcert.unit.store \ + wolfcert.unit.parse_negative wolfcert.unit.scep_msg + + - name: Start the host EST server + run: | + set -euo pipefail + nohup "$WOLFCERT_BUILD/wolfcert-server" --proto est --listen 0.0.0.0:8443 \ + --basic alice:hunter2 \ + --tls-cert examples/certs/ecc/server-cert.pem \ + --tls-key examples/certs/ecc/server-key.pem \ + > "$HOME/wolfcert-server.log" 2>&1 & + for _ in $(seq 20); do + (echo > /dev/tcp/127.0.0.1/8443) >/dev/null 2>&1 && exit 0 + sleep 0.5 + done + echo "::error::wolfcert-server did not come up"; exit 1 + + - name: EST enrollment gate + run: | + cd "$HOME/zephyrproject" + "$HOME/zephyr-venv/bin/python" zephyr/scripts/twister \ + -T "${{ github.workspace }}/zephyr/tests/wolfcert_est" \ + -T "${{ github.workspace }}/zephyr/samples" \ + -p qemu_x86 -X wolfcert_est_server \ + -x=EXTRA_ZEPHYR_MODULES=${{ github.workspace }} \ + --outdir "$HOME/twister-est" -vv + "$HOME/zephyr-venv/bin/python" \ + "${{ github.workspace }}/scripts/ci/twister-assert-ran.py" \ + "$HOME/twister-est/twister.json" \ + wolfcert.est.enroll sample.wolfcert.est_client + + - name: Upload logs on failure + if: failure() + uses: actions/upload-artifact@v6 + with: + name: zephyr-twister-logs + path: | + ~/twister-unit/**/handler.log + ~/twister-unit/**/build.log + ~/twister-est/**/handler.log + ~/twister-est/**/build.log + ~/wolfcert-server.log + retention-days: 5 diff --git a/Makefile.am b/Makefile.am index 3511941..7db25c1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -253,4 +253,8 @@ EXTRA_DIST = \ cmake/wolfCertTargets.cmake.in \ cmake/wolfcert.pc.in \ tests/integration/tls_test_util.h \ + tests/integration/est_client_cases.h \ tests/integration/cli_proto_scoping.sh + +# Zephyr module port +include zephyr/include.am diff --git a/docs/EMBEDDED.md b/docs/EMBEDDED.md index ca43ded..e0cee50 100644 --- a/docs/EMBEDDED.md +++ b/docs/EMBEDDED.md @@ -292,6 +292,32 @@ take that opt-out for a hosted EST service: the server would return its default certificate and `verify_server` would reject the handshake with `WOLFCERT_ERR_TLS`. +## 8. Zephyr + +wolfCert ships a Zephyr module under `zephyr/`; see `zephyr/README.md` for +Kconfig, the west manifest and running the on-target tests. Three sizing +points belong here. + +**The stack, not the heap, is what bites first.** `z_main_stack` grows down +into `z_idle_stacks`, so a main stack too small for RSA key generation +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 defaults for `CONFIG_ISR_STACK_SIZE` (2048) and +`CONFIG_IDLE_STACK_SIZE` (320) are also 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 +error. Targets with no RTC must set `CLOCK_REALTIME` (or supply an RTC, or +SNTP) before the first certificate is parsed. + +**Repeated enrollments need more sockets.** Zephyr's default six network +contexts and 1500 ms TIME_WAIT leave none free for a third connection. A +device that renews or retries wants a larger `CONFIG_NET_MAX_CONTEXTS` and a +smaller `CONFIG_NET_TCP_TIME_WAIT_DELAY`; the failure is a connect error with +no hint at the cause. + ## A worked "small footprint" wolfSSL config ```c diff --git a/examples/certs/README.md b/examples/certs/README.md index b058882..e6de994 100644 --- a/examples/certs/README.md +++ b/examples/certs/README.md @@ -12,7 +12,7 @@ Two algorithm families, each a self-signed CA plus a server and a client leaf: | File | Role | |------|------| | `/ca-cert.pem` / `ca-key.pem` | self-signed CA — the trust anchor | -| `/server-cert.pem` / `server-key.pem` | TLS server leaf, SAN = `localhost`, `127.0.0.1`, `::1` | +| `/server-cert.pem` / `server-key.pem` | TLS server leaf, SAN = `localhost`, `127.0.0.1`, `::1`, `10.0.2.2` | | `/client-cert.pem` / `client-key.pem` | mTLS client leaf | - `ecc/` — ECC P-256 (ECDSA-with-SHA256) diff --git a/examples/certs/ecc/server-cert.pem b/examples/certs/ecc/server-cert.pem index 3ffdb97..7dbd6b1 100644 --- a/examples/certs/ecc/server-cert.pem +++ b/examples/certs/ecc/server-cert.pem @@ -1,12 +1,12 @@ -----BEGIN CERTIFICATE----- -MIIB1DCCAXmgAwIBAgIUNNkjXj154QDAfY75dOhgVMSexggwCgYIKoZIzj0EAwIw +MIIB2jCCAX+gAwIBAgIUU8cxs5a0N08AvYsENJTqXgn8AQEwCgYIKoZIzj0EAwIw KDEmMCQGA1UEAwwdd29sZkNlcnQgRXhhbXBsZSBFQ0MgUC0yNTYgQ0EwHhcNMjYw -NjIzMTczMDI4WhcNMzYwNjIwMTczMDI4WjAUMRIwEAYDVQQDDAlsb2NhbGhvc3Qw +OTE0MDc0ODA3WhcNMzYwOTExMDc0ODA3WjAUMRIwEAYDVQQDDAlsb2NhbGhvc3Qw WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASRUZnW3nvVC1koNUxv8vPvli2glZmS -Cwxzl17oyDNPrNX5Pih/tdpPBmiv+Ls8ICTElOSpBDR1Fl/AnC7JrQcso4GUMIGR -MCwGA1UdEQQlMCOCCWxvY2FsaG9zdIcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATAT -BgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBT4Z3fX -ES77714hD4dp/xG4lXUTlDAfBgNVHSMEGDAWgBSvsq8Xy/EPSXky4IbTJmb2Quvr -SDAKBggqhkjOPQQDAgNJADBGAiEApv4EvqAj5rfuduw2lDPDL/G1LtePkXvxoNrl -EuiMvXMCIQDvTxeeRDDTQjMJzAO/V2FSEyQO2Bx207lmNqKVbHVmgw== +Cwxzl17oyDNPrNX5Pih/tdpPBmiv+Ls8ICTElOSpBDR1Fl/AnC7JrQcso4GaMIGX +MDIGA1UdEQQrMCmCCWxvY2FsaG9zdIcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAAYcE +CgACAjATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMB0GA1UdDgQW +BBT4Z3fXES77714hD4dp/xG4lXUTlDAfBgNVHSMEGDAWgBSvsq8Xy/EPSXky4IbT +Jmb2QuvrSDAKBggqhkjOPQQDAgNJADBGAiEA7g0YT11WpQjDD8cMYaRXGFWWVf3c +EvEOSKUQMRXJEjoCIQDByJr83B+BWy56e7yIf7uzp9vd1UC3h52CzP4of87+eQ== -----END CERTIFICATE----- diff --git a/examples/certs/gen-certs.sh b/examples/certs/gen-certs.sh index b574803..ad4f49d 100755 --- a/examples/certs/gen-certs.sh +++ b/examples/certs/gen-certs.sh @@ -8,7 +8,8 @@ # # /ca-cert.pem self-signed CA (trust anchor) # /ca-key.pem -# /server-cert.pem TLS server leaf, SAN = localhost / 127.0.0.1 / ::1 +# /server-cert.pem TLS server leaf, SAN = localhost / 127.0.0.1 / +# ::1 / 10.0.2.2 # /server-key.pem # /client-cert.pem mTLS client leaf # /client-key.pem @@ -65,8 +66,10 @@ gen_pki() { -out "$dir/ca-cert.pem" # --- TLS server leaf (CN + SAN so hostname verification passes) ----- + # 10.0.2.2 is the QEMU SLIRP gateway, i.e. the host as seen from a Zephyr + # guest, so the same leaf serves the emulator tests. gen_leaf "$dir" server "/CN=localhost" \ -"subjectAltName=DNS:localhost,IP:127.0.0.1,IP:::1 +"subjectAltName=DNS:localhost,IP:127.0.0.1,IP:::1,IP:10.0.2.2 extendedKeyUsage=serverAuth" "$@" # --- mTLS client leaf ---------------------------------------------- diff --git a/examples/certs/mldsa/server-cert.pem b/examples/certs/mldsa/server-cert.pem index e168f21..6a09c75 100644 --- a/examples/certs/mldsa/server-cert.pem +++ b/examples/certs/mldsa/server-cert.pem @@ -1,7 +1,7 @@ -----BEGIN CERTIFICATE----- -MIIV2DCCCNWgAwIBAgIUbXlk8cqX2Y5yV2vcLa51G9XYpgQwCwYJYIZIAWUDBAMS +MIIV3jCCCNugAwIBAgIUXzdvKMVdd46hXamIgcepF6ddQu4wCwYJYIZIAWUDBAMS MCgxJjAkBgNVBAMMHXdvbGZDZXJ0IEV4YW1wbGUgTUwtRFNBLTY1IENBMB4XDTI2 -MDYyMzE3MzAyOFoXDTM2MDYyMDE3MzAyOFowFDESMBAGA1UEAwwJbG9jYWxob3N0 +MDkxNDA3NDgwN1oXDTM2MDkxMTA3NDgwN1owFDESMBAGA1UEAwwJbG9jYWxob3N0 MIIHsjALBglghkgBZQMEAxIDggehAJTEHNhsTUiDj2CgAJdr3wvh0KizuWSjwsz6 V5fCkwqtUGvXEUVGjNPcwTWNZlB/l8b9pDXC7SeJTAJmhXEpbOy9ZDQBHHJQQnkY Ei1UplOYtkpIjrfYPiaRDkh+TcXa1fsRKEhEtFWzTKN29awHv8esHVJxBeSD9wEK @@ -43,77 +43,77 @@ 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 -jbK1vAAAAAAAAAAAAAAAAAAAAAAAAAkPExceJQ== +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 -----END CERTIFICATE----- diff --git a/scripts/ci/twister-assert-ran.py b/scripts/ci/twister-assert-ran.py new file mode 100755 index 0000000..80565c2 --- /dev/null +++ b/scripts/ci/twister-assert-ran.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-3.0-or-later +"""Assert that named twister suites actually executed and passed. + +twister exits 0 when every selected configuration is filtered out or built +only, so a fixture rename or a platform_allow typo turns a gate green without +running anything. Pass the twister.json and the suite names the step exists to +run; this fails when one is missing, skipped, built-only or failed. + + twister-assert-ran.py [ ...] +""" + +import json +import sys + + +def main(argv): + if len(argv) < 3: + print(__doc__.strip(), file=sys.stderr) + return 2 + + report, wanted = argv[1], argv[2:] + + try: + with open(report) as fh: + suites = json.load(fh).get("testsuites", []) + except (OSError, ValueError) as exc: + print(f"::error::cannot read {report}: {exc}", file=sys.stderr) + return 1 + + # twister names a suite by its path; match on the trailing component. + seen = {} + for suite in suites: + seen[suite.get("name", "").rsplit("/", 1)[-1]] = suite.get("status") + + rc = 0 + for name in wanted: + status = seen.get(name) + if status is None: + print(f"::error::{name} did not run (not in {report})", file=sys.stderr) + rc = 1 + elif status != "passed": + print(f"::error::{name} status is '{status}', expected 'passed'", + file=sys.stderr) + rc = 1 + else: + print(f"{name}: passed") + + return rc + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) diff --git a/scripts/ci/zephyr-add-wolfssl.py b/scripts/ci/zephyr-add-wolfssl.py new file mode 100755 index 0000000..bccefd6 --- /dev/null +++ b/scripts/ci/zephyr-add-wolfssl.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-3.0-or-later +"""Add wolfSSL to a Zephyr west manifest. + +Usage: zephyr-add-wolfssl.py + +wolfCert needs wolfSSL features the upstream manifest does not carry, and +calls wc_SetDNSEntry(), which is in no release tag. Idempotent. +""" + +import sys + +REMOTE = """ - name: wolfssl + url-base: https://github.com/wolfssl +""" + +ANCHOR_REMOTE = """ - name: babblesim + url-base: https://github.com/BabbleSim +""" + +ANCHOR_PROJECT = " # zephyr-keep-sorted-stop" + + +def main(path, revision): + with open(path) as f: + text = f.read() + + if "url-base: https://github.com/wolfssl" in text: + print(f"{path}: wolfSSL already present") + return 0 + + if ANCHOR_REMOTE not in text or ANCHOR_PROJECT not in text: + print(f"{path}: anchors not found; manifest layout changed", file=sys.stderr) + return 1 + + text = text.replace(ANCHOR_REMOTE, ANCHOR_REMOTE + REMOTE, 1) + project = ( + f"{ANCHOR_PROJECT}\n" + f" - name: wolfssl\n" + f" revision: {revision}\n" + f" path: modules/crypto/wolfssl\n" + f" remote: wolfssl" + ) + text = text.replace(ANCHOR_PROJECT, project, 1) + + with open(path, "w") as f: + f.write(text) + print(f"{path}: added wolfSSL at {revision}") + return 0 + + +if __name__ == "__main__": + if len(sys.argv) != 3: + print(__doc__, file=sys.stderr) + sys.exit(2) + sys.exit(main(sys.argv[1], sys.argv[2])) diff --git a/src/http.c b/src/http.c index 20b9c41..95e3661 100644 --- a/src/http.c +++ b/src/http.c @@ -940,11 +940,14 @@ static int read_body(WolfCertConn* c, DynBuf* rx, size_t body_start, } size_t n = (size_t)length; - uint8_t* b = (uint8_t*)WOLFCERT_XMALLOC(n, heap); + /* Ask for one byte on a Content-Length: 0 body: XMALLOC(0) returns + * NULL on some allocators */ + uint8_t* b = (uint8_t*)WOLFCERT_XMALLOC(n ? n : 1, heap); if (b == NULL) return WOLFCERT_ERR_MEMORY; - memcpy(b, rx->buf + body_start, n); + if (n > 0) + memcpy(b, rx->buf + body_start, n); *out = b; *out_len = n; diff --git a/tests/integration/est_client_cases.h b/tests/integration/est_client_cases.h new file mode 100644 index 0000000..b0ffb1d --- /dev/null +++ b/tests/integration/est_client_cases.h @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfCert. + * + * wolfCert is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfCert is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfCert. If not, see . + */ + +/* + * EST client cases, parameterised by the server config so they run against an + * in-process server or one across a network. Each returns 0 on success and + * prints the failing check. + */ + +#ifndef WOLFCERT_EST_CLIENT_CASES_H +#define WOLFCERT_EST_CLIENT_CASES_H + +#include + +#include "tls_test_util.h" + +#include +#include +#include +#include + +#include +#include + +#ifndef REQUIRE +#define REQUIRE(cond) \ + do { \ + if (!(cond)) { \ + fprintf(stderr, "FAIL %s:%d %s\n", __FILE__, __LINE__, #cond); \ + return 1; \ + } \ + } while (0) +#endif + +/* Enroll one key of the given type and verify the issued cert against ca_pem. */ +static inline int enroll_one(const WolfCertServerCfg* client_cfg, + WolfCertKeyType kt, int param, + const WolfCertBuffer* ca_pem) +{ + WolfCertKeyCfg kcfg = { .type = kt, .param = param, + .dev_id = WOLFCERT_DEVID_SOFTWARE }; + WolfCertKey* dk = NULL; + REQUIRE(wolfcert_key_generate(&kcfg, &dk) == WOLFCERT_OK); + + const char* dns[] = { "device-7.local" }; + WolfCertCertMeta meta = { .subject_dn = "CN=device-7,O=Acme", + .san_dns = dns, .san_dns_len = 1 }; + WolfCertBuffer csr = { 0 }; + REQUIRE(wolfcert_csr_build(dk, &meta, &csr) == WOLFCERT_OK); + + WolfCertBuffer issued = { 0 }; + int rc = wolfcert_est_simple_enroll(client_cfg, csr.data, csr.len, &issued); + if (rc != WOLFCERT_OK) { + fprintf(stderr, "enroll_one: rc=%d (%s) wolfssl=%d %s\n", rc, + wolfcert_strerror(rc), wolfcert_last_wolfssl_err(), + wolfcert_last_error_message()); + } + REQUIRE(rc == WOLFCERT_OK); + + DerBuffer* issued_der = NULL; + REQUIRE(wc_PemToDer(issued.data, (long)issued.len, CERT_TYPE, + &issued_der, NULL, NULL, NULL) == 0); + WOLFSSL_CERT_MANAGER* cm = wolfSSL_CertManagerNew(); + REQUIRE(cm != NULL); + REQUIRE(wolfSSL_CertManagerLoadCABuffer(cm, ca_pem->data, (long)ca_pem->len, + WOLFSSL_FILETYPE_PEM) + == WOLFSSL_SUCCESS); + REQUIRE(wolfSSL_CertManagerVerifyBuffer(cm, issued_der->buffer, + (long)issued_der->length, + WOLFSSL_FILETYPE_ASN1) + == WOLFSSL_SUCCESS); + wolfSSL_CertManagerFree(cm); + wc_FreeDer(&issued_der); + + wolfcert_buffer_free(&csr); + wolfcert_buffer_free(&issued); + wolfcert_key_free(dk); + return 0; +} + +static inline int has_alt(const DNS_entry* list, int type, const char* val, + int len) +{ + for (const DNS_entry* e = list; e != NULL; e = e->next) { + if (e->type == type && e->len == len && + memcmp(e->name, val, (size_t)len) == 0) + return 1; + } + return 0; +} + +/* Regression: a CSR carrying DNS + IP + rfc822 (email) SANs must round-trip + * into the issued certificate. wolfSSL splits parsed alt names by type -- + * rfc822Name lands in DecodedCert.altEmailNames, not altNames -- so the CA + * must recombine the lists when re-emitting. This previously dropped the + * email SAN from the issued cert entirely. */ +static inline int enroll_check_san(const WolfCertServerCfg* client_cfg) +{ + WolfCertKeyCfg kcfg = { .type = TEST_ENROLL_KEY_TYPE, .param = TEST_ENROLL_KEY_PARAM, + .dev_id = WOLFCERT_DEVID_SOFTWARE }; + WolfCertKey* dk = NULL; + REQUIRE(wolfcert_key_generate(&kcfg, &dk) == WOLFCERT_OK); + + const char* dns[] = { "device-san.local" }; + const char* ips[] = { "10.20.30.40" }; + const char* emails[] = { "dev-san@example.com" }; + WolfCertCertMeta meta = { .subject_dn = "CN=device-san", + .san_dns = dns, .san_dns_len = 1, + .san_ip = ips, .san_ip_len = 1, + .san_email = emails, .san_email_len = 1 }; + WolfCertBuffer csr = { 0 }; + REQUIRE(wolfcert_csr_build(dk, &meta, &csr) == WOLFCERT_OK); + + WolfCertBuffer issued = { 0 }; + int rc = wolfcert_est_simple_enroll(client_cfg, csr.data, csr.len, &issued); + if (rc != WOLFCERT_OK) { + fprintf(stderr, "enroll_check_san: rc=%d (%s) wolfssl=%d %s\n", rc, + wolfcert_strerror(rc), wolfcert_last_wolfssl_err(), + wolfcert_last_error_message()); + } + REQUIRE(rc == WOLFCERT_OK); + + DerBuffer* der = NULL; + REQUIRE(wc_PemToDer(issued.data, (long)issued.len, CERT_TYPE, &der, + NULL, NULL, NULL) == 0); + + DecodedCert dc; + wc_InitDecodedCert(&dc, der->buffer, der->length, NULL); + REQUIRE(wc_ParseCert(&dc, CERT_TYPE, NO_VERIFY, NULL) == 0); + + /* DNS + IP travel in altNames. */ + const byte ip[] = { 10, 20, 30, 40 }; + REQUIRE(has_alt(dc.altNames, ASN_DNS_TYPE, "device-san.local", + (int)strlen("device-san.local"))); + REQUIRE(has_alt(dc.altNames, ASN_IP_TYPE, (const char*)ip, (int)sizeof(ip))); + /* The regression guard: email lands in altEmailNames, must still survive. */ + REQUIRE(has_alt(dc.altEmailNames, ASN_RFC822_TYPE, "dev-san@example.com", + (int)strlen("dev-san@example.com"))); + + wc_FreeDecodedCert(&dc); + wc_FreeDer(&der); + wolfcert_buffer_free(&csr); + wolfcert_buffer_free(&issued); + wolfcert_key_free(dk); + return 0; +} + +/* HTTP Basic (RFC 7030 section 3.2.3) must authenticate a keep-alive session + * too, not just the one-shot calls: the credentials have to ride every request + * on the connection. Enrolls with the good credentials from `client_cfg`, then + * repeats with a wrong password and requires a rejection - so a session that + * silently dropped the Authorization header cannot pass both halves. */ +static inline int session_basic_auth(const WolfCertServerCfg* client_cfg) +{ + WolfCertKeyCfg kcfg = { .type = TEST_ENROLL_KEY_TYPE, .param = TEST_ENROLL_KEY_PARAM, + .dev_id = WOLFCERT_DEVID_SOFTWARE }; + WolfCertKey* dk = NULL; + REQUIRE(wolfcert_key_generate(&kcfg, &dk) == WOLFCERT_OK); + WolfCertCertMeta meta = { .subject_dn = "CN=session-basic-auth" }; + WolfCertBuffer csr = { 0 }; + REQUIRE(wolfcert_csr_build(dk, &meta, &csr) == WOLFCERT_OK); + + WolfCertEstSession* s = NULL; + int orc = wolfcert_est_session_open(client_cfg, &s); + if (orc != WOLFCERT_OK) { + fprintf(stderr, "session_open: rc=%d (%s) wolfssl=%d %s\n", orc, + wolfcert_strerror(orc), wolfcert_last_wolfssl_err(), + wolfcert_last_error_message()); + } + REQUIRE(orc == WOLFCERT_OK); + + WolfCertBuffer ca_pem = { 0 }, issued = { 0 }; + REQUIRE(wolfcert_est_session_get_cacerts(s, &ca_pem) == WOLFCERT_OK); + REQUIRE(ca_pem.len > 0); + REQUIRE(wolfcert_est_session_simple_enroll(s, csr.data, csr.len, &issued) + == WOLFCERT_OK); + + DerBuffer* issued_der = NULL; + REQUIRE(wc_PemToDer(issued.data, (long)issued.len, CERT_TYPE, + &issued_der, NULL, NULL, NULL) == 0); + wc_FreeDer(&issued_der); + + wolfcert_buffer_free(&ca_pem); + wolfcert_buffer_free(&issued); + wolfcert_est_session_close(s); + + /* Same session shape, wrong password: the server must reject the enroll. */ + WolfCertServerCfg bad_cfg = *client_cfg; + bad_cfg.proto_opts.est.password = "wrong"; + + WolfCertEstSession* bs = NULL; + REQUIRE(wolfcert_est_session_open(&bad_cfg, &bs) == WOLFCERT_OK); + + WolfCertBuffer bad_out = { 0 }; + int brc = wolfcert_est_session_simple_enroll(bs, csr.data, csr.len, &bad_out); + if (brc != WOLFCERT_ERR_AUTH) { + fprintf(stderr, "bad-password enroll: rc=%d (%s) wolfssl=%d %s\n", brc, + wolfcert_strerror(brc), wolfcert_last_wolfssl_err(), + wolfcert_last_error_message()); + } + REQUIRE(brc == WOLFCERT_ERR_AUTH); + REQUIRE(bad_out.data == NULL); + wolfcert_est_session_close(bs); + + wolfcert_buffer_free(&csr); + wolfcert_key_free(dk); + return 0; +} + +#endif /* WOLFCERT_EST_CLIENT_CASES_H */ diff --git a/tests/integration/test_est_roundtrip.c b/tests/integration/test_est_roundtrip.c index 33b8164..f5c68b0 100644 --- a/tests/integration/test_est_roundtrip.c +++ b/tests/integration/test_est_roundtrip.c @@ -28,7 +28,7 @@ #include #include -#include "tls_test_util.h" +#include "est_client_cases.h" #include #include @@ -37,14 +37,6 @@ #include #include -#define REQUIRE(cond) \ - do { \ - if (!(cond)) { \ - fprintf(stderr, "FAIL %s:%d %s\n", __FILE__, __LINE__, #cond); \ - return 1; \ - } \ - } while (0) - static void* server_thread(void* arg) { wolfcert_server_run((WolfCertServer*)arg); @@ -106,107 +98,6 @@ static int counting_disconnect(void* ctx, void* conn) return WOLFCERT_OK; } -static int enroll_one(const WolfCertServerCfg* client_cfg, - WolfCertKeyType kt, int param, - const WolfCertBuffer* ca_pem) -{ - WolfCertKeyCfg kcfg = { .type = kt, .param = param, - .dev_id = WOLFCERT_DEVID_SOFTWARE }; - WolfCertKey* dk = NULL; - REQUIRE(wolfcert_key_generate(&kcfg, &dk) == WOLFCERT_OK); - - const char* dns[] = { "device-7.local" }; - WolfCertCertMeta meta = { .subject_dn = "CN=device-7,O=Acme", - .san_dns = dns, .san_dns_len = 1 }; - WolfCertBuffer csr = { 0 }; - REQUIRE(wolfcert_csr_build(dk, &meta, &csr) == WOLFCERT_OK); - - WolfCertBuffer issued = { 0 }; - REQUIRE(wolfcert_est_simple_enroll(client_cfg, csr.data, csr.len, &issued) - == WOLFCERT_OK); - - DerBuffer* issued_der = NULL; - REQUIRE(wc_PemToDer(issued.data, (long)issued.len, CERT_TYPE, - &issued_der, NULL, NULL, NULL) == 0); - WOLFSSL_CERT_MANAGER* cm = wolfSSL_CertManagerNew(); - REQUIRE(cm != NULL); - REQUIRE(wolfSSL_CertManagerLoadCABuffer(cm, ca_pem->data, (long)ca_pem->len, - WOLFSSL_FILETYPE_PEM) - == WOLFSSL_SUCCESS); - REQUIRE(wolfSSL_CertManagerVerifyBuffer(cm, issued_der->buffer, - (long)issued_der->length, - WOLFSSL_FILETYPE_ASN1) - == WOLFSSL_SUCCESS); - wolfSSL_CertManagerFree(cm); - wc_FreeDer(&issued_der); - - wolfcert_buffer_free(&csr); - wolfcert_buffer_free(&issued); - wolfcert_key_free(dk); - return 0; -} - -static int has_alt(const DNS_entry* list, int type, const char* val, int len) -{ - for (const DNS_entry* e = list; e != NULL; e = e->next) { - if (e->type == type && e->len == len && - memcmp(e->name, val, (size_t)len) == 0) - return 1; - } - return 0; -} - -/* Regression: a CSR carrying DNS + IP + rfc822 (email) SANs must round-trip - * into the issued certificate. wolfSSL splits parsed alt names by type -- - * rfc822Name lands in DecodedCert.altEmailNames, not altNames -- so the CA - * must recombine the lists when re-emitting. This previously dropped the - * email SAN from the issued cert entirely. */ -static int enroll_check_san(const WolfCertServerCfg* client_cfg) -{ - WolfCertKeyCfg kcfg = { .type = TEST_ENROLL_KEY_TYPE, .param = TEST_ENROLL_KEY_PARAM, - .dev_id = WOLFCERT_DEVID_SOFTWARE }; - WolfCertKey* dk = NULL; - REQUIRE(wolfcert_key_generate(&kcfg, &dk) == WOLFCERT_OK); - - const char* dns[] = { "device-san.local" }; - const char* ips[] = { "10.20.30.40" }; - const char* emails[] = { "dev-san@example.com" }; - WolfCertCertMeta meta = { .subject_dn = "CN=device-san", - .san_dns = dns, .san_dns_len = 1, - .san_ip = ips, .san_ip_len = 1, - .san_email = emails, .san_email_len = 1 }; - WolfCertBuffer csr = { 0 }; - REQUIRE(wolfcert_csr_build(dk, &meta, &csr) == WOLFCERT_OK); - - WolfCertBuffer issued = { 0 }; - REQUIRE(wolfcert_est_simple_enroll(client_cfg, csr.data, csr.len, &issued) - == WOLFCERT_OK); - - DerBuffer* der = NULL; - REQUIRE(wc_PemToDer(issued.data, (long)issued.len, CERT_TYPE, &der, - NULL, NULL, NULL) == 0); - - DecodedCert dc; - wc_InitDecodedCert(&dc, der->buffer, der->length, NULL); - REQUIRE(wc_ParseCert(&dc, CERT_TYPE, NO_VERIFY, NULL) == 0); - - /* DNS + IP travel in altNames. */ - const byte ip[] = { 10, 20, 30, 40 }; - REQUIRE(has_alt(dc.altNames, ASN_DNS_TYPE, "device-san.local", - (int)strlen("device-san.local"))); - REQUIRE(has_alt(dc.altNames, ASN_IP_TYPE, (const char*)ip, (int)sizeof(ip))); - /* The regression guard: email lands in altEmailNames, must still survive. */ - REQUIRE(has_alt(dc.altEmailNames, ASN_RFC822_TYPE, "dev-san@example.com", - (int)strlen("dev-san@example.com"))); - - wc_FreeDecodedCert(&dc); - wc_FreeDer(&der); - wolfcert_buffer_free(&csr); - wolfcert_buffer_free(&issued); - wolfcert_key_free(dk); - return 0; -} - /* wolfcert_csr_build() leaves wolfSSL's UTF8String default in place, so a * PrintableString CSR has to be built straight on wolfSSL. */ static int enroll_raw_csr(const WolfCertServerCfg* client_cfg, @@ -332,57 +223,6 @@ static int enroll_check_subject_rdns(const WolfCertServerCfg* client_cfg) return 0; } -/* HTTP Basic (RFC 7030 section 3.2.3) must authenticate a keep-alive session - * too, not just the one-shot calls: the credentials have to ride every request - * on the connection. Enrolls with the good credentials from `client_cfg`, then - * repeats with a wrong password and requires a rejection - so a session that - * silently dropped the Authorization header cannot pass both halves. */ -static int session_basic_auth(const WolfCertServerCfg* client_cfg) -{ - WolfCertKeyCfg kcfg = { .type = TEST_ENROLL_KEY_TYPE, .param = TEST_ENROLL_KEY_PARAM, - .dev_id = WOLFCERT_DEVID_SOFTWARE }; - WolfCertKey* dk = NULL; - REQUIRE(wolfcert_key_generate(&kcfg, &dk) == WOLFCERT_OK); - WolfCertCertMeta meta = { .subject_dn = "CN=session-basic-auth" }; - WolfCertBuffer csr = { 0 }; - REQUIRE(wolfcert_csr_build(dk, &meta, &csr) == WOLFCERT_OK); - - WolfCertEstSession* s = NULL; - REQUIRE(wolfcert_est_session_open(client_cfg, &s) == WOLFCERT_OK); - - WolfCertBuffer ca_pem = { 0 }, issued = { 0 }; - REQUIRE(wolfcert_est_session_get_cacerts(s, &ca_pem) == WOLFCERT_OK); - REQUIRE(ca_pem.len > 0); - REQUIRE(wolfcert_est_session_simple_enroll(s, csr.data, csr.len, &issued) - == WOLFCERT_OK); - - DerBuffer* issued_der = NULL; - REQUIRE(wc_PemToDer(issued.data, (long)issued.len, CERT_TYPE, - &issued_der, NULL, NULL, NULL) == 0); - wc_FreeDer(&issued_der); - - wolfcert_buffer_free(&ca_pem); - wolfcert_buffer_free(&issued); - wolfcert_est_session_close(s); - - /* Same session shape, wrong password: the server must reject the enroll. */ - WolfCertServerCfg bad_cfg = *client_cfg; - bad_cfg.proto_opts.est.password = "wrong"; - - WolfCertEstSession* bs = NULL; - REQUIRE(wolfcert_est_session_open(&bad_cfg, &bs) == WOLFCERT_OK); - - WolfCertBuffer bad_out = { 0 }; - REQUIRE(wolfcert_est_session_simple_enroll(bs, csr.data, csr.len, &bad_out) - == WOLFCERT_ERR_AUTH); - REQUIRE(bad_out.data == NULL); - wolfcert_est_session_close(bs); - - wolfcert_buffer_free(&csr); - wolfcert_key_free(dk); - return 0; -} - int main(void) { REQUIRE(wolfcert_init(NULL) == WOLFCERT_OK); diff --git a/tests/unit/test_http.c b/tests/unit/test_http.c index a165a8f..5ec6b50 100644 --- a/tests/unit/test_http.c +++ b/tests/unit/test_http.c @@ -721,6 +721,10 @@ static int test_request_transfer_encoding(void) WolfCertHttpResponse resp = { 0 }; REQUIRE(wolfcert_http_request(&req, &resp) == WOLFCERT_OK); REQUIRE(resp.status_code == 200); + /* The server answers Content-Length: 0. An empty body is still a buffer: + * length 0 and non-NULL. */ + REQUIRE(resp.body_len == 0); + REQUIRE(resp.body != NULL); wolfcert_http_response_free(&resp); pthread_join(tid, NULL); diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt new file mode 100644 index 0000000..3d7c7d4 --- /dev/null +++ b/zephyr/CMakeLists.txt @@ -0,0 +1,107 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +if(CONFIG_WOLFCERT) + zephyr_interface_library_named(wolfCert) + + set(WOLFCERT_DIR ${ZEPHYR_CURRENT_MODULE_DIR}) + + # ---- generated wolfcert/options.h ---------------------------------------- + # Render options.h.in from Kconfig, so wolfcert/types.h reads its config the + # same way it does under CMake and autoconf. + macro(_wc_opt out flag sym) + if(${flag}) + set(${out} "#define ${sym} 1") + else() + set(${out} "/* ${sym} not enabled */") + endif() + endmacro() + # Platform gates always define a macro: have_sym enabled, no_sym disabled. + macro(_wc_plat out flag have_sym no_sym) + if(${flag}) + set(${out} "#define ${have_sym} 1") + else() + set(${out} "#define ${no_sym} 1") + endif() + endmacro() + + _wc_opt(WOLFCERT_OPT_EST "${CONFIG_WOLFCERT_EST}" WOLFCERT_HAVE_EST) + _wc_opt(WOLFCERT_OPT_SCEP "${CONFIG_WOLFCERT_SCEP}" WOLFCERT_HAVE_SCEP) + # Host-only; zephyr/README.md says why. + _wc_opt(WOLFCERT_OPT_SERVER "OFF" WOLFCERT_HAVE_SERVER) + _wc_plat(WOLFCERT_OPT_POSIX_STORE "${CONFIG_WOLFCERT_POSIX_STORE}" + WOLFCERT_HAVE_POSIX_STORE WOLFCERT_NO_POSIX_STORE) + _wc_plat(WOLFCERT_OPT_BUILTIN_TRANSPORT "${CONFIG_WOLFCERT_BUILTIN_TRANSPORT}" + WOLFCERT_HAVE_BUILTIN_TRANSPORT WOLFCERT_NO_BUILTIN_TRANSPORT) + _wc_opt(WOLFCERT_OPT_RSA "${CONFIG_WOLFCERT_RSA}" WOLFCERT_HAVE_RSA) + _wc_opt(WOLFCERT_OPT_ECC "${CONFIG_WOLFCERT_ECC}" WOLFCERT_HAVE_ECC) + _wc_opt(WOLFCERT_OPT_ED25519 "${CONFIG_WOLFCERT_ED25519}" WOLFCERT_HAVE_ED25519) + _wc_opt(WOLFCERT_OPT_ED448 "${CONFIG_WOLFCERT_ED448}" WOLFCERT_HAVE_ED448) + _wc_opt(WOLFCERT_OPT_MLDSA "${CONFIG_WOLFCERT_MLDSA}" WOLFCERT_HAVE_MLDSA) + + set(WOLFCERT_GEN_DIR ${PROJECT_BINARY_DIR}/include/generated/wolfcert_gen) + configure_file(${WOLFCERT_DIR}/wolfcert/options.h.in + ${WOLFCERT_GEN_DIR}/wolfcert/options.h @ONLY) + + # Generated directory first, so an in-source wolfcert/options.h left behind + # by a host ./configure cannot shadow the one Kconfig just produced. Apps + # inherit this order; they must not add the module root themselves. + zephyr_include_directories(${WOLFCERT_GEN_DIR}) + zephyr_include_directories(${WOLFCERT_DIR}) + + # ---- library sources ------------------------------------------------------ + zephyr_library() + zephyr_library_sources( + ${WOLFCERT_DIR}/src/wolfcert.c + ${WOLFCERT_DIR}/src/version.c + ${WOLFCERT_DIR}/src/errors.c + ${WOLFCERT_DIR}/src/internal.c + ${WOLFCERT_DIR}/src/key_algs.c + ${WOLFCERT_DIR}/src/keygen.c + ${WOLFCERT_DIR}/src/csr.c + ${WOLFCERT_DIR}/src/store.c + ${WOLFCERT_DIR}/src/http.c + ${WOLFCERT_DIR}/src/client.c) + + if(CONFIG_WOLFCERT_BUILTIN_TRANSPORT) + zephyr_library_sources(${WOLFCERT_DIR}/src/net_posix.c) + endif() + + if(CONFIG_WOLFCERT_EST OR CONFIG_WOLFCERT_SCEP) + zephyr_library_sources(${WOLFCERT_DIR}/src/pkcs7_util.c) + endif() + if(CONFIG_WOLFCERT_EST) + zephyr_library_sources( + ${WOLFCERT_DIR}/src/est/est_client.c + ${WOLFCERT_DIR}/src/est/csr_attrs.c) + endif() + if(CONFIG_WOLFCERT_SCEP) + zephyr_library_sources( + ${WOLFCERT_DIR}/src/scep/scep_client.c + ${WOLFCERT_DIR}/src/scep/scep_msg.c) + endif() + + zephyr_library_link_libraries(wolfCert wolfSSL) + # Application sources need wolfSSL's settings too, and must not depend on + # CONFIG_APP_LINK_WITH_WOLFSSL being left at its default. + target_link_libraries(wolfCert INTERFACE wolfSSL) + + target_compile_definitions(wolfCert INTERFACE WOLFCERT_ZEPHYR) + + # Use the shipped wolfSSL settings unless the app named its own. This has to + # be global: wolfSSL's own sources need it and they do not link wolfCert. + if(NOT CONFIG_WOLFSSL_SETTINGS_FILE) + zephyr_compile_definitions( + WOLFSSL_SETTINGS_FILE="${WOLFCERT_DIR}/zephyr/wolfssl_user_settings.h") + endif() + + if(CONFIG_WOLFCERT_BUILD_TESTING) + target_compile_definitions(wolfCert INTERFACE WOLFCERT_BUILD_TESTING) + zephyr_library_compile_definitions(WOLFCERT_BUILD_TESTING) + endif() + + if(CONFIG_WOLFCERT_DEBUG) + zephyr_library_compile_options(-g3 -O0) + endif() + + target_link_libraries(wolfCert INTERFACE zephyr_interface) +endif() diff --git a/zephyr/Kconfig b/zephyr/Kconfig new file mode 100644 index 0000000..287f158 --- /dev/null +++ b/zephyr/Kconfig @@ -0,0 +1,96 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +config ZEPHYR_WOLFCERT_MODULE + bool + +config WOLFCERT_PROMPTLESS + bool + help + Hides the wolfCert menu entry. Set this from your own Kconfig when you + "select WOLFCERT" there, so menuconfig shows it as always-on instead of + a switch the user cannot actually turn off. + +menuconfig WOLFCERT + bool "wolfCert Support" if !WOLFCERT_PROMPTLESS + depends on WOLFSSL + select NETWORKING + select POSIX_API + 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. + +if WOLFCERT + +config WOLFCERT_EST + bool "EST (RFC 7030) support" + default y + help + Enable the EST client. + +config WOLFCERT_SCEP + bool "SCEP (RFC 8894) support" + depends on WOLFCERT_RSA + help + Enable the SCEP client. + +config WOLFCERT_BUILTIN_TRANSPORT + bool "Built-in BSD-socket transport" + default y + help + Compile src/net_posix.c, which runs on the POSIX API layer. Hostname + lookup additionally needs DNS_RESOLVER; numeric addresses work without + it. + +config WOLFCERT_POSIX_STORE + bool "POSIX file storage backend" + help + Compile the POSIX file store. Off by default on Zephyr, which has no + filesystem in a typical image; a board backend over NVS or the settings + subsystem goes in your own tree. + +comment "Key algorithms" + +config WOLFCERT_RSA + bool "RSA keys" + default y + help + Required by SCEP. + +config WOLFCERT_ECC + bool "ECC keys" + default y + +config WOLFCERT_ED25519 + bool "Ed25519 keys" + +config WOLFCERT_ED448 + bool "Ed448 keys" + +config WOLFCERT_MLDSA + bool "ML-DSA keys" + +config WOLFCERT_SMALL_MATH + bool "Smaller, slower SP math" + help + Define WOLFSSL_SP_SMALL: smaller wolfCrypt math at a large speed cost, + worst on RSA key generation. For a flash-constrained board. + +config WOLFCERT_BUILD_TESTING + bool "Export internal symbols for in-tree tests" + help + Define WOLFCERT_BUILD_TESTING. Needed only by the in-tree tests that + reach internal helpers. + +config WOLFCERT_DEBUG + bool "wolfCert debug build" + help + Compile wolfCert with -g3 -O0. + +config APP_LINK_WITH_WOLFCERT + bool "Link 'app' with wolfCert" + default y + help + Give 'app' the wolfCert interface library, which carries the compile + definitions the public headers expect. + +endif # WOLFCERT diff --git a/zephyr/README.md b/zephyr/README.md new file mode 100644 index 0000000..c476dc8 --- /dev/null +++ b/zephyr/README.md @@ -0,0 +1,87 @@ +# wolfCert Zephyr module + +Zephyr integration for wolfCert. For what the library does and how its API +works see the top-level `README.md`, `docs/ARCHITECTURE.md` and the headers in +`wolfcert/`. This file covers only what is specific to building it under +Zephyr. + +Only the client runs on target. wolfCert's in-tree test server drives sockets +directly rather than going through `WolfCertTransport`, so no Zephyr image +builds it; the on-target tests enrol against `wolfcert-server` on the host. + +## Requirements + +- Zephyr **v4.4.2** or newer and its matching SDK (v4.4.2 pins 1.0.1). +- wolfSSL from **master**: wolfCert calls `wc_SetDNSEntry()` and + `wc_SetAltNamesFromList()`, which are not in the v5.9.2 release. + +## Adding wolfCert to a west workspace + +```yaml +manifest: + remotes: + - name: wolfssl + url-base: https://github.com/wolfssl + + projects: + - name: wolfssl + path: modules/crypto/wolfssl + revision: master + remote: wolfssl + - name: wolfCert + path: modules/lib/wolfcert + revision: main + remote: wolfssl +``` + +To build from a local checkout without touching the manifest: + +```sh +west build -b qemu_x86 -- -DEXTRA_ZEPHYR_MODULES=/path/to/wolfCert +``` + +## Configuration + +`CONFIG_WOLFCERT=y` enables the library; set `CONFIG_WOLFSSL=y` with it. +`menuconfig` lists the rest under **wolfCert Support**. The module renders +`wolfcert/options.h` from those symbols at build time, so a Zephyr build needs +no `user_settings.h`. + +Three things the Kconfig does that are worth knowing: + +- **It supplies wolfSSL's configuration.** `zephyr/wolfssl_user_settings.h` is + used unless the application sets `CONFIG_WOLFSSL_SETTINGS_FILE` itself. The + wolfSSL module's own defaults do not satisfy `wolfcert/check_config.h`. +- **It selects `NETWORKING` and `POSIX_API`**, even for an image that opens no + socket: wolfSSL needs `clock_gettime` from one and the `AF_INET` definitions + its `OPENSSL_EXTRA` x509 code uses from the other. +- **`CONFIG_WOLFCERT_POSIX_STORE` is off by default.** Use the in-memory + backend, or write a `WolfCertStoreOps` over NVS or the settings subsystem. + +## Sizing + +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. + +## Sample + +`samples/wolfcert_est_client/` — EST enrollment against a host server. Its +README covers running and adapting it. + +## Running the tests + +```sh +west twister -T /zephyr/tests -p qemu_x86 \ + -x=EXTRA_ZEPHYR_MODULES=/path/to/wolfCert +``` + +A networked QEMU image defaults to SLIP and waits forever on `/tmp/slip.sock`. +The unit tests set `CONFIG_NET_TEST=y` to suppress that. An image that really +needs the network sets `CONFIG_NET_QEMU_USER=y` (SLIRP), which reaches the host +at `10.0.2.2` and needs no TAP device or root. + +It does need a QEMU with the SLIRP backend, which the Zephyr SDK's build does +not link. Use the distribution's and point Zephyr at it with `QEMU_BIN_PATH`; +`qemu-system-i386 -netdev help` should list `user`. Nothing else in the port +depends on this — real hardware and TAP-networked QEMU use the SDK's QEMU. diff --git a/zephyr/include.am b/zephyr/include.am new file mode 100644 index 0000000..4c1b96b --- /dev/null +++ b/zephyr/include.am @@ -0,0 +1,43 @@ +# vim:ft=automake +# included from the top-level Makefile.am +# All paths are relative to the project root + +EXTRA_DIST += zephyr/README.md +EXTRA_DIST += zephyr/samples/wolfcert_est_client/README.md +EXTRA_DIST += zephyr/CMakeLists.txt +EXTRA_DIST += zephyr/Kconfig +EXTRA_DIST += zephyr/include.am +EXTRA_DIST += zephyr/module.yml +EXTRA_DIST += zephyr/samples/wolfcert_est_client/CMakeLists.txt +EXTRA_DIST += zephyr/samples/wolfcert_est_client/prj.conf +EXTRA_DIST += zephyr/samples/wolfcert_est_client/sample.yaml +EXTRA_DIST += zephyr/samples/wolfcert_est_client/src/main.c +EXTRA_DIST += zephyr/tests/common/test_shim.c +EXTRA_DIST += zephyr/tests/common/unit_test.cmake +EXTRA_DIST += zephyr/tests/common/wolfcert.conf +EXTRA_DIST += zephyr/tests/wolfcert_est/CMakeLists.txt +EXTRA_DIST += zephyr/tests/wolfcert_est/prj.conf +EXTRA_DIST += zephyr/tests/wolfcert_est/src/main.c +EXTRA_DIST += zephyr/tests/wolfcert_est/testcase.yaml +EXTRA_DIST += zephyr/tests/wolfcert_unit_csr/CMakeLists.txt +EXTRA_DIST += zephyr/tests/wolfcert_unit_csr/prj.conf +EXTRA_DIST += zephyr/tests/wolfcert_unit_csr/testcase.yaml +EXTRA_DIST += zephyr/tests/wolfcert_unit_csr_attrs/CMakeLists.txt +EXTRA_DIST += zephyr/tests/wolfcert_unit_csr_attrs/prj.conf +EXTRA_DIST += zephyr/tests/wolfcert_unit_csr_attrs/testcase.yaml +EXTRA_DIST += zephyr/tests/wolfcert_unit_keygen/CMakeLists.txt +EXTRA_DIST += zephyr/tests/wolfcert_unit_keygen/prj.conf +EXTRA_DIST += zephyr/tests/wolfcert_unit_keygen/testcase.yaml +EXTRA_DIST += zephyr/tests/wolfcert_unit_parse_negative/CMakeLists.txt +EXTRA_DIST += zephyr/tests/wolfcert_unit_parse_negative/prj.conf +EXTRA_DIST += zephyr/tests/wolfcert_unit_parse_negative/testcase.yaml +EXTRA_DIST += zephyr/tests/wolfcert_unit_scep_msg/CMakeLists.txt +EXTRA_DIST += zephyr/tests/wolfcert_unit_scep_msg/prj.conf +EXTRA_DIST += zephyr/tests/wolfcert_unit_scep_msg/testcase.yaml +EXTRA_DIST += zephyr/tests/wolfcert_unit_smoke/CMakeLists.txt +EXTRA_DIST += zephyr/tests/wolfcert_unit_smoke/prj.conf +EXTRA_DIST += zephyr/tests/wolfcert_unit_smoke/testcase.yaml +EXTRA_DIST += zephyr/tests/wolfcert_unit_store/CMakeLists.txt +EXTRA_DIST += zephyr/tests/wolfcert_unit_store/prj.conf +EXTRA_DIST += zephyr/tests/wolfcert_unit_store/testcase.yaml +EXTRA_DIST += zephyr/wolfssl_user_settings.h diff --git a/zephyr/module.yml b/zephyr/module.yml new file mode 100644 index 0000000..2d6edc3 --- /dev/null +++ b/zephyr/module.yml @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# The name is pinned so ZEPHYR_WOLFCERT_MODULE_DIR and the generated +# ZEPHYR_WOLFCERT_MODULE Kconfig symbol do not follow the checkout directory. +name: wolfcert +build: + cmake: zephyr + kconfig: zephyr/Kconfig diff --git a/zephyr/samples/wolfcert_est_client/CMakeLists.txt b/zephyr/samples/wolfcert_est_client/CMakeLists.txt new file mode 100644 index 0000000..d514c40 --- /dev/null +++ b/zephyr/samples/wolfcert_est_client/CMakeLists.txt @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(wolfcert_est_client) + +target_sources(app PRIVATE src/main.c) + +generate_inc_file_for_target(app + ${ZEPHYR_WOLFCERT_MODULE_DIR}/examples/certs/ecc/ca-cert.pem + ${ZEPHYR_BINARY_DIR}/include/generated/est_ca_cert.inc) + +# qemu_x86 has no RTC; the sample sets the clock to build time. +string(TIMESTAMP wolfcert_build_epoch "%s" UTC) +target_compile_definitions(app PRIVATE + WOLFCERT_SAMPLE_EPOCH=${wolfcert_build_epoch}) diff --git a/zephyr/samples/wolfcert_est_client/README.md b/zephyr/samples/wolfcert_est_client/README.md new file mode 100644 index 0000000..951c70f --- /dev/null +++ b/zephyr/samples/wolfcert_est_client/README.md @@ -0,0 +1,52 @@ +# wolfCert EST client sample + +Enrolls one certificate over EST (RFC 7030) from a Zephyr target against a +`wolfcert-server` running on the host. Reads top to bottom: set the clock, +init, `wolfcert_client_enroll()`, print the result. + +Only the client runs on target — see `zephyr/README.md` for why. + +## Running it + +Start the server on the host, from a wolfCert build with the CLIs enabled: + +```sh +build/wolfcert-server --proto est --listen 0.0.0.0:8443 \ + --basic alice:hunter2 \ + --tls-cert examples/certs/ecc/server-cert.pem \ + --tls-key examples/certs/ecc/server-key.pem +``` + +Then build and run the sample: + +```sh +west build -b qemu_x86 /zephyr/samples/wolfcert_est_client \ + -- -DEXTRA_ZEPHYR_MODULES=/path/to/wolfCert +west build -t run +``` + +Expected output: + +``` +enrolled: bytes +``` + +The sample reaches the host at `10.0.2.2`, the QEMU SLIRP gateway. The +server's certificate carries that address in its SAN, so `verify_server` is +left on. + +This needs a QEMU with the SLIRP backend, which the Zephyr SDK's build lacks; +`zephyr/README.md` has the one-line fix. + +## Adapting it + +- **Trust anchor.** `examples/certs/ecc/ca-cert.pem` is embedded by + `generate_inc_file_for_target`. Swap in your CA and the URL to match. +- **RNG.** `CONFIG_TEST_RANDOM_GENERATOR=y` is not a real random source, and + keys generated from it are predictable. Drop it and supply a hardware + entropy source before generating a key you intend to keep. +- **Clock.** The sample fakes one from its build timestamp. A real device + needs a real time source — `docs/EMBEDDED.md` section 8 explains why. +- **Keeping the key and certificate.** They are freed here. A real device + writes them through a `WolfCertStoreOps` — on Zephyr, one over NVS or the + settings subsystem. diff --git a/zephyr/samples/wolfcert_est_client/prj.conf b/zephyr/samples/wolfcert_est_client/prj.conf new file mode 100644 index 0000000..b305b8f --- /dev/null +++ b/zephyr/samples/wolfcert_est_client/prj.conf @@ -0,0 +1,39 @@ +CONFIG_WOLFSSL=y +CONFIG_WOLFSSL_BUILTIN=y + +CONFIG_WOLFCERT=y +CONFIG_WOLFCERT_EST=y +CONFIG_WOLFCERT_ECC=y + +# Reach the host through the QEMU SLIRP gateway at 10.0.2.2. SLIRP's topology +# is fixed, so address statically rather than waiting on DHCP. +CONFIG_NET_QEMU_USER=y +CONFIG_PCIE=y +CONFIG_ETH_E1000=y +CONFIG_NET_L2_ETHERNET=y +CONFIG_NET_IPV4=y +CONFIG_NET_TCP=y +CONFIG_NET_CONFIG_SETTINGS=y +CONFIG_NET_CONFIG_NEED_IPV4=y +CONFIG_NET_CONFIG_MY_IPV4_ADDR="10.0.2.15" +CONFIG_NET_CONFIG_MY_IPV4_GW="10.0.2.2" +CONFIG_NET_CONFIG_MY_IPV4_NETMASK="255.255.255.0" +CONFIG_NET_PKT_RX_COUNT=32 +CONFIG_NET_PKT_TX_COUNT=32 +CONFIG_NET_BUF_RX_COUNT=64 +CONFIG_NET_BUF_TX_COUNT=64 + +CONFIG_POSIX_TIMERS=y +# Not a real RNG. qemu_x86 has no entropy device; a device generating keys +# needs a hardware source instead. +CONFIG_TEST_RANDOM_GENERATOR=y + +# Key generation is the deep path; see zephyr/README.md on stack sizing. +CONFIG_MAIN_STACK_SIZE=131072 +CONFIG_HW_STACK_PROTECTION=y +CONFIG_HEAP_MEM_POOL_SIZE=262144 +CONFIG_COMMON_LIBC_MALLOC_ARENA_SIZE=262144 +CONFIG_ISR_STACK_SIZE=4096 +CONFIG_IDLE_STACK_SIZE=2048 + +CONFIG_PRINTK=y diff --git a/zephyr/samples/wolfcert_est_client/sample.yaml b/zephyr/samples/wolfcert_est_client/sample.yaml new file mode 100644 index 0000000..c5cc818 --- /dev/null +++ b/zephyr/samples/wolfcert_est_client/sample.yaml @@ -0,0 +1,18 @@ +sample: + name: wolfCert EST client + description: Enroll a certificate over EST from a Zephyr target +common: + tags: + - wolfcert + harness: console + harness_config: + fixture: wolfcert_est_server + type: one_line + regex: + - "enrolled: [1-9][0-9]* bytes" +tests: + sample.wolfcert.est_client: + timeout: 120 + platform_allow: qemu_x86 + integration_platforms: + - qemu_x86 diff --git a/zephyr/samples/wolfcert_est_client/src/main.c b/zephyr/samples/wolfcert_est_client/src/main.c new file mode 100644 index 0000000..adcd0c8 --- /dev/null +++ b/zephyr/samples/wolfcert_est_client/src/main.c @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfCert. + * + * wolfCert is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfCert is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfCert. If not, see . + */ + +#include + +#include + +#include +#include +#include + +#define EST_URL "https://10.0.2.2:8443/.well-known/est" +#define EST_USER "alice" +#define EST_PASS "hunter2" + +/* The trust anchor is built in: an EST client must authenticate the server + * (RFC 7030 section 3.3), so it needs one before it can talk to anybody. */ +static const uint8_t ca_cert_pem[] = { +#include "est_ca_cert.inc" +}; + +static void log_sink(WolfCertLogLevel level, const char* module, + const char* msg, void* ctx) +{ + ARG_UNUSED(level); + ARG_UNUSED(ctx); + printk("wolfcert[%s]: %s\n", module, msg); +} + +static int enroll(void) +{ + WolfCertServerCfg srv = { + .protocol = WOLFCERT_PROTO_EST, + .server_url = EST_URL, + .proto_opts.est = { .username = EST_USER, .password = EST_PASS }, + .trust_anchors = ca_cert_pem, + .trust_anchors_len = sizeof(ca_cert_pem), + .verify_server = 1, + }; + WolfCertKeyCfg key_cfg = { .type = WOLFCERT_KEY_ECC, .param = 256, + .dev_id = WOLFCERT_DEVID_SOFTWARE }; + WolfCertCertMeta meta = { .subject_dn = "CN=zephyr-device" }; + WolfCertClient* client = NULL; + WolfCertKey* key = NULL; + WolfCertBuffer cert = { 0 }; + int rc; + + rc = wolfcert_client_new(&client); + if (rc == WOLFCERT_OK) + rc = wolfcert_client_enroll(client, &srv, &key_cfg, &meta, &key, &cert); + + if (rc == WOLFCERT_OK) + printk("enrolled: %u bytes\n", (unsigned)cert.len); + else + printk("enroll failed: %s (%s)\n", wolfcert_strerror(rc), + wolfcert_last_error_message()); + + wolfcert_buffer_free(&cert); + wolfcert_key_free(key); + wolfcert_client_free(client); + return rc; +} + +int main(void) +{ + struct timespec ts = { 0 }; + int rc; + + /* No RTC here; a real device uses its own or SNTP. */ + ts.tv_sec = (time_t)WOLFCERT_SAMPLE_EPOCH; + if (clock_settime(CLOCK_REALTIME, &ts) != 0) { + printk("clock_settime failed\n"); + k_panic(); + } + + wolfcert_set_log_cb(log_sink, NULL); + wolfcert_set_log_level(WOLFCERT_LOG_WARN); + + rc = wolfcert_init(NULL); + if (rc != WOLFCERT_OK) { + printk("wolfcert_init failed: %s\n", wolfcert_strerror(rc)); + k_panic(); + } + + rc = enroll(); + wolfcert_cleanup(); + if (rc != WOLFCERT_OK) + k_panic(); + return 0; +} diff --git a/zephyr/tests/common/test_shim.c b/zephyr/tests/common/test_shim.c new file mode 100644 index 0000000..c016531 --- /dev/null +++ b/zephyr/tests/common/test_shim.c @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfCert. + * + * wolfCert is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfCert is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfCert. If not, see . + */ + +/* + * Runs one tests/unit program as a Zephyr image. The build renames that + * program's main() to wolfcert_test_main, so its source needs no edit. + */ + +#include + +#include + +#include + +int wolfcert_test_main(void); + +int main(void) +{ + int rc = wolfcert_test_main(); + + if (rc != 0) { + printf("WOLFCERT LAST ERROR: %s (wolfssl %d)\n", + wolfcert_last_error_message(), wolfcert_last_wolfssl_err()); + } + printf("WOLFCERT TEST RESULT: %d\n", rc); + + if (rc != 0) { + fflush(stdout); + k_panic(); + } + return rc; +} diff --git a/zephyr/tests/common/unit_test.cmake b/zephyr/tests/common/unit_test.cmake new file mode 100644 index 0000000..8060db4 --- /dev/null +++ b/zephyr/tests/common/unit_test.cmake @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# Build one existing tests/unit/.c as a Zephyr image. Call after +# find_package(Zephyr) and project(). + +macro(wolfcert_zephyr_unit_test name) + set(_wc_src ${ZEPHYR_WOLFCERT_MODULE_DIR}/tests/unit/${name}.c) + + if(NOT EXISTS ${_wc_src}) + message(FATAL_ERROR "wolfcert_zephyr_unit_test: no such test source ${_wc_src}") + endif() + + target_sources(app PRIVATE + ${_wc_src} + ${ZEPHYR_WOLFCERT_MODULE_DIR}/zephyr/tests/common/test_shim.c) + + # Only this source, so the shim keeps the real main(). + set_source_files_properties(${_wc_src} PROPERTIES + COMPILE_DEFINITIONS "main=wolfcert_test_main") + + # For the tests that include "internal.h". + target_include_directories(app PRIVATE + ${ZEPHYR_WOLFCERT_MODULE_DIR}/src) +endmacro() diff --git a/zephyr/tests/common/wolfcert.conf b/zephyr/tests/common/wolfcert.conf new file mode 100644 index 0000000..4f4ec8c --- /dev/null +++ b/zephyr/tests/common/wolfcert.conf @@ -0,0 +1,27 @@ +# Shared configuration for the wolfCert unit-test images on qemu_x86. +# Each test's own prj.conf adds only the feature flags it needs. +# zephyr/README.md explains the networking and stack settings below. + +CONFIG_WOLFSSL=y +CONFIG_WOLFSSL_BUILTIN=y + +CONFIG_WOLFCERT=y + +# No socket is opened here; NET_TEST keeps QEMU off SLIP. +CONFIG_NET_IPV4=y +CONFIG_NET_TEST=y + +# qemu_x86 has no entropy device. +CONFIG_TEST_RANDOM_GENERATOR=y + +# Sized for RSA-2048 key generation. +CONFIG_MAIN_STACK_SIZE=131072 +CONFIG_HW_STACK_PROTECTION=y +CONFIG_HEAP_MEM_POOL_SIZE=262144 +CONFIG_COMMON_LIBC_MALLOC_ARENA_SIZE=262144 + +# The x86 defaults are thin once the networking stack is in the image. +CONFIG_ISR_STACK_SIZE=4096 +CONFIG_IDLE_STACK_SIZE=2048 + +CONFIG_PRINTK=y diff --git a/zephyr/tests/wolfcert_est/CMakeLists.txt b/zephyr/tests/wolfcert_est/CMakeLists.txt new file mode 100644 index 0000000..d2dba26 --- /dev/null +++ b/zephyr/tests/wolfcert_est/CMakeLists.txt @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +cmake_minimum_required(VERSION 3.20.0) + +set(CONF_FILE + ${CMAKE_CURRENT_LIST_DIR}/../common/wolfcert.conf + ${CMAKE_CURRENT_LIST_DIR}/prj.conf) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(wolfcert_est) + +target_sources(app PRIVATE src/main.c) + +# qemu_x86 has no RTC; the test sets the clock to build time. +string(TIMESTAMP wolfcert_build_epoch "%s" UTC) +target_compile_definitions(app PRIVATE + WOLFCERT_TEST_EPOCH=${wolfcert_build_epoch}) + +target_include_directories(app PRIVATE + ${ZEPHYR_WOLFCERT_MODULE_DIR}/tests/integration) + +# Embed the trust anchor rather than copying its bytes into a source file. +generate_inc_file_for_target(app + ${ZEPHYR_WOLFCERT_MODULE_DIR}/examples/certs/ecc/ca-cert.pem + ${ZEPHYR_BINARY_DIR}/include/generated/est_ca_cert.inc) diff --git a/zephyr/tests/wolfcert_est/prj.conf b/zephyr/tests/wolfcert_est/prj.conf new file mode 100644 index 0000000..a40b6b8 --- /dev/null +++ b/zephyr/tests/wolfcert_est/prj.conf @@ -0,0 +1,40 @@ +CONFIG_ZTEST=y +CONFIG_ZTEST_STACK_SIZE=32768 + +CONFIG_WOLFCERT_EST=y +CONFIG_WOLFCERT_RSA=y +CONFIG_WOLFCERT_ECC=y +CONFIG_WOLFCERT_ED25519=y + +# Reach the host through the QEMU SLIRP gateway at 10.0.2.2. NET_TEST is for +# images with no network device, so it has to come back off here. +CONFIG_NET_TEST=n +CONFIG_NET_QEMU_USER=y +CONFIG_PCIE=y +CONFIG_ETH_E1000=y +CONFIG_NET_L2_ETHERNET=y +CONFIG_NET_TCP=y +# SLIRP's topology is fixed (10.0.2.0/24, gateway 10.0.2.2), so address +# statically and skip DHCP. +CONFIG_NET_CONFIG_SETTINGS=y +CONFIG_NET_CONFIG_NEED_IPV4=y +CONFIG_NET_CONFIG_MY_IPV4_ADDR="10.0.2.15" +CONFIG_NET_CONFIG_MY_IPV4_GW="10.0.2.2" +CONFIG_NET_CONFIG_MY_IPV4_NETMASK="255.255.255.0" +CONFIG_NET_CONFIG_INIT_TIMEOUT=30 +CONFIG_NET_PKT_RX_COUNT=32 +CONFIG_NET_PKT_TX_COUNT=32 +CONFIG_NET_BUF_RX_COUNT=64 +CONFIG_NET_BUF_TX_COUNT=64 + +CONFIG_POSIX_TIMERS=y + +# Each enrollment is its own connection; without these the contexts sit in +# TIME_WAIT and the third one fails instantly with none free. +CONFIG_NET_MAX_CONTEXTS=16 +CONFIG_NET_TCP_TIME_WAIT_DELAY=0 + +# A TLS context plus an enrollment does not fit the shared 256 KiB arena a +# second time; wolfSSL_CTX_new then returns NULL and surfaces as a TLS error. +CONFIG_COMMON_LIBC_MALLOC_ARENA_SIZE=2097152 +CONFIG_HEAP_MEM_POOL_SIZE=1048576 diff --git a/zephyr/tests/wolfcert_est/src/main.c b/zephyr/tests/wolfcert_est/src/main.c new file mode 100644 index 0000000..7e9b201 --- /dev/null +++ b/zephyr/tests/wolfcert_est/src/main.c @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfCert. + * + * wolfCert is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfCert is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfCert. If not, see . + */ + +/* + * EST enrollment against a wolfcert-server on the host, reached through the + * QEMU SLIRP gateway. The cases come from tests/integration/est_client_cases.h, + * so this file only supplies the server config and the trust anchor. + */ + +#include + +#include "est_client_cases.h" + +#include + +#include + +#include +#include +#include +#include + +/* Start the host side with: + * wolfcert-server --proto est --listen 0.0.0.0:8443 --basic alice:hunter2 \ + * --tls-cert examples/certs/ecc/server-cert.pem \ + * --tls-key examples/certs/ecc/server-key.pem */ +#define EST_URL "https://10.0.2.2:8443/.well-known/est" +#define EST_USER "alice" +#define EST_PASS "hunter2" + +static const uint8_t ca_cert_pem[] = { +#include "est_ca_cert.inc" +}; + +static WolfCertServerCfg g_cfg; +static WolfCertBuffer g_ca_pem; + +/* Safe guard for waiting until the interface has an IPv4 address */ +static int wait_for_ipv4(struct net_if* iface, int timeout_ms) +{ + int waited = 0; + + if (iface == NULL) + return -1; + + while (waited < timeout_ms) { + if (iface->config.ip.ipv4 != NULL && + iface->config.ip.ipv4->unicast[0].ipv4.is_used) + return 0; + k_sleep(K_MSEC(100)); + waited += 100; + } + return -1; +} + +static void log_sink(WolfCertLogLevel level, const char* module, + const char* msg, void* ctx) +{ + ARG_UNUSED(level); + ARG_UNUSED(ctx); + printk("wolfcert[%s]: %s\n", module, msg); +} + +/* Zephyr resets the realtime clock after every ztest (a ZTEST_RULE in + * lib/os/clock.c under CONFIG_ZTEST). Set it before each test, not once. */ +static void est_set_clock(void* fixture) +{ + struct timespec ts = { 0 }; + + ARG_UNUSED(fixture); + ts.tv_sec = (time_t)WOLFCERT_TEST_EPOCH; + zassert_ok(clock_settime(CLOCK_REALTIME, &ts), "clock_settime"); +} + +static void* est_setup(void) +{ + struct net_if* iface = net_if_get_default(); + char addr[NET_IPV4_ADDR_LEN] = "none"; + int rc; + + g_cfg = (WolfCertServerCfg){ + .protocol = WOLFCERT_PROTO_EST, + .server_url = EST_URL, + .proto_opts.est = { .username = EST_USER, .password = EST_PASS }, + .trust_anchors = ca_cert_pem, + .trust_anchors_len = sizeof(ca_cert_pem), + .verify_server = 1, + }; + + if (wait_for_ipv4(iface, 30000) != 0) { + /* ztest keeps running a setup function past a failed assertion, so + * stop here */ + zassert_unreachable("no IPv4 address"); + return NULL; + } + net_addr_ntop(NET_AF_INET, + &iface->config.ip.ipv4->unicast[0].ipv4.address.in_addr, + addr, sizeof(addr)); + printk("wolfcert_est: local address %s, server %s\n", addr, EST_URL); + + wolfcert_set_log_cb(log_sink, NULL); + wolfcert_set_log_level(WOLFCERT_LOG_DEBUG); + + est_set_clock(NULL); + if (wolfcert_init(NULL) != WOLFCERT_OK) { + zassert_unreachable("wolfcert_init"); + return NULL; + } + rc = wolfcert_est_get_cacerts(&g_cfg, &g_ca_pem); + if (rc != WOLFCERT_OK) { + printk("wolfcert_est: get_cacerts rc=%d (%s) wolfssl=%d: %s\n", rc, + wolfcert_strerror(rc), wolfcert_last_wolfssl_err(), + wolfcert_last_error_message()); + } + zassert_ok(rc, "get_cacerts"); + zassert_true(g_ca_pem.len > 0, "empty CA bundle"); + + return NULL; +} + +static void est_teardown(void* fixture) +{ + ARG_UNUSED(fixture); + wolfcert_buffer_free(&g_ca_pem); + wolfcert_cleanup(); +} + +ZTEST_SUITE(wolfcert_est, NULL, est_setup, est_set_clock, NULL, est_teardown); + +ZTEST(wolfcert_est, test_enroll) +{ + zassert_ok(enroll_one(&g_cfg, TEST_ENROLL_KEY_TYPE, TEST_ENROLL_KEY_PARAM, + &g_ca_pem), "enroll_one"); +} + +ZTEST(wolfcert_est, test_san_roundtrip) +{ + zassert_ok(enroll_check_san(&g_cfg), "enroll_check_san"); +} + +ZTEST(wolfcert_est, test_session_basic_auth) +{ + zassert_ok(session_basic_auth(&g_cfg), "session_basic_auth"); +} diff --git a/zephyr/tests/wolfcert_est/testcase.yaml b/zephyr/tests/wolfcert_est/testcase.yaml new file mode 100644 index 0000000..b8147be --- /dev/null +++ b/zephyr/tests/wolfcert_est/testcase.yaml @@ -0,0 +1,15 @@ +common: + tags: + - wolfcert + - est + harness: ztest + # Needs a wolfcert-server on the host; twister runs this only when the + # fixture is named, so a plain run skips it instead of failing. + harness_config: + fixture: wolfcert_est_server +tests: + wolfcert.est.enroll: + timeout: 300 + platform_allow: qemu_x86 + integration_platforms: + - qemu_x86 diff --git a/zephyr/tests/wolfcert_unit_csr/CMakeLists.txt b/zephyr/tests/wolfcert_unit_csr/CMakeLists.txt new file mode 100644 index 0000000..203a23c --- /dev/null +++ b/zephyr/tests/wolfcert_unit_csr/CMakeLists.txt @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +cmake_minimum_required(VERSION 3.20.0) + +set(CONF_FILE + ${CMAKE_CURRENT_LIST_DIR}/../common/wolfcert.conf + ${CMAKE_CURRENT_LIST_DIR}/prj.conf) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(wolfcert_unit_csr) + +include(${ZEPHYR_WOLFCERT_MODULE_DIR}/zephyr/tests/common/unit_test.cmake) +wolfcert_zephyr_unit_test(test_csr) diff --git a/zephyr/tests/wolfcert_unit_csr/prj.conf b/zephyr/tests/wolfcert_unit_csr/prj.conf new file mode 100644 index 0000000..b7e9b38 --- /dev/null +++ b/zephyr/tests/wolfcert_unit_csr/prj.conf @@ -0,0 +1,4 @@ +CONFIG_WOLFCERT_EST=y +CONFIG_WOLFCERT_RSA=y +CONFIG_WOLFCERT_ECC=y +CONFIG_WOLFCERT_ED25519=y diff --git a/zephyr/tests/wolfcert_unit_csr/testcase.yaml b/zephyr/tests/wolfcert_unit_csr/testcase.yaml new file mode 100644 index 0000000..42b38a6 --- /dev/null +++ b/zephyr/tests/wolfcert_unit_csr/testcase.yaml @@ -0,0 +1,14 @@ +common: + tags: + - wolfcert + harness: console + harness_config: + type: one_line + regex: + - "WOLFCERT TEST RESULT: 0" +tests: + wolfcert.unit.csr: + timeout: 180 + platform_allow: qemu_x86 + integration_platforms: + - qemu_x86 diff --git a/zephyr/tests/wolfcert_unit_csr_attrs/CMakeLists.txt b/zephyr/tests/wolfcert_unit_csr_attrs/CMakeLists.txt new file mode 100644 index 0000000..8db23e9 --- /dev/null +++ b/zephyr/tests/wolfcert_unit_csr_attrs/CMakeLists.txt @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +cmake_minimum_required(VERSION 3.20.0) + +set(CONF_FILE + ${CMAKE_CURRENT_LIST_DIR}/../common/wolfcert.conf + ${CMAKE_CURRENT_LIST_DIR}/prj.conf) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(wolfcert_unit_csr_attrs) + +include(${ZEPHYR_WOLFCERT_MODULE_DIR}/zephyr/tests/common/unit_test.cmake) +wolfcert_zephyr_unit_test(test_csr_attrs) diff --git a/zephyr/tests/wolfcert_unit_csr_attrs/prj.conf b/zephyr/tests/wolfcert_unit_csr_attrs/prj.conf new file mode 100644 index 0000000..b7e9b38 --- /dev/null +++ b/zephyr/tests/wolfcert_unit_csr_attrs/prj.conf @@ -0,0 +1,4 @@ +CONFIG_WOLFCERT_EST=y +CONFIG_WOLFCERT_RSA=y +CONFIG_WOLFCERT_ECC=y +CONFIG_WOLFCERT_ED25519=y diff --git a/zephyr/tests/wolfcert_unit_csr_attrs/testcase.yaml b/zephyr/tests/wolfcert_unit_csr_attrs/testcase.yaml new file mode 100644 index 0000000..f00d672 --- /dev/null +++ b/zephyr/tests/wolfcert_unit_csr_attrs/testcase.yaml @@ -0,0 +1,14 @@ +common: + tags: + - wolfcert + harness: console + harness_config: + type: one_line + regex: + - "WOLFCERT TEST RESULT: 0" +tests: + wolfcert.unit.csr_attrs: + timeout: 180 + platform_allow: qemu_x86 + integration_platforms: + - qemu_x86 diff --git a/zephyr/tests/wolfcert_unit_keygen/CMakeLists.txt b/zephyr/tests/wolfcert_unit_keygen/CMakeLists.txt new file mode 100644 index 0000000..28e02f2 --- /dev/null +++ b/zephyr/tests/wolfcert_unit_keygen/CMakeLists.txt @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +cmake_minimum_required(VERSION 3.20.0) + +set(CONF_FILE + ${CMAKE_CURRENT_LIST_DIR}/../common/wolfcert.conf + ${CMAKE_CURRENT_LIST_DIR}/prj.conf) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(wolfcert_unit_keygen) + +include(${ZEPHYR_WOLFCERT_MODULE_DIR}/zephyr/tests/common/unit_test.cmake) +wolfcert_zephyr_unit_test(test_keygen) diff --git a/zephyr/tests/wolfcert_unit_keygen/prj.conf b/zephyr/tests/wolfcert_unit_keygen/prj.conf new file mode 100644 index 0000000..b7e9b38 --- /dev/null +++ b/zephyr/tests/wolfcert_unit_keygen/prj.conf @@ -0,0 +1,4 @@ +CONFIG_WOLFCERT_EST=y +CONFIG_WOLFCERT_RSA=y +CONFIG_WOLFCERT_ECC=y +CONFIG_WOLFCERT_ED25519=y diff --git a/zephyr/tests/wolfcert_unit_keygen/testcase.yaml b/zephyr/tests/wolfcert_unit_keygen/testcase.yaml new file mode 100644 index 0000000..ced78b6 --- /dev/null +++ b/zephyr/tests/wolfcert_unit_keygen/testcase.yaml @@ -0,0 +1,14 @@ +common: + tags: + - wolfcert + harness: console + harness_config: + type: one_line + regex: + - "WOLFCERT TEST RESULT: 0" +tests: + wolfcert.unit.keygen: + timeout: 180 + platform_allow: qemu_x86 + integration_platforms: + - qemu_x86 diff --git a/zephyr/tests/wolfcert_unit_parse_negative/CMakeLists.txt b/zephyr/tests/wolfcert_unit_parse_negative/CMakeLists.txt new file mode 100644 index 0000000..f00390d --- /dev/null +++ b/zephyr/tests/wolfcert_unit_parse_negative/CMakeLists.txt @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +cmake_minimum_required(VERSION 3.20.0) + +set(CONF_FILE + ${CMAKE_CURRENT_LIST_DIR}/../common/wolfcert.conf + ${CMAKE_CURRENT_LIST_DIR}/prj.conf) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(wolfcert_unit_parse_negative) + +include(${ZEPHYR_WOLFCERT_MODULE_DIR}/zephyr/tests/common/unit_test.cmake) +wolfcert_zephyr_unit_test(test_parse_negative) diff --git a/zephyr/tests/wolfcert_unit_parse_negative/prj.conf b/zephyr/tests/wolfcert_unit_parse_negative/prj.conf new file mode 100644 index 0000000..5109ced --- /dev/null +++ b/zephyr/tests/wolfcert_unit_parse_negative/prj.conf @@ -0,0 +1,5 @@ +CONFIG_WOLFCERT_EST=y +CONFIG_WOLFCERT_SCEP=y +CONFIG_WOLFCERT_RSA=y +CONFIG_WOLFCERT_ECC=y +CONFIG_WOLFCERT_BUILD_TESTING=y diff --git a/zephyr/tests/wolfcert_unit_parse_negative/testcase.yaml b/zephyr/tests/wolfcert_unit_parse_negative/testcase.yaml new file mode 100644 index 0000000..586ad15 --- /dev/null +++ b/zephyr/tests/wolfcert_unit_parse_negative/testcase.yaml @@ -0,0 +1,14 @@ +common: + tags: + - wolfcert + harness: console + harness_config: + type: one_line + regex: + - "WOLFCERT TEST RESULT: 0" +tests: + wolfcert.unit.parse_negative: + timeout: 180 + platform_allow: qemu_x86 + integration_platforms: + - qemu_x86 diff --git a/zephyr/tests/wolfcert_unit_scep_msg/CMakeLists.txt b/zephyr/tests/wolfcert_unit_scep_msg/CMakeLists.txt new file mode 100644 index 0000000..5d75ad7 --- /dev/null +++ b/zephyr/tests/wolfcert_unit_scep_msg/CMakeLists.txt @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +cmake_minimum_required(VERSION 3.20.0) + +set(CONF_FILE + ${CMAKE_CURRENT_LIST_DIR}/../common/wolfcert.conf + ${CMAKE_CURRENT_LIST_DIR}/prj.conf) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(wolfcert_unit_scep_msg) + +include(${ZEPHYR_WOLFCERT_MODULE_DIR}/zephyr/tests/common/unit_test.cmake) +wolfcert_zephyr_unit_test(test_scep_msg) diff --git a/zephyr/tests/wolfcert_unit_scep_msg/prj.conf b/zephyr/tests/wolfcert_unit_scep_msg/prj.conf new file mode 100644 index 0000000..5109ced --- /dev/null +++ b/zephyr/tests/wolfcert_unit_scep_msg/prj.conf @@ -0,0 +1,5 @@ +CONFIG_WOLFCERT_EST=y +CONFIG_WOLFCERT_SCEP=y +CONFIG_WOLFCERT_RSA=y +CONFIG_WOLFCERT_ECC=y +CONFIG_WOLFCERT_BUILD_TESTING=y diff --git a/zephyr/tests/wolfcert_unit_scep_msg/testcase.yaml b/zephyr/tests/wolfcert_unit_scep_msg/testcase.yaml new file mode 100644 index 0000000..bedef27 --- /dev/null +++ b/zephyr/tests/wolfcert_unit_scep_msg/testcase.yaml @@ -0,0 +1,14 @@ +common: + tags: + - wolfcert + harness: console + harness_config: + type: one_line + regex: + - "WOLFCERT TEST RESULT: 0" +tests: + wolfcert.unit.scep_msg: + timeout: 900 + platform_allow: qemu_x86 + integration_platforms: + - qemu_x86 diff --git a/zephyr/tests/wolfcert_unit_smoke/CMakeLists.txt b/zephyr/tests/wolfcert_unit_smoke/CMakeLists.txt new file mode 100644 index 0000000..0b35333 --- /dev/null +++ b/zephyr/tests/wolfcert_unit_smoke/CMakeLists.txt @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +cmake_minimum_required(VERSION 3.20.0) + +set(CONF_FILE + ${CMAKE_CURRENT_LIST_DIR}/../common/wolfcert.conf + ${CMAKE_CURRENT_LIST_DIR}/prj.conf) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(wolfcert_unit_smoke) + +include(${ZEPHYR_WOLFCERT_MODULE_DIR}/zephyr/tests/common/unit_test.cmake) +wolfcert_zephyr_unit_test(test_smoke) diff --git a/zephyr/tests/wolfcert_unit_smoke/prj.conf b/zephyr/tests/wolfcert_unit_smoke/prj.conf new file mode 100644 index 0000000..b7e9b38 --- /dev/null +++ b/zephyr/tests/wolfcert_unit_smoke/prj.conf @@ -0,0 +1,4 @@ +CONFIG_WOLFCERT_EST=y +CONFIG_WOLFCERT_RSA=y +CONFIG_WOLFCERT_ECC=y +CONFIG_WOLFCERT_ED25519=y diff --git a/zephyr/tests/wolfcert_unit_smoke/testcase.yaml b/zephyr/tests/wolfcert_unit_smoke/testcase.yaml new file mode 100644 index 0000000..6ad39bb --- /dev/null +++ b/zephyr/tests/wolfcert_unit_smoke/testcase.yaml @@ -0,0 +1,41 @@ +common: + tags: + - wolfcert + harness: console + harness_config: + type: one_line + regex: + - "WOLFCERT TEST RESULT: 0" +tests: + wolfcert.unit.smoke: + timeout: 180 + platform_allow: qemu_x86 + integration_platforms: + - qemu_x86 + + # Build-only rows for the options the other suites leave at their defaults, + # so a combination nobody runs still has to compile. + wolfcert.build.no_builtin_transport: + build_only: true + platform_allow: qemu_x86 + extra_configs: + - CONFIG_WOLFCERT_BUILTIN_TRANSPORT=n + wolfcert.build.rsa_only: + build_only: true + platform_allow: qemu_x86 + extra_configs: + - CONFIG_WOLFCERT_ECC=n + - CONFIG_WOLFCERT_ED25519=n + wolfcert.build.scep_only_rsa: + build_only: true + platform_allow: qemu_x86 + extra_configs: + - CONFIG_WOLFCERT_EST=n + - CONFIG_WOLFCERT_SCEP=y + - CONFIG_WOLFCERT_ECC=n + - CONFIG_WOLFCERT_ED25519=n + wolfcert.build.posix_store: + build_only: true + platform_allow: qemu_x86 + extra_configs: + - CONFIG_WOLFCERT_POSIX_STORE=y diff --git a/zephyr/tests/wolfcert_unit_store/CMakeLists.txt b/zephyr/tests/wolfcert_unit_store/CMakeLists.txt new file mode 100644 index 0000000..c08f0e1 --- /dev/null +++ b/zephyr/tests/wolfcert_unit_store/CMakeLists.txt @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +cmake_minimum_required(VERSION 3.20.0) + +set(CONF_FILE + ${CMAKE_CURRENT_LIST_DIR}/../common/wolfcert.conf + ${CMAKE_CURRENT_LIST_DIR}/prj.conf) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(wolfcert_unit_store) + +include(${ZEPHYR_WOLFCERT_MODULE_DIR}/zephyr/tests/common/unit_test.cmake) +wolfcert_zephyr_unit_test(test_store) diff --git a/zephyr/tests/wolfcert_unit_store/prj.conf b/zephyr/tests/wolfcert_unit_store/prj.conf new file mode 100644 index 0000000..b7e9b38 --- /dev/null +++ b/zephyr/tests/wolfcert_unit_store/prj.conf @@ -0,0 +1,4 @@ +CONFIG_WOLFCERT_EST=y +CONFIG_WOLFCERT_RSA=y +CONFIG_WOLFCERT_ECC=y +CONFIG_WOLFCERT_ED25519=y diff --git a/zephyr/tests/wolfcert_unit_store/testcase.yaml b/zephyr/tests/wolfcert_unit_store/testcase.yaml new file mode 100644 index 0000000..5be1f10 --- /dev/null +++ b/zephyr/tests/wolfcert_unit_store/testcase.yaml @@ -0,0 +1,14 @@ +common: + tags: + - wolfcert + harness: console + harness_config: + type: one_line + regex: + - "WOLFCERT TEST RESULT: 0" +tests: + wolfcert.unit.store: + timeout: 180 + platform_allow: qemu_x86 + integration_platforms: + - qemu_x86 diff --git a/zephyr/wolfssl_user_settings.h b/zephyr/wolfssl_user_settings.h new file mode 100644 index 0000000..1f48867 --- /dev/null +++ b/zephyr/wolfssl_user_settings.h @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfCert. + * + * wolfCert is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfCert is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfCert. If not, see . + */ + +/* + * wolfSSL configuration for a Zephyr build of wolfCert. The wolfSSL module + * includes this in place of its own default block, so it must be + * self-contained. Key algorithms follow the CONFIG_WOLFCERT_* symbols. + */ + +#ifndef WOLFCERT_ZEPHYR_WOLFSSL_USER_SETTINGS_H +#define WOLFCERT_ZEPHYR_WOLFSSL_USER_SETTINGS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* ---- platform ----------------------------------------------------------- */ +#define WOLFSSL_GENERAL_ALIGNMENT 4 +#define SIZEOF_LONG_LONG 8 +#define WOLFSSL_IGNORE_FILE_WARN +#define NO_FILESYSTEM +#define NO_WRITEV +#define NO_MAIN_DRIVER + +#define WOLFSSL_USER_IO + +/* ---- TLS ---------------------------------------------------------------- */ +#define WOLFSSL_TLS13 +#define NO_OLD_TLS +#define HAVE_TLS_EXTENSIONS +#define HAVE_SUPPORTED_CURVES +#define HAVE_EXTENDED_MASTER +#define HAVE_ENCRYPT_THEN_MAC +#define HAVE_SERVER_RENEGOTIATION_INFO +#define HAVE_HKDF +#define HAVE_SESSION_TICKET +#define SMALL_SESSION_CACHE + +#define HAVE_SNI + +#define WOLFSSL_POST_HANDSHAKE_AUTH + +/* ---- certificate handling ----------------------------------------------- */ +#define OPENSSL_EXTRA +#define WOLFSSL_ASN_TEMPLATE +#define WOLFSSL_CERT_GEN +#define WOLFSSL_CERT_REQ +#define WOLFSSL_CERT_EXT +#define WOLFSSL_CERT_NAME_ALL +#define WOLFSSL_ALT_NAMES +#define WOLFSSL_IP_ALT_NAME +#define WOLFSSL_KEY_GEN +#define WOLFSSL_DER_TO_PEM +#define WOLFSSL_BASE64_ENCODE +#define KEEP_PEER_CERT + +/* ---- PKCS#7 (SCEP pkiMessage, EST /cacerts) ----------------------------- */ +#define HAVE_PKCS7 +#define HAVE_AES_KEYWRAP +#define WOLFSSL_AES_DIRECT +#define HAVE_X963_KDF + +/* ---- CryptoCb ------------------------------------------------------------ */ +#define WOLF_CRYPTO_CB + +/* ---- RNG ----------------------------------------------------------------- */ +#define HAVE_HASHDRBG + +/* ---- symmetric ----------------------------------------------------------- */ +#define HAVE_AES_CBC +#define HAVE_AES_ECB +#define HAVE_AESGCM +#define GCM_SMALL +#define WOLFSSL_SHA224 +#define WOLFSSL_SHA384 +#define WOLFSSL_SHA512 +#define WOLFSSL_SHA3 +#define WOLFSSL_CMAC +#define HAVE_CHACHA +#define HAVE_POLY1305 +#define HAVE_ONE_TIME_AUTH + +/* ---- TLS transport ------------------------------------------------------- */ +/* ECDHE is the handshake's key agreement and essential for EST. */ +#define HAVE_ECC +#define ECC_TIMING_RESISTANT + +/* ---- key algorithms, following Kconfig ----------------------------------- */ +/* These pick what wolfCert can enrol, nothing about the handshake. */ +#ifdef CONFIG_WOLFCERT_RSA + #undef NO_RSA + #define WC_RSA_BLINDING + #define WC_RSA_PSS +#else + #define NO_RSA +#endif + +#ifdef CONFIG_WOLFCERT_ECC + #define HAVE_ECC_KEY_EXPORT +#endif + +#ifdef CONFIG_WOLFCERT_ED25519 + #define HAVE_ED25519 + #define HAVE_CURVE25519 + #define HAVE_ED25519_KEY_EXPORT +#endif + +#ifdef CONFIG_WOLFCERT_ED448 + #define HAVE_ED448 + #define HAVE_CURVE448 + #define WOLFSSL_SHAKE256 +#endif + +#ifdef CONFIG_WOLFCERT_MLDSA + #define WOLFSSL_HAVE_MLDSA + #define WOLFSSL_SHAKE128 + #define WOLFSSL_SHAKE256 +#endif + +#if !defined(WOLFSSL_SHAKE128) && !defined(CONFIG_WOLFCERT_MLDSA) + #define WOLFSSL_NO_SHAKE128 +#endif +#if !defined(WOLFSSL_SHAKE256) + #define WOLFSSL_NO_SHAKE256 +#endif + +/* ---- disabled algorithms -------------------------------------------------- */ +#define NO_DSA +#define NO_DH +#define NO_RC4 +#define NO_MD4 +#define NO_MD5 +#define NO_PSK + +/* No 3DES: the SCEP fallback for a peer that does not advertise AES. */ +#define NO_DES3 + +/* ---- math ---------------------------------------------------------------- */ +/* SP_MATH_ALL, not SP_MATH: the restricted variant cannot generate keys. */ +#define WOLFSSL_SP_MATH_ALL +#define WOLFSSL_OLD_PRIME_CHECK + +#ifdef CONFIG_WOLFCERT_SMALL_MATH + #define WOLFSSL_SP_SMALL +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* WOLFCERT_ZEPHYR_WOLFSSL_USER_SETTINGS_H */