Exemplo n.º 1
0
def route_checksum_create(component_id):
    """ Adds a checksum to a component """

    # check we have data
    for key in ['value']:
        if key not in request.form or not request.form[key]:
            return _error_internal('No %s specified!' % key)

    # get firmware component
    md = db.session.query(Component).\
            filter(Component.component_id == component_id).first()
    if not md:
        flash('No component matched!', 'danger')
        return redirect(url_for('firmware.route_firmware'))

    # security check
    if not md.check_acl('@modify-checksums'):
        flash('Permission denied: Unable to modify other vendor firmware',
              'danger')
        return redirect(
            url_for('components.route_show', component_id=component_id))

    # validate is a valid hash
    hash_value = request.form['value']
    if _is_sha1(hash_value):
        hash_kind = 'SHA1'
    elif _is_sha256(hash_value):
        hash_kind = 'SHA256'
    else:
        flash('%s is not a recognised SHA1 or SHA256 hash' % hash_value,
              'warning')
        return redirect(
            url_for('components.route_show',
                    component_id=md.component_id,
                    page='checksums'))

    # check it's not already been added
    for csum in md.device_checksums:
        if csum.value == hash_value:
            flash('%s has already been added' % hash_value, 'warning')
            return redirect(
                url_for('components.route_show',
                        component_id=md.component_id,
                        page='checksums'))

    # add checksum
    csum = Checksum(kind=hash_kind, value=hash_value)
    md.device_checksums.append(csum)
    md.fw.mark_dirty()
    md.fw.signed_timestamp = None
    db.session.commit()
    _async_sign_fw.apply_async(args=(md.fw.firmware_id, ), queue='firmware')

    flash('Added device checksum', 'info')
    return redirect(
        url_for('components.route_show',
                component_id=md.component_id,
                page='checksums'))
Exemplo n.º 2
0
def route_report():
    """ Upload a report """

    # only accept form data
    if request.method != 'POST':
        return _json_error('only POST supported')

    # parse both content types, either application/json or multipart/form-data
    signature = None
    if request.data:
        payload = request.data.decode('utf8')
    elif request.form:
        data = request.form.to_dict()
        if 'payload' not in data:
            return _json_error('No payload in multipart/form-data')
        payload = data['payload']
        if 'signature' in data:
            signature = data['signature']
    else:
        return _json_error('No data')

    # find user and verify
    crt = None
    if signature:
        try:
            info = _pkcs7_signature_info(signature, check_rc=False)
        except IOError as e:
            return _json_error('Signature invalid: %s' % str(e))
        if 'serial' not in info:
            return _json_error('Signature invalid, no signature')
        crt = db.session.query(Certificate).filter(
            Certificate.serial == info['serial']).first()
        if crt:
            try:
                _pkcs7_signature_verify(crt, payload, signature)
            except IOError as _:
                return _json_error('Signature did not validate')

    # parse JSON data
    try:
        item = json.loads(payload)
    except ValueError as e:
        return _json_error('No JSON object could be decoded: ' + str(e))

    # check we got enough data
    for key in ['ReportVersion', 'MachineId', 'Reports', 'Metadata']:
        if not key in item:
            return _json_error('invalid data, expected %s' % key)
        if item[key] is None:
            return _json_error('missing data, expected %s' % key)

    # parse only this version
    if item['ReportVersion'] != 2:
        return _json_error('report version not supported')

    # add each firmware report
    machine_id = item['MachineId']
    reports = item['Reports']
    if len(reports) == 0:
        return _json_error('no reports included')
    metadata = item['Metadata']
    if len(metadata) == 0:
        return _json_error('no metadata included')

    msgs = []
    uris = []
    for report in reports:
        for key in ['Checksum', 'UpdateState', 'Metadata']:
            if not key in report:
                return _json_error('invalid data, expected %s' % key)
            if report[key] is None:
                return _json_error('missing data, expected %s' % key)

        # flattern the report including the per-machine and per-report metadata
        data = metadata
        for key in report:
            # don't store some data
            if key in [
                    'Created', 'Modified', 'BootTime', 'UpdateState',
                    'DeviceId', 'UpdateState', 'DeviceId', 'Checksum'
            ]:
                continue
            if key == 'Metadata':
                md = report[key]
                for md_key in md:
                    data[md_key] = md[md_key]
                continue
            # allow array of strings for any of the keys
            if isinstance(report[key], list):
                data[key] = ','.join(report[key])
            else:
                data[key] = report[key]

        # try to find the checksum (which might not exist on this server)
        fw = db.session.query(Firmware).filter(
            Firmware.checksum_signed_sha1 == report['Checksum']).first()
        if not fw:
            fw = db.session.query(Firmware).filter(
                Firmware.checksum_signed_sha256 == report['Checksum']).first()
        if not fw:
            msgs.append('%s did not match any known firmware archive' %
                        report['Checksum'])
            continue

        # cannot report this failure
        if fw.do_not_track:
            msgs.append('%s will not accept reports' % report['Checksum'])
            continue

        # update the device checksums if there is only one component
        if crt and crt.user.check_acl(
                '@qa') and 'ChecksumDevice' in data and len(fw.mds) == 1:
            md = fw.md_prio
            found = False

            # fwupd v1.2.6 sends an array of strings, before that just a string
            checksums = data['ChecksumDevice']
            if not isinstance(checksums, list):
                checksums = [checksums]

            # does the submitted checksum already exist as a device checksum
            for checksum in checksums:
                for csum in md.device_checksums:
                    if csum.value == checksum:
                        found = True
                        break
                if found:
                    continue
                _event_log('added device checksum %s to firmware %s' %
                           (checksum, md.fw.checksum_upload_sha1))
                if _is_sha1(checksum):
                    md.device_checksums.append(Checksum(checksum, 'SHA1'))
                elif _is_sha256(checksum):
                    md.device_checksums.append(Checksum(checksum, 'SHA256'))

        # find any matching report
        issue_id = 0
        if report['UpdateState'] == 3:
            issue = _find_issue_for_report_data(data, fw)
            if issue:
                issue_id = issue.issue_id
                msgs.append('The failure is a known issue')
                uris.append(issue.url)

        # update any old report
        r = db.session.query(Report).\
                        filter(Report.checksum == report['Checksum']).\
                        filter(Report.machine_id == machine_id).first()
        if r:
            msgs.append('%s replaces old report' % report['Checksum'])
            r.state = report['UpdateState']
            for e in r.attributes:
                db.session.delete(e)
        else:
            # save a new report in the database
            r = Report(machine_id=machine_id,
                       firmware_id=fw.firmware_id,
                       issue_id=issue_id,
                       state=report['UpdateState'],
                       checksum=report['Checksum'])

        # update the firmware so that the QA user does not have to wait 24h
        if r.state == 2:
            fw.report_success_cnt += 1
        elif r.state == 3:
            if r.issue_id:
                fw.report_issue_cnt += 1
            else:
                fw.report_failure_cnt += 1

        # update the LVFS user
        if crt:
            r.user_id = crt.user_id

        # save all the report entries
        for key in data:
            r.attributes.append(ReportAttribute(key=key, value=data[key]))
        db.session.add(r)

    # all done
    db.session.commit()

    # put messages and URIs on one line
    return _json_success(msg='; '.join(msgs) if msgs else None,
                         uri='; '.join(uris) if uris else None)
