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

Commit

Permalink
signing: access private key only once (#330)
Browse files Browse the repository at this point in the history
Pass a Signing() object 'signer' to Package() and Package.sign_firmware(),
instead of a 'key_file' string (path).

Access the private key only once, when generating 'signer', which:

1. Fixes a security issue where caller must write private key to disk
instead of passing it purely in memory eg:

    nrfutil --key-file $(secure-key-retrieval)

2. Gives a small speed improvement.

Fixes #327

Signed-off-by: Sirio Balmelli <sirio@b-ad.ch>
  • Loading branch information
siriobalmelli committed Aug 5, 2020
1 parent a0034f2 commit a540fe4
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 48 deletions.
20 changes: 14 additions & 6 deletions nordicsemi/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,9 +373,16 @@ def generate(hex_file,
if bl_settings_version == 1 and (app_boot_validation or sd_boot_validation):
raise click.BadParameter("Bootloader settings version 1 does not support boot validation.", param_hint='bl_settings_version')

if (app_boot_validation == 'VALIDATE_ECDSA_P256_SHA256' and key_file is None) or \
(sd_boot_validation == 'VALIDATE_ECDSA_P256_SHA256' and key_file is None):
raise click.UsageError("Key file must be given when 'VALIDATE_ECDSA_P256_SHA256' boot validation is used")
# load signing key (if needed) only once
if 'VALIDATE_ECDSA_P256_SHA256' in (app_boot_validation, sd_boot_validation):
if not os.path.isfile(key_file):
raise click.UsageError("Key file must be given when 'VALIDATE_ECDSA_P256_SHA256' boot validation is used")
signer = Signing()
default_key = signer.load_key(key_file)
if default_key:
display_sec_warning()
else:
signer = None

if app_boot_validation and not application:
raise click.UsageError("--application hex file must be set when using --app_boot_validation")
Expand All @@ -393,7 +400,7 @@ def generate(hex_file,
sett.generate(arch=family, app_file=application, app_ver=application_version_internal, bl_ver=bootloader_version,
bl_sett_ver=bl_settings_version, custom_bl_sett_addr=start_address, no_backup=no_backup,
backup_address=backup_address, app_boot_validation_type=app_boot_validation,
sd_boot_validation_type=sd_boot_validation, sd_file=softdevice, key_file=key_file)
sd_boot_validation_type=sd_boot_validation, sd_file=softdevice, signer=signer)
sett.tohexfile(hex_file)

click.echo("\nGenerated Bootloader DFU settings .hex file and stored it in: {}".format(hex_file))
Expand Down Expand Up @@ -809,6 +816,7 @@ def generate(zipfile,

if key_file is None:
display_nokey_warning()
signer = None
else:
signer = Signing()
default_key = signer.load_key(key_file)
Expand Down Expand Up @@ -869,7 +877,7 @@ def generate(zipfile,
softdevice,
sd_boot_validation,
app_boot_validation,
key_file,
signer,
inner_external_app,
zigbee,
zigbee_manufacturer_id,
Expand Down Expand Up @@ -903,7 +911,7 @@ def generate(zipfile,
None,
None,
None,
key_file,
signer,
True)

package.generate_package(zipfile_path)
Expand Down
6 changes: 3 additions & 3 deletions nordicsemi/dfu/bl_dfu_sett.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ def _calculate_crc32_from_hex(self, ih_object, start_addr=None, end_addr=None):
return binascii.crc32(bytearray(list)) & 0xFFFFFFFF

def generate(self, arch, app_file, app_ver, bl_ver, bl_sett_ver, custom_bl_sett_addr, no_backup,
backup_address, app_boot_validation_type, sd_boot_validation_type, sd_file, key_file):
backup_address, app_boot_validation_type, sd_boot_validation_type, sd_file, signer):

self.set_arch(arch)

Expand Down Expand Up @@ -217,7 +217,7 @@ def generate(self, arch, app_file, app_ver, bl_ver, bl_sett_ver, custom_bl_sett_
self.app_boot_validation_bytes = Package.calculate_sha256_hash(self.app_bin)[::-1]
elif app_boot_validation_type == 'VALIDATE_ECDSA_P256_SHA256':
self.app_boot_validation_type = 3 & 0xffffffff
self.app_boot_validation_bytes = Package.sign_firmware(key_file, self.app_bin)
self.app_boot_validation_bytes = Package.sign_firmware(signer, self.app_bin)
else: # This also covers 'NO_VALIDATION' case
self.app_boot_validation_type = 0 & 0xffffffff
self.app_boot_validation_bytes = bytes(0)
Expand Down Expand Up @@ -257,7 +257,7 @@ def generate(self, arch, app_file, app_ver, bl_ver, bl_sett_ver, custom_bl_sett_
self.sd_boot_validation_bytes = Package.calculate_sha256_hash(self.sd_bin)[::-1]
elif sd_boot_validation_type == 'VALIDATE_ECDSA_P256_SHA256':
self.sd_boot_validation_type = 3 & 0xffffffff
self.sd_boot_validation_bytes = Package.sign_firmware(key_file, self.sd_bin)
self.sd_boot_validation_bytes = Package.sign_firmware(signer, self.sd_bin)
else: # This also covers 'NO_VALIDATION_CASE'
self.sd_boot_validation_type = 0 & 0xffffffff
self.sd_boot_validation_bytes = bytes(0)
Expand Down
22 changes: 10 additions & 12 deletions nordicsemi/dfu/package.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ def __init__(self,
softdevice_fw=None,
sd_boot_validation=DEFAULT_BOOT_VALIDATION_TYPE,
app_boot_validation=DEFAULT_BOOT_VALIDATION_TYPE,
key_file=None,
signer=None,
is_external=False,
zigbee_format=False,
manufacturer_id=0,
Expand All @@ -148,7 +148,7 @@ def __init__(self,
:param str app_fw: Path to application firmware file
:param str bootloader_fw: Path to bootloader firmware file
:param str softdevice_fw: Path to softdevice firmware file
:param str key_file: Path to Signing key file (PEM)
:param Signing signer: Instance of Signing() for Signing key file (PEM)
:param int zigbee_ota_min_hw_version: Minimal zigbee ota hardware version
:param int zigbee_ota_max_hw_version: Maximum zigbee ota hardware version
:return: None
Expand Down Expand Up @@ -201,7 +201,8 @@ def __init__(self,
boot_validation_type=sd_boot_validation_type,
init_packet_data=init_packet_vars)

self.key_file = key_file
assert(not signer or isinstance(signer, Signing))
self.signer = signer

self.work_dir = None
self.manifest = None
Expand Down Expand Up @@ -436,9 +437,9 @@ def generate_package(self, filename, preserve_work_dir=False):
for x in boot_validation_type_array:
if x == ValidationTypes.VALIDATE_ECDSA_P256_SHA256:
if key == HexType.SD_BL:
boot_validation_bytes_array.append(Package.sign_firmware(self.key_file, sd_bin_path))
boot_validation_bytes_array.append(Package.sign_firmware(self.signer, sd_bin_path))
else:
boot_validation_bytes_array.append(Package.sign_firmware(self.key_file, bin_file_path))
boot_validation_bytes_array.append(Package.sign_firmware(self.signer, bin_file_path))
else:
boot_validation_bytes_array.append(b'')

Expand All @@ -458,10 +459,8 @@ 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])

if (self.key_file is not None):
signer = Signing()
signer.load_key(self.key_file)
signature = signer.sign(init_packet.get_init_command_bytes())
if (self.signer is not None):
signature = self.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
Expand Down Expand Up @@ -573,12 +572,11 @@ def calculate_crc(crc, firmware_filename):
raise ValueError("Invalid CRC type")

@staticmethod
def sign_firmware(key, firmware_filename):
def sign_firmware(signer, firmware_filename):
assert(isinstance(signer, Signing))
data_buffer = b''
with open(firmware_filename, 'rb') as firmware_file:
data_buffer = firmware_file.read()
signer = Signing()
signer.load_key(key)
return signer.sign(data_buffer)

def create_manifest(self):
Expand Down
53 changes: 31 additions & 22 deletions nordicsemi/dfu/tests/test_bl_dfu_sett.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import unittest
from nordicsemi.dfu.bl_dfu_sett import BLDFUSettings
from nordicsemi.dfu.nrfhex import nRFArch
from nordicsemi.dfu.signing import Signing


class TestBLDFUSettingsV1(unittest.TestCase):
Expand Down Expand Up @@ -79,7 +80,7 @@ def test_generate_without_application_file(self):
app_boot_validation_type=None,
sd_boot_validation_type=None,
sd_file=None,
key_file=None)
signer=None)

self.assertEqual(nRFArch.NRF52, settings.arch)
self.assertEqual('nRF52', settings.arch_str)
Expand Down Expand Up @@ -107,7 +108,7 @@ def test_generate_with_application_file(self):
app_boot_validation_type=None,
sd_boot_validation_type=None,
sd_file=None,
key_file=None)
signer=None)

self.assertEqual(nRFArch.NRF52, settings.arch)
self.assertEqual('nRF52', settings.arch_str)
Expand Down Expand Up @@ -135,7 +136,7 @@ def test_generate_with_custom_start_address(self):
app_boot_validation_type=None,
sd_boot_validation_type=None,
sd_file=None,
key_file=None)
signer=None)

