mirror of https://github.com/wolfSSL/wolfBoot.git
280 lines
11 KiB
Markdown
280 lines
11 KiB
Markdown
## Encrypted external partitions
|
|
|
|
wolfBoot offers the possibility to encrypt the content of the entire UPDATE partition,
|
|
by using a pre-shared symmetric key which can be temporarily stored in a safer non-volatile memory area.
|
|
|
|
SWAP partition is also temporarily encrypted using the same key, so a dump of the external flash won't reveal
|
|
any content of the firmware update packages.
|
|
|
|
### Rationale
|
|
|
|
Encryption of external partition works at the level of the external flash interface.
|
|
|
|
All write calls to external partitions from the bootloader perform an additional encryption step
|
|
to hide the actual content of the external non-volatile memory.
|
|
|
|
Vice-versa, all read operations will decrypt the data stored when the feature is enabled.
|
|
|
|
An extra option is provided to the `sign` tool to encrypt the firmware update after signing it, so
|
|
that it can be stored as is in the external memory by the application, and will be decrypted by the bootloader
|
|
in order to verify the update and begin the installation.
|
|
|
|
|
|
### Temporary key storage
|
|
|
|
By default, wolfBoot will store the pre-shared symmetric key used for encryption in a temporary area
|
|
on the internal flash. This allows read-out protections to be used to hide the temporary key.
|
|
|
|
Alternatively, more secure mechanisms are available to store the temporary key in a different key storage
|
|
(e.g. using a hardware security module or a TPM device).
|
|
|
|
The temporary key can be set at run time by the application, and will be used exactly once by the bootloader
|
|
to verify and install the next update. The key can be for example received from a back-end during the update
|
|
process using secure communication, and set by the application, using `libwolfboot` API, to be used by
|
|
wolfBoot upon next boot.
|
|
|
|
Aside from setting the temporary key, the update mechanism remains the same for distributing, uploading and
|
|
installing firmware updates through wolfBoot.
|
|
|
|
### Custom encryption key storage
|
|
|
|
You can use the `CUSTOM_ENCRYPT_KEY` option to implement your own functions for:
|
|
`wolfBoot_get_encrypt_key`, `wolfBoot_set_encrypt_key` and
|
|
`wolfBoot_erase_encrypt_key`.
|
|
|
|
To enable:
|
|
|
|
1) Add `CUSTOM_ENCRYPT_KEY=1` to your `.config`
|
|
2) Add your own .c file using `OBJS_EXTRA`. For example, for your own
|
|
`src/custom_encrypt_key.c` add this to your `.config`:
|
|
`OBJS_EXTRA=src/custom_encrypt_key.o`
|
|
|
|
Your custom implementation must provide these functions:
|
|
|
|
```c
|
|
int wolfBoot_set_encrypt_key(const uint8_t *key, const uint8_t *nonce);
|
|
int wolfBoot_get_encrypt_key(uint8_t *key, uint8_t *nonce);
|
|
int wolfBoot_erase_encrypt_key(void);
|
|
```
|
|
|
|
Example custom function for testing:
|
|
|
|
```c
|
|
#include "wolfboot/wolfboot.h"
|
|
#include "image.h"
|
|
|
|
int RAMFUNCTION wolfBoot_get_encrypt_key(uint8_t *key, uint8_t *nonce)
|
|
{
|
|
int i;
|
|
/* Test key: "0123456789abcdef0123456789abcdef" (32 bytes for AES-256) */
|
|
const char test_key[] = "0123456789abcdef0123456789abcdef";
|
|
/* Test nonce: "0123456789abcdef" (16 bytes) */
|
|
const char test_nonce[] = "0123456789abcdef";
|
|
|
|
for (i = 0; i < ENCRYPT_KEY_SIZE && i < (int)sizeof(test_key); i++) {
|
|
key[i] = (uint8_t)test_key[i];
|
|
}
|
|
for (i = 0; i < ENCRYPT_NONCE_SIZE && i < (int)sizeof(test_nonce); i++) {
|
|
nonce[i] = (uint8_t)test_nonce[i];
|
|
}
|
|
return 0;
|
|
}
|
|
```
|
|
|
|
Note: On platforms that use the src/update_disk.c loader it only reads from a
|
|
GPT partition and with ENCRYPT=1 it only needs `wolfBoot_get_encrypt_key` implemented.
|
|
|
|
|
|
### Libwolfboot API
|
|
|
|
The API to communicate with the bootloader from the application is expanded when this feature is enabled,
|
|
to allow setting a temporary key to process the next update.
|
|
|
|
The functions
|
|
|
|
```
|
|
int wolfBoot_set_encrypt_key(const uint8_t *key, const uint8_t *nonce);
|
|
int wolfBoot_erase_encrypt_key(void);
|
|
```
|
|
|
|
can be used to set a temporary encryption key for the external partition, or erase a key previously set, respectively.
|
|
|
|
Moreover, using `libwolfboot` to access the external flash with wolfboot hal from the application will not
|
|
use encryption. This way the received update, already encrypted at origin, can be stored in the external
|
|
memory unchanged, and retrieved in its encrypted format, e.g. to verify that the transfer has been successful before
|
|
reboot.
|
|
|
|
### Symmetric encryption algorithm
|
|
|
|
The default algorithm used to encrypt and decrypt data in external partitions
|
|
is Chacha20-256.
|
|
|
|
- The `key` provided to `wolfBoot_set_encrypt_key()` must be exactly 32 Bytes long.
|
|
- The `nonce` argument must be a 96-bit (12 Bytes) randomly generated buffer, to be used as IV for encryption and decryption.
|
|
|
|
AES-128 and AES-256 are also supported. AES is used in counter mode. AES-128 and AES-256 have a key length of 16 and 32 bytes
|
|
respectively, and the IV size is 16 bytes long in both cases.
|
|
|
|
## Example usage
|
|
|
|
To compile wolfBoot with encryption support, use the option `ENCRYPT=1`.
|
|
|
|
By default, this also selects `ENCRYPT_WITH_CHACHA=1`. To use AES encryption instead,
|
|
select `ENCRYPT_WITH_AES128=1` or `ENCRYPT_WITH_AES256=1`.
|
|
|
|
### PKCS#11 backend
|
|
|
|
On ARM TrustZone configurations with `WOLFCRYPT_TZ_PKCS11` enabled, it is
|
|
possible to use the keyvault provided by wolfBoot to store the encryption key.
|
|
To enable this, use the configuration option `ENCRYPT_PKCS11=1` alongside
|
|
`WOLFCRYPT_TZ=1`, `WOLFCRYPT_TZ_PKCS11=1` and `ENCRYPT=1`, and set
|
|
`ENCRYPT_PKCS11_PIN` to the user PIN for the PKCS#11 vault. This should be set
|
|
in the form of a C string literal, for example
|
|
`ENCRYPT_PKCS11_PIN="\x01\x02\x03\x04"`, and the ending null byte will be
|
|
ignored.
|
|
|
|
When this is enabled, instead of providing the key to wolfBoot via
|
|
`wolfBoot_set_encrypt_key`, the application should use the PKCS#11 API to store
|
|
it in the keyvault with an appropriate ID (via the `CKA_ID` attribute). This ID
|
|
should then be used as the `key` parameter (in place of the key) when calling
|
|
`wolfBoot_set_encrypt_key`.
|
|
|
|
The following configuration options are also available:
|
|
|
|
- `ENCRYPT_PKCS11_KEY_ID_SIZE` (default `4`): this is the size, in bytes, of
|
|
the key ID.
|
|
- `ENCRYPT_PKCS11_MECHANISM` (default `0x00001086UL`, i.e. AES-CTR): this is
|
|
the numeric ID of the PKCS#11 mechanism to be used for encryption. Currently
|
|
only AES-CTR is supported. The list of IDs can be found in the [appendix of
|
|
the PKCS#11 mechanism specification](https://docs.oasis-open.org/pkcs11/pkcs11-curr/v2.40/cs01/pkcs11-curr-v2.40-cs01.html#_Toc228894920),
|
|
while the list of IDs supported by wolfPKCS11 is set in the [pkcs11.h](https://github.com/wolfSSL/wolfPKCS11/blob/master/wolfpkcs11/pkcs11.h)
|
|
header file.
|
|
- `ENCRYPT_PKCS11_BLOCK_SIZE` (default `16`, for AES-CTR) and
|
|
`ENCRYPT_PKCS11_NONCE_SIZE` (default `16`, for AES-CTR) should be set in case
|
|
you set `ENCRYPT_PKCS11_MECHANISM` to something other than the default.
|
|
|
|
### Signing and encrypting the update bundle with ChaCha20-256
|
|
|
|
The `sign` tool can sign and encrypt the image with a single command.
|
|
In case of chacha20, the encryption secret is provided in a binary file that should contain a concatenation of
|
|
a 32B ChaCha-256 key and a 12B nonce.
|
|
|
|
In the examples provided, the test application uses the following parameters:
|
|
|
|
```
|
|
key = "0123456789abcdef0123456789abcdef"
|
|
nonce = "0123456789ab"
|
|
```
|
|
|
|
So it is easy to prepare the encryption secret in the test scripts or from the command line using:
|
|
|
|
```
|
|
echo -n "0123456789abcdef0123456789abcdef0123456789ab" > enc_key.der
|
|
```
|
|
|
|
The `sign` tool can now be invoked to produce a signed+encrypted image, by using the extra argument `--encrypt` followed by the
|
|
secret file:
|
|
|
|
```
|
|
./tools/keytools/sign --encrypt enc_key.der test-app/image.bin wolfboot_signing_private_key.der 24
|
|
|
|
```
|
|
|
|
which will produce as output the file `test-app/image_v24_signed_and_encrypted.bin`, that can be transferred to the target's external device.
|
|
|
|
### Signing and encrypting the update bundle with AES-256
|
|
|
|
In case of AES-256, the encryption secret is provided in a binary file that should contain a concatenation of
|
|
a 32B key and a 16B IV.
|
|
|
|
In the examples provided, the test application uses the following parameters:
|
|
|
|
```
|
|
key = "0123456789abcdef0123456789abcdef"
|
|
iv = "0123456789abcdef"
|
|
```
|
|
|
|
So it is easy to prepare the encryption secret in the test scripts or from the command line using:
|
|
|
|
```
|
|
echo -n "0123456789abcdef0123456789abcdef0123456789abcdef" > enc_key.der
|
|
```
|
|
|
|
The `sign` tool can now be invoked to produce a signed+encrypted image, by using the extra argument `--encrypt` followed by the
|
|
secret file. To select AES-256, use the `--aes256` option.
|
|
|
|
```
|
|
./tools/keytools/sign --aes256 --encrypt enc_key.der test-app/image.bin wolfboot_signing_private_key.der 24
|
|
|
|
```
|
|
|
|
which will produce as output the file `test-app/image_v24_signed_and_encrypted.bin`, that can be transferred to the target's external device.
|
|
|
|
|
|
### Encryption of incremental (delta) updates
|
|
|
|
When used in combination with delta updates, encryption works the same way as in full-update mode. The final delta image is encrypted with the selected algorithm.
|
|
|
|
|
|
### Encryption of self-updates
|
|
|
|
When used in combination with bootloader 'self' updates, the encryption algorithm must be configured to run from RAM.
|
|
|
|
This is done by changing the linker script for the target. At the moment the feature has been successfully tested
|
|
with the ChaCha algorithm.
|
|
|
|
The `.text` and `.rodata` segments in FLASH must be updated to not include symbols to be loaded in memory, so the following lines in the .text section:
|
|
|
|
```
|
|
*(.text*)
|
|
*(.rodata*)
|
|
|
|
|
|
```
|
|
|
|
|
|
Must be replaced with:
|
|
|
|
```
|
|
*(EXCLUDE_FILE(*chacha.o).text*)
|
|
*(EXCLUDE_FILE(*chacha.o).rodata*)
|
|
```
|
|
|
|
|
|
Similarly, the .data section loaded in RAM should contain all the .text and .rodata also coming
|
|
from the symbols of the encryption algorithm. The .data section should have the following added,
|
|
after `KEEP(*(.ramcode))`:
|
|
|
|
```
|
|
KEEP(*(.text.wc_Chacha*))
|
|
KEEP(*(.text.rotlFixed*))
|
|
KEEP(*(.rodata.sigma))
|
|
KEEP(*(.rodata.tau))
|
|
```
|
|
|
|
The combination of encryption + self update has been successfully tested on STM32L0.
|
|
When using makefile based build, a different linker script `hal/$(TARGET)_chacha_ram.ld` is used
|
|
as template. The file `hal/stm32l0_chacha_ram.ld` contains the changes described above to place
|
|
all the needed symbols in RAM.
|
|
|
|
|
|
### Using a custom buffer as encrypt/decrypt cache
|
|
|
|
By default, encryption support requires a buffer of the same size as the external flash page size to be allocated in RAM.
|
|
You can provide a custom pre-allocated buffer by passing its address via the option `ENCRYPT_CACHE`, e.g.:
|
|
|
|
`ENCRYPT_CACHE=0x20010000`
|
|
|
|
### API usage in the application
|
|
|
|
When transferring the image, the application can still use the libwolfboot API functions to store the encrypted firmware. When called from the application,
|
|
the function `ext_flash_write` will store the payload unencrypted.
|
|
|
|
In order to trigger an update, before calling `wolfBoot_update_trigger` it is necessary to set the temporary key used by the bootloader by calling `wolfBoot_set_encrypt_key`.
|
|
|
|
An example of encrypted update trigger can be found in the [stm32wb test application source code](../test-app/app_stm32wb.c).
|
|
|
|
|
|
|
|
|