KW45xx and K32W1xx Load NBU image#

This notebook describes how to load NBU image to KW45xx/K32W1xx device.

Keys preparation#

First we need to generate RoTKs (Root of Trust Keys) and optionally ISK (Image Signing Certificate). We will use nxpcrypto app for this purpose. Script by default generates 4 RoTKs and 1 ISK key (full set of possible keys). Feel free to modify it according your needs. RoTK 0 generation is mandatory.

See the script’s comments and modify the script according to the application security requirements. Key generation is done only once on the beginning. Based on generated keys, RoTKTH value is calculated and loaded in the device fuses so that’s why keys cannot be changed anymore for the device.

%run ../init_notebook.ipynb

import os
import pprint

pp = pprint.PrettyPrinter(indent=4)

WORKSPACE = "workspace/" # change this to path to your workspace
VERBOSITY = "-v" # verbosity of commands, might be -v or -vv for debug or blank for no additional info
# choose family (kw45xx or k32w1xx)
FAMILY = "kw45xx"
env: JUPYTER_SPSDK=1
Created `%!` as an alias for `%execute`.
# generate private key based on secp384r1 curve - ROTK0
ROTK0_PRIVATE_KEY_PATH = WORKSPACE + "ec_pk_secp384r1_cert0.pem"
ROTK0_PUBLIC_KEY_PATH = WORKSPACE + "ec_pk_secp384r1_cert0.pub"

%! nxpcrypto $VERBOSITY key generate -k secp384r1 -o $ROTK0_PRIVATE_KEY_PATH --force

# verify that keys were generated
assert os.path.exists(ROTK0_PRIVATE_KEY_PATH)
assert os.path.exists(ROTK0_PUBLIC_KEY_PATH)


# generate private key based on secp384r1 curve - ROTK1
ROTK1_PRIVATE_KEY_PATH = WORKSPACE + "ec_pk_secp384r1_cert1.pem"
ROTK1_PUBLIC_KEY_PATH = WORKSPACE + "ec_pk_secp384r1_cert1.pub"

%! nxpcrypto $VERBOSITY key generate -k secp384r1 -o $ROTK1_PRIVATE_KEY_PATH --force

# verify that keys were generated
assert os.path.exists(ROTK1_PRIVATE_KEY_PATH)
assert os.path.exists(ROTK1_PUBLIC_KEY_PATH)


# generate private key based on secp384r1 curve - ROTK2
ROTK2_PRIVATE_KEY_PATH = WORKSPACE + "ec_pk_secp384r1_cert2.pem"
ROTK2_PUBLIC_KEY_PATH = WORKSPACE + "ec_pk_secp384r1_cert2.pub"

%! nxpcrypto $VERBOSITY key generate -k secp384r1 -o $ROTK2_PRIVATE_KEY_PATH --force

# verify that keys were generated
assert os.path.exists(ROTK2_PRIVATE_KEY_PATH)
assert os.path.exists(ROTK2_PUBLIC_KEY_PATH)


# generate private key based on secp384r1 curve - ROTK3
ROTK3_PRIVATE_KEY_PATH = WORKSPACE + "ec_pk_secp384r1_cert3.pem"
ROTK3_PUBLIC_KEY_PATH = WORKSPACE + "ec_pk_secp384r1_cert3.pub"

%! nxpcrypto $VERBOSITY key generate -k secp384r1 -o $ROTK3_PRIVATE_KEY_PATH --force

# verify that keys were generated
assert os.path.exists(ROTK3_PRIVATE_KEY_PATH)
assert os.path.exists(ROTK3_PUBLIC_KEY_PATH)


# generate private key based on secp384r1 curve - ISK
ISK_PRIVATE_KEY_PATH = WORKSPACE + "ec_pk_secp384r1_sign_cert.pem"
ISK_PUBLIC_KEY_PATH = WORKSPACE + "ec_pk_secp384r1_sign_cert.pub"

%! nxpcrypto $VERBOSITY key generate -k secp384r1 -o $ISK_PRIVATE_KEY_PATH --force

# verify that keys were generated
assert os.path.exists(ISK_PRIVATE_KEY_PATH)
assert os.path.exists(ISK_PUBLIC_KEY_PATH)
nxpcrypto -v key generate -k secp384r1 -o workspace/ec_pk_secp384r1_cert0.pem --force 
The key pair has been created: C:\spsdk\examples\jupyter_examples\kw45xx_k32w1xx\workspace\ec_pk_secp384r1_cert0.pub, C:\spsdk\examples\jupyter_examples\kw45xx_k32w1xx\workspace\ec_pk_secp384r1_cert0.pem
nxpcrypto -v key generate -k secp384r1 -o workspace/ec_pk_secp384r1_cert1.pem --force 
The key pair has been created: C:\spsdk\examples\jupyter_examples\kw45xx_k32w1xx\workspace\ec_pk_secp384r1_cert1.pub, C:\spsdk\examples\jupyter_examples\kw45xx_k32w1xx\workspace\ec_pk_secp384r1_cert1.pem
nxpcrypto -v key generate -k secp384r1 -o workspace/ec_pk_secp384r1_cert2.pem --force 
The key pair has been created: C:\spsdk\examples\jupyter_examples\kw45xx_k32w1xx\workspace\ec_pk_secp384r1_cert2.pub, C:\spsdk\examples\jupyter_examples\kw45xx_k32w1xx\workspace\ec_pk_secp384r1_cert2.pem
nxpcrypto -v key generate -k secp384r1 -o workspace/ec_pk_secp384r1_cert3.pem --force 
The key pair has been created: C:\spsdk\examples\jupyter_examples\kw45xx_k32w1xx\workspace\ec_pk_secp384r1_cert3.pub, C:\spsdk\examples\jupyter_examples\kw45xx_k32w1xx\workspace\ec_pk_secp384r1_cert3.pem
nxpcrypto -v key generate -k secp384r1 -o workspace/ec_pk_secp384r1_sign_cert.pem --force 
The key pair has been created: C:\spsdk\examples\jupyter_examples\kw45xx_k32w1xx\workspace\ec_pk_secp384r1_sign_cert.pub, C:\spsdk\examples\jupyter_examples\kw45xx_k32w1xx\workspace\ec_pk_secp384r1_sign_cert.pem