self.assertEqual(settings.bl_sett_addr, 0x0006F000)

Expand All @@ -152,7 +153,7 @@ def test_generate_with_backup_page_check_size(self):
app_boot_validation_type=None,
sd_boot_validation_type=None,
sd_file=None,
key_file=None)
signer=None)

settings = BLDFUSettings()
settings.generate(arch='NRF52',
Expand All @@ -166,7 +167,7 @@ def test_generate_with_backup_page_check_size(self):
app_boot_validation_type=None,
sd_boot_validation_type=None,
sd_file=None,
key_file=None)
signer=None)

self.assertEqual(len(list(settings.ihex.todict().keys())), len(list(settings_raw.ihex.todict().keys())) * 2)

Expand All @@ -183,7 +184,7 @@ def test_generate_with_backup_page_check_values(self):
app_boot_validation_type=None,
sd_boot_validation_type=None,
sd_file=None,
key_file=None)
signer=None)

backup_dict = {(k + settings.bl_sett_backup_offset): v for k, v in list(settings.ihex.todict().items()) if k < settings.bl_sett_addr}
settings_dict = {k: v for k, v in list(settings.ihex.todict().items()) if k >= settings.bl_sett_addr}
Expand All @@ -202,7 +203,7 @@ def test_generate_with_backup_page_custom_address(self):
app_boot_validation_type=None,
sd_boot_validation_type=None,
sd_file=None,
key_file=None)
signer=None)

