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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ This layer currently provides recipes for the following wolfSSL products:
- [wolfSSH lightweight SSH library](https://www.wolfssl.com/products/wolfssh/)
- [wolfMQTT lightweight MQTT client library](https://www.wolfssl.com/products/wolfmqtt/)
- [wolfTPM portable TPM 2.0 library](https://www.wolfssl.com/products/wolftpm/)
- [wolfHSM hardware security module framework](https://www.wolfssl.com/products/wolfhsm/)
(source staging only, see `recipes-wolfssl/wolfhsm/README.md`)
- [wolfSSL-py A Python wrapper for the wolfSSL library](https://github.com/wolfSSL/wolfssl-py)
- [wolfCrypt-py A Python Wrapper for the wolfCrypt API](https://github.com/wolfSSL/wolfcrypt-py)
- [wolfPKCS11 A PKCS#11 implementation using wolfSSL](https://github.com/wolfSSL/wolfpkcs11)
Expand Down
17 changes: 15 additions & 2 deletions classes/wolfssl-commercial.bbclass
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,14 @@
#
# Optional format variables:
# COMMERCIAL_BUNDLE_FILE - Bundle filename including extension (defaults to <NAME>.7z)
# COMMERCIAL_BUNDLE_URL - URL bitbake fetches the bundle from, e.g. an https://
# link to a publicly downloadable GPLv3 FIPS Ready release. Avoids having to
# stage the archive on every build host.
# COMMERCIAL_BUNDLE_GCS_URI - gs:// path to the protected bundle
# COMMERCIAL_BUNDLE_SRC_DIR - Direct path to already-extracted source directory (skips fetch/extract)
#
# Resolution order in get_commercial_src_uri: SRC_DIR (no fetch), then URL, then
# GCS_URI, then a local file under COMMERCIAL_BUNDLE_DIR.

# Commercial bundles already ship generated configure scripts, so skip autoreconf
AUTOTOOLS_AUTORECONF = "no"
Expand Down Expand Up @@ -67,11 +73,17 @@ def get_commercial_src_uri(d):
gcs_uri = d.getVar('COMMERCIAL_BUNDLE_GCS_URI')
placeholder = d.getVar('COMMERCIAL_BUNDLE_PLACEHOLDER') or ''

if gcs_uri and bundle_archive:
# A plain download URL and a gs:// path are handled identically here: both
# are just a URI bitbake's fetcher understands. COMMERCIAL_BUNDLE_URL takes
# precedence so a recipe can offer the public download as its default while
# still allowing a GCS override.
Comment on lines +76 to +79

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment says GCS can still override, but url or gcs_uri means a non-empty URL wins and gcs_uri is never read. Might be good to comment that if both are set GCS is ignored.

remote_uri = d.getVar('COMMERCIAL_BUNDLE_URL') or gcs_uri

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When BitBake cannot find a variable, it does not blank it out. It leaves the raw text ${FOO} sitting in the value. That text is not empty, so this test passes it through and it ends up in SRC_URI as if it were a real web address. I tried it and parsing stopped across the whole build with MalformedUrl. The function above already guards for this with not X.startswith('${'), so the same check here would fix it.


if remote_uri and bundle_archive:
unpack_flag = ';unpack=false' if bundle_archive.endswith('.7z') else ''
sha_flag = f';sha256sum={bundle_sha}' if bundle_sha else ''
filename_flag = f';downloadfilename={bundle_archive}'
return f'{gcs_uri}{filename_flag}{unpack_flag}{sha_flag}'
return f'{remote_uri}{filename_flag}{unpack_flag}{sha_flag}'

bundle_dir = d.getVar('COMMERCIAL_BUNDLE_DIR')
Comment on lines +82 to 88

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This task exists to unpack password-protected .7z bundles by hand, which is why it wants a folder to look in. A plain .zip from a URL needs none of that, because BitBake has already downloaded and unpacked it. The task knows this at line 198, where it says "not a .7z, let BitBake handle it" and returns, but line 186 demands the folder first and stops the build before getting there. I built it: do_fetch succeeds, then COMMERCIAL_BUNDLE_DIR not set. Moving :198 above :186 fixes it and leaves the .7z path unchanged.


Expand Down Expand Up @@ -126,6 +138,7 @@ COMMERCIAL_BUNDLE_SHA ?= ""
COMMERCIAL_BUNDLE_TARGET ?= "${WORKDIR}"
COMMERCIAL_BUNDLE_PLACEHOLDER ?= "${WOLFSSL_LAYERDIR}/recipes-wolfssl/wolfssl/commercial/files/README.md"
COMMERCIAL_BUNDLE_GCS_URI ?= ""
COMMERCIAL_BUNDLE_URL ?= ""
COMMERCIAL_BUNDLE_SRC_DIR ?= ""
COMMERCIAL_BUNDLE_ARCHIVE = "${@get_commercial_bundle_archive(d)}"

Expand Down
3 changes: 3 additions & 0 deletions conf/layer.conf
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ BBFILES += "${LAYERDIR}/recipes-wolfssl/wolfssl/*.bb \
${LAYERDIR}/recipes-wolfssl/wolfengine/wolfssl*.bbappend \
${LAYERDIR}/recipes-wolfssl/wolfboot/*.bb \
${LAYERDIR}/recipes-wolfssl/wolfboot/*.bbappend \
${LAYERDIR}/recipes-wolfssl/wolfhsm/*.bb \
${LAYERDIR}/recipes-wolfssl/wolfhsm/*.bbappend \
${LAYERDIR}/recipes-examples/wolfcrypt/wolfcryptbenchmark/*.bb \
${LAYERDIR}/recipes-examples/wolfcrypt/wolfcryptbenchmark/*.bbappend \
${LAYERDIR}/recipes-examples/wolfcrypt/wolfcrypttest/*.bb \
Expand Down Expand Up @@ -110,6 +112,7 @@ PREFERRED_PROVIDER_wolfcrypt-py ??= "wolfcrypt-py"
PREFERRED_PROVIDER_wolfprovider ??= "wolfprovider"
PREFERRED_PROVIDER_wolfengine ??= "wolfengine"
PREFERRED_PROVIDER_wolfboot ??= "wolfboot"
PREFERRED_PROVIDER_wolfhsm ??= "wolfhsm"

BBFILES += "${@bb.utils.contains('WOLFSSL_TYPE', \
'fips', \
Expand Down
21 changes: 19 additions & 2 deletions conf/wolfssl-fips-ready.conf.sample
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,20 @@
#
# Instructions:
# 1. Set the variables below with your FIPS Ready bundle details
# 2. Set WOLFSSL_SRC_DIR to the directory containing your .zip bundle
# 2. Say where the bundle comes from: WOLFSSL_SRC_URL to have bitbake download
# it, or WOLFSSL_SRC_DIR if the .zip is already staged on the build host
# 3. Build once to get the FIPS_HASH value (if using manual mode)
# 4. Set FIPS_HASH and rebuild (if using manual mode)
#
# The GPLv3 FIPS Ready bundles are published openly, so the URL route needs no
# credentials and nothing staged by hand. A worked example:
#
# WOLFSSL_VERSION = "5.9.2"
# WOLFSSL_SRC = "wolfssl-5.9.2-gplv3-fips-ready"
# WOLFSSL_BUNDLE_FILE = "wolfssl-5.9.2-gplv3-fips-ready.zip"
# WOLFSSL_SRC_URL = "https://www.wolfssl.com/${WOLFSSL_BUNDLE_FILE}"
# WOLFSSL_SRC_SHA = "e3941a3dd38f054ec041b8146c737bde6fa34cda0300708c8207ab3c4e0ab1ad"
# WOLFSSL_LICENSE_MD5 = "d32239bcb673463ab874e80d47fae504"

# Use wolfSSL FIPS Ready as the wolfSSL provider
PREFERRED_PROVIDER_virtual/wolfssl = "wolfssl-fips-ready"
Expand All @@ -33,7 +44,13 @@ WOLFSSL_BUNDLE_FILE = ""
# Bundle SHA256 checksum
WOLFSSL_SRC_SHA = ""

# Directory containing the .zip bundle (REQUIRED - must be set)
# Where the bundle comes from. Set exactly one of these.
#
# URL bitbake downloads the archive from (no manual staging required).
WOLFSSL_SRC_URL = ""
#
# Directory on the build host already holding the .zip, for hosts with no
# outbound network access.
WOLFSSL_SRC_DIR = ""

# MD5 of the COPYING (GPLv3) license file in the bundle
Expand Down
41 changes: 41 additions & 0 deletions recipes-wolfssl/wolfboot/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,47 @@ Artifacts deployed to `tmp/deploy/images/<MACHINE>/`:
Note: the private signing key is **not** deployed — it stays on the
workstation / secrets store you pointed `WOLFBOOT_SIGNING_KEY` at.

## Using an existing wolfSSL source tree

By default `wolfboot.inc` fetches a pinned wolfSSL (`SRCREV_wolfssl`) into
`lib/wolfssl` alongside wolfBoot. To build wolfCrypt from a tree you already
have instead, set an absolute path in `local.conf`:

```
WOLFBOOT_WOLFSSL_SRC = "/path/to/wolfssl"
```

This drops the wolfSSL entry from `SRC_URI` entirely — nothing is downloaded
and `SRCREV_wolfssl` is unused. Both `wolfboot_git.bb` and
`wolfboot-keytools-native_git.bb` pick it up, so the bootloader and the
signing tools stay on one wolfCrypt version.

Things worth knowing:

- **It is a source tree, not a library.** wolfBoot compiles the wolfCrypt
sources into a `-nostdlib` bare-metal image; it cannot link the target
`libwolfssl.so` that the `wolfssl` recipe builds. Point this at wolfSSL
*sources*.
- **The tree is copied into `${WORKDIR}` before use**, by the
`do_stage_external_wolfssl` task. wolfBoot writes its object files next to
the wolfCrypt sources, so an in-place build would dirty your tree. The
original may therefore be read-only or shared, but the copy is a full one —
prefer a clean source tree over a working directory with a large `.git` and
build output in it.
- **Prebuilt objects are excluded** from that copy (`*.o`, `*.a`, `*.lo`,
`*.la`, `*.so*`, `.libs`, `.git`). Without this, a natively configured tree
(`./configure && make`) would hand the cross build host x86 objects that
`make` considers newer than their sources.
- **Changes to the tree do trigger a rebuild.** The recipe fingerprints every
file's path, size and mtime into the task hashes, since BitBake would
otherwise hash only the `WOLFBOOT_WOLFSSL_SRC` string and happily reuse a
stale `wolfboot.elf`.
- The path must be absolute. wolfBoot's `Makefile` abspaths
`WOLFBOOT_LIB_WOLFSSL` only for its own default; a command-line override —
how the recipe passes it — reaches every sub-makefile verbatim. The recipe
rejects relative paths rather than let them resolve against whichever
directory each sub-make runs in.

## SD card layout (wolfBoot A/B scheme)

| Partition | Size | Type | Contents |
Expand Down
18 changes: 15 additions & 3 deletions recipes-wolfssl/wolfboot/wolfboot-keytools-native_git.bb
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
SUMMARY = "wolfBoot signing and key generation tools (native)"
DESCRIPTION = "Host-side keygen and sign utilities for wolfBoot secure-boot \
image signing. Builds RSA4096 signing keys and signs firmware images with \
SHA3-384 hashes. Uses wolfBoot's bundled wolfCrypt (under lib/wolfssl) -- \
no external wolfSSL dependency."
SHA3-384 hashes. Uses wolfBoot's bundled wolfCrypt (under lib/wolfssl) by \
default, or the tree named by WOLFBOOT_WOLFSSL_SRC when that is set."

require wolfboot.inc

Expand All @@ -12,11 +12,23 @@ do_configure[noexec] = "1"

do_compile() {
# Build the keytools (host-side signing/keygen utilities).
#
# Track wolfboot_git.bb's choice of wolfCrypt: the keytools produce the
# keystore and the image signatures that wolfboot.elf then verifies, so
# building the two halves from different wolfSSL versions risks a format
# mismatch that only shows up as a failed verification on the target.
# tools/keytools/Makefile also emits its objects under
# $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src, hence the staged copy here too.
WOLFSSL_DIR="${S}/lib/wolfssl"
if [ -n "${WOLFBOOT_WOLFSSL_SRC}" ]; then
WOLFSSL_DIR="${WOLFBOOT_WOLFSSL_STAGED_SRC}"
fi

oe_runmake -C tools/keytools \
CC="${CC}" \
LD="${CC}" \
WOLFBOOTDIR=${S} \
WOLFBOOT_LIB_WOLFSSL=${S}/lib/wolfssl \
WOLFBOOT_LIB_WOLFSSL="$WOLFSSL_DIR" \
V=1
}

Expand Down
109 changes: 106 additions & 3 deletions recipes-wolfssl/wolfboot/wolfboot.inc
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,129 @@
# Pulls the wolfBoot source tree and the wolfSSL submodule source side-by-side.
# wolfBoot bundles wolfSSL under lib/wolfssl, so we stage wolfSSL there instead
# of fetching it from the wolfBoot submodule pointer (keeps the two SRCREVs
# explicit and greppable).
# explicit and greppable). Set WOLFBOOT_WOLFSSL_SRC to build against an
# existing wolfSSL source tree instead, see the block below.

HOMEPAGE = "https://github.com/wolfssl/wolfBoot"
BUGTRACKER = "https://github.com/wolfssl/wolfBoot/issues"
SECTION = "bootloaders"
LICENSE = "GPL-3.0-only"
LIC_FILES_CHKSUM = "file://LICENSE;md5=1ebbd3e34237af26da5dc08a4e440464"

# --- Optional: build against an existing wolfSSL source tree ----------------
#
# Set WOLFBOOT_WOLFSSL_SRC to the absolute path of a wolfSSL source tree to
# build wolfCrypt from it instead of the pinned copy fetched below. When it is
# set the wolfSSL entry drops out of SRC_URI entirely and nothing is downloaded
# making SRCREV_wolfssl unused.
#
# NOTE: this reuses a source *tree*, not a built library. wolfBoot compiles the
# wolfCrypt sources itself into a -nostdlib bare-metal image; it can never link
# against the target libwolfssl.so that the wolfssl recipe produces.
WOLFBOOT_WOLFSSL_SRC ?= ""

# Private copy of that tree, made by do_stage_external_wolfssl below. The copy
# is not an optimisation: wolfBoot compiles objects *alongside* the wolfCrypt
# sources ($(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/*.o, in both options.mk and
# tools/keytools/Makefile), so building in place would write into the caller's
# tree -- and race with it if it belongs to another recipe. Copying also lets
# WOLFBOOT_WOLFSSL_SRC live somewhere read-only or shared.
WOLFBOOT_WOLFSSL_STAGED_SRC = "${WORKDIR}/wolfssl-external"

# NOTE: SRCREVs below are pinned to wolfSSL/wolfBoot master and
# wolfSSL/wolfssl master tips at the time of writing. Bump these as
# upstream evolves. Downstream users can override via local.conf:
# SRCREV_wolfboot:pn-wolfboot = "<sha>"
# SRCREV_wolfboot:pn-wolfboot-keytools-native = "<sha>"
SRC_URI = " \
git://github.com/wolfssl/wolfBoot.git;protocol=https;branch=master;name=wolfboot;destsuffix=git \
git://github.com/wolfssl/wolfssl.git;protocol=https;branch=master;name=wolfssl;destsuffix=git/lib/wolfssl \
${@'' if d.getVar('WOLFBOOT_WOLFSSL_SRC') else 'git://github.com/wolfssl/wolfssl.git;protocol=https;branch=master;name=wolfssl;destsuffix=git/lib/wolfssl'} \
"
SRCREV_wolfboot ?= "9a667f2a7527da2b8e490ae7923665321af2d3ac"
SRCREV_wolfssl ?= "1d363f3adceba9d1478230ede476a37b0dcdef24"
SRCREV_FORMAT = "wolfboot_wolfssl"
SRCREV_FORMAT = "${@'wolfboot' if d.getVar('WOLFBOOT_WOLFSSL_SRC') else 'wolfboot_wolfssl'}"

def wolfboot_wolfssl_src_id(d):
"""Fingerprint the external wolfSSL tree: relative path, size and mtime of
every file. Fed into the task hashes below so that editing the tree rebuilds
wolfBoot. BitBake otherwise hashes only the WOLFBOOT_WOLFSSL_SRC *string*,
and changes to its contents would silently reuse a stale wolfboot.elf.
Returns '' (and costs nothing) when the feature is not in use."""
import os
import hashlib

src = d.getVar('WOLFBOOT_WOLFSSL_SRC')
if not src or not os.path.isdir(src):
return ''

h = hashlib.sha256()
for root, dirs, files in os.walk(src):
# Same pruning as the copy below, so the fingerprint tracks exactly
# what gets staged.
dirs[:] = sorted(x for x in dirs if x not in ('.git', '.libs'))
for name in sorted(files):
path = os.path.join(root, name)
try:
st = os.lstat(path)
except OSError:
continue
entry = '%s %d %d\n' % (os.path.relpath(path, src),
st.st_size, st.st_mtime_ns)
h.update(entry.encode())
return h.hexdigest()

WOLFBOOT_WOLFSSL_SRC_ID = "${@wolfboot_wolfssl_src_id(d)}"

python check_wolfboot_wolfssl_src() {
import os

src = d.getVar('WOLFBOOT_WOLFSSL_SRC') or ''
if not src:
return
if not os.path.isabs(src):
bb.fatal("WOLFBOOT_WOLFSSL_SRC='%s' must be an absolute path. wolfBoot's "
"Makefile only abspaths WOLFBOOT_LIB_WOLFSSL when the value "
"comes from a makefile; the recipe passes it on the make "
"command line, which overrides that assignment, so a relative "
"path reaches every sub-makefile verbatim and resolves against "
"whichever working directory each one runs in." % src)
if not os.path.isdir(os.path.join(src, 'wolfcrypt', 'src')):
bb.fatal("WOLFBOOT_WOLFSSL_SRC='%s' does not look like a wolfSSL source "
"tree (no wolfcrypt/src directory)." % src)
}

do_stage_external_wolfssl() {
if [ -z "${WOLFBOOT_WOLFSSL_SRC}" ]; then
return 0
fi

rm -rf "${WOLFBOOT_WOLFSSL_STAGED_SRC}"
mkdir -p "${WOLFBOOT_WOLFSSL_STAGED_SRC}"

# Prebuilt objects are excluded so a natively-configured wolfSSL tree
# (./configure && make) cannot leak host x86 .o/.a files into the cross
# build: wolfBoot's object paths sit inside this tree, and make would
# happily reuse an existing .o that is newer than its .c.
tar -cf - -C "${WOLFBOOT_WOLFSSL_SRC}" \
--exclude=.git --exclude=.libs \
--exclude='*.o' --exclude='*.lo' --exclude='*.a' --exclude='*.la' \
--exclude='*.so' --exclude='*.so.*' \
. | tar -xf - -C "${WOLFBOOT_WOLFSSL_STAGED_SRC}"

# Guard against a half-copied tree: the pipeline above reports only the
# extract side's exit status under a plain POSIX shell.
if [ ! -f "${WOLFBOOT_WOLFSSL_STAGED_SRC}/wolfcrypt/src/asn.c" ]; then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This catches a copy that produced nothing at all. It will not catch one that started fine and died halfway, disk filling up, or a permissions problem part way down. In that case asn.c already exists, the check passes, and the build carries on with an incomplete tree.

Not sure if this is something we want to account for.

bbfatal "Failed to stage WOLFBOOT_WOLFSSL_SRC='${WOLFBOOT_WOLFSSL_SRC}':" \
"wolfcrypt/src/asn.c is missing from the copy at" \
"${WOLFBOOT_WOLFSSL_STAGED_SRC}."
fi

chmod -R u+w "${WOLFBOOT_WOLFSSL_STAGED_SRC}"
}
do_stage_external_wolfssl[prefuncs] += "check_wolfboot_wolfssl_src"
do_stage_external_wolfssl[vardeps] += "WOLFBOOT_WOLFSSL_SRC_ID"
do_compile[vardeps] += "WOLFBOOT_WOLFSSL_SRC_ID"
addtask stage_external_wolfssl after do_unpack before do_compile

python () {
if d.getVar('UNPACKDIR', False):
Expand Down
16 changes: 16 additions & 0 deletions recipes-wolfssl/wolfboot/wolfboot_git.bb
Original file line number Diff line number Diff line change
Expand Up @@ -133,13 +133,29 @@ do_compile() {
# the resulting AArch64 binary on the x86_64 build host. Point it at
# the native keygen from wolfboot-keytools-native instead.
NATIVE_KEYGEN="$(command -v wolfboot-keygen)"

# Build wolfCrypt from a caller-supplied wolfSSL tree when asked.
# WOLFBOOT_LIB_WOLFSSL is wolfBoot's way to set an external wolfSSL source
# location. Leaving it unset keeps the in-tree lib/wolfssl fetched
# by wolfboot.inc. Always point it at the WORKDIR copy staged by
# do_stage_external_wolfssl, never at the caller's tree because the build
# needs to write object files into this directory. Unquoted below so it
# vanishes when empty.
WOLFSSL_LIB_ARG=""
if [ -n "${WOLFBOOT_WOLFSSL_SRC}" ]; then
WOLFSSL_LIB_ARG="WOLFBOOT_LIB_WOLFSSL=${WOLFBOOT_WOLFSSL_STAGED_SRC}"
bbnote "wolfBoot: building wolfCrypt from ${WOLFBOOT_WOLFSSL_SRC}" \
"(staged at ${WOLFBOOT_WOLFSSL_STAGED_SRC})"
fi

make wolfboot.elf \
CROSS_COMPILE=${TARGET_PREFIX} \
CC="${TARGET_PREFIX}gcc $SYSROOT_FLAG" \
LD="${TARGET_PREFIX}gcc $SYSROOT_FLAG" \
USER_PRIVATE_KEY="${WOLFBOOT_SIGNING_KEY}" \
USER_PUBLIC_KEY="$PUBKEY_FOR_MAKE" \
KEYGEN_TOOL="$NATIVE_KEYGEN" \
$WOLFSSL_LIB_ARG \
${WOLFBOOT_EXTRA_MAKE_FLAGS} \
V=1
}
Expand Down
Loading