Certificates preparation#

Generate self-signed x509 certificate(s) containing public key for private key generated in previous step (ROTKX). Application nxpcrypto will be used. First step is to get a template.

# obtain a template for root cert ROTK0
ROOT0_CERT_CONFIG_PATH = WORKSPACE + "cert0_template.yml"
%! nxpcrypto $VERBOSITY cert get-template -o $ROOT0_CERT_CONFIG_PATH --force

# obtain a template for root cert ROTK1
ROOT1_CERT_CONFIG_PATH = WORKSPACE + "cert1_template.yml"
%! nxpcrypto $VERBOSITY cert get-template -o $ROOT1_CERT_CONFIG_PATH --force

# obtain a template for root cert ROTK2
ROOT2_CERT_CONFIG_PATH = WORKSPACE + "cert2_template.yml"
%! nxpcrypto $VERBOSITY cert get-template -o $ROOT2_CERT_CONFIG_PATH --force

# obtain a template for root cert ROTK3
ROOT3_CERT_CONFIG_PATH = WORKSPACE + "cert3_template.yml"
%! nxpcrypto $VERBOSITY cert get-template -o $ROOT3_CERT_CONFIG_PATH --force

# obtain a template for root cert ISK
ISK_CERT_CONFIG_PATH = WORKSPACE + "sign_cert_template.yml"
%! nxpcrypto $VERBOSITY cert get-template -o $ISK_CERT_CONFIG_PATH --force
nxpcrypto -v cert get-template -o workspace/cert0_template.yml --force 
INFO:spsdk.apps.nxpcertgen:Creating Certificate template...
The configuration template file has been created: C:\spsdk\examples\jupyter_examples\kw45xx_k32w1xx\workspace\cert0_template.yml
nxpcrypto -v cert get-template -o workspace/cert1_template.yml --force 
INFO:spsdk.apps.nxpcertgen:Creating Certificate template...
The configuration template file has been created: C:\spsdk\examples\jupyter_examples\kw45xx_k32w1xx\workspace\cert1_template.yml
nxpcrypto -v cert get-template -o workspace/cert2_template.yml --force 
INFO:spsdk.apps.nxpcertgen:Creating Certificate template...
The configuration template file has been created: C:\spsdk\examples\jupyter_examples\kw45xx_k32w1xx\workspace\cert2_template.yml
nxpcrypto -v cert get-template -o workspace/cert3_template.yml --force 
INFO:spsdk.apps.nxpcertgen:Creating Certificate template...
The configuration template file has been created: C:\spsdk\examples\jupyter_examples\kw45xx_k32w1xx\workspace\cert3_template.yml
nxpcrypto -v cert get-template -o workspace/sign_cert_template.yml --force 
INFO:spsdk.apps.nxpcertgen:Creating Certificate template...
The configuration template file has been created: C:\spsdk\examples\jupyter_examples\kw45xx_k32w1xx\workspace\sign_cert_template.yml

Configuration template for certificates should look like this:

# This is template for configuration file used for generating certificates

# ==============================================
# Issuer identification fields
# ==============================================
# All available option can be found within class NameOID in
# cryptography/src/cryptography/x509/oid.py at https://github.com/pyca/cryptography

issuer:
  COMMON_NAME: NXP
  COUNTRY_NAME: CZ
  LOCALITY_NAME: Roznov pod Radhostem
  STATE_OR_PROVINCE_NAME: Morava
  STREET_ADDRESS: 1.maje 1009
  ORGANIZATION_NAME: SPSDK Team

# ==============================================
# Subject identification fields
# ==============================================
# All available option can be found within class NameOID in
# cryptography/src/cryptography/x509/oid.py at https://github.com/pyca/cryptography
subject:
  COMMON_NAME: NXP - SPSDK
  COUNTRY_NAME: CZ
  LOCALITY_NAME: Roznov pod Radhostem
  STATE_OR_PROVINCE_NAME: Morava
  STREET_ADDRESS: 1.maje 1009
  ORGANIZATION_NAME: SPSDK Team
  POSTAL_CODE: 756 61

# ==============================================
# The certificate settings
# ==============================================

# Path, where issuer private key is stored
issuer_private_key: issuer_key.pem
# Path, where subject public key is stored
subject_public_key: subject_key.pub
# Serial number of certificate
serial_number: 12346578
# Validity duration in days
duration: 3650

# ==============================================
# Certificate basic extensions
# ==============================================
extensions:
  BASIC_CONSTRAINTS:
    # Delegate certificate as a signing authority to create an intermediate certificates.
    ca: false  # Valid values true|false
    # Integer length of the path of certificate signature from a given certificate, back to the root certificate
    path_length: 0

Certificates are in x.509 format and should be DER encoded.

ROOT_0_CERT_PATH = WORKSPACE + "ec_secp384r1_cert0.pem"
ROOT_1_CERT_PATH = WORKSPACE + "ec_secp384r1_cert1.pem"
ROOT_2_CERT_PATH = WORKSPACE + "ec_secp384r1_cert2.pem"
ROOT_3_CERT_PATH = WORKSPACE + "ec_secp384r1_cert3.pem"
ISK_CERT_PATH = WORKSPACE + "ec_secp384r1_sign_cert.pem"

# Fill the configuration file accordingly
import yaml