self.assertEqual(settings.backup_address, 0x0006F000)
self.assertTrue(0x0006F000 in list(settings.ihex.todict().keys()))
Expand All @@ -220,7 +221,7 @@ def test_generate_with_backup_page_default_address(self):
app_boot_validation_type=None,
sd_boot_validation_type=None,
sd_file=None,
key_file=None)
signer=None)

self.assertEqual(settings.backup_address, (0x0006F000 - settings.bl_sett_backup_offset))
self.assertTrue((0x0006F000 - settings.bl_sett_backup_offset) in list(settings.ihex.todict().keys()))
Expand Down Expand Up @@ -267,7 +268,7 @@ def test_generate_without_application_or_sd_file(self):
app_boot_validation_type=None,
sd_boot_validation_type=None,
sd_file=None,
key_file=None)
signer=None)

self.assertEqual(nRFArch.NRF52, settings.arch)
self.assertEqual('nRF52', settings.arch_str)
Expand Down Expand Up @@ -301,7 +302,7 @@ def test_generate_with_application_and_sd_file(self):
app_boot_validation_type=None,
sd_boot_validation_type=None,
sd_file='firmwares/s132_nrf52_mini.hex',
key_file=None)
signer=None)

self.assertEqual(nRFArch.NRF52, settings.arch)
self.assertEqual('nRF52', settings.arch_str)
Expand Down Expand Up @@ -335,7 +336,7 @@ def test_generate_with_custom_start_address(self):
app_boot_validation_type=None,
sd_boot_validation_type=None,
sd_file=None,
key_file=None)
signer=None)

self.assertEqual(settings.bl_sett_addr, 0x0006F000)

Expand All @@ -352,7 +353,7 @@ def test_generate_with_backup_page_check_size(self):
app_boot_validation_type=None,
sd_boot_validation_type=None,
sd_file=None,
key_file=None)
signer=None)

settings = BLDFUSettings()
settings.generate(arch='NRF52',
Expand All @@ -366,7 +367,7 @@ def test_generate_with_backup_page_check_size(self):
app_boot_validation_type=None,
sd_boot_validation_type=None,
sd_file=None,
key_file=None)
signer=None)

self.assertEqual(len(list(settings.ihex.todict().keys())), len(list(settings_raw.ihex.todict().keys())) * 2)

Expand All @@ -383,7 +384,7 @@ def test_generate_with_backup_page_check_values(self):
app_boot_validation_type=None,
sd_boot_validation_type=None,
sd_file=None,
key_file=None)
signer=None)

