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.

[1]:
%run ../init_notebook.ipynb

import os
import pprint

pp = pprint.PrettyPrinter(indent=4)

WORKSPACE = "workspace/" # change this to path to your workspace
VERBOSITY = "" # verbosity of commands, might be -v or -vv for debug or blank for no additional info
env: JUPYTER_SPSDK=1
Created `%!` as an alias for `%execute`.
[2]:
# 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 $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 $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 $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 $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 $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  key generate -k secp384r1 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  key generate -k secp384r1 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  key generate -k secp384r1 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  key generate -k secp384r1 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  key generate -k secp384r1 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.

[3]:
# obtain a template for root cert ROTK0
ROOT0_CERT_CONFIG_PATH = WORKSPACE + "cert0_template.yml"
%! nxpcrypto $VERBOSITY cert get-template $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 $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 $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 $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 $ISK_CERT_CONFIG_PATH --force
nxpcrypto  cert get-template workspace/cert0_template.yml --force
The configuration template file has been created: C:\spsdk\examples\jupyter_examples\kw45xx_k32w1xx\workspace\cert0_template.yml
nxpcrypto  cert get-template workspace/cert1_template.yml --force
The configuration template file has been created: C:\spsdk\examples\jupyter_examples\kw45xx_k32w1xx\workspace\cert1_template.yml
nxpcrypto  cert get-template workspace/cert2_template.yml --force
The configuration template file has been created: C:\spsdk\examples\jupyter_examples\kw45xx_k32w1xx\workspace\cert2_template.yml
nxpcrypto  cert get-template workspace/cert3_template.yml --force
The configuration template file has been created: C:\spsdk\examples\jupyter_examples\kw45xx_k32w1xx\workspace\cert3_template.yml
nxpcrypto  cert get-template workspace/sign_cert_template.yml --force
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.

[4]:
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  cert generate -c workspace/cert0_template.yml -o workspace/ec_secp384r1_cert0.pem --force
The certificate file has been created: C:\spsdk\examples\jupyter_examples\kw45xx_k32w1xx\workspace\ec_secp384r1_cert0.pem
nxpcrypto  cert generate -c workspace/cert1_template.yml -o workspace/ec_secp384r1_cert1.pem --force
The certificate file has been created: C:\spsdk\examples\jupyter_examples\kw45xx_k32w1xx\workspace\ec_secp384r1_cert1.pem
nxpcrypto  cert generate -c workspace/cert2_template.yml -o workspace/ec_secp384r1_cert2.pem --force
The certificate file has been created: C:\spsdk\examples\jupyter_examples\kw45xx_k32w1xx\workspace\ec_secp384r1_cert2.pem
nxpcrypto  cert generate -c workspace/cert3_template.yml -o workspace/ec_secp384r1_cert3.pem --force
The certificate file has been created: C:\spsdk\examples\jupyter_examples\kw45xx_k32w1xx\workspace\ec_secp384r1_cert3.pem
nxpcrypto  cert generate -c workspace/sign_cert_template.yml -o workspace/ec_secp384r1_sign_cert.pem --force
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.

[5]:
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.

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

with open(SB31_TEMPLATE_PATH) as f:
    print(f.read())
