Skip to content
This repository has been archived by the owner on Jan 17, 2023. It is now read-only.

Commit

Permalink
Allow creation of sign-less init packets (#111)
Browse files Browse the repository at this point in the history
* WIP: Unit tests for init packet generation

* WIP: Working unit test for init packet serialization

* Allow init_packet_pb.py to create unsigned protobufs

* crypto key not required to generate a dfu .zip. Unsigned dfu .zips are displayed properly.

* linting after @chunfantasy's review

* Add "no key" warning, lint tests
  • Loading branch information
IvanSanchez committed Nov 16, 2017
1 parent 7f6addb commit e4a7c94
Show file tree
Hide file tree
Showing 5 changed files with 206 additions and 45 deletions.
42 changes: 33 additions & 9 deletions nordicsemi/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,25 @@ def display_sec_warning():
"""
click.echo("{}".format(default_key_warning))

def display_nokey_warning():
default_nokey_warning = """
|===============================================================|
|## ## ### ######## ## ## #### ## ## ###### |
|## ## ## ## ## ## ## ### ## ## ### ## ## ## |
|## ## ## ## ## ## ## #### ## ## #### ## ## |
|## ## ## ## ## ######## ## ## ## ## ## ## ## ## ####|
|## ## ## ######### ## ## ## #### ## ## #### ## ## |
|## ## ## ## ## ## ## ## ### ## ## ### ## ## |
| ### ### ## ## ## ## ## ## #### ## ## ###### |
|===============================================================|
|You are not providing a signature key, which means the DFU |
|files will not be signed, and are vulnerable to tampering. |
|This is only compatible with a signature-less bootloader and is|
|not suitable for production environments. |
|===============================================================|
"""
click.echo("{}".format(default_nokey_warning))

def display_debug_warning():
debug_warning = """
|===============================================================|
Expand Down Expand Up @@ -350,15 +369,15 @@ def display(key_file, key, format, out_file):
kfile.write(kstr)


@cli.group(short_help='Generate a Device Firmware Update package.')
@cli.group(short_help='Display or generate a DFU package (zip file).')
def pkg():
"""
This set of commands supports Nordic DFU package generation.
"""
pass


@pkg.command(short_help='Generate a firmware package for over-the-air firmware updates.')
@pkg.command(short_help='Generate a zip file for performing DFU.')
@click.argument('zipfile',
required=True,
type=click.Path())
Expand All @@ -384,6 +403,7 @@ def pkg():
type=BASED_INT_OR_NONE)
@click.option('--hw-version',
help='The hardware version.',
required=True,
type=BASED_INT)
@click.option('--sd-req',
help='The SoftDevice requirements. A comma-separated list of SoftDevice firmware IDs '
Expand All @@ -404,6 +424,7 @@ def pkg():
'\n|s132_nrf52_4.0.4|0x9E|'
'\n|s132_nrf52_5.0.0|0x9D|',
type=click.STRING,
required=True,
multiple=True)
@click.option('--sd-id',
help='The new SoftDevice ID to be used as --sd-req for the Application update in case the ZIP '
Expand All @@ -415,7 +436,7 @@ def pkg():
type=click.STRING)
@click.option('--key-file',
help='The private (signing) key in PEM fomat.',
required=True,
required=False,
type=click.Path(exists=True, resolve_path=True, file_okay=True, dir_okay=False))
def generate(zipfile,
debug_mode,
Expand Down Expand Up @@ -532,7 +553,7 @@ def generate(zipfile,

if application is not None and application_version_internal is None:
click.echo('Error: --application-version or --application-version-string'
'required with application image.')
' required with application image.')
return

if bootloader is not None and bootloader_version is None:
Expand Down Expand Up @@ -573,10 +594,13 @@ def generate(zipfile,
else:
sd_id_list = sd_req_list

signer = Signing()
default_key = signer.load_key(key_file)
if default_key:
display_sec_warning()
if key_file is None:
display_nokey_warning()
else:
signer = Signing()
default_key = signer.load_key(key_file)
if default_key:
display_sec_warning()

package = Package(debug_mode,
hw_version,
Expand Down Expand Up @@ -609,7 +633,7 @@ def update_progress(progress=0):
if global_bar:
global_bar.update(progress)

@cli.group(short_help='Perform a Device Firmware Update over, BLE, Thread, or serial transport.')
@cli.group(short_help='Perform a Device Firmware Update over, BLE, Thread, or serial transport given a DFU package (zip file).')
def dfu():
"""
This set of commands supports Device Firmware Upgrade procedures over both BLE and serial transports.
Expand Down
8 changes: 3 additions & 5 deletions nordicsemi/dfu/dfu-cc.proto
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ enum HashType {
SHA512 = 4;
}

message Hash
{
message Hash {
required HashType hash_type = 1;
required bytes hash = 2;
}
Expand All @@ -45,8 +44,7 @@ message InitCommand {
optional bool is_debug = 9 [default = false];
}

message ResetCommand
{
message ResetCommand {
required uint32 timeout = 1;
}

Expand All @@ -73,4 +71,4 @@ message SignedCommand {
message Packet {
optional Command command = 1;
optional SignedCommand signed_command = 2;
}
}
37 changes: 24 additions & 13 deletions nordicsemi/dfu/init_packet_pb.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,18 +76,27 @@ def __init__(self,
):

if from_bytes is not None:
# construct from a stream
# construct from a protobuf string/buffer
self.packet = pb.Packet()
self.packet.ParseFromString(from_bytes)
self.signed_command = self.packet.signed_command
self.init_command = self.signed_command.command.init

if self.packet.HasField('signed_command'):
self.init_command = self.packet.signed_command.command.init
else:
self.init_command = self.packet.command.init

else:
# construct from input variables
if not sd_req:
sd_req = [0xfffe] # Set to default value
self.packet = pb.Packet()
self.signed_command = self.packet.signed_command
self.init_command = self.signed_command.command.init

# By default, set the packet's command to an unsigned command
# If a signature is set (via set_signature), this will get overwritten
# with an instance of SignedCommand instead.
self.packet.command.op_code = pb.INIT

self.init_command = pb.InitCommand()
self.init_command.hash.hash_type = hash_type.value
self.init_command.type = dfu_type.value
self.init_command.hash.hash = hash_bytes
Expand All @@ -98,7 +107,8 @@ def __init__(self,
self.init_command.sd_size = sd_size
self.init_command.bl_size = bl_size
self.init_command.app_size = app_size
self.signed_command.command.op_code = pb.INIT

self.packet.command.init.CopyFrom(self.init_command)

self._validate()

Expand All @@ -116,7 +126,7 @@ def _validate(self):

if self.init_command.fw_version < 0 or self.init_command.fw_version > 0xffffffff or \
self.init_command.hw_version < 0 or self.init_command.hw_version > 0xffffffff:
raise RuntimeError("Invalid range of firmware argument. [0 - 0xffffff] is valid range")
raise RuntimeError("Invalid range of firmware argument. [0 - 0xffffffff] is valid range")

def _is_valid(self):
try:
Expand All @@ -127,17 +137,18 @@ def _is_valid(self):
return self.signed_command.signature is not None

def get_init_packet_pb_bytes(self):
if self.signed_command.signature is not None:
return self.packet.SerializeToString()
else:
raise RuntimeError("Did not set signature")
return self.packet.SerializeToString()

def get_init_command_bytes(self):
return self.init_command.SerializeToString()

def set_signature(self, signature, signature_type):
self.signed_command.signature = signature
self.signed_command.signature_type = signature_type.value
new_packet = pb.Packet()
new_packet.signed_command.signature = signature
new_packet.signed_command.signature_type = signature_type.value
new_packet.signed_command.command.CopyFrom(self.packet.command)

self.packet = new_packet

def __str__(self):
return str(self.init_command)
45 changes: 27 additions & 18 deletions nordicsemi/dfu/package.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,7 @@ def __init__(self,
filename=softdevice_fw,
init_packet_data=init_packet_vars)

if key_file:
self.key_file = key_file
self.key_file = key_file

self.work_dir = None
self.manifest = None
Expand Down Expand Up @@ -221,6 +220,15 @@ def image_str(self, index, hex_type, img):
if len(sd_req) != 0:
sd_req = sd_req[:-2]

if (initp.packet.HasField('signed_command')):
cmd = initp.packet.signed_command.command
signature_type = SigningTypes(initp.packet.signed_command.signature_type).name
signature_hex = binascii.hexlify(initp.packet.signed_command.signature)
else:
cmd = initp.packet.command
signature_type = 'UNSIGNED'
signature_hex = 'N/A'

s = """|
|- Image #{0}:
|- Type: {1}
Expand Down Expand Up @@ -248,19 +256,19 @@ def image_str(self, index, hex_type, img):
type_strs[hex_type],
img.bin_file,
img.dat_file,
CommandTypes(initp.signed_command.command.op_code).name,
SigningTypes(initp.signed_command.signature_type).name,
binascii.hexlify(initp.signed_command.signature),
initp.init_command.fw_version,
initp.init_command.hw_version,
CommandTypes(cmd.op_code).name,
signature_type,
signature_hex,
cmd.init.fw_version,
cmd.init.hw_version,
sd_req,
DFUType(initp.init_command.type).name,
initp.init_command.sd_size,
initp.init_command.bl_size,
initp.init_command.app_size,
HashTypes(initp.init_command.hash.hash_type).name,
binascii.hexlify(initp.init_command.hash.hash),
initp.init_command.is_debug,
DFUType(cmd.init.type).name,
cmd.init.sd_size,
cmd.init.bl_size,
cmd.init.app_size,
HashTypes(cmd.init.hash.hash_type).name,
binascii.hexlify(cmd.init.hash.hash),
cmd.init.is_debug,
)

return s
Expand Down Expand Up @@ -369,10 +377,11 @@ def generate_package(self, filename, preserve_work_dir=False):
bl_size=bl_size,
sd_req=firmware_data[FirmwareKeys.INIT_PACKET_DATA][PacketField.REQUIRED_SOFTDEVICES_ARRAY])

signer = Signing()
signer.load_key(self.key_file)
signature = signer.sign(init_packet.get_init_command_bytes())
init_packet.set_signature(signature, SigningTypes.ECDSA_P256_SHA256)
if (self.key_file is not None):
signer = Signing()
signer.load_key(self.key_file)
signature = signer.sign(init_packet.get_init_command_bytes())
init_packet.set_signature(signature, SigningTypes.ECDSA_P256_SHA256)

# Store the .dat file in the work directory
init_packet_filename = firmware_data[FirmwareKeys.BIN_FILENAME].replace(".bin", ".dat")
Expand Down
Loading

0 comments on commit e4a7c94

Please sign in to comment.