backup_dict = {(k + settings.bl_sett_backup_offset): v for k, v in list(settings.ihex.todict().items()) if k < settings.bl_sett_addr}
settings_dict = {k: v for k, v in list(settings.ihex.todict().items()) if k >= settings.bl_sett_addr}
Expand All @@ -402,7 +403,7 @@ def test_generate_with_backup_page_custom_address(self):
app_boot_validation_type=None,
sd_boot_validation_type=None,
sd_file=None,
key_file=None)
signer=None)

self.assertEqual(settings.backup_address, 0x0006F000)
self.assertTrue(0x0006F000 in list(settings.ihex.todict().keys()))
Expand All @@ -420,7 +421,7 @@ def test_generate_with_backup_page_default_address(self):
app_boot_validation_type=None,
sd_boot_validation_type=None,
sd_file=None,
key_file=None)
signer=None)

self.assertEqual(settings.backup_address, (0x0006F000 - settings.bl_sett_backup_offset))
self.assertTrue((0x0006F000 - settings.bl_sett_backup_offset) in list(settings.ihex.todict().keys()))
Expand All @@ -438,7 +439,7 @@ def test_generate_with_app_boot_validation_crc(self):
app_boot_validation_type='VALIDATE_GENERATED_CRC',
sd_boot_validation_type=None,
sd_file=None,
key_file=None)
signer=None)

self.assertEqual(0x1316CFD0, settings.crc)
self.assertEqual(0x49A0F45A, settings.boot_validation_crc)
Expand All @@ -458,7 +459,7 @@ def test_generate_with_app_boot_validation_sha256(self):
app_boot_validation_type='VALIDATE_GENERATED_SHA256',
sd_boot_validation_type=None,
sd_file=None,
key_file=None)
signer=None)

self.assertEqual(0x1316CFD0, settings.crc)
self.assertEqual(0xF78E451E, settings.boot_validation_crc)
Expand All @@ -468,6 +469,10 @@ def test_generate_with_app_boot_validation_sha256(self):

def test_generate_with_app_boot_validation_ecdsa(self):
settings = BLDFUSettings()

signer = Signing()
signer.load_key('key.pem')

settings.generate(arch='NRF52',
app_file='firmwares/s132_nrf52_mini.hex',
app_ver=1,
Expand All @@ -479,7 +484,7 @@ def test_generate_with_app_boot_validation_ecdsa(self):
app_boot_validation_type='VALIDATE_ECDSA_P256_SHA256',
sd_boot_validation_type=None,
sd_file=None,
key_file='key.pem')
signer=signer)

# Since ECDSA contains a random component the signature will be different every time
# it is generated. Therefore only overall structure of the boot validation will be checked.
Expand All @@ -499,7 +504,7 @@ def test_generate_with_sd_boot_validation_crc(self):
app_boot_validation_type=None,
sd_boot_validation_type='VALIDATE_GENERATED_CRC',
sd_file='firmwares/s132_nrf52_mini.hex',
key_file=None)
signer=None)

self.assertEqual(0x4637019F, settings.crc)
self.assertEqual(0xCB5F90FB, settings.boot_validation_crc)
Expand All @@ -519,7 +524,7 @@ def test_generate_with_sd_boot_validation_sha256(self):
app_boot_validation_type=None,
sd_boot_validation_type='VALIDATE_GENERATED_SHA256',
sd_file='firmwares/s132_nrf52_mini.hex',
key_file=None)
signer=None)

self.assertEqual(0x4637019F, settings.crc)
self.assertEqual(0x9C761426, settings.boot_validation_crc)
Expand All @@ -529,6 +534,10 @@ def test_generate_with_sd_boot_validation_sha256(self):

def test_generate_with_sd_boot_validation_ecdsa(self):
settings = BLDFUSettings()

signer = Signing()
signer.load_key('key.pem')

settings.generate(arch='NRF52',
app_file=None,
app_ver=1,
Expand All @@ -540,7 +549,7 @@ def test_generate_with_sd_boot_validation_ecdsa(self):
app_boot_validation_type=None,
sd_boot_validation_type='VALIDATE_ECDSA_P256_SHA256',
sd_file='firmwares/s132_nrf52_mini.hex',
key_file='key.pem')
signer=signer)

# Since ECDSA contains a random component the signature will be different every time
# it is generated. Therefore only overall structure of the boot validation will be checked.
Expand Down
Loading

0 comments on commit a540fe4

Please sign in to comment.