assert os.path.exists(ROOT0_CERT_CONFIG_PATH)
assert os.path.exists(ROOT1_CERT_CONFIG_PATH)
assert os.path.exists(ROOT2_CERT_CONFIG_PATH)
assert os.path.exists(ROOT3_CERT_CONFIG_PATH)

# Create configuration for root certificate 0 
with open(ROOT0_CERT_CONFIG_PATH) as cert_config:
    # load yaml configuration to dictionary
    cert = yaml.safe_load(cert_config)
    # change path to private and public keys
    cert['issuer_private_key'] = ROTK0_PRIVATE_KEY_PATH
    cert['subject_public_key'] = ROTK0_PUBLIC_KEY_PATH

with open(ROOT0_CERT_CONFIG_PATH, "w+") as cert_config:
    print("Root Certificate config:")
    pp.pprint(cert)
    # dump the dictionary back to YAML
    yaml.dump(cert, cert_config)

# Create configuration for root certificate 1
with open(ROOT1_CERT_CONFIG_PATH) as cert_config:
    # load yaml configuration to dictionary
    cert = yaml.safe_load(cert_config)
    # change path to private and public keys
    cert['issuer_private_key'] = ROTK1_PRIVATE_KEY_PATH
    cert['subject_public_key'] = ROTK1_PUBLIC_KEY_PATH

with open(ROOT1_CERT_CONFIG_PATH, "w+") as cert_config:
    print("Root Certificate config:")
    pp.pprint(cert)
    # dump the dictionary back to YAML
    yaml.dump(cert, cert_config)

# Create configuration for root certificate 2
with open(ROOT2_CERT_CONFIG_PATH) as cert_config:
    # load yaml configuration to dictionary
    cert = yaml.safe_load(cert_config)
    # change path to private and public keys
    cert['issuer_private_key'] = ROTK2_PRIVATE_KEY_PATH
    cert['subject_public_key'] = ROTK2_PUBLIC_KEY_PATH

with open(ROOT2_CERT_CONFIG_PATH, "w+") as cert_config:
    print("Root Certificate config:")
    pp.pprint(cert)
    # dump the dictionary back to YAML
    yaml.dump(cert, cert_config)

# Create configuration for root certificate 3
with open(ROOT3_CERT_CONFIG_PATH) as cert_config:
    # load yaml configuration to dictionary
    cert = yaml.safe_load(cert_config)
    # change path to private and public keys
    cert['issuer_private_key'] = ROTK3_PRIVATE_KEY_PATH
    cert['subject_public_key'] = ROTK3_PUBLIC_KEY_PATH

with open(ROOT3_CERT_CONFIG_PATH, "w+") as cert_config:
    print("Root Certificate config:")
    pp.pprint(cert)
    # dump the dictionary back to YAML
    yaml.dump(cert, cert_config)

# Create configuration for ISK certificate 
with open(ISK_CERT_CONFIG_PATH) as cert_config:
    # load yaml configuration to dictionary
    cert = yaml.safe_load(cert_config)
    # change path to private and public keys
    cert['issuer_private_key'] = ISK_PRIVATE_KEY_PATH
    cert['subject_public_key'] = ISK_PUBLIC_KEY_PATH

with open(ISK_CERT_CONFIG_PATH, "w+") as cert_config:
    print("Root Certificate config:")
    pp.pprint(cert)
    # dump the dictionary back to YAML
    yaml.dump(cert, cert_config)


# Generate root certificates 0
%! nxpcrypto $VERBOSITY cert generate -c $ROOT0_CERT_CONFIG_PATH -o $ROOT_0_CERT_PATH --force
# Generate root certificates 1
%! nxpcrypto $VERBOSITY cert generate -c $ROOT1_CERT_CONFIG_PATH -o $ROOT_1_CERT_PATH --force
# Generate root certificates 2
%! nxpcrypto $VERBOSITY cert generate -c $ROOT2_CERT_CONFIG_PATH -o $ROOT_2_CERT_PATH --force
# Generate root certificates 3
%! nxpcrypto $VERBOSITY cert generate -c $ROOT3_CERT_CONFIG_PATH -o $ROOT_3_CERT_PATH --force

# Generate ISK certificate
%! nxpcrypto $VERBOSITY cert generate -c $ISK_CERT_CONFIG_PATH -o $ISK_CERT_PATH --force

# verify that certificates were generated
assert os.path.exists(ROOT_0_CERT_PATH)
assert os.path.exists(ROOT_1_CERT_PATH)
assert os.path.exists(ROOT_2_CERT_PATH)
assert os.path.exists(ROOT_3_CERT_PATH)
assert os.path.exists(ISK_CERT_PATH)
Root Certificate config:
{   'duration': 3650,
    'extensions': {'BASIC_CONSTRAINTS': {'ca': False, 'path_length': 0}},
    'issuer': {   'COMMON_NAME': 'NXP',
                  'COUNTRY_NAME': 'CZ',
                  'LOCALITY_NAME': 'Roznov pod Radhostem',
                  'ORGANIZATION_NAME': 'SPSDK Team',
                  'STATE_OR_PROVINCE_NAME': 'Morava',
                  'STREET_ADDRESS': '1.maje 1009'},
    'issuer_private_key': 'workspace/ec_pk_secp384r1_cert0.pem',
    'serial_number': 12346578,
    'subject': {   'COMMON_NAME': 'NXP - SPSDK',
                   'COUNTRY_NAME': 'CZ',
                   'LOCALITY_NAME': 'Roznov pod Radhostem',
                   'ORGANIZATION_NAME': 'SPSDK Team',
                   'POSTAL_CODE': '756 61',
                   'STATE_OR_PROVINCE_NAME': 'Morava',
                   'STREET_ADDRESS': '1.maje 1009'},
    'subject_public_key': 'workspace/ec_pk_secp384r1_cert0.pub'}
