diff --git a/.github/workflows/cra-kit.yml b/.github/workflows/cra-kit.yml
new file mode 100644
index 000000000..60cc9ce77
--- /dev/null
+++ b/.github/workflows/cra-kit.yml
@@ -0,0 +1,35 @@
+name: CRA Kit
+
+on:
+ push:
+ paths:
+ - 'cra-kit/**'
+ - '.github/workflows/cra-kit.yml'
+ pull_request:
+ paths:
+ - 'cra-kit/**'
+ - '.github/workflows/cra-kit.yml'
+
+# Least-privilege default; this workflow only needs to read the repo contents.
+permissions:
+ contents: read
+
+jobs:
+ validate-auditor-packet:
+ runs-on: ubuntu-latest
+ steps:
+ # Actions pinned to commit SHAs (supply-chain hygiene), not mutable tags.
+ - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
+ - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
+ with:
+ python-version: '3.x'
+ - name: Validate pinned auditor packet
+ run: ./cra-kit/scripts/validate.sh
+ - name: Shell syntax check (sh -n)
+ run: |
+ for s in cra-kit/scripts/*.sh; do
+ echo "sh -n $s"
+ sh -n "$s"
+ done
+ - name: ShellCheck scripts
+ run: shellcheck cra-kit/scripts/*.sh
diff --git a/README.md b/README.md
index 3a6371b9e..8cb04a3d2 100644
--- a/README.md
+++ b/README.md
@@ -413,6 +413,30 @@ Please see the
for further usage and details.
+
+
+#### cra-kit (wolfSSL CRA Kit)
+
+This directory is **not** a TLS/crypto tutorial. It demonstrates how to
+generate wolfSSL **component SBOMs** (SPDX + CycloneDX), nest them in a
+**fictional product SBOM**, and understand optional **bomsh** build provenance
+(Linux host only) for EU Cyber Resilience Act-style software transparency.
+
+Includes a [CRA compliance shortlist](cra-kit/CRA-Compliance-Shortlist.md), a
+[who provides what cheat sheet](cra-kit/CRA-Cheat-Sheet.md), full
+[glossary](cra-kit/CRA-Supply-Chain-Glossary.md), [AI playbook](cra-kit/SKILL.md), sample
+[customer-side auditor packet](cra-kit/auditor-packet/) (fictional Acme Connect
+Gateway), [manufacturer-side filings](cra-kit/wolfssl-inc-auditor-packet/) (what
+wolfSSL Inc. itself ships under CRA — classification, conformity assessment,
+declaration of conformity template, EU AR status, etc.), and helper scripts
+(`validate.sh` runs without building wolfSSL, with optional `cyclonedx-cli` /
+`pyspdxtools` schema validation). Regenerating component SBOMs requires a
+wolfSSL tree with SBOM support — see [cra-kit/README.md](cra-kit/README.md).
+
+Please see the [cra-kit/README.md](cra-kit/README.md) for further
+usage and details.
+
+
#### uefi-library (wolfCrypt UEFI boot module and test app)
diff --git a/cra-kit/CRA-Cheat-Sheet.md b/cra-kit/CRA-Cheat-Sheet.md
new file mode 100644
index 000000000..3ccddc7ce
--- /dev/null
+++ b/cra-kit/CRA-Cheat-Sheet.md
@@ -0,0 +1,133 @@
+# wolfSSL CRA Supply Chain Cheat Sheet
+
+**Who provides what** — **you** vs **wolfSSL**
+Print this page; use **[CRA-Supply-Chain-Glossary.md](CRA-Supply-Chain-Glossary.md)** for full definitions (SBOM, SPDX, CycloneDX, CBOM, VEX, bomsh, PURL, …).
+
+**Not legal advice.** You are the **manufacturer** for your product on the EU market.
+wolfSSL provides **component evidence** for the **wolfSSL library only**.
+wolfSSL Inc. is itself a manufacturer under CRA for libraries it places on the EU market —
+see our [`security.txt`](https://www.wolfssl.com/.well-known/security.txt),
+[CVD policy](https://www.wolfssl.com/.well-known/vulnerability-disclosure-policy.txt),
+and our manufacturer-side filings in
+[`wolfssl-inc-auditor-packet/`](wolfssl-inc-auditor-packet/) for reference.
+
+Requires a wolfSSL tree with SBOM support (`make sbom` / `scripts/gen-sbom`).
+`make sbom` also needs `pyspdxtools` (`pip install spdx-tools`).
+
+**CRA Kit:** `wolfssl-examples/cra-kit/` · **AI playbook:** [SKILL.md](SKILL.md)
+**Product-level CRA shortlist (4 pillars):** [CRA-Compliance-Shortlist.md](CRA-Compliance-Shortlist.md)
+
+---
+
+## CRA compliance shortlist (four pillars)
+
+| Pillar | You | wolfSSL |
+|--------|-----|---------|
+| **1. Know your components** | Product SBOM + vuln process for whole product | Component SBOMs, advisories, updates — **this kit** |
+| **2. Secure boot** | Trusted firmware + update path | **wolfBoot** |
+| **3. Data in transfer** | Secure protocols for remote/cloud traffic | **TLS**, **SSH**, **MQTTS**, … |
+| **4. Vulnerability handling & reporting** | Published CVD policy + `security.txt`; 24h ENISA reporting (Art. 14); on-call coverage | Reference templates: wolfSSL [`security.txt`](https://www.wolfssl.com/.well-known/security.txt) + [CVD policy](https://www.wolfssl.com/.well-known/vulnerability-disclosure-policy.txt); advisories; CNA |
+
+Detail: [CRA-Compliance-Shortlist.md](CRA-Compliance-Shortlist.md)
+
+---
+
+## Who provides what (you vs wolfSSL)
+
+| | **You (product manufacturer)** | **wolfSSL (library supplier)** |
+|---|-------------------------------|--------------------------------|
+| **Inventory** | **Product SBOM** — OS, apps, all third-party code | **Component SBOM** — wolfSSL only (SPDX + CycloneDX) |
+| **How you connect** | Nest or reference our files in your product SBOM | Ship `wolfssl-*.spdx.json` and `wolfssl-*.cdx.json` |
+| **Vulnerabilities** | Your process + owner for the shipped product | [Advisories](https://www.wolfssl.com/docs/security-vulnerabilities/) + [CVD policy](https://www.wolfssl.com/.well-known/vulnerability-disclosure-policy.txt) + [`security.txt`](https://www.wolfssl.com/.well-known/security.txt) |
+| **Optional build proof** | Only if your contract/auditor asks | `make bomsh` / OmniBOR (**Linux build host** only) |
+
+**Worked example:** [`auditor-packet/`](auditor-packet/) — fictional *Acme Connect Gateway* + wolfSSL SBOMs nested.
+
+---
+
+## What auditors ask
+
+| Question | Term | wolfSSL today |
+|----------|------|---------------|
+| What software is in the product? | **SBOM** | `make sbom`, `cmake --target sbom`, or `gen-sbom` → SPDX + CycloneDX |
+| What crypto is enabled in *your* build? | **CBOM** (path) | `wolfssl:build:*` in CycloneDX — not full `cryptographic-asset` yet |
+| How was the library binary built? | **Provenance** | `make bomsh` (**Linux** host, optional) |
+
+*See glossary for SPDX vs CycloneDX, VEX, PURL, OmniBOR.*
+
+---
+
+## Build system integration quick-reference
+
+| Build system | How to generate SBOM | Script env var |
+|---|---|---|
+| **autotools** | `make sbom` | `CRA_SBOM_MODE=autotools` |
+| **cmake** | `cmake --build build --target sbom` | `CRA_SBOM_MODE=cmake` + `WOLFSSL_BUILD_DIR=build` |
+| **embedded / custom** (source list) | `gen-sbom --user-settings … --srcs *.c` | `CRA_SBOM_MODE=embedded` + `CRA_SBOM_SRCS_FILE=srcs.txt` |
+| **embedded** (no hashable artifact) | `gen-sbom --user-settings … --no-artifact-hash` | `CRA_SBOM_MODE=embedded` + `CRA_SBOM_NO_HASH=true` |
+
+For the embedded path the `generate-wolfssl-sbom.sh` script:
+- Tries **pcpp** (pure-Python preprocessor) first — `pip install pcpp`
+- Falls back to **`CC -dM -E`** — set `CC=arm-none-eabi-gcc` for cross builds
+- Accepts a source file list from `CRA_SBOM_SRCS_FILE` (one path per line, `#` lines ignored)
+- Accepts `CRA_SBOM_NO_HASH=true` when no source list is available
+
+Contact wolfssl@wolfssl.com before shipping a `--no-artifact-hash` SBOM in production.
+
+---
+
+## BOMs at a glance
+
+| Name | Owner | wolfSSL today |
+|------|-------|---------------|
+| **Product SBOM** | **You** | — |
+| **Component SBOM** | **wolfSSL** (you nest) | **Yes** |
+| **CBOM** | **You** document; we signal config | **Partial** (build properties) |
+| **VEX** | **You** (+ scanner) | Advisories only |
+| **bomsh** | **wolfSSL** (optional) | **Yes**, Linux host only |
+
+Details: [CRA-Supply-Chain-Glossary.md](CRA-Supply-Chain-Glossary.md) · roadmap: [ROADMAP.md](ROADMAP.md)
+
+---
+
+## Four decisions
+
+| Question | Answer |
+|----------|--------|
+| Need **our own** SBOM? | **Yes** |
+| wolfSSL SBOM **enough alone**? | **No** — nest or reference in yours |
+| Need **bomsh** for CRA? | **Usually no** |
+| **SPDX** or **CycloneDX**? | **Both** — use what your tools consume |
+
+---
+
+## Beyond this kit (don't skip)
+
+This kit covers **software transparency** only. Before placing your product on
+the EU market you also need:
+
+| Obligation | Article | Action |
+|------------|---------|--------|
+| **EU Authorised Representative** | Art. 18 | Required if you're established outside the EU |
+| **Product class** (Annex III/IV) | — | Determines self-cert vs **Notified Body** — long queues |
+| **Conformity assessment + CE mark** | Art. 32, 30 | Module A or external review |
+| **Technical documentation** | Annex VII | Risk assessment, support-period commitment |
+| **Free security updates** | Art. 13(8) | 5+ year support period default |
+
+Engage CRA counsel/consultant — these are legal/structural decisions, not
+artefacts. See [`CRA-Compliance-Shortlist.md`](CRA-Compliance-Shortlist.md)
+"Beyond this kit" for detail.
+
+---
+
+## What to read next
+
+| Resource | File |
+|----------|------|
+| Full glossary | [CRA-Supply-Chain-Glossary.md](CRA-Supply-Chain-Glossary.md) |
+| Integration guide | [README.md](README.md) |
+| Sample auditor folder | [auditor-packet/](auditor-packet/) |
+| AI + scripts playbook | [SKILL.md](SKILL.md) |
+| Upstream SBOM reference (flags, formats, OmniBOR) | [wolfssl/doc/SBOM.md](https://github.com/wolfSSL/wolfssl/blob/master/doc/SBOM.md) |
+
+**Questions about this kit:** support@wolfssl.com · **Security reports:** see [`security.txt`](https://www.wolfssl.com/.well-known/security.txt)
diff --git a/cra-kit/CRA-Compliance-Shortlist.md b/cra-kit/CRA-Compliance-Shortlist.md
new file mode 100644
index 000000000..96d83a5e5
--- /dev/null
+++ b/cra-kit/CRA-Compliance-Shortlist.md
@@ -0,0 +1,130 @@
+# Shortlist towards CRA compliance
+
+**Not legal advice.** The EU Cyber Resilience Act applies to **your product** as a whole.
+wolfSSL helps on **specific pillars** below; you remain the **manufacturer** for market obligations.
+
+This page is the **product-level shortlist** (what to do). For **software transparency** work
+(SBOM, nesting, sample auditor folder), use the **[CRA Kit](README.md)** cheat sheet and
+[`CRA-Cheat-Sheet.md`](CRA-Cheat-Sheet.md).
+
+---
+
+## 1. Know your software components
+
+| **Your job (manufacturer)** | **wolfSSL can help** |
+|----------------------------|----------------------|
+| Run a **survey** of every component in your embedded system or product: What is it? Who maintains it? Is it actively developed? How do you learn about vulnerabilities, fixes, and releases? | **Component SBOMs** (SPDX + CycloneDX) for wolfSSL libraries you ship — `make sbom` / `gen-sbom` |
+| Build and maintain a **product SBOM** for the whole thing you place on the EU market | **Continuous vulnerability management**: [security advisories](https://www.wolfssl.com/docs/security-vulnerabilities/), coordinated disclosure, updates — see wolfSSL [`security.txt`](https://www.wolfssl.com/.well-known/security.txt) and [CVD policy](https://www.wolfssl.com/.well-known/vulnerability-disclosure-policy.txt) |
+| Own vulnerability **process**, owners, and fix timelines for **your** release | Nest or reference our component SBOM in yours — worked example: [`auditor-packet/`](auditor-packet/) |
+
+**CRA Kit focus:** pillar 1 — who provides what cheat sheet, glossary, scripts, [`SKILL.md`](SKILL.md).
+
+---
+
+## 2. Implement secure boot
+
+| **Your job (manufacturer)** | **wolfSSL can help** |
+|----------------------------|----------------------|
+| Treat secure boot as one of the **most influential actions** you can take now: firmware that boots **trusted**, with a defined path to **update** when needed | **[wolfBoot](https://www.wolfssl.com/products/wolfboot/)** — secure bootloader for embedded systems |
+| Align update mechanics with your **complaint / incident** procedures and required **timelines** under CRA | Integration with wolfSSL/wolfCrypt; see wolfBoot docs and support |
+
+Secure boot is **product architecture**, not something an SBOM file alone satisfies.
+
+---
+
+## 3. Bring remote data processing and data-in-transfer up to compliance
+
+CRA is **not only about software inventory** — it also concerns **data** moving between the device and the network.
+
+| **Your job (manufacturer)** | **wolfSSL can help** |
+|----------------------------|----------------------|
+| Map **remote processing** and **connectivity** in your product (cloud, OTA, admin interfaces, telemetry) | Implementations of **state-of-the-art** secure protocols, for example: |
+| Use **current cryptography** and **secure protocols** for data in transfer; document what is enabled in **your** build | **TLS** (wolfSSL), **SSH** (wolfSSH), **MQTTS** (wolfMQTT), and related stacks |
+| Reflect enabled algorithms in **your** product documentation / SBOM / crypto inventory | Build properties in CycloneDX today (`wolfssl:build:*`); formal CBOM profile: **roadmap** — [ROADMAP.md](ROADMAP.md) |
+
+---
+
+## 4. Handle vulnerabilities and report on time
+
+CRA imposes **continuous** vulnerability handling obligations on manufacturers
+(Art. 13) and a hard **24-hour** reporting clock for actively exploited
+vulnerabilities (Art. 14). This is the only CRA pillar that requires **ongoing
+operational capacity**, not a one-time deliverable.
+
+| **Your job (manufacturer)** | **wolfSSL can help** |
+|----------------------------|----------------------|
+| Publish a **Coordinated Vulnerability Disclosure (CVD) policy** and a working security contact (`security.txt` per RFC 9116) so researchers can reach you | Reference templates: wolfSSL's [`security.txt`](https://www.wolfssl.com/.well-known/security.txt) and [CVD policy](https://www.wolfssl.com/.well-known/vulnerability-disclosure-policy.txt) |
+| Operate a **vulnerability handling process** with named owners and stated response targets | wolfSSL [security advisories](https://www.wolfssl.com/docs/security-vulnerabilities/) for libraries you ship; wolfSSL is a CVE Numbering Authority |
+| Notify **ENISA within 24 hours** when a vulnerability in your product is **actively exploited** (Art. 14); follow up at 72 hours and a final report at 14 days | wolfSSL handles ENISA reporting for **wolfSSL libraries placed on the EU market by wolfSSL Inc.**; coordinate with us on shared advisories |
+| Maintain **on-call coverage** including weekends and holidays so the 24-hour clock can be met at any time | — |
+
+This pillar is **not satisfied by SBOM artefacts alone** — it requires
+documented process, named owners, and on-call capacity. The 24-hour ENISA clock
+starts from your **awareness** of active exploitation, not from public disclosure.
+
+---
+
+## Beyond this kit (structural CRA obligations)
+
+The four pillars above cover **software transparency**. A full CRA conformity
+assessment also requires structural obligations that **this kit does not
+cover** — flag these to your CRA consultant or counsel **before** assuming
+SBOMs alone make you ready:
+
+| Obligation | Article | What it means |
+|------------|---------|---------------|
+| **EU Authorised Representative** | Art. 18 | Manufacturers established **outside** the EU must appoint a written-mandated representative **inside** the EU before placing a product on the EU market. Either contract a third-party AR service or use an existing EU subsidiary. |
+| **Product classification** | Annex III / IV | Determines whether conformity assessment is self-declared (default class) or requires a **Notified Body** (important / critical class). Notified-body queues are already long — if you may need one, get in queue early. |
+| **Conformity assessment + CE mark** | Art. 32, 30 | Module A (self-assessment) or external review per classification; CE marking before placing the product on the EU market. |
+| **Technical documentation** | Annex VII | Risk assessment, secure-design rationale, vulnerability handling process, support-period commitment — more than the SBOM. |
+| **Free security updates** | Art. 13(8) | Minimum 5-year support period for security updates by default (longer if the product's expected lifetime is longer). |
+| **Importer / distributor obligations** | Art. 19, 20 | If your product enters the EU via an importer or moves through distributors, additional obligations attach to those parties. |
+
+These are **legal and structural decisions**, not artefacts you can generate
+from source code. wolfSSL ships SBOMs, security-policy templates, and the
+narrative in this kit; **you** appoint your EU AR, classify your product, run
+your conformity assessment, and produce your declaration of conformity. If
+you do not yet have a CRA consultant, engaging one for the
+classification + AR questions specifically is usually the highest-leverage
+early step.
+
+**See how wolfSSL Inc. itself answers each of these.**
+[`wolfssl-inc-auditor-packet/`](wolfssl-inc-auditor-packet/) holds the
+manufacturer-side filings wolfSSL Inc. ships under CRA: Annex III/IV
+classification statement, conformity assessment route, declaration of
+conformity template, EU Authorised Representative status, support-period
+policy, vulnerability-handling process, technical documentation outline,
+and CE marking statement. Where decisions are made, they're stated; where
+they're in flight (EU AR appointment, public SLA), the gap is named.
+Adapt as a template for your own product.
+
+---
+
+## How this maps to the CRA Kit
+
+| Shortlist pillar | Kit deliverable |
+|------------------|-----------------|
+| Know your components | Cheat sheet (who provides what), glossary, `auditor-packet/`, generate/validate scripts |
+| Secure boot | Out of scope for SBOM files — evaluate **wolfBoot** separately |
+| Data in transfer | Configure and document **your** protocol stack; wolfSSL ships crypto libraries, not your full product compliance |
+| Vulnerability handling & reporting | Outside scope of SBOM artefacts — see Art. 13/14 obligations above; wolfSSL's own [CVD policy](https://www.wolfssl.com/.well-known/vulnerability-disclosure-policy.txt) and [`security.txt`](https://www.wolfssl.com/.well-known/security.txt) are usable as reference templates |
+| Structural CRA obligations (EU AR, Annex III/IV, CE, technical docs, support period) | **Out of scope** for this kit — see "Beyond this kit" section above; engage CRA counsel or consultant |
+
+**You will leave with (presentation Promise):**
+
+1. **Who provides what** — [`CRA-Cheat-Sheet.md`](CRA-Cheat-Sheet.md)
+2. **Worked example** — [`auditor-packet/`](auditor-packet/)
+3. **Helper scripts + AI playbook** — product SBOM, nest wolfSSL, optional bomsh on **Linux CI** + [`SKILL.md`](SKILL.md)
+
+---
+
+## Related wolfSSL products (beyond this kit)
+
+| Area | Product / doc |
+|------|----------------|
+| TLS / wolfCrypt | [wolfssl.com](https://www.wolfssl.com/) · upstream SBOM reference: [doc/SBOM.md](https://github.com/wolfSSL/wolfssl/blob/master/doc/SBOM.md) |
+| Secure boot | [wolfBoot](https://www.wolfssl.com/products/wolfboot/) |
+| SSH | wolfSSH |
+| MQTT | wolfMQTT |
+
+**Questions about this kit:** support@wolfssl.com · **Security reports:** see [`security.txt`](https://www.wolfssl.com/.well-known/security.txt)
diff --git a/cra-kit/CRA-Supply-Chain-Glossary.md b/cra-kit/CRA-Supply-Chain-Glossary.md
new file mode 100644
index 000000000..c310828bf
--- /dev/null
+++ b/cra-kit/CRA-Supply-Chain-Glossary.md
@@ -0,0 +1,139 @@
+# CRA & Supply Chain Terminology — Customer Cheat Sheet
+
+One-page reference for teams shipping products that include wolfSSL.
+**Not legal advice.** Map obligations to your product class and role with counsel.
+
+This kit is **self-contained** in [wolfssl-examples `cra-kit/`](https://github.com/wolfSSL/wolfssl-examples/tree/master/cra-kit).
+Upstream technical reference for the SBOM feature (flags, output formats,
+`SBOM_LICENSE_OVERRIDE`, OmniBOR/Bomsh — requires a wolfSSL source tree with
+SBOM support):
+
+- [SBOM.md](https://github.com/wolfSSL/wolfssl/blob/master/doc/SBOM.md)
+
+CRA shortlist (4 pillars): [`CRA-Compliance-Shortlist.md`](CRA-Compliance-Shortlist.md) · Who provides what: [`CRA-Cheat-Sheet.md`](CRA-Cheat-Sheet.md) · AI playbook: [`SKILL.md`](SKILL.md) · Worked example: [`auditor-packet/`](auditor-packet/)
+
+---
+
+## The big picture (30 seconds)
+
+```mermaid
+flowchart LR
+ subgraph you["Your company (manufacturer)"]
+ PSBOM["Product SBOM\n(all components)"]
+ end
+ subgraph wolf["wolfSSL (component)"]
+ WSBOM["wolfSSL SBOM\n(SPDX + CycloneDX)"]
+ BOMSH["OmniBOR / bomsh\n(optional)"]
+ end
+ PSBOM -->|"references or contains"| WSBOM
+ WSBOM -.->|"optional deeper proof"| BOMSH
+```
+
+| Question | Short answer |
+|----------|--------------|
+| Do we need **our own** SBOM? | **Yes** — for the **whole product** you place on the EU market. |
+| Is wolfSSL’s SBOM enough by itself? | **No** (unless you only redistribute wolfSSL). Use it **inside** your product SBOM. |
+| Do we need **bomsh**? | **Usually no.** SBOM alone covers most CRA transparency needs; bomsh adds build traceability if you want it. |
+| SPDX or CycloneDX? | **Both are fine.** wolfSSL ships both; use whichever your tools expect (many teams keep both). |
+
+---
+
+## Glossary
+
+| Term | Stands for / means | Plain English |
+|------|-------------------|---------------|
+| **CRA** | EU **Cyber Resilience Act** | EU law for products with digital elements: inventory, security, vulnerability handling. |
+| **SBOM** | **Software Bill of Materials** | Machine-readable “ingredients list” of software in a product (name, version, supplier, license, IDs, relationships). |
+| **Product SBOM** | — | **Yours:** every OSS/third-party component in the **shipped product**. |
+| **Component SBOM** | — | **wolfSSL’s:** inventory of **wolfSSL only** (`make sbom` or `gen-sbom`). |
+| **SPDX** | **Software Package Data Exchange** | A standard **format** for SBOMs (Linux Foundation). Files: `*.spdx.json`, `*.spdx`. |
+| **CycloneDX** | (project name) | Another standard **format** for SBOMs (OWASP ecosystem). File: `*.cdx.json`. |
+| **NTIA minimum elements** | US NTIA guidance | Checklist of what a “good” SBOM must include (supplier, name, version, unique ID, deps, author, timestamp). CRA practice aligns with this. |
+| **PURL** | **Package URL** | Standard ID like `pkg:github/wolfSSL/wolfssl@v5.9.1` — helps tools match components. wolfSSL ships PURLs in both `github` (canonical, resolves in OSV / GHSA / Snyk / Trivy) and CPE forms. |
+| **CPE** | **Common Platform Enumeration** | Standard ID like `cpe:2.3:a:wolfssl:wolfssl:…` — used by many vulnerability databases. |
+| **VEX** | **Vulnerability Exploitability eXchange** | CycloneDX-side signal: “this CVE does/doesn’t apply to our build.” Often layered on top of SBOM in security tools. |
+| **CBOM** | **Cryptographic Bill of Materials** | Inventory of **crypto algorithms/keys/modules** (beyond generic SBOM). Today: `wolfssl:build:*` in CycloneDX; formal CBOM: see [`ROADMAP.md`](ROADMAP.md). |
+| **bomsh** | wolfSSL **make** target | Runs **OmniBOR** provenance: proves **how** the library binary was built from sources (**Linux host only**). |
+| **OmniBOR** | Omni **Bill of Resources** | Merkle DAG of build inputs/outputs; stored under `omnibor/`. |
+| **gitoid** | Git-object-style ID | Hash pointer (`gitoid:blob:sha1:…`) into the OmniBOR graph; appears in `omnibor.*.spdx.json`. |
+| **Manufacturer** | CRA role | Entity that places the product on the EU market — **owns** product SBOM and vulnerability process. |
+| **Integrator / OEM** | Industry term | You build a device/app containing wolfSSL → you typically act as **manufacturer** for your product. |
+| **externalDocumentRefs** | SPDX feature | Your product SPDX **points to** wolfSSL’s SPDX file without copying every file entry. |
+| **SOURCE_DATE_EPOCH** | Reproducible builds | Fixed timestamp so two `make sbom` runs produce **byte-identical** SBOMs (useful in CI/attestation). |
+
+---
+
+## CRA structural terms
+
+These appear throughout the kit's "Beyond this kit" guidance. They are **not**
+software-transparency artefacts — they are legal/structural CRA obligations
+that no SBOM tool can satisfy. **Not legal advice** — engage CRA counsel.
+
+| Term | Article / location | Plain English |
+|------|--------------------|---------------|
+| **EU Authorised Representative** (EU AR) | Art. 18 | Required if the manufacturer is established **outside** the EU. A written-mandated EU-resident legal entity that receives regulator correspondence on the manufacturer's behalf. Either contract a third-party AR service or use an existing EU subsidiary. **Long-lead** — start now. |
+| **Notified Body** | — | Independent third-party conformity-assessment organisation. For "important" or "critical" products (Annex III/IV) the conformity assessment must involve a Notified Body. Queues are long — engage early if you may need one. |
+| **Annex III** | Annex III | List of **"important"** products with above-baseline cybersecurity risk (e.g. password managers, network management systems, browsers, certain identity-management components). Triggers stricter conformity assessment than the default class. |
+| **Annex IV** | Annex IV | List of **"critical"** products (highest-risk class), e.g. hardware security modules, secure-boot devices, smart-meter gateways of certain types. Always requires Notified Body involvement. |
+| **Annex VII** | Annex VII | Required contents of the **technical documentation**: risk assessment, secure-design rationale, vulnerability handling process, support-period commitment, SBOM, etc. Much more than the SBOM alone. |
+| **Conformity assessment** | Art. 32 | Process to demonstrate the product meets CRA essential requirements. **Module A** self-assessment (default class) or external review by a Notified Body (important/critical). Output is the declaration of conformity. |
+| **Module A** | Annex VIII | Self-assessment conformity procedure. The manufacturer alone performs the assessment and signs the declaration. Default for non-Annex III/IV products. |
+| **CE marking** | Art. 30 | Visible mark indicating conformity with applicable EU regulations. Affixed to the product (or packaging/documentation) before placing on the EU market. Backed by the declaration of conformity. |
+| **Declaration of conformity** | Art. 28 | Manufacturer's signed statement of CRA compliance. Names the product, lists applicable EU acts, identifies the manufacturer (and EU AR if applicable). |
+| **Importer** | Art. 19 | EU entity placing a non-EU product on the EU market. Carries CRA obligations parallel to the manufacturer (verify CE mark, retain AR contact, assist regulators). |
+| **Distributor** | Art. 20 | Party in the supply chain making the product available on the EU market without altering it. Lighter obligations than importer/manufacturer, but must verify CE mark and assist regulators. |
+| **Support period** | Art. 13(2), 13(8) | Minimum duration during which the manufacturer must provide **free security updates**. Default: at least **5 years** (or the product's expected lifetime if longer). Must be declared in the technical documentation. |
+| **ENISA** | Art. 14 | EU Agency for Cybersecurity. Recipient of the **24-hour** early-warning report when a vulnerability in your product is **actively exploited**, plus 72-hour update and 14-day final report. |
+| **CNA** | (CVE programme) | **CVE Numbering Authority** — organisation authorised to assign CVE IDs within its scope. wolfSSL is a CNA for wolfSSL libraries. |
+
+For execution detail on these obligations, see [`CRA-Compliance-Shortlist.md`](CRA-Compliance-Shortlist.md) "Beyond this kit (structural CRA obligations)".
+
+---
+
+## wolfSSL artefacts (what we ship)
+
+| Command | Outputs | Answers |
+|---------|---------|---------|
+| `make sbom` | `wolfssl-.spdx.json`, `.cdx.json`, `.spdx` | **What** is in wolfSSL (version, license, hashes, config flags). |
+| `make bomsh` *(optional)* | `omnibor/`, `omnibor.wolfssl-.spdx.json` | **How** wolfSSL was built (source → binary traceability). |
+
+Embedded/custom builds: `scripts/gen-sbom` with **your** `user_settings.h` and source list — see kit
+[`scripts/generate-embedded-sbom.sh`](scripts/generate-embedded-sbom.sh) and upstream [SBOM.md §1](https://github.com/wolfSSL/wolfssl/blob/master/doc/SBOM.md).
+
+---
+
+## Your checklist
+
+1. **Product SBOM** in release CI (SPDX and/or CycloneDX).
+2. **wolfSSL component** — reference our SBOM (`externalDocumentRefs` / CycloneDX `bom` ref) or copy the package entry; link with `STATIC_LINK` / `DYNAMIC_LINK` / `CONTAINS`.
+3. **Match your build** — if `user_settings.h` or source set differs from stock, regenerate wolfSSL’s SBOM for **your** build.
+4. **Commercial license** — override GPL in SBOM (`SBOM_LICENSE_OVERRIDE`) or in **your** product SBOM entry for wolfSSL; see upstream [SBOM.md § Commercial Licenses](https://github.com/wolfSSL/wolfssl/blob/master/doc/SBOM.md).
+5. **Vulnerabilities** — document your process; wolfSSL disclosure: [`security.txt`](https://www.wolfssl.com/.well-known/security.txt) + [CVD policy](https://www.wolfssl.com/.well-known/vulnerability-disclosure-policy.txt) + [advisories](https://www.wolfssl.com/docs/security-vulnerabilities/).
+6. **bomsh** — only if auditors or contracts ask for build-level proof beyond the SBOM (Linux CI).
+
+---
+
+## SPDX vs CycloneDX (same job, different tools)
+
+| | **SPDX** | **CycloneDX** |
+|---|----------|----------------|
+| **Typical use** | License compliance, legal review, nested documents | Security scanners, VEX, commercial SBOM platforms |
+| **wolfSSL file** | `wolfssl-.spdx.json` | `wolfssl-.cdx.json` |
+| **Nesting wolfSSL** | `externalDocumentRefs` + relationship | Component + `externalReferences` type `bom` |
+
+You do **not** choose “CRA format” — you provide an SBOM that meets NTIA-style expectations; SPDX and CycloneDX are both widely accepted encodings.
+
+---
+
+## Who provides what to an auditor
+
+| Evidence | Provided by |
+|----------|-------------|
+| Product SBOM (full inventory) | **Customer** |
+| wolfSSL SBOM files | **wolfSSL** (customer integrates or references) |
+| OmniBOR / bomsh bundle | **wolfSSL** *(optional)* |
+| Vulnerability disclosure & advisories | **wolfSSL** ([security page](https://www.wolfssl.com/docs/security-vulnerabilities/)); **customer** owns product incident process |
+
+---
+
+*wolfSSL · Part of the [CRA Kit](README.md). Questions about this kit: support@wolfssl.com · Security reports: see [`security.txt`](https://www.wolfssl.com/.well-known/security.txt)*
diff --git a/cra-kit/README.md b/cra-kit/README.md
new file mode 100644
index 000000000..f93e492ad
--- /dev/null
+++ b/cra-kit/README.md
@@ -0,0 +1,309 @@
+# wolfSSL CRA Kit
+
+Example project and scripts for teams that ship products containing wolfSSL and
+need **EU Cyber Resilience Act (CRA)**-style **software transparency** artifacts.
+
+**This kit does not make your product “CRA compliant.”** It shows how to obtain
+and nest **wolfSSL component evidence** inside **your** product SBOM and auditor
+packet.
+
+**Not legal advice.** Map obligations to your product class and role with counsel.
+
+**wolfSSL's own CRA posture.** wolfSSL Inc. is itself a **manufacturer** under
+the CRA for libraries it places on the EU market. We publish our own
+[`security.txt`](https://www.wolfssl.com/.well-known/security.txt) and
+[CVD policy](https://www.wolfssl.com/.well-known/vulnerability-disclosure-policy.txt),
+and the manufacturer-side filings we ship under CRA — Annex III/IV
+classification, conformity assessment route, declaration of conformity
+template, EU Authorised Representative status, support-period policy,
+and vulnerability-handling process — are in
+[`wolfssl-inc-auditor-packet/`](wolfssl-inc-auditor-packet/). Use them as
+reference templates for **your** product.
+
+| Document | Use |
+|----------|-----|
+| [`CRA-Compliance-Shortlist.md`](CRA-Compliance-Shortlist.md) | Four pillars towards CRA (components, secure boot, data in transfer, vulnerability handling) |
+| [`CRA-Cheat-Sheet.md`](CRA-Cheat-Sheet.md) | **Who provides what** — you vs wolfSSL (print/PDF) |
+| [`CRA-Supply-Chain-Glossary.md`](CRA-Supply-Chain-Glossary.md) | Full terminology (**self-contained in this kit**) |
+| [`SKILL.md`](SKILL.md) | **AI playbook** — agent checklist, scripts, Cursor install |
+| [`ROADMAP.md`](ROADMAP.md) | SBOM / CBOM / VEX / bomsh / CSAF — today vs roadmap |
+| [`auditor-packet/`](auditor-packet/) | **Customer-side worked example** — fictional Acme Connect Gateway + wolfSSL SBOM samples |
+| [`wolfssl-inc-auditor-packet/`](wolfssl-inc-auditor-packet/) | **Manufacturer-side filings** — what wolfSSL Inc. itself ships under CRA |
+
+**Self-contained:** all customer-facing docs live in this directory. You only need a
+separate **wolfSSL source tree** (with SBOM support) to **regenerate** component SBOMs.
+
+---
+
+## Prerequisites
+
+- **wolfSSL** source with SBOM support (see [wolfSSL SBOM feature (upstream)](#wolfssl-sbom-feature-upstream) below).
+ Typical layout:
+
+ ```
+ wolf/
+ ├── wolfssl/ ← WOLFSSL_DIR (default: ../../wolfssl from here)
+ └── wolfssl-examples/
+ └── cra-kit/ ← you are here
+ ```
+
+- **Python 3** for `scripts/gen-sbom` (embedded path) and `scripts/validate.sh`.
+- **`pcpp`** (optional for embedded): install on the **same** interpreter as `python3`:
+ `python3 -m pip install pcpp`. If `pip install pcpp` used conda but your shell runs
+ `/usr/local/bin/python3`, use `CRA_PYTHON=python` or rely on the script's automatic
+ **compiler `-dM -E` fallback** (no pcpp required).
+- **Cross-compile note for embedded** (`-dM -E` fallback only): the script defaults to
+ host `cc`. For target-accurate macros set `CC=arm-none-eabi-gcc` (or your toolchain)
+ before running so the SBOM reflects target `__ARM_ARCH`, `__SIZEOF_LONG__`, etc.
+ rather than your laptop's. Skip this if you have `pcpp` installed.
+- **Optional schema validators** (used by `validate.sh` if installed):
+ - [`cyclonedx-cli`](https://github.com/CycloneDX/cyclonedx-cli/releases) for CycloneDX 1.6 schema validation
+ - [`pyspdxtools`](https://pypi.org/project/spdx-tools/) (`pip install spdx-tools`) for SPDX 2.3 schema validation
+
+---
+
+## All the “BOMs” (today vs roadmap)
+
+| Name | What it lists | Who owns it | wolfSSL today | Roadmap |
+|------|----------------|-------------|---------------|---------|
+| **Product SBOM** | Entire shipped product | **You** | — | — |
+| **Component SBOM** | wolfSSL only | **wolfSSL** (you integrate) | **Yes** — SPDX 2.3 + CycloneDX 1.6 | Ongoing |
+| **VEX** | Does CVE X apply to our build? | **You** | [Advisories](https://www.wolfssl.com/docs/security-vulnerabilities/) (VEX inputs) | Templates / automation |
+| **CBOM** | Crypto algorithms / modules | **You**; we **signal** | **Partial** — `wolfssl:build:*` in CycloneDX | Formal `cryptographic-asset` |
+| **OmniBOR / bomsh** | How the library binary was built | **wolfSSL** (optional) | **Yes** — Linux **host** only | Same |
+
+Details: [`ROADMAP.md`](ROADMAP.md).
+
+**Plain summary:** SBOM = what’s inside. Crypto build properties = what crypto you
+compiled in (CBOM direction). bomsh = how the library was built (optional). Product
+SBOM = your job.
+
+---
+
+## Which path are you?
+
+| Profile | Build | Generate wolfSSL SBOM |
+|---------|-------|------------------------|
+| **A. Linux / server / Yocto / package** | `./configure && make` | `make sbom` in wolfSSL tree |
+| **B. Embedded / RTOS / IDE** | `user_settings.h` + your Makefile / Keil / Zephyr / ESP-IDF | `./scripts/generate-embedded-sbom.sh` (kit demo) or upstream `gen-sbom` |
+| **C. Commercial license** | Either | `CRA_LICENSE_OVERRIDE=LicenseRef-wolfSSL-Commercial CRA_LICENSE_TEXT=/path/to/commercial-license.txt ./scripts/generate-wolfssl-sbom.sh` |
+
+> **Commercial (`LicenseRef-*`) overrides require `CRA_LICENSE_TEXT`** pointing at
+> the plain-text licence you received from wolfSSL. SPDX 2.3 §10.1 requires the
+> licence text to be embedded for any `LicenseRef-*`; both `gen-sbom` and
+> `make sbom` hard-fail without it. A stock SPDX id (e.g. `Apache-2.0`) needs no
+> text. If you don't have the text file handy, use
+> [`scripts/make-commercial-sample.sh`](scripts/make-commercial-sample.sh) to
+> derive a commercial sample from the pinned GPL samples instead.
+
+**Every manufacturer still:**
+
+1. Maintains a **product SBOM** (all components).
+2. **References or copies** wolfSSL’s `.spdx.json` / `.cdx.json` into it.
+3. **Regenerates** wolfSSL SBOM when `user_settings.h` or your source list changes.
+4. Owns **vulnerability handling** (process + owner).
+5. Uses **bomsh** only if an auditor or contract requires build proof — on a **Linux** host.
+
+---
+
+## Quick start
+
+### 1. Validate the bundled sample (no wolfSSL build required)
+
+```sh
+cd wolfssl-examples/cra-kit
+./scripts/validate.sh
+```
+
+### 2. Regenerate component SBOMs (requires wolfSSL with `make sbom`)
+
+```sh
+export WOLFSSL_DIR=../../wolfssl
+./scripts/refresh-samples.sh # make sbom + auto-fix product SPDX checksum
+```
+
+Or without updating the product stub checksum:
+
+```sh
+./scripts/generate-wolfssl-sbom.sh # default: autotools if Makefile exists
+CRA_SBOM_MODE=embedded ./scripts/generate-wolfssl-sbom.sh # rarely used for packet/
+./scripts/generate-embedded-sbom.sh # writes wolfssl-component-embedded/
+
+CRA_LICENSE_OVERRIDE=LicenseRef-wolfSSL-Commercial \
+ CRA_LICENSE_TEXT=/path/to/wolfssl-commercial-license.txt \
+ ./scripts/generate-wolfssl-sbom.sh # commercial-license sample (text required)
+./scripts/make-commercial-sample.sh # derive from pinned GPL samples (no rebuild)
+```
+
+**Pinned samples** in `auditor-packet/wolfssl-component/` are from **`make sbom`**
+(autotools), with a sibling `*.commercial.{cdx,spdx}.json` showing the override pattern.
+Embedded regen produces a **different** SBOM (watermarked `wolfssl:sbom:demo=true`) —
+see [`auditor-packet/wolfssl-component/SAMPLE-PROVENANCE.md`](auditor-packet/wolfssl-component/SAMPLE-PROVENANCE.md).
+
+### 3. Study the sample product packet
+
+Open [`auditor-packet/00-INDEX.md`](auditor-packet/00-INDEX.md) — fictional **Acme
+Connect Gateway** shows CycloneDX `bom` external reference and SPDX
+`externalDocumentRefs` pointing at wolfSSL’s files.
+
+### 4. Integrate into your real product SBOM
+
+Copy the pattern from `product-acme-connect-gateway.*` in [`auditor-packet/`](auditor-packet/) — both
+SPDX `externalDocumentRefs` and CycloneDX `bom` external references are shown
+end-to-end. For the upstream technical reference on `make sbom` flags, output
+formats, and `SBOM_LICENSE_OVERRIDE` for commercial licensees, see
+[`wolfssl/doc/SBOM.md`](https://github.com/wolfSSL/wolfssl/blob/master/doc/SBOM.md).
+
+---
+
+## `make bomsh` — Linux host only (simple explanation)
+
+`make bomsh` is **optional** for most CRA transparency needs. Use it when someone
+asks: *“Prove this `libwolfssl.so` was built from these exact sources.”*
+
+**Why only Linux?** Bomsh runs **bomtrace3** — a patched **strace** that watches
+every compiler call during a **full rebuild**. That program is built and tested on
+**Linux build machines** (normal `ptrace`, no kernel patches).
+
+| Your situation | What to do |
+|----------------|------------|
+| Build on **Linux** | `make bomsh` after `make sbom` in wolfSSL |
+| Build on **macOS / Windows** | Run bomsh in **Linux CI**, **WSL2**, or a **container** |
+| Ship firmware to **MCU / RTOS** | **Target OS does not matter** — tracing runs on the **build host** |
+| **Embedded**, no Linux in house | Use **`gen-sbom`** for SBOM on any OS; skip bomsh unless required |
+
+The sample packet does **not** ship `omnibor/` (large). See
+[`auditor-packet/wolfssl-component/README-bomsh.md`](auditor-packet/wolfssl-component/README-bomsh.md).
+
+Full detail: [wolfssl/doc/SBOM.md §3](https://github.com/wolfSSL/wolfssl/blob/master/doc/SBOM.md).
+
+---
+
+## wolfSSL SBOM feature (upstream)
+
+SBOM and optional bomsh provenance are developed in the main **wolfSSL** repository:
+
+| Item | Location |
+|------|----------|
+| Generator | `wolfssl/scripts/gen-sbom` |
+| Autotools | `make sbom`, `make bomsh` |
+| CI | `wolfssl/.github/workflows/sbom.yml` |
+| Reference (flags, formats, OmniBOR) | [doc/SBOM.md](https://github.com/wolfSSL/wolfssl/blob/master/doc/SBOM.md) |
+| Customer-facing CRA narrative, glossary, auditor packet, AI playbook | this kit (you are here) |
+
+Use a wolfSSL tree where the `make sbom` (and optionally `make bomsh`) targets are
+available before running the scripts here. Once these targets land on `master`, any
+recent wolfSSL checkout works; until then, use the integration branch / PR.
+
+Pinned sample version: see [`VERSION`](VERSION) (default **5.9.1**).
+
+---
+
+## Embedded demo settings
+
+[`user_settings.h`](user_settings.h) in this directory is included when
+`WOLFSSL_USER_SETTINGS` is defined for `./scripts/generate-embedded-sbom.sh`.
+Production SBOMs must use **your** project's `user_settings.h` and **your** full
+`--srcs` list (every wolfSSL `.c` you compile).
+
+See **[SRCS-FILE-HOWTO.md](SRCS-FILE-HOWTO.md)** for instructions on extracting
+your wolfSSL source list from common embedded build systems (Makefile, CMake,
+Zephyr, ESP-IDF, Keil, IAR) and passing it via `CRA_SBOM_SRCS_FILE`.
+
+---
+
+## Presentation
+
+15-minute co-sponsor slide track: [`presentations/SLIDE-OUTLINE.md`](presentations/SLIDE-OUTLINE.md).
+
+Handouts: [`CRA-Cheat-Sheet.md`](CRA-Cheat-Sheet.md) + [`CRA-Supply-Chain-Glossary.md`](CRA-Supply-Chain-Glossary.md);
+point AI users at [`SKILL.md`](SKILL.md) (copy to `.cursor/skills/wolfssl-cra-kit/`).
+
+---
+
+## Agent skill
+
+[`SKILL.md`](SKILL.md) is a customer deliverable (not internal-only) — see
+[`presentations/SLIDE-OUTLINE.md`](presentations/SLIDE-OUTLINE.md). Copy to
+`.cursor/skills/wolfssl-cra-kit/` for Cursor.
+
+---
+
+## FAQ
+
+**Do we need our own SBOM?**
+Yes — for the whole product you place on the EU market.
+
+**Is wolfSSL’s SBOM enough alone?**
+No — nest or reference it in your product SBOM (see `auditor-packet/`).
+
+**SPDX or CycloneDX?**
+wolfSSL ships both; use what your tools expect.
+
+**Do we need bomsh for CRA?**
+Usually no. SBOM alone covers most transparency asks.
+
+**What about CBOM?**
+Many RFQs ask for crypto inventory. Today: `wolfssl:build:*` properties in
+CycloneDX from your real config. Formal CycloneDX CBOM: **roadmap** — see
+[`ROADMAP.md`](ROADMAP.md).
+
+**FIPS builds?**
+The SBOM generator does not change validated module code; your FIPS boundary
+documentation remains separate.
+
+**What does this kit NOT cover?**
+Software transparency only. **Structural** CRA obligations are out of scope:
+appointing an EU Authorised Representative (Art. 18), product classification
+(Annex III/IV), conformity assessment + CE marking, full technical
+documentation per Annex VII, the support-period commitment, and importer /
+distributor obligations. See [`CRA-Compliance-Shortlist.md`](CRA-Compliance-Shortlist.md)
+"Beyond this kit" for the list. Engage CRA counsel or consultant — these are
+legal/structural decisions, not artefacts.
+
+**Are we outside the EU? (US / Asia / etc.)**
+Then you almost certainly need an **EU Authorised Representative** (Art. 18)
+appointed in writing **before** placing your product on the EU market. Either
+contract a third-party AR service or use an existing EU subsidiary. This is a
+long-lead item — start now, do not wait for September 2026.
+
+---
+
+## Further reading
+
+### OpenSSF guidance
+
+- [CRA Brief Guide for OSS Developers](https://best.openssf.org/CRA-Brief-Guide-for-OSS-Developers.html)
+ — When the CRA applies to open source projects and what obligations fall on
+ manufacturers integrating OSS components into commercial products.
+- [SBOM in Compliance](https://sbom-catalog.openssf.org/sbom-compliance.html)
+ — OpenSSF SBOM Everywhere SIG survey of the global regulatory landscape:
+ CRA, NTIA minimum elements, US EO 14028, Germany TR-03183, others.
+- [Getting Started with SBOMs](https://sbom-catalog.openssf.org/getting-started)
+ — OpenSSF guidance on SBOM generation approaches (build-integrated vs.
+ separate tooling), phase selection, publication. wolfSSL's `make sbom`
+ follows the build-integrated approach.
+- [OpenSSF CRA Policy Hub](https://openssf.org/category/policy/cra/)
+ — Ongoing OpenSSF coverage of CRA developments and community responses.
+- [SBOM Everywhere Wiki](https://sbom-catalog.openssf.org/) — tooling
+ catalog, working group resources, naming conventions, cross-format
+ guidance for SPDX and CycloneDX.
+
+### Standards
+
+- SPDX 2.3 specification:
+- CycloneDX 1.6 specification:
+- NTIA minimum elements for an SBOM:
+
+- RFC 9116 (`security.txt`):
+
+---
+
+## Support
+
+Questions about this kit: **support@wolfssl.com**
+
+Security reports: see [`security.txt`](https://www.wolfssl.com/.well-known/security.txt)
+and our [Coordinated Vulnerability Disclosure policy](https://www.wolfssl.com/.well-known/vulnerability-disclosure-policy.txt).
+Do **not** send vulnerability details to `support@` — use the security contact
+listed in `security.txt`.
diff --git a/cra-kit/ROADMAP.md b/cra-kit/ROADMAP.md
new file mode 100644
index 000000000..e6bed2e5d
--- /dev/null
+++ b/cra-kit/ROADMAP.md
@@ -0,0 +1,43 @@
+# Supply-chain artefacts — today vs roadmap
+
+Honest status for customer conversations. This is **not** a commitment schedule.
+
+| Capability | Status | What you do today |
+|--------------|--------|-------------------|
+| **SBOM** (SPDX 2.3 + CycloneDX 1.6) | **Available** | `make sbom` or `scripts/gen-sbom` |
+| **Config-accurate build properties** | **Available** | Read `wolfssl:build:*` in `.cdx.json` |
+| **Embedded source-merkle checksum** | **Available** | `gen-sbom` with `--srcs` (no `libwolfssl.a` required) |
+| **Commercial license in SBOM** | **Available** | `CRA_LICENSE_OVERRIDE=LicenseRef-wolfSSL-Commercial CRA_LICENSE_TEXT=/path/to/commercial-license.txt ./scripts/generate-wolfssl-sbom.sh` (a `LicenseRef-*` override requires the licence text; or use `make-commercial-sample.sh` to derive from pinned GPL samples) |
+| **Reproducible SBOM timestamps** | **Available** | `SOURCE_DATE_EPOCH` |
+| **OmniBOR / `make bomsh`** | **Available** | Linux **build host** only; optional for CRA |
+| **`pkg:github` PURL** | **Available** | Emitted natively by `gen-sbom`; resolves in OSV / GHSA / Snyk / Trivy without per-vendor mapping |
+| **Cryptographic-asset draft** (CycloneDX 1.6) | **Draft sample** | Hand-rolled `wolfssl-.cbom-draft.cdx.json` alongside SBOM (4–6 starter entries); upstream automation: roadmap |
+| **Formal CBOM** (`cryptographic-asset` profile, all primitives) | **Roadmap** | Use draft sample + `wolfssl:build:*` properties |
+| **VEX templates / automation** | **Roadmap** | Your scanner + wolfSSL [advisories](https://www.wolfssl.com/docs/security-vulnerabilities/) |
+| **CSAF 2.0 advisory feed** (`/.well-known/csaf/`) | **Roadmap** | Human-readable [advisories](https://www.wolfssl.com/docs/security-vulnerabilities/) today; CSAF 2.0 publication is on the roadmap (BSI's CRA reference architecture assumes CSAF) |
+| **Signed SBOMs** (in-toto / cosign / Sigstore) | **Roadmap** | Unsigned today; signing is conspicuous-by-absence for a crypto vendor and is on the roadmap |
+| **SBOM publication channel** | **Roadmap** | Per-release artefacts on GitHub Releases (proposed); `wolfssl.com/sbom/` (proposed); discovery via PURL is the long-term goal |
+| **Product SBOM tool** | **Out of scope** | Your BOM platform or manual merge |
+
+Upstream implementation detail: [wolfssl/doc/SBOM.md](https://github.com/wolfSSL/wolfssl/blob/master/doc/SBOM.md).
+
+---
+
+## Vulnerability-handling roadmap (Pillar 4)
+
+The kit's vulnerability-handling pillar is the only **ongoing** CRA obligation.
+Status of wolfSSL Inc.'s own filings is tracked here so customers can see what
+they're actually inheriting when they reference us as a component supplier.
+
+| Capability | Status | Notes |
+|------------|--------|-------|
+| `security.txt` (RFC 9116) | **Available** | [`/.well-known/security.txt`](https://www.wolfssl.com/.well-known/security.txt) |
+| Coordinated Vulnerability Disclosure policy | **Available** | [`/.well-known/vulnerability-disclosure-policy.txt`](https://www.wolfssl.com/.well-known/vulnerability-disclosure-policy.txt) |
+| CNA status | **Available** | wolfSSL is a CVE Numbering Authority |
+| Public SLA (24h ack / 72h triage) | **Pending leadership approval** | Will be added to CVD policy once approved |
+| 24h ENISA reporting (Art. 14) runbook | **In progress** | Owner assignment pending; on-call rotation TBD |
+| EU Authorised Representative (Art. 18) | **In progress** | wolfSSL Inc. is US-established; AR appointment underway |
+| CSAF 2.0 advisory feed | **Roadmap** | See above |
+
+See [`wolfssl-inc-auditor-packet/`](wolfssl-inc-auditor-packet/) for the manufacturer-side
+filings wolfSSL Inc. ships under CRA.
diff --git a/cra-kit/SKILL.md b/cra-kit/SKILL.md
new file mode 100644
index 000000000..06bdba6f0
--- /dev/null
+++ b/cra-kit/SKILL.md
@@ -0,0 +1,136 @@
+---
+name: wolfssl-cra-kit
+description: >-
+ wolfSSL CRA Kit playbook: who-provides-what cheat sheet, full glossary,
+ auditor-packet sample, generate/validate/refresh scripts for product SBOM +
+ nested wolfSSL SBOM, bomsh Linux-only, vulnerability handling (CVD policy +
+ security.txt), and pointers to structural CRA obligations (EU Authorised
+ Representative Art. 18, Annex III/IV product classification, conformity
+ assessment, CE mark) that this kit does NOT cover. Use with Cursor, Claude,
+ or any agent for EU CRA software transparency (make sbom, SPDX, CycloneDX).
+---
+
+# wolfSSL CRA Kit — AI playbook
+
+Use this file with **Cursor**, **Claude Code**, **Copilot**, or any coding agent
+to drive the kit's scripts and narrative without re-explaining CRA terms.
+
+**Not legal advice.** Never claim “CRA compliant.” **Product SBOM** is always yours;
+wolfSSL ships **component** evidence only.
+
+wolfSSL Inc. is itself a manufacturer under CRA for libraries it places on the
+EU market — see our [`security.txt`](https://www.wolfssl.com/.well-known/security.txt),
+[CVD policy](https://www.wolfssl.com/.well-known/vulnerability-disclosure-policy.txt),
+and the [`wolfssl-inc-auditor-packet/`](wolfssl-inc-auditor-packet/) (manufacturer-side
+filings: classification, conformity assessment, declaration of conformity template,
+EU AR status, support-period, vulnerability-handling process) as reference templates
+for the customer's own CRA artefacts.
+
+---
+
+## What you leave with (matches the presentation)
+
+| Deliverable | File / folder |
+|-------------|----------------|
+| **CRA shortlist** (4 pillars: components, secure boot, data in transfer, vulnerability handling) | [CRA-Compliance-Shortlist.md](CRA-Compliance-Shortlist.md) |
+| **Who provides what** (you vs wolfSSL) | [CRA-Cheat-Sheet.md](CRA-Cheat-Sheet.md) |
+| **Full glossary** (SBOM, CBOM, bomsh, …) | [CRA-Supply-Chain-Glossary.md](CRA-Supply-Chain-Glossary.md) |
+| **Worked example (customer-side)** | [auditor-packet/](auditor-packet/) — fictional Acme Connect Gateway |
+| **Manufacturer-side filings (wolfSSL Inc.)** | [wolfssl-inc-auditor-packet/](wolfssl-inc-auditor-packet/) — classification, DoC template, EU AR status, etc. |
+| **Scripts + agent checklist** | This SKILL — below |
+
+---
+
+## Install (Cursor)
+
+```bash
+mkdir -p .cursor/skills/wolfssl-cra-kit
+cp wolfssl-examples/cra-kit/SKILL.md .cursor/skills/wolfssl-cra-kit/SKILL.md
+```
+
+Point the agent at `wolfssl-examples/cra-kit/` (clone or monorepo path).
+Set `WOLFSSL_DIR` to your wolfSSL source tree when regenerating SBOMs.
+
+**Other tools:** paste this file into the system prompt, or `@`-mention the kit README.
+
+---
+
+## Agent checklist
+
+**Before starting**, confirm with the customer (do not assume):
+
+- Where is the customer **established** (US / EU / other)? If outside the EU, flag the **EU Authorised Representative** requirement (Art. 18) — long-lead item, start now.
+- What is the **product classification** under Annex III/IV? Self-declared (default class) or Notified Body required (important / critical)? Flag if unknown — Notified Body queues are long.
+- Is the customer's CRA work **on track for 11 Sep 2026** (Art. 14 reporting wave) and **11 Dec 2027** (full applicability)? If structural items are open, SBOM work alone won't make them ready.
+
+Then run the SBOM execution checklist:
+
+1. **Component SBOM**
+ - `cd wolfssl-examples/cra-kit`
+ - `WOLFSSL_DIR=/path/to/wolfssl ./scripts/generate-wolfssl-sbom.sh`
+ - Or in wolfSSL: `make sbom` (needs `pip install spdx-tools`)
+
+2. **Product SBOM**
+ - Open `auditor-packet/product-acme-connect-gateway.{cdx,spdx}.json`
+ - Mirror how wolfSSL is referenced/nested for **your** product name and version
+ - Embedded builds: `CRA_SBOM_MODE=embedded ./scripts/generate-embedded-sbom.sh` + your `user_settings.h`
+
+3. **Validate without rebuilding wolfSSL**
+ - `./scripts/validate.sh`
+
+4. **Refresh pinned samples** (maintainers / after wolfSSL release)
+ - `WOLFSSL_DIR=... ./scripts/refresh-samples.sh`
+
+5. **Optional — bomsh**
+ - Only if contract/auditor requires; **Linux build host** only (`make bomsh` in wolfSSL tree)
+ - Not in the sample auditor packet by default
+
+---
+
+## Scope rules (tell the agent)
+
+- **Product SBOM** = customer owns entire shipped product.
+- **Component SBOM** = wolfSSL only; nest via SPDX `externalDocumentRefs` or CycloneDX `bom` ref.
+- **CBOM** = partial today (`wolfssl:build:*`); do not claim full CycloneDX CBOM profile.
+- **VEX** = customer + scanner; wolfSSL provides advisories, not VEX files.
+- **bomsh** = optional provenance; not required for most CRA transparency asks.
+- **Vulnerability handling (Art. 13/14)** = customer publishes their own CVD policy + `security.txt`, runs on-call, files 24h ENISA reports for their product; wolfSSL provides reference templates and handles ENISA reporting only for libraries placed on the EU market by wolfSSL Inc.
+- **Structural CRA (out of scope for this kit)** = EU Authorised Representative (Art. 18 — required if customer is outside the EU), Annex III/IV classification (determines self-cert vs Notified Body), conformity assessment + CE mark (Art. 32, 30), technical documentation (Annex VII), support-period commitment (Art. 13(8), 5+ years default). When a customer asks "are we ready?", surface these — SBOMs alone are not enough. Recommend engaging CRA counsel or consultant.
+
+---
+
+## Scripts
+
+| Script | Purpose |
+|--------|---------|
+| `scripts/validate.sh` | JSON + SPDX checksum on sample packet |
+| `scripts/refresh-samples.sh` | `make sbom` + patch product SPDX checksum |
+| `scripts/generate-wolfssl-sbom.sh` | `CRA_SBOM_MODE=autotools\|embedded` |
+| `scripts/generate-embedded-sbom.sh` | → `auditor-packet/wolfssl-component-embedded/` |
+
+Embedded demo: `user_settings.h` + `WOLFSSL_USER_SETTINGS`.
+
+---
+
+## Sample paths
+
+- Product: `auditor-packet/product-acme-connect-gateway.{spdx,cdx}.json`
+- Component: `auditor-packet/wolfssl-component/wolfssl-5.9.1.*`
+- Embedded (optional): `auditor-packet/wolfssl-component-embedded/`
+
+---
+
+## Example prompts
+
+- “Walk me through nesting wolfSSL’s CycloneDX SBOM into our product SBOM using `auditor-packet/` as a template.”
+- “Run `validate.sh` and fix any checksum mismatch after I regenerated the component SBOM.”
+- “Generate an embedded SBOM with our `user_settings.h` and list which algorithms appear in `wolfssl:build:*`.”
+- “Do we need bomsh for CRA? When would we run it on Linux CI only?”
+- “We're a US company shipping into the EU — what CRA structural items do we need beyond the SBOM?”
+- “What's the difference between Annex III and Annex IV classification, and how does it affect our conformity assessment?”
+
+---
+
+## Upstream docs (wolfSSL repo)
+
+- [doc/SBOM.md](https://github.com/wolfSSL/wolfssl/blob/master/doc/SBOM.md) — SBOM/Bomsh feature reference (flags, formats, commercial license override, OmniBOR)
diff --git a/cra-kit/SRCS-FILE-HOWTO.md b/cra-kit/SRCS-FILE-HOWTO.md
new file mode 100644
index 000000000..d8d5d10f0
--- /dev/null
+++ b/cra-kit/SRCS-FILE-HOWTO.md
@@ -0,0 +1,462 @@
+# Generating a wolfSSL source list for `CRA_SBOM_SRCS_FILE`
+
+The embedded SBOM path hashes every wolfSSL `.c` file you compile — not the
+library binary. That list comes from your build system. This guide shows how
+to extract it for the most common embedded build systems.
+
+Once you have the file, pass it to the kit script:
+
+```sh
+CRA_SBOM_MODE=embedded \
+CRA_SBOM_SRCS_FILE=/path/to/wolfssl-srcs.txt \
+CRA_SBOM_SRCS_ONLY_FROM_FILE=true \
+WOLFSSL_DIR=/path/to/wolfssl \
+./scripts/generate-wolfssl-sbom.sh
+```
+
+`CRA_SBOM_SRCS_ONLY_FROM_FILE=true` suppresses the demo watermark and uses
+only the paths in your file. Omit it to merge your list with the kit's
+built-in 9-file demo list (keeps the `wolfssl:sbom:demo=true` watermark).
+
+Manual extraction is now optional for most build systems. Set the right
+environment variable for your build system and the kit script extracts the
+source list automatically — no `CRA_SBOM_SRCS_FILE` needed. See the
+relevant section below for the variable to set and any tool requirements.
+
+---
+
+## Custom Makefile
+
+### Option A — add a `print-wolfssl-srcs` target (recommended)
+
+Add this to your `Makefile`. Replace `$(WOLFSSL_SRCS)` with however your
+project names the wolfSSL source variable:
+
+```makefile
+.PHONY: print-wolfssl-srcs
+print-wolfssl-srcs:
+ @printf '%s\n' $(WOLFSSL_SRCS)
+```
+
+Then extract:
+
+```sh
+make print-wolfssl-srcs > wolfssl-srcs.txt
+```
+
+This is immune to recursive makes, response files, and multi-rule compilation.
+
+### Option B — `make -n` dry-run (when you cannot modify the Makefile)
+
+```sh
+make -n 2>/dev/null \
+ | grep -oE '[^ ]+wolfssl[^ ]+\.c' \
+ | sort -u \
+ > wolfssl-srcs.txt
+```
+
+`make -n` prints compiler command lines without running them, so a missing
+cross-compiler is not a problem. The grep pattern matches any token that
+contains `wolfssl` and ends in `.c`.
+
+**Limitation**: fails if sources are passed via response files (`@srcs.rsp`)
+or compiled through recursive `$(MAKE) -C` sub-invocations that do not echo
+the final compile lines. Use Option A in those cases.
+
+### Automatic extraction
+
+Set `CRA_SBOM_MAKEFILE_DIR` to the directory containing your project Makefile, then run the
+kit script with no `CRA_SBOM_SRCS_FILE`:
+
+```sh
+CRA_SBOM_MODE=embedded \
+CRA_SBOM_MAKEFILE_DIR=/path/to/your/project \
+WOLFSSL_DIR=/path/to/wolfssl \
+./scripts/generate-wolfssl-sbom.sh
+```
+
+The script tries the `print-wolfssl-srcs` target first; if that target does not exist, it
+falls back to `make -n` dry-run. Either way the extracted list is used automatically —
+no manual `CRA_SBOM_SRCS_FILE` needed.
+
+---
+
+## CMake — `compile_commands.json`
+
+Enable compile commands at configure time:
+
+```sh
+cmake -B build /path/to/your/project \
+ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
+cmake --build build
+```
+
+Extract wolfSSL library sources:
+
+```sh
+WOLFSSL_DIR=/path/to/wolfssl
+jq -r '.[].file' build/compile_commands.json \
+ | grep "^${WOLFSSL_DIR}/" \
+ | grep -E "/(wolfcrypt/src|src)/[^/]+\.c$" \
+ | sort -u \
+ > wolfssl-srcs.txt
+```
+
+The `grep -E` step restricts to `src/` and `wolfcrypt/src/` — without it,
+`examples/` and `tests/` files are included, which inflates the SBOM with
+files you did not ship.
+
+**Requirements**: `jq` (`apt install jq` / `brew install jq`).
+
+### Automatic extraction
+
+Set `WOLFSSL_BUILD_DIR` to your cmake build directory. The kit script detects
+`compile_commands.json` automatically and extracts wolfssl sources without manual steps:
+
+```sh
+CRA_SBOM_MODE=embedded \
+WOLFSSL_BUILD_DIR=/path/to/build \
+WOLFSSL_DIR=/path/to/wolfssl \
+./scripts/generate-wolfssl-sbom.sh
+```
+
+Requires `jq` on the host.
+
+---
+
+## Zephyr RTOS
+
+Zephyr uses CMake internally. Add `-DCMAKE_EXPORT_COMPILE_COMMANDS=ON` at
+build time to get `compile_commands.json` in your build directory:
+
+```sh
+west build -b -- -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
+ -DZEPHYR_EXTRA_MODULES=/path/to/wolfssl
+```
+
+`compile_commands.json` is written by CMake at configure time — it exists
+even if the compilation itself fails (e.g., missing cross-compiler).
+
+Extract wolfSSL library sources:
+
+```sh
+WOLFSSL_DIR=/path/to/wolfssl
+jq -r '.[].file' build/compile_commands.json \
+ | grep "^${WOLFSSL_DIR}/" \
+ | grep -E "/(wolfcrypt/src|src)/[^/]+\.c$" \
+ | sort -u \
+ > wolfssl-srcs.txt
+```
+
+All paths in `compile_commands.json` are absolute. The `WOLFSSL_DIR` filter
+matches entries from the wolfssl module directly; no path translation needed.
+A typical wolfssl Zephyr build produces around 89 library sources
+(`wolfcrypt/src/` + `src/`).
+
+**Requirements**: `jq` must be installed on the host running the extraction
+(not the target board).
+
+### Automatic extraction
+
+Same as CMake — set `WOLFSSL_BUILD_DIR` to the `west build` output directory:
+
+```sh
+CRA_SBOM_MODE=embedded \
+WOLFSSL_BUILD_DIR=/path/to/wolfssl-app/build \
+WOLFSSL_DIR=/path/to/wolfssl \
+./scripts/generate-wolfssl-sbom.sh
+```
+
+Requires `jq`. The `compile_commands.json` must have been generated at cmake configure time
+(pass `-DCMAKE_EXPORT_COMPILE_COMMANDS=ON` to `west build`).
+
+---
+
+## ESP-IDF
+
+ESP-IDF uses CMake and writes `compile_commands.json` to the `build/`
+subdirectory automatically. Build your project normally:
+
+```sh
+idf.py build
+```
+
+When wolfssl is added as a **managed component** (via `idf_component.yml`
+declaring `wolfssl/wolfssl`), the ESP-IDF component manager downloads it into
+`managed_components/wolfssl__wolfssl/` inside your project. The directory name
+is `wolfssl__wolfssl` (registry namespace and name joined with double
+underscore).
+
+Extract the wolfssl library sources:
+
+```sh
+PROJECT_DIR=/path/to/your/esp-idf-project
+jq -r '.[].file' "${PROJECT_DIR}/build/compile_commands.json" \
+ | grep "^${PROJECT_DIR}/managed_components/wolfssl__wolfssl/" \
+ | grep -E "/(wolfcrypt/src|src)/[^/]+\.c$" \
+ | sort -u \
+ > wolfssl-srcs.txt
+```
+
+All paths in `compile_commands.json` are absolute. The
+`grep -E "/(wolfcrypt/src|src)/[^/]+\.c$"` step excludes build-generated
+files (e.g., `build/project_elf_src_esp32.c`) that also appear under the
+project directory.
+
+If wolfssl is added as a **local component** (placed manually in
+`components/wolfssl/` rather than managed), replace `managed_components/wolfssl__wolfssl`
+with `components/wolfssl` in the filter.
+
+### Automatic extraction
+
+Set `WOLFSSL_BUILD_DIR` to your project's `build/` directory:
+
+```sh
+CRA_SBOM_MODE=embedded \
+WOLFSSL_BUILD_DIR=/path/to/esp-idf-project/build \
+WOLFSSL_DIR=/path/to/wolfssl \
+./scripts/generate-wolfssl-sbom.sh
+```
+
+The script detects the ESP-IDF managed-component layout (`managed_components/wolfssl__wolfssl/`)
+automatically when `WOLFSSL_DIR` sources are not found under `WOLFSSL_BUILD_DIR` directly.
+Requires `jq`.
+
+---
+
+## Keil MDK / uVision (`.uvprojx`)
+
+> **Note**: Keil MDK is Windows-only and requires a license. This section
+> was verified against real wolfSSL Keil project files from
+> `wolfssl/IDE/MDK5-ARM/`. The CMSIS Pack note below reflects actual
+> wolfSSL project structure.
+
+Keil projects integrate wolfssl in one of two ways — the extraction method
+differs between them.
+
+### Option A — wolfSSL CMSIS Pack (modern, recommended)
+
+The official wolfSSL Keil projects (e.g., `wolfSSL-Lib.uvprojx`) use the
+**CMSIS Pack RTE (Run-Time Environment)**. In this mode the wolfssl sources
+are **not listed in the `.uvprojx` file** — they are resolved at build time
+from the installed wolfSSL CMSIS pack. The project XML records which pack
+components are selected, not which `.c` files they compile.
+
+To find the source list, locate the installed pack descriptor:
+
+```
+# Windows
+%LOCALAPPDATA%\Arm\Packs\wolfSSL\wolfSSL\\wolfSSL.pdsc
+
+# Linux / macOS (Keil Studio / CMSIS-Toolbox)
+~/.arm/Packs/wolfSSL/wolfSSL//wolfSSL.pdsc
+```
+
+The `.pdsc` file is XML. Extract the `.c` sources for your selected
+component group (e.g., `wolfCrypt/CORE`):
+
+```python
+#!/usr/bin/env python3
+"""Extract .c sources for a wolfSSL CMSIS Pack component from its .pdsc."""
+import sys, xml.etree.ElementTree as ET
+
+pdsc = ET.parse(sys.argv[1])
+cgroup = sys.argv[2] if len(sys.argv) > 2 else '' # e.g. "wolfCrypt"
+
+for comp in pdsc.findall('.//component'):
+ if cgroup and comp.get('Cgroup', '') != cgroup:
+ continue
+ for f in comp.findall('.//file[@category="source"]'):
+ name = f.get('name', '')
+ if name.lower().endswith('.c'):
+ print(name.replace('\\', '/'))
+```
+
+Usage:
+
+```sh
+python3 extract-pdsc-srcs.py wolfSSL.pdsc wolfCrypt > wolfssl-srcs.txt
+```
+
+Paths in the `.pdsc` are relative to the pack root directory. Prefix with
+the pack install path to make them absolute before passing to `gen-sbom`.
+
+### Option B — wolfssl sources listed directly in the project
+
+Older or custom projects may list wolfssl `.c` files explicitly as
+`` entries under ``. The Python script
+from the CMSIS approach will produce no output for these — use this
+instead:
+
+```python
+#!/usr/bin/env python3
+"""Extract explicit .c FilePath entries from a Keil .uvprojx (non-pack)."""
+import sys, xml.etree.ElementTree as ET
+
+proj = ET.parse(sys.argv[1])
+paths = set()
+for file_elem in proj.findall('.//File'):
+ fp = file_elem.find('FilePath')
+ ft = file_elem.find('FileType')
+ if fp is None or not fp.text:
+ continue
+ ftype = int(ft.text) if ft is not None and ft.text else 0
+ if ftype == 1 or fp.text.lower().endswith('.c'):
+ paths.add(fp.text.replace('\\', '/'))
+
+for p in sorted(paths):
+ print(p)
+```
+
+Usage:
+
+```sh
+python3 extract-keil-srcs.py MyProject.uvprojx > wolfssl-srcs.txt
+```
+
+Paths are relative to the `.uvprojx` file. Resolve to absolute before
+passing to `gen-sbom`.
+
+**How to tell which option you need**: open the `.uvprojx` in a text editor
+and search for ``. If present and `` is
+inside it, you are using the CMSIS Pack (Option A). If wolfssl `.c` files
+appear under `` directly, use Option B.
+
+### Automatic extraction
+
+Set `CRA_SBOM_KEIL_PROJECT` to the path of your `.uvprojx` file:
+
+```sh
+CRA_SBOM_MODE=embedded \
+CRA_SBOM_KEIL_PROJECT=/path/to/MyProject.uvprojx \
+WOLFSSL_DIR=/path/to/wolfssl \
+./scripts/generate-wolfssl-sbom.sh
+```
+
+The script parses the project file and chooses Option A (CMSIS Pack) or Option B
+(explicit FilePath) automatically based on whether a `` is
+present in the RTE block. For CMSIS Pack mode the installed `.pdsc` must be present at
+`~/.arm/Packs/wolfSSL/wolfSSL//wolfSSL.pdsc`. Requires `python3`.
+
+---
+
+## IAR Embedded Workbench (`.ewp`)
+
+> **Note**: IAR EW is Windows-only and requires a license. This section
+> was verified against real wolfSSL IAR project files from
+> `wolfssl/IDE/IAR-EWARM/`.
+
+IAR stores source files as `` elements with a `$PROJ_DIR$`
+path prefix (IAR's built-in variable for the directory containing the `.ewp`
+file) and Windows backslash separators.
+
+**Important**: wolfssl sources live under `wolfcrypt/src/` and `src/` — neither
+path segment contains the string `"wolfssl"`. Do **not** filter by `"wolfssl"`
+substring; instead filter by path depth or accept all `.c` files from the
+project.
+
+```python
+#!/usr/bin/env python3
+r"""
+Extract .c source paths from an IAR EWARM .ewp project file.
+
+Paths are emitted as absolute paths (resolves $PROJ_DIR$ automatically).
+Pass --raw to keep the original $PROJ_DIR$ prefix instead.
+
+Usage:
+ python3 extract-iar-srcs.py MyProject.ewp [--raw] > wolfssl-srcs.txt
+"""
+import sys, os, argparse, xml.etree.ElementTree as ET
+
+
+def is_excluded(file_elem):
+ """True if the file is excluded from at least one build configuration."""
+ return file_elem.find('excluded') is not None
+
+
+def main():
+ ap = argparse.ArgumentParser()
+ ap.add_argument('ewp')
+ ap.add_argument('--raw', action='store_true',
+ help='Keep $PROJ_DIR$ prefix instead of resolving')
+ args = ap.parse_args()
+
+ proj_dir = os.path.dirname(os.path.abspath(args.ewp))
+ proj = ET.parse(args.ewp)
+ paths = set()
+
+ for file_elem in proj.findall('.//file'):
+ if is_excluded(file_elem):
+ continue
+ name = file_elem.find('name')
+ if name is None or not name.text:
+ continue
+ raw = name.text
+ if not raw.lower().endswith('.c'):
+ continue
+ if args.raw:
+ paths.add(raw.replace('\\', '/'))
+ else:
+ resolved = raw.replace('$PROJ_DIR$', proj_dir)
+ paths.add(os.path.normpath(resolved.replace('\\', '/')))
+
+ for p in sorted(paths):
+ print(p)
+
+
+if __name__ == '__main__':
+ main()
+```
+
+Usage:
+
+```sh
+# Absolute paths (ready for gen-sbom)
+python3 extract-iar-srcs.py wolfSSL-Lib.ewp > wolfssl-srcs.txt
+
+# Keep $PROJ_DIR$ prefix (for inspection)
+python3 extract-iar-srcs.py wolfSSL-Lib.ewp --raw > wolfssl-srcs.txt
+```
+
+This produces 65 sources (56 under `wolfcrypt/src/`, 9 under `src/`) for the
+standard `wolfSSL-Lib.ewp` project.
+
+**Caveats**:
+- The script skips files that appear in `` blocks (per-configuration
+ exclusions). If you need sources for a specific configuration only, check
+ `` matches against your target config name.
+- Application-specific `.c` files (test runners, benchmark harness) will also
+ appear; remove them from `wolfssl-srcs.txt` manually if they are not part
+ of your shipped wolfssl build.
+
+### Automatic extraction
+
+Set `CRA_SBOM_IAR_PROJECT` to the path of your `.ewp` file:
+
+```sh
+CRA_SBOM_MODE=embedded \
+CRA_SBOM_IAR_PROJECT=/path/to/wolfSSL-Lib.ewp \
+WOLFSSL_DIR=/path/to/wolfssl \
+./scripts/generate-wolfssl-sbom.sh
+```
+
+The script resolves `$PROJ_DIR$` automatically. Requires `python3`.
+
+---
+
+## Verifying the output
+
+After generating `wolfssl-srcs.txt`, sanity-check it:
+
+```sh
+# Count should match your mental model of what you compile
+wc -l wolfssl-srcs.txt
+
+# All paths should exist on disk
+while IFS= read -r f; do
+ [ -f "$f" ] || echo "MISSING: $f"
+done < wolfssl-srcs.txt
+
+# No duplicates (gen-sbom deduplicates, but worth checking)
+sort wolfssl-srcs.txt | uniq -d
+```
diff --git a/cra-kit/VERSION b/cra-kit/VERSION
new file mode 100644
index 000000000..b64a5e97f
--- /dev/null
+++ b/cra-kit/VERSION
@@ -0,0 +1,3 @@
+# Pinned wolfSSL SBOM samples under auditor-packet/wolfssl-component/
+# Regenerate with: ./scripts/generate-wolfssl-sbom.sh && ./scripts/refresh-samples.sh
+WOLFSSL_VERSION=5.9.1
diff --git a/cra-kit/auditor-packet/00-INDEX.md b/cra-kit/auditor-packet/00-INDEX.md
new file mode 100644
index 000000000..96ef38605
--- /dev/null
+++ b/cra-kit/auditor-packet/00-INDEX.md
@@ -0,0 +1,37 @@
+# Auditor packet index (fictional Acme Connect Gateway)
+
+Example of what a **manufacturer** might bundle alongside wolfSSL component
+artefacts. **Not legal advice** — adapt to your product and counsel.
+
+| File | Role |
+|------|------|
+| `product-acme-connect-gateway.cdx.json` | **Your** product SBOM (CycloneDX) — references wolfSSL |
+| `product-acme-connect-gateway.spdx.json` | **Your** product SBOM (SPDX) — `externalDocumentRefs` to wolfSSL |
+| `wolfssl-component/wolfssl-5.9.1.cdx.json` | wolfSSL component SBOM — **autotools / make sbom** sample (GPL) |
+| `wolfssl-component/wolfssl-5.9.1.spdx.json` | wolfSSL component SBOM (SPDX, GPL) |
+| `wolfssl-component/wolfssl-5.9.1.commercial.cdx.json` | wolfSSL component SBOM with commercial license override |
+| `wolfssl-component/wolfssl-5.9.1.commercial.spdx.json` | wolfSSL component SBOM (SPDX) with commercial license override |
+| `wolfssl-component/wolfssl-5.9.1.cbom-draft.cdx.json` | Hand-rolled cryptographic-asset draft (CycloneDX 1.6 CBOM profile) |
+| `wolfssl-component/SAMPLE-PROVENANCE.md` | How the pinned autotools samples were produced |
+| `wolfssl-component/omnibor.wolfssl-5.9.1.spdx.json.sample` | Truncated OmniBOR / bomsh provenance sample |
+| `wolfssl-component-embedded/` | Optional embedded `gen-sbom` output (generated locally; gitignored) |
+| `wolfssl-component/README-bomsh.md` | Optional OmniBOR — not included by default |
+
+Also provide: your vulnerability process, release notes, and the upstream
+wolfSSL disclosure context — [`security.txt`](https://www.wolfssl.com/.well-known/security.txt),
+[CVD policy](https://www.wolfssl.com/.well-known/vulnerability-disclosure-policy.txt),
+and [advisories](https://www.wolfssl.com/docs/security-vulnerabilities/).
+
+**This packet shows the software-transparency artefacts only.** A complete
+CRA conformity packet for a real product also includes:
+
+- Declaration of conformity (Art. 28)
+- Technical documentation per Annex VII (risk assessment, design info, support-period commitment, vulnerability handling process)
+- Proof of conformity assessment (self-declared per Art. 32 Module A, or Notified Body certificate per product class)
+- Identity of the EU Authorised Representative (Art. 18) if the manufacturer is established outside the EU
+- CE marking declaration
+
+See [`../CRA-Compliance-Shortlist.md`](../CRA-Compliance-Shortlist.md)
+"Beyond this kit" for the structural obligations not covered by SBOMs.
+
+**Regenerate autotools samples + product checksum:** `./scripts/refresh-samples.sh`
diff --git a/cra-kit/auditor-packet/README.md b/cra-kit/auditor-packet/README.md
new file mode 100644
index 000000000..fd3e8e261
--- /dev/null
+++ b/cra-kit/auditor-packet/README.md
@@ -0,0 +1,9 @@
+# Sample auditor packet
+
+This directory is a **teaching example** only. **Acme Industries** and
+**acme-connect-gateway** are fictional.
+
+It shows how a **product SBOM** references wolfSSL’s **component SBOM** in
+both CycloneDX and SPDX forms.
+
+See [`00-INDEX.md`](00-INDEX.md) for the file list.
diff --git a/cra-kit/auditor-packet/product-acme-connect-gateway.cdx.json b/cra-kit/auditor-packet/product-acme-connect-gateway.cdx.json
new file mode 100644
index 000000000..3264ae1db
--- /dev/null
+++ b/cra-kit/auditor-packet/product-acme-connect-gateway.cdx.json
@@ -0,0 +1,63 @@
+{
+ "$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json",
+ "bomFormat": "CycloneDX",
+ "specVersion": "1.6",
+ "serialNumber": "urn:uuid:c7a4f9b2-8e1d-4a3f-b5c6-d2e8f4a7b9c1",
+ "version": 1,
+ "metadata": {
+ "timestamp": "2026-05-18T12:00:00Z",
+ "component": {
+ "type": "firmware",
+ "bom-ref": "acme-connect-gateway-1.0.0",
+ "name": "acme-connect-gateway",
+ "version": "1.0.0",
+ "supplier": {
+ "name": "Acme Industries (fictional example)"
+ }
+ }
+ },
+ "components": [
+ {
+ "type": "library",
+ "bom-ref": "wolfssl-5.9.1",
+ "name": "wolfssl",
+ "version": "5.9.1",
+ "supplier": {
+ "name": "wolfSSL Inc."
+ },
+ "purl": "pkg:github/wolfSSL/wolfssl@v5.9.1",
+ "cpe": "cpe:2.3:a:wolfssl:wolfssl:5.9.1:*:*:*:*:*:*:*",
+ "externalReferences": [
+ {
+ "type": "bom",
+ "url": "file:./wolfssl-component/wolfssl-5.9.1.cdx.json",
+ "comment": "Component SBOM from wolfSSL; regenerate with scripts/generate-wolfssl-sbom.sh",
+ "hashes": [
+ {
+ "alg": "SHA-256",
+ "content": "bc8c6b9f5fbe829edb594dc74bcb95a202ca1b402ab1dca60f858aa9fe2ec6e3"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "dependencies": [
+ {
+ "ref": "acme-connect-gateway-1.0.0",
+ "dependsOn": [
+ "wolfssl-5.9.1"
+ ]
+ },
+ {
+ "ref": "wolfssl-5.9.1",
+ "dependsOn": []
+ }
+ ],
+ "properties": [
+ {
+ "name": "wolfssl:sample:component-deps",
+ "value": "wolfSSL has no transitive runtime library dependencies; the host CRT is the only build-time requirement and is excluded per NTIA SBOM practice."
+ }
+ ]
+}
diff --git a/cra-kit/auditor-packet/product-acme-connect-gateway.spdx.json b/cra-kit/auditor-packet/product-acme-connect-gateway.spdx.json
new file mode 100644
index 000000000..150728ae3
--- /dev/null
+++ b/cra-kit/auditor-packet/product-acme-connect-gateway.spdx.json
@@ -0,0 +1,46 @@
+{
+ "spdxVersion": "SPDX-2.3",
+ "dataLicense": "CC0-1.0",
+ "SPDXID": "SPDXRef-DOCUMENT",
+ "name": "acme-connect-gateway-1.0.0",
+ "documentNamespace": "urn:uuid:8d3c2f9e-6b4a-4d7c-9f1e-a5b8c0d2e4f6",
+ "creationInfo": {
+ "creators": [
+ "Organization: Acme Industries (fictional example)"
+ ],
+ "created": "2026-05-18T12:00:00Z"
+ },
+ "externalDocumentRefs": [
+ {
+ "externalDocumentId": "DocumentRef-wolfssl",
+ "spdxDocument": "file:./wolfssl-component/wolfssl-5.9.1.spdx.json",
+ "checksum": {
+ "algorithm": "SHA256",
+ "checksumValue": "a60bda42e4e0c874f21abaed7b34e72ac6ea329662fbac33f8487608753042f2"
+ }
+ }
+ ],
+ "packages": [
+ {
+ "SPDXID": "SPDXRef-Package-Product",
+ "name": "acme-connect-gateway",
+ "versionInfo": "1.0.0",
+ "supplier": "Organization: Acme Industries (fictional example)",
+ "downloadLocation": "NOASSERTION",
+ "filesAnalyzed": false
+ }
+ ],
+ "relationships": [
+ {
+ "spdxElementId": "SPDXRef-DOCUMENT",
+ "relatedSpdxElement": "SPDXRef-Package-Product",
+ "relationshipType": "DESCRIBES"
+ },
+ {
+ "spdxElementId": "SPDXRef-Package-Product",
+ "relatedSpdxElement": "DocumentRef-wolfssl:SPDXRef-Package-wolfssl",
+ "relationshipType": "STATIC_LINK",
+ "comment": "Fictional embedded firmware links wolfSSL statically; use DYNAMIC_LINK for .so"
+ }
+ ]
+}
diff --git a/cra-kit/auditor-packet/wolfssl-component-embedded/.gitignore b/cra-kit/auditor-packet/wolfssl-component-embedded/.gitignore
new file mode 100644
index 000000000..30803144e
--- /dev/null
+++ b/cra-kit/auditor-packet/wolfssl-component-embedded/.gitignore
@@ -0,0 +1,3 @@
+wolfssl-*.cdx.json
+wolfssl-*.spdx.json
+wolfssl-*.spdx
diff --git a/cra-kit/auditor-packet/wolfssl-component-embedded/README.md b/cra-kit/auditor-packet/wolfssl-component-embedded/README.md
new file mode 100644
index 000000000..1ac918512
--- /dev/null
+++ b/cra-kit/auditor-packet/wolfssl-component-embedded/README.md
@@ -0,0 +1,23 @@
+# Embedded component SBOM (optional sample)
+
+This directory's `wolfssl-*.{cdx,spdx}.json` outputs are **gitignored** — generate
+them locally with the embedded path. Only this README is committed.
+
+```sh
+export WOLFSSL_DIR=../../wolfssl # wolfSSL tree with scripts/gen-sbom
+python3 -m pip install pcpp # same python3 as in your PATH (see README)
+./scripts/generate-embedded-sbom.sh
+```
+
+If pcpp is not on your `python3`, the script falls back to `cc -dM -E` and `--options-h`
+(no extra install). For cross builds, set `CC=arm-none-eabi-gcc` (or your target
+compiler) so the fallback reflects target macros, not the host's.
+
+Uses [`../../user_settings.h`](../../user_settings.h) via `WOLFSSL_USER_SETTINGS` and a
+**demo** `--srcs` list (see `scripts/generate-wolfssl-sbom.sh`). Production firmware
+must pass **your** `user_settings.h` and **every** wolfSSL `.c` file you compile.
+Embedded outputs are watermarked `wolfssl:sbom:demo=true` so an auditor can tell at
+a glance that they came from the kit's demo `--srcs` list and not a real build.
+
+Outputs differ from [`../wolfssl-component/`](../wolfssl-component/) (autotools /
+`make sbom`). Compare `wolfssl:sbom:hash-kind` in the CycloneDX files.
diff --git a/cra-kit/auditor-packet/wolfssl-component/README-bomsh.md b/cra-kit/auditor-packet/wolfssl-component/README-bomsh.md
new file mode 100644
index 000000000..3c451b09b
--- /dev/null
+++ b/cra-kit/auditor-packet/wolfssl-component/README-bomsh.md
@@ -0,0 +1,26 @@
+# Optional: OmniBOR / bomsh bundle
+
+`make bomsh` is **not** included in this sample packet. Most CRA transparency
+workflows need the SBOM files only.
+
+When an auditor or contract requires **build provenance**:
+
+1. On a **Linux** build host (or Linux CI / WSL2 / container), in your wolfSSL tree:
+ ```sh
+ ./configure && make sbom && make bomsh
+ ```
+2. Add to your release bundle:
+ - `omnibor/` directory (Merkle DAG of build inputs/outputs)
+ - `omnibor.wolfssl-.spdx.json` (file-level provenance)
+
+**Sample shape:** see [`omnibor.wolfssl-5.9.1.spdx.json.sample`](omnibor.wolfssl-5.9.1.spdx.json.sample) — a
+truncated illustrative document (3 source files instead of every wolfSSL `.c`,
+placeholder gitoids instead of real ones) so customers know what shape `make bomsh`
+produces before they run it.
+
+**Why Linux only?** `bomsh` uses `bomtrace3`, a patched `strace` that records
+compiler invocations during a full rebuild. That tooling is built and supported
+on Linux hosts. The **target** of your firmware (MCU, RTOS, etc.) does not need
+to run Linux — only the machine **tracing the build** does.
+
+Details: [wolfssl/doc/SBOM.md §3](https://github.com/wolfSSL/wolfssl/blob/master/doc/SBOM.md)
diff --git a/cra-kit/auditor-packet/wolfssl-component/SAMPLE-PROVENANCE.md b/cra-kit/auditor-packet/wolfssl-component/SAMPLE-PROVENANCE.md
new file mode 100644
index 000000000..3e45902da
--- /dev/null
+++ b/cra-kit/auditor-packet/wolfssl-component/SAMPLE-PROVENANCE.md
@@ -0,0 +1,34 @@
+# Sample provenance
+
+Pinned files in this directory (`wolfssl-5.9.1.cdx.json`, `wolfssl-5.9.1.spdx.json`)
+were produced with the **autotools** path:
+
+```sh
+cd "$WOLFSSL_DIR" && ./configure && make sbom
+```
+
+They reflect a **configured library build** (SHA-256 of `libwolfssl` and full
+`wolfssl:build:*` properties from `options.h`).
+
+They are **not** the same as the **embedded** demo under
+[`../wolfssl-component-embedded/`](../wolfssl-component-embedded/), which uses
+`user_settings.h` and a trimmed `--srcs` list (source-merkle checksum).
+
+Regenerate autotools samples and fix the product stub checksum:
+
+```sh
+./scripts/refresh-samples.sh
+```
+
+## A note on timestamps
+
+The sample SBOMs carry different `metadata.timestamp` / `created` values because
+they were generated at different times, not in a single run:
+
+- `wolfssl-component/` (autotools): `2026-05-12`
+- `wolfssl-component-embedded/` (embedded demo): `2026-05-18`
+- `product-acme-connect-gateway.*` (product stub): `2026-05-18`
+
+This is expected for hand-pinned samples and does not affect validation
+(`scripts/validate.sh` checks cross-document checksums, not timestamps).
+Regenerating via `refresh-samples.sh` will update them to the current time.
diff --git a/cra-kit/auditor-packet/wolfssl-component/omnibor.wolfssl-5.9.1.spdx.json.sample b/cra-kit/auditor-packet/wolfssl-component/omnibor.wolfssl-5.9.1.spdx.json.sample
new file mode 100644
index 000000000..116a6476a
--- /dev/null
+++ b/cra-kit/auditor-packet/wolfssl-component/omnibor.wolfssl-5.9.1.spdx.json.sample
@@ -0,0 +1,92 @@
+{
+ "spdxVersion": "SPDX-2.3",
+ "dataLicense": "CC0-1.0",
+ "SPDXID": "SPDXRef-DOCUMENT",
+ "name": "omnibor.wolfssl-5.9.1",
+ "documentNamespace": "urn:uuid:9a8b7c6d-5e4f-4a3b-9c2d-1e0f3a4b5c6d",
+ "comment": "TRUNCATED SAMPLE — illustrates the shape of bomsh / OmniBOR output. A real omnibor.wolfssl-.spdx.json from `make bomsh` lists every wolfSSL .c source via gitoid:blob:sha1 alongside the resulting libwolfssl.so. The full omnibor/ Merkle DAG (under auditor-packet/wolfssl-component/omnibor/) is large and not committed here.",
+ "creationInfo": {
+ "creators": [
+ "Organization: wolfSSL Inc.",
+ "Tool: bomsh-1.0",
+ "Tool: bomtrace3"
+ ],
+ "created": "2026-05-12T17:01:12Z"
+ },
+ "packages": [
+ {
+ "SPDXID": "SPDXRef-Package-libwolfssl-so",
+ "name": "libwolfssl.so.43.0.0",
+ "versionInfo": "5.9.1",
+ "supplier": "Organization: wolfSSL Inc.",
+ "downloadLocation": "NOASSERTION",
+ "filesAnalyzed": false,
+ "checksums": [
+ {
+ "algorithm": "SHA1",
+ "checksumValue": "0000000000000000000000000000000000000001"
+ },
+ {
+ "algorithm": "SHA256",
+ "checksumValue": "0000000000000000000000000000000000000000000000000000000000000000"
+ }
+ ],
+ "comment": "SENTINEL VALUES — both the SHA-1 gitoid and the SHA-256 here are all-zero placeholders, NOT real digests. A compiled .so cannot share the source archive's hash, so this sample deliberately avoids any real-looking value a customer might copy. Real builds emit the actual binary digests and the gitoid covering all .o inputs."
+ }
+ ],
+ "files": [
+ {
+ "SPDXID": "SPDXRef-File-aes-c",
+ "fileName": "wolfcrypt/src/aes.c",
+ "checksums": [
+ {
+ "algorithm": "SHA1",
+ "checksumValue": "1111111111111111111111111111111111111111"
+ }
+ ],
+ "comment": "Sample gitoid:blob:sha1 for aes.c. Real entries cover every .c compiled into libwolfssl.so during the traced make bomsh run."
+ },
+ {
+ "SPDXID": "SPDXRef-File-sha256-c",
+ "fileName": "wolfcrypt/src/sha256.c",
+ "checksums": [
+ {
+ "algorithm": "SHA1",
+ "checksumValue": "2222222222222222222222222222222222222222"
+ }
+ ]
+ },
+ {
+ "SPDXID": "SPDXRef-File-tls13-c",
+ "fileName": "src/tls13.c",
+ "checksums": [
+ {
+ "algorithm": "SHA1",
+ "checksumValue": "3333333333333333333333333333333333333333"
+ }
+ ]
+ }
+ ],
+ "relationships": [
+ {
+ "spdxElementId": "SPDXRef-DOCUMENT",
+ "relatedSpdxElement": "SPDXRef-Package-libwolfssl-so",
+ "relationshipType": "DESCRIBES"
+ },
+ {
+ "spdxElementId": "SPDXRef-Package-libwolfssl-so",
+ "relatedSpdxElement": "SPDXRef-File-aes-c",
+ "relationshipType": "GENERATED_FROM"
+ },
+ {
+ "spdxElementId": "SPDXRef-Package-libwolfssl-so",
+ "relatedSpdxElement": "SPDXRef-File-sha256-c",
+ "relationshipType": "GENERATED_FROM"
+ },
+ {
+ "spdxElementId": "SPDXRef-Package-libwolfssl-so",
+ "relatedSpdxElement": "SPDXRef-File-tls13-c",
+ "relationshipType": "GENERATED_FROM"
+ }
+ ]
+}
diff --git a/cra-kit/auditor-packet/wolfssl-component/wolfssl-5.9.1.cbom-draft.cdx.json b/cra-kit/auditor-packet/wolfssl-component/wolfssl-5.9.1.cbom-draft.cdx.json
new file mode 100644
index 000000000..007ac6829
--- /dev/null
+++ b/cra-kit/auditor-packet/wolfssl-component/wolfssl-5.9.1.cbom-draft.cdx.json
@@ -0,0 +1,247 @@
+{
+ "$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json",
+ "bomFormat": "CycloneDX",
+ "specVersion": "1.6",
+ "serialNumber": "urn:uuid:f1a2b3c4-d5e6-4f78-9012-3456789abcde",
+ "version": 1,
+ "metadata": {
+ "timestamp": "2026-05-12T16:59:40Z",
+ "tools": {
+ "components": [
+ {
+ "type": "application",
+ "author": "wolfSSL Inc.",
+ "name": "cra-kit cbom-draft (hand-rolled)",
+ "version": "0.1"
+ }
+ ]
+ },
+ "component": {
+ "type": "library",
+ "bom-ref": "wolfssl-5.9.1-cbom",
+ "name": "wolfssl",
+ "version": "5.9.1",
+ "supplier": { "name": "wolfSSL Inc." },
+ "purl": "pkg:github/wolfSSL/wolfssl@v5.9.1"
+ },
+ "properties": [
+ {
+ "name": "wolfssl:cbom:status",
+ "value": "DRAFT — illustrative starter set for the CycloneDX 1.6 cryptographic-asset profile. Derived from the build configuration in wolfssl-5.9.1.cdx.json (HAVE_AESGCM, HAVE_CHACHA, HAVE_POLY1305, HAVE_ECC, HAVE_HKDF, WOLFSSL_SHA256/384/512, WOLFSSL_TLS13, WOLFSSL_HAVE_MLKEM). Not exhaustive. See ROADMAP.md."
+ }
+ ]
+ },
+ "components": [
+ {
+ "type": "cryptographic-asset",
+ "bom-ref": "crypto-aes-gcm",
+ "name": "AES-GCM",
+ "cryptoProperties": {
+ "assetType": "algorithm",
+ "algorithmProperties": {
+ "primitive": "ae",
+ "parameterSetIdentifier": "AES-256-GCM",
+ "cryptoFunctions": ["encrypt", "decrypt"],
+ "executionEnvironment": "software-plain-ram",
+ "implementationPlatform": "x86_64",
+ "certificationLevel": ["none"],
+ "nistQuantumSecurityLevel": 0
+ },
+ "oid": "2.16.840.1.101.3.4.1.46"
+ },
+ "properties": [
+ {"name": "wolfssl:build:macro", "value": "HAVE_AESGCM"},
+ {"name": "wolfssl:build:macro", "value": "GCM_TABLE_4BIT"}
+ ]
+ },
+ {
+ "type": "cryptographic-asset",
+ "bom-ref": "crypto-chacha20-poly1305",
+ "name": "ChaCha20-Poly1305",
+ "cryptoProperties": {
+ "assetType": "algorithm",
+ "algorithmProperties": {
+ "primitive": "ae",
+ "parameterSetIdentifier": "ChaCha20-Poly1305 (RFC 8439)",
+ "cryptoFunctions": ["encrypt", "decrypt"],
+ "executionEnvironment": "software-plain-ram",
+ "implementationPlatform": "x86_64",
+ "nistQuantumSecurityLevel": 0
+ }
+ },
+ "properties": [
+ {"name": "wolfssl:build:macro", "value": "HAVE_CHACHA"},
+ {"name": "wolfssl:build:macro", "value": "HAVE_POLY1305"}
+ ]
+ },
+ {
+ "type": "cryptographic-asset",
+ "bom-ref": "crypto-ecdh-p256",
+ "name": "ECDH (P-256)",
+ "cryptoProperties": {
+ "assetType": "algorithm",
+ "algorithmProperties": {
+ "primitive": "key-agree",
+ "parameterSetIdentifier": "secp256r1 (NIST P-256)",
+ "curve": "P-256",
+ "cryptoFunctions": ["keygen", "derive"],
+ "executionEnvironment": "software-plain-ram",
+ "nistQuantumSecurityLevel": 0
+ }
+ },
+ "properties": [
+ {"name": "wolfssl:build:macro", "value": "HAVE_ECC"},
+ {"name": "wolfssl:build:macro", "value": "ECC_TIMING_RESISTANT"}
+ ]
+ },
+ {
+ "type": "cryptographic-asset",
+ "bom-ref": "crypto-ecdsa-p256",
+ "name": "ECDSA (P-256)",
+ "cryptoProperties": {
+ "assetType": "algorithm",
+ "algorithmProperties": {
+ "primitive": "signature",
+ "parameterSetIdentifier": "secp256r1 (NIST P-256)",
+ "curve": "P-256",
+ "cryptoFunctions": ["sign", "verify"],
+ "executionEnvironment": "software-plain-ram",
+ "nistQuantumSecurityLevel": 0
+ }
+ },
+ "properties": [
+ {"name": "wolfssl:build:macro", "value": "HAVE_ECC"}
+ ]
+ },
+ {
+ "type": "cryptographic-asset",
+ "bom-ref": "crypto-hkdf",
+ "name": "HKDF",
+ "cryptoProperties": {
+ "assetType": "algorithm",
+ "algorithmProperties": {
+ "primitive": "kdf",
+ "parameterSetIdentifier": "HKDF-SHA256 (RFC 5869)",
+ "cryptoFunctions": ["derive"],
+ "executionEnvironment": "software-plain-ram"
+ }
+ },
+ "properties": [
+ {"name": "wolfssl:build:macro", "value": "HAVE_HKDF"}
+ ]
+ },
+ {
+ "type": "cryptographic-asset",
+ "bom-ref": "crypto-sha-256",
+ "name": "SHA-256",
+ "cryptoProperties": {
+ "assetType": "algorithm",
+ "algorithmProperties": {
+ "primitive": "hash",
+ "parameterSetIdentifier": "SHA-256",
+ "cryptoFunctions": ["digest"],
+ "executionEnvironment": "software-plain-ram"
+ },
+ "oid": "2.16.840.1.101.3.4.2.1"
+ }
+ },
+ {
+ "type": "cryptographic-asset",
+ "bom-ref": "crypto-sha-384",
+ "name": "SHA-384",
+ "cryptoProperties": {
+ "assetType": "algorithm",
+ "algorithmProperties": {
+ "primitive": "hash",
+ "parameterSetIdentifier": "SHA-384",
+ "cryptoFunctions": ["digest"],
+ "executionEnvironment": "software-plain-ram"
+ },
+ "oid": "2.16.840.1.101.3.4.2.2"
+ },
+ "properties": [
+ {"name": "wolfssl:build:macro", "value": "WOLFSSL_SHA384"}
+ ]
+ },
+ {
+ "type": "cryptographic-asset",
+ "bom-ref": "crypto-sha-512",
+ "name": "SHA-512",
+ "cryptoProperties": {
+ "assetType": "algorithm",
+ "algorithmProperties": {
+ "primitive": "hash",
+ "parameterSetIdentifier": "SHA-512",
+ "cryptoFunctions": ["digest"],
+ "executionEnvironment": "software-plain-ram"
+ },
+ "oid": "2.16.840.1.101.3.4.2.3"
+ },
+ "properties": [
+ {"name": "wolfssl:build:macro", "value": "WOLFSSL_SHA512"}
+ ]
+ },
+ {
+ "type": "cryptographic-asset",
+ "bom-ref": "crypto-ml-kem",
+ "name": "ML-KEM (post-quantum hybrid)",
+ "cryptoProperties": {
+ "assetType": "algorithm",
+ "algorithmProperties": {
+ "primitive": "kem",
+ "parameterSetIdentifier": "ML-KEM-768 (NIST FIPS 203, hybrid TLS 1.3)",
+ "cryptoFunctions": ["encapsulate", "decapsulate"],
+ "executionEnvironment": "software-plain-ram",
+ "nistQuantumSecurityLevel": 3
+ }
+ },
+ "properties": [
+ {"name": "wolfssl:build:macro", "value": "WOLFSSL_HAVE_MLKEM"},
+ {"name": "wolfssl:build:macro", "value": "WOLFSSL_PQC_HYBRIDS"}
+ ]
+ },
+ {
+ "type": "cryptographic-asset",
+ "bom-ref": "crypto-tls-1.3",
+ "name": "TLS 1.3",
+ "cryptoProperties": {
+ "assetType": "protocol",
+ "protocolProperties": {
+ "type": "tls",
+ "version": "1.3",
+ "cryptoRefArray": [
+ "crypto-aes-gcm",
+ "crypto-chacha20-poly1305",
+ "crypto-ecdh-p256",
+ "crypto-ecdsa-p256",
+ "crypto-hkdf",
+ "crypto-sha-256",
+ "crypto-sha-384",
+ "crypto-ml-kem"
+ ]
+ }
+ },
+ "properties": [
+ {"name": "wolfssl:build:macro", "value": "WOLFSSL_TLS13"},
+ {"name": "wolfssl:note:sha-512-omitted", "value": "SHA-512 is built into the component (crypto-sha-512) but is intentionally absent from this cryptoRefArray: TLS 1.3 handshake/HKDF use SHA-256/SHA-384, not SHA-512. SHA-512 is exposed as a crypto asset for callers, not as a TLS 1.3 protocol dependency."}
+ ]
+ }
+ ],
+ "dependencies": [
+ {
+ "ref": "wolfssl-5.9.1-cbom",
+ "dependsOn": [
+ "crypto-tls-1.3",
+ "crypto-aes-gcm",
+ "crypto-chacha20-poly1305",
+ "crypto-ecdh-p256",
+ "crypto-ecdsa-p256",
+ "crypto-hkdf",
+ "crypto-sha-256",
+ "crypto-sha-384",
+ "crypto-sha-512",
+ "crypto-ml-kem"
+ ]
+ }
+ ]
+}
diff --git a/cra-kit/auditor-packet/wolfssl-component/wolfssl-5.9.1.cdx.json b/cra-kit/auditor-packet/wolfssl-component/wolfssl-5.9.1.cdx.json
new file mode 100644
index 000000000..f05521ef4
--- /dev/null
+++ b/cra-kit/auditor-packet/wolfssl-component/wolfssl-5.9.1.cdx.json
@@ -0,0 +1,336 @@
+{
+ "$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json",
+ "bomFormat": "CycloneDX",
+ "specVersion": "1.6",
+ "serialNumber": "urn:uuid:bbd8fa2c-814a-5921-b121-e872fe1b42a2",
+ "version": 1,
+ "metadata": {
+ "timestamp": "2026-05-12T16:59:40Z",
+ "tools": {
+ "components": [
+ {
+ "type": "application",
+ "author": "wolfSSL Inc.",
+ "name": "wolfssl-sbom-gen",
+ "version": "1.1"
+ }
+ ]
+ },
+ "component": {
+ "bom-ref": "721ce791-b9c8-5edf-a9d2-ef3b0539043b",
+ "type": "library",
+ "supplier": {
+ "name": "wolfSSL Inc."
+ },
+ "name": "wolfssl",
+ "version": "5.9.1",
+ "licenses": [
+ {
+ "license": {
+ "id": "GPL-3.0-only"
+ }
+ }
+ ],
+ "copyright": "Copyright (C) 2006-2026 wolfSSL Inc.",
+ "cpe": "cpe:2.3:a:wolfssl:wolfssl:5.9.1:*:*:*:*:*:*:*",
+ "purl": "pkg:github/wolfSSL/wolfssl@v5.9.1",
+ "hashes": [
+ {
+ "alg": "SHA-256",
+ "content": "391e86477f5eee025e677a24b13e2d9a4d3e4c18d88e6359853ebf1c9932279e"
+ }
+ ],
+ "externalReferences": [
+ {
+ "type": "vcs",
+ "url": "https://github.com/wolfSSL/wolfssl"
+ },
+ {
+ "type": "website",
+ "url": "https://www.wolfssl.com/"
+ },
+ {
+ "type": "issue-tracker",
+ "url": "https://github.com/wolfSSL/wolfssl/issues"
+ },
+ {
+ "type": "advisories",
+ "url": "https://github.com/wolfSSL/wolfssl/security/advisories"
+ },
+ {
+ "type": "security-contact",
+ "url": "https://www.wolfssl.com/.well-known/security.txt"
+ }
+ ],
+ "properties": [
+ {
+ "name": "wolfssl:build:ECC_MIN_KEY_SZ",
+ "value": "224"
+ },
+ {
+ "name": "wolfssl:build:ECC_SHAMIR",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:ECC_TIMING_RESISTANT",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:ERROR_QUEUE_PER_THREAD",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:GCM_TABLE_4BIT",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:HAVE_AESGCM",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:HAVE_CHACHA",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:HAVE_C___ATOMIC",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:HAVE_DH_DEFAULT_PARAMS",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:HAVE_ECC",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:HAVE_ENCRYPT_THEN_MAC",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:HAVE_EXTENDED_MASTER",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:HAVE_FFDHE_2048",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:HAVE_GETPID",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:HAVE_HASHDRBG",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:HAVE_HKDF",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:HAVE_POLY1305",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:HAVE_SERVER_RENEGOTIATION_INFO",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:HAVE_SNI",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:HAVE_SUPPORTED_CURVES",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:HAVE_THREAD_LS",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:HAVE_TLS_EXTENSIONS",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:HAVE_WC_INTROSPECTION",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:HAVE___UINT128_T",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:NO_DES3",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:NO_DES3_TLS_SUITES",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:NO_DO178",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:NO_DSA",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:NO_MD4",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:NO_MD5",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:NO_OLD_TLS",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:NO_PSK",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:NO_RC4",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:TFM_TIMING_RESISTANT",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WC_NO_ASYNC_THREADING",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WC_RSA_BLINDING",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WC_RSA_PSS",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_ARMASM_NO_HW_CRYPTO",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_ASN_PRINT",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_ASN_TEMPLATE",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_BASE64_ENCODE",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_DRBG_SHA512",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_HAVE_ASSERT_H",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_HAVE_ATOMIC_H",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_HAVE_MLKEM",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_PQC_HYBRIDS",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_PSS_LONG_SALT",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_SHA224",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_SHA3",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_SHA384",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_SHA512",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_SHAKE128",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_SHAKE256",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_SP_MATH_ALL",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_SP_X86_64",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_SYS_CA_CERTS",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_TLS13",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_TLS_NO_MLKEM_STANDALONE",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_USE_ALIGN",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_X86_64_BUILD",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:sbom:hash-kind",
+ "value": "library-binary"
+ }
+ ],
+ "components": [
+ {
+ "type": "file",
+ "name": "libwolfssl.44.dylib",
+ "hashes": [
+ {
+ "alg": "SHA-1",
+ "content": "def1d74ce45e708d8230084cdea4f45a9cad144c"
+ },
+ {
+ "alg": "SHA-256",
+ "content": "391e86477f5eee025e677a24b13e2d9a4d3e4c18d88e6359853ebf1c9932279e"
+ }
+ ]
+ }
+ ]
+ }
+ },
+ "components": [],
+ "dependencies": [
+ {
+ "ref": "721ce791-b9c8-5edf-a9d2-ef3b0539043b",
+ "dependsOn": []
+ }
+ ]
+}
diff --git a/cra-kit/auditor-packet/wolfssl-component/wolfssl-5.9.1.commercial.cdx.json b/cra-kit/auditor-packet/wolfssl-component/wolfssl-5.9.1.commercial.cdx.json
new file mode 100644
index 000000000..2b8d05b17
--- /dev/null
+++ b/cra-kit/auditor-packet/wolfssl-component/wolfssl-5.9.1.commercial.cdx.json
@@ -0,0 +1,340 @@
+{
+ "$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json",
+ "bomFormat": "CycloneDX",
+ "specVersion": "1.6",
+ "serialNumber": "urn:uuid:554e996c-5c73-46f2-a642-5c1dc0c853d8",
+ "version": 1,
+ "metadata": {
+ "timestamp": "2026-05-12T16:59:40Z",
+ "tools": {
+ "components": [
+ {
+ "type": "application",
+ "author": "wolfSSL Inc.",
+ "name": "wolfssl-sbom-gen",
+ "version": "1.1"
+ }
+ ]
+ },
+ "component": {
+ "bom-ref": "721ce791-b9c8-5edf-a9d2-ef3b0539043b",
+ "type": "library",
+ "supplier": {
+ "name": "wolfSSL Inc."
+ },
+ "name": "wolfssl",
+ "version": "5.9.1",
+ "licenses": [
+ {
+ "license": {
+ "name": "wolfSSL Commercial License (LicenseRef-wolfSSL-Commercial)"
+ }
+ }
+ ],
+ "copyright": "Copyright (C) 2006-2026 wolfSSL Inc.",
+ "cpe": "cpe:2.3:a:wolfssl:wolfssl:5.9.1:*:*:*:*:*:*:*",
+ "purl": "pkg:github/wolfSSL/wolfssl@v5.9.1",
+ "hashes": [
+ {
+ "alg": "SHA-256",
+ "content": "391e86477f5eee025e677a24b13e2d9a4d3e4c18d88e6359853ebf1c9932279e"
+ }
+ ],
+ "externalReferences": [
+ {
+ "type": "vcs",
+ "url": "https://github.com/wolfSSL/wolfssl"
+ },
+ {
+ "type": "website",
+ "url": "https://www.wolfssl.com/"
+ },
+ {
+ "type": "issue-tracker",
+ "url": "https://github.com/wolfSSL/wolfssl/issues"
+ },
+ {
+ "type": "advisories",
+ "url": "https://github.com/wolfSSL/wolfssl/security/advisories"
+ },
+ {
+ "type": "security-contact",
+ "url": "https://www.wolfssl.com/.well-known/security.txt"
+ }
+ ],
+ "properties": [
+ {
+ "name": "wolfssl:build:ECC_MIN_KEY_SZ",
+ "value": "224"
+ },
+ {
+ "name": "wolfssl:build:ECC_SHAMIR",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:ECC_TIMING_RESISTANT",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:ERROR_QUEUE_PER_THREAD",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:GCM_TABLE_4BIT",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:HAVE_AESGCM",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:HAVE_CHACHA",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:HAVE_C___ATOMIC",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:HAVE_DH_DEFAULT_PARAMS",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:HAVE_ECC",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:HAVE_ENCRYPT_THEN_MAC",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:HAVE_EXTENDED_MASTER",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:HAVE_FFDHE_2048",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:HAVE_GETPID",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:HAVE_HASHDRBG",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:HAVE_HKDF",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:HAVE_POLY1305",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:HAVE_SERVER_RENEGOTIATION_INFO",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:HAVE_SNI",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:HAVE_SUPPORTED_CURVES",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:HAVE_THREAD_LS",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:HAVE_TLS_EXTENSIONS",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:HAVE_WC_INTROSPECTION",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:HAVE___UINT128_T",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:NO_DES3",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:NO_DES3_TLS_SUITES",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:NO_DO178",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:NO_DSA",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:NO_MD4",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:NO_MD5",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:NO_OLD_TLS",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:NO_PSK",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:NO_RC4",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:TFM_TIMING_RESISTANT",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WC_NO_ASYNC_THREADING",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WC_RSA_BLINDING",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WC_RSA_PSS",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_ARMASM_NO_HW_CRYPTO",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_ASN_PRINT",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_ASN_TEMPLATE",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_BASE64_ENCODE",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_DRBG_SHA512",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_HAVE_ASSERT_H",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_HAVE_ATOMIC_H",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_HAVE_MLKEM",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_PQC_HYBRIDS",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_PSS_LONG_SALT",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_SHA224",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_SHA3",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_SHA384",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_SHA512",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_SHAKE128",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_SHAKE256",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_SP_MATH_ALL",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_SP_X86_64",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_SYS_CA_CERTS",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_TLS13",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_TLS_NO_MLKEM_STANDALONE",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_USE_ALIGN",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:build:WOLFSSL_X86_64_BUILD",
+ "value": "1"
+ },
+ {
+ "name": "wolfssl:sbom:hash-kind",
+ "value": "library-binary"
+ },
+ {
+ "name": "wolfssl:license:override",
+ "value": "LicenseRef-wolfSSL-Commercial"
+ }
+ ],
+ "components": [
+ {
+ "type": "file",
+ "name": "libwolfssl.44.dylib",
+ "hashes": [
+ {
+ "alg": "SHA-1",
+ "content": "def1d74ce45e708d8230084cdea4f45a9cad144c"
+ },
+ {
+ "alg": "SHA-256",
+ "content": "391e86477f5eee025e677a24b13e2d9a4d3e4c18d88e6359853ebf1c9932279e"
+ }
+ ]
+ }
+ ]
+ }
+ },
+ "components": [],
+ "dependencies": [
+ {
+ "ref": "721ce791-b9c8-5edf-a9d2-ef3b0539043b",
+ "dependsOn": []
+ }
+ ]
+}
diff --git a/cra-kit/auditor-packet/wolfssl-component/wolfssl-5.9.1.commercial.spdx.json b/cra-kit/auditor-packet/wolfssl-component/wolfssl-5.9.1.commercial.spdx.json
new file mode 100644
index 000000000..19ca426c5
--- /dev/null
+++ b/cra-kit/auditor-packet/wolfssl-component/wolfssl-5.9.1.commercial.spdx.json
@@ -0,0 +1,76 @@
+{
+ "spdxVersion": "SPDX-2.3",
+ "dataLicense": "CC0-1.0",
+ "SPDXID": "SPDXRef-DOCUMENT",
+ "name": "wolfssl-5.9.1",
+ "documentNamespace": "urn:uuid:59b18c7a-8572-4743-85f2-ffeac74c310d",
+ "creationInfo": {
+ "creators": [
+ "Organization: wolfSSL Inc.",
+ "Tool: wolfssl-sbom-gen-1.1"
+ ],
+ "created": "2026-05-12T16:59:40Z"
+ },
+ "packages": [
+ {
+ "SPDXID": "SPDXRef-Package-wolfssl",
+ "name": "wolfssl",
+ "versionInfo": "5.9.1",
+ "supplier": "Organization: wolfSSL Inc.",
+ "downloadLocation": "https://github.com/wolfSSL/wolfssl",
+ "filesAnalyzed": false,
+ "checksums": [
+ {
+ "algorithm": "SHA256",
+ "checksumValue": "391e86477f5eee025e677a24b13e2d9a4d3e4c18d88e6359853ebf1c9932279e"
+ }
+ ],
+ "licenseConcluded": "LicenseRef-wolfSSL-Commercial",
+ "licenseDeclared": "LicenseRef-wolfSSL-Commercial",
+ "copyrightText": "Copyright (C) 2006-2026 wolfSSL Inc.",
+ "comment": "License override applied: LicenseRef-wolfSSL-Commercial. Build configuration defines: ECC_MIN_KEY_SZ, ECC_SHAMIR, ECC_TIMING_RESISTANT, ERROR_QUEUE_PER_THREAD, GCM_TABLE_4BIT, HAVE_AESGCM, HAVE_CHACHA, HAVE_C___ATOMIC, HAVE_DH_DEFAULT_PARAMS, HAVE_ECC, HAVE_ENCRYPT_THEN_MAC, HAVE_EXTENDED_MASTER, HAVE_FFDHE_2048, HAVE_GETPID, HAVE_HASHDRBG, HAVE_HKDF, HAVE_POLY1305, HAVE_SERVER_RENEGOTIATION_INFO, HAVE_SNI, HAVE_SUPPORTED_CURVES, HAVE_THREAD_LS, HAVE_TLS_EXTENSIONS, HAVE_WC_INTROSPECTION, HAVE___UINT128_T, NO_DES3, NO_DES3_TLS_SUITES, NO_DO178, NO_DSA, NO_MD4, NO_MD5, NO_OLD_TLS, NO_PSK, NO_RC4, TFM_TIMING_RESISTANT, WC_NO_ASYNC_THREADING, WC_RSA_BLINDING, WC_RSA_PSS, WOLFSSL_ARMASM_NO_HW_CRYPTO, WOLFSSL_ASN_PRINT, WOLFSSL_ASN_TEMPLATE, WOLFSSL_BASE64_ENCODE, WOLFSSL_DRBG_SHA512, WOLFSSL_HAVE_ASSERT_H, WOLFSSL_HAVE_ATOMIC_H, WOLFSSL_HAVE_MLKEM, WOLFSSL_PQC_HYBRIDS, WOLFSSL_PSS_LONG_SALT, WOLFSSL_SHA224, WOLFSSL_SHA3, WOLFSSL_SHA384, WOLFSSL_SHA512, WOLFSSL_SHAKE128, WOLFSSL_SHAKE256, WOLFSSL_SP_MATH_ALL, WOLFSSL_SP_X86_64, WOLFSSL_SYS_CA_CERTS, WOLFSSL_TLS13, WOLFSSL_TLS_NO_MLKEM_STANDALONE, WOLFSSL_USE_ALIGN, WOLFSSL_X86_64_BUILD",
+ "annotations": [
+ {
+ "annotationDate": "2026-05-12T16:59:40Z",
+ "annotationType": "OTHER",
+ "annotator": "Tool: wolfssl-sbom-gen-1.1",
+ "comment": "wolfssl:sbom:hash-kind=library-binary"
+ }
+ ],
+ "externalRefs": [
+ {
+ "referenceCategory": "SECURITY",
+ "referenceType": "cpe23Type",
+ "referenceLocator": "cpe:2.3:a:wolfssl:wolfssl:5.9.1:*:*:*:*:*:*:*"
+ },
+ {
+ "referenceCategory": "PACKAGE-MANAGER",
+ "referenceType": "purl",
+ "referenceLocator": "pkg:github/wolfSSL/wolfssl@v5.9.1"
+ },
+ {
+ "referenceCategory": "SECURITY",
+ "referenceType": "advisory",
+ "referenceLocator": "https://github.com/wolfSSL/wolfssl/security/advisories"
+ }
+ ]
+ }
+ ],
+ "relationships": [
+ {
+ "spdxElementId": "SPDXRef-DOCUMENT",
+ "relatedSpdxElement": "SPDXRef-Package-wolfssl",
+ "relationshipType": "DESCRIBES"
+ }
+ ],
+ "hasExtractedLicensingInfos": [
+ {
+ "licenseId": "LicenseRef-wolfSSL-Commercial",
+ "extractedText": "wolfSSL commercial license. See https://www.wolfssl.com/license/ for terms. Replaces the GPL-3.0-only declaration of the open-source distribution.",
+ "name": "wolfSSL Commercial License",
+ "seeAlsos": [
+ "https://www.wolfssl.com/license/"
+ ]
+ }
+ ]
+}
diff --git a/cra-kit/auditor-packet/wolfssl-component/wolfssl-5.9.1.spdx b/cra-kit/auditor-packet/wolfssl-component/wolfssl-5.9.1.spdx
new file mode 100644
index 000000000..96c31e868
--- /dev/null
+++ b/cra-kit/auditor-packet/wolfssl-component/wolfssl-5.9.1.spdx
@@ -0,0 +1,38 @@
+## Document Information
+SPDXVersion: SPDX-2.3
+DataLicense: CC0-1.0
+SPDXID: SPDXRef-DOCUMENT
+DocumentName: wolfssl-5.9.1
+DocumentNamespace: urn:uuid:480ff203-f994-5b71-b858-0653e74e422a
+
+## Creation Information
+Creator: Organization: wolfSSL Inc.
+Creator: Tool: wolfssl-sbom-gen-1.1
+Created: 2026-05-12T16:59:40Z
+
+## Package Information
+PackageName: wolfssl
+SPDXID: SPDXRef-Package-wolfssl
+PackageVersion: 5.9.1
+PackageSupplier: Organization: wolfSSL Inc.
+PackageDownloadLocation: https://github.com/wolfSSL/wolfssl
+FilesAnalyzed: false
+PackageChecksum: SHA256: 391e86477f5eee025e677a24b13e2d9a4d3e4c18d88e6359853ebf1c9932279e
+PackageLicenseConcluded: GPL-3.0-only
+PackageLicenseDeclared: GPL-3.0-only
+PackageCopyrightText: Copyright (C) 2006-2026 wolfSSL Inc.
+PackageComment: Build configuration defines: ECC_MIN_KEY_SZ, ECC_SHAMIR, ECC_TIMING_RESISTANT, ERROR_QUEUE_PER_THREAD, GCM_TABLE_4BIT, HAVE_AESGCM, HAVE_CHACHA, HAVE_C___ATOMIC, HAVE_DH_DEFAULT_PARAMS, HAVE_ECC, HAVE_ENCRYPT_THEN_MAC, HAVE_EXTENDED_MASTER, HAVE_FFDHE_2048, HAVE_GETPID, HAVE_HASHDRBG, HAVE_HKDF, HAVE_POLY1305, HAVE_SERVER_RENEGOTIATION_INFO, HAVE_SNI, HAVE_SUPPORTED_CURVES, HAVE_THREAD_LS, HAVE_TLS_EXTENSIONS, HAVE_WC_INTROSPECTION, HAVE___UINT128_T, NO_DES3, NO_DES3_TLS_SUITES, NO_DO178, NO_DSA, NO_MD4, NO_MD5, NO_OLD_TLS, NO_PSK, NO_RC4, TFM_TIMING_RESISTANT, WC_NO_ASYNC_THREADING, WC_RSA_BLINDING, WC_RSA_PSS, WOLFSSL_ARMASM_NO_HW_CRYPTO, WOLFSSL_ASN_PRINT, WOLFSSL_ASN_TEMPLATE, WOLFSSL_BASE64_ENCODE, WOLFSSL_DRBG_SHA512, WOLFSSL_HAVE_ASSERT_H, WOLFSSL_HAVE_ATOMIC_H, WOLFSSL_HAVE_MLKEM, WOLFSSL_PQC_HYBRIDS, WOLFSSL_PSS_LONG_SALT, WOLFSSL_SHA224, WOLFSSL_SHA3, WOLFSSL_SHA384, WOLFSSL_SHA512, WOLFSSL_SHAKE128, WOLFSSL_SHAKE256, WOLFSSL_SP_MATH_ALL, WOLFSSL_SP_X86_64, WOLFSSL_SYS_CA_CERTS, WOLFSSL_TLS13, WOLFSSL_TLS_NO_MLKEM_STANDALONE, WOLFSSL_USE_ALIGN, WOLFSSL_X86_64_BUILD
+ExternalRef: SECURITY cpe23Type cpe:2.3:a:wolfssl:wolfssl:5.9.1:*:*:*:*:*:*:*
+ExternalRef: PACKAGE-MANAGER purl pkg:github/wolfSSL/wolfssl@v5.9.1
+ExternalRef: SECURITY advisory https://github.com/wolfSSL/wolfssl/security/advisories
+
+## Relationships
+Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-Package-wolfssl
+
+## Annotations
+Annotator: Tool: wolfssl-sbom-gen-1.1
+AnnotationDate: 2026-05-12T16:59:40Z
+AnnotationType: OTHER
+SPDXREF: SPDXRef-Package-wolfssl
+AnnotationComment: wolfssl:sbom:hash-kind=library-binary
+
diff --git a/cra-kit/auditor-packet/wolfssl-component/wolfssl-5.9.1.spdx.json b/cra-kit/auditor-packet/wolfssl-component/wolfssl-5.9.1.spdx.json
new file mode 100644
index 000000000..7c609dbc6
--- /dev/null
+++ b/cra-kit/auditor-packet/wolfssl-component/wolfssl-5.9.1.spdx.json
@@ -0,0 +1,66 @@
+{
+ "spdxVersion": "SPDX-2.3",
+ "dataLicense": "CC0-1.0",
+ "SPDXID": "SPDXRef-DOCUMENT",
+ "name": "wolfssl-5.9.1",
+ "documentNamespace": "urn:uuid:480ff203-f994-5b71-b858-0653e74e422a",
+ "creationInfo": {
+ "creators": [
+ "Organization: wolfSSL Inc.",
+ "Tool: wolfssl-sbom-gen-1.1"
+ ],
+ "created": "2026-05-12T16:59:40Z"
+ },
+ "packages": [
+ {
+ "SPDXID": "SPDXRef-Package-wolfssl",
+ "name": "wolfssl",
+ "versionInfo": "5.9.1",
+ "supplier": "Organization: wolfSSL Inc.",
+ "downloadLocation": "https://github.com/wolfSSL/wolfssl",
+ "filesAnalyzed": false,
+ "checksums": [
+ {
+ "algorithm": "SHA256",
+ "checksumValue": "391e86477f5eee025e677a24b13e2d9a4d3e4c18d88e6359853ebf1c9932279e"
+ }
+ ],
+ "licenseConcluded": "GPL-3.0-only",
+ "licenseDeclared": "GPL-3.0-only",
+ "copyrightText": "Copyright (C) 2006-2026 wolfSSL Inc.",
+ "comment": "Build configuration defines: ECC_MIN_KEY_SZ, ECC_SHAMIR, ECC_TIMING_RESISTANT, ERROR_QUEUE_PER_THREAD, GCM_TABLE_4BIT, HAVE_AESGCM, HAVE_CHACHA, HAVE_C___ATOMIC, HAVE_DH_DEFAULT_PARAMS, HAVE_ECC, HAVE_ENCRYPT_THEN_MAC, HAVE_EXTENDED_MASTER, HAVE_FFDHE_2048, HAVE_GETPID, HAVE_HASHDRBG, HAVE_HKDF, HAVE_POLY1305, HAVE_SERVER_RENEGOTIATION_INFO, HAVE_SNI, HAVE_SUPPORTED_CURVES, HAVE_THREAD_LS, HAVE_TLS_EXTENSIONS, HAVE_WC_INTROSPECTION, HAVE___UINT128_T, NO_DES3, NO_DES3_TLS_SUITES, NO_DO178, NO_DSA, NO_MD4, NO_MD5, NO_OLD_TLS, NO_PSK, NO_RC4, TFM_TIMING_RESISTANT, WC_NO_ASYNC_THREADING, WC_RSA_BLINDING, WC_RSA_PSS, WOLFSSL_ARMASM_NO_HW_CRYPTO, WOLFSSL_ASN_PRINT, WOLFSSL_ASN_TEMPLATE, WOLFSSL_BASE64_ENCODE, WOLFSSL_DRBG_SHA512, WOLFSSL_HAVE_ASSERT_H, WOLFSSL_HAVE_ATOMIC_H, WOLFSSL_HAVE_MLKEM, WOLFSSL_PQC_HYBRIDS, WOLFSSL_PSS_LONG_SALT, WOLFSSL_SHA224, WOLFSSL_SHA3, WOLFSSL_SHA384, WOLFSSL_SHA512, WOLFSSL_SHAKE128, WOLFSSL_SHAKE256, WOLFSSL_SP_MATH_ALL, WOLFSSL_SP_X86_64, WOLFSSL_SYS_CA_CERTS, WOLFSSL_TLS13, WOLFSSL_TLS_NO_MLKEM_STANDALONE, WOLFSSL_USE_ALIGN, WOLFSSL_X86_64_BUILD",
+ "annotations": [
+ {
+ "annotationDate": "2026-05-12T16:59:40Z",
+ "annotationType": "OTHER",
+ "annotator": "Tool: wolfssl-sbom-gen-1.1",
+ "comment": "wolfssl:sbom:hash-kind=library-binary"
+ }
+ ],
+ "externalRefs": [
+ {
+ "referenceCategory": "SECURITY",
+ "referenceType": "cpe23Type",
+ "referenceLocator": "cpe:2.3:a:wolfssl:wolfssl:5.9.1:*:*:*:*:*:*:*"
+ },
+ {
+ "referenceCategory": "PACKAGE-MANAGER",
+ "referenceType": "purl",
+ "referenceLocator": "pkg:github/wolfSSL/wolfssl@v5.9.1"
+ },
+ {
+ "referenceCategory": "SECURITY",
+ "referenceType": "advisory",
+ "referenceLocator": "https://github.com/wolfSSL/wolfssl/security/advisories"
+ }
+ ]
+ }
+ ],
+ "relationships": [
+ {
+ "spdxElementId": "SPDXRef-DOCUMENT",
+ "relatedSpdxElement": "SPDXRef-Package-wolfssl",
+ "relationshipType": "DESCRIBES"
+ }
+ ]
+}
diff --git a/cra-kit/presentations/SLIDE-OUTLINE.md b/cra-kit/presentations/SLIDE-OUTLINE.md
new file mode 100644
index 000000000..0df7ad6a1
--- /dev/null
+++ b/cra-kit/presentations/SLIDE-OUTLINE.md
@@ -0,0 +1,73 @@
+# CRA co-sponsor slide track (~15 min)
+
+Companion kit: [`../CRA-Cheat-Sheet.md`](../CRA-Cheat-Sheet.md) ·
+[`../CRA-Supply-Chain-Glossary.md`](../CRA-Supply-Chain-Glossary.md) ·
+[`../SKILL.md`](../SKILL.md) · [`../auditor-packet/`](../auditor-packet/)
+
+---
+
+## Slide: Shortlist towards CRA compliance
+
+Use **[`CRA-Compliance-Shortlist.md`](../CRA-Compliance-Shortlist.md)** — two columns per pillar:
+**your job** vs **wolfSSL helps**.
+
+| Pillar | On slide (customer) | wolfSSL |
+|--------|---------------------|---------|
+| **Know your software components** | Survey all integrated components: who maintains them? how do you track vulns/releases? | SBOMs for our products; continuous vulnerability management and updates |
+| **Implement secure boot** | Most influential action today: trusted firmware + update path aligned with complaint/timing rules | **wolfBoot** |
+| **Remote data processing / data in transfer** | CRA covers data between device and network — use current crypto and secure protocols | **TLS**, **SSH**, **MQTTS**, … |
+| **Vulnerability handling & reporting** | Published CVD policy + `security.txt`; 24h ENISA reporting (Art. 14); on-call coverage — process, not a deliverable | wolfSSL [`security.txt`](https://www.wolfssl.com/.well-known/security.txt) + [CVD policy](https://www.wolfssl.com/.well-known/vulnerability-disclosure-policy.txt) as reference templates; advisories; CNA |
+
+**Bridge to this session:** pillar 1 is where the **CRA Kit** lands (SBOM, auditor packet, scripts).
+
+---
+
+## Slide: Promise — what you leave with
+
+**You will leave with:**
+
+1. **Who provides what** — what **you** provide vs what **wolfSSL** provides
+ → [`CRA-Cheat-Sheet.md`](../CRA-Cheat-Sheet.md) (print/PDF)
+ → full terms: [`CRA-Supply-Chain-Glossary.md`](../CRA-Supply-Chain-Glossary.md)
+
+2. **A worked example** — wolfSSL CRA Kit
+ → [`wolfssl-examples/cra-kit/auditor-packet/`](../auditor-packet/)
+
+3. **Helper scripts + AI playbook** — product SBOM, nest wolfSSL, optional **bomsh** on **Linux CI** only
+ → **[`SKILL.md`](../SKILL.md)** for AI-assisted execution (Cursor / agents)
+
+---
+
+## Talking points
+
+| Instead of… | Say… |
+|-------------|------|
+| Learn every acronym | “Cheat sheet for roles; glossary in the same kit.” |
+| wolfSSL is CRA compliant | “Component SBOMs from us; **product** SBOM and vuln process from you.” |
+| We ship CBOM | “Build properties today; formal CBOM profile on the roadmap.” |
+| You need bomsh | “Usually no — Linux CI only if a contract asks.” |
+| AI is extra | “**SKILL.md** is the playbook—copy it into Cursor and run the scripts with your tree.” |
+
+---
+
+## Demo path (optional live)
+
+```bash
+cd wolfssl-examples/cra-kit
+./scripts/validate.sh
+```
+
+Show `auditor-packet/product-acme-connect-gateway.cdx.json` → wolfSSL component reference.
+
+Optional: show copying `SKILL.md` into `.cursor/skills/wolfssl-cra-kit/`.
+
+---
+
+## Kit documents (handout stack)
+
+| Layer | File |
+|-------|------|
+| Who provides what (1 page) | `CRA-Cheat-Sheet.md` |
+| Glossary (reference) | `CRA-Supply-Chain-Glossary.md` |
+| AI playbook | `SKILL.md` |
+| Full guide | `README.md` |
diff --git a/cra-kit/scripts/_cra-sbom-extract.sh b/cra-kit/scripts/_cra-sbom-extract.sh
new file mode 100644
index 000000000..591f720f3
--- /dev/null
+++ b/cra-kit/scripts/_cra-sbom-extract.sh
@@ -0,0 +1,271 @@
+#!/bin/sh
+# _cra-sbom-extract.sh — shared source-extraction helper for CRA Kit SBOM scripts.
+#
+# Source this file; do not execute it directly. It provides one function,
+# _cra_extract_srcs, that product SBOM scripts (generate-wolfssl-sbom.sh,
+# generate-wolfssh-sbom.sh, ...) call to auto-detect the list of .c files that
+# went into an embedded build.
+#
+# _cra_extract_srcs PRODUCT_DIR PRODUCT_NAME OUT_FILE
+#
+# Tries each extraction method in priority order. On success, writes the
+# sorted, de-duplicated list of .c paths to OUT_FILE and returns 0. If a
+# method is selected (its env var is set) but yields no sources, prints an
+# error to stderr and returns 1. If no extraction env var is set at all,
+# returns 2 so the caller can fall back to its default source glob.
+#
+# Methods, in priority order:
+# 1. CRA_SBOM_SRCS_FILE — copy verbatim to OUT_FILE (safety net; callers
+# normally handle this themselves before calling)
+# 2. CRA_SBOM_KEIL_PROJECT — parse .uvprojx, filter to PRODUCT_DIR
+# 3. CRA_SBOM_IAR_PROJECT — parse .ewp, filter to PRODUCT_DIR
+# 4. CRA_SBOM_MAKEFILE_DIR — `make -n`, grep for PRODUCT_DIR/...*.c
+# 5. CRA_SBOM_BUILD_DIR — jq filter compile_commands.json to PRODUCT_DIR
+# none set — return 2
+#
+# PRODUCT_DIR: absolute path to the product source tree; used to filter paths.
+# PRODUCT_NAME: short name for log messages and the wolfssl CMSIS-Pack special
+# case (e.g. "wolfssl", "wolfssh", "wolftpm").
+# OUT_FILE: path the caller has already created (mktemp) for the result.
+#
+# The function appends any temp files it creates to _cra_auto_tempfiles so the
+# caller's EXIT trap can clean them up. CRA_SBOM_NO_HASH is honoured by callers,
+# not here: a caller that sees CRA_SBOM_NO_HASH=true should skip hashing (and
+# thus skip this function) entirely.
+
+_cra_extract_srcs() {
+ _cra_product_dir="$1"
+ _cra_product_name="$2"
+ _cra_out_file="$3"
+
+ if [ -z "$_cra_product_dir" ] || [ -z "$_cra_product_name" ] || [ -z "$_cra_out_file" ]; then
+ echo "ERROR: _cra_extract_srcs requires PRODUCT_DIR PRODUCT_NAME OUT_FILE." >&2
+ return 1
+ fi
+
+ # Method 1: explicit source list file. Callers usually handle this before
+ # calling us, but honour it here too so the function is safe to call blindly.
+ if [ -n "${CRA_SBOM_SRCS_FILE:-}" ]; then
+ if [ ! -f "$CRA_SBOM_SRCS_FILE" ]; then
+ echo "ERROR: CRA_SBOM_SRCS_FILE=$CRA_SBOM_SRCS_FILE not found." >&2
+ return 1
+ fi
+ sort -u "$CRA_SBOM_SRCS_FILE" > "$_cra_out_file" || {
+ echo "ERROR: failed to read CRA_SBOM_SRCS_FILE=$CRA_SBOM_SRCS_FILE." >&2
+ return 1
+ }
+ if [ ! -s "$_cra_out_file" ]; then
+ echo "ERROR: CRA_SBOM_SRCS_FILE=$CRA_SBOM_SRCS_FILE is empty." >&2
+ return 1
+ fi
+ _cra_n=$(wc -l < "$_cra_out_file" | tr -d ' ')
+ echo " Using $_cra_n sources from CRA_SBOM_SRCS_FILE"
+ return 0
+ fi
+
+ # Method 2: Keil .uvprojx
+ if [ -n "${CRA_SBOM_KEIL_PROJECT:-}" ]; then
+ if [ ! -f "$CRA_SBOM_KEIL_PROJECT" ]; then
+ echo "ERROR: CRA_SBOM_KEIL_PROJECT=$CRA_SBOM_KEIL_PROJECT not found." >&2
+ return 1
+ fi
+ if ! command -v python3 >/dev/null 2>&1; then
+ echo "ERROR: python3 is required to parse a Keil .uvprojx file." >&2
+ return 1
+ fi
+ # The CMSIS Pack RTE lookup only applies when PRODUCT_NAME=wolfssl:
+ # wolfSSL ships a CMSIS Pack whose sources live in the installed pack,
+ # not in the project. Other products have no CMSIS pack, so the parser
+ # skips that path and enumerates / entries directly.
+ python3 - "$CRA_SBOM_KEIL_PROJECT" "$_cra_product_dir" "$_cra_product_name" \
+ > "$_cra_out_file" <<'PYEOF' || {
+import sys, os, glob, xml.etree.ElementTree as ET
+
+proj_file = sys.argv[1]
+product_dir = sys.argv[2] if len(sys.argv) > 2 else ''
+product_name = sys.argv[3] if len(sys.argv) > 3 else ''
+proj_dir = os.path.dirname(os.path.abspath(proj_file))
+proj = ET.parse(proj_file)
+paths = set()
+
+rte = proj.find('.//RTE')
+# CMSIS Pack RTE special case: wolfSSL only.
+if (product_name == 'wolfssl' and rte is not None
+ and rte.find('.//component[@Cvendor="wolfSSL"]') is not None):
+ pdsc_candidates = sorted(glob.glob(
+ os.path.expanduser('~/.arm/Packs/wolfSSL/wolfSSL/*/wolfSSL.pdsc')
+ ))
+ if os.name == 'nt':
+ appdata = os.environ.get('LOCALAPPDATA', '')
+ pdsc_candidates += sorted(glob.glob(
+ os.path.join(appdata, 'Arm', 'Packs', 'wolfSSL', 'wolfSSL', '*', 'wolfSSL.pdsc')
+ ))
+ if pdsc_candidates:
+ pdsc_file = pdsc_candidates[-1]
+ pack_dir = os.path.dirname(pdsc_file)
+ pdsc = ET.parse(pdsc_file)
+ for f in pdsc.findall('.//file[@category="source"]'):
+ name = f.get('name', '')
+ if name.lower().endswith('.c'):
+ paths.add(os.path.normpath(os.path.join(pack_dir, name.replace('\\', '/'))))
+ elif product_dir and os.path.isdir(product_dir):
+ # Pack not installed locally; enumerate sources from PRODUCT_DIR.
+ for subdir in ('wolfcrypt/src', 'src'):
+ d = os.path.join(product_dir, subdir)
+ if os.path.isdir(d):
+ for name in os.listdir(d):
+ if name.endswith('.c'):
+ paths.add(os.path.join(d, name))
+else:
+ for file_elem in proj.findall('.//File'):
+ fp = file_elem.find('FilePath')
+ ft = file_elem.find('FileType')
+ if fp is None or not fp.text:
+ continue
+ ftype = int(ft.text) if ft is not None and ft.text else 0
+ if ftype == 1 or fp.text.lower().endswith('.c'):
+ abs_path = os.path.normpath(
+ os.path.join(proj_dir, fp.text.replace('\\', '/'))
+ )
+ paths.add(abs_path)
+
+for p in sorted(paths):
+ print(p)
+PYEOF
+ echo "ERROR: failed to parse Keil project $CRA_SBOM_KEIL_PROJECT." >&2
+ return 1
+ }
+ # wolfssl's CMSIS-Pack branch legitimately emits paths under the installed
+ # pack dir (~/.arm/Packs/...), which lie OUTSIDE PRODUCT_DIR; filtering
+ # would wrongly drop them. The wolfssl parser already constrains its
+ # output to wolfssl sources, so skip the PRODUCT_DIR filter for wolfssl.
+ if [ "$_cra_product_name" != "wolfssl" ]; then
+ _cra_filter_to_product "$_cra_out_file" "$_cra_product_dir"
+ fi
+ if [ ! -s "$_cra_out_file" ]; then
+ echo "ERROR: CRA_SBOM_KEIL_PROJECT is set but no $_cra_product_name sources were extracted from $CRA_SBOM_KEIL_PROJECT." >&2
+ return 1
+ fi
+ _cra_n=$(wc -l < "$_cra_out_file" | tr -d ' ')
+ echo " Auto-extracted $_cra_n $_cra_product_name sources from Keil project"
+ return 0
+ fi
+
+ # Method 3: IAR .ewp
+ if [ -n "${CRA_SBOM_IAR_PROJECT:-}" ]; then
+ if [ ! -f "$CRA_SBOM_IAR_PROJECT" ]; then
+ echo "ERROR: CRA_SBOM_IAR_PROJECT=$CRA_SBOM_IAR_PROJECT not found." >&2
+ return 1
+ fi
+ if ! command -v python3 >/dev/null 2>&1; then
+ echo "ERROR: python3 is required to parse an IAR .ewp file." >&2
+ return 1
+ fi
+ python3 - "$CRA_SBOM_IAR_PROJECT" > "$_cra_out_file" <<'PYEOF' || {
+import sys, os, xml.etree.ElementTree as ET
+
+def is_excluded(file_elem):
+ return file_elem.find('excluded') is not None
+
+proj_file = sys.argv[1]
+proj_dir = os.path.dirname(os.path.abspath(proj_file))
+proj = ET.parse(proj_file)
+paths = set()
+
+for file_elem in proj.findall('.//file'):
+ if is_excluded(file_elem):
+ continue
+ name_elem = file_elem.find('name')
+ if name_elem is None or not name_elem.text:
+ continue
+ raw = name_elem.text
+ if not raw.lower().endswith('.c'):
+ continue
+ resolved = raw.replace('$PROJ_DIR$', proj_dir)
+ paths.add(os.path.normpath(resolved.replace('\\', '/')))
+
+for p in sorted(paths):
+ print(p)
+PYEOF
+ echo "ERROR: failed to parse IAR project $CRA_SBOM_IAR_PROJECT." >&2
+ return 1
+ }
+ # Preserve wolfssl's original behaviour (all project .c, unfiltered); other
+ # products filter to PRODUCT_DIR to drop demo/BSP sources from the project.
+ if [ "$_cra_product_name" != "wolfssl" ]; then
+ _cra_filter_to_product "$_cra_out_file" "$_cra_product_dir"
+ fi
+ if [ ! -s "$_cra_out_file" ]; then
+ echo "ERROR: CRA_SBOM_IAR_PROJECT is set but no $_cra_product_name sources were extracted from $CRA_SBOM_IAR_PROJECT." >&2
+ return 1
+ fi
+ _cra_n=$(wc -l < "$_cra_out_file" | tr -d ' ')
+ echo " Auto-extracted $_cra_n $_cra_product_name sources from IAR project"
+ return 0
+ fi
+
+ # Method 4: Makefile dry-run
+ if [ -n "${CRA_SBOM_MAKEFILE_DIR:-}" ]; then
+ if [ ! -d "$CRA_SBOM_MAKEFILE_DIR" ]; then
+ echo "ERROR: CRA_SBOM_MAKEFILE_DIR=$CRA_SBOM_MAKEFILE_DIR is not a directory." >&2
+ return 1
+ fi
+ if ! command -v make >/dev/null 2>&1; then
+ echo "ERROR: make is required to auto-extract sources from CRA_SBOM_MAKEFILE_DIR." >&2
+ return 1
+ fi
+ # `make -n` (dry run) emits the compile commands; pull out any .c path
+ # that lives under PRODUCT_DIR. grep -F on the dir keeps the pattern
+ # literal (PRODUCT_DIR may contain regex metacharacters).
+ make -C "$CRA_SBOM_MAKEFILE_DIR" -n 2>/dev/null \
+ | grep -oE '[^ ]+\.c' \
+ | grep -F "$_cra_product_dir/" \
+ | sort -u > "$_cra_out_file" || true
+ if [ ! -s "$_cra_out_file" ]; then
+ echo "ERROR: CRA_SBOM_MAKEFILE_DIR is set but make yielded no $_cra_product_name sources." >&2
+ echo " Ensure 'make -n' in $CRA_SBOM_MAKEFILE_DIR references .c files under $_cra_product_dir." >&2
+ return 1
+ fi
+ _cra_n=$(wc -l < "$_cra_out_file" | tr -d ' ')
+ echo " Auto-extracted $_cra_n $_cra_product_name sources via Makefile (CRA_SBOM_MAKEFILE_DIR=$CRA_SBOM_MAKEFILE_DIR)"
+ return 0
+ fi
+
+ # Method 5: compile_commands.json (CMake / Zephyr / ESP-IDF)
+ if [ -n "${CRA_SBOM_BUILD_DIR:-}" ] && [ -f "$CRA_SBOM_BUILD_DIR/compile_commands.json" ]; then
+ if ! command -v jq >/dev/null 2>&1; then
+ echo "ERROR: jq is required to auto-extract sources from compile_commands.json." >&2
+ echo " Install jq, or set CRA_SBOM_SRCS_FILE manually. See SRCS-FILE-HOWTO.md." >&2
+ return 1
+ fi
+ jq -r '.[].file' "$CRA_SBOM_BUILD_DIR/compile_commands.json" \
+ | grep -F "$_cra_product_dir/" \
+ | grep -E '\.c$' \
+ | sort -u > "$_cra_out_file" || true
+ if [ ! -s "$_cra_out_file" ]; then
+ echo "ERROR: compile_commands.json in $CRA_SBOM_BUILD_DIR yielded no $_cra_product_name sources under $_cra_product_dir." >&2
+ return 1
+ fi
+ _cra_n=$(wc -l < "$_cra_out_file" | tr -d ' ')
+ echo " Auto-extracted $_cra_n $_cra_product_name sources from compile_commands.json"
+ return 0
+ fi
+
+ # No extraction method selected — caller should use its default glob.
+ return 2
+}
+
+# _cra_filter_to_product OUT_FILE PRODUCT_DIR
+# In-place filter: keep only lines that are .c paths under PRODUCT_DIR.
+# grep -F keeps PRODUCT_DIR literal (it may contain regex metacharacters).
+_cra_filter_to_product() {
+ _cra_f="$1"
+ _cra_dir="$2"
+ _cra_tmp=$(mktemp "${TMPDIR:-/tmp}/cra-filter.XXXXXX") || {
+ echo "ERROR: mktemp failed while filtering sources." >&2
+ return 1
+ }
+ _cra_auto_tempfiles="${_cra_auto_tempfiles:-} $_cra_tmp"
+ grep -F "$_cra_dir/" "$_cra_f" | grep -E '\.c$' | sort -u > "$_cra_tmp" || true
+ mv -f "$_cra_tmp" "$_cra_f"
+}
diff --git a/cra-kit/scripts/generate-embedded-sbom.sh b/cra-kit/scripts/generate-embedded-sbom.sh
new file mode 100755
index 000000000..146606c5c
--- /dev/null
+++ b/cra-kit/scripts/generate-embedded-sbom.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+# Force embedded gen-sbom (user_settings.h + --srcs) into wolfssl-component-embedded/.
+set -eu
+
+SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)
+KIT_DIR=$(dirname "$SCRIPT_DIR")
+export CRA_SBOM_MODE=embedded
+export CRA_SBOM_OUT_DIR="$KIT_DIR/auditor-packet/wolfssl-component-embedded"
+exec "$SCRIPT_DIR/generate-wolfssl-sbom.sh"
diff --git a/cra-kit/scripts/generate-wolfboot-sbom.sh b/cra-kit/scripts/generate-wolfboot-sbom.sh
new file mode 100755
index 000000000..dcc8849d9
--- /dev/null
+++ b/cra-kit/scripts/generate-wolfboot-sbom.sh
@@ -0,0 +1,292 @@
+#!/bin/sh
+# Generate wolfBoot component SBOMs via gen-sbom.
+#
+# wolfBoot is build-configuration-specific: its compiled source list depends
+# on TARGET, SIGN, HASH, and EXT_FLASH. This script runs `make -n` against
+# the wolfBoot tree to extract the exact set of .c files for the requested
+# configuration, then calls gen-sbom directly.
+#
+# wolfcrypt sources are compiled directly into the wolfBoot image (there is
+# no separate wolfssl shared library). They appear in OBJS alongside core
+# wolfBoot sources and are therefore included as wolfBoot's own component
+# sources, not as a separate dependency.
+#
+# Required variables:
+# WOLFBOOT_DIR=path/to/wolfBoot (source tree root)
+# WOLFBOOT_TARGET= (e.g. stm32h7, x86_64_efi)
+# WOLFBOOT_SIGN= (e.g. ECC256, RSA2048, ED25519)
+#
+# Optional variables:
+# WOLFBOOT_HASH= (default: SHA256)
+# WOLFBOOT_EXT_FLASH=<0|1> (default: 0)
+# CRA_PYTHON=python3 (Python interpreter with gen-sbom deps)
+# CRA_LICENSE_OVERRIDE= (e.g. LicenseRef-wolfBoot-Commercial)
+# CRA_LICENSE_TEXT= (required when CRA_LICENSE_OVERRIDE is a
+# LicenseRef-* id: plain-text license
+# embedded in the SBOM)
+# CRA_SBOM_OUT_DIR= (override output directory)
+# CRA_SBOM_SRCS_FILE=path explicit .c file list (overrides make -n)
+# CRA_SBOM_KEIL_PROJECT=path auto-extract from Keil .uvprojx (overrides make -n)
+# CRA_SBOM_IAR_PROJECT=path auto-extract from IAR .ewp (overrides make -n)
+# CRA_SBOM_NO_HASH=true emit SBOM without an artifact hash, skipping
+# the source list — for NDA customers who cannot
+# share source lists; WARNING: not suitable for
+# production compliance
+set -eu
+
+SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)
+KIT_DIR=$(dirname "$SCRIPT_DIR")
+
+# Shared source-extraction helper. It appends temp files it creates to
+# _cra_auto_tempfiles, so the EXIT trap below cleans both lists.
+_auto_tempfiles=""
+_cra_auto_tempfiles=""
+trap 'rm -f ${_auto_tempfiles:-} ${_cra_auto_tempfiles:-}' EXIT
+
+# shellcheck source=_cra-sbom-extract.sh disable=SC1091
+. "$SCRIPT_DIR/_cra-sbom-extract.sh"
+
+# Default wolfBoot directory: sibling of the wolfssl-examples checkout.
+# shellcheck disable=SC2015
+WOLFBOOT_DIR=${WOLFBOOT_DIR:-$(cd "$KIT_DIR/../../wolfBoot" 2>/dev/null && pwd || true)}
+
+if [ -z "${WOLFBOOT_DIR:-}" ] || [ ! -d "$WOLFBOOT_DIR" ]; then
+ echo "ERROR: wolfBoot source not found." >&2
+ echo " Set WOLFBOOT_DIR to your wolfBoot checkout." >&2
+ exit 1
+fi
+
+# CRA_SBOM_NO_HASH skips the make -n source extraction entirely, so TARGET/SIGN
+# (which only drive that extraction) are not required in that mode.
+if [ "${CRA_SBOM_NO_HASH:-}" = "true" ] || [ "${CRA_SBOM_NO_HASH:-}" = "1" ]; then
+ _no_hash=1
+else
+ _no_hash=0
+fi
+
+if [ "$_no_hash" = "0" ]; then
+ if [ -z "${WOLFBOOT_TARGET:-}" ]; then
+ echo "ERROR: WOLFBOOT_TARGET is not set." >&2
+ echo " Example: WOLFBOOT_TARGET=stm32h7 $0" >&2
+ exit 1
+ fi
+
+ if [ -z "${WOLFBOOT_SIGN:-}" ]; then
+ echo "ERROR: WOLFBOOT_SIGN is not set." >&2
+ echo " Example: WOLFBOOT_SIGN=ECC256 $0" >&2
+ exit 1
+ fi
+fi
+
+WOLFBOOT_HASH=${WOLFBOOT_HASH:-SHA256}
+WOLFBOOT_EXT_FLASH=${WOLFBOOT_EXT_FLASH:-0}
+
+OUT_DIR=${CRA_SBOM_OUT_DIR:-"$KIT_DIR/auditor-packet/wolfboot-component"}
+
+# gen-sbom lives inside the wolfssl submodule under wolfBoot; GEN_SBOM env var overrides.
+GEN_SBOM="${GEN_SBOM:-$WOLFBOOT_DIR/lib/wolfssl/scripts/gen-sbom}"
+if [ ! -f "$GEN_SBOM" ]; then
+ echo "ERROR: gen-sbom not found at $GEN_SBOM" >&2
+ echo " Ensure the wolfssl submodule is initialized:" >&2
+ echo " git -C \"$WOLFBOOT_DIR\" submodule update --init lib/wolfssl" >&2
+ exit 1
+fi
+
+# Extract version from wolfBoot's version header.
+VERSION=$(sed -n \
+ 's/.*LIBWOLFBOOT_VERSION_STRING[[:space:]]*"\([^"]*\)".*/\1/p' \
+ "$WOLFBOOT_DIR/include/wolfboot/version.h")
+if [ -z "$VERSION" ]; then
+ echo "ERROR: could not detect wolfBoot version from $WOLFBOOT_DIR/include/wolfboot/version.h" >&2
+ exit 1
+fi
+
+mkdir -p "$OUT_DIR"
+CDX_OUT="$OUT_DIR/wolfboot-${VERSION}.cdx.json"
+SPDX_OUT="$OUT_DIR/wolfboot-${VERSION}.spdx.json"
+
+echo "wolfBoot tree: $WOLFBOOT_DIR"
+if [ "$_no_hash" = "0" ]; then
+ echo "Configuration: TARGET=$WOLFBOOT_TARGET SIGN=$WOLFBOOT_SIGN HASH=$WOLFBOOT_HASH EXT_FLASH=$WOLFBOOT_EXT_FLASH"
+fi
+echo "Version: $VERSION"
+echo "Outputs: $CDX_OUT"
+echo " $SPDX_OUT"
+if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then
+ echo "License override: $CRA_LICENSE_OVERRIDE"
+fi
+
+# A LicenseRef-* override requires the license text to be embedded in the SBOM
+# (SPDX 2.3 §10.1). gen-sbom hard-fails without it; catch the omission here.
+if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then
+ case "$CRA_LICENSE_OVERRIDE" in
+ LicenseRef-*)
+ if [ -z "${CRA_LICENSE_TEXT:-}" ]; then
+ echo "ERROR: CRA_LICENSE_OVERRIDE=$CRA_LICENSE_OVERRIDE is a LicenseRef-* identifier," >&2
+ echo " but CRA_LICENSE_TEXT is not set. SPDX 2.3 requires the license text to be" >&2
+ echo " embedded for any LicenseRef-* used in licenseConcluded/licenseDeclared." >&2
+ echo " Re-run with CRA_LICENSE_TEXT=/path/to/wolfboot-license.txt" >&2
+ exit 1
+ fi
+ if [ ! -f "$CRA_LICENSE_TEXT" ]; then
+ echo "ERROR: CRA_LICENSE_TEXT=$CRA_LICENSE_TEXT not found." >&2
+ exit 1
+ fi
+ ;;
+ esac
+fi
+
+# Canonicalize CRA_LICENSE_TEXT to an absolute path.
+if [ -n "${CRA_LICENSE_TEXT:-}" ] && [ -f "$CRA_LICENSE_TEXT" ]; then
+ CRA_LICENSE_TEXT=$(CDPATH='' cd -- "$(dirname -- "$CRA_LICENSE_TEXT")" && pwd)/$(basename -- "$CRA_LICENSE_TEXT")
+fi
+
+# CRA_SBOM_NO_HASH (resolved to $_no_hash above) emits a placeholder checksum
+# and skips the source list entirely (NDA customers who cannot share sources).
+# Extract the configuration-specific source list.
+#
+# Priority order:
+# 1. CRA_SBOM_SRCS_FILE explicit .c list
+# 2. CRA_SBOM_KEIL_PROJECT Keil .uvprojx
+# 3. CRA_SBOM_IAR_PROJECT IAR .ewp
+# 4. make -n TARGET/SIGN wolfBoot's product-specific default (below)
+#
+# The shared helper handles 1-3. wolfBoot's Makefile path is product-specific
+# (driven by TARGET/SIGN/HASH/EXT_FLASH, not the generic CRA_SBOM_MAKEFILE_DIR /
+# compile_commands.json handlers), so we blank those two env vars before calling
+# the helper and run our own make -n below when no IDE project is set.
+if [ "$_no_hash" = "0" ]; then
+_srcs_tmp=$(mktemp "${TMPDIR:-/tmp}/wolfboot-sbom-srcs.XXXXXX")
+_auto_tempfiles="${_auto_tempfiles:-} $_srcs_tmp"
+
+_saved_makefile_dir="${CRA_SBOM_MAKEFILE_DIR:-}"
+_saved_build_dir="${CRA_SBOM_BUILD_DIR:-}"
+CRA_SBOM_MAKEFILE_DIR=""
+CRA_SBOM_BUILD_DIR=""
+
+_cra_rc=0
+_cra_extract_srcs "$WOLFBOOT_DIR" "wolfboot" "$_srcs_tmp" || _cra_rc=$?
+
+CRA_SBOM_MAKEFILE_DIR="$_saved_makefile_dir"
+CRA_SBOM_BUILD_DIR="$_saved_build_dir"
+
+if [ "$_cra_rc" -eq 0 ]; then
+ _n=$(wc -l < "$_srcs_tmp" | tr -d ' ')
+ echo " Extracted $_n source files (from IDE project / source list)"
+elif [ "$_cra_rc" -eq 2 ]; then
+ # No IDE project set: use wolfBoot's product-specific make -n extraction.
+ #
+ # `make -n TARGET=... SIGN=...` prints every command make would run without
+ # executing them. The compiler invocations include every .c file on the
+ # wolfBoot link line, including both core wolfBoot sources and wolfcrypt
+ # files compiled inline. grep -oE pulls out every .c argument; sort -u
+ # deduplicates.
+ echo "Extracting source list via make -n TARGET=$WOLFBOOT_TARGET SIGN=$WOLFBOOT_SIGN ..."
+ make --no-print-directory \
+ -C "$WOLFBOOT_DIR" \
+ -n \
+ TARGET="$WOLFBOOT_TARGET" \
+ SIGN="$WOLFBOOT_SIGN" \
+ HASH="$WOLFBOOT_HASH" \
+ EXT_FLASH="$WOLFBOOT_EXT_FLASH" \
+ 2>/dev/null \
+ | grep -oE '[^ ]+\.c' \
+ | grep -v '\.h' \
+ | sort -u > "$_srcs_tmp" || true
+
+ if [ ! -s "$_srcs_tmp" ]; then
+ echo "ERROR: make -n yielded no .c source files for TARGET=$WOLFBOOT_TARGET SIGN=$WOLFBOOT_SIGN." >&2
+ echo " Check that TARGET and SIGN are valid for this wolfBoot tree." >&2
+ exit 1
+ fi
+
+ _n=$(wc -l < "$_srcs_tmp" | tr -d ' ')
+ echo " Extracted $_n source files"
+else
+ exit 1
+fi
+
+# Resolve paths to absolute: make -n emits relative paths; gen-sbom needs to
+# open the files to compute gitoid hashes.
+_srcs_abs_tmp=$(mktemp "${TMPDIR:-/tmp}/wolfboot-sbom-srcs-abs.XXXXXX")
+_auto_tempfiles="${_auto_tempfiles:-} $_srcs_abs_tmp"
+
+while IFS= read -r _src; do
+ [ -n "$_src" ] || continue
+ # Paths from make -n are relative to wolfBoot root.
+ if [ "${_src#/}" = "$_src" ]; then
+ _abs="$WOLFBOOT_DIR/$_src"
+ else
+ _abs="$_src"
+ fi
+ # Skip paths that do not exist on disk (generated files, stubs, etc.).
+ if [ -f "$_abs" ]; then
+ echo "$_abs"
+ fi
+done < "$_srcs_tmp" > "$_srcs_abs_tmp"
+
+if [ ! -s "$_srcs_abs_tmp" ]; then
+ echo "ERROR: no source files from make -n exist on disk." >&2
+ echo " Verify WOLFBOOT_DIR=$WOLFBOOT_DIR and submodules are initialized." >&2
+ exit 1
+fi
+
+_n_abs=$(wc -l < "$_srcs_abs_tmp" | tr -d ' ')
+echo " Resolved $_n_abs paths ($(( _n - _n_abs )) non-existent skipped)"
+else
+ echo "==> CRA_SBOM_NO_HASH=true: emitting SBOM without artifact hash."
+ echo " WARNING: not suitable for production CRA compliance." >&2
+fi
+
+# Preprocess build settings for gen-sbom --options-h.
+#
+# wolfBoot has no options.h (it is not an autotools project). We use
+# cc -dM -E on the host with wolfBoot's include dirs to produce a flat
+# #define file that gen-sbom can parse for algorithm enablement.
+_defines_tmp=$(mktemp "${TMPDIR:-/tmp}/wolfboot-sbom-defines.XXXXXX")
+_auto_tempfiles="${_auto_tempfiles:-} $_defines_tmp"
+
+CC=${CC:-cc}
+echo " Preprocessing build settings via $CC -dM -E ..."
+if ! "$CC" -dM -E \
+ -I"$WOLFBOOT_DIR/include" \
+ -I"$WOLFBOOT_DIR/lib/wolfssl" \
+ -I"$WOLFBOOT_DIR/lib/wolfssl/wolfcrypt/src" \
+ -DWOLFSSL_USER_SETTINGS \
+ -x c /dev/null > "$_defines_tmp" 2>/dev/null; then
+ echo "ERROR: $CC -dM -E failed; install a host C compiler or set CC." >&2
+ exit 1
+fi
+
+# Build gen-sbom argument list.
+_PYTHON=${CRA_PYTHON:-python3}
+command -v "$_PYTHON" >/dev/null 2>&1 || \
+ { echo "ERROR: $_PYTHON not found. Set CRA_PYTHON to your Python interpreter." >&2; exit 1; }
+
+_license_override=${CRA_LICENSE_OVERRIDE:-GPL-3.0-only}
+if [ "$_no_hash" = "1" ]; then
+ set -- --no-artifact-hash
+else
+ set -- --srcs-file "$_srcs_abs_tmp"
+fi
+set -- "$@" \
+ --cdx-out "$CDX_OUT" \
+ --spdx-out "$SPDX_OUT" \
+ --license-override "$_license_override"
+
+if [ -n "${CRA_LICENSE_TEXT:-}" ]; then
+ set -- "$@" --license-text "$CRA_LICENSE_TEXT"
+fi
+
+echo "==> Running gen-sbom ..."
+"$_PYTHON" "$GEN_SBOM" \
+ --name wolfboot \
+ --version "$VERSION" \
+ --supplier "wolfSSL Inc." \
+ --license-file "$WOLFBOOT_DIR/LICENSE" \
+ --options-h "$_defines_tmp" \
+ "$@"
+
+echo "SBOM written:"
+echo " $CDX_OUT"
+echo " $SPDX_OUT"
diff --git a/cra-kit/scripts/generate-wolfhsm-sbom.sh b/cra-kit/scripts/generate-wolfhsm-sbom.sh
new file mode 100755
index 000000000..3fa5e927f
--- /dev/null
+++ b/cra-kit/scripts/generate-wolfhsm-sbom.sh
@@ -0,0 +1,226 @@
+#!/bin/sh
+# Generate wolfHSM component SBOM (embedded gen-sbom path).
+#
+# wolfHSM is a Makefile-only library with no autotools configure step.
+# This script always uses the embedded gen-sbom path: it enumerates
+# wolfHSM sources directly from the source tree and derives compile-time
+# defines via CC -dM -E (or pcpp when available).
+#
+# Required variables:
+# WOLFSSL_DIR=path/to/wolfssl (source tree root; must contain scripts/gen-sbom)
+# WOLFHSM_DIR=path/to/wolfHSM (source tree root)
+#
+# Optional variables:
+# CC= (default: cc; set for cross builds)
+# CRA_PYTHON=python3 (interpreter with pcpp)
+# CRA_LICENSE_OVERRIDE= (e.g. LicenseRef-wolfSSL-Commercial)
+# CRA_LICENSE_TEXT= (required when CRA_LICENSE_OVERRIDE is LicenseRef-*)
+# WOLFHSM_BUILD_DIR=path auto-extract from compile_commands.json
+# CRA_SBOM_SRCS_FILE=path explicit .c file list, one per line
+# CRA_SBOM_KEIL_PROJECT=path auto-extract from Keil .uvprojx
+# CRA_SBOM_IAR_PROJECT=path auto-extract from IAR .ewp
+# CRA_SBOM_MAKEFILE_DIR=path auto-extract via make -n dry-run
+# CRA_SBOM_NO_HASH=true emit SBOM without an artifact hash (NDA
+# customers who cannot share source lists;
+# WARNING: not suitable for production compliance)
+set -eu
+
+. "$(dirname "$0")/_cra-sbom-extract.sh"
+
+# Accumulator for temp files; cleaned up on exit. The shared extraction library
+# appends to _cra_auto_tempfiles, so trap both.
+_auto_tempfiles=""
+_cra_auto_tempfiles=""
+trap 'rm -f ${_auto_tempfiles:-} ${_cra_auto_tempfiles:-}' EXIT
+
+SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)
+KIT_DIR=$(dirname "$SCRIPT_DIR")
+
+# Locate WOLFSSL_DIR (default: sibling of wolfssl-examples).
+# shellcheck disable=SC2015
+WOLFSSL_DIR=${WOLFSSL_DIR:-$(cd "$KIT_DIR/../../wolfssl" 2>/dev/null && pwd || true)}
+# WOLFHSM_DIR has no sensible default; must be explicit.
+WOLFHSM_DIR=${WOLFHSM_DIR:-}
+
+if [ -z "${WOLFSSL_DIR:-}" ] || [ ! -d "$WOLFSSL_DIR" ]; then
+ echo "ERROR: wolfSSL source not found." >&2
+ echo " Set WOLFSSL_DIR to your wolfssl checkout." >&2
+ exit 1
+fi
+
+if [ -z "${WOLFHSM_DIR:-}" ] || [ ! -d "$WOLFHSM_DIR" ]; then
+ echo "ERROR: WOLFHSM_DIR is not set or not a directory." >&2
+ echo " Set WOLFHSM_DIR to your wolfHSM source tree." >&2
+ exit 1
+fi
+
+GEN="$WOLFSSL_DIR/scripts/gen-sbom"
+if [ ! -f "$GEN" ]; then
+ echo "ERROR: $GEN not found (need wolfSSL with SBOM support)." >&2
+ exit 1
+fi
+
+# Parse version from ChangeLog.md: first line matching "## wolfHSM Release vX.Y.Z"
+VERSION=$(sed -n 's/^# wolfHSM Release v\([0-9][0-9.]*\).*/\1/p' \
+ "$WOLFHSM_DIR/ChangeLog.md" 2>/dev/null | head -1)
+if [ -z "$VERSION" ]; then
+ echo "ERROR: could not parse version from $WOLFHSM_DIR/ChangeLog.md." >&2
+ exit 1
+fi
+
+OUT_DIR=${CRA_SBOM_OUT_DIR:-"$KIT_DIR/auditor-packet/wolfhsm-component"}
+mkdir -p "$OUT_DIR"
+CDX_OUT="$OUT_DIR/wolfhsm-${VERSION}.cdx.json"
+SPDX_OUT="$OUT_DIR/wolfhsm-${VERSION}.spdx.json"
+
+echo "wolfHSM tree: $WOLFHSM_DIR"
+echo "wolfSSL tree: $WOLFSSL_DIR"
+echo "Version: $VERSION"
+echo "Outputs: $CDX_OUT"
+echo " $SPDX_OUT"
+if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then
+ echo "License override: $CRA_LICENSE_OVERRIDE"
+fi
+
+# A LicenseRef-* override requires the licence text to be embedded (SPDX 2.3 §10.1).
+if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then
+ case "$CRA_LICENSE_OVERRIDE" in
+ LicenseRef-*)
+ if [ -z "${CRA_LICENSE_TEXT:-}" ]; then
+ echo "ERROR: CRA_LICENSE_OVERRIDE=$CRA_LICENSE_OVERRIDE is a LicenseRef-* identifier," >&2
+ echo " but CRA_LICENSE_TEXT is not set. SPDX 2.3 requires the licence text" >&2
+ echo " to be embedded. Re-run with CRA_LICENSE_TEXT=/path/to/license.txt." >&2
+ exit 1
+ fi
+ if [ ! -f "$CRA_LICENSE_TEXT" ]; then
+ echo "ERROR: CRA_LICENSE_TEXT=$CRA_LICENSE_TEXT not found." >&2
+ exit 1
+ fi
+ ;;
+ esac
+fi
+
+# Canonicalize CRA_LICENSE_TEXT to absolute path (subshells resolve relative paths
+# against their own CWD; gen-sbom may be invoked from a different directory).
+if [ -n "${CRA_LICENSE_TEXT:-}" ] && [ -f "$CRA_LICENSE_TEXT" ]; then
+ CRA_LICENSE_TEXT=$(CDPATH='' cd -- "$(dirname -- "$CRA_LICENSE_TEXT")" && pwd)/$(basename -- "$CRA_LICENSE_TEXT")
+ echo "License text: $CRA_LICENSE_TEXT"
+fi
+
+# Pick a Python that can `import pcpp`.
+_python_with_pcpp() {
+ for py in ${CRA_PYTHON:-} python3 python; do
+ [ -n "$py" ] || continue
+ if command -v "$py" >/dev/null 2>&1 && \
+ "$py" -c "import pcpp" 2>/dev/null; then
+ echo "$py"
+ return 0
+ fi
+ done
+ return 1
+}
+
+echo "==> Embedded path: gen-sbom with CC -dM -E (no user_settings.h)"
+
+# CRA_SBOM_NO_HASH emits a placeholder checksum and skips the source list
+# entirely (for NDA customers who cannot share source lists).
+if [ "${CRA_SBOM_NO_HASH:-}" = "true" ] || [ "${CRA_SBOM_NO_HASH:-}" = "1" ]; then
+ echo " NOTE: CRA_SBOM_NO_HASH=true: emitting SBOM without artifact hash."
+ echo " WARNING: not suitable for production CRA compliance." >&2
+ _hash_arg="--no-artifact-hash"
+else
+ _srcs_file=$(mktemp "${TMPDIR:-/tmp}/wolfhsm-srcs.XXXXXX")
+ _auto_tempfiles="${_auto_tempfiles:-} $_srcs_file"
+
+ # Allow WOLFHSM_BUILD_DIR to feed compile_commands.json extraction. The
+ # shared library reads CRA_SBOM_BUILD_DIR; map our env var onto it.
+ CRA_SBOM_BUILD_DIR="${WOLFHSM_BUILD_DIR:-}"
+
+ _cra_rc=0
+ _cra_extract_srcs "$WOLFHSM_DIR" "wolfhsm" "$_srcs_file" || _cra_rc=$?
+
+ if [ "$_cra_rc" -eq 2 ]; then
+ # No extraction method active: enumerate all wolfHSM C sources via find.
+ # find is used (not glob) because wolfHSM sources span subdirectories.
+ find "$WOLFHSM_DIR/src" -name "*.c" | sort > "$_srcs_file" || {
+ echo "ERROR: find failed on $WOLFHSM_DIR/src" >&2; exit 1
+ }
+ _n=$(wc -l < "$_srcs_file" | tr -d ' ')
+ echo " Source list: find $WOLFHSM_DIR/src -name '*.c' ($_n files)"
+ elif [ "$_cra_rc" -ne 0 ]; then
+ exit 1
+ fi
+
+ if [ ! -s "$_srcs_file" ]; then
+ echo "ERROR: no wolfHSM sources found in $WOLFHSM_DIR/src" >&2
+ exit 1
+ fi
+ _n=$(wc -l < "$_srcs_file" | tr -d ' ')
+ echo "NOTE: hashed $_n source file(s)"
+ _hash_arg="--srcs-file $_srcs_file"
+fi
+
+# Build license-override args.
+_license_args=""
+if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then
+ _license_args="--license-override $CRA_LICENSE_OVERRIDE"
+ if [ -n "${CRA_LICENSE_TEXT:-}" ]; then
+ _license_args="$_license_args --license-text $CRA_LICENSE_TEXT"
+ fi
+fi
+
+if _py=$(_python_with_pcpp); then
+ echo " Using $_py (pcpp) for --user-settings"
+ # wolfHSM has no user_settings.h equivalent; pass settings.h from wolfssl
+ # so gen-sbom has a preprocessable configuration source. The include path
+ # covers wolfHSM headers and the wolfssl tree.
+ SETTINGS_H="$WOLFSSL_DIR/wolfssl/wolfcrypt/settings.h"
+ if [ ! -f "$SETTINGS_H" ]; then
+ echo "ERROR: $SETTINGS_H not found." >&2
+ exit 1
+ fi
+ # shellcheck disable=SC2086
+ "$_py" "$GEN" \
+ --name wolfhsm \
+ --version "$VERSION" \
+ --supplier "wolfSSL Inc." \
+ --license-file "$WOLFHSM_DIR/LICENSING" \
+ --user-settings "$SETTINGS_H" \
+ --user-settings-include "$WOLFHSM_DIR" \
+ --user-settings-include "$WOLFSSL_DIR" \
+ ${_hash_arg} \
+ --cdx-out "$CDX_OUT" \
+ --spdx-out "$SPDX_OUT" \
+ ${_license_args}
+else
+ echo "NOTE: pcpp not found; using CC -dM -E -> --options-h"
+ echo " Install pcpp: python3 -m pip install pcpp"
+ echo " For cross builds: set CC="
+
+ CC=${CC:-cc}
+ _defines=$(mktemp "${TMPDIR:-/tmp}/wolfhsm-defines.XXXXXX")
+ _auto_tempfiles="${_auto_tempfiles:-} $_defines"
+ if ! "$CC" -dM -E \
+ -I"$WOLFHSM_DIR" \
+ -I"$WOLFSSL_DIR" \
+ -x c /dev/null >"$_defines" 2>/dev/null; then
+ echo "ERROR: $CC -dM -E failed; install pcpp or set CC to your cross-compiler." >&2
+ exit 1
+ fi
+
+ _python=python3
+ command -v python3 >/dev/null 2>&1 || _python=python
+ # shellcheck disable=SC2086
+ "$_python" "$GEN" \
+ --name wolfhsm \
+ --version "$VERSION" \
+ --supplier "wolfSSL Inc." \
+ --license-file "$WOLFHSM_DIR/LICENSING" \
+ --options-h "$_defines" \
+ ${_hash_arg} \
+ --cdx-out "$CDX_OUT" \
+ --spdx-out "$SPDX_OUT" \
+ ${_license_args}
+fi
+
+echo "Done."
diff --git a/cra-kit/scripts/generate-wolfmqtt-sbom.sh b/cra-kit/scripts/generate-wolfmqtt-sbom.sh
new file mode 100755
index 000000000..cfae983a8
--- /dev/null
+++ b/cra-kit/scripts/generate-wolfmqtt-sbom.sh
@@ -0,0 +1,292 @@
+#!/bin/sh
+# Generate wolfMQTT component SBOM (autotools make sbom, or embedded gen-sbom).
+#
+# Mode selection:
+# CRA_SBOM_MODE=autotools|embedded (default: autotools)
+# autotools: runs `make sbom` inside the wolfMQTT tree
+# embedded: runs gen-sbom directly over the MQTT protocol sources, hashing
+# them with an OmniBOR gitoid Merkle hash. Use this when wolfMQTT
+# is compiled into firmware (ESP-IDF, Arduino, STM32, bare-metal)
+# and there is no .so/.a to hash.
+#
+# Required variables:
+# WOLFSSL_DIR=path/to/wolfssl (source tree root; must contain scripts/gen-sbom)
+# WOLFMQTT_DIR=path/to/wolfMQTT (source tree root)
+#
+# Embedded-mode variables (CRA_SBOM_MODE=embedded):
+# CRA_SBOM_SRCS_FILE=path/to/srcs.txt (explicit .c list, one path per line;
+# used verbatim — highest priority)
+# CRA_SBOM_KEIL_PROJECT=path/to/x.uvprojx (parse Keil project for .c sources)
+# CRA_SBOM_IAR_PROJECT=path/to/x.ewp (parse IAR project for .c sources)
+# CRA_SBOM_MAKEFILE_DIR=path/to/dir (run `make -n` to extract .c sources)
+# CRA_SBOM_BUILD_DIR=path/to/build (CMake/ESP-IDF build dir; sources are
+# read from its compile_commands.json)
+# CRA_SBOM_NO_HASH=true (emit SBOM without an artifact hash,
+# skipping the source list — for NDA
+# customers who cannot share source lists;
+# WARNING: not suitable for production compliance)
+#
+# Optional variables:
+# CRA_LICENSE_OVERRIDE= (e.g. LicenseRef-wolfSSL-Commercial)
+# CRA_LICENSE_TEXT= (required when CRA_LICENSE_OVERRIDE is LicenseRef-*)
+# CRA_SBOM_OUT_DIR= (default: /auditor-packet/wolfmqtt-component)
+set -eu
+# Enable pipefail when the shell supports it (bash/ksh/some dash builds).
+# Plain POSIX sh may not; tolerate its absence so the script still runs.
+# shellcheck disable=SC3040
+(set -o pipefail) 2>/dev/null && set -o pipefail || true
+
+SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)
+KIT_DIR=$(dirname "$SCRIPT_DIR")
+
+# Shared source-extraction helper (Keil/IAR/Makefile/compile_commands.json).
+. "$SCRIPT_DIR/_cra-sbom-extract.sh"
+
+# shellcheck disable=SC2015
+WOLFSSL_DIR=${WOLFSSL_DIR:-$(cd "$KIT_DIR/../../wolfssl" 2>/dev/null && pwd || true)}
+WOLFMQTT_DIR=${WOLFMQTT_DIR:-}
+
+if [ -z "${WOLFSSL_DIR:-}" ] || [ ! -d "$WOLFSSL_DIR" ]; then
+ echo "ERROR: wolfSSL source not found." >&2
+ echo " Set WOLFSSL_DIR to your wolfssl checkout (contains scripts/gen-sbom)." >&2
+ exit 1
+fi
+
+if [ -z "${WOLFMQTT_DIR:-}" ] || [ ! -d "$WOLFMQTT_DIR" ]; then
+ echo "ERROR: WOLFMQTT_DIR is not set or not a directory." >&2
+ echo " Set WOLFMQTT_DIR to your wolfMQTT source tree." >&2
+ exit 1
+fi
+
+GEN="$WOLFSSL_DIR/scripts/gen-sbom"
+if [ ! -f "$GEN" ]; then
+ echo "ERROR: $GEN not found (need wolfSSL with SBOM support)." >&2
+ exit 1
+fi
+
+# Parse version from wolfmqtt/version.h.
+VERSION=$(sed -n \
+ 's/.*LIBWOLFMQTT_VERSION_STRING[[:space:]]*"\([^"]*\)".*/\1/p' \
+ "$WOLFMQTT_DIR/wolfmqtt/version.h" 2>/dev/null || true)
+if [ -z "$VERSION" ]; then
+ echo "ERROR: could not parse version from $WOLFMQTT_DIR/wolfmqtt/version.h." >&2
+ exit 1
+fi
+
+OUT_DIR=${CRA_SBOM_OUT_DIR:-"$KIT_DIR/auditor-packet/wolfmqtt-component"}
+mkdir -p "$OUT_DIR"
+CDX_OUT="$OUT_DIR/wolfmqtt-${VERSION}.cdx.json"
+SPDX_OUT="$OUT_DIR/wolfmqtt-${VERSION}.spdx.json"
+
+echo "wolfMQTT tree: $WOLFMQTT_DIR"
+echo "wolfSSL tree: $WOLFSSL_DIR"
+echo "Version: $VERSION"
+echo "Outputs: $CDX_OUT"
+echo " $SPDX_OUT"
+if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then
+ echo "License override: $CRA_LICENSE_OVERRIDE"
+fi
+
+# A LicenseRef-* override requires the licence text to be embedded (SPDX 2.3 §10.1).
+if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then
+ case "$CRA_LICENSE_OVERRIDE" in
+ LicenseRef-*)
+ if [ -z "${CRA_LICENSE_TEXT:-}" ]; then
+ echo "ERROR: CRA_LICENSE_OVERRIDE=$CRA_LICENSE_OVERRIDE is a LicenseRef-* identifier," >&2
+ echo " but CRA_LICENSE_TEXT is not set. SPDX 2.3 requires the licence text" >&2
+ echo " to be embedded. Re-run with CRA_LICENSE_TEXT=/path/to/license.txt." >&2
+ exit 1
+ fi
+ if [ ! -f "$CRA_LICENSE_TEXT" ]; then
+ echo "ERROR: CRA_LICENSE_TEXT=$CRA_LICENSE_TEXT not found." >&2
+ exit 1
+ fi
+ ;;
+ esac
+fi
+
+# Canonicalize CRA_LICENSE_TEXT to an absolute path: make sbom runs inside a
+# subshell `cd "$WOLFMQTT_DIR"`, where a relative path would resolve against
+# the wolfMQTT tree rather than the caller's CWD.
+if [ -n "${CRA_LICENSE_TEXT:-}" ] && [ -f "$CRA_LICENSE_TEXT" ]; then
+ CRA_LICENSE_TEXT=$(CDPATH='' cd -- "$(dirname -- "$CRA_LICENSE_TEXT")" && pwd)/$(basename -- "$CRA_LICENSE_TEXT")
+ echo "License text: $CRA_LICENSE_TEXT"
+fi
+
+_run_autotools() {
+ echo "==> Autotools path: make sbom"
+
+ # Detect whether the wolfMQTT tree is already configured; run ./configure first
+ # if no Makefile is present.
+ (cd "$WOLFMQTT_DIR" && {
+ if [ ! -f Makefile ]; then
+ echo " Running ./configure first (WOLFSSL_DIR=$WOLFSSL_DIR)..."
+ ./configure --with-wolfssl="$WOLFSSL_DIR"
+ fi
+ if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then
+ if [ -n "${CRA_LICENSE_TEXT:-}" ]; then
+ make sbom WOLFSSL_DIR="$WOLFSSL_DIR" \
+ SBOM_LICENSE_OVERRIDE="$CRA_LICENSE_OVERRIDE" \
+ SBOM_LICENSE_TEXT="$CRA_LICENSE_TEXT"
+ else
+ make sbom WOLFSSL_DIR="$WOLFSSL_DIR" \
+ SBOM_LICENSE_OVERRIDE="$CRA_LICENSE_OVERRIDE"
+ fi
+ else
+ make sbom WOLFSSL_DIR="$WOLFSSL_DIR"
+ fi
+ cp -f "wolfmqtt-${VERSION}.cdx.json" "$CDX_OUT"
+ cp -f "wolfmqtt-${VERSION}.spdx.json" "$SPDX_OUT"
+ if [ -f "wolfmqtt-${VERSION}.spdx" ]; then
+ cp -f "wolfmqtt-${VERSION}.spdx" "$OUT_DIR/"
+ fi
+ })
+}
+
+_run_embedded() {
+ echo "==> Embedded path: gen-sbom over MQTT protocol sources"
+
+ # wolfMQTT's MQTT protocol implementation lives entirely in src/mqtt_*.c.
+ # Unlike wolfTPM there are no platform-specific HAL files to exclude and no
+ # host-only sources, so the mqtt_*.c glob is the correct default source set.
+ #
+ # wolfcrypt/wolfSSL crypto sources and wolfSSL TLS sources are deliberately
+ # NOT hashed here: they are a separate component with their own SBOM,
+ # produced by generate-wolfssl-sbom.sh. Mixing them in would double-count
+ # the crypto component and misattribute its provenance to wolfMQTT.
+ if [ ! -d "$WOLFMQTT_DIR/src" ]; then
+ echo "ERROR: $WOLFMQTT_DIR/src not found; cannot locate MQTT sources." >&2
+ exit 1
+ fi
+
+ GEN="$WOLFSSL_DIR/scripts/gen-sbom"
+ if [ ! -f "$GEN" ]; then
+ echo "ERROR: $GEN not found (need wolfSSL with SBOM support)." >&2
+ exit 1
+ fi
+
+ PY=$(command -v python3 2>/dev/null || command -v python 2>/dev/null || true)
+ if [ -z "$PY" ]; then
+ echo "ERROR: python3 (or python) not found; required to run gen-sbom." >&2
+ exit 1
+ fi
+
+ # wolfMQTT ships its own licence file; detect the SPDX id from it (not from
+ # the wolfSSL tree, which carries a different LICENSING file).
+ LICENSE_FILE=""
+ for _lf in "$WOLFMQTT_DIR/LICENSE" "$WOLFMQTT_DIR/COPYING" "$WOLFMQTT_DIR/LICENSING"; do
+ if [ -f "$_lf" ]; then
+ LICENSE_FILE="$_lf"
+ break
+ fi
+ done
+ if [ -z "$LICENSE_FILE" ]; then
+ echo "ERROR: no LICENSE/COPYING/LICENSING file found in $WOLFMQTT_DIR." >&2
+ exit 1
+ fi
+
+ SRCS_LIST=$(mktemp "${TMPDIR:-/tmp}/wolfmqtt-srcs.XXXXXX") || {
+ echo "ERROR: mktemp failed for the source-list temp file." >&2
+ exit 1
+ }
+ # gen-sbom requires exactly one of --options-h / --user-settings to source
+ # its build properties. Those describe wolfSSL's crypto/TLS configuration,
+ # which belongs to the wolfssl component's SBOM, not wolfMQTT's. Feed an
+ # empty options file so gen-sbom records no (and thus no misattributed)
+ # build defines for the wolfMQTT component.
+ EMPTY_OPTS=$(mktemp "${TMPDIR:-/tmp}/wolfmqtt-opts.XXXXXX") || {
+ echo "ERROR: mktemp failed for the empty options temp file." >&2
+ exit 1
+ }
+ # _cra_auto_tempfiles collects any temp files the shared extractor creates;
+ # initialise it so the EXIT trap is safe under `set -u` even when the
+ # extractor adds nothing (e.g. the default-glob path).
+ _cra_auto_tempfiles=""
+ trap 'rm -f "$SRCS_LIST" "$EMPTY_OPTS" $_cra_auto_tempfiles' EXIT
+
+ # CRA_SBOM_NO_HASH emits a placeholder checksum and skips the source list
+ # entirely (for NDA customers who cannot share source lists).
+ if [ "${CRA_SBOM_NO_HASH:-}" = "true" ] || [ "${CRA_SBOM_NO_HASH:-}" = "1" ]; then
+ echo " NOTE: CRA_SBOM_NO_HASH=true: emitting SBOM without artifact hash."
+ echo " WARNING: not suitable for production CRA compliance." >&2
+ _count=0
+ set -- --no-artifact-hash --cdx-out "$CDX_OUT" --spdx-out "$SPDX_OUT"
+ else
+ # Resolve the source list via the shared extractor (CRA_SBOM_SRCS_FILE,
+ # Keil/IAR projects, Makefile dry-run, or compile_commands.json). It
+ # returns 2 when no extraction method is selected, in which case we
+ # fall back to the default mqtt_*.c glob.
+ _cra_rc=0
+ _cra_extract_srcs "$WOLFMQTT_DIR" "wolfmqtt" "$SRCS_LIST" || _cra_rc=$?
+
+ if [ "$_cra_rc" -eq 2 ]; then
+ # No extraction method active: use default glob (all src/mqtt_*.c
+ # sorted). MQTT has no HAL split, so the full source set is the
+ # right default for bare-metal builds without an extractable list.
+ echo " Source list: default glob $WOLFMQTT_DIR/src/mqtt_*.c"
+ for _c in "$WOLFMQTT_DIR"/src/mqtt_*.c; do
+ [ -f "$_c" ] && echo "$_c"
+ done | sort > "$SRCS_LIST"
+ elif [ "$_cra_rc" -ne 0 ]; then
+ exit 1
+ fi
+
+ if [ ! -s "$SRCS_LIST" ]; then
+ echo "ERROR: no MQTT source files found to hash." >&2
+ exit 1
+ fi
+
+ # Validate every resolved path exists before handing the file to gen-sbom.
+ _count=0
+ while IFS= read -r _src; do
+ [ -n "$_src" ] || continue
+ if [ ! -f "$_src" ]; then
+ echo "ERROR: listed source not found: $_src" >&2
+ exit 1
+ fi
+ _count=$((_count + 1))
+ done < "$SRCS_LIST"
+
+ set -- --srcs-file "$SRCS_LIST" --cdx-out "$CDX_OUT" --spdx-out "$SPDX_OUT"
+ fi
+ if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then
+ set -- "$@" --license-override "$CRA_LICENSE_OVERRIDE"
+ if [ -n "${CRA_LICENSE_TEXT:-}" ]; then
+ set -- "$@" --license-text "$CRA_LICENSE_TEXT"
+ fi
+ fi
+
+ "$PY" "$GEN" \
+ --name wolfmqtt --version "$VERSION" \
+ --license-file "$LICENSE_FILE" \
+ --options-h "$EMPTY_OPTS" \
+ "$@" || {
+ echo "ERROR: gen-sbom failed." >&2
+ exit 1
+ }
+
+ if [ "${CRA_SBOM_NO_HASH:-}" = "true" ] || [ "${CRA_SBOM_NO_HASH:-}" = "1" ]; then
+ echo "NOTE: artifact hash omitted (CRA_SBOM_NO_HASH)"
+ else
+ echo "NOTE: hashed ${_count} source file(s)"
+ fi
+}
+
+case "${CRA_SBOM_MODE:-autotools}" in
+ autotools) _run_autotools ;;
+ embedded) _run_embedded ;;
+ *)
+ echo "ERROR: unknown CRA_SBOM_MODE='${CRA_SBOM_MODE:-}' (expected 'autotools' or 'embedded')" >&2
+ exit 1
+ ;;
+esac
+
+# Verify the generator actually produced non-empty SBOM files.
+for _out in "$CDX_OUT" "$SPDX_OUT"; do
+ if [ ! -s "$_out" ]; then
+ echo "ERROR: expected SBOM output missing or empty: $_out" >&2
+ exit 1
+ fi
+done
+
+echo "Done."
diff --git a/cra-kit/scripts/generate-wolfsentry-sbom.sh b/cra-kit/scripts/generate-wolfsentry-sbom.sh
new file mode 100755
index 000000000..8acbed986
--- /dev/null
+++ b/cra-kit/scripts/generate-wolfsentry-sbom.sh
@@ -0,0 +1,202 @@
+#!/bin/sh
+# Generate wolfSentry component SBOMs via gen-sbom.
+#
+# Required variables:
+# WOLFSENTRY_DIR=path/to/wolfsentry (source tree root)
+#
+# gen-sbom location (one required):
+# WOLFSSL_DIR=path/to/wolfssl (gen-sbom taken from scripts/gen-sbom)
+# CRA_GEN_SBOM=path/to/gen-sbom (direct path; overrides WOLFSSL_DIR)
+#
+# Optional variables:
+# CRA_SBOM_OUT_DIR= (default: $KIT_DIR/auditor-packet/wolfsentry-component)
+# CC= (default: cc; for -dM -E options dump)
+# CRA_WOLFSENTRY_IP_STACK=wolfip|lwip|none
+# (default: none; selects which optional IP-stack glue
+# to include in the firmware source set)
+# CRA_SBOM_NO_HASH=true emit SBOM without an artifact hash, skipping the
+# source list — for NDA customers who cannot share
+# source lists; WARNING: not suitable for production
+# compliance
+set -eu
+
+SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)
+KIT_DIR=$(dirname "$SCRIPT_DIR")
+
+# shellcheck disable=SC2015
+WOLFSENTRY_DIR=${WOLFSENTRY_DIR:-$(cd "$KIT_DIR/../../wolfsentry" 2>/dev/null && pwd || true)}
+OUT_DIR=${CRA_SBOM_OUT_DIR:-"$KIT_DIR/auditor-packet/wolfsentry-component"}
+
+# CRA_WOLFSENTRY_IP_STACK selects which optional IP stack glue is included.
+# Values: wolfip, lwip, none (default: none).
+# Why default none / exactly one: wolfip/ and lwip/ both contain
+# packet_filter_glue.c, and a firmware build compiles exactly one IP stack;
+# including both would misrepresent what is actually in the firmware.
+# (gen-sbom keys its Merkle hash on relative path, not basename, so the two
+# same-named files no longer collide -- but a build still uses only one.)
+CRA_WOLFSENTRY_IP_STACK="${CRA_WOLFSENTRY_IP_STACK:-none}"
+
+case "$CRA_WOLFSENTRY_IP_STACK" in
+ wolfip|lwip|none) ;;
+ *) echo "ERROR: CRA_WOLFSENTRY_IP_STACK must be wolfip, lwip, or none (got: $CRA_WOLFSENTRY_IP_STACK)" >&2; exit 1 ;;
+esac
+
+if [ -z "${WOLFSENTRY_DIR:-}" ] || [ ! -d "$WOLFSENTRY_DIR" ]; then
+ echo "ERROR: wolfSentry source not found." >&2
+ echo " Set WOLFSENTRY_DIR to your wolfsentry checkout (sibling of wolfssl-examples)." >&2
+ exit 1
+fi
+
+# Resolve gen-sbom: CRA_GEN_SBOM takes precedence, then WOLFSSL_DIR.
+if [ -n "${CRA_GEN_SBOM:-}" ]; then
+ GEN_SBOM="$CRA_GEN_SBOM"
+elif [ -n "${WOLFSSL_DIR:-}" ]; then
+ GEN_SBOM="$WOLFSSL_DIR/scripts/gen-sbom"
+else
+ echo "ERROR: gen-sbom location not specified." >&2
+ echo " Set WOLFSSL_DIR (path to wolfssl repo) or CRA_GEN_SBOM (direct path to gen-sbom)." >&2
+ exit 1
+fi
+
+if [ ! -f "$GEN_SBOM" ]; then
+ echo "ERROR: gen-sbom not found: $GEN_SBOM" >&2
+ exit 1
+fi
+
+# Extract version from wolfsentry/wolfsentry.h macros.
+HEADER="$WOLFSENTRY_DIR/wolfsentry/wolfsentry.h"
+if [ ! -f "$HEADER" ]; then
+ echo "ERROR: version header not found: $HEADER" >&2
+ exit 1
+fi
+_extract_ver() {
+ grep -E "^#define[[:space:]]+$1[[:space:]]+[0-9]+" "$HEADER" | awk '{print $3}'
+}
+_major=$(_extract_ver WOLFSENTRY_VERSION_MAJOR)
+_minor=$(_extract_ver WOLFSENTRY_VERSION_MINOR)
+_tiny=$(_extract_ver WOLFSENTRY_VERSION_TINY)
+VERSION="${_major}.${_minor}.${_tiny}"
+case "$VERSION" in
+ [0-9]*.[0-9]*.[0-9]*) ;;
+ *) echo "ERROR: could not parse wolfsentry version from $HEADER (got: '$VERSION')" >&2; exit 1 ;;
+esac
+
+mkdir -p "$OUT_DIR"
+CDX_OUT="$OUT_DIR/wolfsentry-${VERSION}.cdx.json"
+SPDX_OUT="$OUT_DIR/wolfsentry-${VERSION}.spdx.json"
+
+echo "wolfSentry tree: $WOLFSENTRY_DIR"
+echo "Version: $VERSION"
+echo "gen-sbom: $GEN_SBOM"
+echo "Outputs: $CDX_OUT"
+echo " $SPDX_OUT"
+
+# CRA_SBOM_NO_HASH emits a placeholder checksum and skips the source list
+# entirely (for NDA customers who cannot share source lists).
+if [ "${CRA_SBOM_NO_HASH:-}" = "true" ] || [ "${CRA_SBOM_NO_HASH:-}" = "1" ]; then
+ _no_hash=1
+ echo " NOTE: CRA_SBOM_NO_HASH=true: emitting SBOM without artifact hash."
+ echo " WARNING: not suitable for production CRA compliance." >&2
+else
+ _no_hash=0
+ # Core sources: all .c files except the two IP stack subdirs, which are
+ # selected individually via CRA_WOLFSENTRY_IP_STACK (a build compiles one).
+ SRCS=$(find "$WOLFSENTRY_DIR/src" -name "*.c" \
+ ! -path "*/wolfip/*" \
+ ! -path "*/lwip/*" \
+ | sort)
+ if [ -z "$SRCS" ]; then
+ echo "ERROR: no .c files found under $WOLFSENTRY_DIR/src/" >&2
+ exit 1
+ fi
+
+ # Optionally add the selected IP stack.
+ if [ "$CRA_WOLFSENTRY_IP_STACK" != "none" ]; then
+ SRCS="$SRCS
+$(find "$WOLFSENTRY_DIR/src/$CRA_WOLFSENTRY_IP_STACK" -name "*.c" | sort)"
+ echo " IP stack: $CRA_WOLFSENTRY_IP_STACK"
+ else
+ echo " NOTE: CRA_WOLFSENTRY_IP_STACK not set; IP glue excluded from SBOM."
+ echo " Set CRA_WOLFSENTRY_IP_STACK=wolfip or lwip to include it."
+ fi
+
+ _n=$(echo "$SRCS" | wc -l | tr -d ' ')
+ echo "Sources: $_n .c files from $WOLFSENTRY_DIR/src/"
+fi
+
+# Dump compiler defines for --options-h (no user_settings.h; wolfsentry is
+# configured via Makefile flags, not a settings header).
+CC=${CC:-cc}
+_defines_h=$(mktemp "${TMPDIR:-/tmp}/wolfsentry-defines.XXXXXX")
+_srcs_file=$(mktemp "${TMPDIR:-/tmp}/wolfsentry-srcs.XXXXXX")
+trap 'rm -f "$_defines_h" "$_srcs_file"' EXIT
+if ! "$CC" -dM -E -I"$WOLFSENTRY_DIR" -x c /dev/null >"$_defines_h" 2>/dev/null; then
+ echo "ERROR: $CC -dM -E failed; set CC to an available compiler." >&2
+ exit 1
+fi
+
+if ! command -v python3 >/dev/null 2>&1; then
+ echo "ERROR: python3 not found in PATH." >&2
+ exit 1
+fi
+
+# Build the component-checksum argument: either a placeholder (NO_HASH) or the
+# enumerated source list written one path per line for gen-sbom's --srcs-file.
+if [ "$_no_hash" = "1" ]; then
+ set -- --no-artifact-hash
+else
+ printf '%s\n' "$SRCS" > "$_srcs_file"
+ set -- --srcs-file "$_srcs_file"
+fi
+
+python3 "$GEN_SBOM" \
+ --name wolfsentry \
+ --version "$VERSION" \
+ --supplier "wolfSSL Inc." \
+ --license-file "$WOLFSENTRY_DIR/LICENSING" \
+ --options-h "$_defines_h" \
+ "$@" \
+ --cdx-out "$CDX_OUT" \
+ --spdx-out "$SPDX_OUT"
+
+# Post-process: rewrite pkg:generic/wolfsentry@X -> pkg:github/wolfSSL/wolfsentry@vX
+# gen-sbom emits pkg:generic/{name}@{version} for non-wolfssl names; the canonical
+# PURL for wolfsentry is the GitHub package form.
+if ! CDX_OUT="$CDX_OUT" SPDX_OUT="$SPDX_OUT" VERSION="$VERSION" \
+python3 <<'PY'
+import json, os, pathlib
+
+cdx = pathlib.Path(os.environ["CDX_OUT"])
+spdx = pathlib.Path(os.environ["SPDX_OUT"])
+ver = os.environ["VERSION"]
+
+GENERIC = "pkg:generic/wolfsentry@"
+GITHUB = "pkg:github/wolfSSL/wolfsentry@v"
+
+def fix(s):
+ if isinstance(s, str) and s.startswith(GENERIC):
+ return GITHUB + s[len(GENERIC):]
+ return s
+
+if cdx.exists():
+ d = json.loads(cdx.read_text())
+ comp = d.get("metadata", {}).get("component", {})
+ comp["purl"] = fix(comp.get("purl", ""))
+ cdx.write_text(json.dumps(d, indent=2) + "\n")
+ print(f"Post-processed {cdx.name}: PURL -> {comp['purl']}")
+
+if spdx.exists():
+ d = json.loads(spdx.read_text())
+ for pkg in d.get("packages", []):
+ for ref in pkg.get("externalRefs", []):
+ if ref.get("referenceType") == "purl":
+ ref["referenceLocator"] = fix(ref.get("referenceLocator", ""))
+ spdx.write_text(json.dumps(d, indent=2) + "\n")
+ print(f"Post-processed {spdx.name}: PURL canonicalized")
+PY
+then
+ echo "ERROR: PURL post-processing failed; SBOM may carry pkg:generic PURLs." >&2
+ exit 1
+fi
+
+echo "Done."
diff --git a/cra-kit/scripts/generate-wolfssh-sbom.sh b/cra-kit/scripts/generate-wolfssh-sbom.sh
new file mode 100755
index 000000000..e1234bcb5
--- /dev/null
+++ b/cra-kit/scripts/generate-wolfssh-sbom.sh
@@ -0,0 +1,366 @@
+#!/bin/sh
+# Generate wolfSSH component SBOM (autotools make sbom, or embedded gen-sbom).
+#
+# Mode selection:
+# CRA_SBOM_MODE=autotools|embedded (default: autotools)
+# autotools: builds libwolfssh.so and runs `make sbom`
+# embedded: hashes wolfSSH source files directly (no .so is produced when
+# wolfSSH is compiled into firmware), via wolfSSL's gen-sbom.
+#
+# Required variables:
+# WOLFSSL_DIR=path/to/wolfssl (source tree root; provides gen-sbom)
+# WOLFSSH_DIR=path/to/wolfssh (source tree root)
+#
+# Embedded-mode variables:
+# CRA_SBOM_BUILD_DIR=path/to/build (embedded: dir containing
+# compile_commands.json for CMake / ESP-IDF
+# / Zephyr builds, used to extract the exact
+# wolfSSH .c files on the link line)
+# CRA_SBOM_SRCS_FILE=path/to/srcs.txt (embedded: explicit list of wolfSSH .c
+# paths, one per line; takes priority over
+# every other source-resolution method)
+# CRA_SBOM_KEIL_PROJECT=path (embedded: auto-extract from Keil .uvprojx)
+# CRA_SBOM_IAR_PROJECT=path (embedded: auto-extract from IAR .ewp)
+# CRA_SBOM_MAKEFILE_DIR=path (embedded: auto-extract via make -n dry-run)
+# CRA_SBOM_NO_HASH=true (embedded: emit SBOM without an artifact
+# hash, skipping the source list — for NDA
+# customers who cannot share source lists;
+# WARNING: not suitable for production compliance)
+#
+# Optional variables:
+# CRA_SBOM_OUT_DIR= (output directory; default auditor-packet)
+# CRA_LICENSE_OVERRIDE= (e.g. LicenseRef-wolfSSH-Commercial)
+# CRA_LICENSE_TEXT= (required when CRA_LICENSE_OVERRIDE is a
+# LicenseRef-* id: plain-text licence embedded
+# in the SBOM; make sbom / gen-sbom hard-fail
+# without it.)
+# POSIX sh (script is #!/bin/sh and run via `sh`); dash has no `set -o pipefail`,
+# so we use `set -eu` like the rest of the kit. Pipelines that must not mask a
+# failed first stage are checked explicitly instead.
+set -eu
+
+# Accumulator for temp files (embedded source extraction); cleaned up on exit.
+# Why: mktemp temp files must not leak if any later command fails under set -e;
+# a single EXIT trap removes them on every exit path including errors.
+_auto_tempfiles=""
+# _cra_auto_tempfiles is populated by _cra-sbom-extract.sh's helpers; clean both.
+trap 'rm -f ${_auto_tempfiles:-} ${_cra_auto_tempfiles:-}' EXIT
+
+SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)
+KIT_DIR=$(dirname "$SCRIPT_DIR")
+
+# shared extraction methods (Keil, IAR, Makefile, compile_commands.json)
+. "$SCRIPT_DIR/_cra-sbom-extract.sh"
+# shellcheck disable=SC2015 # `|| true` is a deliberate set -e guard, not if-then-else
+WOLFSSL_DIR=${WOLFSSL_DIR:-$(cd "$KIT_DIR/../../wolfssl" 2>/dev/null && pwd || true)}
+WOLFSSH_DIR=${WOLFSSH_DIR:-$(cd "$KIT_DIR/../../wolfSSH" 2>/dev/null && pwd || true)}
+OUT_DIR=${CRA_SBOM_OUT_DIR:-"$KIT_DIR/auditor-packet/wolfssh-component"}
+
+if [ -z "${WOLFSSL_DIR:-}" ] || [ ! -d "$WOLFSSL_DIR" ]; then
+ echo "ERROR: wolfSSL source not found." >&2
+ echo " Set WOLFSSL_DIR to your wolfssl checkout (sibling of wolfssl-examples)." >&2
+ exit 1
+fi
+
+if [ -z "${WOLFSSH_DIR:-}" ] || [ ! -d "$WOLFSSH_DIR" ]; then
+ echo "ERROR: wolfSSH source not found." >&2
+ echo " Set WOLFSSH_DIR to your wolfSSH checkout (sibling of wolfssl-examples)." >&2
+ exit 1
+fi
+
+VERSION=$(sed -n \
+ 's/.*LIBWOLFSSH_VERSION_STRING[[:space:]]*"\([^"]*\)".*/\1/p' \
+ "$WOLFSSH_DIR/wolfssh/version.h" 2>/dev/null || true)
+if [ -z "$VERSION" ]; then
+ echo "ERROR: could not extract LIBWOLFSSH_VERSION_STRING from $WOLFSSH_DIR/wolfssh/version.h" >&2
+ exit 1
+fi
+
+mkdir -p "$OUT_DIR"
+CDX_OUT="$OUT_DIR/wolfssh-${VERSION}.cdx.json"
+SPDX_OUT="$OUT_DIR/wolfssh-${VERSION}.spdx.json"
+
+echo "wolfSSL tree: $WOLFSSL_DIR"
+echo "wolfSSH tree: $WOLFSSH_DIR"
+echo "Outputs: $CDX_OUT"
+echo " $SPDX_OUT"
+if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then
+ echo "License override: $CRA_LICENSE_OVERRIDE"
+fi
+
+# A LicenseRef-* override (e.g. the commercial license) requires the actual
+# licence text to be embedded in the SBOM (SPDX 2.3 §10.1). Both gen-sbom and
+# `make sbom` hard-fail without it, so catch the omission here with an
+# actionable message instead of letting the run die deep in the generator.
+if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then
+ case "$CRA_LICENSE_OVERRIDE" in
+ LicenseRef-*)
+ if [ -z "${CRA_LICENSE_TEXT:-}" ]; then
+ echo "ERROR: CRA_LICENSE_OVERRIDE=$CRA_LICENSE_OVERRIDE is a LicenseRef-* identifier," >&2
+ echo " but CRA_LICENSE_TEXT is not set. SPDX 2.3 requires the licence text to be" >&2
+ echo " embedded for any LicenseRef-* used in licenseConcluded/licenseDeclared." >&2
+ echo " Re-run with CRA_LICENSE_TEXT=/path/to/wolfssh-commercial-license.txt." >&2
+ exit 1
+ fi
+ if [ ! -f "$CRA_LICENSE_TEXT" ]; then
+ echo "ERROR: CRA_LICENSE_TEXT=$CRA_LICENSE_TEXT not found." >&2
+ exit 1
+ fi
+ ;;
+ esac
+fi
+
+# Canonicalize CRA_LICENSE_TEXT to an absolute path: make sbom runs inside a
+# `cd "$WOLFSSH_DIR"` subshell, where a relative path would resolve against the
+# wolfSSH tree rather than the caller's CWD.
+if [ -n "${CRA_LICENSE_TEXT:-}" ] && [ -f "$CRA_LICENSE_TEXT" ]; then
+ CRA_LICENSE_TEXT=$(CDPATH='' cd -- "$(dirname -- "$CRA_LICENSE_TEXT")" && pwd)/$(basename -- "$CRA_LICENSE_TEXT")
+ echo "License text: $CRA_LICENSE_TEXT"
+fi
+
+# Pick a Python that can `import pcpp` (pip may target a different python3 than
+# the one first on PATH). pcpp lets gen-sbom walk settings.h without a compiler.
+_python_with_pcpp() {
+ for py in ${CRA_PYTHON:-} python3 python; do
+ [ -n "$py" ] || continue
+ if command -v "$py" >/dev/null 2>&1 && \
+ "$py" -c "import pcpp" 2>/dev/null; then
+ echo "$py"
+ return 0
+ fi
+ done
+ return 1
+}
+
+# Resolve the wolfSSH source list for embedded mode into the file named by $1.
+#
+# Source set rule: ONLY wolfSSH protocol sources (${WOLFSSH_DIR}/src/*.c) are
+# product-owned and belong in this component. The wolfcrypt / wolfSSL sources
+# that wolfSSH links against are NOT included here — they are a separate
+# component covered by generate-wolfssl-sbom.sh and referenced as a dependency.
+# Mixing them in would double-count the crypto library across two SBOMs.
+#
+# Priority order (first match wins), all but the last handled by
+# _cra_extract_srcs from _cra-sbom-extract.sh:
+# 1. CRA_SBOM_SRCS_FILE — explicit user list beats anything inferred, because
+# the user knows their exact link line.
+# 2. CRA_SBOM_KEIL_PROJECT — parse Keil .uvprojx, filter to WOLFSSH_DIR.
+# 3. CRA_SBOM_IAR_PROJECT — parse IAR .ewp, filter to WOLFSSH_DIR.
+# 4. CRA_SBOM_MAKEFILE_DIR — `make -n` dry-run, filter to WOLFSSH_DIR.
+# 5. compile_commands.json at CRA_SBOM_BUILD_DIR — per-build extraction for
+# CMake / ESP-IDF / Zephyr.
+# 6. Default: all ${WOLFSSH_DIR}/src/*.c sorted — the "all sources" fallback
+# for toolchains where we cannot infer the exact
+# subset compiled. Listing all sources is the safe
+# over-approximation: it never omits a file that
+# shipped.
+_resolve_wolfssh_srcs() {
+ _out="$1"
+
+ # Try every env-var-driven extraction method (SRCS_FILE, Keil, IAR,
+ # Makefile, compile_commands.json). rc=2 means none was set: fall back to
+ # the wolfSSH default glob below.
+ _cra_rc=0
+ _cra_extract_srcs "$WOLFSSH_DIR" "wolfssh" "$_out" || _cra_rc=$?
+ if [ "$_cra_rc" -eq 0 ]; then
+ return 0
+ elif [ "$_cra_rc" -ne 2 ]; then
+ exit 1 # _cra_extract_srcs already printed the error
+ fi
+
+ # No extraction env var set: every wolfSSH src/*.c. A POSIX glob expands in
+ # sorted order; guard the no-match case where the pattern stays literal.
+ : > "$_out"
+ for _c in "$WOLFSSH_DIR"/src/*.c; do
+ [ -f "$_c" ] || continue
+ printf '%s\n' "$_c" >> "$_out"
+ done
+ if [ ! -s "$_out" ]; then
+ echo "ERROR: no wolfSSH sources found in $WOLFSSH_DIR/src/*.c." >&2
+ exit 1
+ fi
+ _n=$(wc -l < "$_out" | tr -d ' ')
+ echo " Using all $_n wolfSSH sources from $WOLFSSH_DIR/src/*.c (default)"
+ return 0
+}
+
+_run_embedded() {
+ echo "==> Embedded path: gen-sbom hashing wolfSSH source files"
+
+ GEN="$WOLFSSL_DIR/scripts/gen-sbom"
+ if [ ! -f "$GEN" ]; then
+ echo "ERROR: $GEN not found (need a wolfSSL tree with SBOM support)." >&2
+ exit 1
+ fi
+
+ # gen-sbom's embedded entry point walks wolfSSL's settings.h to resolve the
+ # build config. wolfSSH headers pull in wolfSSL headers, so we reuse the same
+ # settings.h + kit user_settings.h the wolfssl embedded path uses.
+ SETTINGS_H="$WOLFSSL_DIR/wolfssl/wolfcrypt/settings.h"
+ if [ ! -f "$SETTINGS_H" ]; then
+ echo "ERROR: $SETTINGS_H not found." >&2
+ exit 1
+ fi
+ if [ ! -f "$KIT_DIR/user_settings.h" ]; then
+ echo "ERROR: $KIT_DIR/user_settings.h missing (demo WOLFSSL_USER_SETTINGS)." >&2
+ exit 1
+ fi
+
+ if ! command -v python3 >/dev/null 2>&1; then
+ echo "ERROR: python3 not found in PATH (required by gen-sbom)." >&2
+ exit 1
+ fi
+
+ # CRA_SBOM_NO_HASH emits a placeholder checksum and skips the source list
+ # entirely (for NDA customers who cannot share source lists).
+ if [ "${CRA_SBOM_NO_HASH:-}" = "true" ] || [ "${CRA_SBOM_NO_HASH:-}" = "1" ]; then
+ echo " NOTE: CRA_SBOM_NO_HASH=true: emitting SBOM without artifact hash."
+ echo " WARNING: not suitable for production CRA compliance." >&2
+ set -- --no-artifact-hash --cdx-out "$CDX_OUT" --spdx-out "$SPDX_OUT"
+ else
+ # Resolve the source list into a temp file (cleaned up by the EXIT trap).
+ _srcs=$(mktemp "${TMPDIR:-/tmp}/wolfssh-srcs.XXXXXX") || {
+ echo "ERROR: mktemp failed for the source-list temp file." >&2
+ exit 1
+ }
+ _auto_tempfiles="${_auto_tempfiles:-} $_srcs"
+ _resolve_wolfssh_srcs "$_srcs"
+
+ # Validate every resolved path exists before handing the file to gen-sbom.
+ while IFS= read -r _src; do
+ [ -n "$_src" ] || continue
+ if [ ! -f "$_src" ]; then
+ echo "ERROR: source file does not exist: $_src" >&2
+ exit 1
+ fi
+ done < "$_srcs"
+ if [ ! -s "$_srcs" ]; then
+ echo "ERROR: resolved source list is empty." >&2
+ exit 1
+ fi
+ set -- --srcs-file "$_srcs" --cdx-out "$CDX_OUT" --spdx-out "$SPDX_OUT"
+ fi
+ if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then
+ set -- "$@" --license-override "$CRA_LICENSE_OVERRIDE"
+ if [ -n "${CRA_LICENSE_TEXT:-}" ]; then
+ set -- "$@" --license-text "$CRA_LICENSE_TEXT"
+ fi
+ fi
+
+ if _py=$(_python_with_pcpp); then
+ echo " Using $_py (pcpp) for --user-settings"
+ else
+ echo "ERROR: no python3/python with pcpp installed (required for embedded mode)." >&2
+ echo " Install it on the same interpreter: python3 -m pip install pcpp" >&2
+ exit 1
+ fi
+
+ "$_py" "$GEN" \
+ --name wolfssh --version "$VERSION" \
+ --license-file "$WOLFSSH_DIR/LICENSING" \
+ --user-settings "$SETTINGS_H" \
+ --user-settings-include "$WOLFSSL_DIR" \
+ --user-settings-include "$KIT_DIR" \
+ --user-settings-define WOLFSSL_USER_SETTINGS \
+ "$@"
+
+ # Verify gen-sbom actually produced both outputs and they are non-empty.
+ for _f in "$CDX_OUT" "$SPDX_OUT"; do
+ if [ ! -s "$_f" ]; then
+ echo "ERROR: expected output missing or empty: $_f" >&2
+ exit 1
+ fi
+ done
+}
+
+_run_autotools() {
+ echo "==> Autotools path: make sbom"
+ # `make sbom` names its output after the wolfSSH TREE's version
+ # (PACKAGE_VERSION). Detect mismatches early so the cp below doesn't fail
+ # with a cryptic "No such file or directory" under `set -eu`.
+ _tree_ver=$(sed -n \
+ 's/.*LIBWOLFSSH_VERSION_STRING[[:space:]]*"\([^"]*\)".*/\1/p' \
+ "$WOLFSSH_DIR/wolfssh/version.h" 2>/dev/null || true)
+ if [ -n "$_tree_ver" ] && [ "$_tree_ver" != "$VERSION" ]; then
+ echo "ERROR: wolfSSH tree is version $_tree_ver but expected $VERSION." >&2
+ exit 1
+ fi
+ (cd "$WOLFSSH_DIR" && {
+ if [ ! -f Makefile ]; then
+ echo " Running ./configure first..."
+ ./configure --with-wolfssl="$WOLFSSL_DIR"
+ fi
+ if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then
+ if [ -n "${CRA_LICENSE_TEXT:-}" ]; then
+ make sbom WOLFSSL_DIR="$WOLFSSL_DIR" \
+ SBOM_LICENSE_OVERRIDE="$CRA_LICENSE_OVERRIDE" \
+ SBOM_LICENSE_TEXT="$CRA_LICENSE_TEXT"
+ else
+ make sbom WOLFSSL_DIR="$WOLFSSL_DIR" \
+ SBOM_LICENSE_OVERRIDE="$CRA_LICENSE_OVERRIDE"
+ fi
+ else
+ make sbom WOLFSSL_DIR="$WOLFSSL_DIR"
+ fi
+ cp -f "wolfssh-${VERSION}.cdx.json" "$CDX_OUT"
+ cp -f "wolfssh-${VERSION}.spdx.json" "$SPDX_OUT"
+ if [ -f "wolfssh-${VERSION}.spdx" ]; then
+ cp -f "wolfssh-${VERSION}.spdx" "$OUT_DIR/"
+ fi
+ })
+}
+
+MODE=${CRA_SBOM_MODE:-autotools}
+case "$MODE" in
+ embedded) _run_embedded ;;
+ autotools) _run_autotools ;;
+ *)
+ echo "ERROR: CRA_SBOM_MODE must be 'autotools' or 'embedded', not '$MODE'" >&2
+ exit 1
+ ;;
+esac
+
+# ---- Post-process: defensive PURL canonicalization ----
+# Current gen-sbom already emits pkg:github/wolfSSL/wolfSSH@vX natively for
+# known component names; the rewrite below is a defensive no-op kept only for
+# older generator versions that may emit pkg:generic/wolfssh@X.
+if ! CDX_OUT="$CDX_OUT" SPDX_OUT="$SPDX_OUT" \
+ python3 <<'PY'
+import json, os, pathlib
+
+cdx = pathlib.Path(os.environ["CDX_OUT"])
+spdx = pathlib.Path(os.environ["SPDX_OUT"])
+
+GENERIC = "pkg:generic/wolfssh@"
+GITHUB = "pkg:github/wolfSSL/wolfSSH@v"
+
+def canonicalize_purl(s):
+ if isinstance(s, str) and s.startswith(GENERIC):
+ return GITHUB + s[len(GENERIC):]
+ return s
+
+if cdx.exists():
+ d = json.loads(cdx.read_text())
+ comp = d.get("metadata", {}).get("component", {})
+ comp["purl"] = canonicalize_purl(comp.get("purl", ""))
+ cdx.write_text(json.dumps(d, indent=2) + "\n")
+ print(f"Post-processed {cdx.name}")
+
+if spdx.exists():
+ d = json.loads(spdx.read_text())
+ for pkg in d.get("packages", []):
+ for ref in pkg.get("externalRefs", []):
+ if ref.get("referenceType") == "purl":
+ ref["referenceLocator"] = canonicalize_purl(ref.get("referenceLocator", ""))
+ spdx.write_text(json.dumps(d, indent=2) + "\n")
+ print(f"Post-processed {spdx.name}")
+PY
+then
+ echo "ERROR: post-process failed (PURL canonicalization incomplete)." >&2
+ echo " The emitted SBOM may carry pkg:generic PURLs; not trusting it." >&2
+ exit 1
+fi
+
+echo "Done. SBOM outputs:"
+echo " $CDX_OUT"
+echo " $SPDX_OUT"
diff --git a/cra-kit/scripts/generate-wolfssl-sbom.sh b/cra-kit/scripts/generate-wolfssl-sbom.sh
new file mode 100755
index 000000000..57c746984
--- /dev/null
+++ b/cra-kit/scripts/generate-wolfssl-sbom.sh
@@ -0,0 +1,502 @@
+#!/bin/sh
+# Generate wolfSSL component SBOMs (autotools make sbom, cmake sbom, or embedded gen-sbom).
+#
+# Mode selection:
+# CRA_SBOM_MODE=autotools|cmake|embedded
+# autotools (default when configure+Makefile exist): runs `make sbom`
+# cmake: runs `cmake --build $WOLFSSL_BUILD_DIR --target sbom`
+# embedded: runs gen-sbom directly with source files and user_settings.h
+#
+# Required variables:
+# WOLFSSL_DIR=path/to/wolfssl (source tree root)
+#
+# Mode-specific variables:
+# WOLFSSL_BUILD_DIR=path/to/build (cmake mode: path to cmake build directory;
+# embedded: also triggers compile_commands.json
+# auto-extraction when present)
+# CRA_SBOM_SRCS_FILE=path/to/srcs.txt (embedded: file listing .c paths, one per line;
+# combined with the built-in demo list unless
+# CRA_SBOM_SRCS_ONLY_FROM_FILE=true)
+# CRA_SBOM_SRCS_ONLY_FROM_FILE=true (embedded: skip the built-in demo list and
+# use only paths from CRA_SBOM_SRCS_FILE)
+# CRA_SBOM_NO_HASH=true (embedded: emit SBOM without a real artifact
+# hash; use when no source list is available)
+# CRA_SBOM_MAKEFILE_DIR= (embedded: auto-extract srcs via make -n)
+# CRA_SBOM_KEIL_PROJECT= (embedded: auto-extract srcs from .uvprojx)
+# CRA_SBOM_IAR_PROJECT= (embedded: auto-extract srcs from .ewp)
+#
+# Optional variables:
+# CRA_PYTHON=python3 (interpreter with pcpp; for embedded path)
+# CRA_LICENSE_OVERRIDE= (e.g. LicenseRef-wolfSSL-Commercial)
+# CRA_LICENSE_TEXT= (required when CRA_LICENSE_OVERRIDE is a
+# LicenseRef-* id: plain-text licence embedded
+# in the SBOM; gen-sbom / make sbom hard-fail
+# without it.)
+set -eu
+
+# Accumulator for temp files created by _auto_extract_srcs / the shared
+# extraction library; cleaned up on exit. The library appends to
+# _cra_auto_tempfiles, so trap both.
+_auto_tempfiles=""
+_cra_auto_tempfiles=""
+trap 'rm -f ${_auto_tempfiles:-} ${_cra_auto_tempfiles:-}' EXIT
+
+SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)
+# shellcheck source=_cra-sbom-extract.sh disable=SC1091
+. "$SCRIPT_DIR/_cra-sbom-extract.sh"
+KIT_DIR=$(dirname "$SCRIPT_DIR")
+# shellcheck disable=SC2015 # `|| true` is a deliberate set -e guard, not if-then-else
+WOLFSSL_DIR=${WOLFSSL_DIR:-$(cd "$KIT_DIR/../../wolfssl" 2>/dev/null && pwd || true)}
+OUT_DIR=${CRA_SBOM_OUT_DIR:-"$KIT_DIR/auditor-packet/wolfssl-component"}
+VERSION_FILE="$KIT_DIR/VERSION"
+
+if [ -z "${WOLFSSL_DIR:-}" ] || [ ! -d "$WOLFSSL_DIR" ]; then
+ echo "ERROR: wolfSSL source not found." >&2
+ echo " Set WOLFSSL_DIR to your wolfssl checkout (sibling of wolfssl-examples)." >&2
+ exit 1
+fi
+
+# shellcheck disable=SC1090
+. "$VERSION_FILE" 2>/dev/null || true
+VERSION=${WOLFSSL_VERSION:-5.9.1}
+
+mkdir -p "$OUT_DIR"
+CDX_OUT="$OUT_DIR/wolfssl-${VERSION}.cdx.json"
+SPDX_OUT="$OUT_DIR/wolfssl-${VERSION}.spdx.json"
+
+echo "wolfSSL tree: $WOLFSSL_DIR"
+echo "Outputs: $CDX_OUT"
+echo " $SPDX_OUT"
+if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then
+ echo "License override: $CRA_LICENSE_OVERRIDE"
+fi
+
+# A LicenseRef-* override (e.g. the commercial license) requires the actual
+# licence text to be embedded in the SBOM (SPDX 2.3 §10.1). Both gen-sbom and
+# `make sbom` hard-fail without it, so catch the omission here with an
+# actionable message instead of letting the run die deep in the generator.
+if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then
+ case "$CRA_LICENSE_OVERRIDE" in
+ LicenseRef-*)
+ if [ -z "${CRA_LICENSE_TEXT:-}" ]; then
+ echo "ERROR: CRA_LICENSE_OVERRIDE=$CRA_LICENSE_OVERRIDE is a LicenseRef-* identifier," >&2
+ echo " but CRA_LICENSE_TEXT is not set. SPDX 2.3 requires the licence text to be" >&2
+ echo " embedded for any LicenseRef-* used in licenseConcluded/licenseDeclared." >&2
+ echo " Re-run with CRA_LICENSE_TEXT=/path/to/wolfssl-commercial-license.txt," >&2
+ echo " or use scripts/make-commercial-sample.sh to derive from the pinned GPL samples." >&2
+ exit 1
+ fi
+ if [ ! -f "$CRA_LICENSE_TEXT" ]; then
+ echo "ERROR: CRA_LICENSE_TEXT=$CRA_LICENSE_TEXT not found." >&2
+ exit 1
+ fi
+ ;;
+ esac
+fi
+
+# Canonicalize CRA_LICENSE_TEXT to an absolute path: the autotools path runs
+# `make sbom` inside a `cd "$WOLFSSL_DIR"` subshell, where a relative path would
+# otherwise resolve against the wolfSSL tree rather than the caller's CWD.
+if [ -n "${CRA_LICENSE_TEXT:-}" ] && [ -f "$CRA_LICENSE_TEXT" ]; then
+ CRA_LICENSE_TEXT=$(CDPATH='' cd -- "$(dirname -- "$CRA_LICENSE_TEXT")" && pwd)/$(basename -- "$CRA_LICENSE_TEXT")
+ echo "License text: $CRA_LICENSE_TEXT"
+fi
+
+# Pick a Python that can `import pcpp` (pip may target a different python3 than /usr/local/bin).
+_python_with_pcpp() {
+ for py in ${CRA_PYTHON:-} python3 python; do
+ [ -n "$py" ] || continue
+ if command -v "$py" >/dev/null 2>&1 && \
+ "$py" -c "import pcpp" 2>/dev/null; then
+ echo "$py"
+ return 0
+ fi
+ done
+ return 1
+}
+
+_embedded_srcs() {
+ # Demo list only — production SBOMs must mirror every wolfSSL .c on your link line.
+ # Outputs from this list are watermarked wolfssl:sbom:demo=true.
+ for f in \
+ "$WOLFSSL_DIR/wolfcrypt/src/aes.c" \
+ "$WOLFSSL_DIR/wolfcrypt/src/sha.c" \
+ "$WOLFSSL_DIR/wolfcrypt/src/sha256.c" \
+ "$WOLFSSL_DIR/wolfcrypt/src/random.c" \
+ "$WOLFSSL_DIR/wolfcrypt/src/ecc.c" \
+ "$WOLFSSL_DIR/wolfcrypt/src/wc_port.c" \
+ "$WOLFSSL_DIR/src/tls.c" \
+ "$WOLFSSL_DIR/src/tls13.c" \
+ "$WOLFSSL_DIR/src/keys.c"
+ do
+ if [ -f "$f" ]; then
+ echo "$f"
+ fi
+ done
+}
+
+_auto_extract_srcs() {
+ # Delegate to the shared extraction library. The library reads
+ # CRA_SBOM_BUILD_DIR for compile_commands.json; map WOLFSSL_BUILD_DIR onto it
+ # so the embedded cmake/Zephyr/ESP-IDF auto-extraction keeps working.
+ if [ -n "${WOLFSSL_BUILD_DIR:-}" ]; then
+ CRA_SBOM_BUILD_DIR=${CRA_SBOM_BUILD_DIR:-$WOLFSSL_BUILD_DIR}
+ fi
+ _auto=$(mktemp "${TMPDIR:-/tmp}/wolfssl-auto-srcs.XXXXXX") || {
+ echo "ERROR: mktemp failed for the auto-extract source list." >&2
+ exit 1
+ }
+ _auto_tempfiles="${_auto_tempfiles:-} $_auto"
+ # `|| _rc=$?` keeps `set -e` from aborting on the library's non-zero returns
+ # (1 = error, 2 = no method) so we can dispatch on the code below.
+ _rc=0
+ _cra_extract_srcs "$WOLFSSL_DIR" "wolfssl" "$_auto" || _rc=$?
+ case "$_rc" in
+ 0)
+ CRA_SBOM_SRCS_FILE="$_auto"
+ CRA_SBOM_SRCS_ONLY_FROM_FILE=true
+ ;;
+ 2)
+ # No extraction method selected; fall back to the built-in demo list.
+ ;;
+ *)
+ # Library already printed an actionable error to stderr.
+ exit 1
+ ;;
+ esac
+}
+
+_run_embedded() {
+ echo "==> Embedded path: gen-sbom with CRA Kit user_settings.h"
+ if [ ! -f "$KIT_DIR/user_settings.h" ]; then
+ echo "ERROR: $KIT_DIR/user_settings.h missing (demo settings for WOLFSSL_USER_SETTINGS)." >&2
+ exit 1
+ fi
+ GEN="$WOLFSSL_DIR/scripts/gen-sbom"
+ if [ ! -f "$GEN" ]; then
+ echo "ERROR: $GEN not found (need wolfSSL with SBOM support)." >&2
+ exit 1
+ fi
+
+ SETTINGS_H="$WOLFSSL_DIR/wolfssl/wolfcrypt/settings.h"
+ if [ ! -f "$SETTINGS_H" ]; then
+ echo "ERROR: $SETTINGS_H not found." >&2
+ exit 1
+ fi
+
+ # --no-artifact-hash: skip all source-file logic and emit a placeholder hash.
+ # Use when no compiled library AND no source file list is accessible.
+ if [ "${CRA_SBOM_NO_HASH:-}" = "true" ] || [ "${CRA_SBOM_NO_HASH:-}" = "1" ]; then
+ if [ -n "${CRA_SBOM_SRCS_FILE:-}" ] || [ -n "${CRA_SBOM_SRCS_ONLY_FROM_FILE:-}" ]; then
+ echo "ERROR: CRA_SBOM_NO_HASH cannot be combined with CRA_SBOM_SRCS_FILE." >&2
+ exit 1
+ fi
+ echo " NOTE: CRA_SBOM_NO_HASH=true: emitting SBOM with placeholder hash."
+ echo " Contact wolfssl@wolfssl.com to discuss integrity verification"
+ echo " options before using this in production."
+ set -- --no-artifact-hash --cdx-out "$CDX_OUT" --spdx-out "$SPDX_OUT"
+ if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then
+ set -- "$@" --license-override "$CRA_LICENSE_OVERRIDE"
+ if [ -n "${CRA_LICENSE_TEXT:-}" ]; then
+ set -- "$@" --license-text "$CRA_LICENSE_TEXT"
+ fi
+ fi
+ _py=$(command -v python3 2>/dev/null || command -v python)
+ [ -n "$_py" ] || { echo "ERROR: python3 not found." >&2; exit 1; }
+ "$_py" "$GEN" \
+ --name wolfssl --version "$VERSION" \
+ --license-file "$WOLFSSL_DIR/LICENSING" \
+ --user-settings "$SETTINGS_H" \
+ --user-settings-include "$WOLFSSL_DIR" \
+ --user-settings-include "$KIT_DIR" \
+ --user-settings-define WOLFSSL_USER_SETTINGS \
+ "$@"
+ return 0
+ fi
+
+ # Auto-extract if caller didn't supply CRA_SBOM_SRCS_FILE
+ [ -z "${CRA_SBOM_SRCS_FILE:-}" ] && [ "${CRA_SBOM_NO_HASH:-}" != "true" ] && \
+ _auto_extract_srcs
+
+ # Build the source file list.
+ #
+ # Priority:
+ # CRA_SBOM_SRCS_ONLY_FROM_FILE=true — use only the caller-supplied file
+ # CRA_SBOM_SRCS_FILE (without ONLY) — merge file with built-in demo list
+ # (neither) — use built-in demo list
+ #
+ # The built-in 9-file demo list is for kit demonstration only. Production
+ # SBOMs MUST list every wolfSSL .c file on your link line. The post-
+ # processing step below watermarks demo outputs with wolfssl:sbom:demo=true.
+ set --
+ if [ "${CRA_SBOM_SRCS_ONLY_FROM_FILE:-}" = "true" ]; then
+ if [ -z "${CRA_SBOM_SRCS_FILE:-}" ]; then
+ echo "ERROR: CRA_SBOM_SRCS_ONLY_FROM_FILE=true requires CRA_SBOM_SRCS_FILE." >&2
+ exit 1
+ fi
+ echo " Using source list from $CRA_SBOM_SRCS_FILE (CRA_SBOM_SRCS_ONLY_FROM_FILE=true)"
+ else
+ echo " NOTE: --srcs uses the kit's built-in 9-file DEMO list. Production SBOMs"
+ echo " must list every wolfSSL .c file you compile. Set CRA_SBOM_SRCS_FILE"
+ echo " to your link-time source list to replace the demo list."
+ echo " Output is watermarked wolfssl:sbom:demo=true."
+ while IFS= read -r _src; do
+ [ -n "$_src" ] || continue
+ set -- "$@" "$_src"
+ done <&2
+ exit 1
+ fi
+ _srcs_file_args="--srcs-file $CRA_SBOM_SRCS_FILE"
+ fi
+
+ # Optional commercial license override (LicenseRef-wolfSSL-Commercial etc).
+ # A LicenseRef-* override must be accompanied by --license-text (validated
+ # up front above); a stock SPDX id needs no text.
+ # Append the --srcs positional args last; argparse stops --srcs consumption
+ # at the next -- option, so --cdx-out / --spdx-out end the list cleanly.
+ # Capture whether positional srcs exist before output flags are appended.
+ _srcs_flag=""
+ [ $# -gt 0 ] && _srcs_flag="--srcs"
+ set -- "$@" --cdx-out "$CDX_OUT" --spdx-out "$SPDX_OUT"
+ if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then
+ set -- "$@" --license-override "$CRA_LICENSE_OVERRIDE"
+ if [ -n "${CRA_LICENSE_TEXT:-}" ]; then
+ set -- "$@" --license-text "$CRA_LICENSE_TEXT"
+ fi
+ fi
+
+ if _py=$(_python_with_pcpp); then
+ echo " Using $_py (pcpp) for --user-settings"
+ # shellcheck disable=SC2086
+ "$_py" "$GEN" \
+ --name wolfssl --version "$VERSION" \
+ --license-file "$WOLFSSL_DIR/LICENSING" \
+ --user-settings "$SETTINGS_H" \
+ --user-settings-include "$WOLFSSL_DIR" \
+ --user-settings-include "$KIT_DIR" \
+ --user-settings-define WOLFSSL_USER_SETTINGS \
+ ${_srcs_file_args} \
+ ${_srcs_flag} "$@"
+ return 0
+ fi
+
+ echo "NOTE: pcpp not found for python3/python; using compiler -dM -E -> --options-h"
+ echo " Install pcpp on the same interpreter: python3 -m pip install pcpp"
+ echo " (conda users: pip install pcpp often targets conda python, not /usr/local/bin/python3)"
+ echo " Cross builds: set CC=arm-none-eabi-gcc (or your target compiler) so the"
+ echo " fallback reflects target macros, not the host's."
+
+ # Use mktemp so the temp filename is unpredictable: a fixed PID-based name in
+ # a shared/CI directory could be pre-created or raced by another job.
+ DEFINES_H=$(mktemp "${TMPDIR:-/tmp}/wolfssl-defines.XXXXXX") || {
+ echo "ERROR: mktemp failed for the defines temp file." >&2
+ exit 1
+ }
+ # Clean up the temp defines file on every exit path, including a failing
+ # generator run (it previously leaked the file under `set -e` if the
+ # final gen-sbom invocation failed before the manual `rm -f`).
+ _auto_tempfiles="${_auto_tempfiles:-} $DEFINES_H"
+ CC=${CC:-cc}
+ if ! "$CC" -dM -E \
+ -I"$WOLFSSL_DIR" \
+ -I"$KIT_DIR" \
+ -DWOLFSSL_USER_SETTINGS \
+ -include "$SETTINGS_H" \
+ -x c /dev/null >"$DEFINES_H" 2>/dev/null; then
+ echo "ERROR: $CC -dM -E failed; install pcpp or set CC to your cross-compiler." >&2
+ exit 1
+ fi
+
+ PYTHON=python3
+ command -v python3 >/dev/null 2>&1 || PYTHON=python
+ # shellcheck disable=SC2086
+ "$PYTHON" "$GEN" \
+ --name wolfssl --version "$VERSION" \
+ --license-file "$WOLFSSL_DIR/LICENSING" \
+ --options-h "$DEFINES_H" \
+ ${_srcs_file_args} \
+ ${_srcs_flag} "$@"
+}
+
+_run_cmake() {
+ echo "==> cmake path: cmake --build --target sbom"
+ if [ -z "${WOLFSSL_BUILD_DIR:-}" ]; then
+ echo "ERROR: WOLFSSL_BUILD_DIR is not set." >&2
+ echo " Set it to your cmake out-of-source build directory." >&2
+ echo " Example: cmake -B build && WOLFSSL_BUILD_DIR=\$PWD/build $0" >&2
+ exit 1
+ fi
+ if [ ! -d "$WOLFSSL_BUILD_DIR" ]; then
+ echo "ERROR: WOLFSSL_BUILD_DIR=$WOLFSSL_BUILD_DIR is not a directory." >&2
+ exit 1
+ fi
+ if ! command -v cmake >/dev/null 2>&1; then
+ echo "ERROR: cmake not found in PATH." >&2
+ exit 1
+ fi
+
+ # Detect version from cmake cache so we can find the output files.
+ # cmake -L/-LA both omit :STATIC (internal) entries; grep the cache file directly.
+ _cmake_ver=$(grep -m1 '^CMAKE_PROJECT_VERSION:STATIC=' \
+ "$WOLFSSL_BUILD_DIR/CMakeCache.txt" 2>/dev/null | cut -d= -f2)
+ if [ -z "$_cmake_ver" ]; then
+ echo "WARNING: could not detect PROJECT_VERSION from cmake cache; using kit VERSION=$VERSION" >&2
+ _cmake_ver="$VERSION"
+ fi
+ if [ "$_cmake_ver" != "$VERSION" ]; then
+ echo "ERROR: cmake build has wolfSSL $_cmake_ver but the kit is pinned to $VERSION." >&2
+ echo " Update cra-kit/VERSION or reconfigure cmake against wolfSSL $VERSION." >&2
+ exit 1
+ fi
+
+ cmake --build "$WOLFSSL_BUILD_DIR" --target sbom
+
+ _cdx_src="$WOLFSSL_BUILD_DIR/wolfssl-${VERSION}.cdx.json"
+ _spdx_src="$WOLFSSL_BUILD_DIR/wolfssl-${VERSION}.spdx.json"
+ _tv_src="$WOLFSSL_BUILD_DIR/wolfssl-${VERSION}.spdx"
+ for _f in "$_cdx_src" "$_spdx_src"; do
+ if [ ! -f "$_f" ]; then
+ echo "ERROR: expected cmake sbom output not found: $_f" >&2
+ echo " The sbom target may have failed; check cmake build output above." >&2
+ exit 1
+ fi
+ done
+
+ cp -f "$_cdx_src" "$CDX_OUT"
+ cp -f "$_spdx_src" "$SPDX_OUT"
+ if [ -f "$_tv_src" ]; then
+ cp -f "$_tv_src" "$OUT_DIR/"
+ fi
+}
+
+_run_autotools() {
+ echo "==> Autotools path: make sbom"
+ # `make sbom` names its output after the wolfSSL TREE's version
+ # (PACKAGE_VERSION), not the kit's pinned VERSION. If they differ, the
+ # `cp` below would otherwise fail with a cryptic "No such file or
+ # directory" under `set -eu`. Detect the mismatch early and explain it.
+ _tree_ver=$(sed -n \
+ 's/.*LIBWOLFSSL_VERSION_STRING[[:space:]]*"\([^"]*\)".*/\1/p' \
+ "$WOLFSSL_DIR/wolfssl/version.h" 2>/dev/null || true)
+ if [ -n "$_tree_ver" ] && [ "$_tree_ver" != "$VERSION" ]; then
+ echo "ERROR: wolfSSL tree is version $_tree_ver but the kit is pinned to $VERSION." >&2
+ echo " 'make sbom' emits wolfssl-${_tree_ver}.* while the pinned auditor" >&2
+ echo " packet references wolfssl-${VERSION}.*. Check out a wolfSSL $VERSION" >&2
+ echo " tree, or update cra-kit/VERSION (and the pinned sample references)." >&2
+ exit 1
+ fi
+ (cd "$WOLFSSL_DIR" && {
+ if [ ! -f Makefile ]; then
+ echo " Running ./configure first..."
+ ./configure
+ fi
+ if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then
+ if [ -n "${CRA_LICENSE_TEXT:-}" ]; then
+ make sbom SBOM_LICENSE_OVERRIDE="$CRA_LICENSE_OVERRIDE" \
+ SBOM_LICENSE_TEXT="$CRA_LICENSE_TEXT"
+ else
+ make sbom SBOM_LICENSE_OVERRIDE="$CRA_LICENSE_OVERRIDE"
+ fi
+ else
+ make sbom
+ fi
+ cp -f "wolfssl-${VERSION}.cdx.json" "$CDX_OUT"
+ cp -f "wolfssl-${VERSION}.spdx.json" "$SPDX_OUT"
+ if [ -f "wolfssl-${VERSION}.spdx" ]; then
+ cp -f "wolfssl-${VERSION}.spdx" "$OUT_DIR/"
+ fi
+ })
+}
+
+MODE=${CRA_SBOM_MODE:-}
+case "$MODE" in
+ embedded) _run_embedded ;;
+ autotools) _run_autotools ;;
+ cmake) _run_cmake ;;
+ "")
+ if [ -n "${WOLFSSL_BUILD_DIR:-}" ] && [ -d "${WOLFSSL_BUILD_DIR}" ]; then
+ MODE=cmake
+ _run_cmake
+ elif [ -f "$WOLFSSL_DIR/Makefile" ] && [ -f "$WOLFSSL_DIR/configure" ]; then
+ MODE=autotools
+ _run_autotools
+ else
+ MODE=embedded
+ _run_embedded
+ fi
+ ;;
+ *)
+ echo "ERROR: CRA_SBOM_MODE must be 'autotools', 'cmake', or 'embedded', not '$MODE'" >&2
+ exit 1
+ ;;
+esac
+
+# ---- Post-process: demo watermarks (+ defensive PURL canonicalization) ----
+# Current gen-sbom already emits pkg:github/wolfSSL/wolfssl@vX natively, so the
+# PURL rewrite below is a defensive no-op kept only for older wolfSSL trees that
+# emitted pkg:generic/wolfssl@X. The substantive step here is the demo
+# watermark: embedded outputs from the kit's 9-file demo --srcs list get a
+# wolfssl:sbom:demo property so a downstream auditor cannot mistake them for
+# production-complete SBOMs.
+if ! CDX_OUT="$CDX_OUT" SPDX_OUT="$SPDX_OUT" CRA_SBOM_MODE_FINAL="$MODE" \
+ CRA_SBOM_SRCS_ONLY_FROM_FILE="${CRA_SBOM_SRCS_ONLY_FROM_FILE:-}" \
+python3 <<'PY'
+import json, os, pathlib
+
+cdx = pathlib.Path(os.environ["CDX_OUT"])
+spdx = pathlib.Path(os.environ["SPDX_OUT"])
+demo = os.environ.get("CRA_SBOM_MODE_FINAL") == "embedded" and \
+ os.environ.get("CRA_SBOM_SRCS_ONLY_FROM_FILE") != "true"
+
+GENERIC = "pkg:generic/wolfssl@"
+GITHUB = "pkg:github/wolfSSL/wolfssl@v"
+
+def canonicalize_purl(s):
+ if isinstance(s, str) and s.startswith(GENERIC):
+ return GITHUB + s[len(GENERIC):]
+ return s
+
+if cdx.exists():
+ d = json.loads(cdx.read_text())
+ comp = d.get("metadata", {}).get("component", {})
+ comp["purl"] = canonicalize_purl(comp.get("purl", ""))
+ if demo:
+ props = comp.setdefault("properties", [])
+ if not any(p.get("name") == "wolfssl:sbom:demo" for p in props):
+ props.append({
+ "name": "wolfssl:sbom:demo",
+ "value": "true (built-in --srcs list, not production-complete)"
+ })
+ cdx.write_text(json.dumps(d, indent=2) + "\n")
+ print(f"Post-processed {cdx.name}" + (": demo watermark added" if demo else ": no changes needed"))
+
+if spdx.exists():
+ d = json.loads(spdx.read_text())
+ for pkg in d.get("packages", []):
+ for ref in pkg.get("externalRefs", []):
+ if ref.get("referenceType") == "purl":
+ ref["referenceLocator"] = canonicalize_purl(ref.get("referenceLocator", ""))
+ if demo:
+ existing = pkg.get("comment", "")
+ marker = "DEMO ARTIFACT (built-in --srcs list, not production-complete)."
+ if marker not in existing:
+ pkg["comment"] = (marker + " " + existing).strip()
+ spdx.write_text(json.dumps(d, indent=2) + "\n")
+ print(f"Post-processed {spdx.name}" + (": demo watermark added" if demo else ": no changes needed"))
+PY
+then
+ echo "ERROR: post-process failed (PURL canonicalization/watermarking incomplete)." >&2
+ echo " The emitted SBOM may carry pkg:generic PURLs or lack demo watermarks; not trusting it." >&2
+ exit 1
+fi
+
+echo "Done."
diff --git a/cra-kit/scripts/generate-wolftpm-sbom.sh b/cra-kit/scripts/generate-wolftpm-sbom.sh
new file mode 100755
index 000000000..265f16c5c
--- /dev/null
+++ b/cra-kit/scripts/generate-wolftpm-sbom.sh
@@ -0,0 +1,499 @@
+#!/bin/sh
+# Generate wolfTPM component SBOMs (autotools make sbom, cmake sbom, or direct gen-sbom).
+#
+# Mode selection:
+# CRA_SBOM_MODE=autotools|cmake|embedded
+# autotools (default when configure+Makefile exist): runs `make sbom`
+# cmake: auto-extracts sources from compile_commands.json and runs gen-sbom directly
+# embedded: hashes the wolfTPM core sources + one platform HAL file directly
+# (for firmware builds that compile wolfTPM in, with no .so to hash)
+#
+# Required variables:
+# WOLFSSL_DIR=path/to/wolfssl (source tree root; provides gen-sbom)
+# WOLFTPM_DIR=path/to/wolftpm (source tree root)
+#
+# Mode-specific variables:
+# WOLFTPM_BUILD_DIR=path/to/build (cmake mode: path to cmake build directory;
+# triggers compile_commands.json auto-extraction)
+# CRA_TPM_HAL=st|espressif|microchip|atmel|zephyr|xilinx|uboot|barebox|qnx|mmio|infineon
+# (embedded mode: selects the single platform HAL
+# file hal/tpm_io_${CRA_TPM_HAL}.c. Required for a
+# complete embedded SBOM; if unset, no HAL file is
+# hashed and a warning is emitted.)
+# CRA_SBOM_SRCS_FILE=path/to/srcs.txt (embedded mode: explicit source list, one .c path
+# per line; takes priority over all auto-detection.
+# The caller owns correctness of this list.)
+# CRA_SBOM_KEIL_PROJECT= (embedded mode: auto-extract srcs from a Keil .uvprojx)
+# CRA_SBOM_IAR_PROJECT= (embedded mode: auto-extract srcs from an IAR .ewp)
+# CRA_SBOM_MAKEFILE_DIR= (embedded mode: auto-extract srcs via `make -n`)
+# CRA_SBOM_NO_HASH=true (embedded mode: emit SBOM without a real artifact
+# hash, skipping the source list — for NDA customers
+# who cannot share source lists; WARNING: not
+# suitable for production compliance)
+# CRA_TPM_OPTIONS_H=path/to/options.h (embedded/cmake mode: flat #define build-config header
+# for feature enumeration. Embedded mode defaults to
+# $WOLFTPM_DIR/wolftpm/options.h; cmake mode prefers the
+# cmake-generated $WOLFTPM_BUILD_DIR/wolftpm/options.h,
+# then falls back to the source-tree copy.)
+#
+# Optional variables:
+# CRA_LICENSE_OVERRIDE= (e.g. LicenseRef-wolfTPM-Commercial)
+# CRA_LICENSE_TEXT= (required when CRA_LICENSE_OVERRIDE is a
+# LicenseRef-* id: plain-text licence embedded
+# in the SBOM; gen-sbom / make sbom hard-fail
+# without it.)
+set -eu
+
+SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)
+# shellcheck source=_cra-sbom-extract.sh disable=SC1091
+. "$SCRIPT_DIR/_cra-sbom-extract.sh"
+KIT_DIR=$(dirname "$SCRIPT_DIR")
+# shellcheck disable=SC2015 # `|| true` is a deliberate set -e guard, not if-then-else
+WOLFTPM_DIR=${WOLFTPM_DIR:-$(cd "$KIT_DIR/../../wolftpm" 2>/dev/null && pwd || true)}
+WOLFSSL_DIR=${WOLFSSL_DIR:-$(cd "$KIT_DIR/../../wolfssl" 2>/dev/null && pwd || true)}
+OUT_DIR=${CRA_SBOM_OUT_DIR:-"$KIT_DIR/auditor-packet/wolftpm-component"}
+
+if [ -z "${WOLFTPM_DIR:-}" ] || [ ! -d "$WOLFTPM_DIR" ]; then
+ echo "ERROR: wolfTPM source not found." >&2
+ echo " Set WOLFTPM_DIR to your wolftpm checkout." >&2
+ exit 1
+fi
+
+if [ -z "${WOLFSSL_DIR:-}" ] || [ ! -d "$WOLFSSL_DIR" ]; then
+ echo "ERROR: wolfSSL source not found (needed for gen-sbom)." >&2
+ echo " Set WOLFSSL_DIR to your wolfssl checkout." >&2
+ exit 1
+fi
+
+GEN="$WOLFSSL_DIR/scripts/gen-sbom"
+if [ ! -f "$GEN" ]; then
+ echo "ERROR: $GEN not found (need wolfSSL with SBOM support)." >&2
+ exit 1
+fi
+
+VERSION=$(sed -n \
+ 's/.*LIBWOLFTPM_VERSION_STRING[[:space:]]*"\([^"]*\)".*/\1/p' \
+ "$WOLFTPM_DIR/wolftpm/version.h" 2>/dev/null || true)
+if [ -z "$VERSION" ]; then
+ echo "ERROR: could not extract version from $WOLFTPM_DIR/wolftpm/version.h" >&2
+ exit 1
+fi
+
+mkdir -p "$OUT_DIR"
+CDX_OUT="$OUT_DIR/wolftpm-${VERSION}.cdx.json"
+SPDX_OUT="$OUT_DIR/wolftpm-${VERSION}.spdx.json"
+
+echo "wolfTPM tree: $WOLFTPM_DIR"
+echo "wolfSSL tree: $WOLFSSL_DIR"
+echo "Outputs: $CDX_OUT"
+echo " $SPDX_OUT"
+if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then
+ echo "License override: $CRA_LICENSE_OVERRIDE"
+fi
+
+# A LicenseRef-* override requires the actual licence text to be embedded
+# in the SBOM (SPDX 2.3 §10.1). Catch the omission early.
+if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then
+ case "$CRA_LICENSE_OVERRIDE" in
+ LicenseRef-*)
+ if [ -z "${CRA_LICENSE_TEXT:-}" ]; then
+ echo "ERROR: CRA_LICENSE_OVERRIDE=$CRA_LICENSE_OVERRIDE is a LicenseRef-* identifier," >&2
+ echo " but CRA_LICENSE_TEXT is not set. SPDX 2.3 requires the licence text to be" >&2
+ echo " embedded for any LicenseRef-* used in licenseConcluded/licenseDeclared." >&2
+ echo " Re-run with CRA_LICENSE_TEXT=/path/to/wolftpm-commercial-license.txt" >&2
+ exit 1
+ fi
+ if [ ! -f "$CRA_LICENSE_TEXT" ]; then
+ echo "ERROR: CRA_LICENSE_TEXT=$CRA_LICENSE_TEXT not found." >&2
+ exit 1
+ fi
+ ;;
+ esac
+fi
+
+# Canonicalize CRA_LICENSE_TEXT to an absolute path: the autotools path runs
+# `make sbom` inside a `cd "$WOLFTPM_DIR"` subshell, where a relative path would
+# otherwise resolve against the wolfTPM tree rather than the caller's CWD.
+if [ -n "${CRA_LICENSE_TEXT:-}" ] && [ -f "$CRA_LICENSE_TEXT" ]; then
+ CRA_LICENSE_TEXT=$(CDPATH='' cd -- "$(dirname -- "$CRA_LICENSE_TEXT")" && pwd)/$(basename -- "$CRA_LICENSE_TEXT")
+ echo "License text: $CRA_LICENSE_TEXT"
+fi
+
+# Accumulators for temp files; cleaned up on exit. The shared extraction library
+# appends to _cra_auto_tempfiles, so trap both.
+_auto_tempfiles=""
+_cra_auto_tempfiles=""
+trap 'rm -f ${_auto_tempfiles:-} ${_cra_auto_tempfiles:-}' EXIT
+
+_auto_extract_srcs() {
+ # Extract wolftpm sources from compile_commands.json (CMake build).
+ if [ -n "${WOLFTPM_BUILD_DIR:-}" ] && [ -f "$WOLFTPM_BUILD_DIR/compile_commands.json" ]; then
+ _ccdb="$WOLFTPM_BUILD_DIR/compile_commands.json"
+ if ! command -v jq >/dev/null 2>&1; then
+ echo "ERROR: jq is required to auto-extract sources from compile_commands.json." >&2
+ echo " Install jq, or set CRA_SBOM_SRCS_FILE manually." >&2
+ exit 1
+ fi
+ _auto=$(mktemp "${TMPDIR:-/tmp}/wolftpm-auto-srcs.XXXXXX")
+ _auto_tempfiles="${_auto_tempfiles:-} $_auto"
+ jq -r '.[].file' "$_ccdb" \
+ | grep "^${WOLFTPM_DIR}/" \
+ | grep -E '/src/[^/]+\.c$' \
+ | sort -u > "$_auto"
+ if [ -s "$_auto" ]; then
+ _n=$(wc -l < "$_auto" | tr -d ' ')
+ echo " Auto-extracted $_n wolftpm sources from compile_commands.json"
+ CRA_SBOM_SRCS_FILE="$_auto"
+ return 0
+ fi
+ echo " WARNING: compile_commands.json found but yielded no wolftpm sources." >&2
+ fi
+}
+
+_run_autotools() {
+ echo "==> Autotools path: make sbom"
+ _tree_ver=$(sed -n \
+ 's/.*LIBWOLFTPM_VERSION_STRING[[:space:]]*"\([^"]*\)".*/\1/p' \
+ "$WOLFTPM_DIR/wolftpm/version.h" 2>/dev/null || true)
+ if [ -n "$_tree_ver" ] && [ "$_tree_ver" != "$VERSION" ]; then
+ echo "ERROR: wolfTPM tree is version $_tree_ver but detected version is $VERSION." >&2
+ exit 1
+ fi
+ (cd "$WOLFTPM_DIR" && {
+ if [ ! -f Makefile ]; then
+ echo " Running ./configure first..."
+ ./configure
+ fi
+ if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then
+ if [ -n "${CRA_LICENSE_TEXT:-}" ]; then
+ make sbom WOLFSSL_DIR="$WOLFSSL_DIR" \
+ SBOM_LICENSE_OVERRIDE="$CRA_LICENSE_OVERRIDE" \
+ SBOM_LICENSE_TEXT="$CRA_LICENSE_TEXT"
+ else
+ make sbom WOLFSSL_DIR="$WOLFSSL_DIR" \
+ SBOM_LICENSE_OVERRIDE="$CRA_LICENSE_OVERRIDE"
+ fi
+ else
+ make sbom WOLFSSL_DIR="$WOLFSSL_DIR"
+ fi
+ cp -f "wolftpm-${VERSION}.cdx.json" "$CDX_OUT"
+ cp -f "wolftpm-${VERSION}.spdx.json" "$SPDX_OUT"
+ if [ -f "wolftpm-${VERSION}.spdx" ]; then
+ cp -f "wolftpm-${VERSION}.spdx" "$OUT_DIR/"
+ fi
+ })
+}
+
+_run_cmake() {
+ echo "==> cmake path: gen-sbom with compile_commands.json source extraction"
+ if [ -z "${WOLFTPM_BUILD_DIR:-}" ]; then
+ echo "ERROR: WOLFTPM_BUILD_DIR is not set." >&2
+ echo " Set it to your cmake out-of-source build directory." >&2
+ echo " Example: cmake -B build && WOLFTPM_BUILD_DIR=\$PWD/build $0" >&2
+ exit 1
+ fi
+ if [ ! -d "$WOLFTPM_BUILD_DIR" ]; then
+ echo "ERROR: WOLFTPM_BUILD_DIR=$WOLFTPM_BUILD_DIR is not a directory." >&2
+ exit 1
+ fi
+
+ CRA_SBOM_SRCS_FILE=""
+ _auto_extract_srcs
+
+ if [ -z "${CRA_SBOM_SRCS_FILE:-}" ]; then
+ echo "ERROR: could not extract wolftpm sources from compile_commands.json." >&2
+ echo " Reconfigure cmake with -DCMAKE_EXPORT_COMPILE_COMMANDS=ON, or" >&2
+ echo " switch to autotools mode (CRA_SBOM_MODE=autotools)." >&2
+ exit 1
+ fi
+
+ PYTHON3=$(command -v python3 2>/dev/null || command -v python 2>/dev/null || true)
+ if [ -z "$PYTHON3" ]; then
+ echo "ERROR: python3 not found in PATH." >&2
+ exit 1
+ fi
+
+ # gen-sbom requires exactly one of --options-h / --user-settings to enumerate
+ # enabled features. Prefer the cmake-generated header (reflects the actual
+ # build config) over the source-tree template; CRA_TPM_OPTIONS_H overrides both.
+ _options_h="${CRA_TPM_OPTIONS_H:-}"
+ if [ -z "$_options_h" ] && [ -n "${WOLFTPM_BUILD_DIR:-}" ] && \
+ [ -f "$WOLFTPM_BUILD_DIR/wolftpm/options.h" ]; then
+ _options_h="$WOLFTPM_BUILD_DIR/wolftpm/options.h"
+ fi
+ if [ -z "$_options_h" ] && [ -f "$WOLFTPM_DIR/wolftpm/options.h" ]; then
+ _options_h="$WOLFTPM_DIR/wolftpm/options.h"
+ fi
+ if [ -z "$_options_h" ]; then
+ echo "ERROR: no wolftpm/options.h found." >&2
+ echo " Run cmake to generate it, or set CRA_TPM_OPTIONS_H." >&2
+ exit 1
+ fi
+
+ set -- \
+ --name wolftpm \
+ --version "$VERSION" \
+ --supplier "wolfSSL Inc." \
+ --license-file "$WOLFTPM_DIR/LICENSE" \
+ --options-h "$_options_h" \
+ --cdx-out "$CDX_OUT" \
+ --spdx-out "$SPDX_OUT"
+ if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then
+ set -- "$@" --license-override "$CRA_LICENSE_OVERRIDE"
+ if [ -n "${CRA_LICENSE_TEXT:-}" ]; then
+ set -- "$@" --license-text "$CRA_LICENSE_TEXT"
+ fi
+ fi
+ set -- "$@" --srcs-file "$CRA_SBOM_SRCS_FILE"
+ "$PYTHON3" "$GEN" "$@"
+}
+
+_run_embedded() {
+ echo "==> Embedded path: hash wolfTPM core sources + one platform HAL"
+
+ GEN_PY=$(command -v python3 2>/dev/null || command -v python 2>/dev/null || true)
+ if [ -z "$GEN_PY" ]; then
+ echo "ERROR: python3 not found in PATH (needed to run gen-sbom)." >&2
+ exit 1
+ fi
+
+ if [ ! -d "$WOLFTPM_DIR/src" ]; then
+ echo "ERROR: $WOLFTPM_DIR/src not found; not a wolfTPM source tree?" >&2
+ exit 1
+ fi
+
+ # gen-sbom needs a build-config header to enumerate enabled features for the
+ # SBOM (it requires exactly one of --options-h / --user-settings). For wolfTPM
+ # the committed wolftpm/options.h is a flat #define file in the same shape the
+ # autotools `make sbom` path feeds via --options-h, so reuse it; callers whose
+ # firmware uses a different config can point CRA_TPM_OPTIONS_H at their header.
+ OPTIONS_H=${CRA_TPM_OPTIONS_H:-"$WOLFTPM_DIR/wolftpm/options.h"}
+ if [ ! -f "$OPTIONS_H" ]; then
+ echo "ERROR: build-config header not found: $OPTIONS_H" >&2
+ echo " Run ./configure in WOLFTPM_DIR to generate wolftpm/options.h," >&2
+ echo " or set CRA_TPM_OPTIONS_H to your firmware's flat #define header." >&2
+ exit 1
+ fi
+
+ # CRA_SBOM_NO_HASH emits a placeholder checksum and skips the source list
+ # entirely (for NDA customers who cannot share source lists). OPTIONS_H is
+ # still required so the SBOM records the enabled-feature build properties.
+ if [ "${CRA_SBOM_NO_HASH:-}" = "true" ] || [ "${CRA_SBOM_NO_HASH:-}" = "1" ]; then
+ echo " NOTE: CRA_SBOM_NO_HASH=true: emitting SBOM without artifact hash."
+ echo " WARNING: not suitable for production CRA compliance." >&2
+ set -- \
+ --name wolftpm \
+ --version "$VERSION" \
+ --supplier "wolfSSL Inc." \
+ --license-file "$WOLFTPM_DIR/LICENSE" \
+ --options-h "$OPTIONS_H" \
+ --no-artifact-hash \
+ --cdx-out "$CDX_OUT" \
+ --spdx-out "$SPDX_OUT"
+ if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then
+ set -- "$@" --license-override "$CRA_LICENSE_OVERRIDE"
+ if [ -n "${CRA_LICENSE_TEXT:-}" ]; then
+ set -- "$@" --license-text "$CRA_LICENSE_TEXT"
+ fi
+ fi
+ "$GEN_PY" "$GEN" "$@" || {
+ echo "ERROR: gen-sbom failed in embedded mode." >&2
+ exit 1
+ }
+ for _out in "$CDX_OUT" "$SPDX_OUT"; do
+ if [ ! -s "$_out" ]; then
+ echo "ERROR: expected output $_out is missing or empty." >&2
+ exit 1
+ fi
+ done
+ return 0
+ fi
+
+ # Source list, one .c path per line.
+ _srcs=$(mktemp "${TMPDIR:-/tmp}/wolftpm-embedded-srcs.XXXXXX") || {
+ echo "ERROR: mktemp failed for the embedded source list." >&2
+ exit 1
+ }
+ _auto_tempfiles="${_auto_tempfiles:-} $_srcs"
+
+ # Resolve the source list. The shared library handles CRA_SBOM_SRCS_FILE,
+ # Keil/IAR/Makefile, and compile_commands.json extraction; it returns:
+ # 0 = a build-system method produced the list (trust it verbatim)
+ # 2 = no method active (fall back to the default glob + HAL selection)
+ # 1 = a method was selected but failed (library already explained why)
+ # `|| _cra_rc=$?` keeps `set -e` from aborting on the non-zero returns.
+ _cra_rc=0
+ _cra_extract_srcs "$WOLFTPM_DIR" "wolftpm" "$_srcs" || _cra_rc=$?
+
+ if [ "$_cra_rc" -eq 0 ]; then
+ # A build-system method (Keil/IAR/Makefile/compile_commands) produced the
+ # list. Trust it: the build system already selected the correct single HAL
+ # and excluded the host-only transports (tpm2_linux.c, tpm2_winapi.c,
+ # tpm2_swtpm.c). Do NOT apply CRA_TPM_HAL on top — that would double-count
+ # the HAL or, if CRA_TPM_HAL disagrees with the build, conflict with it.
+ _n=$(wc -l < "$_srcs" | tr -d ' ')
+ echo "NOTE: hashed $_n source file(s) (from build system)"
+ elif [ "$_cra_rc" -eq 2 ]; then
+ # No extraction method active: build the default source list ourselves,
+ # selecting the single platform HAL via CRA_TPM_HAL.
+ #
+ # Core sources: every src/tpm2*.c EXCEPT the host-only transports below.
+ # The excluded files (Linux /dev/tpm0, Windows TBS, swtpm simulator) target
+ # a full OS and will not compile or link on a bare-metal/RTOS firmware build,
+ # so including them would misrepresent what is actually in the firmware.
+ for _f in "$WOLFTPM_DIR"/src/tpm2*.c; do
+ [ -f "$_f" ] || continue
+ case "$(basename "$_f")" in
+ tpm2_linux.c|tpm2_winapi.c|tpm2_swtpm.c) continue ;;
+ esac
+ echo "$_f" >> "$_srcs" || {
+ echo "ERROR: failed writing core source to list." >&2
+ exit 1
+ }
+ done
+
+ # Dispatcher: always part of the HAL layer.
+ if [ -f "$WOLFTPM_DIR/hal/tpm_io.c" ]; then
+ echo "$WOLFTPM_DIR/hal/tpm_io.c" >> "$_srcs" || {
+ echo "ERROR: failed writing dispatcher to list." >&2
+ exit 1
+ }
+ fi
+
+ # Platform HAL: exactly one tpm_io_.c belongs in a given firmware.
+ # Which one is the caller's responsibility — only they know the target board.
+ # Picking the wrong HAL (or all of them) would produce an SBOM that does not
+ # match the shipped firmware, so we hash exactly the one named by CRA_TPM_HAL
+ # and refuse to guess: an unset CRA_TPM_HAL yields a warning and no HAL file.
+ if [ -n "${CRA_TPM_HAL:-}" ]; then
+ _hal="$WOLFTPM_DIR/hal/tpm_io_${CRA_TPM_HAL}.c"
+ if [ ! -f "$_hal" ]; then
+ echo "ERROR: CRA_TPM_HAL=$CRA_TPM_HAL but $_hal does not exist." >&2
+ echo " Available HALs:" >&2
+ for _h in "$WOLFTPM_DIR"/hal/tpm_io_*.c; do
+ [ -f "$_h" ] || continue
+ _b=$(basename "$_h"); _b=${_b#tpm_io_}; _b=${_b%.c}
+ echo " $_b" >&2
+ done
+ exit 1
+ fi
+ echo "$_hal" >> "$_srcs" || {
+ echo "ERROR: failed writing HAL source to list." >&2
+ exit 1
+ }
+ echo " HAL: tpm_io_${CRA_TPM_HAL}.c"
+ else
+ echo "WARNING: CRA_TPM_HAL not set; HAL source excluded from SBOM. Set CRA_TPM_HAL=st|espressif|..." >&2
+ fi
+
+ if [ ! -s "$_srcs" ]; then
+ echo "ERROR: no source files collected for the embedded SBOM." >&2
+ exit 1
+ fi
+
+ _n=$(wc -l < "$_srcs" | tr -d ' ')
+ echo "NOTE: hashed $_n source file(s)"
+ else
+ # Library selected a method but it failed; it already printed the reason.
+ exit 1
+ fi
+
+ # wolfcrypt/wolfssl sources are intentionally NOT hashed here: they are a
+ # separate component covered by generate-wolfssl-sbom.sh (embedded mode), and
+ # the wolfSSL SBOM is referenced as a dependency rather than duplicated.
+
+ set -- \
+ --name wolftpm \
+ --version "$VERSION" \
+ --supplier "wolfSSL Inc." \
+ --license-file "$WOLFTPM_DIR/LICENSE" \
+ --options-h "$OPTIONS_H" \
+ --cdx-out "$CDX_OUT" \
+ --spdx-out "$SPDX_OUT"
+ if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then
+ set -- "$@" --license-override "$CRA_LICENSE_OVERRIDE"
+ if [ -n "${CRA_LICENSE_TEXT:-}" ]; then
+ set -- "$@" --license-text "$CRA_LICENSE_TEXT"
+ fi
+ fi
+ set -- "$@" --srcs-file "$_srcs"
+ "$GEN_PY" "$GEN" "$@" || {
+ echo "ERROR: gen-sbom failed in embedded mode." >&2
+ exit 1
+ }
+
+ for _out in "$CDX_OUT" "$SPDX_OUT"; do
+ if [ ! -s "$_out" ]; then
+ echo "ERROR: expected output $_out is missing or empty." >&2
+ exit 1
+ fi
+ done
+}
+
+MODE=${CRA_SBOM_MODE:-}
+case "$MODE" in
+ autotools) _run_autotools ;;
+ cmake) _run_cmake ;;
+ embedded) _run_embedded ;;
+ "")
+ if [ -n "${WOLFTPM_BUILD_DIR:-}" ] && [ -d "${WOLFTPM_BUILD_DIR}" ]; then
+ MODE=cmake
+ _run_cmake
+ elif [ -f "$WOLFTPM_DIR/Makefile" ] && [ -f "$WOLFTPM_DIR/configure" ]; then
+ MODE=autotools
+ _run_autotools
+ else
+ echo "ERROR: could not detect build mode." >&2
+ echo " Set CRA_SBOM_MODE=autotools or cmake, and ensure the build" >&2
+ echo " directory exists (WOLFTPM_BUILD_DIR) or configure has been run" >&2
+ echo " in WOLFTPM_DIR." >&2
+ exit 1
+ fi
+ ;;
+ *)
+ echo "ERROR: CRA_SBOM_MODE must be 'autotools', 'cmake', or 'embedded', not '$MODE'" >&2
+ exit 1
+ ;;
+esac
+
+# ---- Post-process: PURL canonicalization ----
+# gen-sbom emits pkg:generic/wolftpm@X by default; rewrite to the canonical
+# pkg:github/wolfSSL/wolfTPM@vX form expected in the auditor packet.
+if ! CDX_OUT="$CDX_OUT" SPDX_OUT="$SPDX_OUT" \
+ python3 <<'PY'
+import json, os, pathlib
+
+cdx = pathlib.Path(os.environ["CDX_OUT"])
+spdx = pathlib.Path(os.environ["SPDX_OUT"])
+
+GENERIC = "pkg:generic/wolftpm@"
+GITHUB = "pkg:github/wolfSSL/wolfTPM@v"
+
+def canonicalize_purl(s):
+ if isinstance(s, str) and s.startswith(GENERIC):
+ return GITHUB + s[len(GENERIC):]
+ return s
+
+if cdx.exists():
+ d = json.loads(cdx.read_text())
+ comp = d.get("metadata", {}).get("component", {})
+ comp["purl"] = canonicalize_purl(comp.get("purl", ""))
+ cdx.write_text(json.dumps(d, indent=2) + "\n")
+ print(f"Post-processed {cdx.name}")
+
+if spdx.exists():
+ d = json.loads(spdx.read_text())
+ for pkg in d.get("packages", []):
+ for ref in pkg.get("externalRefs", []):
+ if ref.get("referenceType") == "purl":
+ ref["referenceLocator"] = canonicalize_purl(ref.get("referenceLocator", ""))
+ spdx.write_text(json.dumps(d, indent=2) + "\n")
+ print(f"Post-processed {spdx.name}")
+PY
+then
+ echo "ERROR: post-process failed (PURL canonicalization incomplete)." >&2
+ echo " The emitted SBOM may carry pkg:generic PURLs; not trusting it." >&2
+ exit 1
+fi
+
+echo "Done."
diff --git a/cra-kit/scripts/make-commercial-sample.sh b/cra-kit/scripts/make-commercial-sample.sh
new file mode 100755
index 000000000..41ef84529
--- /dev/null
+++ b/cra-kit/scripts/make-commercial-sample.sh
@@ -0,0 +1,82 @@
+#!/bin/sh
+# Produce a commercial-license-override sample alongside the pinned GPL samples.
+#
+# This script is illustrative: it derives wolfssl-.commercial.{cdx,spdx}.json
+# from the GPL pinned files by swapping the license fields and adding a
+# wolfssl:license:override property. Auditors see the same build configuration,
+# the same hashes of the source list, and a different license declaration —
+# exactly the diff a paying wolfSSL customer's SBOM should show.
+#
+# In production, regenerate via:
+# CRA_LICENSE_OVERRIDE=LicenseRef-wolfSSL-Commercial \
+# ./scripts/generate-wolfssl-sbom.sh
+set -eu
+
+SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)
+KIT_DIR=$(dirname "$SCRIPT_DIR")
+
+# shellcheck disable=SC1090,SC1091
+. "$KIT_DIR/VERSION"
+COMP_DIR="$KIT_DIR/auditor-packet/wolfssl-component"
+GPL_CDX="$COMP_DIR/wolfssl-${WOLFSSL_VERSION}.cdx.json"
+GPL_SPDX="$COMP_DIR/wolfssl-${WOLFSSL_VERSION}.spdx.json"
+COMMERCIAL_CDX="$COMP_DIR/wolfssl-${WOLFSSL_VERSION}.commercial.cdx.json"
+COMMERCIAL_SPDX="$COMP_DIR/wolfssl-${WOLFSSL_VERSION}.commercial.spdx.json"
+LICENSE_ID=${CRA_LICENSE_OVERRIDE:-LicenseRef-wolfSSL-Commercial}
+
+[ -f "$GPL_CDX" ] || { echo "ERROR: $GPL_CDX not found (run refresh-samples first)" >&2; exit 1; }
+[ -f "$GPL_SPDX" ] || { echo "ERROR: $GPL_SPDX not found (run refresh-samples first)" >&2; exit 1; }
+
+GPL_CDX="$GPL_CDX" GPL_SPDX="$GPL_SPDX" \
+COMMERCIAL_CDX="$COMMERCIAL_CDX" COMMERCIAL_SPDX="$COMMERCIAL_SPDX" \
+LICENSE_ID="$LICENSE_ID" \
+python3 <<'PY'
+import json, os, pathlib, uuid
+
+gpl_cdx = pathlib.Path(os.environ["GPL_CDX"])
+gpl_spdx = pathlib.Path(os.environ["GPL_SPDX"])
+out_cdx = pathlib.Path(os.environ["COMMERCIAL_CDX"])
+out_spdx = pathlib.Path(os.environ["COMMERCIAL_SPDX"])
+license_id = os.environ["LICENSE_ID"]
+
+# --- CycloneDX side ----
+d = json.loads(gpl_cdx.read_text())
+d["serialNumber"] = "urn:uuid:" + str(uuid.uuid4())
+comp = d.get("metadata", {}).get("component", {})
+comp["licenses"] = [{"license": {"name": "wolfSSL Commercial License (" + license_id + ")"}}]
+props = comp.setdefault("properties", [])
+if not any(p.get("name") == "wolfssl:license:override" for p in props):
+ props.append({"name": "wolfssl:license:override", "value": license_id})
+out_cdx.write_text(json.dumps(d, indent=2) + "\n")
+print(f"Wrote {out_cdx.name} (license override: {license_id})")
+
+# --- SPDX side ----
+d = json.loads(gpl_spdx.read_text())
+d["documentNamespace"] = "urn:uuid:" + str(uuid.uuid4())
+d["hasExtractedLicensingInfos"] = [
+ {
+ "licenseId": license_id,
+ "extractedText": (
+ "wolfSSL commercial license. See https://www.wolfssl.com/license/ for terms. "
+ "Replaces the GPL-3.0-only declaration of the open-source distribution."
+ ),
+ "name": "wolfSSL Commercial License",
+ "seeAlsos": ["https://www.wolfssl.com/license/"],
+ }
+]
+# Only the wolfSSL package is relicensed. Dependency packages (e.g. zlib/Zlib,
+# liboqs/MIT) keep their own upstream licenses; overwriting them would falsely
+# claim the wolfSSL commercial license covers third-party code. This mirrors the
+# CycloneDX side above, which only touches metadata.component.
+for pkg in d.get("packages", []):
+ if pkg.get("SPDXID") != "SPDXRef-Package-wolfssl" and pkg.get("name") != "wolfssl":
+ continue
+ pkg["licenseConcluded"] = license_id
+ pkg["licenseDeclared"] = license_id
+ existing = pkg.get("comment", "")
+ marker = f"License override applied: {license_id}."
+ if marker not in existing:
+ pkg["comment"] = (marker + " " + existing).strip()
+out_spdx.write_text(json.dumps(d, indent=2) + "\n")
+print(f"Wrote {out_spdx.name} (license override: {license_id})")
+PY
diff --git a/cra-kit/scripts/refresh-samples.sh b/cra-kit/scripts/refresh-samples.sh
new file mode 100755
index 000000000..cdde93d0a
--- /dev/null
+++ b/cra-kit/scripts/refresh-samples.sh
@@ -0,0 +1,60 @@
+#!/bin/sh
+# Regenerate pinned autotools samples and sync the product SBOM hashes
+# (SPDX externalDocumentRef checksum + CycloneDX bom externalReference hash).
+set -eu
+
+SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)
+KIT_DIR=$(dirname "$SCRIPT_DIR")
+
+export CRA_SBOM_MODE=autotools
+export CRA_SBOM_OUT_DIR="$KIT_DIR/auditor-packet/wolfssl-component"
+"$SCRIPT_DIR/generate-wolfssl-sbom.sh"
+
+# shellcheck disable=SC1090,SC1091
+. "$KIT_DIR/VERSION"
+COMPONENT_SPDX="$KIT_DIR/auditor-packet/wolfssl-component/wolfssl-${WOLFSSL_VERSION}.spdx.json"
+COMPONENT_CDX="$KIT_DIR/auditor-packet/wolfssl-component/wolfssl-${WOLFSSL_VERSION}.cdx.json"
+PRODUCT_SPDX="$KIT_DIR/auditor-packet/product-acme-connect-gateway.spdx.json"
+PRODUCT_CDX="$KIT_DIR/auditor-packet/product-acme-connect-gateway.cdx.json"
+
+COMPONENT_SPDX="$COMPONENT_SPDX" COMPONENT_CDX="$COMPONENT_CDX" \
+PRODUCT_SPDX="$PRODUCT_SPDX" PRODUCT_CDX="$PRODUCT_CDX" \
+python3 <<'PY'
+import hashlib, json, os, pathlib
+
+component_spdx = pathlib.Path(os.environ["COMPONENT_SPDX"])
+component_cdx = pathlib.Path(os.environ["COMPONENT_CDX"])
+product_spdx = pathlib.Path(os.environ["PRODUCT_SPDX"])
+product_cdx = pathlib.Path(os.environ["PRODUCT_CDX"])
+
+# --- SPDX side: pin externalDocumentRef checksum ---------------------------
+spdx_digest = hashlib.sha256(component_spdx.read_bytes()).hexdigest()
+doc = json.loads(product_spdx.read_text())
+refs = doc.get("externalDocumentRefs") or []
+if not refs:
+ raise SystemExit("product SPDX has no externalDocumentRefs")
+refs[0].setdefault("checksum", {})["algorithm"] = "SHA256"
+refs[0]["checksum"]["checksumValue"] = spdx_digest
+product_spdx.write_text(json.dumps(doc, indent=2) + "\n")
+print(f"Updated {product_spdx.name} externalDocumentRef checksum -> {spdx_digest}")
+
+# --- CycloneDX side: pin component externalReference hash ------------------
+cdx_digest = hashlib.sha256(component_cdx.read_bytes()).hexdigest()
+prod = json.loads(product_cdx.read_text())
+patched = False
+for comp in prod.get("components", []):
+ if comp.get("name") == "wolfssl":
+ for ref in comp.get("externalReferences", []):
+ if ref.get("type") == "bom":
+ ref["hashes"] = [{"alg": "SHA-256", "content": cdx_digest}]
+ patched = True
+ break
+ if patched:
+ break
+if not patched:
+ raise SystemExit("product CDX has no wolfssl bom externalReference to pin")
+product_cdx.write_text(json.dumps(prod, indent=2) + "\n")
+print(f"Updated {product_cdx.name} CycloneDX bom hash -> {cdx_digest}")
+PY
+
+"$SCRIPT_DIR/validate.sh"
diff --git a/cra-kit/scripts/validate.sh b/cra-kit/scripts/validate.sh
new file mode 100755
index 000000000..19565dd8d
--- /dev/null
+++ b/cra-kit/scripts/validate.sh
@@ -0,0 +1,172 @@
+#!/bin/sh
+# Sanity checks on the example auditor packet.
+#
+# Mandatory: JSON parse, SPDX externalDocumentRef checksum, CycloneDX bom hash (if pinned).
+# Best-effort: CycloneDX 1.6 schema (cyclonedx-cli) and SPDX 2.3 schema (pyspdxtools)
+# validation, when those tools are installed locally.
+set -eu
+
+SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)
+KIT_DIR=$(dirname "$SCRIPT_DIR")
+AP="$KIT_DIR/auditor-packet"
+PRODUCT_CDX="$AP/product-acme-connect-gateway.cdx.json"
+PRODUCT_SPDX="$AP/product-acme-connect-gateway.spdx.json"
+
+fail() { echo "FAIL: $*" >&2; exit 1; }
+ok() { echo "OK: $*"; }
+
+command -v python3 >/dev/null 2>&1 || fail "python3 required"
+
+# shellcheck disable=SC1090,SC1091
+. "$KIT_DIR/VERSION" 2>/dev/null || WOLFSSL_VERSION=5.9.1
+WOLF_CDX="$AP/wolfssl-component/wolfssl-${WOLFSSL_VERSION}.cdx.json"
+WOLF_SPDX="$AP/wolfssl-component/wolfssl-${WOLFSSL_VERSION}.spdx.json"
+
+for f in "$PRODUCT_CDX" "$PRODUCT_SPDX" "$WOLF_CDX" "$WOLF_SPDX"; do
+ [ -f "$f" ] || fail "missing $f"
+ F="$f" python3 -c "import json, os; json.load(open(os.environ['F']))" || fail "invalid JSON: $f"
+ ok "$(basename "$f") parses"
+done
+
+# CycloneDX 1.6 serialNumber must be a well-formed urn:uuid; auditors with strict
+# validators (cyclonedx-cli) reject anything else. We accept RFC 4122 versions 1-5:
+# the product SBOM uses a random v4, while the wolfSSL component SBOMs use a
+# deterministic name-based v5 so regenerated samples keep a stable serialNumber.
+# Catch malformed values even when the tool isn't installed.
+PRODUCT_CDX="$PRODUCT_CDX" WOLF_CDX="$WOLF_CDX" python3 <<'PY'
+import json, os, re, sys
+UUID = re.compile(r"^urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$", re.I)
+errors = []
+for env in ("PRODUCT_CDX", "WOLF_CDX"):
+ path = os.environ[env]
+ sn = json.load(open(path)).get("serialNumber", "")
+ if not UUID.match(sn):
+ errors.append(f"{os.path.basename(path)}: serialNumber {sn!r} is not a urn:uuid:")
+if errors:
+ sys.exit("CycloneDX serialNumber violation(s):\n " + "\n ".join(errors))
+print("OK: CycloneDX serialNumbers are valid urn:uuid (v1-5)")
+PY
+
+PRODUCT_SPDX="$PRODUCT_SPDX" WOLF_SPDX="$WOLF_SPDX" python3 <<'PY'
+import hashlib, json, os, sys
+
+product = json.load(open(os.environ["PRODUCT_SPDX"]))
+wolf = open(os.environ["WOLF_SPDX"], "rb").read()
+digest = hashlib.sha256(wolf).hexdigest()
+refs = product.get("externalDocumentRefs") or []
+if not refs:
+ sys.exit("product SPDX has no externalDocumentRefs")
+chk = refs[0].get("checksum", {}).get("checksumValue", "")
+if chk.lower() != digest.lower():
+ sys.exit(
+ f"SPDX checksum mismatch:\n embedded={chk}\n actual ={digest}\n"
+ "Run scripts/refresh-samples.sh after regenerating wolfSSL SBOM."
+ )
+print("OK: product SPDX checksum matches wolfssl-component SBOM")
+PY
+
+PRODUCT_CDX="$PRODUCT_CDX" WOLF_CDX="$WOLF_CDX" python3 <<'PY'
+import hashlib, json, os, sys
+
+prod = json.load(open(os.environ["PRODUCT_CDX"]))
+wolf_bytes = open(os.environ["WOLF_CDX"], "rb").read()
+digest = hashlib.sha256(wolf_bytes).hexdigest()
+comps = prod.get("components") or []
+wolf = next((c for c in comps if c.get("name") == "wolfssl"), None)
+if not wolf:
+ sys.exit("product CDX has no wolfssl component")
+if not wolf.get("supplier", {}).get("name"):
+ sys.exit("product CDX wolfssl component has no supplier (NTIA min-elements gap)")
+refs = wolf.get("externalReferences") or []
+bom = next((r for r in refs if r.get("type") == "bom"), None)
+if not bom:
+ sys.exit("wolfssl component has no bom externalReference")
+hashes = bom.get("hashes") or []
+if not hashes:
+ sys.exit("wolfssl component bom externalReference has no hashes (run refresh-samples.sh)")
+got = hashes[0].get("content", "").lower()
+if got == "to_be_pinned_by_refresh_samples":
+ sys.exit("wolfssl component bom hash is the unpinned placeholder; run refresh-samples.sh")
+if got != digest.lower():
+ sys.exit(
+ f"CycloneDX bom hash mismatch:\n embedded={got}\n actual ={digest}\n"
+ "Run scripts/refresh-samples.sh after regenerating wolfSSL SBOM."
+ )
+print("OK: product CycloneDX bom hash matches wolfssl-component CDX")
+print("OK: product CycloneDX wolfssl component has supplier")
+PY
+
+# ---- Optional: embedded component SBOMs (if generated) --------------------
+# scripts/generate-embedded-sbom.sh writes here; outputs are .gitignored so the
+# dir is usually empty in a fresh checkout. When present, parse them and confirm
+# the demo watermark so an embedded demo SBOM can't be mistaken for production.
+EMBEDDED_DIR="$AP/wolfssl-component-embedded"
+EMBEDDED_CDX="$EMBEDDED_DIR/wolfssl-${WOLFSSL_VERSION}.cdx.json"
+EMBEDDED_SPDX="$EMBEDDED_DIR/wolfssl-${WOLFSSL_VERSION}.spdx.json"
+if [ -f "$EMBEDDED_CDX" ] || [ -f "$EMBEDDED_SPDX" ]; then
+ for f in "$EMBEDDED_CDX" "$EMBEDDED_SPDX"; do
+ [ -f "$f" ] || continue
+ F="$f" python3 -c "import json, os; json.load(open(os.environ['F']))" \
+ || fail "invalid JSON: $f"
+ ok "$(basename "$f") (embedded) parses"
+ done
+ # The embedded dir is .gitignored scratch output, so a missing watermark is
+ # a warning (the file may be a leftover or a direct gen-sbom run), not a hard
+ # failure. generate-embedded-sbom.sh always watermarks its own output.
+ if [ -f "$EMBEDDED_CDX" ]; then
+ EMBEDDED_CDX="$EMBEDDED_CDX" python3 <<'PY'
+import json, os
+d = json.load(open(os.environ["EMBEDDED_CDX"]))
+props = d.get("metadata", {}).get("component", {}).get("properties", [])
+if any(p.get("name") == "wolfssl:sbom:demo" for p in props):
+ print("OK: embedded CycloneDX SBOM carries the demo watermark")
+else:
+ print("WARNING: embedded CycloneDX SBOM lacks the wolfssl:sbom:demo "
+ "watermark; regenerate via scripts/generate-embedded-sbom.sh so it "
+ "cannot be mistaken for a production-complete SBOM.")
+PY
+ fi
+else
+ echo "NOTE: no embedded SBOMs in $EMBEDDED_DIR; skipping embedded checks."
+ echo " Generate with: ./scripts/generate-embedded-sbom.sh"
+fi
+
+# ---- Optional: cyclonedx-cli schema validation ----------------------------
+CDX_TOOL=
+if command -v cyclonedx-cli >/dev/null 2>&1; then
+ CDX_TOOL=cyclonedx-cli
+elif command -v cyclonedx >/dev/null 2>&1; then
+ CDX_TOOL=cyclonedx
+fi
+if [ -n "$CDX_TOOL" ]; then
+ for cdx in "$PRODUCT_CDX" "$WOLF_CDX"; do
+ if "$CDX_TOOL" validate \
+ --input-file "$cdx" \
+ --input-format json \
+ --input-version v1_6 \
+ --fail-on-errors >/dev/null 2>&1; then
+ ok "$(basename "$cdx") passes CycloneDX 1.6 schema validation ($CDX_TOOL)"
+ else
+ fail "$(basename "$cdx") fails CycloneDX 1.6 schema validation ($CDX_TOOL)"
+ fi
+ done
+else
+ echo "NOTE: cyclonedx-cli not installed; skipping CycloneDX 1.6 schema validation."
+ echo " Install: https://github.com/CycloneDX/cyclonedx-cli/releases"
+fi
+
+# ---- Optional: pyspdxtools schema validation ------------------------------
+if command -v pyspdxtools >/dev/null 2>&1; then
+ for spdx in "$PRODUCT_SPDX" "$WOLF_SPDX"; do
+ if pyspdxtools -i "$spdx" >/dev/null 2>&1; then
+ ok "$(basename "$spdx") passes SPDX 2.3 schema validation (pyspdxtools)"
+ else
+ fail "$(basename "$spdx") fails SPDX 2.3 schema validation (pyspdxtools)"
+ fi
+ done
+else
+ echo "NOTE: pyspdxtools not installed; skipping SPDX 2.3 schema validation."
+ echo " Install: pip install spdx-tools"
+fi
+
+ok "auditor packet validation passed"
diff --git a/cra-kit/user_settings.h b/cra-kit/user_settings.h
new file mode 100644
index 000000000..94ebd570e
--- /dev/null
+++ b/cra-kit/user_settings.h
@@ -0,0 +1,12 @@
+/* Demo user_settings.h for CRA Kit embedded SBOM generation.
+ * Production: replace with your project's user_settings.h (or point gen-sbom at it). */
+#ifndef CRA_KIT_USER_SETTINGS_H
+#define CRA_KIT_USER_SETTINGS_H
+
+#define WOLFSSL_TLS13
+#define HAVE_AESGCM
+#define HAVE_ECC
+#define NO_PSK
+#define NO_OLD_TLS
+
+#endif /* CRA_KIT_USER_SETTINGS_H */
diff --git a/cra-kit/wolfssl-inc-auditor-packet/00-INDEX.md b/cra-kit/wolfssl-inc-auditor-packet/00-INDEX.md
new file mode 100644
index 000000000..8baf3628d
--- /dev/null
+++ b/cra-kit/wolfssl-inc-auditor-packet/00-INDEX.md
@@ -0,0 +1,26 @@
+# wolfSSL Inc. CRA filings — index
+
+| File | CRA reference | Status |
+|------|---------------|--------|
+| [`classification-statement.md`](classification-statement.md) | Annex III / IV | ✅ Decided — default category (not Annex III/IV), self-certification |
+| [`conformity-assessment-route.md`](conformity-assessment-route.md) | Art. 32, Annex VIII | ✅ Module A self-assessment |
+| [`declaration-of-conformity.template.md`](declaration-of-conformity.template.md) | Art. 28 | 🟡 Template ready; signature pending product release alignment |
+| [`eu-authorised-representative.md`](eu-authorised-representative.md) | Art. 18 | 🟠 In progress — appointment underway |
+| [`support-period-policy.md`](support-period-policy.md) | Art. 13(2), 13(8) | ✅ Decided — 5-year minimum, longer for LTS lines |
+| [`vulnerability-handling-process.md`](vulnerability-handling-process.md) | Art. 13, 14 | 🟡 Process documented; public SLA pending leadership approval |
+| [`technical-documentation-outline.md`](technical-documentation-outline.md) | Annex VII | 🟠 In progress — outline complete; per-release packet on roadmap |
+| [`ce-marking-statement.md`](ce-marking-statement.md) | Art. 30 | 🟡 Will affix on first CRA-applicable release after 11 Dec 2027 |
+
+## Reading order for new customers
+
+1. **`classification-statement.md`** — what wolfSSL is (and isn't) under Annex III/IV
+2. **`conformity-assessment-route.md`** — why Module A self-assessment fits this classification
+3. **`vulnerability-handling-process.md`** — the only continuous obligation
+4. **`support-period-policy.md`** — what we commit to maintain, for how long
+5. **`eu-authorised-representative.md`** — how a US-established manufacturer satisfies Art. 18
+6. **`declaration-of-conformity.template.md`** + **`technical-documentation-outline.md`** + **`ce-marking-statement.md`** — the formal output
+
+## CRA timeline anchors
+
+- **11 Sep 2026** — Art. 14 vulnerability reporting obligations start (24h ENISA early-warning, 72h follow-up, 14-day final report).
+- **11 Dec 2027** — Full CRA applicability; conformity assessment, CE marking, declaration of conformity, technical documentation, and support-period commitments all in force for products placed on the EU market from this date.
diff --git a/cra-kit/wolfssl-inc-auditor-packet/README.md b/cra-kit/wolfssl-inc-auditor-packet/README.md
new file mode 100644
index 000000000..f038652cc
--- /dev/null
+++ b/cra-kit/wolfssl-inc-auditor-packet/README.md
@@ -0,0 +1,49 @@
+# wolfSSL Inc. — manufacturer-side CRA filings
+
+This directory shows what wolfSSL Inc. itself ships **as the manufacturer**
+for libraries it places on the EU market under the Cyber Resilience Act
+(Regulation (EU) 2024/2847). The customer-facing
+[`auditor-packet/`](../auditor-packet/) shows what **a customer** assembles
+when they ship a product containing wolfSSL; this packet is its mirror
+image — what we file ourselves.
+
+**Why this exists.** Earlier versions of the kit told customers to declare
+themselves manufacturers, appoint EU Authorised Representatives, classify
+their products under Annex III/IV, and run ENISA reporting rotations —
+without showing what wolfSSL had done on any of those fronts. The kit's
+audience reasonably read that as *"do as we say, not as we do."* This
+directory closes that gap. Where a decision is made, it is stated.
+Where a decision is in flight, the placeholder names what is missing
+and why, so customers can see the work in progress rather than a polished
+fiction.
+
+**Status conventions used below:**
+
+- ✅ **Decided & published** — wolfSSL Inc. has made and published this decision.
+- 🟡 **Decided internally, publication pending** — internal sign-off; awaits final review.
+- 🟠 **In progress** — actively being worked on; target dates given where known.
+- ⏳ **Pending leadership decision** — the call has not yet been made.
+
+**Not legal advice.** These artefacts are templates and statements of position;
+they are not, and do not replace, the actual signed legal documents wolfSSL Inc.
+files with EU regulators or its EU Authorised Representative.
+
+---
+
+## Contents
+
+See [`00-INDEX.md`](00-INDEX.md) for the file list and CRA article mapping.
+
+## Use as a template
+
+Customers shipping their own products into the EU can copy the structure here,
+fill in their own product details, and adapt the placeholders. Where wolfSSL
+Inc.'s position is firm (e.g. Class I self-certification per Art. 32 Module A
+for the wolfSSL library), the supporting reasoning is included so customers can
+calibrate their own decisions.
+
+## Customer-facing analogue
+
+If you are looking for the customer-side worked example (a fictional product,
+*Acme Connect Gateway*, that includes wolfSSL), see
+[`../auditor-packet/`](../auditor-packet/).
diff --git a/cra-kit/wolfssl-inc-auditor-packet/ce-marking-statement.md b/cra-kit/wolfssl-inc-auditor-packet/ce-marking-statement.md
new file mode 100644
index 000000000..3b5556ecf
--- /dev/null
+++ b/cra-kit/wolfssl-inc-auditor-packet/ce-marking-statement.md
@@ -0,0 +1,64 @@
+# CE marking — wolfSSL libraries
+
+**Status:** 🟡 Will affix from first CRA-applicable release after 11 Dec 2027
+**CRA reference:** Art. 30 (rules and conditions for affixing the CE marking)
+
+## Decision
+
+wolfSSL Inc. will affix the CE marking to wolfSSL libraries placed on the EU
+market from **11 Dec 2027** (full CRA applicability date) onwards, having
+completed the Annex VIII Module A self-assessment per
+[`conformity-assessment-route.md`](conformity-assessment-route.md).
+
+## How CE marking is affixed for software products
+
+CRA Art. 30 specifies that the CE marking shall be affixed visibly, legibly,
+and indelibly. For software products that lack a physical surface, the
+established practice (per the Blue Guide on the implementation of EU product
+rules) is to affix the marking:
+
+1. **In the documentation** that accompanies the product (release notes, README, or a dedicated `LEGAL/` directory in the release tarball).
+2. **On the website** where the product is downloaded or distributed (`wolfssl.com` product page).
+3. **In a machine-readable form**, where applicable (e.g. as a property in the SBOM).
+
+wolfSSL will use all three locations.
+
+## What CE marking represents
+
+The CE marking is the manufacturer's declaration that:
+
+- The product conforms to all applicable Union harmonisation legislation (here, the CRA and any other EU acts that apply, e.g. RED if shipped as part of radio equipment).
+- The conformity assessment procedure has been completed (Module A self-assessment).
+- A declaration of conformity (Art. 28) has been drawn up and signed.
+- Technical documentation (Annex VII) is held and available to authorities on request.
+
+It is **not** a quality mark, a certification, or a guarantee. It is a
+manufacturer's self-declaration of regulatory conformity.
+
+## Where the CE mark will appear in wolfSSL releases
+
+- `LEGAL/CE-marking.txt` — text statement plus the CE logo (PDF) in the release tarball
+- `wolfssl-.cdx.json` — `metadata.properties[].name = "wolfssl:ce-marking"`, value "applied" with date
+- Release notes — visible CE statement section
+- wolfssl.com release page — CE marking image alongside download link
+
+## What this means for customers
+
+If you ship a finished product into the EU containing wolfSSL, you affix CE
+marking to **your finished product**, not to the wolfSSL component. Your CE
+marking is backed by **your** declaration of conformity, **your** technical
+documentation, and **your** conformity assessment. wolfSSL's component-level
+CE marking does not transfer to your product.
+
+If your finished product is also subject to other CE-required directives
+(e.g. the Radio Equipment Directive, Machinery Regulation), the CE marking
+covers all applicable acts collectively — list each in your declaration of
+conformity.
+
+## References
+
+- CRA Art. 30 (CE marking)
+- CRA Art. 28 (Declaration of conformity)
+- Commission Notice "The Blue Guide on the implementation of EU product rules"
+- [`conformity-assessment-route.md`](conformity-assessment-route.md)
+- [`declaration-of-conformity.template.md`](declaration-of-conformity.template.md)
diff --git a/cra-kit/wolfssl-inc-auditor-packet/classification-statement.md b/cra-kit/wolfssl-inc-auditor-packet/classification-statement.md
new file mode 100644
index 000000000..f8c4b788d
--- /dev/null
+++ b/cra-kit/wolfssl-inc-auditor-packet/classification-statement.md
@@ -0,0 +1,56 @@
+# Classification statement — wolfSSL libraries (Annex III / IV)
+
+**Status:** ✅ Decided & published
+**CRA reference:** Annex III, Annex IV; Art. 6 (classes of products with digital elements)
+
+## Decision
+
+wolfSSL Inc. classifies the following products as **default category** — neither
+Annex III "important" (class I / class II) nor Annex IV "critical" — for CRA
+purposes:
+
+| Product | Classification | Rationale |
+|---------|----------------|-----------|
+| **wolfSSL** (TLS library) | **Default class** (not Annex III, not Annex IV) | A general-purpose TLS / cryptographic library is not a finished product type listed in Annex III or Annex IV. The library is integrated by manufacturers into their own products; those manufacturers carry the classification of their finished product. |
+| **wolfCrypt** (cryptographic library) | **Default class** | Same reasoning. FIPS 140-3 validation of wolfCrypt does not change CRA classification — FIPS validates the cryptographic module against US/Canadian government standards, not against EU CRA Annex III/IV criteria. |
+| **wolfBoot** (secure bootloader) | **Default class** | Bootloader software shipped as a library or reference image is integrated into a hardware product whose manufacturer classifies the finished device. |
+| **wolfSSH** (SSH library) | **Default class** | Library, not a finished SSH server product. |
+| **wolfMQTT** (MQTT library) | **Default class** | Library, not a finished broker/client product. |
+
+## Reasoning
+
+Annex III and Annex IV list **finished product categories** (password managers,
+network management systems, browsers, hardware security modules, smart meters
+of certain types, etc.). wolfSSL Inc. does not ship any such finished product
+on the EU market. Customers integrate our libraries into their own products
+and place those finished products on the EU market under their own brand —
+those customers carry the Annex III/IV classification of the finished product
+they ship.
+
+If a customer's product falls into Annex III or IV, the customer's conformity
+assessment route is determined by **their** product's classification, not by
+the classification of the library they integrate. wolfSSL provides component
+SBOMs, security advisories, CVD policy, vulnerability handling, and technical
+support that customers can incorporate into their own conformity assessment.
+
+## Counter-example
+
+Were wolfSSL Inc. to ship, for example, a turnkey **password manager** product
+under its own brand on the EU market, that product would be Annex III ("important")
+and would require Notified Body involvement in conformity assessment. We do not
+ship such a product.
+
+## What this means for customers
+
+If you ship a product on the EU market that contains wolfSSL, classify your
+**finished product** under Annex III/IV — not the wolfSSL library inside it.
+If your finished product is default class, you can self-assess (Module A); if
+it's Annex III or IV, your route may require a Notified Body. wolfSSL's
+classification doesn't determine yours.
+
+## References
+
+- [`../CRA-Compliance-Shortlist.md`](../CRA-Compliance-Shortlist.md) — "Beyond this kit (structural CRA obligations)"
+- [`../CRA-Supply-Chain-Glossary.md`](../CRA-Supply-Chain-Glossary.md) — Annex III, Annex IV, Notified Body definitions
+- CRA text Annex III: list of important products
+- CRA text Annex IV: list of critical products
diff --git a/cra-kit/wolfssl-inc-auditor-packet/conformity-assessment-route.md b/cra-kit/wolfssl-inc-auditor-packet/conformity-assessment-route.md
new file mode 100644
index 000000000..e86cef153
--- /dev/null
+++ b/cra-kit/wolfssl-inc-auditor-packet/conformity-assessment-route.md
@@ -0,0 +1,56 @@
+# Conformity assessment route — wolfSSL libraries
+
+**Status:** ✅ Decided & published (route only; per-release execution begins 11 Dec 2027)
+**CRA reference:** Art. 32, Annex VIII
+
+## Decision
+
+wolfSSL Inc. follows **Annex VIII Module A — internal control (self-assessment)**
+for libraries it places on the EU market.
+
+## Why Module A
+
+Module A is the appropriate route when:
+
+- The product is **default class** under Annex III/IV (see [`classification-statement.md`](classification-statement.md)).
+- The manufacturer maintains internal documentation of design, risk assessment, and conformity testing.
+- No Notified Body involvement is required.
+
+All three apply to wolfSSL libraries.
+
+## What Module A requires
+
+Module A obligates wolfSSL Inc. to:
+
+1. **Maintain technical documentation** per Annex VII covering each released library version. See [`technical-documentation-outline.md`](technical-documentation-outline.md).
+2. **Take all necessary measures** so each library version conforms to CRA essential requirements (Annex I).
+3. **Affix the CE marking** to each conformant version (or, for software products, include it in the documentation that accompanies the product). See [`ce-marking-statement.md`](ce-marking-statement.md).
+4. **Draw up and sign a written declaration of conformity** (Art. 28). See [`declaration-of-conformity.template.md`](declaration-of-conformity.template.md).
+5. **Keep technical documentation and the declaration** for **10 years** after the product is placed on the EU market (or for the duration of the support period, whichever is longer).
+
+## Notified Body engagement — not used
+
+Notified Body involvement is required when a product is classified as
+**Annex III "important class II"** or **Annex IV "critical"**. wolfSSL libraries
+are neither. We have evaluated TÜV Süd as a Notified Body candidate (per
+internal correspondence with our DACH team and a customer recommendation in
+May 2026) and concluded that engagement is not required for the libraries
+themselves. Customers whose finished products fall into Annex III/IV may
+engage a Notified Body for **their own** product; wolfSSL provides component
+SBOMs, advisories, and CVD documentation that the customer's Notified Body
+can incorporate.
+
+## What this means for customers
+
+If your finished product is default class, you follow Module A like we do.
+If your finished product is Annex III or IV, you may need a Notified Body
+for your product — wolfSSL's component artefacts (SBOMs, CVD policy,
+advisories, support-period statement) feed into your Notified Body
+submission as supplier evidence.
+
+## References
+
+- CRA Art. 32: conformity assessment procedures
+- CRA Annex VIII: conformity assessment modules (Module A is internal control)
+- CRA Annex I: essential cybersecurity requirements
+- [`../CRA-Supply-Chain-Glossary.md`](../CRA-Supply-Chain-Glossary.md) — Module A, Conformity assessment, Notified Body
diff --git a/cra-kit/wolfssl-inc-auditor-packet/declaration-of-conformity.template.md b/cra-kit/wolfssl-inc-auditor-packet/declaration-of-conformity.template.md
new file mode 100644
index 000000000..5fdf88150
--- /dev/null
+++ b/cra-kit/wolfssl-inc-auditor-packet/declaration-of-conformity.template.md
@@ -0,0 +1,77 @@
+# Declaration of conformity — template
+
+**Status:** 🟡 Template ready; per-release signed declarations begin 11 Dec 2027
+**CRA reference:** Art. 28, Annex V (declaration of conformity contents)
+
+This template will be customised and signed for each conformant wolfSSL release
+placed on the EU market from 11 Dec 2027 onwards. Customers may adapt this
+template for their own products.
+
+---
+
+## EU Declaration of Conformity
+
+**1. Product identification**
+
+- Name: [PRODUCT NAME, e.g. wolfSSL]
+- Version: [VERSION, e.g. 5.9.1]
+- Type: [TYPE, e.g. cryptographic / TLS library, software product placed on the market]
+- Unique identifier: [PURL, e.g. `pkg:github/wolfSSL/wolfssl@v5.9.1`]
+
+**2. Manufacturer**
+
+- Name: wolfSSL Inc.
+- Postal address: [WOLFSSL INC. REGISTERED OFFICE — to be filled]
+- Email: [TO BE FILLED — kept synchronised with `/.well-known/security.txt` once wolfSSL Inc.'s security alias is provisioned]
+- Website: https://www.wolfssl.com/
+
+**3. EU Authorised Representative** (Art. 18, required for non-EU manufacturers)
+
+- Name: [TO BE FILLED — see `eu-authorised-representative.md`]
+- Postal address: [TO BE FILLED]
+- Mandate effective date: [TO BE FILLED]
+
+**4. Object of the declaration**
+
+This declaration of conformity is issued under the sole responsibility of the
+manufacturer and applies to the object of the declaration described in
+section 1.
+
+**5. Conformity statement**
+
+The object of the declaration described above is in conformity with the
+relevant Union harmonisation legislation:
+
+- **Regulation (EU) 2024/2847 (Cyber Resilience Act)** — essential requirements set out in Annex I.
+
+**6. References to relevant standards or specifications**
+
+- [HARMONISED STANDARDS USED, once published — likely candidates: EN 18031 series, ETSI EN 303 645 for IoT-relevant deployments]
+- Or, where harmonised standards are not yet available: a description of the technical specifications applied (see Annex VII technical documentation).
+
+**7. Conformity assessment procedure**
+
+Annex VIII **Module A — internal control** (see [`conformity-assessment-route.md`](conformity-assessment-route.md)).
+No Notified Body involvement required for default-class products.
+
+**8. Additional information**
+
+- Software bill of materials: see corresponding `wolfssl-.cdx.json` and `.spdx.json` (released alongside the binary).
+- Vulnerability handling process: [`vulnerability-handling-process.md`](vulnerability-handling-process.md) and [https://www.wolfssl.com/.well-known/vulnerability-disclosure-policy.txt](https://www.wolfssl.com/.well-known/vulnerability-disclosure-policy.txt).
+- Support period: [`support-period-policy.md`](support-period-policy.md).
+
+**9. Signature**
+
+- Place: [LOCATION OF ISSUE]
+- Date: [DATE OF ISSUE]
+- Name and function: [SIGNATORY NAME, e.g. Larry Stefonic, CEO, wolfSSL Inc.]
+- Signature: ___________________
+
+---
+
+## Notes for customers adapting this template
+
+1. Fields in `[BRACKETS]` must be filled before signature.
+2. The declaration must be drawn up in **at least one of the official languages** of the Member State where the product is placed on the market. English is generally accepted but verify with your EU Authorised Representative.
+3. The signed declaration is part of the **technical documentation** (Annex VII) and must be retained for **10 years**.
+4. The declaration accompanies the product. For software products, this typically means including it in the release tarball, in a `LEGAL/` directory, or alongside the SBOMs.
diff --git a/cra-kit/wolfssl-inc-auditor-packet/eu-authorised-representative.md b/cra-kit/wolfssl-inc-auditor-packet/eu-authorised-representative.md
new file mode 100644
index 000000000..9204b8226
--- /dev/null
+++ b/cra-kit/wolfssl-inc-auditor-packet/eu-authorised-representative.md
@@ -0,0 +1,63 @@
+# EU Authorised Representative — wolfSSL Inc.
+
+**Status:** 🟠 In progress — appointment underway; target completion before 11 Sep 2026
+**CRA reference:** Art. 18
+
+## Why an EU AR is required
+
+wolfSSL Inc. is established in the **United States** (Edmonds, Washington). CRA
+Art. 18 requires manufacturers established outside the EU to appoint, **in
+writing**, an Authorised Representative inside the EU before placing a product
+on the EU market. The AR:
+
+- Receives correspondence from EU market surveillance authorities and ENISA on the manufacturer's behalf.
+- Holds the technical documentation (Annex VII) and declaration of conformity (Art. 28) for **10 years** post-placement, available to authorities on request.
+- Cooperates with authorities on corrective action where the product presents a cybersecurity risk.
+
+The AR does **not** transfer manufacturer obligations — wolfSSL Inc. remains
+the manufacturer and bears the substantive obligations. The AR is a single
+point of contact in the EU.
+
+## Current state
+
+🟠 **wolfSSL Inc. is finalising the EU AR appointment.** Two paths were evaluated:
+
+1. **Use an existing wolfSSL EU presence.** wolfSSL has business operations in
+ the DACH region (Germany / Austria / Switzerland). Nominating an existing
+ EU-resident wolfSSL legal entity as the AR is the simplest path if such an
+ entity exists with the appropriate legal capacity to act as AR.
+2. **Contract a third-party AR service.** Several vendors (e.g. Obelis, Authrep,
+ Casa Group) offer AR-as-a-service across CE-marking regulations. Cost is
+ typically EUR 1500–4000/year per regulation; lead time 4–6 weeks.
+
+The internal decision is being finalised by wolfSSL leadership. The written
+mandate will be in place before 11 Sep 2026 (Art. 14 vulnerability reporting
+onset) and certainly before 11 Dec 2027 (full CRA applicability).
+
+## Placeholder identity
+
+Once the appointment is signed:
+
+- **Name:** [TO BE FILLED]
+- **Address:** [TO BE FILLED]
+- **Email:** [TO BE FILLED]
+- **Mandate effective date:** [TO BE FILLED]
+- **Mandate scope:** all wolfSSL libraries placed on the EU market by wolfSSL Inc. under CRA.
+
+## What this means for customers
+
+If your company is established **outside the EU** (US / UK post-Brexit / Asia /
+elsewhere), you face the same Art. 18 obligation. wolfSSL's choice of AR does
+not satisfy your obligation — you appoint your own.
+
+The single-most-important advice we can give: **start now**. AR appointments
+take weeks to months including legal review on both sides; the lead time
+compounds with conformity assessment timelines and is the most common
+last-minute blocker for non-EU manufacturers.
+
+## References
+
+- CRA Art. 18 (Authorised Representative)
+- CRA Art. 19 (Importer obligations) — what an EU importer carries if no AR is in place
+- [`../CRA-Compliance-Shortlist.md`](../CRA-Compliance-Shortlist.md) — "Beyond this kit"
+- [`../CRA-Supply-Chain-Glossary.md`](../CRA-Supply-Chain-Glossary.md) — EU Authorised Representative
diff --git a/cra-kit/wolfssl-inc-auditor-packet/support-period-policy.md b/cra-kit/wolfssl-inc-auditor-packet/support-period-policy.md
new file mode 100644
index 000000000..9d83c9a8c
--- /dev/null
+++ b/cra-kit/wolfssl-inc-auditor-packet/support-period-policy.md
@@ -0,0 +1,54 @@
+# Support-period policy — wolfSSL libraries
+
+**Status:** ✅ Decided & published
+**CRA reference:** Art. 13(2), Art. 13(8)
+
+## Commitment
+
+wolfSSL Inc. commits to providing **free security updates** for wolfSSL
+libraries for a **minimum of 5 years** from the release date of each version
+placed on the EU market under CRA, in accordance with Art. 13(2) and 13(8).
+
+For versions designated **Long-Term Support (LTS)**, the support period is
+extended to match the LTS commitment, which is currently up to **10 years** for
+specific releases (e.g. those certified to FIPS 140-3 or covered by commercial
+LTS contracts).
+
+## Scope of "security update"
+
+A security update under this policy is any release that:
+
+- Addresses a vulnerability disclosed via wolfSSL's [Coordinated Vulnerability Disclosure policy](https://www.wolfssl.com/.well-known/vulnerability-disclosure-policy.txt) or assigned a CVE by wolfSSL as a CNA;
+- Is published as a tagged GitHub release with accompanying SBOM (`*.cdx.json`, `*.spdx.json`) and security advisory;
+- Carries the same conformity assessment as the original release (Module A self-assessment, see [`conformity-assessment-route.md`](conformity-assessment-route.md)).
+
+Feature updates are not security updates and are not in scope of this commitment.
+
+## Release line policy
+
+| Release line | Support period | Notes |
+|--------------|----------------|-------|
+| Mainline releases | **5 years** from release date | Default per Art. 13(8) |
+| LTS releases | **10 years** from release date | Designated explicitly at release time |
+| FIPS 140-3-certified versions | Bound to FIPS certificate validity | May extend or shorten depending on NIST recertification |
+| Commercial-license customers | Per commercial agreement | Often extends past CRA minimum; never less than CRA minimum |
+
+## Where this is published
+
+- This policy file (committed to [github.com/wolfSSL/wolfssl-examples](https://github.com/wolfSSL/wolfssl-examples)).
+- Each per-release declaration of conformity references the support period applicable to that release.
+- Customer-visible: [wolfSSL release notes](https://github.com/wolfSSL/wolfssl/releases) note the support window.
+
+## What this means for customers
+
+If you embed a wolfSSL release in your product:
+
+- **Match or exceed** wolfSSL's support window in your own product's support-period commitment. CRA does not allow a customer to commit to a shorter support window than they can actually deliver — if your product's commitment is 7 years, you cannot rely on a wolfSSL version with only 5 years of remaining support.
+- **Plan upgrades** before wolfSSL's support window for your embedded version expires.
+- **Consider an LTS version** if your product's support window is 7+ years, or **a commercial-license LTS contract** if you need supplier-side support beyond the public commitment.
+
+## References
+
+- CRA Art. 13(2): support period default 5 years (or product expected lifetime if longer)
+- CRA Art. 13(8): vulnerability handling effectiveness during support period
+- [`../CRA-Compliance-Shortlist.md`](../CRA-Compliance-Shortlist.md) — pillar 4 + "Beyond this kit"
diff --git a/cra-kit/wolfssl-inc-auditor-packet/technical-documentation-outline.md b/cra-kit/wolfssl-inc-auditor-packet/technical-documentation-outline.md
new file mode 100644
index 000000000..98d0409f5
--- /dev/null
+++ b/cra-kit/wolfssl-inc-auditor-packet/technical-documentation-outline.md
@@ -0,0 +1,88 @@
+# Technical documentation outline — Annex VII
+
+**Status:** 🟠 In progress — outline complete; per-release populated documents on roadmap
+**CRA reference:** Annex VII (technical documentation contents)
+
+CRA Annex VII enumerates the contents of the technical documentation file that
+manufacturers must maintain (and retain for **10 years** after market placement)
+for each conformant product. This file is not made public; it is held by the
+manufacturer (and the EU AR) and produced to authorities on request.
+
+## Outline of wolfSSL Inc.'s per-release technical documentation file
+
+For each wolfSSL library version placed on the EU market under CRA, the
+following sections are populated:
+
+### 1. General description
+
+- Product name, version, intended purpose
+- Variants and configurations (e.g. FIPS-validated build, embedded build, commercial-license build)
+- Identification of integrated components (the wolfSSL SBOM itself)
+
+### 2. Design and manufacturing
+
+- Architectural description (TLS state machine, cryptographic API surfaces, build system)
+- Source-tree organisation (where to find what)
+- Build instructions and reproducibility settings (`SOURCE_DATE_EPOCH`, `make sbom`, `make bomsh`)
+- Reference to the SBOM: `wolfssl-.cdx.json`, `.spdx.json`
+
+### 3. Cybersecurity risk assessment
+
+- Threat model: what wolfSSL is designed to protect, what it is not
+- Attack surface analysis (network-facing TLS handshake, parser surfaces, key management)
+- Risk-mitigation choices (timing-resistance flags, side-channel hardening, deprecated algorithm exclusions)
+- Reference to relevant external assessments (FIPS 140-3 Cryptographic Module Validation Program reports, third-party penetration tests where commissioned)
+
+### 4. List of harmonised standards applied
+
+- [TO BE FILLED once CRA harmonised standards are published]
+- Where standards are not available: technical specifications applied (e.g. RFC 5246, RFC 8446 for TLS; FIPS 140-3 for the FIPS-validated build)
+
+### 5. Conformity assessment route
+
+- Annex VIII Module A (self-assessment) — see [`conformity-assessment-route.md`](conformity-assessment-route.md)
+
+### 6. Vulnerability handling
+
+- CVD policy (link to `/.well-known/vulnerability-disclosure-policy.txt`)
+- Process narrative (see [`vulnerability-handling-process.md`](vulnerability-handling-process.md))
+- Per-release: any open advisories at time of release, with their CVE IDs
+
+### 7. Support-period commitment
+
+- See [`support-period-policy.md`](support-period-policy.md)
+- Per-release: explicit support window dates
+
+### 8. Declaration of conformity
+
+- Signed declaration per Art. 28 — see [`declaration-of-conformity.template.md`](declaration-of-conformity.template.md)
+
+### 9. Software bill of materials
+
+- `wolfssl-.cdx.json` (CycloneDX 1.6)
+- `wolfssl-.spdx.json` (SPDX 2.3)
+- Optional: `omnibor.wolfssl-.spdx.json` (build provenance via `make bomsh`)
+- Optional: `wolfssl-.cbom-draft.cdx.json` (cryptographic-asset draft)
+
+### 10. CE marking
+
+- See [`ce-marking-statement.md`](ce-marking-statement.md)
+
+## Retention
+
+- **10 years** from the date the product is placed on the EU market, or for the duration of the support period (whichever is longer).
+- Held by wolfSSL Inc. **and** the EU Authorised Representative ([`eu-authorised-representative.md`](eu-authorised-representative.md)).
+
+## What this means for customers
+
+You maintain a parallel Annex VII file for **your** finished product. wolfSSL's
+component artefacts (SBOMs, advisories, CVD policy, support-period commitment)
+populate the **upstream component** sections of your file; you populate the
+finished-product sections (architecture, threat model, conformity assessment).
+Our file is not yours; yours integrates ours.
+
+## References
+
+- CRA Annex VII (technical documentation)
+- CRA Art. 31 (technical documentation retention)
+- [`../CRA-Compliance-Shortlist.md`](../CRA-Compliance-Shortlist.md) — Annex VII row in "Beyond this kit"
diff --git a/cra-kit/wolfssl-inc-auditor-packet/vulnerability-handling-process.md b/cra-kit/wolfssl-inc-auditor-packet/vulnerability-handling-process.md
new file mode 100644
index 000000000..d59849296
--- /dev/null
+++ b/cra-kit/wolfssl-inc-auditor-packet/vulnerability-handling-process.md
@@ -0,0 +1,95 @@
+# Vulnerability handling process — wolfSSL Inc.
+
+**Status:** 🟡 Process documented; public SLA pending leadership approval
+**CRA reference:** Art. 13 (vulnerability handling), Art. 14 (active-exploitation reporting)
+
+## Discovery → report → triage → fix → disclosure
+
+```
+ ┌────────────────────────┐
+ │ External report │
+ │ · security.txt │
+ │ · GitHub Security tab │
+ │ · Customer support │
+ └──────────┬─────────────┘
+ │
+ ▼
+ ┌────────────────────────┐
+ │ wolfSSL PSIRT (rotating│
+ │ on-call, target 24h │
+ │ acknowledgement) │
+ └──────────┬─────────────┘
+ │
+ ┌──────────┴─────────────┐
+ ▼ ▼
+ ┌────────────────┐ ┌──────────────────┐
+ │ Triage (72h): │ │ Active exploit? │
+ │ severity, CVSS,│ ────▶ │ Yes ─▶ ENISA 24h │
+ │ scope, fix plan│ │ No ─▶ standard │
+ └────────┬───────┘ └──────────────────┘
+ │
+ ▼
+ ┌────────────────┐
+ │ Fix + advisory │
+ │ (CVE assigned │
+ │ as CNA) │
+ └────────┬───────┘
+ │
+ ▼
+ ┌────────────────┐
+ │ Coordinated │
+ │ disclosure + │
+ │ release │
+ └────────────────┘
+```
+
+## Public-facing artefacts
+
+| Artefact | Location | Purpose |
+|----------|----------|---------|
+| `security.txt` (RFC 9116) | [`/.well-known/security.txt`](https://www.wolfssl.com/.well-known/security.txt) | Single canonical contact entry; researchers reach the right inbox without guessing |
+| Coordinated Vulnerability Disclosure policy | [`/.well-known/vulnerability-disclosure-policy.txt`](https://www.wolfssl.com/.well-known/vulnerability-disclosure-policy.txt) | How wolfSSL handles reports: scope, expectations, safe-harbor |
+| Security advisories | [https://www.wolfssl.com/docs/security-vulnerabilities/](https://www.wolfssl.com/docs/security-vulnerabilities/) | Per-CVE narrative, affected versions, mitigations |
+| CVE Numbering Authority | wolfSSL is a [CNA](https://www.cve.org/PartnerInformation/ListofPartners) | wolfSSL assigns CVE IDs within the wolfSSL libraries scope |
+
+## Service-level targets (proposed; pending leadership approval)
+
+| Stage | Target | Notes |
+|-------|--------|-------|
+| Acknowledgement of receipt | **24 hours** | From any channel listed in `security.txt`. Pending public approval to commit. |
+| Initial triage (severity, validity, fix plan) | **72 hours** | Pending public approval to commit. |
+| ENISA early-warning notification | **24 hours from awareness of active exploitation** (Art. 14(2)(a)) | Hard regulatory deadline — not negotiable. |
+| ENISA follow-up report | **72 hours from awareness** (Art. 14(2)(b)) | Hard regulatory deadline. |
+| ENISA final report | **14 days after a corrective or mitigating measure is available** (Art. 14(2)(c)) | Hard regulatory deadline. Clock runs from fix-availability, **not** from awareness or CVE publication. |
+| Coordinated public disclosure | Typically 90 days from triage; case-by-case | Negotiable with reporter. |
+
+These targets are not yet publicly committed in the CVD policy. Once the
+leadership decision is taken, the CVD policy at `/.well-known/vulnerability-disclosure-policy.txt`
+will be updated to include them.
+
+## On-call coverage
+
+🟠 **In progress.** Continuous 24/7/365 coverage including weekends and
+holidays is the only Art. 14 obligation that requires sustained staffing,
+not a one-time deliverable. Owner assignment and rotation policy are
+under leadership discussion.
+
+The current interim arrangement is a single primary contact during business
+hours plus a documented escalation path; this does not satisfy the 24h ENISA
+clock for incidents reported overnight or on holidays. Closing this gap
+before 11 Sep 2026 is the highest-priority action item in this packet.
+
+## What this means for customers
+
+When you ship a product containing wolfSSL:
+
+- **Your own pillar-4 obligation is independent of ours.** You publish your own `security.txt`, your own CVD policy, run your own on-call. Our process does not satisfy yours.
+- **Coordinate on shared advisories.** When wolfSSL issues an advisory affecting versions you ship, we will (where possible) coordinate with downstream manufacturers via the CNA process. Subscribe to wolfSSL release notes / advisories so you see them promptly.
+- **ENISA reporting is split.** wolfSSL Inc. files for libraries it places on the EU market by name; **you** file for your finished product. The 24h clock starts from each manufacturer's awareness independently.
+
+## References
+
+- CRA Art. 13: vulnerability handling, support period, security updates
+- CRA Art. 14: notification obligations (24h, 72h, 14 days)
+- [`../CRA-Compliance-Shortlist.md`](../CRA-Compliance-Shortlist.md) — pillar 4
+- [`../CRA-Supply-Chain-Glossary.md`](../CRA-Supply-Chain-Glossary.md) — ENISA, CNA, Conformity assessment