Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 176 additions & 0 deletions .github/workflows/zephyr.yml
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -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
26 changes: 26 additions & 0 deletions docs/EMBEDDED.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion examples/certs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Two algorithm families, each a self-signed CA plus a server and a client leaf:
| File | Role |
|------|------|
| `<alg>/ca-cert.pem` / `ca-key.pem` | self-signed CA — the trust anchor |
| `<alg>/server-cert.pem` / `server-key.pem` | TLS server leaf, SAN = `localhost`, `127.0.0.1`, `::1` |
| `<alg>/server-cert.pem` / `server-key.pem` | TLS server leaf, SAN = `localhost`, `127.0.0.1`, `::1`, `10.0.2.2` |
| `<alg>/client-cert.pem` / `client-key.pem` | mTLS client leaf |

- `ecc/` — ECC P-256 (ECDSA-with-SHA256)
Expand Down
16 changes: 8 additions & 8 deletions examples/certs/ecc/server-cert.pem
Original file line number Diff line number Diff line change
@@ -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-----
7 changes: 5 additions & 2 deletions examples/certs/gen-certs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
#
# <alg>/ca-cert.pem self-signed CA (trust anchor)
# <alg>/ca-key.pem
# <alg>/server-cert.pem TLS server leaf, SAN = localhost / 127.0.0.1 / ::1
# <alg>/server-cert.pem TLS server leaf, SAN = localhost / 127.0.0.1 /
# ::1 / 10.0.2.2
# <alg>/server-key.pem
# <alg>/client-cert.pem mTLS client leaf
# <alg>/client-key.pem
Expand Down Expand Up @@ -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 ----------------------------------------------
Expand Down
Loading
Loading