Root Certificate config:
{   'duration': 3650,
    'extensions': {'BASIC_CONSTRAINTS': {'ca': False, 'path_length': 0}},
    'issuer': {   'COMMON_NAME': 'NXP',
                  'COUNTRY_NAME': 'CZ',
                  'LOCALITY_NAME': 'Roznov pod Radhostem',
                  'ORGANIZATION_NAME': 'SPSDK Team',
                  'STATE_OR_PROVINCE_NAME': 'Morava',
                  'STREET_ADDRESS': '1.maje 1009'},
    'issuer_private_key': 'workspace/ec_pk_secp384r1_cert1.pem',
    'serial_number': 12346578,
    'subject': {   'COMMON_NAME': 'NXP - SPSDK',
                   'COUNTRY_NAME': 'CZ',
                   'LOCALITY_NAME': 'Roznov pod Radhostem',
                   'ORGANIZATION_NAME': 'SPSDK Team',
                   'POSTAL_CODE': '756 61',
                   'STATE_OR_PROVINCE_NAME': 'Morava',
                   'STREET_ADDRESS': '1.maje 1009'},
    'subject_public_key': 'workspace/ec_pk_secp384r1_cert1.pub'}
Root Certificate config:
{   'duration': 3650,
    'extensions': {'BASIC_CONSTRAINTS': {'ca': False, 'path_length': 0}},
    'issuer': {   'COMMON_NAME': 'NXP',
                  'COUNTRY_NAME': 'CZ',
                  'LOCALITY_NAME': 'Roznov pod Radhostem',
                  'ORGANIZATION_NAME': 'SPSDK Team',
                  'STATE_OR_PROVINCE_NAME': 'Morava',
                  'STREET_ADDRESS': '1.maje 1009'},
    'issuer_private_key': 'workspace/ec_pk_secp384r1_cert2.pem',
    'serial_number': 12346578,
    'subject': {   'COMMON_NAME': 'NXP - SPSDK',
                   'COUNTRY_NAME': 'CZ',
                   'LOCALITY_NAME': 'Roznov pod Radhostem',
                   'ORGANIZATION_NAME': 'SPSDK Team',
                   'POSTAL_CODE': '756 61',
                   'STATE_OR_PROVINCE_NAME': 'Morava',
                   'STREET_ADDRESS': '1.maje 1009'},
    'subject_public_key': 'workspace/ec_pk_secp384r1_cert2.pub'}
Root Certificate config:
{   'duration': 3650,
    'extensions': {'BASIC_CONSTRAINTS': {'ca': False, 'path_length': 0}},
    'issuer': {   'COMMON_NAME': 'NXP',
                  'COUNTRY_NAME': 'CZ',
                  'LOCALITY_NAME': 'Roznov pod Radhostem',
                  'ORGANIZATION_NAME': 'SPSDK Team',
                  'STATE_OR_PROVINCE_NAME': 'Morava',
                  'STREET_ADDRESS': '1.maje 1009'},
    'issuer_private_key': 'workspace/ec_pk_secp384r1_cert3.pem',
    'serial_number': 12346578,
    'subject': {   'COMMON_NAME': 'NXP - SPSDK',
                   'COUNTRY_NAME': 'CZ',
                   'LOCALITY_NAME': 'Roznov pod Radhostem',
                   'ORGANIZATION_NAME': 'SPSDK Team',
                   'POSTAL_CODE': '756 61',
                   'STATE_OR_PROVINCE_NAME': 'Morava',
                   'STREET_ADDRESS': '1.maje 1009'},
    'subject_public_key': 'workspace/ec_pk_secp384r1_cert3.pub'}
Root Certificate config:
{   'duration': 3650,
    'extensions': {'BASIC_CONSTRAINTS': {'ca': False, 'path_length': 0}},
    'issuer': {   'COMMON_NAME': 'NXP',
                  'COUNTRY_NAME': 'CZ',
                  'LOCALITY_NAME': 'Roznov pod Radhostem',
                  'ORGANIZATION_NAME': 'SPSDK Team',
                  'STATE_OR_PROVINCE_NAME': 'Morava',
                  'STREET_ADDRESS': '1.maje 1009'},
    'issuer_private_key': 'workspace/ec_pk_secp384r1_sign_cert.pem',
    'serial_number': 12346578,
    'subject': {   'COMMON_NAME': 'NXP - SPSDK',
                   'COUNTRY_NAME': 'CZ',
                   'LOCALITY_NAME': 'Roznov pod Radhostem',
                   'ORGANIZATION_NAME': 'SPSDK Team',
                   'POSTAL_CODE': '756 61',
                   'STATE_OR_PROVINCE_NAME': 'Morava',
                   'STREET_ADDRESS': '1.maje 1009'},
    'subject_public_key': 'workspace/ec_pk_secp384r1_sign_cert.pub'}
