def entry_point(argv=sys.argv[1:]): # pylint: disable=dangerous-default-value parser = get_parser() args = parser.parse_args(argv) logging.basicConfig( stream=sys.stdout, format='%(asctime)s %(levelname)s %(message)s', level=logging.ERROR if args.quiet else logging.DEBUG ) try: action = Actions(args.action) if action == Actions.PARSE: ParseAction.entry_point(args) elif action == Actions.CREATE: CreateAction.entry_point(args, ManifestAsnCodecV3) elif action == Actions.CREATE_V1: CreateAction.entry_point(args, ManifestAsnCodecV1) elif action == Actions.SCHEMA: PrintSchemaAction.entry_point(args) elif action == Actions.PUB_KEY: PublicKeyAction.entry_point(args) else: # will never get here raise AssertionError('Invalid action') except Exception as ex: # pylint: disable=broad-except logger.error( str(ex), exc_info=args.debug ) return 1 return 0
def get_parser(): parser = argparse.ArgumentParser( description='Manifest tool for creating, signing and verifying ' 'Pelion Device management update certificates') parser.add_argument('--version', action='version', version='Manifest-Tool version {}'.format(__version__)) parser.add_argument('-q', '--quiet', action='store_true', help='Suppress information prints. Error prints only.') parser.add_argument('--debug', action='store_true', help='Show exception info on error.') actions_parser = parser.add_subparsers(dest='action') actions_parser.required = True create_parser = actions_parser.add_parser( Actions.CREATE.value, help='Create a Pelion Device management update certificate', add_help=False) CreateAction.register_parser_args(create_parser, ManifestAsnCodecV3.get_name()) create_parser = actions_parser.add_parser( Actions.CREATE_V1.value, help='Create a Pelion Device management update certificate', add_help=False) CreateAction.register_parser_args(create_parser, ManifestAsnCodecV1.get_name()) verify_parser = actions_parser.add_parser( Actions.PARSE.value, help='Parse and verify Pelion Device management update certificate', add_help=False) ParseAction.register_parser_args(verify_parser) schema_parser = actions_parser.add_parser( Actions.SCHEMA.value, help='Print input validation schema') PrintSchemaAction.register_parser_args(schema_parser) schema_parser = actions_parser.add_parser( Actions.PUB_KEY.value, help='Get Uncompressed Public key in binary form') PublicKeyAction.register_parser_args(schema_parser) return parser
def create_dev_manifest(dev_cfg: dict, manifest_version: Type[ManifestAsnCodecBase], vendor_data_path: Path, payload_path: Path, payload_url: str, encrypted_digest: str, encrypted_size: int, priority: int, fw_version: str, sign_image: bool, component: str, combined_package: bool): key_file = Path(dev_cfg['key_file']) if not key_file.is_file(): raise AssertionError('{} not found'.format(key_file.as_posix())) key_data = key_file.read_bytes() payload_file = payload_path delta_meta_file = payload_file.with_suffix('.yaml') payload_format = \ set_payload_format(delta_meta_file, combined_package, encrypted_digest) input_cfg = { 'vendor': { 'vendor-id': dev_cfg['vendor-id'] }, 'device': { 'class-id': dev_cfg['class-id'] }, 'payload': { 'url': payload_url, 'file-path': payload_path.as_posix(), 'format': payload_format.value } } if encrypted_digest: input_cfg['payload']['encrypted'] = { 'digest': encrypted_digest, 'size': encrypted_size } if priority is not None: input_cfg['priority'] = priority if manifest_version.get_name() != 'v1': input_cfg['sign-image'] = sign_image input_cfg['component'] = component if vendor_data_path: input_cfg['vendor']['custom-data-path'] = vendor_data_path.as_posix() # logger.debug('input_cfg=\n%s', yaml.dump(input_cfg)) manifest_bin = CreateAction.do_create(pem_key_data=key_data, input_cfg=input_cfg, fw_version=fw_version, update_certificate=Path( dev_cfg['certificate']), asn1_codec_class=manifest_version) logger.info('Created manifest in %s schema for %s update campaign', manifest_version.get_name(), 'delta' if payload_format == PayloadFormat.PATCH else 'full') return manifest_bin
def create_dev_manifest(dev_cfg: dict, manifest_version: Type[ManifestAsnCodecBase], vendor_data_path: Path, payload_path: Path, payload_url: str, priority: int, fw_version: str, sign_image: bool, component: str): key_file = Path(dev_cfg['key_file']) if not key_file.is_file(): raise AssertionError('{} not found'.format(key_file.as_posix())) key_data = key_file.read_bytes() payload_file = payload_path delta_meta_file = payload_file.with_suffix('.yaml') if delta_meta_file.is_file(): payload_format = PayloadFormat.PATCH else: payload_format = PayloadFormat.RAW input_cfg = { 'vendor': { 'vendor-id': dev_cfg['vendor-id'] }, 'device': { 'class-id': dev_cfg['class-id'] }, 'priority': priority, 'payload': { 'url': payload_url, 'file-path': payload_path.as_posix(), 'format': payload_format.value } } if manifest_version.get_name() != 'v1': input_cfg['sign-image'] = sign_image input_cfg['component'] = component if vendor_data_path: input_cfg['vendor']['custom-data-path'] = vendor_data_path.as_posix() manifest_bin = CreateAction.do_create(pem_key_data=key_data, input_cfg=input_cfg, fw_version=fw_version, update_certificate=Path( dev_cfg['certificate']), asn1_codec_class=manifest_version) return manifest_bin
def get_parser(): parser = argparse.ArgumentParser( description='Tool for creating, signing and verifying ' 'manifest files for running Pelion Device ' 'management update campaigns.', add_help=False ) parser.add_argument( '-h', '--help', action='help', help='Show this help message and exit.' ) parser.add_argument( '--version', action='version', version='Manifest-Tool version {}'.format(__version__), help='Show program\'s version number and exit.' ) parser.add_argument( '-q', '--quiet', action='store_true', help='Print error logs only.' ) parser.add_argument( '--debug', action='store_true', help='Print exception info upon exiting.' ) actions_parser = parser.add_subparsers(title='Commands', dest='action') actions_parser.required = True create_parser = actions_parser.add_parser( Actions.CREATE.value, help='Create a manifest.', description='Create a manifest.', add_help=False ) CreateAction.register_parser_args( create_parser, ManifestAsnCodecV3.get_name()) create_parser = actions_parser.add_parser( Actions.CREATE_V1.value, help='Create a V1 schema manifest.', description='Create a V1 schema manifest.', add_help=False ) CreateAction.register_parser_args( create_parser, ManifestAsnCodecV1.get_name()) verify_parser = actions_parser.add_parser( Actions.PARSE.value, help='Parse and verify a manifest against the input ' 'validation schema.', description='Parse and verify a manifest against the input ' 'validation schema.', add_help=False ) ParseAction.register_parser_args(verify_parser) schema_parser = actions_parser.add_parser( Actions.SCHEMA.value, help='Print the input validation schema.', description='Print the input validation schema.', add_help=False ) PrintSchemaAction.register_parser_args(schema_parser) public_key_parser = actions_parser.add_parser( Actions.PUB_KEY.value, help='Create a public key file containing a key in ' 'uncompressed point format.', description='Create a public key file containing a key in ' 'uncompressed point format.', add_help=False ) PublicKeyAction.register_parser_args(public_key_parser) return parser
def happy_day_data( tmp_path_factory: TempPathFactory, request ): tmp_path = tmp_path_factory.mktemp("data") key_file = tmp_path / 'dev.key.pem' certificate_file = tmp_path / 'dev.cert.der' manifest_version = request.param # Type[ManifestAsnCodecBase] generate_credentials( key_file=key_file, certificate_file=certificate_file, do_overwrite=False, cred_valid_time=8 ) fw_file = tmp_path / 'fw.bin' fw_file.write_bytes(os.urandom(512)) input_cfg = { "manifest-version": manifest_version.get_name(), "vendor": { "domain": "arm.com", "custom-data-path": fw_file.as_posix() }, "device": { "model-name": "my-device" }, "priority": 15, "payload": { "url": "https://my.server.com/some.file?new=1", "file-path": fw_file.as_posix(), "format": "raw-binary" } } fw_version = '100.500.0' if 'v1' == manifest_version.get_name(): fw_version = 0 else: input_cfg['sign-image'] = True manifest_data = CreateAction.do_create( pem_key_data=key_file.read_bytes(), input_cfg=input_cfg, fw_version=fw_version, update_certificate=certificate_file, asn1_codec_class=manifest_version ) manifest_file = tmp_path / 'fota_manifest.bin' manifest_file.write_bytes(manifest_data) private_key = serialization.load_pem_private_key( key_file.read_bytes(), password=None, backend=default_backend() ) public_key = private_key.public_key() public_key_bytes = public_key.public_bytes( encoding=serialization.Encoding.X962, format=serialization.PublicFormat.UncompressedPoint ) public_key_file = tmp_path / 'pub_key.bin' public_key_file.write_bytes(public_key_bytes) return { 'manifest_file': manifest_file, 'certificate_file': certificate_file, 'pub_key_file': public_key_file, 'priv_key_file': key_file, 'manifest_version': manifest_version.get_name(), }
def test_create_happy_day_full(tmp_path_factory, fw_size): global FILE_ID GEN_DIR.mkdir(exist_ok=True) happy_day_data = data_generator(tmp_path_factory, fw_size) manifest = None for manifest_codec in ManifestVersion.list_codecs(): input_cfg = { 'vendor': { 'domain': 'arm.com' }, 'device': { 'model-name': 'my-device' }, 'priority': 15, 'payload': { 'url': '../test_data/{}_f_payload.bin'.format(FILE_ID), 'file-path': happy_day_data['fw_file'].as_posix(), 'format': 'raw-binary' } } if issubclass(manifest_codec, ManifestAsnCodecV1): version = 100500 version_file = GEN_DIR / '{}_f_version_{}.txt'.format( FILE_ID, manifest_codec.get_name()) version_file.write_text(str(version)) else: component = 'MAIN' if (FILE_ID % 2) == 0: component = 'TESTCOMP' input_cfg['component'] = component elif (FILE_ID % 3) == 0: input_cfg['component'] = component version = '100.500.0' version_file = GEN_DIR / '{}_f_version_{}.txt'.format( FILE_ID, manifest_codec.get_name()) version_file.write_text('.'.join(version)) component_file = GEN_DIR / '{}_f_component.txt'.format(FILE_ID) component_file.write_text(component) manifest = CreateAction.do_create( pem_key_data=happy_day_data['key_file'].read_bytes(), input_cfg=input_cfg, fw_version=version, update_certificate=happy_day_data['certificate_file'], asn1_codec_class=manifest_codec) GEN_DIR.mkdir(exist_ok=True) manifest_file = GEN_DIR / '{}_f_manifest_{}.bin'.format( FILE_ID, manifest_codec.get_name()) manifest_file.write_bytes(manifest) certificate_file = GEN_DIR / '{}_f_certificate.bin'.format(FILE_ID) certificate_file.write_bytes( happy_day_data['certificate_file'].read_bytes()) payload_file = GEN_DIR / '{}_f_payload.bin'.format(FILE_ID) payload_file.write_bytes(happy_day_data['fw_file'].read_bytes()) orig_fw_file = GEN_DIR / '{}_f_curr_fw.bin'.format(FILE_ID) orig_fw_file.write_bytes(happy_day_data['fw_file'].read_bytes()) new_fw_file = GEN_DIR / '{}_f_final_image.bin'.format(FILE_ID) new_fw_file.write_bytes(happy_day_data['fw_file'].read_bytes()) dom = manifest_codec.decode(manifest, None) vendor_id_file = GEN_DIR / '{}_f_vendor_id.bin'.format(FILE_ID) if manifest_codec.get_name() == 'v3': vendor_id_bytes = dom['manifest']['vendor-id'] class_id_bytes = dom['manifest']['class-id'] elif manifest_codec.get_name() == 'v1': vendor_id_bytes = \ dom['resource']['resource']['manifest']['vendorId'] class_id_bytes = dom['resource']['resource']['manifest']['classId'] else: raise AssertionError('invalid manifest version ' + manifest_codec.get_name()) vendor_id_file.write_bytes(vendor_id_bytes) class_id_file = GEN_DIR / '{}_f_class_id.bin'.format(FILE_ID) class_id_file.write_bytes(class_id_bytes) key_file = GEN_DIR / '{}_f_priv_key.bin'.format(FILE_ID) private_key_data = happy_day_data['key_file'].read_bytes() key_file.write_bytes(private_key_data) public_key = ecdsa_helper.public_key_from_private(private_key_data) public_key_bytes = ecdsa_helper.public_key_to_bytes(public_key) public_key_file = GEN_DIR / '{}_f_pub_key.bin'.format(FILE_ID) public_key_file.write_bytes(public_key_bytes) FILE_ID += 1 print('Full manifest in HEX to be viewed on ' 'https://asn1.io/asn1playground/ \n' + binascii.hexlify(manifest).decode('utf-8'))
def test_create_happy_day_full(tmp_path_factory, fw_size, manifest_codec, payload_format): global FILE_ID GEN_DIR.mkdir(exist_ok=True) happy_day_data = data_generator(tmp_path_factory, fw_size, ENCRYPTION_KEY) payload_file_path = happy_day_data['fw_file'].as_posix() if payload_format in ('combined', 'encrypted-combined'): generate_encrypted_package(happy_day_data) payload_file_path = happy_day_data['package_data']['out_file_name'] manifest = None input_cfg = { 'vendor': { 'domain': 'pelion.com' }, 'device': { 'model-name': 'my-device' }, 'priority': 15, 'payload': { 'url': '../test_data/{}_f_payload.bin'.format(FILE_ID), 'file-path': payload_file_path, 'format': payload_format }, 'component': 'MAIN' } if issubclass(manifest_codec, ManifestAsnCodecV1): version = 100500 version_file = GEN_DIR / '{}_f_version_{}.txt'.format( FILE_ID, manifest_codec.get_name()) version_file.write_text(str(version)) else: component = 'MAIN' if payload_format in ('raw-binary', 'combined') and (FILE_ID % 2) == 0: component = 'TESTCOMP' input_cfg['component'] = component elif payload_format == 'encrypted-raw': input_cfg['component'] = component # encrypted payload with dummy metadata input_cfg['payload']['encrypted'] = { 'digest': calc_digest(happy_day_data['encrypted_fw_file']), 'size': happy_day_data['encrypted_fw_file'].stat().st_size } input_cfg['payload'][ 'url'] = '../test_data/{}_f_encrypted_payload.bin'.format( FILE_ID) elif payload_format == 'encrypted-combined': input_cfg['component'] = component # create encrypted package generate_encrypted_package(happy_day_data) # encrypted payload with dummy metadata input_cfg['payload']['encrypted'] = { 'digest': calc_digest(happy_day_data['encrypted_fw_file']), 'size': happy_day_data['encrypted_fw_file'].stat().st_size } input_cfg['payload'][ 'url'] = '../test_data/{}_f_encrypted_payload.bin'.format( FILE_ID) version = '100.500.0' version_file = GEN_DIR / '{}_f_version_{}.txt'.format( FILE_ID, manifest_codec.get_name()) version_file.write_text('.'.join(version)) component_file = GEN_DIR / '{}_f_component.txt'.format(FILE_ID) component_file.write_text(component) manifest = CreateAction.do_create( pem_key_data=happy_day_data['key_file'].read_bytes(), input_cfg=input_cfg, fw_version=version, update_certificate=happy_day_data['certificate_file'], asn1_codec_class=manifest_codec) GEN_DIR.mkdir(exist_ok=True) manifest_file = GEN_DIR / '{}_f_manifest_{}.bin'.format( FILE_ID, manifest_codec.get_name()) if 'encrypted' in input_cfg['payload']: # mimic service behaviour, # concatenate DER with dummy aes-128-bit key manifest_file.write_bytes(manifest + bytearray.fromhex('8110') + ENCRYPTION_KEY) else: manifest_file.write_bytes(manifest) certificate_file = GEN_DIR / '{}_f_certificate.bin'.format(FILE_ID) certificate_file.write_bytes( happy_day_data['certificate_file'].read_bytes()) payload_file = GEN_DIR / '{}_f_payload.bin'.format(FILE_ID) payload_file.write_bytes(happy_day_data['fw_file'].read_bytes()) orig_fw_file = GEN_DIR / '{}_f_curr_fw.bin'.format(FILE_ID) orig_fw_file.write_bytes(happy_day_data['fw_file'].read_bytes()) new_fw_file = GEN_DIR / '{}_f_final_image.bin'.format(FILE_ID) new_fw_file.write_bytes(happy_day_data['fw_file'].read_bytes()) if input_cfg['payload']['format'] == 'encrypted-raw': encrypted_payload_file = GEN_DIR / '{}_f_encrypted_payload.bin'.format( FILE_ID) encrypted_payload_file.write_bytes( happy_day_data['encrypted_fw_file'].read_bytes()) dom = manifest_codec.decode(manifest, None) vendor_id_file = GEN_DIR / '{}_f_vendor_id.bin'.format(FILE_ID) if manifest_codec.get_name() == 'v3': vendor_id_bytes = dom['manifest']['vendor-id'] class_id_bytes = dom['manifest']['class-id'] elif manifest_codec.get_name() == 'v1': vendor_id_bytes = \ dom['resource']['resource']['manifest']['vendorId'] class_id_bytes = dom['resource']['resource']['manifest']['classId'] else: raise AssertionError('invalid manifest version ' + manifest_codec.get_name()) vendor_id_file.write_bytes(vendor_id_bytes) class_id_file = GEN_DIR / '{}_f_class_id.bin'.format(FILE_ID) class_id_file.write_bytes(class_id_bytes) key_file = GEN_DIR / '{}_f_priv_key.bin'.format(FILE_ID) private_key_data = happy_day_data['key_file'].read_bytes() key_file.write_bytes(private_key_data) public_key = ecdsa_helper.public_key_from_private(private_key_data) public_key_bytes = ecdsa_helper.public_key_to_bytes(public_key) public_key_file = GEN_DIR / '{}_f_pub_key.bin'.format(FILE_ID) public_key_file.write_bytes(public_key_bytes) FILE_ID += 1 print('Full manifest in HEX to be viewed on ' 'https://asn1.io/asn1playground/ \n' + binascii.hexlify(manifest).decode('utf-8'))