nxpimage  sb31 get-template -f kw45xx -o workspace/sb31_config.yml
Creating C:\spsdk\examples\jupyter_examples\kw45xx_k32w1xx\workspace\sb31_config.yml template file.
# ===========  Secure Binary v3.1 Configuration template for kw45xx.  ===========
# ----------------------------------------------------------------------------------------------------
#                                         == Basic Settings ==
# ----------------------------------------------------------------------------------------------------
firmwareVersion: 0  # [Optional], Firmware version; Version of application image firmware.
family: kw45xx # [Required], MCU family name; Possible options:['k32w1xx', 'kw45xx', 'lpc55s3x', 'mcxn9xx']
containerOutputFile: my_new.sb3 # [Required], SB3 filename; Generated SB3 container filename.
# ----------------------------------------------------------------------------------------------------
#                                       == Root Keys Settings ==
# ----------------------------------------------------------------------------------------------------
rootCertificate0File: my_certificate0.pub # [Conditionally required], Root Certificate File 0; Root certificate file index 0.
rootCertificate1File: my_certificate1.pub # [Optional], Root Certificate File 1; Root certificate file index 1.
rootCertificate2File: my_certificate2.pub # [Optional], Root Certificate File 2; Root certificate file index 2.
rootCertificate3File: my_certificate3.pub # [Optional], Root Certificate File 3; Root certificate file index 3.
mainRootCertId: 0 # [Conditionally required], Main Certificate Index; Index of certificate that is used as a main. If not defined, the certificate matching private key will be selected.
# ----------------------------------------------------------------------------------------------------
#                                   == Certificate V3.1 Settings ==
# ----------------------------------------------------------------------------------------------------
rootCertificateEllipticCurve: secp256r1 # [Conditionally required], Type of elliptic curve of root key; Elliptic curve type used for root key; Possible options:['secp256r1', 'secp384r1']
iskCertificateEllipticCurve: secp256r1 # [Conditionally required], Type of elliptic curve of ISK key; Elliptic curve type used for ISK key; Possible options:['secp256r1', 'secp384r1']
# ----------------------------------------------------------------------------------------------------
#                                    == ISK Certificate Settings ==
# ----------------------------------------------------------------------------------------------------
binaryCertificateBlock: my_isk_cert.bin # [Conditionally required], Binary Certificate; Optionally the certificate could be defined as a pre-generated binary block. In case that is defined, all other configuration for certification block must be deleted ('useIsk', 'mainRootCertPrivateKeyFile', 'signingCertificateFile', 'signingCertificateConstraint', 'signCertData') In case that ISK is defined, certicate block must be deleted
useIsk: false # [Conditionally required], Use ISK for signature certification; Enable ISK type of signature certification. Don't use when 'binaryCertificateBlock' is defined
mainRootCertPrivateKeyFile: main_cert_prv_key.pem # [Conditionally required], Main root Certification Private Key; Path to Main root Certification Private Key. Don't use when 'binaryCertificateBlock' is defined
signingCertificateFile: sign_cert.pem # [Conditionally required], Signing Certificate; Path to Signing Certificate. Don't use when 'binaryCertificateBlock' is defined
signingCertificateConstraint: 0 # [Optional], Signing certificate constrain number. Don't use when 'binaryCertificateBlock' is defined
signCertData: sign_cert.bin # [Optional], Signing Certificate data; Path to Signing Certificate data. Don't use when 'binaryCertificateBlock' is defined
signingCertificatePrivateKeyFile: isk_prv_key.pem # [Optional], ISK Certificate private key used to sign certificate. It can be replaced by signProvider key.
iskSignProvider: type=file;file_path=my_isk_prv_key.pem # [Optional], ISK Signature Provider; Signature provider configuration in format 'type=<sp_type>;<key1>=<value1>;<key2>=<value2>".
# ----------------------------------------------------------------------------------------------------
#                                  == Secure Binary v3.1 Settings ==
# ----------------------------------------------------------------------------------------------------
containerKeyBlobEncryptionKey: my_pck.txt # [Optional], Part Common Key; Path to PCK/NPK key in plain hex string format.
isNxpContainer: false # [Optional], Enable NXP Container format; Internal usage only, used for generating SB files with NXP content e.g. provisioning firmware, sentinel firmware...
kdkAccessRights: 0 # [Optional], KDK access rights; 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]
containerConfigurationWord: 0 # [Optional], Container configuration word; 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.
description: This is description of generated SB file. # [Optional], Description up to 16 characters, longer will be truncated. Stored in SB3.1 manifest.
# ----------------------------------------------------------------------------------------------------
#                              == Secure Binary v3.1 Commands Settings ==
# ----------------------------------------------------------------------------------------------------
commands: # [Required], SB3.1 Commands; Secure Binary v3.1 commands block, list of all possible options - Modify it according to your application
    # ----------------------------------------------------------------------------------------------------
    #  == List of possible 10 options. Option types[object,object,object,object,object,object,object,object,object,object] ==
    # ----------------------------------------------------------------------------------------------------
  -  # [Example of possible configuration #0]
    erase:  # [Required], Erase; Performs a flash erase of the given address range. The erase will be rounded up to the sector size.
      address: 0  # [Required], Address of memory block to be erased.
      size: 4096 # [Required], Size of memory block to be erased.
      memoryId: 0 # [Optional], Memory ID; ID of memory block to be erased.
  - # [Example of possible configuration #1]
    load:  # [Required], Load; If set, then the data to write immediately follows the range header. The length field contains the actual data length
      address: 0  # [Required], Address of memory block to be loaded.
      memoryId: 0 # [Optional], Memory ID; ID of memory block to be loaded.
      file: my_binary.bin # [Optional], Binary file to be loaded.
      values: 0x1234, 0x5678, 0, 12345678 # [Optional], Binary values delimited by comma to be loaded.
      authentication: cmac # [Optional], Authentication; If authentication is not used, just omit this option or set 'none'; Possible options:['none', 'cmac', 'hashlocking']
  - # [Example of possible configuration #2]
    execute:  # [Required], Execute; Address is the jump-to address. No further processing of SB after jump, ROM do not expect to return.
      address: 0  # [Required], Address; Jump-to address to start execute code.
  - # [Example of possible configuration #3]
    programFuses:  # [Required], Program Fuses; 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.
      address: 0  # [Required], Address; OTP Index of fuses to be programmed. Depends on the chip ROM.
      values: 0x1234, 0x5678, 0, 12345678 # [Required], Binary values; 32bit binary values delimited by comma to be programmed.
  - # [Example of possible configuration #4]
    programIFR:  # [Required], Program IFR; 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
      address: 0  # [Required], Address of IFR region to be programmed.
      file: my_binary.bin # [Required], Binary file to be programmed.
  - # [Example of possible configuration #5]
    loadCMAC:  # [Required], Load CMAC; 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.
      address: 0  # [Required], Address of memory block to be CMAC loaded.
      memoryId: 0 # [Optional], Memory ID; ID of memory block to be CMAC loaded.
      file: my_cmac_binary.bin # [Required], Binary file to be loaded.
  - # [Example of possible configuration #6]
    loadHashLocking:  # [Required], Load with HASH locking; 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.
      address: 0  # [Required], Address of memory block to be loaded.
      memoryId: 0 # [Optional], Memory ID; ID of memory block to be loaded.
      file: my_hashlocking_binary.bin # [Required], Binary file to be loaded.
  - # [Example of possible configuration #7]
    configureMemory:  # [Required], Configure memory.
      configAddress: 0  # [Required], Address; Configuration address.
      memoryId: 0 # [Optional], Memory ID; ID of memory block to be configured.
  - # [Example of possible configuration #8]
    fillMemory:  # [Required], Fill memory; Used for filling of the memory range by same repeated int32 pattern.
      address: 0  # [Required], Address of memory block to be filled.
      size: 4096 # [Required], Size of memory block to be filled.
      pattern: 2779096485 # [Required], Pattern which will be used to fill memory.
  - # [Example of possible configuration #9]
    checkFwVersion:  # [Required], Check firmware version; Used to execute check of provided counter value with value stored in specified monotonous counter in device. If values are not same, SB file is rejected.
      value: 1  # [Required], Value - Firmware version; Firmware version to be compared.
      counterId: secure # [Required], Counter ID; ID of FW counter to be checked; Possible options:['none', 'nonsecure', 'secure', 'radio', 'snt', 'bootloader']