nxpcrypto -v cert generate -c workspace/cert0_template.yml -o workspace/ec_secp384r1_cert0.pem --force 
INFO:spsdk.apps.nxpcertgen:Generating Certificate...
INFO:spsdk.apps.nxpcertgen:Loading configuration from yml file...
INFO:spsdk.apps.nxpcertgen:Saving the generated certificate to the specified path...
INFO:spsdk.apps.nxpcertgen:Certificate generated successfully...
The certificate file has been created: C:\spsdk\examples\jupyter_examples\kw45xx_k32w1xx\workspace\ec_secp384r1_cert0.pem
nxpcrypto -v cert generate -c workspace/cert1_template.yml -o workspace/ec_secp384r1_cert1.pem --force 
INFO:spsdk.apps.nxpcertgen:Generating Certificate...
INFO:spsdk.apps.nxpcertgen:Loading configuration from yml file...
INFO:spsdk.apps.nxpcertgen:Saving the generated certificate to the specified path...
INFO:spsdk.apps.nxpcertgen:Certificate generated successfully...
The certificate file has been created: C:\spsdk\examples\jupyter_examples\kw45xx_k32w1xx\workspace\ec_secp384r1_cert1.pem
nxpcrypto -v cert generate -c workspace/cert2_template.yml -o workspace/ec_secp384r1_cert2.pem --force 
INFO:spsdk.apps.nxpcertgen:Generating Certificate...
INFO:spsdk.apps.nxpcertgen:Loading configuration from yml file...
INFO:spsdk.apps.nxpcertgen:Saving the generated certificate to the specified path...
INFO:spsdk.apps.nxpcertgen:Certificate generated successfully...
The certificate file has been created: C:\spsdk\examples\jupyter_examples\kw45xx_k32w1xx\workspace\ec_secp384r1_cert2.pem
nxpcrypto -v cert generate -c workspace/cert3_template.yml -o workspace/ec_secp384r1_cert3.pem --force 
INFO:spsdk.apps.nxpcertgen:Generating Certificate...
INFO:spsdk.apps.nxpcertgen:Loading configuration from yml file...
INFO:spsdk.apps.nxpcertgen:Saving the generated certificate to the specified path...
INFO:spsdk.apps.nxpcertgen:Certificate generated successfully...
The certificate file has been created: C:\spsdk\examples\jupyter_examples\kw45xx_k32w1xx\workspace\ec_secp384r1_cert3.pem
nxpcrypto -v cert generate -c workspace/sign_cert_template.yml -o workspace/ec_secp384r1_sign_cert.pem --force 
INFO:spsdk.apps.nxpcertgen:Generating Certificate...
INFO:spsdk.apps.nxpcertgen:Loading configuration from yml file...
INFO:spsdk.apps.nxpcertgen:Saving the generated certificate to the specified path...
INFO:spsdk.apps.nxpcertgen:Certificate generated successfully...
The certificate file has been created: C:\spsdk\examples\jupyter_examples\kw45xx_k32w1xx\workspace\ec_secp384r1_sign_cert.pem

Generate SB3KDK#

To generate SB3KDK use the below script. Key generation can be done only once on the beginning, then is SB3KDK key loaded in device fuses and key cannot be changed anymore for device.

import binascii

SB3KDK_KEY_PATH = WORKSPACE + "sb3kdk.txt"
with open(SB3KDK_KEY_PATH, "wb") as f:
    f.write(binascii.b2a_hex(os.urandom(32)))

assert os.path.exists(SB3KDK_KEY_PATH)

Prepare SB3.1 configuration file#

In order to generate SB3.1 file, npximage tool is used. The nxpimage tool generates the SB3.1 file according to the configuration file. Let’s create a template for SB3.1. Modify examples according your needs.

SB31_TEMPLATE_PATH = WORKSPACE + "sb31_config.yml"
%! nxpimage $VERBOSITY sb31 get-template -f $FAMILY -o $SB31_TEMPLATE_PATH --force
# For K32W1XX device:
# %! nxpimage $VERBOSITY sb31 get-template -f $FAMILY -o $SB31_TEMPLATE_PATH --force
assert os.path.exists(SB31_TEMPLATE_PATH)

with open(SB31_TEMPLATE_PATH) as f:
    print(f.read())
nxpimage -v sb31 get-template -f kw45xx -o workspace/sb31_config.yml --force 
Creating C:\spsdk\examples\jupyter_examples\kw45xx_k32w1xx\workspace\sb31_config.yml template file.
# ==============================  Secure Binary v3.1 Configuration template for kw45xx.  ===============================

