Пример #1
0
def parse(options, prefix):
    data = prefix + options.input_file.read()
    LOG.debug('Read {} bytes of encoded data. Will try to decode...'.format(len(data)))

    decoded_data = {
        "der": lambda d: codec.bin2obj(d, manifest_definition.SignedResource(), der_decoder, True),
    }[options.encoding](data)
    LOG.debug('Successfully decoded data from {} encoded binary'.format(options.encoding))
    return decoded_data
Пример #2
0
def main(options):
    LOG.debug('Creating new manifest from input file and options')

    # Parse input files and options. Generate hydrated and hierachial manifest JSON.
    manifest = create_signed_resource(options)
    LOG.debug('Manifest python object successfully created from ASN.1 definition and input')

    # Encode data if requested
    output = utils.encode(manifest, options, manifest_definition.SignedResource())
    LOG.debug('Manifest successfully encoded into desired format ({}). Size: {} bytes.'.format(options.encoding, len(output)))

    # And we're done
    write_result(output, options)
Пример #3
0
def verifyManifestV1(options, data, signedResource, resource, manifest):
    if manifest['manifestVersion'] != 'v1':
        LOG.critical('Not a version 1 manifest')
        return None
    LOG.info('Manifest version {}: OK'.format(manifest['manifestVersion']))
    # Extract the crypto mode
    if 'oid' in manifest['encryptionMode']:
        LOG.critical('Cannot verify Object ID encryptionMode')
        return None

    if not 'enum' in manifest['encryptionMode']:
        LOG.critical('No encryptionMode specified')
        return None

    emode = manifest['encryptionMode']['enum']
    modes = verify_manifest_minimal.Manifest().getComponentType(
    )['encryptionMode'].getType().getComponentType()['enum'].getType(
    ).getNamedValues()
    if modes.getValue(emode) != None and emode != modes.getName(0):
        LOG.info('Encryption mode {}: OK'.format(emode))
    else:
        LOG.critical('Encryption mode {} not supported'.format(emode))
        return None
    # NOTE: Currently only modes 1-3 are supported and these modes only allow SHA256.
    # This means that the manifest is hashed with SHA256.

    # Load defaults
    defaultConfig = {}
    if os.path.exists(defaults.config):
        with open(defaults.config) as f:
            defaultConfig = json.load(f)

    # Verify UUIDs
    for i in ['vendorId', 'classId', 'deviceId']:
        if i in manifest:
            try:
                LOG.debug('Found {}: {}'.format(i, manifest[i]))
                manifest_id = uuid.UUID(manifest[i])
            except:
                #if len(uuid) != 0 and len(uuid) != 128/8:
                LOG.critical('UUIDs ({}) must be 0 or 128 bits'.format(i))
                return None
            expect_id = None
            if hasattr(options, i) and getattr(options, i):
                expect_id = uuid.UUID(getattr(options, i))
            else:
                expect_id = defaultConfig.get(i, None)
                if expect_id:
                    expect_id = uuid.UUID(expect_id)
                    LOG.debug('Using default {} from {}'.format(
                        i, defaults.config))
            if expect_id:
                if manifest_id != expect_id:
                    LOG.critical('UUID mismatch for {}\n'
                                 'Expected: {}\n'
                                 'Actual:   {}'.format(i, expect_id,
                                                       manifest_id))
                    return None
                LOG.info('UUID {}: OK'.format(i))
            else:
                LOG.warning('UUID {} not specified; ignoring'.format(i))

    # Verify the manifest hash
    if not 'signature' in signedResource:
        LOG.critical(
            'Signature missing, but encryption mode {} requires a signature'.
            format(emode))
        return None
    c_hash = utils.sha_hash(signedResource['resource'])
    if not 'hash' in signedResource['signature']:
        LOG.critical('Manifest does not contain a hash')
        return None
    e_hash = binascii.a2b_hex(signedResource['signature']['hash'])
    if c_hash != e_hash:
        LOG.critical('Hash mismatch\nExpected: {}\nActual:   {}'.format(
            binascii.b2a_hex(e_hash), binascii.b2a_hex(c_hash)))
        return None
    LOG.debug('Manifest hash: {}'.format(
        binascii.b2a_hex(c_hash).decode('utf-8')))
    LOG.info('Maninfest hash: OK')

    if not options.certificateQuery:
        LOG.warning('No certificateQuery provided, will not verify signatures')
    if (emode == 'none-ecc-secp256r1-sha256'
            or emode == 'aes-128-ctr-ecc-secp256r1-sha256'
        ) and options.certificateQuery:
        if not 'signatures' in signedResource['signature']:
            LOG.critical(
                'Signature missing, but encryption mode {} requires a signature'
                .format(emode))
            return None
        for signature in signedResource['signature']['signatures']:
            if not 'certificates' in signature:
                LOG.critical(
                    'A certificate reference is mandatory in a signature')
                return None
            if len(signature['certificates']) == 0:
                LOG.critical(
                    'At least one certificate reference is mandatory in a signature'
                )
                return None
            if not 'fingerprint' in signature['certificates'][0] or len(
                    signature['certificates'][0]['fingerprint']) == 0:
                LOG.critical('A fingerprint is mandatory in a certificate')
                return None
            LOG.debug('Verifying signature by {}'.format(
                signature['certificates'][0]['fingerprint']))
            fingerprints = [
                x['fingerprint'] for x in signature['certificates']
            ]
            URLs = [
                x['uri'] if 'uri' in x else ''
                for x in signature['certificates']
            ]
            certfile = options.certificateQuery(options, fingerprints, URLs)
            if certfile == None:  # TODO: Option for mandatory certificate verification
                if options.mandatory_signature:
                    LOG.critical(
                        'Could not find certificate chain matching {}'.format(
                            fingerprints[0]))
                    return None
                LOG.warning(
                    'Could not find certificate chain matching {}'.format(
                        fingerprints[0]))
            else:
                #if not options.ecdsaVerify(cert, signature):
                # ok = False
                # vk = ecdsa.VerifyingKey.from_pem(keypem)
                # ok = vk.verify(binascii.a2b_hex(signature['signature']),
                #           signedResource['resource'],
                #           hashfunc=hashlib.sha256,
                #           sigdecode=ecdsa.util.sigdecode_der)
                LOG.debug('Opening {} ...'.format(certfile))
                with open(certfile, 'rb') as cert:
                    ok = options.ecdsaVerify(cert, signature['signature'],
                                             e_hash)
                    if not ok:
                        LOG.critical('Signature verification failed')
                        return None
                    LOG.info('Signature by {}: OK'.format(fingerprints[0]))

    # Decode the full manifest.
    full_manifest = {
        "der":
        lambda d: codec.bin2obj(data, manifest_definition.SignedResource(),
                                der_decoder),
    }[options.encoding](data)
    LOG.debug('Parsed whole manifest from {}-encoded binary object'.format(
        options.encoding))

    full_manifest = full_manifest['resource']['resource']['manifest']
    # TODO: Measure nonce entropy
    if not 'nonce' in full_manifest:
        LOG.critical('A nonce is required in the manifest')
        return None
    else:
        nonce = binascii.a2b_hex(full_manifest['nonce'])
        if len(nonce) * 8 != 128:
            LOG.critical('Nonce must be 128 bits. Got {}: {}'.format(
                len(nonce) * 8, binascii.b2a_hex(nonce)))
            return None
    LOG.info('nonce: {} OK'.format(full_manifest['nonce']))

    # Call vendor-supplied Vendor Info Validator
    if hasattr(options, 'validateVendorInfo'):
        if options.validateVendorInfo(options, full_manifest['vendorInfo']):
            LOG.critical('Vendor Info Validation failed')
            return None
        LOG.info('VendorInfo: OK')

    # Extract apply period
    applyPeriod = full_manifest.get('applyPeriod')

    # must have either a payload or a dependency
    if not 'payload' in full_manifest and (
            not 'dependencies' in full_manifest
            or len(full_manifest['dependencies']) == 0):
        LOG.critical('Manifest must contain either a dependency or a payload')
        sys.exit(1)

    # Verify the payload
    if 'payload' in full_manifest:
        payload = full_manifest['payload']
        LOG.debug('Manifest contains a payload')
        # Verify the payload format
        if 'enum' in payload['format']:
            enum = payload['format']['enum']
            payloadFormats = manifest_definition.PayloadDescription(
            ).getComponentType()['format'].getType().getComponentType(
            )['enum'].getType().getNamedValues()
            if payloadFormats.getValue(
                    enum) == None or enum == payloadFormats.getName(0):
                LOG.critical('Payload format not recognized')
                return None
            LOG.info('Payload format {}: OK'.format(enum))
        elif 'objectId' in payload['format']:
            LOG.warning('Cannot verify Object ID payload format')
        else:
            LOG.critical('Payload does not contain a format')
            return None

        if emode == 'aes-128-ctr-ecc-secp256r1-sha256':
            if not 'encryptionInfo' in payload:
                LOG.critical(
                    'Encryption info must be present for encrypted payload distribution'
                )
                return None
            cryptinfo = payload['encryptionInfo']
            # Validate the encryption information
            # Validate the init vector:
            if not 'initVector' in cryptinfo or len(
                    binascii.a2b_hex(cryptinfo['initVector'])) != 128 / 8:
                LOG.critical(
                    'When using aes-128-ctr-ecc-secp256r1-sha256, a 128-bit AES initialization vector is mandatory'
                )
                if 'initVector' in cryptinfo:
                    iv = binascii.a2b_hex(cryptinfo['initVector'])
                    LOG.critical('Expected 128 bits, got {}: {}'.format(
                        len(iv) * 8, binascii.b2a_hex(iv)))
                return None
            # TODO: Verify the entropy of the init vector

            # Determine the key mode
            kmode = 0
            # Options:
            if 'key' in cryptinfo['id'] and 'cipherKey' in cryptinfo['key']:
                if len(cryptinfo['key']['cipherKey']) == 0:
                    # Select a preshared local key
                    kmode = 1
                    LOG.debug('Using local preshared key for decryption')
                else:
                    # Select a preshared local key & decrypt a session key
                    kmode = 2
                    LOG.debug(
                        'Using local preshared key to decrypt the payload key')
            # Select certificate & decrypt a session key (Single-device only)
            elif 'certificate' in cryptinfo['id'] and 'cipherKey' in cryptinfo[
                    'key']:
                kmode = 3
                LOG.debug('Using ECDH to decrypt the device key')
            # Select a certificate & a keytable
            elif 'certificate' in cryptinfo['id'] and 'keyTable' in cryptinfo[
                    'key']:
                kmode = 4
                LOG.debug(
                    'Using ECDH to decrypt the payload key from the key table')

            if kmode == 0:
                LOG.critical('Unrecognized key distribution mode')
                return None

            # NOTE: It is not possible to verify payload without a device key when it is encrypted.
        # Verify the storage identifier
        if not 'storageIdentifier' in payload or len(
                payload['storageIdentifier']) == 0:
            LOG.critical('storageIdentifier must be provided')
            return None
        # Verify the resource reference
        if not 'reference' in payload:
            LOG.critical(
                'A resource reference is mandatory in a payload-bearing manifest'
            )
            return None
        if not 'hash' in payload['reference'] or len(
                payload['reference']['hash']) == 0:
            LOG.critical('A resource hash is mandatory in a payload reference')
            return None
        if not 'size' in payload['reference'] or payload['reference'][
                'size'] == 0:
            LOG.critical('Zero-size resources are not permitted')
            return None

        if 'uri' in payload['reference']:
            LOG.debug('Payload refers to URI: {}'.format(
                payload['reference']['uri']))
            # TODO: verify payload URI

        # Do not verify the version string; it is for presentation only.

    # TODO: Validate aliases
    # TODO: Validate dependencies

    return {
        'timestamp': full_manifest['timestamp'],
        'applyPeriod': applyPeriod
    }