Skip to content

Commit

Permalink
Update to version 0.2.1
Browse files Browse the repository at this point in the history
  • Loading branch information
Maria Wisniewska committed Sep 18, 2020
1 parent 285637a commit b14199e
Show file tree
Hide file tree
Showing 26 changed files with 423 additions and 95 deletions.
2 changes: 1 addition & 1 deletion spsdk/__version__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@
Having the version in a separate file makes it easier to share it with setup.py
"""

__version__ = "0.2.0"
__version__ = "0.2.1"
19 changes: 19 additions & 0 deletions spsdk/apps/elftosb_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
#
# Copyright 2020 NXP
#
# SPDX-License-Identifier: BSD-3-Clause

"""Module for parsing original elf2sb configuration files."""

class RootOfTrustInfo:
"""Filters out Root Of Trust information given to elf2sb application."""
def __init__(self, data: dict) -> None:
"""Create object out of data loaded from elf2sb configuration file."""
self.config_data = data
self.private_key = data["mainCertPrivateKeyFile"]
self.public_keys = [data.get(f"rootCertificate{idx}File") for idx in range(4)]
# filter out None and empty values
self.public_keys = list(filter(None, self.public_keys))
self.public_key_index = self.config_data["mainCertChainId"]
12 changes: 6 additions & 6 deletions spsdk/apps/nxpdebugmbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@
"""Main Debug Authentication Tool application."""

import logging
import struct
import sys

import click
import struct

from spsdk import __version__ as spsdk_version
from spsdk.apps.utils import INT
from spsdk.dat import dm_commands, DebugCredential, DebugAuthenticationChallenge
from spsdk.dat import dar_packet
from spsdk.dat import (DebugAuthenticationChallenge, DebugCredential,
dar_packet, dm_commands)
from spsdk.dat.debug_mailbox import DebugMailbox

logger = logging.getLogger("DebugMBox")
Expand Down Expand Up @@ -61,9 +61,9 @@ def main(ctx: click.Context, interface: str, protocol: str, log_level: str, timi


@main.command()
@click.option('-b', '--beacon', type=INT())
@click.option('-c', '--certificate')
@click.option('-k', '--key')
@click.option('-b', '--beacon', type=INT(), help='Authentication beacon')
@click.option('-c', '--certificate', help='Path to Debug Credentials.')
@click.option('-k', '--key', help='Path to DCK private key.')
@click.option('-f', '--force', is_flag=True, default=True)
@click.pass_obj
def auth(pass_obj: dict, beacon: int, certificate: str, key: str, force: bool) -> None:
Expand Down
84 changes: 64 additions & 20 deletions spsdk/apps/nxpkeygen.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
# SPDX-License-Identifier: BSD-3-Clause

"""Module is used to generate public/private key and generating debug credential file."""
import json
import logging
import os
import sys
Expand All @@ -17,7 +18,9 @@
from spsdk import __version__ as version
from spsdk.crypto import generate_rsa_private_key, generate_rsa_public_key, save_rsa_private_key, save_rsa_public_key, \
generate_ecc_public_key, generate_ecc_private_key, save_ecc_public_key, save_ecc_private_key
from spsdk.dat import DebugCredentialECC, DebugCredentialRSA
from spsdk.dat import DebugCredential

from .elftosb_helper import RootOfTrustInfo

logger = logging.getLogger(__name__)
LOG_LEVEL_NAMES = [name.lower() for name in logging._nameToLevel]
Expand Down Expand Up @@ -79,20 +82,55 @@ def determine_protocol_version(protocol: str) -> Tuple[bool, List[str]]:
return is_rsa, protocol_version


def check_destination_dir(path: str, create_folder: bool = False) -> None:
"""Checks path's destination dir, optionally create the destination folder.
:param path: Path to file to create/consider
:param create_folder: Create destination folder
"""
dest_dir = os.path.dirname(path)
if not dest_dir:
return
if create_folder:
os.makedirs(dest_dir, exist_ok=True)
return
if not os.path.isdir(dest_dir):
click.echo(f"Can't create '{path}', folder '{dest_dir}' doesn't exit.")
sys.exit(1)


def check_file_exists(path: str, force_overwrite: bool = False) -> bool: #type: ignore
"""Check if file exists, exits if file exists and overwriting is disabled.
:param path: Path to a file
:param force_overwrite: allows file overwriting
:return: if file overwriting is allowed, it return True if file exists
"""
if force_overwrite:
return os.path.isfile(path)
if os.path.isfile(path) and not force_overwrite:
click.echo(f"File '{path}' already exists. Use --force to overwrite it.")
sys.exit(1)


@main.command()
@click.option('--password', 'password', metavar='PASSWORD', help='Password with which the output file will be '
'encrypted. If not provided, the output will be '
'unencrypted.')
@click.argument('path', type=click.Path())
@click.argument('path', type=click.Path(file_okay=True))
@click.option('--force', is_flag=True, default=False,
help="Force overwritting of an existing file. Create destination folder, if doesn't exist already.")
@click.pass_context
def genkey(ctx: click.Context, path: str, password: str) -> None:
def genkey(ctx: click.Context, path: str, password: str, force: bool) -> None:
"""Generate key pair for RoT or DCK.
\b
PATH_WHERE_TO_SAVE_KEYS - path where the key pairs will be stored
PATH - path where the key pairs will be stored
"""
is_rsa = ctx.obj['is_rsa']
key_param = ctx.obj['key_param']
check_destination_dir(path, force)
check_file_exists(path, force)

if is_rsa:
logger.info("Generating RSA private key...")
Expand All @@ -101,40 +139,46 @@ def genkey(ctx: click.Context, path: str, password: str) -> None:
pub_key_rsa = generate_rsa_public_key(priv_key_rsa)
logger.info("Saving RSA key pair...")
save_rsa_private_key(priv_key_rsa, path, password if password else None)
save_rsa_public_key(pub_key_rsa, path[:-3] + 'pub')
save_rsa_public_key(pub_key_rsa, os.path.splitext(path)[0] + '.pub')
else:
logger.info("Generating ECC private key...")
priv_key_ec = generate_ecc_private_key(curve_name=key_param)
logger.info("Generating ECC public key...")
pub_key_ec = generate_ecc_public_key(priv_key_ec)
logger.info("Saving ECC key pair...")
save_ecc_private_key(priv_key_ec, path, password if password else None)
save_ecc_public_key(pub_key_ec, path[:-3] + 'pub')
save_ecc_public_key(pub_key_ec, os.path.splitext(path)[0] + '.pub')


@main.command()
@click.option('-c', '--config', metavar='PATH', help='Specify YAML credential config file.')
@click.argument('dc_file_path', metavar='PATH', type=click.Path())
@click.option('-c', '--config', type=click.File('r'), required=True,
help='Specify YAML credential config file.')
@click.option('-e', '--elf2sb-config', type=click.File('r'), required=False,
help='Specify Root Of Trust from configuration file used by elf2sb tool')
@click.option('--force', is_flag=True, default=False,
help="Force overwritting of an existing file. Create destination folder, if doesn't exist already.")
@click.argument('dc_file_path', metavar='PATH', type=click.Path(file_okay=True))
@click.pass_context
def gendc(ctx: click.Context, dc_file_path: str, config: str) -> None:
def gendc(ctx: click.Context, dc_file_path: str, config: click.File, elf2sb_config: click.File, force: bool) -> None:
"""Generate debug certificate (DC).
\b
PATH_TO_DC_FILE - path to dc file
PATH - path to dc file
"""
is_rsa = ctx.obj['is_rsa']
protocol = ctx.obj['protocol_version']
assert os.path.isdir(os.path.dirname(config)), \
f"The target directory '{os.path.dirname(config)}' does not exist."
check_destination_dir(dc_file_path, force)
check_file_exists(dc_file_path, force)

logger.info("Loading configuration from yml file...")
with open(config, 'r') as stream:
yaml_content = yaml.safe_load(stream)
if is_rsa:
logger.info("Creating debug credential RSA object from yml file...")
dc = DebugCredentialRSA.from_yaml_config(version=protocol, yaml_config=yaml_content)
else:
logger.info("Creating debug credential ECC object from yml file...")
dc = DebugCredentialECC.from_yaml_config(version=protocol, yaml_config=yaml_content)
yaml_content = yaml.safe_load(config) #type: ignore
if elf2sb_config:
logger.info("Loading configuration from elf2sb config file...")
rot_info = RootOfTrustInfo(json.load(elf2sb_config)) #type: ignore
yaml_content["rot_meta"] = rot_info.public_keys
yaml_content["rotk"] = rot_info.private_key
logger.info(f"Creating {'RSA' if is_rsa else 'ECC'} debug credential object...")
dc = DebugCredential.from_yaml_config(version=protocol, yaml_config=yaml_content)
data = dc.export()
logger.info("Saving the debug credential to a file...")
with open(dc_file_path, 'wb') as f:
Expand Down
61 changes: 39 additions & 22 deletions spsdk/apps/pfr.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,20 @@
import json
import sys
# no_type_check decorator is used to suppress mypy's confusion in Click and cryptography libraries
from typing import Iterable, List, Mapping, Optional, Type, Union, Tuple, no_type_check
from typing import (Iterable, List, Mapping, Optional, Tuple, Type, Union,
no_type_check)

import click
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey

from cryptography.x509 import load_pem_x509_certificate
from jinja2 import Environment, FileSystemLoader

from spsdk import crypto
from spsdk import SPSDK_DATA_FOLDER
from spsdk import __version__ as spsdk_version
from spsdk import crypto
from spsdk.image import pfr
from spsdk.image.misc import dict_diff

from .elftosb_helper import RootOfTrustInfo

HTMLDataElement = Mapping[str, Union[str, Iterable[dict]]]
HTMLData = List[HTMLDataElement]
PFRArea = Union[Type[pfr.CMPA], Type[pfr.CFPA]]
Expand Down Expand Up @@ -67,7 +66,7 @@ def _extract_public_key(file_path: str, password: Optional[str]) -> crypto.RSAPu


@no_type_check
def _extract_public_keys(secret_files: Tuple[str], password: Optional[str]) -> List[RSAPublicKey]:
def _extract_public_keys(secret_files: Tuple[str], password: Optional[str]) -> List[crypto.RSAPublicKey]:
"""Extract RSAPublic key from a file that contains Certificate, Private Key o Public Key."""
return [
_extract_public_key(file_path=source, password=password)
Expand Down Expand Up @@ -136,7 +135,13 @@ def user_config(device: str, revision: str, area: str, output: click.Path, full:
"""Generate user configuration."""
pfr_obj = _get_pfr_class(area)(device=device, revision=revision)
data = pfr_obj.generate_config(not full)
json_data = json.dumps(data, indent=2)
config = {
'device': pfr_obj.device,
'revision': pfr_obj.revision,
'type': area,
'settings': data
}
json_data = json.dumps(config, indent=2)
_store_output(json_data, output)


Expand All @@ -160,35 +165,47 @@ def parse(device: str, revision: str, area: str, output: click.Path, binary: cli
parsed = dict_diff(
pfr_obj.generate_config(exclude_computed=not show_calc),
parsed)
json_data = json.dumps(parsed, indent=2)
config = {
'device': pfr_obj.device,
'revision': pfr_obj.revision,
'type': area,
'settings': parsed
}
json_data = json.dumps(config, indent=2)
_store_output(json_data, output)


@main.command()
@click.option('-d', '--device', type=click.Choice(pfr.CMPA.devices()), help="Device to use", required=True)
@click.option('-r', '--revision', help="Chip revision; if not specivfied, most recent one will be used")
@click.option('-t', '--type', 'area', required=True, type=click.Choice(['cmpa', 'cfpa']),
help='Select PFR partition')
@click.option('-o', '--output', type=click.Path(), required=True,
help="Save the output into a file instead of console")
@click.option('-c', '--user-config', 'user_config_file', type=click.File('r'), required=True,
help="JSON file with user configuration")
@click.option('-o', '--output', type=click.Path(), required=True,
help="Save the output into a file instead of console")
@click.option('-a', '--add-hash', is_flag=True,
help="Add HASH digest at the end. CAUTION!!! It locks the device")
@click.option('-i', '--calc-inverse', is_flag=True,
help="Calculate the INVERSE values CAUTION!!! It locks the settings")
@click.option('-e', '--elf2sb-config', type=click.File('r'), required=False,
help='Specify Root Of Trust from configuration file used by elf2sb tool')
@click.option('-f', '--secret-file', type=click.Path(exists=True), multiple=True, required=False,
help="Secret file (certificate, public key, private key); can be defined multiple times")
@click.option('-p', '--password', help="Password when using Encrypted private keys as --secret-file")
def generate(device: str, revision: str, area: str, output: click.Path, user_config_file: click.File,
add_hash: bool, calc_inverse: bool, secret_file: Tuple[str], password: str) -> None:
def generate(output: click.Path, user_config_file: click.File, add_hash: bool, calc_inverse: bool,
elf2sb_config: click.File, secret_file: Tuple[str], password: str) -> None:
"""Generate binary data."""
if area == 'cmpa' and not secret_file:
click.echo('Error: CMPA page requires --secret-file(s)')
user_config = _load_user_config(user_config_file)
root_of_trust = None
if elf2sb_config:
keys = RootOfTrustInfo(json.load(elf2sb_config)).public_keys #type: ignore
root_of_trust = tuple(keys)
if secret_file:
root_of_trust = secret_file
area = user_config['type']
pfr_obj = _get_pfr_class(area)(device=user_config['device'], revision=user_config['revision'])
if area == 'cmpa' and not root_of_trust:
click.echo('Error: CMPA page requires either --secret-file(s) or --elf2sb-config')
sys.exit(1)
pfr_obj = _get_pfr_class(area)(device=device, revision=revision)
pfr_obj.keys = _extract_public_keys(secret_file, password)
pfr_obj.user_config = _load_user_config(user_config_file)
pfr_obj.keys = _extract_public_keys(root_of_trust, password) if area == 'cmpa' else None
pfr_obj.user_config = user_config['settings']
data = pfr_obj.export(add_hash=add_hash, compute_inverses=calc_inverse)
_store_output(data, output, 'wb')

Expand Down
3 changes: 1 addition & 2 deletions spsdk/image/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -725,8 +725,7 @@ def parse(cls, data: bytes, offset: int = 0) -> 'CmdUnlockAbstract':
return CmdUnlockCAAM(features)
if engine == EnumEngine.OCOTP:
return CmdUnlockOCOTP(features, uid)
else:
return CmdUnlock(engine, features, uid)
return CmdUnlock(engine, features, uid)

def export(self, dbg_info: DebugInfo = DebugInfo.disabled()) -> bytes:
"""Export to binary form (serialization).
Expand Down
8 changes: 8 additions & 0 deletions tests/dat/data/dck.pub
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEA1he46CuxkrKa+P+Nu4t796KAyDJLQDQHRCDK2GoOZPjxpKfBTfZL
cDA94hYyLD3WNKkXauz2rN1YpYxyXuSeSow1IfxW5HtZyIq4aOtlURDie0kR35Gw
H9B0wpmgj/Ms3+8plGbtUHmjs50GSXP5LHU+BzxyCosLG7+YJusYfUcRisalox55
e56F7MFxvA6XVgDWe7Nk2Ydrh0svN7Pzj1lm6yzkxogQ7yyRgLcXdTunzIN0HBta
RZZY05PTX3Li2tCTPeFB0cARr8qS/gtKB7G1kuj2utO5ZBGQ84kE1m45fsbEyOP1
aNpT5y9OpSy3oIJCNfzHi5JiOqWrlpDeyQIDAQAB
-----END RSA PUBLIC KEY-----
21 changes: 21 additions & 0 deletions tests/dat/data/elf2sb_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"family": "rt5xx",
"inputImageFile": "input_images/rt500_hello_world_plain.bin",
"imageLinkAddress": "0x08001000",
"imageLinkAdressFromImage": false,
"outputImageExecutionTarget": "External flash (XIP)",
"outputImageAuthenticationType": "Signed",
"deviceKeySource": "",
"outputImageEncryptionKeyFile": "",
"enableTrustZone": false,
"trustZonePresetFile": "",
"useKeyStore": false,
"keyStoreFile": "",
"rootCertificate0File": "p0_cert0_2048.pub",
"rootCertificate1File": "p1_cert0_2048.pub",
"rootCertificate2File": "p2_cert0_2048.pub",
"rootCertificate3File": "p3_cert0_2048.pub",
"mainCertChainId": 0,
"mainCertPrivateKeyFile": "k0_cert0_2048.pem",
"masterBootOutputFile": "output_images/application_signed.bin"
}
28 changes: 28 additions & 0 deletions tests/dat/data/k0_cert0_2048.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCku4PI1IBXyIh3
clvkoTWYqWhD93aHDGkboSTcqnkQQcKN9Dg+oFiZJ8h4NIlni5R+f2ayETeizOrp
h/A41ZT2Zcg+a+ts72BotAlTwNngBE4RGJkB2hk0PtpAKjAAf8bXrmTTXQDz4Bg0
zteNPjeh/Fuwqs6BmrQjQUcnNVHd+pTh49Ev7bl5vIzFdsJqrotk6c4aQnFqtsO0
Zto5CG7wEanuRYrOMhBcw0XqqdzpO+1vbXgk3+o7ioBgME1Pnfe1ce6kBh5l/fyv
8KdOttAdlqqzPrjDZArQr9iAW7ZzSocSZPxsmC2V2tbHXrbH1uVT/H88yf6UFHYQ
A2X17Hl/AgMBAAECggEAbB/IOCGCvBubtwsQ1dgaXcGT9kiPO8UhmEkE8PHT1J/V
G2eZI0IL5Tr/kiapqZUsOntU5Lv4UJs/9ViMjEFkLPZRoOck97OHDDJfjOGgIDGz
K/WBOH323RwEFOmb6Df2Q8rr0u/QmEIWoVLCmKqlyWTiqery8I6ifiFymoGc4p1w
6Y6+PkGjFxrK93EIIx1Jp9iz//tHooPQ8R9uU9A53KutsQe0MquYTfZi92rTwQvr
BhuPIpBN7yDZP244/o31NOzQjfomNlluGjnPLmNofJE8zi8SJfoGn0/f95safCRQ
nQuQUcOptOrVVGA9vKrmRvrFbgHU4DRWscNYrNp+AQKBgQDZO6I+hJOEgMkGnrSm
SUifuZl+e1pZAyRscx2bJhKIEjoayWVJUC8e3usl5E44eZnHVGbscE9mp085p/AX
jmvCXy6Ccemz4V9XZ4eBf7cFOFm1RcE2P6B5ZCsvT15Y6GrsoEnJ5oUk99nkzAq8
zw1STawWTNIkAjyzBsgTuqAHnwKBgQDCIWDOcCBB3tNsJJeMg3FdwqDwljLAGc28
35Z5L9IpEC4+Rh+YCI7ye2yFQdYTrZOCH79WWHEu+qX37Fbq4g2rQBCjN21gR2Un
VQcoVKxWrdtx4lmoSYMHZNymGYwWm5d+QJ1Iudm86d4Ime6aDkF8ov41XvOxzaJN
Wt79lFvCIQKBgQCIDpCcpX6bc+n0inxM1gN1ftKDZJD+xTgP8L2vSdY7gWcBFfip
RV5t8GLJNchEGO1W6icYmXMxsUKust9ucZZOhDzmGKCuOE71uHMnia1AyL1vCsRr
zMgen71ogUZvWwp1MCNnIEluEQpZAe8LuIb4cIuC4BSR1xDbdDjmGnJWswKBgGbB
Bh2fCePzztLB95l/hYUMXOWbitdVkSm060/P+RyVHPUHZvexKAC/RayvMWIPETHi
HgPVImusbibxaPxAlN2dNnE+CF3azHbqMbSuRN5IfgwktDI4XuuN/qDIivb4elJw
XxA8lzzASS8iU0Il45HWMFoNnU3yu0LYo4lzerIhAoGAdc7Op5SNGJdzLo6ynUab
HAIzaYcEw2uUU+GGjNnzflxko0tTcbj35D/xtXBMG036JwfbK5t6A1JLE1XPt2op
kzFj/QN4CyUG7Twjt+CKGmSgPb+qYZ6IKjoiOOh1bN9ozdjbaf6cgK6Nwrwgfmu7
VJhEBdSKjs9m0yMF8YIVSs4=
-----END PRIVATE KEY-----
6 changes: 6 additions & 0 deletions tests/dat/data/no_key_dck_rsa_2048.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
socc: 0x0001
uuid: "E004090E6BDD2155BBCE9E0665805BE3"
cc_socu: 0x03FF
cc_vu: 0x5678
cc_beacon: 0
dck: ./dck.pub
Loading

0 comments on commit b14199e

Please sign in to comment.