# ======================================================================================================================
#                                                  == Basic Settings ==                                                 
# ======================================================================================================================
# ---------------------------------------===== Firmware version. [Optional] =====---------------------------------------
# Description: Value compared with Secure_FW_Version monotonic counter value stored in PFR/IFR. If value is lower than
# value in PFR/IFR, then is image rejected (rollback protection)..
firmwareVersion: 0
# ------------------------------------------===== MCU family [Required] =====-------------------------------------------
# Description: MCU family name.
# Possible options: <k32w1xx, kw45xx, lpc55s3x, mcxn9xx, rw61x>
family: kw45xx
# -----------------------------------------===== SB3 filename [Required] =====------------------------------------------
# Description: Generated SB3 container filename.
containerOutputFile: my_new.sb3
# ======================================================================================================================
#                                              == Image Signing Settings ==                                             
# ======================================================================================================================
# --------------------------===== Main Certificate private key [Conditionally required] =====---------------------------
# Description: Main Certificate private key used to sign certificate. It can be replaced by signProvider key.
signPrivateKey: main_prv_key.pem
# -------------------------------===== Signature Provider [Conditionally required] =====--------------------------------
# Description: Signature provider configuration in format 'type=<sp_type>;<key1>=<value1>;<key2>=<value2>".
signProvider: type=file;file_path=my_prv_key.pem
# ======================================================================================================================
#                                              == Certificate Block V2.1 ==                                             
# ======================================================================================================================
# -----------------------------===== Certificate Block binary/config file [Required] =====------------------------------
# Description: Path to certificate block binary or config file.
certBlock: cert_block.yaml
# ======================================================================================================================
#                                           == Secure Binary v3.1 Settings ==                                           
# ======================================================================================================================
# ----------------------------------------===== Part Common Key [Optional] =====----------------------------------------
# Description: Path to PCK/NPK 256 or 128 bit key in plain hex string format or path to binary file or hex string.
containerKeyBlobEncryptionKey: my_pck.txt
# ----------------------------------===== Enable NXP Container format [Optional] =====----------------------------------
# Description: Internal usage only, used for generating SB files with NXP content e.g. provisioning firmware, etc...
isNxpContainer: false
# ---------------------------------------===== KDK access rights [Optional] =====---------------------------------------
# Description: Accepted values are 0, 1, 2 and 3. Value used as key properties for key derivation process, more details
# can be found in CSSv2 manual.
# Possible options: <0, 1, 2, 3>
kdkAccessRights: 0
# ---------------------------------===== Container configuration word [Optional] =====----------------------------------
# Description: Flag value in SB3.1 manifest, not used by silicons with LPC55S3x ROM. Value can be kept 0, or it can be
# removed from the configuration file.
containerConfigurationWord: 0
# ------------------------------------------===== Description [Optional] =====------------------------------------------
# Description: Description up to 16 characters, longer will be truncated. Stored in SB3.1 manifest.
description: This is description of generated SB file.
# ======================================================================================================================
#                                       == Secure Binary v3.1 Commands Settings ==                                      
# ======================================================================================================================
# ----------------------------------------===== SB3.1 Commands [Required] =====-----------------------------------------
# Description: Secure Binary v3.1 commands block, list of all possible options - Modify it according to your application
commands:
  -
  # ====================================================================================================================
  #  == List of possible 9 options. Options [erase, load, execute, programFuses, programIFR, loadCMAC, loadHashLocking, fillMemory, checkFwVersion] == 
  # ====================================================================================================================

  #  ====================================== [Example of possible configuration #0] ======================================
    # -------------------------------------------===== Erase [Required] =====-------------------------------------------
    # Description: Performs a flash erase of the given address range. The erase will be rounded up to the sector size.
    erase:
      # -----------------------------------------===== Address [Required] =====-----------------------------------------
      # Description: Address of memory block to be erased.
      address: 0
      # ------------------------------------------===== Size [Required] =====-------------------------------------------
      # Description: Size of memory block to be erased.
      size: 4096
      # ----------------------------------------===== Memory ID [Optional] =====----------------------------------------
      # Description: ID of memory block to be erased.
      memoryId: 0

  #  ====================================== [Example of possible configuration #1] ======================================
    # -------------------------------------------===== Load [Required] =====--------------------------------------------
    # Description: If set, then the data to write immediately follows the range header. The length field contains the
    # actual data length
    load:
      # -----------------------------------------===== Address [Required] =====-----------------------------------------
      # Description: Address of memory block to be loaded.
      address: 0
      # ----------------------------------------===== Memory ID [Optional] =====----------------------------------------
      # Description: ID of memory block to be loaded.
      memoryId: 0
      # --------------------------------------===== Binary file. [Optional] =====---------------------------------------
      # Description: Binary file to be loaded.
      file: my_binary.bin
      # -------------------------------------===== Binary values. [Optional] =====--------------------------------------
      # Description: Binary values delimited by comma to be loaded.
      values: 0x1234, 0x5678, 0, 12345678
      # -------------------------------------===== Authentication [Optional] =====--------------------------------------
      # Description: If authentication is not used, just omit this option or set 'none'.
      # Possible options: <none, cmac, hashlocking>
      authentication: cmac

  #  ====================================== [Example of possible configuration #2] ======================================
    # ------------------------------------------===== Execute [Required] =====------------------------------------------
    # Description: Address is the jump-to address. No further processing of SB after jump, ROM do not expect to return.
    execute:
      # -----------------------------------------===== Address [Required] =====-----------------------------------------
      # Description: Jump-to address to start execute code.
      address: 0

  #  ====================================== [Example of possible configuration #3] ======================================
    # ---------------------------------------===== Program Fuses [Required] =====---------------------------------------
    # Description: Address is OTP index of fuses to be programmed (Check the reference manual for more information).
    # Values is a comma separated list of 32bit values.
    programFuses:
      # -----------------------------------------===== Address [Required] =====-----------------------------------------
      # Description: OTP Index of fuses to be programmed. Depends on the chip ROM.
      address: 0
      # --------------------------------------===== Binary values [Required] =====--------------------------------------
      # Description: 32bit binary values delimited by comma to be programmed.
      values: 0x1234, 0x5678, 0, 12345678

  #  ====================================== [Example of possible configuration #4] ======================================
    # ----------------------------------------===== Program IFR [Required] =====----------------------------------------
    # Description: The startAddress will be the address into the IFR region, length will be in number of bytes to write
    # to IFR region. The data to write to IFR region at the given address will immediately follow the header
    programIFR:
      # -----------------------------------------===== Address [Required] =====-----------------------------------------
      # Description: Address of IFR region to be programmed.
      address: 0
      # ---------------------------------------===== Binary file [Required] =====---------------------------------------
      # Description: Binary file to be programmed.
      file: my_binary.bin

  #  ====================================== [Example of possible configuration #5] ======================================
    # -----------------------------------------===== Load CMAC [Required] =====-----------------------------------------
    # Description: If set, then the data to write immediately follows the range header. The length field contains the
    # actual data length. ROM is calculating cmac from loaded data and storing on address known by ROM decided based on
    # startAddress.
    loadCMAC:
      # -----------------------------------------===== Address [Required] =====-----------------------------------------
      # Description: Address of memory block to be CMAC loaded.
      address: 0
      # ----------------------------------------===== Memory ID [Optional] =====----------------------------------------
      # Description: ID of memory block to be CMAC loaded.
      memoryId: 0
      # ---------------------------------------===== Binary file [Required] =====---------------------------------------
      # Description: Binary file to be loaded.
      file: my_cmac_binary.bin

  #  ====================================== [Example of possible configuration #6] ======================================
    # ----------------------------------===== Load with HASH locking [Required] =====-----------------------------------
    # Description: If set, then the data to write immediately follows the range header. The length field contains the
    # actual data length. ROM is calculating hash of the data and storing the value in the last 64 bytes of the loaded
    # data, which are reserved for it.
    loadHashLocking:
      # -----------------------------------------===== Address [Required] =====-----------------------------------------
      # Description: Address of memory block to be loaded.
      address: 0
      # ----------------------------------------===== Memory ID [Optional] =====----------------------------------------
      # Description: ID of memory block to be loaded.
      memoryId: 0
      # ---------------------------------------===== Binary file [Required] =====---------------------------------------
      # Description: Binary file to be loaded.
      file: my_hashlocking_binary.bin

  #  ====================================== [Example of possible configuration #7] ======================================
    # ----------------------------------------===== Fill memory [Required] =====----------------------------------------
    # Description: Used for filling of the memory range by same repeated int32 pattern.
    fillMemory:
      # -----------------------------------------===== Address [Required] =====-----------------------------------------
      # Description: Address of memory block to be filled.
      address: 0
      # ------------------------------------------===== Size [Required] =====-------------------------------------------
      # Description: Size of memory block to be filled.
      size: 4096
      # -----------------------------------------===== Pattern [Required] =====-----------------------------------------
      # Description: Pattern which will be used to fill memory.
      pattern: 2779096485

  #  ====================================== [Example of possible configuration #8] ======================================
    # ----------------------------------===== Check firmware version [Required] =====-----------------------------------
    # Description: Checks FW version value specified in command for specified counter ID. FW version value in command
    # must be greater than value programmed in OTP to be accepted, otherwise rollback is detected and receive SB fails
    checkFwVersion:
      # --------------------------------===== Value - Firmware version [Required] =====---------------------------------
      # Description: Firmware version to be compared.
      value: 1
      # ---------------------------------------===== Counter ID [Required] =====----------------------------------------
      # Description: ID of FW counter to be checked.
      # Possible options: <none, nonsecure, secure, radio, snt, bootloader>
      counterId: secure