Exemplo n.º 3
0
    def _parse_release(md, release):

        # get description
        try:
            md.release_description = _node_validate_text(
                release.xpath('description')[0],
                minlen=3,
                maxlen=1000,
                nourl=True)
        except IndexError as _:
            pass

        md.install_duration = int(release.get('install_duration', '0'))
        md.release_urgency = release.get('urgency')

        # date, falling back to timestamp
        if 'date' in release.attrib:
            try:
                dt = datetime.datetime.strptime(release.get('date'),
                                                "%Y-%m-%d")
                dt_utc = dt.replace(tzinfo=datetime.timezone.utc)
                md.release_timestamp = int(dt_utc.timestamp())
            except ValueError as e:
                raise MetadataInvalid(
                    '<release> has invalid date attribute: {}'.format(str(e)))
        elif 'timestamp' in release.attrib:
            try:
                md.release_timestamp = int(release.get('timestamp'))
            except ValueError as e:
                raise MetadataInvalid(
                    '<release> has invalid timestamp attribute: {}'.format(
                        str(e)))
        else:
            raise MetadataInvalid(
                '<release> had no date or timestamp attributes')

        # optional release tag
        if 'tag' in release.attrib:
            md.release_tag = release.attrib['tag']
            if len(md.release_tag) < 4:
                raise MetadataInvalid(
                    '<release> tag was too short to identify the firmware')
            md.add_keywords_from_string(md.release_tag, priority=5)

        # get list of CVEs
        for issue in release.xpath('issues/issue'):
            kind = issue.get('type')
            if not kind:
                raise MetadataInvalid('<issue> had no type attribute')
            if kind != 'cve':
                raise MetadataInvalid('<issue> type can only be \'cve\'')
            value = _node_validate_text(issue,
                                        minlen=3,
                                        maxlen=1000,
                                        nourl=True)
            md.issues.append(ComponentIssue(kind=kind, value=value))

        # get <url type="details">
        try:
            md.details_url = _node_validate_text(
                release.xpath('url[@type="details"]')[0],
                minlen=12,
                maxlen=1000)
        except IndexError as _:
            pass

        # get <url type="source">
        try:
            md.source_url = _node_validate_text(
                release.xpath('url[@type="source"]')[0],
                minlen=12,
                maxlen=1000)
        except IndexError as _:
            pass

        # fix up hex version
        md.version = release.get('version')
        if not md.version:
            raise MetadataInvalid('<release> had no version attribute')
        if md.version.startswith('0x'):
            md.version = str(int(md.version[2:], 16))

        # ensure there's always a contents filename
        try:
            md.filename_contents = release.xpath(
                'checksum[@target="content"]')[0].get('filename')
        except IndexError as _:
            pass
        if not md.filename_contents:
            md.filename_contents = 'firmware.bin'

        # ensure there's always a contents filename
        for csum in release.xpath('checksum[@target="device"]'):
            text = _node_validate_text(csum, minlen=32, maxlen=128)
            if csum.get('kind') == 'sha1':
                md.device_checksums.append(Checksum(text, 'SHA1'))
            elif csum.get('kind') == 'sha256':
                md.device_checksums.append(Checksum(text, 'SHA256'))
        if not md.filename_contents:
            md.filename_contents = 'firmware.bin'