wolfBoot/docs/compile.md

552 lines
27 KiB
Markdown

# Compiling wolfBoot
WolfBoot is portable across different types of embedded systems. The platform-specific code
is contained in a single file under the `hal` directory, and implements the hardware-specific functions.
To enable specific compile options, use environment variables while calling make, e.g.
`make CORTEX_M0=1`
As an alternative, you can provide a .config file in the root directory of wolfBoot.
Command line options have priority on `.config` options, as long as .config options are
defined using the `?=` operator, e.g.:
`WOLFBOOT_PARTITION_BOOT_ADDRESS?=0x14000`
## Generate a new configuration
A new `.config` file with a set of default parameters
can be generated by running `make config`. The build script will ask to enter a default value for
each configuration parameter. Enter confirm the current value, indicated in between `[]`.
Once a .config file is in place, it will change the default compile-time options when running `make`
without parameters.
.config can be modified with a text editor to alter the default options later on.
## Platform selection
If supported natively, the target platform can be specified using the `TARGET` variable.
Make will automatically select the correct compile option, and include the corresponding HAL for
the selected target.
For a list of the platforms currently supported, see the [HAL documentation](./HAL.md).
To add a new platform, simply create the corresponding HAL driver and linker script file
in the [hal](../hal) directory.
Default option if none specified: `TARGET=stm32f4`
Some platforms will require extra options, specific for the architecture.
By default, wolfBoot is compiled for ARM Cortex-M3/4/7. To compile for Cortex-M0, use:
`CORTEX_M0=1`
### Speed vs. size
Some targets support assembly optimizations by default.
To disable assembly optimizations, use `NO_ASM=1`. This option will
produce smaller code, but will also impact on the boot time.
ARM-specific ARM optimizations affecting hash and symmetric key ciphers can be
disabled with the option `NO_ARM_ASM=1`. This is useful for example when you want
to use SP math optimizations for key verification, but exclude SHA2/AES optimizations
to save some space.
#### Example: ECC256 + SHA256 on STM32H7
Benchmark footprint vs. boot time SHA of 100KB image + signature verification
| Description | Selected options | wolfBoot size (B) | Boot time (s) |
|-------------|------------------|-------------------|---------------|
| Full ECC256 assembly optimizations. Fastest. | `SIGN=ECC256` | 21836 | .583 |
| Optimize ECC only (SP math assembly only) | `SIGN=ECC256 NO_ARM_ASM=1` | 18624 | .760 |
| No assembly optimizations (smallest) | `SIGN=ECC256 NO_ASM=1` | 14416 | 3.356 |
### Flash partitions
The file [include/target.h](../include/target.h) is generated according to the configured flash geometry,
partitions size and offset of the target system. The following values must be set to provide the
desired flash configuration, either via the command line, or using the .config file:
- `WOLFBOOT_SECTOR_SIZE`
This variable determines the size of the physical sector on the flash memory. If areas with different
block sizes are used for the two partitions (e.g. update partition on an external flash), this variable
should indicate the size of the biggest sector shared between the two partitions.
WolfBoot uses this value as minimum unit when swapping the firmware images in place. For this reason,
this value is also used to set the size of the SWAP partition.
- `WOLFBOOT_PARTITION_BOOT_ADDRESS`
This is the start address of the boot partition, aligned to the beginning of a new flash sector.
The application code starts after a further offset, equal to the partition header size (256B
for Ed25519 and ECC signature headers).
- `WOLFBOOT_PARTITION_UPDATE_ADDRESS`
This is the start address of the update partition. If an external memory is used via the
`EXT_FLASH` option, this variable contains the offset of the update partition from the
beginning of the external memory addressable space.
- `WOLFBOOT_PARTITION_SWAP_ADDRESS`
The address for the swap spaced used by wolfBoot to swap the two firmware images in place,
in order to perform a reversible update. The size of the SWAP partition is exactly one sector on the flash.
If an external memory is used, the variable contains the offset of the SWAP area from the beginning
of its addressable space.
- `WOLFBOOT_PARTITION_SIZE`
The size of the BOOT and UPDATE partitions. The size is the same for both partitions unless using the monolithic
self-update mode, in which case the UPDATE partition can optionally be sized independently.
- `WOLFBOOT_PARTITION_UPDATE_SIZE`
Optional. Sizes the UPDATE partition independently of BOOT; defaults to
`WOLFBOOT_PARTITION_SIZE`. A different value is only allowed together with
`SELF_UPDATE_MONOLITHIC=1`, so the UPDATE partition can stage a monolithic
payload (bootloader region + application) without inflating the BOOT
partition. The staged payload also needs `IMAGE_HEADER_SIZE` plus one
trailer sector of room on top — see the sizing guide in
[firmware_update.md](firmware_update.md#independent-partition-sizing).
Not supported with `ENCRYPT` or `PULL_LINKER_DEFINES`.
## Bootloader features
A number of characteristics can be turned on/off during wolfBoot compilation. Bootloader size,
performance and activated features are affected by compile-time flags.
### Change DSA algorithm
By default, wolfBoot is compiled to use Ed25519 DSA. The implementation of ed25519 is smaller,
while giving a good compromise in terms of boot-up time.
Better performance can be achieved using ECDSA with curve p-256.
To activate ECC256, ECC384 or ECC521 support, use:
`SIGN=ECC256` or `SIGN=ECC384` or `SIGN=ECC521` respectively.
when invoking `make`.
RSA is also supported, with different key length.
To activate RSA2048, RSA3072 or RSA4096, use:
`SIGN=RSA2048` or `SIGN=RSA3072` or `SIGN=RSA4096` respectively.
Ed448 is also supported via `SIGN=ED448`.
The default option, if no value is provided for the `SIGN` variable, is
`SIGN=ED25519`
Changing the DSA algorithm will also result in compiling a different set of tools for key generation
and firmware signature.
Find the corresponding key generation and firmware signing tools in the [tools](../tools) directory.
It's possible to disable authentication of the firmware image by explicitly using:
`SIGN=NONE`
in the Makefile commandline. This will compile a minimal bootloader with no support for public-key authenticated
secure boot.
### Auxiliary crypto algorithms
Some features need crypto algorithms beyond the ones selected with `SIGN` and
`HASH`: certificate-chain verification may meet CA certificates signed with a
different algorithm than the leaf, and TPM support needs ECC or RSA for its
primary key and parameter encryption even when `SIGN` is a post-quantum or
Ed25519/Ed448 algorithm.
`AUX_PK_ALGOS` and `AUX_HASH_ALGOS` compile extra wolfCrypt algorithms into
wolfBoot for those uses. Both take a comma-separated, case-insensitive list
(no spaces):
```sh
make SIGN=ECC256 AUX_PK_ALGOS=rsa2048,ecc384 AUX_HASH_ALGOS=sha384
```
Valid `AUX_PK_ALGOS` entries: `ecc256`, `ecc384`, `ecc521`, `rsa2048`,
`rsa3072`, `rsa4096`, `rsapss2048`, `rsapss3072`, `rsapss4096`, `ed25519`,
`ed448`. Valid `AUX_HASH_ALGOS` entries: `sha256`, `sha384`, `sha512`,
`sha3`. Post-quantum algorithms (ML-DSA, LMS, XMSS) are not yet supported as
auxiliary algorithms.
Auxiliary algorithms are never used to verify the firmware image signature;
that remains bound to `SIGN` (and `SIGN_SECONDARY` for hybrid mode). Each
entry defines a `WOLFBOOT_AUX_PK_<ALGO>` or `WOLFBOOT_AUX_HASH_<ALGO>` macro
and links the matching wolfCrypt objects; entries already covered by
`SIGN`/`SIGN_SECONDARY` are no-ops.
Notes:
- Not compatible with `WOLFBOOT_SMALL_STACK=1`: the static memory pools are
sized for the primary `SIGN` algorithm only.
- `STACK_USAGE` is not adjusted automatically. If an auxiliary algorithm with
larger stack needs (e.g. `rsa4096`) is actually exercised, consider
`WOLFBOOT_HUGE_STACK=1` (the RSA4096 cert-chain configurations already do).
- When `WOLFTPM=1` and neither `SIGN`, `SIGN_SECONDARY`, nor `AUX_PK_ALGOS`
provides an ECC or RSA algorithm, `ecc256` is added automatically so the
TPM SRK and parameter encryption work.
- With `CERT_CHAIN_VERIFY=1`, the generated dummy chain's CA algorithms
(`CERT_CHAIN_GEN_CA_ALGO`/`CERT_CHAIN_GEN_CA_HASH`) are bridged into these
lists automatically. Users providing `USER_CERT_CHAIN` must list their
chain's algorithms here themselves.
### Incremental updates
wolfBoot support incremental updates. To enable this feature, compile with `DELTA_UPDATES=1`.
An additional file is generated when the sign tool is invoked with the `--delta` option, containing only the
differences between the old firmware to replace, currently running on the target, and the new version.
For more information and examples, see the [firmware update](firmware_update.md) section.
### Enable debug symbols
To debug the bootloader, simply compile with `DEBUG=1`. The size of the bootloader will increase
consistently, so ensure that you have enough space at the beginning of the flash before
`WOLFBOOT_PARTITION_BOOT_ADDRESS`.
### Disable interrupt vector relocation
On some platforms, it might be convenient to avoid the interrupt vector relocation before boot-up.
This is required when a component on the system already manages the interrupt relocation at a different
stage, or on these platform that do not support interrupt vector relocation.
To disable interrupt vector table relocation, compile with `VTOR=0`. By default, wolfBoot will relocate the
interrupt vector by setting the offset in the vector relocation offset register (VTOR).
### Limit stack usage
By default, wolfBoot does not require any memory allocation. It does this by performing all the operations
using the stack. Although the stack space used by the algorithms can be predicted at compile time, the amount
of stack space be relatively big, depending on the algorithm selected.
Some targets offer limited amount of RAM to use as stack space, either in general, or in a configuration
dedicated for the bootloader stage.
In these cases, it might be useful to activate `WOLFBOOT_SMALL_STACK=1`. With this option, a fixed-size pool
is created at compile time to assist the allocation of the object needed by the cryptography implementation.
When compiled with `WOLFBOOT_SMALL_STACK=1`, wolfBoot reduces the stack usage considerably, and simulates dynamic
memory allocations by assigning dedicated, statically allocated, pre-sized memory areas.
### Allow bigger stack size allocation
Some combinations of authentication algorithms, key sizes and math configuration in wolfCrypt require
a large amount of memory to be allocated in the stack at runtime. By default, if your configuration
falls in one of these cases, wolfBoot compilation will terminate with an explicit error.
In some cases you might have enough memory available to allow large stack allocations.
To circumvent the compile-time checks on the maximum allowed stack size, use `WOLFBOOT_HUGE_STACK=1`.
### One-shot hash verification
By default, wolfBoot hashes firmware images in blocks of `WOLFBOOT_SHA_BLOCK_SIZE` bytes during
verification. This block-by-block approach is required when firmware resides in external flash or
other non-memory-mapped storage, where data must be read through intermediate buffers.
When firmware images are stored in directly memory-mapped flash (e.g. internal flash with
execute-in-place support), the block-by-block overhead can be eliminated by enabling
`WOLFBOOT_IMG_HASH_ONESHOT=1`. With this option, the entire image buffer is passed to the wolfCrypt
hash function in a single call, which can improve verification performance.
**warning** This option assumes that `fw_base` pointers are directly dereferenceable for the full
firmware size. It is incompatible with `EXT_FLASH=1` configurations where partitions reside on
external SPI or UART flash. Only use `WOLFBOOT_IMG_HASH_ONESHOT=1` when all firmware partitions are
in directly addressable, memory-mapped flash.
### Disable Backup of current running firmware
Optionally, it is possible to disable the backup copy of the current running firmware upon the installation of the
update. This implies that no fall-back mechanism is protecting the target from a faulty firmware installation, but may be useful
in some cases where it is not possible to write on the update partition from the bootloader. This also removes the
power-fail-safe swap behavior: if power is lost while the update is being copied into the BOOT partition, the original
firmware may already be partially overwritten and the device can be left unrecoverable.
The associated compile-time option is
`DISABLE_BACKUP=1`
### Enable workaround for 'write once' flash memories
On some microcontrollers, the internal flash memory does not allow subsequent writes (adding zeroes) to a
sector, after the entire sector has been erased. WolfBoot relies on the mechanism of adding zeroes to the
'flags' fields at the end of both partitions to provide a fail-safe swap mechanism.
To enable the workaround for 'write once' internal flash, compile with
`NVM_FLASH_WRITEONCE=1`
**warning** When this option is enabled, the fail-safe swap is not guaranteed, i.e. the microcontroller
cannot be safely powered down or restarted during a swap operation.
### Allow version roll-back
WolfBoot will not allow updates to a firmware with a version number smaller than the current one. To allow
downgrades, compile with `ALLOW_DOWNGRADE=1`.
Warning: this option will disable version checking before the updates, thus exposing the system to potential
forced downgrade attacks.
### Require an authenticated device tree (raw-DTB targets)
On non-FIT MMU targets that load a raw device tree from flash, wolfBoot authenticates the DTB against the `HDR_DEVICE_TREE_DIGEST` TLV bound to the signed kernel (`sign --dts <board.dtb>`, see `docs/Signing.md`). A DTB carrying the digest is always verified; a raw DTB with no digest only warns and boots by default. Compile with `WOLFBOOT_REQUIRE_SIGNED_DTB=1` to make a missing digest a hard failure (fail-closed) once every raw-DTB payload is signed with `--dts`.
### Enable optional support for external flash memory
WolfBoot can be compiled with the makefile option `EXT_FLASH=1`. When the external flash support is
enabled, update and swap partitions can be associated to an external memory, and will use alternative
HAL function for read/write/erase access.
To associate the update or the swap partition to an external memory, define `PART_UPDATE_EXT` and/or
`PART_SWAP_EXT`, respectively. By default, the makefile assumes that if an external memory is present,
both `PART_UPDATE_EXT` and `PART_SWAP_EXT` are defined.
If the `NO_XIP=1` makefile option is present, `PART_BOOT_EXT` is assumed too, as no execute-in-place is
available on the system. This is typically the case of MMU system (e.g. Cortex-A) where the operating system
image(s) are position-independent ELF images stored in a non-executable non-volatile memory, and must be
copied in RAM to boot after verification.
When external memory is used, the HAL API must be extended to define methods to access the custom memory.
Refer to the [HAL](HAL.md) page for the description of the `ext_flash_*` API.
The `EXT_FLASH` option can also be used if the target device requires special handling for flash reads
(e.g. word size requirements or other restrictions), regardless of whether the flash is internal or external.
Note that the `EXT_FLASH` option is incompatible with the `NVM_FLASH_WRITEONCE` option. Targets that need
both these options must implement the sector-based read-modify-erase-write sequence at the HAL layer.
For an example of using `EXT_FLASH` to bypass read restrictions, (in this case, the inability to read from
erased flash due to ECC errors) on a platform with write-once flash, see the [infineon tricore port](../hal/aurix_tc3xx.c).
#### SPI devices
In combination with the `EXT_FLASH=1` configuration parameter, it is possible to use a platform-specific SPI drivers,
e.g. to access an external SPI flash memory. By compiling wolfBoot with the makefile option `SPI_FLASH=1`, the external
memory is directly mapped to the additional SPI layer, so the user does not have to define the `ext_flash_*` functions.
SPI functions, instead, must be defined. Example SPI drivers are available for multiple platforms in the [hal/spi](../hal/spi) directory.
#### UART bridge towards neighbor systems
Another alternative available to map external devices consists in enabling a UART bridge towards a neighbor system.
The neighbor system must expose a service through the UART interface that is compatible with the wolfBoot protocol.
In the same way as for SPI devices, the `ext_flash_*` API is automatically defined by wolfBoot when the option `UART_FLASH=1` is used.
For more details, see the manual page [Remote External flash memory support via UART](remote_flash.md)
#### Encryption support for external partitions
When update and swap partitions are mapped to an external device using `EXT_FLASH=1`, either in combination with `SPI_FLASH`,
`UART_FLASH`, or any custom external mapping, it is possible to enable ChaCha20 encryption when accessing those partition from the
bootloader. The update images must be pre-encrypted at the source using the key tools, and wolfBoot should be instructed to use a temporary
ChaCha20 symmetric key to access the content of the updates.
For more details about this optional feature, please refer to the [Encrypted external partitions](encrypted_partitions.md) manual page.
### Executing flash access code from RAM
On some platform, flash access code requires to be executed from RAM, to avoid conflict e.g. when writing
to the same device where wolfBoot is executing, or when changing the configuration of the flash itself.
To move all the code accessing the internal flash for writing, into a section in RAM, use the compile time option
`RAM_CODE=1` (on some hardware configurations this is required for the bootloader to access the flash for writing).
### Enable Dual-bank hardware-assisted swapping
When supported by the target platform, hardware-assisted dual-bank swapping can be used to perform updates.
To enable this functionality, use `DUALBANK_SWAP=1`. Currently, only STM32F76x and F77x support this feature.
### Store UPDATE partition flags in a sector in the BOOT partition
By default, wolfBoot keeps track of the status of the update procedure to the single sectors in a specific area at the end of each partition, dedicated
to store and retrieve a set of flags associated to the partition itself.
In some cases it might be helpful to store the status flags related to the UPDATE partition and its sectors in the internal flash, alongside with
the same set of flags used for the BOOT partition. By compiling wolfBoot with the `FLAGS_HOME=1` makefile option, the flags
associated to the UPDATE partition are stored in the BOOT partition itself.
While on one hand this option slightly reduces the space available in the BOOT partition to store the firmware image, it keeps all the flags in
the BOOT partition.
### Flash Erase value / Flag logic inversion
By default, most NVMs set the content of erased pages to `0xFF` (all ones).
Some FLASH memory models use inverted logic for erased page, setting the content to `0x00` (all zeroes) after erase.
For these special cases, the option `FLAGS_INVERT = 1` can be used to modify the logic of the partition/sector flags used in wolfBoot.
You can also manually override the fill bytes using `FILL_BYTE=` at build-time. It default to `0xFF`, but will use `0x00` if `FLAGS_INVERT` is set.
Note: if you are using an external FLASH (e.g. SPI) in combination with a flash with inverted logic, ensure that you store all the flags in one partition, by using the `FLAGS_HOME=1` option described above.
### Using One-time programmable (OTP) flash as keystore
By default, keys are directly incorporated in the firmware image. To store the keys in a separate, one-time programmable (OTP) flash memory, use the `FLASH_OTP_KEYSTORE=1` option.
For more information, see [/docs/OTP-keystore.md](/docs/OTP-keystore.md).
### Prefer multi-sector flash erase operations
wolfBoot HAL flash erase function must be able to handle erase lengths larger than `WOLFBOOT_SECTOR_SIZE`, even if the underlying flash controller does not. However, in some cases, wolfBoot defaults to
iterating over a range of flash sectors and erasing them one at a time. Setting the `FLASH_MULTI_SECTOR_ERASE=1` config option prevents this behavior when possible, configuring wolfBoot to instead prefer a
single HAL flash erase invocation with a larger erase length versus the iterative approach. On targets where multi-sector erases are more performant, this option can be used to dramatically speed up the
image swap procedure.
### Building with the ARM Compiler for Embedded (armclang)
wolfBoot can be built with the [ARM Compiler for Embedded](https://developer.arm.com/Tools%20and%20Software/Arm%20Compiler%20for%20Embedded)
(AC6: `armclang`, `armlink`, `armar`, `fromelf`), the toolchain used by Keil
MDK. No tool from the GNU or LLVM toolchains is required.
At the moment, the only supported and tested target is the STM32U5
(`config/examples/{stm32u5.config,stm32u5-wolfcrypt-tz.config}`).
To use this, either have the AC6 tools in your `PATH` or set `ARMCLANG_PATH` to
their location (including a trailing slash). Then build with USE_ARMCLANG=1:
```
cp config/examples/stm32u5.config .config
make USE_ARMCLANG=1
```
Alternatively, append `USE_ARMCLANG=1` and/or `ARMCLANG_PATH` to your `.config`.
### Using Mac OS/X
If you see 0xC3 0xBF (C3BF) repeated in your factory.bin then your OS is using Unicode characters.
The "tr" command for assembling the 0xFF padding between `"bootloader" ... 0xFF ... "application" = factory.bin`, which requires the "C" locale.
Set this in your terminal
```
LANG=
LC_COLLATE="C"
LC_CTYPE="C"
LC_MESSAGES="C"
LC_MONETARY="C"
LC_NUMERIC="C"
LC_TIME="C"
LC_ALL=
```
Then run the normal `make` steps.
### Building Against Alternate Library Dependency Versions
wolfBoot includes its internal dependencies (all official wolfSSL projects) as git submodules under `lib/`, ensuring known-compatible versions. You may override these paths with `WOLFBOOT_LIB_XXX` environment variables to point to local copies of the libraries.
Note that all paths MUST be supplied to the Makefiles as absolute paths.
Available overrides:
- `WOLFBOOT_LIB_WOLFSSL`: Path to the [wolfSSL](https://github.com/wolfSSL/wolfssl) library source code
- `WOLFBOOT_LIB_WOLFTPM`: Path to the [wolfTPM](https://github.com/wolfSSL/wolfTPM) library source code
- `WOLFBOOT_LIB_WOLFPKCS11`: Path to the [wolfPKCS11](https://github.com/wolfssl/wolfpkcs11) library source code
- `WOLFBOOT_LIB_WOLFPSA`: Path to the wolfPSA library source code
- `WOLFBOOT_LIB_WOLFHSM`: Path to the [wolfHSM](https://github.com/wolfSSL/wolfHSM) library source code
## Key Generation and Signing
### Default Key Behavior
When building wolfBoot for the first time, the build system automatically generates the cryptographic keys needed for firmware signing and verification.
- **Private Key**: `wolfboot_signing_private_key.der` - Used to sign firmware images
- **Keystore**: `src/keystore.c` - Contains the public key embedded in the bootloader
The key algorithm is determined by the `SIGN` variable (e.g., `SIGN=ECC256`, `SIGN=RSA2048`).
For most targets, the makefile also builds the wolfBoot test app and signs it with the aforementioned key.
### Pre-existing Local Keys for Test App Builds
The `USER_*` Makefile variables provide a convenience for building the test app with your own locally-managed keys, avoiding the need to manually run `keygen -i` and place key files before building.
**Note:** If your private key is managed by a third party (e.g., HSM-as-a-service, Azure KeyVault) and you only have access to the public key, use the `keygen -i` option instead. See [Signing.md](Signing.md#keygen-tool) and [Manual Key Management](#manual-key-management) below.
The following variables are available:
- `USER_PRIVATE_KEY`: Path to your private signing key (DER format)
- `USER_PUBLIC_KEY`: Path to your public key (DER format)
**Usage:**
```sh
make USER_PRIVATE_KEY=/path/to/my-signing-key.der \
USER_PUBLIC_KEY=/path/to/my-public-key.der
```
#### Requirements
- Both `USER_PRIVATE_KEY` and `USER_PUBLIC_KEY` must be provided together
- Keys must be in DER format appropriate for the selected `SIGN` algorithm
When these variables are specified, the build:
1. Skips auto-generation of `wolfboot_signing_private_key.der`
2. Generates the keystore (`src/keystore.c`) from your public key via `keygen -i`
3. Uses your private key to sign the test app
This is primarily useful when you want a single `make` invocation to build wolfBoot and a signed test app using keys you've generated externally. For wolfBoot-only builds (without the test app), the main benefit is automating the `keygen -i` step for simple single-key keystores. If you need multiple keys in the keystore then you must invoke `keygen -i` manually before building wolfBoot.
### Pre-existing Certificate Chain for Test App Builds
When building the test app using certificate chain verification (`CERT_CHAIN_VERIFY=1`), you can provide your own certificate chain:
- `USER_CERT_CHAIN`: Path to your certificate chain (DER format, leaf cert last)
**Usage:**
```sh
make CERT_CHAIN_VERIFY=1 \
USER_PRIVATE_KEY=/path/to/leaf-signing-key.der \
USER_PUBLIC_KEY=/path/to/leaf-public-key.der \
USER_CERT_CHAIN=/path/to/my-cert-chain.der
```
**Requirements:**
- `USER_CERT_CHAIN` requires both `USER_PRIVATE_KEY` and `USER_PUBLIC_KEY`
- The private and public keys must correspond to the leaf certificate identity in the chain
When `CERT_CHAIN_VERIFY=1` is set without `USER_CERT_CHAIN`, the build auto-generates a dummy 3-tier certificate hierarchy in `test-dummy-ca/` for testing. This also applies to wolfHSM NVM image generation when applicable.
### Manual Key Management
For advanced scenarios (multiple keys, mixed algorithms, partition-restricted keys, or third-party managed private keys), use the `keygen` tool directly instead of the `USER_*` variables.
**Importing a public key (when private key is externally managed):**
```sh
./tools/keytools/keygen --ecc256 -i my-public-key.der
```
This creates `src/keystore.c` from your public key. Signing must then be performed in two steps following the steps outlined in [Signing.md](./Signing.md#signing-firmware-with-external-private-key-hsm)
**Using locally-managed keys without `USER_XXX` variables:**
1. Import your public key to generate the keystore:
```sh
./tools/keytools/keygen --ecc256 -i my-public-key.der
```
2. Place your signing key at the expected location:
```sh
cp my-private-key.der wolfboot_signing_private_key.der
```
Now the build system detects existing files and skips auto-generation when building wolfBoot and the test app.
**Multiple keys and advanced keystores:**
The `keygen` tool supports multiple `-g` (generate) and `-i` (import) arguments, mixed key algorithms, and partition ID restrictions. See [keystore.md](keystore.md) for full details on keystore capabilities. When using these advanced features, image signing via the `sign` tool must also be performed manually.