Esempio n. 1
0
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
Esempio n. 2
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
Esempio n. 3
0
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
Esempio n. 4
0
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
Esempio n. 5
0
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
Esempio n. 6
0
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(),
    }
Esempio n. 7
0
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'))
Esempio n. 8
0
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'))