diff --git a/meta-wolfssl-linux-fips/.gitignore b/meta-wolfssl-linux-fips/.gitignore
new file mode 100644
index 00000000..466a04b3
--- /dev/null
+++ b/meta-wolfssl-linux-fips/.gitignore
@@ -0,0 +1,38 @@
+# Cloned repos (pulled by setup.sh, symlinked into layers/)
+repos/
+
+# Symlinks in layers/ pointing to repos/ (recreated by setup.sh)
+layers/poky
+layers/meta-raspberrypi
+layers/meta-openembedded
+layers/meta-wolfssl
+
+# FIPS Ready config (contains bundle-specific details)
+conf/wolfssl-fips-ready.conf
+
+# Build artifacts
+build/
+downloads/
+sstate-cache/
+
+# Images copied by make move-image
+*.wic
+*.wic.bz2
+*.wic.gz
+*.wic.bmap
+
+# FIPS Ready bundle (commercial, do not commit)
+*.zip
+
+# Generated network config (created by make ip-* / set-network.sh)
+layers/meta-network-overrides/recipes-core/network-config/files/20-wired.network
+
+# direnv (generated by setup.sh)
+.envrc
+
+# Project root is not a layer (old locations)
+/conf/layer.conf
+/recipes-core/
+
+# Claude Code memory (local only)
+memory/
diff --git a/meta-wolfssl-linux-fips/Makefile b/meta-wolfssl-linux-fips/Makefile
new file mode 100644
index 00000000..168313f3
--- /dev/null
+++ b/meta-wolfssl-linux-fips/Makefile
@@ -0,0 +1,268 @@
+# Makefile - Yocto Scarthgap RPi5 build targets
+#
+# Usage:
+# ./setup.sh - clone layers and check host tools
+# make configure - initialize build dir and write config
+# make build - build the image
+
+SHELL := /bin/bash
+TOPDIR := $(shell pwd)
+BUILD_DIR := $(TOPDIR)/build
+POKY_DIR := $(TOPDIR)/layers/poky
+FIPS_IMAGE := wolfssl-fips-ready-image
+BASE_IMAGE := core-image-base
+MACHINE := raspberrypi5
+DEPLOY_DIR := $(BUILD_DIR)/tmp/deploy/images/$(MACHINE)
+QEMU_MACHINE := qemuarm64
+QEMU_IMAGE := wolfssl-fips-ready-image-qemu
+QEMU_CONF := $(TOPDIR)/conf/qemu-override.conf
+QEMU_DEPLOY_DIR := $(BUILD_DIR)/tmp/deploy/images/$(QEMU_MACHINE)
+
+# Helper: source the OE environment and run a bitbake command
+define bitbake-cmd
+ @if [ ! -d "$(POKY_DIR)" ]; then \
+ echo "Error: poky not found. Run ./setup.sh first."; \
+ exit 1; \
+ fi
+ @source $(POKY_DIR)/oe-init-build-env $(BUILD_DIR) > /dev/null 2>&1 && $(1)
+endef
+
+LAYERS_DIR := $(TOPDIR)/layers
+OVERRIDES_LAYER := $(LAYERS_DIR)/meta-wolfssl-overrides
+BBLAYERS_CONF := $(BUILD_DIR)/conf/bblayers.conf
+LOCAL_CONF := $(BUILD_DIR)/conf/local.conf
+FIPS_CONF := $(TOPDIR)/conf/wolfssl-fips-ready.conf
+FIPS_MARKER := \# ==== wolfssl-fips-ready config ====
+
+.PHONY: configure build build-minimal clean distclean clean-project image-info move-image ip-dhcp fips-on fips-off fips-status qemu run-qemu shell help
+
+## configure: Initialize build dir, write bblayers.conf and local.conf
+configure:
+ @$(TOPDIR)/configure.sh
+
+## build: Build the image (FIPS image if enabled, base image otherwise)
+build:
+ @if [ -f "$(BBLAYERS_CONF)" ] && grep -q "meta-wolfssl-overrides" "$(BBLAYERS_CONF)" 2>/dev/null; then \
+ echo "FIPS enabled - building $(FIPS_IMAGE)"; \
+ else \
+ echo "Building $(BASE_IMAGE)"; \
+ fi
+ $(call bitbake-cmd, \
+ if grep -q "meta-wolfssl-overrides" "$(BBLAYERS_CONF)" 2>/dev/null; then \
+ bitbake $(FIPS_IMAGE); \
+ else \
+ bitbake $(BASE_IMAGE); \
+ fi)
+
+## build-minimal: Build core-image-minimal for RPi5
+build-minimal:
+ $(call bitbake-cmd, bitbake core-image-minimal)
+
+## clean: Clean the active image sstate (cleansstate)
+clean:
+ $(call bitbake-cmd, \
+ if grep -q "meta-wolfssl-overrides" "$(BBLAYERS_CONF)" 2>/dev/null; then \
+ bitbake -c cleansstate $(FIPS_IMAGE); \
+ else \
+ bitbake -c cleansstate $(BASE_IMAGE); \
+ fi)
+
+## distclean: Remove the entire build directory
+distclean:
+ @echo "Removing build directory: $(BUILD_DIR)"
+ rm -rf $(BUILD_DIR)
+
+## clean-project: Reset to clean state (keeps source files and FIPS bundle)
+clean-project:
+ @echo "Removing all generated files and caches..."
+ rm -rf $(BUILD_DIR) $(TOPDIR)/repos $(TOPDIR)/downloads $(TOPDIR)/sstate-cache
+ rm -rf $(TOPDIR)/conf/wolfssl-fips-ready.conf $(TOPDIR)/.envrc
+ rm -f $(TOPDIR)/*.wic $(TOPDIR)/*.wic.bz2 $(TOPDIR)/*.wic.gz $(TOPDIR)/*.wic.bmap
+ rm -f $(TOPDIR)/layers/meta-network-overrides/recipes-core/network-config/files/20-wired.network
+ @echo "Done. Run './setup.sh' to start fresh."
+
+## image-info: Show the output image location and file sizes
+image-info:
+ @echo "Deploy directory: $(DEPLOY_DIR)"
+ @echo ""
+ @if [ -d "$(DEPLOY_DIR)" ]; then \
+ ls -lh $(DEPLOY_DIR)/*.wic.bz2 2>/dev/null || \
+ ls -lh $(DEPLOY_DIR)/*.wic.gz 2>/dev/null || \
+ ls -lh $(DEPLOY_DIR)/*.wic 2>/dev/null || \
+ echo "No .wic image found yet. Run 'make build' first."; \
+ else \
+ echo "Deploy directory does not exist. Run 'make build' first."; \
+ fi
+
+## move-image: Copy the latest .wic image to the current directory
+move-image:
+ @LATEST=$$(ls -t $(DEPLOY_DIR)/*.wic.bz2 2>/dev/null | head -1); \
+ if [ -z "$$LATEST" ]; then \
+ LATEST=$$(ls -t $(DEPLOY_DIR)/*.wic.gz 2>/dev/null | head -1); \
+ fi; \
+ if [ -z "$$LATEST" ]; then \
+ LATEST=$$(ls -t $(DEPLOY_DIR)/*.wic 2>/dev/null | head -1); \
+ fi; \
+ if [ -z "$$LATEST" ]; then \
+ echo "No .wic image found. Run 'make build' first."; \
+ exit 1; \
+ fi; \
+ echo "Copying $$LATEST to ."; \
+ cp "$$LATEST" . ; \
+ echo "Done: $$(basename $$LATEST)"
+
+## generate-fips-conf: Generate wolfssl-fips-ready.conf from a bundle .zip
+## Usage: make generate-fips-conf BUNDLE=/path/to/wolfssl-x.x.x-gplv3-fips-ready.zip
+generate-fips-conf:
+ @if [ -z "$(BUNDLE)" ]; then \
+ echo "Usage: make generate-fips-conf BUNDLE=/path/to/wolfssl-x.x.x-gplv3-fips-ready.zip"; \
+ exit 1; \
+ fi
+ @$(TOPDIR)/generate-fips-conf.sh "$(BUNDLE)"
+
+## fips-on: Enable FIPS Ready - add overrides layer and require conf
+fips-on:
+ @if [ ! -f "$(BBLAYERS_CONF)" ]; then \
+ echo "Error: bblayers.conf not found. Run 'make configure' first."; \
+ exit 1; \
+ fi
+ @if [ ! -f "$(FIPS_CONF)" ]; then \
+ echo "Error: conf/wolfssl-fips-ready.conf not found."; \
+ echo ""; \
+ echo "Generate it from your bundle .zip:"; \
+ echo " make generate-fips-conf BUNDLE=/path/to/wolfssl-x.x.x-gplv3-fips-ready.zip"; \
+ echo ""; \
+ echo "The bundle can live anywhere on disk."; \
+ exit 1; \
+ fi
+ @# --- Add overrides layer to bblayers.conf ---
+ @if grep -q "meta-wolfssl-overrides" "$(BBLAYERS_CONF)"; then \
+ echo "[FIPS] Overrides layer already in bblayers.conf"; \
+ else \
+ sed -i 's|$(LAYERS_DIR)/meta-wolfssl \\|$(LAYERS_DIR)/meta-wolfssl \\\n $(OVERRIDES_LAYER) \\|' "$(BBLAYERS_CONF)"; \
+ echo "[FIPS] Added overrides layer to bblayers.conf"; \
+ fi
+ @# --- Add FIPS config to local.conf ---
+ @if grep -q "$(FIPS_MARKER)" "$(LOCAL_CONF)"; then \
+ echo "[FIPS] FIPS config block already in local.conf"; \
+ else \
+ echo '' >> "$(LOCAL_CONF)"; \
+ echo '$(FIPS_MARKER)' >> "$(LOCAL_CONF)"; \
+ echo 'require $(FIPS_CONF)' >> "$(LOCAL_CONF)"; \
+ echo '' >> "$(LOCAL_CONF)"; \
+ echo '# FIPS initramfs - kernel bundles wolfSSL kernel module for early boot' >> "$(LOCAL_CONF)"; \
+ echo 'INITRAMFS_IMAGE = "core-image-minimal-initramfs"' >> "$(LOCAL_CONF)"; \
+ echo 'INITRAMFS_IMAGE_BUNDLE = "1"' >> "$(LOCAL_CONF)"; \
+ echo '$(FIPS_MARKER) END' >> "$(LOCAL_CONF)"; \
+ echo "[FIPS] Added FIPS config to local.conf"; \
+ fi
+ @echo ""
+ @echo "FIPS Ready ENABLED. Run 'make build' to build the FIPS image."
+
+## fips-off: Disable FIPS Ready - remove overrides layer and FIPS config
+fips-off:
+ @if [ ! -f "$(BBLAYERS_CONF)" ]; then \
+ echo "Error: bblayers.conf not found. Run 'make configure' first."; \
+ exit 1; \
+ fi
+ @# --- Remove overrides layer from bblayers.conf ---
+ @if grep -q "meta-wolfssl-overrides" "$(BBLAYERS_CONF)"; then \
+ sed -i '\|meta-wolfssl-overrides|d' "$(BBLAYERS_CONF)"; \
+ echo "[FIPS] Removed overrides layer from bblayers.conf"; \
+ else \
+ echo "[FIPS] Overrides layer already removed from bblayers.conf"; \
+ fi
+ @# --- Remove FIPS config block from local.conf ---
+ @if grep -q "$(FIPS_MARKER)" "$(LOCAL_CONF)"; then \
+ sed -i '/^$(FIPS_MARKER)/,/^$(FIPS_MARKER) END/d' "$(LOCAL_CONF)"; \
+ echo "[FIPS] Removed FIPS config block from local.conf"; \
+ else \
+ echo "[FIPS] FIPS config block already removed from local.conf"; \
+ fi
+ @echo ""
+ @echo "FIPS Ready DISABLED. Run 'make clean && make build' to rebuild."
+
+## fips-status: Show whether FIPS Ready overrides are enabled
+fips-status:
+ @if [ ! -f "$(BBLAYERS_CONF)" ]; then \
+ echo "FIPS: unknown (bblayers.conf not found)"; \
+ elif grep -q "meta-wolfssl-overrides" "$(BBLAYERS_CONF)"; then \
+ echo "FIPS: ENABLED"; \
+ if [ -f "$(FIPS_CONF)" ]; then \
+ echo "Config: conf/wolfssl-fips-ready.conf found"; \
+ else \
+ echo "Config: conf/wolfssl-fips-ready.conf MISSING"; \
+ fi; \
+ else \
+ echo "FIPS: DISABLED"; \
+ fi
+
+## ip-dhcp: Set network to DHCP
+ip-dhcp:
+ @$(TOPDIR)/set-network.sh dhcp
+
+## ip-
: Set static IP (e.g. make ip-192.168.1.100)
+## Optional: GW=192.168.1.1 PREFIX=24 DNS=8.8.8.8
+ip-%:
+ @$(TOPDIR)/set-network.sh static $* $(GW) $(PREFIX) $(DNS)
+
+## qemu: Build the FIPS Ready image for QEMU (aarch64)
+qemu:
+ @if [ ! -f "$(BBLAYERS_CONF)" ]; then \
+ echo "Error: bblayers.conf not found. Run 'make configure' first."; \
+ exit 1; \
+ fi
+ @if ! grep -q "meta-wolfssl-overrides" "$(BBLAYERS_CONF)" 2>/dev/null; then \
+ echo "Error: FIPS not enabled. Run 'make fips-on' first."; \
+ exit 1; \
+ fi
+ @echo "Building $(QEMU_IMAGE) for $(QEMU_MACHINE)"
+ $(call bitbake-cmd, bitbake -R $(QEMU_CONF) $(QEMU_IMAGE))
+
+## run-qemu: Launch the QEMU image (use QEMU_ARGS for extra options, e.g. QEMU_ARGS=nographic)
+run-qemu:
+ @QBCONF=$$(ls -t $(QEMU_DEPLOY_DIR)/*.qemuboot.conf 2>/dev/null | head -1); \
+ if [ -z "$$QBCONF" ]; then \
+ echo "Error: No .qemuboot.conf found. Run 'make qemu' first."; \
+ exit 1; \
+ fi; \
+ echo "Using: $$QBCONF"
+ $(call bitbake-cmd, \
+ QBCONF=$$(ls -t $(QEMU_DEPLOY_DIR)/*.qemuboot.conf 2>/dev/null | head -1) && \
+ runqemu $$QBCONF nographic $(QEMU_ARGS))
+
+## shell: Open an interactive shell with the bitbake environment sourced
+shell:
+ @if [ ! -d "$(POKY_DIR)" ]; then \
+ echo "Error: poky not found. Run ./setup.sh first."; \
+ exit 1; \
+ fi
+ @echo "Entering bitbake environment shell. Type 'exit' to leave."
+ @bash --init-file <(echo "source $(POKY_DIR)/oe-init-build-env $(BUILD_DIR)")
+
+## help: Show available targets
+help:
+ @echo "Yocto Scarthgap RPi5 Build Targets"
+ @echo "==================================="
+ @echo ""
+ @echo " make configure - Initialize build dir and write config"
+ @echo " make build - Build image for $(MACHINE) (FIPS or base, auto-detected)"
+ @echo " make build-minimal - Build core-image-minimal for $(MACHINE)"
+ @echo " make clean - Clean active image sstate"
+ @echo " make distclean - Remove entire build directory"
+ @echo " make clean-project - Reset to clean state (keeps source + bundle)"
+ @echo " make image-info - Show output image location and sizes"
+ @echo " make move-image - Copy latest .wic image to current directory"
+ @echo " make generate-fips-conf BUNDLE= - Generate FIPS config from .zip bundle"
+ @echo " make fips-on - Enable FIPS Ready overrides"
+ @echo " make fips-off - Disable FIPS overrides layer"
+ @echo " make fips-status - Show FIPS override status"
+ @echo " make qemu - Build FIPS Ready image for QEMU ($(QEMU_MACHINE))"
+ @echo " make run-qemu - Launch QEMU image (nographic/headless by default)"
+ @echo " make ip-dhcp - Set network to DHCP"
+ @echo " make ip- - Set static IP (e.g. make ip-192.168.1.100)"
+ @echo " Optional: GW=x.x.x.x PREFIX=24 DNS=x.x.x.x"
+ @echo " make shell - Open a bitbake-ready shell"
+ @echo " make help - Show this help"
+ @echo ""
+ @echo "Run ./setup.sh first to clone layers and check host tools."
diff --git a/meta-wolfssl-linux-fips/README.md b/meta-wolfssl-linux-fips/README.md
new file mode 100644
index 00000000..5f763ac5
--- /dev/null
+++ b/meta-wolfssl-linux-fips/README.md
@@ -0,0 +1,620 @@
+# meta-wolfssl-linux-fips
+
+Yocto Scarthgap build environment for Raspberry Pi 5 and QEMU (aarch64) with
+wolfSSL FIPS Ready cryptography.
+
+## Overview
+
+This project provides a turnkey setup for building a Linux image targeting the
+Raspberry Pi 5 or QEMU (aarch64) using the Yocto Project's **Scarthgap** (5.0)
+release. The RPi5 base image includes SSH, HDMI console output, Wi-Fi,
+Bluetooth, and systemd. The QEMU target allows testing the full FIPS Ready
+stack without physical hardware.
+
+When FIPS is enabled, the image includes:
+
+- **wolfSSL FIPS Ready** cryptographic library
+- **libgcrypt** with wolfSSL backend
+- **GnuTLS** with wolfSSL backend
+- **wolfProvider** (OpenSSL 3.x provider, replace-default mode)
+- **wolfSSL FIPS kernel module** loaded via initramfs at early boot
+- Kernel FIPS mode (`CONFIG_CRYPTO_FIPS=y`) and LKCAPI registration
+- wolfcrypttest, wolfcryptbenchmark, ptest suites for validation
+
+## Prerequisites
+
+### Host packages (Ubuntu/Debian)
+
+```bash
+sudo apt install gawk wget git diffstat unzip texinfo gcc build-essential \
+ chrpath socat cpio python3 python3-pip python3-pexpect xz-utils \
+ debianutils iputils-ping python3-git python3-jinja2 python3-subunit \
+ zstd liblz4-tool file locales libacl1
+sudo locale-gen en_US.UTF-8
+```
+
+You can also run `./setup.sh` which will check for all required tools and
+report any that are missing. On non-Debian distros where the tool names differ,
+use `--ignore-missing-packages` to skip the check:
+
+```bash
+./setup.sh --ignore-missing-packages
+```
+
+### direnv (recommended)
+
+direnv auto-sources the bitbake environment when you `cd` into the project
+directory. `setup.sh` generates the `.envrc` file automatically. Install
+direnv once:
+
+```bash
+sudo apt install -y direnv
+
+# Add the hook to ~/.bashrc (only needed once)
+echo 'eval "$(direnv hook bash)"' >> ~/.bashrc
+source ~/.bashrc
+
+# After running setup.sh, allow direnv to load the generated .envrc
+direnv allow
+```
+
+### System requirements
+
+- **Disk space:** At least 65 GB free (downloads + sstate cache + build output)
+- **RAM:** 8 GB minimum, 16 GB+ recommended
+- **OS:** Linux (tested on Ubuntu 22.04+, Debian 13, WSL2)
+
+## Quick Start
+
+### 1. Common setup (required for all targets)
+
+```bash
+# Clone required Yocto layers and check host tools
+./setup.sh
+
+# Initialize build directory and write configuration
+make configure
+```
+
+### 2a. RPi5 base image (no FIPS)
+
+```bash
+# (Optional) Set a static IP - default is DHCP
+make ip-192.168.2.227 GW=192.168.2.1 PREFIX=24 DNS=8.8.8.8
+
+# Build the base image
+make build
+
+# Copy the image to the current directory for flashing
+make move-image
+```
+
+### 2b. RPi5 FIPS Ready image
+
+```bash
+# Generate FIPS config from your bundle (bundle can live anywhere on disk)
+make generate-fips-conf BUNDLE=/path/to/wolfssl-x.x.x-gplv3-fips-ready.zip
+
+# Enable FIPS (adds overrides layer, initramfs, and FIPS conf to local.conf)
+make fips-on
+
+# Build the FIPS image (auto-detected when FIPS is enabled)
+make build
+
+# Copy and flash
+make move-image
+```
+
+To disable FIPS and go back to the base image:
+
+```bash
+make fips-off
+make build
+```
+
+### 2c. QEMU FIPS Ready image (no RPi hardware needed)
+
+This builds and runs the same FIPS Ready stack in QEMU, using `linux-yocto`
+(6.6) instead of `linux-raspberrypi`. The wolfSSL FIPS kernel module loads
+via initramfs at early boot, same as on RPi5.
+
+```bash
+# Generate FIPS config and enable FIPS (if not already done)
+make generate-fips-conf BUNDLE=/path/to/wolfssl-x.x.x-gplv3-fips-ready.zip
+make fips-on
+
+# Build the QEMU FIPS image
+make qemu
+
+# Launch in QEMU (nographic/headless, serial on stdio)
+make run-qemu
+```
+
+Exit QEMU with `Ctrl-A X`.
+
+> **Note:** Both `make build` (RPi5) and `make qemu` (QEMU) can be used in
+> the same build directory. FIPS must be enabled (`make fips-on`) for both.
+> The `conf/qemu-override.conf` is applied via `bitbake -R` to swap the
+> machine and kernel without modifying `local.conf`.
+
+## Build Targets
+
+| Command | Description |
+|----------------------|----------------------------------------------|
+| `make configure` | Initialize build dir and write config |
+| `make build` | Build image (FIPS or base, auto-detected) |
+| `make build-minimal` | Build `core-image-minimal` for RPi5 |
+| `make clean` | Clean the active image (cleansstate) |
+| `make distclean` | Remove the entire build directory |
+| `make image-info` | Show output image location and size |
+| `make move-image` | Copy latest `.wic` image to current directory|
+| `make generate-fips-conf BUNDLE=` | Generate FIPS config from .zip bundle |
+| `make fips-on` | Enable FIPS Ready overrides and initramfs |
+| `make fips-off` | Disable FIPS Ready overrides |
+| `make fips-status` | Show FIPS Ready status |
+| `make qemu` | Build FIPS Ready image for QEMU (qemuarm64) |
+| `make run-qemu` | Launch QEMU image (nographic by default) |
+| `make ip-dhcp` | Set network to DHCP |
+| `make ip-` | Set static IP (e.g. `make ip-192.168.1.100`) |
+| `make shell` | Open a shell with the bitbake env sourced |
+
+## FIPS Ready Architecture
+
+When FIPS is enabled (`make fips-on`), the overrides layer
+(`layers/meta-wolfssl-overrides/`) is added to the build. This layer:
+
+- Provides the `wolfssl-fips-ready-image` recipe for RPi5 and
+ `wolfssl-fips-ready-image-qemu` for QEMU (both extend `core-image-base`)
+- Configures wolfSSL, libgcrypt, GnuTLS, wolfProvider, OpenSSL, curl, and
+ OpenSSH via bbappends
+- Enables kernel FIPS mode and the wolfSSL kernel randomness patch
+- Bundles the wolfSSL FIPS kernel module into an initramfs for early boot
+- Signs the kernel module with the kernel's build key
+
+### Boot sequence (FIPS enabled)
+
+**RPi5:**
+```
+1. RPi5 firmware loads kernel (with bundled initramfs)
+2. Initramfs mounts, modprobe libwolfssl loads FIPS kernel module
+3. Root filesystem mounts
+4. Userspace applications use wolfSSL FIPS via:
+ - wolfProvider (OpenSSL 3.x replace-default)
+ - GnuTLS with wolfSSL backend
+ - libgcrypt with wolfSSL backend
+```
+
+**QEMU (qemuarm64):**
+```
+1. QEMU loads linux-yocto kernel (with bundled initramfs)
+2. Initramfs mounts, modprobe libwolfssl loads FIPS kernel module
+3. Root filesystem mounts (ext4 via virtio-blk)
+4. Same userspace FIPS stack as RPi5
+```
+
+### FIPS bundle
+
+The FIPS Ready bundle `.zip` can live anywhere on disk. Running
+`make generate-fips-conf BUNDLE=/path/to/bundle.zip` computes checksums and
+generates `conf/wolfssl-fips-ready.conf` pointing to the bundle's location.
+The bundle is never copied into the source tree.
+
+## Network Configuration
+
+The image uses systemd-networkd for network management. Set the IP configuration
+**before** building the image - it gets baked in.
+
+### DHCP (default)
+
+```bash
+make ip-dhcp
+make configure && make build
+```
+
+### Static IP
+
+```bash
+make ip-192.168.2.227 GW=192.168.2.1 PREFIX=24 DNS=8.8.8.8
+make configure && make build
+```
+
+| Parameter | Default | Description |
+|-----------|--------------------------|--------------------------|
+| `GW` | Auto (IP with `.1`) | Gateway address |
+| `PREFIX` | `24` | Subnet prefix length |
+| `DNS` | `8.8.8.8` | DNS server |
+
+## Flashing and Booting
+
+### RPi5: Flash to SD card
+
+```bash
+make move-image
+lsblk # find your SD card device
+
+# Using bmaptool (recommended)
+sudo bmaptool copy wolfssl-fips-ready-image-raspberrypi5.rootfs.wic.bz2 /dev/sdX
+
+# Using dd
+bzcat wolfssl-fips-ready-image-raspberrypi5.rootfs.wic.bz2 | sudo dd of=/dev/sdX bs=4M status=progress
+```
+
+### RPi5: Boot and connect
+
+**Login:** `root` with no password (`debug-tweaks` is enabled).
+
+**SSH:**
+```bash
+ssh root@192.168.2.227
+```
+
+### QEMU: Launch and connect
+
+```bash
+make run-qemu
+```
+
+Boots directly to a login prompt on stdio. Login as `root` (no password).
+Exit with `Ctrl-A X`.
+
+## Testing
+
+These tests apply to both RPi5 and QEMU. Run them after booting the image.
+
+### 1. Kernel Module Verification
+
+Verify that the wolfSSL FIPS kernel module loaded via initramfs:
+
+```bash
+lsmod | grep libwolfssl
+dmesg | grep -i wolfssl
+```
+
+Expected dmesg output includes:
+```
+FIPS 140-3 wolfCrypt-fips ... startup self-test succeeded.
+wolfCrypt self-test passed.
+wolfCrypt: changing fips_enabled from 0 to 1 for FIPS module.
+```
+
+Followed by LKCAPI algorithm registrations (AES-CBC, AES-GCM, HMAC-SHA*, SHA*, RSA, ECDSA, ECDH, DRBG).
+
+Verify kernel crypto API registration:
+```bash
+cat /proc/crypto | grep -A 5 wolfcrypt
+```
+
+### 2. Kernel Randomness
+
+If the kernel randomness patch is applied, verify the wolfSSL DRBG is integrated:
+
+```bash
+dmesg | grep "kernel global random_bytes handlers installed"
+```
+
+Test the DRBG under load:
+```bash
+# Basic test (2 GB)
+dd if=/dev/urandom bs=1M count=2000 status=progress of=/dev/null
+```
+
+### 3. Cryptographic Benchmarking
+
+Test kernel crypto via cryptsetup:
+
+```bash
+cryptsetup benchmark --cipher aes-cbc --key-size 256
+cryptsetup benchmark
+```
+
+### 4. wolfCrypt Tests
+
+```bash
+wolfcrypttest
+wolfcryptbenchmark
+```
+
+### 5. Testing libgcrypt
+
+libgcrypt is configured to use wolfSSL as its crypto backend.
+
+```bash
+ptest-runner libgcrypt
+```
+
+Expected output: all tests PASS (basic, mpitests, curves, fips186-dsa, etc.).
+
+Verify library linking:
+```bash
+ldd /usr/lib/libgcrypt.so.20 # should show libwolfssl
+```
+
+### 6. Testing GnuTLS
+
+GnuTLS uses wolfSSL via the wolfssl-gnutls-wrapper.
+
+```bash
+cd /opt/wolfssl-gnutls-wrapper/tests/
+make run_fips
+```
+
+All tests should pass with wrapper log messages showing crypto operations routed to wolfSSL.
+
+Verify wrapper linking:
+```bash
+ldd /opt/wolfssl-gnutls-wrapper/lib/libgnutls-wolfssl-wrapper.so
+# should show both libgnutls.so and libwolfssl.so
+```
+
+### 7. Testing wolfProvider
+
+wolfProvider is an OpenSSL 3.x provider using wolfSSL as the crypto backend, configured in replace-default mode.
+
+```bash
+wolfprovidertest
+```
+
+Expected output:
+```
+Setting up environment...
+Detected replace-default mode (from config file)
+Detected wolfSSL FIPS build (from config file)
+...
+###### TESTSUITE SUCCESS
+==========================================
+Unit tests PASSED!
+==========================================
+```
+
+### 8. Testing libssh (via libgcrypt)
+
+```bash
+ptest-runner libssh
+```
+
+Some tests may fail due to MD5 not being available in FIPS mode. Expected failures:
+- `torture_keyfiles` - legacy key formats using MD5
+- `torture_pki_rsa` - legacy RSA key formats using MD5
+- `torture_threads_pki_rsa` - multi-threaded legacy RSA using MD5
+
+## Project Structure
+
+```
+meta-wolfssl-linux-fips/
+├── README.md
+├── .gitignore
+├── .envrc # (generated by setup.sh) direnv config
+├── setup.sh # Clone repos, create symlinks, check host tools
+├── configure.sh # Initialize build dir and write config
+├── generate-fips-conf.sh # Generate FIPS config from bundle .zip
+├── set-network.sh # Write network config (called by make ip-*)
+├── Makefile # Build/clean/utility targets
+├── conf/
+│ ├── wolfssl-fips-ready.conf # (generated) FIPS bundle config
+│ └── qemu-override.conf # QEMU machine overrides for bitbake -R
+├── layers/
+│ ├── poky -> ../repos/poky # (symlink)
+│ ├── meta-raspberrypi -> ../repos/meta-raspberrypi # (symlink)
+│ ├── meta-openembedded -> ../repos/meta-openembedded # (symlink)
+│ ├── meta-wolfssl -> ../repos/meta-wolfssl # (symlink)
+│ └── meta-wolfssl-overrides/ # FIPS overrides layer (tracked)
+│ ├── conf/layer.conf
+│ ├── recipes-core/images/
+│ │ ├── wolfssl-fips-ready-image.bb # RPi5 FIPS image
+│ │ ├── wolfssl-fips-ready-image-qemu.bb # QEMU FIPS image
+│ │ └── core-image-minimal-initramfs.bbappend
+│ ├── recipes-kernel/linux/
+│ │ ├── linux-raspberrypi_%.bbappend # RPi kernel patches
+│ │ ├── linux-yocto_%.bbappend # QEMU kernel patches
+│ │ └── files/fips-crypto.cfg
+│ ├── recipes-wolfssl/
+│ │ ├── wolfssl/wolfssl-fips-ready.bbappend
+│ │ ├── wolfssl/wolfssl-linuxkm-fips-ready.bbappend
+│ │ └── wolfprovider/wolfprovider_%.bbappend
+│ ├── recipes-support/
+│ │ ├── curl/curl_%.bbappend
+│ │ ├── gnupg/gnupg_%.bbappend
+│ │ ├── gnutls/gnutls_%.bbappend
+│ │ ├── libgcrypt/libgcrypt_%.bbappend
+│ │ └── nettle/nettle_%.bbappend
+│ ├── recipes-connectivity/
+│ │ ├── openssh/openssh_%.bbappend
+│ │ └── openssl/openssl_%.bbappend
+│ └── recipes-core/network-config/ # Network config recipe
+│ ├── network-config_1.0.bb
+│ └── files/20-wired.network # (generated by make ip-*)
+├── repos/ # Cloned git repos (git-ignored)
+├── build/ # Build directory (git-ignored)
+├── downloads/ # Shared download cache (git-ignored)
+└── sstate-cache/ # Shared state cache (git-ignored)
+```
+
+## Image Configuration
+
+### RPi5
+
+| Setting | Value |
+|-------------|--------------------------------------|
+| Machine | `raspberrypi5` |
+| Kernel | `linux-raspberrypi` (6.6) |
+| Base image | `core-image-base` |
+| FIPS image | `wolfssl-fips-ready-image` |
+| SSH | OpenSSH server enabled |
+| Display | vc4graphics / HDMI 1080p |
+| Wi-Fi | Enabled (linux-firmware-rpidistro) |
+| Bluetooth | Enabled (bluez5) |
+| Init system | systemd |
+| U-Boot | Disabled (direct kernel boot) |
+| Login | `root` / no password (debug-tweaks) |
+| Branch | Scarthgap (Yocto 5.0) |
+
+### QEMU (aarch64)
+
+| Setting | Value |
+|-------------|--------------------------------------|
+| Machine | `qemuarm64` |
+| Kernel | `linux-yocto` (6.6) |
+| FIPS image | `wolfssl-fips-ready-image-qemu` |
+| CPU | `-cpu max` (supports A76 binaries) |
+| Console | nographic (serial on stdio) |
+| Networking | TAP via virtio-net-pci |
+| Init system | systemd |
+| Login | `root` / no password (debug-tweaks) |
+| Branch | Scarthgap (Yocto 5.0) |
+
+## What Each Step Does (Manual Equivalent)
+
+If you want to integrate wolfSSL FIPS Ready into your own Yocto project, here
+is what each make target does under the hood and the equivalent manual steps.
+
+### `./setup.sh`
+
+Clones the required Yocto layers into `repos/` and creates symlinks in
+`layers/` so `bblayers.conf` has a single directory to reference:
+
+```bash
+git clone -b scarthgap git://git.yoctoproject.org/poky repos/poky
+git clone -b scarthgap git://git.yoctoproject.org/meta-raspberrypi repos/meta-raspberrypi
+git clone -b scarthgap git://git.openembedded.org/meta-openembedded repos/meta-openembedded
+git clone -b fips-ready https://github.com/night1rider/meta-wolfssl.git repos/meta-wolfssl
+
+ln -s ../repos/poky layers/poky
+ln -s ../repos/meta-raspberrypi layers/meta-raspberrypi
+ln -s ../repos/meta-openembedded layers/meta-openembedded
+ln -s ../repos/meta-wolfssl layers/meta-wolfssl
+```
+
+Also checks that required host tools (gcc, python3, etc.) are installed and
+generates a `.envrc` for direnv.
+
+### `make configure`
+
+Runs `configure.sh`, which:
+
+1. Sources `oe-init-build-env` to create the `build/` directory
+2. Writes `build/conf/bblayers.conf` with all layer paths (poky, meta-oe,
+ meta-raspberrypi, meta-wolfssl, meta-network-overrides)
+3. Writes an RPi5-specific config block into `build/conf/local.conf`:
+ - `MACHINE = "raspberrypi5"`
+ - SSH, HDMI, Wi-Fi, Bluetooth, systemd
+ - Shared download/sstate cache directories
+
+Manual equivalent: run `source layers/poky/oe-init-build-env build` and edit
+`build/conf/bblayers.conf` and `build/conf/local.conf` by hand.
+
+### `make generate-fips-conf BUNDLE=`
+
+Runs `generate-fips-conf.sh`, which inspects a wolfSSL FIPS Ready `.zip`
+bundle and generates `conf/wolfssl-fips-ready.conf` containing:
+
+- `PREFERRED_PROVIDER_virtual/wolfssl = "wolfssl-fips-ready"` (and linuxkm)
+- `WOLFSSL_VERSION`, `WOLFSSL_SRC_SHA` (SHA256 of the zip)
+- `WOLFSSL_LICENSE_MD5` (MD5 of the COPYING file inside the zip)
+- `WOLFSSL_SRC_DIR` (absolute path to the directory containing the zip)
+- `FIPS_HASH` placeholder (auto-extracted during build via QEMU)
+
+Manual equivalent: compute the SHA256 and license MD5 yourself and write the
+conf file with the correct variable assignments.
+
+### `make fips-on`
+
+Enables the FIPS overrides by modifying two files:
+
+1. **`build/conf/bblayers.conf`** - adds `layers/meta-wolfssl-overrides` to
+ the `BBLAYERS` list (after `meta-wolfssl`)
+2. **`build/conf/local.conf`** - appends a config block:
+ ```
+ require conf/wolfssl-fips-ready.conf
+ INITRAMFS_IMAGE = "core-image-minimal-initramfs"
+ INITRAMFS_IMAGE_BUNDLE = "1"
+ ```
+
+The overrides layer (`meta-wolfssl-overrides`) provides:
+- Image recipes (`wolfssl-fips-ready-image`, `wolfssl-fips-ready-image-qemu`)
+- Kernel bbappends that apply the wolfSSL randomness patches and FIPS crypto
+ config (`CONFIG_CRYPTO_FIPS=y`, module signing)
+- An initramfs bbappend that adds `wolfssl-linuxkm` to the initramfs and
+ injects `modprobe libwolfssl` into the init script
+- bbappends for libgcrypt, GnuTLS, wolfProvider, OpenSSL, curl, and OpenSSH
+ to use wolfSSL as the crypto backend
+
+### `make build` (RPi5)
+
+Sources the bitbake environment and runs:
+
+```bash
+source layers/poky/oe-init-build-env build
+bitbake wolfssl-fips-ready-image # if FIPS enabled
+bitbake core-image-base # if FIPS disabled
+```
+
+This builds the full image for `MACHINE = "raspberrypi5"` using
+`linux-raspberrypi` (6.6). The kernel is bundled with the initramfs
+containing the wolfSSL FIPS kernel module. Output is a `.wic.bz2` image
+in `build/tmp/deploy/images/raspberrypi5/`.
+
+### `make qemu`
+
+Sources the bitbake environment and runs:
+
+```bash
+source layers/poky/oe-init-build-env build
+bitbake -R conf/qemu-override.conf wolfssl-fips-ready-image-qemu
+```
+
+The `-R conf/qemu-override.conf` flag layers additional config on top of
+`local.conf` without modifying it. The override conf:
+
+- Sets `MACHINE = "qemuarm64"`
+- Switches kernel to `linux-yocto` (since `linux-raspberrypi` doesn't
+ support QEMU)
+- Sets `INITRAMFS_IMAGE` and `INITRAMFS_IMAGE_BUNDLE` for the QEMU kernel
+- Clears RPi device trees (`KERNEL_DEVICETREE = ""`)
+- Adds `IMAGE_CLASSES += "qemuboot"` and `IMAGE_FSTYPES += "ext4"` (normally
+ provided by `qemuarm64.conf`, but `-R` is parsed too late for machine
+ config files to take effect)
+- Provides all `QB_*` variables (`QB_MACHINE`, `QB_CPU`, `QB_SERIAL_OPT`,
+ etc.) that `runqemu` needs to launch the VM
+- Sets `console=ttyAMA0` (QEMU's virtual PL011 UART, replacing RPi's
+ `ttyAMA10`)
+- Removes RPi-specific packages (`linux-firmware-rpidistro-bcm43455`)
+
+Output is an `.ext4` rootfs and `.qemuboot.conf` in
+`build/tmp/deploy/images/qemuarm64/`.
+
+### `make run-qemu`
+
+Sources the bitbake environment and runs:
+
+```bash
+source layers/poky/oe-init-build-env build
+runqemu /path/to/wolfssl-fips-ready-image-qemu-qemuarm64.qemuboot.conf nographic
+```
+
+The `.qemuboot.conf` file contains all the QEMU launch parameters (machine
+type, CPU, memory, drives, network, serial). `runqemu` reads it and
+constructs the full `qemu-system-aarch64` command line. The `nographic` flag
+redirects the VM console to stdio.
+
+### `make fips-off`
+
+Reverses `make fips-on`:
+
+1. Removes `meta-wolfssl-overrides` from `build/conf/bblayers.conf`
+2. Removes the FIPS config block (including `INITRAMFS_IMAGE` settings) from
+ `build/conf/local.conf`
+
+After this, `make build` produces a plain `core-image-base` without wolfSSL.
+
+### `make ip-` / `make ip-dhcp`
+
+Runs `set-network.sh` to write a systemd-networkd config file at
+`layers/meta-network-overrides/recipes-core/network-config/files/20-wired.network`.
+This file is baked into the image at build time - it cannot be changed after
+flashing without rebuilding.
+
+## License
+
+This layer configuration is provided under the MIT license. wolfSSL itself is
+dual-licensed under GPLv2 and a commercial license. See
+[wolfSSL licensing](https://www.wolfssl.com/license/) for details.
diff --git a/meta-wolfssl-linux-fips/conf/qemu-override.conf b/meta-wolfssl-linux-fips/conf/qemu-override.conf
new file mode 100644
index 00000000..6c4da169
--- /dev/null
+++ b/meta-wolfssl-linux-fips/conf/qemu-override.conf
@@ -0,0 +1,45 @@
+# QEMU override configuration
+#
+# Applied via 'bitbake -R' to override RPi5 settings for QEMU builds.
+# This file is layered on top of local.conf - last assignment wins.
+
+MACHINE = "qemuarm64"
+
+# Use the standard QEMU kernel instead of linux-raspberrypi
+PREFERRED_PROVIDER_virtual/kernel = "linux-yocto"
+
+# Bundle initramfs into the kernel so wolfSSL FIPS module loads at early boot
+# (same mechanism as RPi - these must be global so linux-yocto picks them up)
+INITRAMFS_IMAGE = "core-image-minimal-initramfs"
+INITRAMFS_IMAGE_BUNDLE = "1"
+
+# Clear RPi device trees - linux-yocto doesn't have broadcom/ DTBs
+KERNEL_DEVICETREE = ""
+
+# Generate .qemuboot.conf so runqemu can launch the image
+# (qemuarm64.conf / qemu.inc are not parsed because -R is too late for
+# machine config resolution - we must replicate the essential settings here)
+IMAGE_CLASSES += "qemuboot"
+IMAGE_FSTYPES += "ext4"
+
+# QEMU machine settings (from qemuarm64.conf - not parsed via -R)
+QB_SYSTEM_NAME = "qemu-system-aarch64"
+QB_MACHINE = "-machine virt"
+QB_CPU = "-cpu max"
+QB_SMP = "-smp 4"
+QB_CPU_KVM = "-cpu host -machine gic-version=3"
+QB_GRAPHICS = "-device virtio-gpu-pci"
+QB_OPT_APPEND = "-device qemu-xhci -device usb-tablet -device usb-kbd"
+QB_TAP_OPT = "-netdev tap,id=net0,ifname=@TAP@,script=no,downscript=no"
+QB_NETWORK_DEVICE = "-device virtio-net-pci,netdev=net0,mac=@MAC@"
+QB_ROOTFS_OPT = "-drive id=disk0,file=@ROOTFS@,if=none,format=raw -device virtio-blk-pci,drive=disk0"
+QB_SERIAL_OPT = "-device virtio-serial-pci -chardev null,id=virtcon -device virtconsole,chardev=virtcon"
+QB_TCPSERIAL_OPT = "-device virtio-serial-pci -chardev socket,id=virtcon,port=@PORT@,host=127.0.0.1,nodelay=on -device virtconsole,chardev=virtcon"
+
+# Serial console - override RPi's ttyAMA10/tty1 with QEMU's ttyAMA0
+SERIAL_CONSOLES = "115200;ttyAMA0 115200;hvc0"
+CMDLINE_DEBUG = ""
+QB_KERNEL_CMDLINE_APPEND = "console=ttyAMA0"
+
+# Remove RPi-specific packages that don't exist for QEMU
+IMAGE_INSTALL:remove = "linux-firmware-rpidistro-bcm43455"
diff --git a/meta-wolfssl-linux-fips/configure.sh b/meta-wolfssl-linux-fips/configure.sh
new file mode 100755
index 00000000..f06ebda6
--- /dev/null
+++ b/meta-wolfssl-linux-fips/configure.sh
@@ -0,0 +1,132 @@
+#!/bin/bash
+#
+# configure.sh - Initialize Yocto build directory and write configuration
+#
+# Called by 'make configure'. Safe to re-run — managed config block is replaced.
+#
+
+set -e
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+BUILD_DIR="${SCRIPT_DIR}/build"
+LAYERS_DIR="${SCRIPT_DIR}/layers"
+POKY_DIR="${LAYERS_DIR}/poky"
+
+if [ ! -d "${POKY_DIR}" ]; then
+ echo "Error: poky not found. Run ./setup.sh first."
+ exit 1
+fi
+
+# --------------------------------------------------------------------------
+# Initialize build directory if needed
+# --------------------------------------------------------------------------
+
+if [ ! -d "${BUILD_DIR}/conf" ]; then
+ echo "[INIT] Initializing build directory..."
+ source "${POKY_DIR}/oe-init-build-env" "${BUILD_DIR}" > /dev/null 2>&1
+fi
+
+# --------------------------------------------------------------------------
+# Write bblayers.conf
+# --------------------------------------------------------------------------
+
+echo "[CONFIG] Writing bblayers.conf..."
+
+cat > "${BUILD_DIR}/conf/bblayers.conf" <> "${BUILD_DIR}/conf/local.conf" <<'LOCALCONF'
+# ==== meta-wolfssl-linux-fips managed config ====
+# This block is managed by 'make configure'. To change, edit configure.sh
+# and re-run 'make configure'.
+
+MACHINE = "raspberrypi5"
+
+# ---- Image features ----
+IMAGE_FEATURES += "ssh-server-openssh"
+
+# ---- Display / GPU ----
+MACHINE_FEATURES:append = " vc4graphics"
+VC4DTBO = "vc4-kms-v3d"
+
+# ---- Early HDMI console ----
+# Force HDMI output and route kernel console to framebuffer
+RPI_EXTRA_CONFIG = "hdmi_force_hotplug=1\nhdmi_group=1\nhdmi_mode=16\ndisable_overscan=1"
+CMDLINE_DEBUG = "console=tty1"
+SERIAL_CONSOLES:append = " 115200;tty1"
+
+# ---- Wi-Fi and Bluetooth ----
+DISTRO_FEATURES:append = " wifi bluetooth"
+IMAGE_INSTALL:append = " linux-firmware-rpidistro-bcm43455 bluez5 wpa-supplicant network-config"
+LICENSE_FLAGS_ACCEPTED:append = " synaptics-killswitch"
+
+# ---- systemd ----
+DISTRO_FEATURES:append = " systemd usrmerge"
+VIRTUAL-RUNTIME_init_manager = "systemd"
+VIRTUAL-RUNTIME_initscripts = "systemd-compat-units"
+
+# ---- Serial console (debug) ----
+ENABLE_UART = "1"
+
+# ---- U-Boot ----
+# Disabled — U-Boot can cause HDMI issues on RPi5, boot kernel directly
+RPI_USE_U_BOOT = "0"
+
+# ---- Shared caches (keep outside build dir for reuse) ----
+DL_DIR ?= "${TOPDIR}/../downloads"
+SSTATE_DIR ?= "${TOPDIR}/../sstate-cache"
+
+# ---- Host SDK architecture ----
+SDKMACHINE ?= "x86_64"
+# ==== meta-wolfssl-linux-fips managed config ==== END
+LOCALCONF
+
+# --------------------------------------------------------------------------
+# Ensure default network config exists (DHCP)
+# --------------------------------------------------------------------------
+
+NETCONF="${SCRIPT_DIR}/layers/meta-network-overrides/recipes-core/network-config/files/20-wired.network"
+
+if [ ! -f "${NETCONF}" ]; then
+ echo "[CONFIG] Writing default network config (DHCP)..."
+ mkdir -p "$(dirname "${NETCONF}")"
+ cat > "${NETCONF}" <<'NETEOF'
+[Match]
+Name=end0 eth0
+
+[Network]
+DHCP=yes
+NETEOF
+fi
+
+echo "[CONFIG] Done. Run 'make build' to build the image."
diff --git a/meta-wolfssl-linux-fips/generate-fips-conf.sh b/meta-wolfssl-linux-fips/generate-fips-conf.sh
new file mode 100755
index 00000000..bef28495
--- /dev/null
+++ b/meta-wolfssl-linux-fips/generate-fips-conf.sh
@@ -0,0 +1,145 @@
+#!/bin/bash
+#
+# generate-fips-conf.sh - Generate wolfssl-fips-ready.conf from a FIPS Ready .zip bundle
+#
+# Usage: ./generate-fips-conf.sh /path/to/wolfssl-x.x.x-gplv3-fips-ready.zip
+#
+
+set -e
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+CONF_FILE="${SCRIPT_DIR}/conf/wolfssl-fips-ready.conf"
+
+ZIP_PATH="$1"
+
+if [ -z "${ZIP_PATH}" ]; then
+ echo "Usage: $0 /path/to/wolfssl-x.x.x-gplv3-fips-ready.zip"
+ exit 1
+fi
+
+if [ ! -f "${ZIP_PATH}" ]; then
+ echo "Error: File not found: ${ZIP_PATH}"
+ exit 1
+fi
+
+ZIP_NAME="$(basename "${ZIP_PATH}")"
+
+# --------------------------------------------------------------------------
+# Extract version from filename
+# Expected format: wolfssl-X.X.X-gplv3-fips-ready.zip
+# --------------------------------------------------------------------------
+
+VERSION="$(echo "${ZIP_NAME}" | sed -n 's/wolfssl-\([0-9]*\.[0-9]*\.[0-9]*\).*/\1/p')"
+
+if [ -z "${VERSION}" ]; then
+ echo "Error: Could not extract version from filename: ${ZIP_NAME}"
+ echo "Expected format: wolfssl-X.X.X-gplv3-fips-ready.zip"
+ exit 1
+fi
+
+echo "=== Generating FIPS Ready Configuration ==="
+echo ""
+echo "Bundle: ${ZIP_NAME}"
+echo "Version: ${VERSION}"
+
+# --------------------------------------------------------------------------
+# Compute SHA256 of the bundle
+# --------------------------------------------------------------------------
+
+echo ""
+echo "[HASH] Computing SHA256..."
+BUNDLE_SHA="$(sha256sum "${ZIP_PATH}" | awk '{print $1}')"
+echo " SHA256: ${BUNDLE_SHA}"
+
+# --------------------------------------------------------------------------
+# Extract COPYING file and compute MD5
+# --------------------------------------------------------------------------
+
+echo "[HASH] Extracting license MD5..."
+TMPDIR="$(mktemp -d)"
+trap "rm -rf ${TMPDIR}" EXIT
+
+# Try to find COPYING in the zip — it may be at the root or in a subdirectory
+COPYING_PATH="$(unzip -l "${ZIP_PATH}" | grep -m1 'COPYING$' | awk '{print $NF}')"
+
+if [ -z "${COPYING_PATH}" ]; then
+ echo "Warning: COPYING file not found in zip. You'll need to set WOLFSSL_LICENSE_MD5 manually."
+ LICENSE_MD5=""
+else
+ unzip -q -o "${ZIP_PATH}" "${COPYING_PATH}" -d "${TMPDIR}"
+ LICENSE_MD5="$(md5sum "${TMPDIR}/${COPYING_PATH}" | awk '{print $1}')"
+ echo " License MD5: ${LICENSE_MD5}"
+fi
+
+# --------------------------------------------------------------------------
+# Derive bundle name
+# --------------------------------------------------------------------------
+
+SRC_NAME="${ZIP_NAME%.zip}"
+
+# --------------------------------------------------------------------------
+# Resolve bundle directory (absolute path)
+# --------------------------------------------------------------------------
+
+BUNDLE_DIR="$(cd "$(dirname "${ZIP_PATH}")" && pwd)"
+
+# --------------------------------------------------------------------------
+# Generate conf file
+# --------------------------------------------------------------------------
+
+echo "[CONFIG] Writing conf/wolfssl-fips-ready.conf..."
+mkdir -p "$(dirname "${CONF_FILE}")"
+
+cat > "${CONF_FILE}" <
+Subject: [PATCH] Fix KRB5KDF missing wc_AesInit causing segfault
+
+The Aes struct in wp_kdf_krb5kdf_derive is never initialized with
+wc_AesInit. In FIPS mode wc_AesCbcEncrypt dereferences uninitialized
+internal pointers causing a segfault. Additionally wc_AesFree is called
+unconditionally at the end, so early-exit error paths (wrong key size
+etc.) also segfault on the uninitialized struct.
+
+Fix by zeroing the struct at declaration and calling wc_AesInit
+immediately, before any validation that could skip to wc_AesFree.
+
+Signed-off-by: meta-wolfssl-overrides
+---
+--- a/src/wp_krb5kdf.c
++++ b/src/wp_krb5kdf.c
+@@ -446,7 +446,7 @@
+ size_t osize = 0;
+ size_t cipherLen = 0;
+ int rc;
+- Aes aes;
++ Aes aes = {0};
+ byte block[AES_BLOCK_SIZE];
+ byte cipherBlock[AES_BLOCK_SIZE];
+ byte *plain = NULL;
+@@ -457,6 +457,13 @@
+ if (!wolfssl_prov_is_running()) {
+ ok = 0;
+ }
++ if (ok) {
++ rc = wc_AesInit(&aes, NULL, INVALID_DEVID);
++ if (rc != 0) {
++ WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_AesInit", rc);
++ ok = 0;
++ }
++ }
+ if (ok && (!wp_kdf_krb5kdf_set_ctx_params(ctx, params))) {
+ ok = 0;
+ }
diff --git a/meta-wolfssl-linux-fips/layers/meta-wolfssl-overrides/recipes-wolfssl/wolfprovider/wolfprovider_%.bbappend b/meta-wolfssl-linux-fips/layers/meta-wolfssl-overrides/recipes-wolfssl/wolfprovider/wolfprovider_%.bbappend
new file mode 100644
index 00000000..235dee2b
--- /dev/null
+++ b/meta-wolfssl-linux-fips/layers/meta-wolfssl-overrides/recipes-wolfssl/wolfprovider/wolfprovider_%.bbappend
@@ -0,0 +1,10 @@
+# Fix n_fold unsigned underflow segfault in KRB5KDF with small constants
+FILESEXTRAPATHS:prepend := "${THISDIR}/files:"
+SRC_URI:append = " file://0001-fix-krb5kdf-n_fold-unsigned-underflow.patch"
+
+# Disable the feature check for manual image configuration
+require ${WOLFSSL_LAYERDIR}/inc/wolfssl-manual-config.inc
+
+# Enable unit tests for wolfprovider replace default mode
+require ${WOLFSSL_LAYERDIR}/inc/wolfprovider/wolfprovider-enable-replace-default-unittest.inc
+require ${WOLFSSL_LAYERDIR}/inc/wolfprovider/wolfprovider-enable-unittest.inc
diff --git a/meta-wolfssl-linux-fips/layers/meta-wolfssl-overrides/recipes-wolfssl/wolfssl/wolfssl-fips-ready.bbappend b/meta-wolfssl-linux-fips/layers/meta-wolfssl-overrides/recipes-wolfssl/wolfssl/wolfssl-fips-ready.bbappend
new file mode 100644
index 00000000..084065b7
--- /dev/null
+++ b/meta-wolfssl-linux-fips/layers/meta-wolfssl-overrides/recipes-wolfssl/wolfssl/wolfssl-fips-ready.bbappend
@@ -0,0 +1,17 @@
+# Configure wolfSSL FIPS Ready with libgcrypt, gnutls, and wolfProvider support
+#
+# These .inc files from meta-wolfssl enable the necessary wolfSSL configure
+# options for each subsystem to use wolfSSL FIPS Ready as the crypto backend.
+
+require ${WOLFSSL_LAYERDIR}/inc/wolfssl-fips-ready/wolfssl-enable-libgcrypt.inc
+require ${WOLFSSL_LAYERDIR}/inc/wolfssl-fips-ready/wolfssl-enable-gnutls.inc
+require ${WOLFSSL_LAYERDIR}/inc/wolfprovider/wolfssl-enable-wolfprovider-fips-ready.inc
+
+# Fix for bundle missing stamp-h.in required by automake
+do_configure_create_stamph() {
+ if [ ! -f ${S}/stamp-h.in ]; then
+ touch ${S}/stamp-h.in
+ fi
+}
+
+addtask do_configure_create_stamph after do_patch before do_configure
diff --git a/meta-wolfssl-linux-fips/layers/meta-wolfssl-overrides/recipes-wolfssl/wolfssl/wolfssl-linuxkm-fips-ready.bbappend b/meta-wolfssl-linux-fips/layers/meta-wolfssl-overrides/recipes-wolfssl/wolfssl/wolfssl-linuxkm-fips-ready.bbappend
new file mode 100644
index 00000000..b43f2b78
--- /dev/null
+++ b/meta-wolfssl-linux-fips/layers/meta-wolfssl-overrides/recipes-wolfssl/wolfssl/wolfssl-linuxkm-fips-ready.bbappend
@@ -0,0 +1,8 @@
+# RPi5 (Cortex-A76 aarch64) — enable ARM assembly and LKCAPI registration
+EXTRA_OECONF:append:raspberrypi5 = " --enable-linuxkm-lkcapi-register=all-kconfig"
+
+# QEMU (aarch64) — enable LKCAPI registration for virtual machine testing
+EXTRA_OECONF:append:qemuarm64 = " --enable-linuxkm-lkcapi-register=all-kconfig"
+
+# Sign the module with the kernel's own key to prevent "module verification failed" taint
+require ${WOLFSSL_LAYERDIR}/inc/wolfssl-linuxkm/wolfssl-linuxkm-sign-module.inc
diff --git a/meta-wolfssl-linux-fips/set-network.sh b/meta-wolfssl-linux-fips/set-network.sh
new file mode 100755
index 00000000..f9d79a94
--- /dev/null
+++ b/meta-wolfssl-linux-fips/set-network.sh
@@ -0,0 +1,63 @@
+#!/bin/bash
+#
+# set-network.sh - Write systemd-networkd config for the RPi5 image
+#
+# Usage:
+# ./set-network.sh dhcp
+# ./set-network.sh static 192.168.1.100 [GATEWAY] [PREFIX] [DNS]
+#
+
+set -e
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+NETCONF="${SCRIPT_DIR}/layers/meta-network-overrides/recipes-core/network-config/files/20-wired.network"
+
+MODE="$1"
+ADDR="$2"
+GW="${3:-}"
+PREFIX="${4:-24}"
+DNS="${5:-8.8.8.8}"
+
+if [ "${MODE}" = "dhcp" ]; then
+ echo "[CONFIG] Setting network to DHCP..."
+ cat > "${NETCONF}" <<'EOF'
+[Match]
+Name=end0 eth0
+
+[Network]
+DHCP=yes
+EOF
+
+elif [ "${MODE}" = "static" ] && [ -n "${ADDR}" ]; then
+ # Auto-detect gateway if not provided: use .1 of the same subnet
+ if [ -z "${GW}" ]; then
+ GW="$(echo "${ADDR}" | sed 's/\.[0-9]*$/.1/')"
+ fi
+
+ echo "[CONFIG] Setting static IP: ${ADDR}/${PREFIX} gw ${GW} dns ${DNS}"
+ cat > "${NETCONF}" < [GATEWAY] [PREFIX] [DNS]"
+ echo ""
+ echo "Examples:"
+ echo " $0 dhcp"
+ echo " $0 static 192.168.1.100"
+ echo " $0 static 192.168.1.100 192.168.1.1 24 8.8.8.8"
+ exit 1
+fi
+
+echo ""
+cat "${NETCONF}"
+echo ""
+echo "Rebuild the image with 'make build' to apply."
diff --git a/meta-wolfssl-linux-fips/setup.sh b/meta-wolfssl-linux-fips/setup.sh
new file mode 100755
index 00000000..e836fa21
--- /dev/null
+++ b/meta-wolfssl-linux-fips/setup.sh
@@ -0,0 +1,178 @@
+#!/bin/bash
+#
+# setup.sh - Clone Yocto layers and verify host tools for RPi5 Scarthgap build
+#
+# Usage: ./setup.sh [--ignore-missing-packages]
+#
+# This script is idempotent — safe to run multiple times.
+#
+# Repos are cloned into repos/ (git-ignored). Symlinks are created in layers/
+# so that layers/ is the single directory referenced by bblayers.conf.
+#
+
+set -e
+
+IGNORE_MISSING=false
+for arg in "$@"; do
+ case "$arg" in
+ --ignore-missing-packages) IGNORE_MISSING=true ;;
+ esac
+done
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+BRANCH="scarthgap"
+REPOS_DIR="${SCRIPT_DIR}/repos"
+LAYERS_DIR="${SCRIPT_DIR}/layers"
+
+echo "=== Yocto Scarthgap RPi5 Setup ==="
+echo "Working directory: ${SCRIPT_DIR}"
+echo ""
+
+cd "${SCRIPT_DIR}"
+
+# --------------------------------------------------------------------------
+# Check required host tools
+# --------------------------------------------------------------------------
+
+echo "--- Checking host tools ---"
+
+MISSING=()
+
+REQUIRED_TOOLS=(
+ git
+ gcc
+ g++
+ make
+ python3
+ wget
+ diffstat
+ unzip
+ makeinfo
+ chrpath
+ socat
+ cpio
+ xz
+ zstd
+ lz4
+ file
+ gawk
+ locale
+)
+
+for tool in "${REQUIRED_TOOLS[@]}"; do
+ if command -v "${tool}" &> /dev/null; then
+ echo " [OK] ${tool}"
+ else
+ echo " [MISS] ${tool}"
+ MISSING+=("${tool}")
+ fi
+done
+
+if [ ${#MISSING[@]} -gt 0 ]; then
+ echo ""
+ echo "WARNING: Missing required tools: ${MISSING[*]}"
+ echo ""
+ echo "On Debian/Ubuntu, install them with:"
+ echo " sudo apt install gawk wget git diffstat unzip texinfo gcc build-essential \\"
+ echo " chrpath socat cpio python3 python3-pip python3-pexpect xz-utils \\"
+ echo " debianutils iputils-ping python3-git python3-jinja2 python3-subunit \\"
+ echo " zstd liblz4-tool file locales libacl1"
+ echo ""
+ if [ "${IGNORE_MISSING}" = true ]; then
+ echo "Continuing anyway (--ignore-missing-packages)."
+ else
+ echo "Re-run with --ignore-missing-packages to skip this check."
+ exit 1
+ fi
+fi
+
+echo ""
+
+# --------------------------------------------------------------------------
+# Clone repos
+# --------------------------------------------------------------------------
+
+mkdir -p "${REPOS_DIR}"
+
+echo "--- Cloning repos (branch: ${BRANCH}) ---"
+
+clone_repo() {
+ local url="$1"
+ local dir="$2"
+ local branch="$3"
+
+ if [ -d "${REPOS_DIR}/${dir}" ]; then
+ echo " [SKIP] ${dir} already exists"
+ else
+ echo " [CLONE] ${dir}"
+ git clone -b "${branch}" "${url}" "${REPOS_DIR}/${dir}"
+ fi
+}
+
+clone_repo "git://git.yoctoproject.org/poky" "poky" "${BRANCH}"
+clone_repo "git://git.yoctoproject.org/meta-raspberrypi" "meta-raspberrypi" "${BRANCH}"
+clone_repo "git://git.openembedded.org/meta-openembedded" "meta-openembedded" "${BRANCH}"
+clone_repo "https://github.com/night1rider/meta-wolfssl.git" "meta-wolfssl" "fips-ready"
+
+echo ""
+
+# --------------------------------------------------------------------------
+# Create symlinks in layers/
+# --------------------------------------------------------------------------
+
+echo "--- Linking repos into layers/ ---"
+
+link_layer() {
+ local name="$1"
+
+ if [ -L "${LAYERS_DIR}/${name}" ]; then
+ echo " [SKIP] layers/${name} symlink exists"
+ elif [ -d "${LAYERS_DIR}/${name}" ]; then
+ echo " [SKIP] layers/${name} is a directory (not a symlink)"
+ else
+ echo " [LINK] layers/${name} -> repos/${name}"
+ ln -s "../repos/${name}" "${LAYERS_DIR}/${name}"
+ fi
+}
+
+link_layer "poky"
+link_layer "meta-raspberrypi"
+link_layer "meta-openembedded"
+link_layer "meta-wolfssl"
+
+# --------------------------------------------------------------------------
+# Generate .envrc for direnv
+# --------------------------------------------------------------------------
+
+echo "--- Generating .envrc ---"
+
+if [ -f "${SCRIPT_DIR}/.envrc" ]; then
+ echo " [SKIP] .envrc already exists"
+else
+ cat > "${SCRIPT_DIR}/.envrc" <<'ENVRC'
+# Auto-source the Yocto/bitbake build environment when entering this directory.
+# Managed by direnv — run 'direnv allow' after any changes to this file.
+
+POKY_DIR="${PWD}/layers/poky"
+BUILD_DIR="${PWD}/build"
+
+if [ -d "${POKY_DIR}" ] && [ -d "${BUILD_DIR}/conf" ]; then
+ # Suppress the oe-init-build-env banner output
+ source "${POKY_DIR}/oe-init-build-env" "${BUILD_DIR}" > /dev/null 2>&1
+
+ # Stay in the original directory (oe-init-build-env changes cwd)
+ cd "${ENVRC_DIR:-${PWD}}"
+else
+ echo "direnv: poky or build dir not found — run ./setup.sh first"
+fi
+ENVRC
+ echo " [OK] .envrc generated"
+fi
+
+echo ""
+echo "=== Setup Complete ==="
+echo ""
+echo "Next steps:"
+echo " make configure - Initialize build dir and write local.conf"
+echo " make build - Build core-image-base"
+echo ""