For signed container scenario, we need to create a certificate blok separately. In order to do this, use nxpimage cert-block get-template command as described below.

CERT_BLOCK_TEMPLATE = WORKSPACE +"cert_block_kw45.yaml"
%! nxpimage $VERBOSITY cert-block get-template -f $FAMILY -o $CERT_BLOCK_TEMPLATE --force
nxpimage -v cert-block get-template -f kw45xx -o workspace/cert_block_kw45.yaml --force 
Creating C:\spsdk\examples\jupyter_examples\kw45xx_k32w1xx\workspace\cert_block_kw45.yaml template file.
assert os.path.exists(CERT_BLOCK_TEMPLATE)
CERT_BLOCK_BIN = WORKSPACE + "cert_block.bin"

# Create configuration for Certificate Block
with open(CERT_BLOCK_TEMPLATE) as cert_block_config:
    # load yaml configuration to dictionary
    ct = yaml.safe_load(cert_block_config)
    # change paths
    ct['rootCertificate0File'] = ROTK0_PUBLIC_KEY_PATH
    ct['rootCertificate1File'] = ROTK1_PUBLIC_KEY_PATH
    ct['rootCertificate2File'] = ROTK2_PUBLIC_KEY_PATH
    ct['rootCertificate3File'] = ROTK3_PUBLIC_KEY_PATH
    ct['mainRootCertId'] = 0
    ct['iskPublicKey'] = ISK_PUBLIC_KEY_PATH
    ct['containerOutputFile'] = CERT_BLOCK_BIN
    del ct['iskCertData']
    del ct['signPrivateKey']
    del ct['signProvider']  
with open(CERT_BLOCK_TEMPLATE, "w+") as cert_block_config:
    print("Certificate Block:")
    pp.pprint(ct)
    # dump the dictionary back to YAML
    yaml.dump(ct, cert_block_config)
Certificate Block:
{   'containerOutputFile': 'workspace/cert_block.bin',
    'iskCertificateConstraint': 0,
    'iskPublicKey': 'workspace/ec_pk_secp384r1_sign_cert.pub',
    'mainRootCertId': 0,
    'rootCertificate0File': 'workspace/ec_pk_secp384r1_cert0.pub',
    'rootCertificate1File': 'workspace/ec_pk_secp384r1_cert1.pub',
    'rootCertificate2File': 'workspace/ec_pk_secp384r1_cert2.pub',
    'rootCertificate3File': 'workspace/ec_pk_secp384r1_cert3.pub',
    'useIsk': False}
SB31_FILE_PATH = "sb3.sb3"
import yaml
assert os.path.exists(SB31_TEMPLATE_PATH)

# Create configuration for sb31
with open(SB31_TEMPLATE_PATH) as sb31_config:
    # load yaml configuration to dictionary
    sb31 = yaml.safe_load(sb31_config)
    # change paths
    sb31['containerOutputFile'] = SB31_FILE_PATH
    sb31['containerKeyBlobEncryptionKey'] = SB3KDK_KEY_PATH
    sb31['description'] = "384_none_nbu_only"
    sb31['kdkAccessRights'] = 3
    sb31['containerConfigurationWord'] = 0
    sb31['rootCertificate0File'] = ROOT_0_CERT_PATH
    sb31['rootCertificate1File'] = ROOT_1_CERT_PATH
    sb31['rootCertificate2File'] = ROOT_2_CERT_PATH
    sb31['rootCertificate3File'] = ROOT_3_CERT_PATH
    sb31['mainRootCertId'] = 0
    sb31['mainRootCertPrivateKeyFile'] = ROTK0_PRIVATE_KEY_PATH
    sb31['rootCertificateEllipticCurve'] = "secp384r1"
    sb31['certBlock'] = CERT_BLOCK_TEMPLATE
    del sb31['signPrivateKey']
    del sb31['signProvider']

    # This is common for both scenarios:
    del sb31['commands']
    sb31['commands'] = [
        {
            "erase": {
                "address": "0x48800000 ",
                "size": "0x30000"
            }   
        },
        {
            "load": {
                "address": "0x48800000 ",
                "file": "kw45b41_nbu_ble_hosted_04.xip"
            }
        }
    ]