[7]:
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"
    del sb31['binaryCertificateBlock']
    # Choose between two scenarios:

    # # scenario 1
    # # if you want to use ISK:
    # sb31['useIsk'] = True
    # sb31['signingCertificateFile'] = ISK_CERT_PATH
    # sb31['signingCertificatePrivateKeyFile'] = ISK_PRIVATE_KEY_PATH
    # sb31['signingCertificateConstraint'] = 0
    # sb31['iskCertificateEllipticCurve'] = "secp384r1"
    # # [optional, can contain user data
    # del sb31['signCertData']

    # scenario 2
    #if you do not use ISK:
    del sb31['signingCertificatePrivateKeyFile']
    del sb31['signingCertificateFile']
    del sb31['signCertData']
    del sb31['iskCertificateEllipticCurve']

    # 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:
{   '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,
    'iskSignProvider': 'type=file;file_path=my_isk_prv_key.pem',
    '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',
    'signingCertificateConstraint': 0,
    'useIsk': False}

SB3.1 generation

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

[8]:
# Verbosity needs to be at least info (-v) in order to get SB3KDK and RoTKTH values
%! nxpimage $VERBOSITY sb31 export $SB31_TEMPLATE_PATH
assert os.path.exists(WORKSPACE+SB31_FILE_PATH)
nxpimage -v sb31 export workspace/sb31_config.yml
INFO:spsdk.sbfile.sb31.images:SB3KDK: 74cd7710ba864fc92244e6b2bef5f998b2bc3f505c82ad339152dbb8bbaec040
INFO:spsdk.utils.crypto.cert_blocks:RoTKTH: 9af7e81ec9957cea6b4b4f8ede279e417d2a91f07cafdca7679764594933ac338292a8683e30a15644dd1fad84bbfc3a
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.

[9]:
# check if the device is connected and detected by PC
%! nxpdevscan
nxpdevscan
-------- Connected NXP USB Devices --------

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

Port: COM6
Type: mboot device

-------- Connected NXP SIO Devices --------

[10]:
# choose com port
UART_CONNECTION = "-p com6"

%! blhost $UART_CONNECTION get-property current-version
blhost -p com6 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

[11]:
# 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 [[74cd7710ba864fc92244e6b2bef5f998b2bc3f505c82ad339152dbb8bbaec040]]
# program RoTKTH (CUST_PROD_OEMFW_AUTH_PUK)
# put value RoTKTH generated by nxpimage
%! blhost $UART_CONNECTION fuse-program 0x1F [[9af7e81ec9957cea6b4b4f8ede279e417d2a91f07cafdca7679764594933ac338292a8683e30a15644dd1fad84bbfc3a]]
# Set voltage to normal value
%! blhost $UART_CONNECTION set-property 0x16 0
blhost -p com6 set-property 0x16 1
Response status = 0 (0x0) Success.
blhost -p com6 fuse-program 0x20 [[74cd7710ba864fc92244e6b2bef5f998b2bc3f505c82ad339152dbb8bbaec040]]
Response status = 0 (0x0) Success.
Response word 1 = 32 (0x20)
blhost -p com6 fuse-program 0x1F [[9af7e81ec9957cea6b4b4f8ede279e417d2a91f07cafdca7679764594933ac338292a8683e30a15644dd1fad84bbfc3a]]
Response status = 0 (0x0) Success.
Response word 1 = 48 (0x30)
blhost -p com6 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.

[12]:
# 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 com6 receive-sb-file workspace/sb3.sb3
Sending SB file
Response status = 0 (0x0) Success.