with open(SB31_TEMPLATE_PATH, "w+") as sb31_config:
    print("SB31:")
    pp.pprint(sb31)
    # dump the dictionary back to YAML
    yaml.dump(sb31, sb31_config)
SB31:
{   'certBlock': 'workspace/cert_block_kw45.yaml',
    'commands': [   {'erase': {'address': '0x48800000 ', 'size': '0x30000'}},
                    {   'load': {   'address': '0x48800000 ',
                                    'file': 'kw45b41_nbu_ble_hosted_04.xip'}}],
    'containerConfigurationWord': 0,
    'containerKeyBlobEncryptionKey': 'workspace/sb3kdk.txt',
    'containerOutputFile': 'sb3.sb3',
    'description': '384_none_nbu_only',
    'family': 'kw45xx',
    'firmwareVersion': 0,
    'isNxpContainer': False,
    'kdkAccessRights': 3,
    'mainRootCertId': 0,
    'mainRootCertPrivateKeyFile': 'workspace/ec_pk_secp384r1_cert0.pem',
    'rootCertificate0File': 'workspace/ec_secp384r1_cert0.pem',
    'rootCertificate1File': 'workspace/ec_secp384r1_cert1.pem',
    'rootCertificate2File': 'workspace/ec_secp384r1_cert2.pem',
    'rootCertificate3File': 'workspace/ec_secp384r1_cert3.pem',
    'rootCertificateEllipticCurve': 'secp384r1'}

SB3.1 generation#

We have created certificates and keys required for the creation of SB3.1 file. Let’s create a SB3.1.

# Verbosity needs to be at least info (-v) in order to get SB3KDK and RoTKTH values
%! nxpimage $VERBOSITY sb31 export -c $SB31_TEMPLATE_PATH
assert os.path.exists(WORKSPACE+SB31_FILE_PATH)
nxpimage -v sb31 export -c workspace/sb31_config.yml 
INFO:spsdk.sbfile.sb31.images:SB3KDK: c2d3eafa44cedc1edf237a7655acf18a1f41e85ea605dcc3bf473e4618b7882f
RKTH: f673058d55c56b95d51734df6a74de7346d7526c6a5f0cc891cd2adbb8bd428630eb13c85716636124d8b1b648c05535
Success. (Secure binary 3.1: C:/spsdk/examples/jupyter_examples/kw45xx_k32w1xx/workspace/sb3.sb3 created.)

Device preparation#

Now it’s time to prepare the device. In this example we will use KW45xx-EVK/K32W1xx board.

First step is to enter ISP mode, this could be achieved by:

1 ) Put JP25 to (1-2)

2 ) Reset the board with SW4 pressed

KW45xx-EVK

K32W1xx-EVK

Use app nxpdevscan to check if the device is connected to the PC in ISP mode.

# check if the device is connected and detected by PC
%! nxpdevscan
nxpdevscan 
-------- Connected NXP SDIO Devices --------

-------- Connected NXP USB Devices --------

-------- Connected NXP UART Devices --------

Port: COM10
Type: mboot device

-------- Connected NXP SIO Devices --------
# choose com port
UART_CONNECTION = "-p com10"

%! blhost $UART_CONNECTION get-property current-version
blhost -p com10 get-property current-version 
Response status = 0 (0x0) Success.
Response word 1 = 1258488064 (0x4b030100)
Current Version = K3.1.0

Program device fuses with keys/RoTKTH generated in previous steps#

To program fuses blhost tool is used. Device needs to be in ISP mode, where it can communicate with blhost and process blhost commands. To serve the purpose of this document, ISP communication only over UART peripheral is considered for scripts. Also, accurate COMx port must be used.

  • WARNING!!! This step is destructive operation (burning fuses), be sure that you set values of SB3KDK and RoTKH correctly in script as printed in output from nxpimage

# Increase voltage for fuse burning
%! blhost $UART_CONNECTION set-property 0x16 1
# program SB3KDK (CUST_PROD_OEMFW_ENC_SK)
# put value SB3KDK generated by nxpimage
%! blhost $UART_CONNECTION fuse-program 0x20 [[c2d3eafa44cedc1edf237a7655acf18a1f41e85ea605dcc3bf473e4618b7882f]]
# program RoTKTH (CUST_PROD_OEMFW_AUTH_PUK)
# put value RoTKTH generated by nxpimage
%! blhost $UART_CONNECTION fuse-program 0x1F [[f673058d55c56b95d51734df6a74de7346d7526c6a5f0cc891cd2adbb8bd428630eb13c85716636124d8b1b648c05535]]
# Set voltage to normal value
%! blhost $UART_CONNECTION set-property 0x16 0
blhost -p com10 set-property 0x16 1 
Response status = 0 (0x0) Success.
blhost -p com10 fuse-program 0x20 [[c2d3eafa44cedc1edf237a7655acf18a1f41e85ea605dcc3bf473e4618b7882f]] 
Response status = 0 (0x0) Success.
Response word 1 = 32 (0x20)
blhost -p com10 fuse-program 0x1F [[f673058d55c56b95d51734df6a74de7346d7526c6a5f0cc891cd2adbb8bd428630eb13c85716636124d8b1b648c05535]] 
Response status = 0 (0x0) Success.
Response word 1 = 48 (0x30)
blhost -p com10 set-property 0x16 0 
Response status = 0 (0x0) Success.

Send SB3.1 file to device#

Last step is to uploads SB3.1 file with NBU image to device.

# uploads SB3.1
SB31_FILE_FINAL = WORKSPACE+SB31_FILE_PATH
assert os.path.exists(SB31_FILE_FINAL)

%! blhost $UART_CONNECTION receive-sb-file $SB31_FILE_FINAL
blhost -p com10 receive-sb-file workspace/sb3.sb3 
Sending SB file
Response status = 0 (0x0) Success.