Exemple #1
0
 def test_uncompressed(self):
     cabarchive = CabArchive()
     cabarchive['README.txt'] = CabFile(b'foofoofoofoofoofoofoofoo')
     cabarchive['firmware.bin'] = CabFile(b'barbarbarbarbarbarbarbar')
     buf = cabarchive.save()
     self.assertEqual(len(buf), 156)
     self.assertEqual(hashlib.sha1(buf).hexdigest(), '676654685d6b5918d68081a786ae1d4dbfeb5e01')
Exemple #2
0
 def test_compressed(self):
     cabarchive = CabArchive()
     cabarchive['README.txt'] = CabFile(b'foofoofoofoofoofoofoofoo')
     cabarchive['firmware.bin'] = CabFile(b'barbarbarbarbarbarbarbar')
     buf = cabarchive.save(compress=True)
     self.assertEqual(len(buf), 122)
     self.assertEqual(hashlib.sha1(buf).hexdigest(), '74e94703c403aa93b16d01b088eb52e3a9c73288')
 def test_inf_invalid(self):
     cabarchive = CabArchive()
     cabarchive['firmware.bin'] = _get_valid_firmware()
     cabarchive['firmware.metainfo.xml'] = CabFile(b'<component/>')
     cabarchive['firmware.inf'] = CabFile(b'fubar')
     with self.assertRaises(MetadataInvalid):
         ufile = UploadedFile()
         ufile.parse('foo.cab', cabarchive.save())
Exemple #4
0
    def test_create_compressed(self):
        cabarchive = CabArchive()

        # make predictable
        dt_epoch = datetime.datetime.fromtimestamp(0, datetime.timezone.utc)
        cabarchive["README.txt"] = CabFile(b"foofoofoofoofoofoofoofoo",
                                           mtime=dt_epoch)
        cabarchive["firmware.bin"] = CabFile(b"barbarbarbarbarbarbarbar",
                                             mtime=dt_epoch)
        buf = cabarchive.save(compress=True)
        self.assertEqual(len(buf), 122)
        self.assertEqual(
            hashlib.sha1(buf).hexdigest(),
            "74e94703c403aa93b16d01b088eb52e3a9c73288")
def _get_valid_metainfo(release_description='This stable release fixes bugs',
                        version_format='quad'):
    txt = """<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright 2015 Richard Hughes <*****@*****.**> -->
<component type="firmware">
  <id>com.hughski.ColorHug.firmware</id>
  <name>ColorHug Firmware</name>
  <summary>Firmware for the ColorHug</summary>
  <description><p>Updating the firmware improves performance.</p></description>
  <provides>
    <firmware type="flashed">84f40464-9272-4ef7-9399-cd95f12da696</firmware>
  </provides>
  <url type="homepage">http://www.hughski.com/</url>
  <metadata_license>CC0-1.0</metadata_license>
  <project_license>GPL-2.0+</project_license>
  <developer_name>Hughski Limited</developer_name>
  <releases>
    <release version="0x30002" timestamp="1424116753">
      <description><p>%s</p></description>
    </release>
  </releases>
  <custom>
    <value key="foo">bar</value>
    <value key="LVFS::InhibitDownload"/>
    <value key="LVFS::VersionFormat">%s</value>
  </custom>
</component>
""" % (release_description, version_format)
    return CabFile(txt.encode('utf-8'))
Exemple #6
0
 def test_metainfo_invalid(self):
     cabarchive = CabArchive()
     cabarchive['firmware.bin'] = _get_valid_firmware()
     cabarchive['firmware.metainfo.xml'] = CabFile(b'<compoXXXXnent/>')
     with self.assertRaises(MetadataInvalid):
         ufile = UploadedFile()
         _add_version_formats(ufile)
         ufile.parse('foo.cab', cabarchive.save())
Exemple #7
0
 def test_invalid_bom(self):
     cabarchive = CabArchive()
     cabarchive['firmware.bin'] = _get_valid_firmware()
     cabarchive['firmware.metainfo.xml'] = CabFile(b'\xEF\xBB\xBF<?xml version="1.0" encoding="UTF-8"?>\n'
                                                   b'<component type="firmware"/>\n')
     with self.assertRaises(MetadataInvalid):
         ufile = UploadedFile()
         _add_version_formats(ufile)
         ufile.parse('foo.cab', cabarchive.save())
Exemple #8
0
def _get_generated_metainfo():
    txt = """<?xml version="1.0" encoding="utf-8"?>
<component type="firmware">
 <id>
  com.dell.tbt7d538854.firmware
 </id>
 <provides>
  <firmware type="flashed">
   7d538854-204d-51b2-8f9d-1fe881c70200
  </firmware>
 </provides>
 <name>
  XPS 7390 Thunderbolt
 </name>
 <summary>
  Update for the Thunderbolt host controller in a XPS 7390
 </summary>
 <description>
  <p>
   Updating the thunderbolt NVM improves performance and stability.
  </p>
 </description>
 <url type="homepage">
  http://support.dell.com/
 </url>
 <metadata_license>
  CC0-1.0
 </metadata_license>
 <project_license>
  proprietary
 </project_license>
 <developer_name>
  Dell Inc.
 </developer_name>
 <requires>
  <id compare="ge" version="1.2.3">org.freedesktop.fwupd</id>
  <hardware>
   foo|bar|baz
  </hardware>
  <firmware compare="ge" version="0.2.3"/>
  <firmware compare="eq" version="0.0.1">
   bootloader
  </firmware>
 </requires>
 <keywords>
  <keyword>
   thunderbolt
  </keyword>
 </keywords>
 <releases>
  <release timestamp="1561009099" version="41.01">
   <checksum filename="0x0962_nonsecure.bin" target="content"/>
  </release>
 </releases>
</component>
"""
    return CabFile(txt.encode('utf-8'))
Exemple #9
0
 def test_invalid_xml_header(self):
     cabarchive = CabArchive()
     cabarchive['firmware.bin'] = _get_valid_firmware()
     cabarchive['firmware.metainfo.xml'] = CabFile(b'<!-- Copyright 2015 Richard Hughes <*****@*****.**> -->\n'
                                                   b'<?xml version="1.0" encoding="UTF-8"?>\n'
                                                   b'<component type="firmware"/>\n')
     with self.assertRaises(MetadataInvalid):
         ufile = UploadedFile()
         _add_version_formats(ufile)
         ufile.parse('foo.cab', cabarchive.save())
 def test_extra_files(self):
     cabarchive = CabArchive()
     cabarchive['firmware.bin'] = _get_valid_firmware()
     cabarchive['firmware.metainfo.xml'] = _get_valid_metainfo()
     cabarchive['README.txt'] = CabFile(b'fubar')
     ufile = UploadedFile()
     ufile.parse('foo.cab', cabarchive.save())
     cabarchive2 = ufile.cabarchive_repacked
     self.assertIsNotNone(cabarchive2['firmware.bin'])
     self.assertIsNotNone(cabarchive2['firmware.metainfo.xml'])
     with self.assertRaises(KeyError):
         self.assertIsNotNone(cabarchive2['README.txt'])
Exemple #11
0
 def test_valid_with_ignored_inf(self):
     cabarchive = CabArchive()
     cabarchive['firmware.bin'] = _get_valid_firmware()
     cabarchive['firmware.metainfo.xml'] = _get_valid_metainfo(enable_inf_parsing=False)
     cabarchive['firmware.inf'] = CabFile(b'fubar')
     ufile = UploadedFile()
     _add_version_formats(ufile)
     ufile.parse('foo.cab', cabarchive.save())
     cabarchive2 = ufile.cabarchive_repacked
     self.assertIsNotNone(cabarchive2['firmware.bin'])
     self.assertIsNotNone(cabarchive2['firmware.metainfo.xml'])
     self.assertIsNotNone(cabarchive2['firmware.inf'])
Exemple #12
0
def repack(arc: CabArchive, arg: str) -> None:
    with tempfile.TemporaryDirectory("cabarchive") as tmpdir:
        print("Extracting to {}".format(tmpdir))
        subprocess.call(
            ["cabextract", "--fix", "--quiet", "--directory", tmpdir, arg])
        for fn in glob.iglob(os.path.join(tmpdir, "**"), recursive=True):
            try:
                with open(fn, "rb") as f:
                    fn_noprefix = fn[len(tmpdir) + 1:]
                    print("Adding: {}".format(fn_noprefix))
                    arc[fn_noprefix] = CabFile(f.read())
            except IsADirectoryError as _:
                pass
    def archive_sign(self, cabarchive, cabfile):

        # already signed
        detached_fn = cabfile.filename + '.p7b'
        if detached_fn in cabarchive:
            return

        # create the detached signature
        blob_p7b = self._sign_blob(cabfile.buf)
        if not blob_p7b:
            return

        # add it to the archive
        cabarchive[detached_fn] = CabFile(blob_p7b.encode('utf-8'))
Exemple #14
0
    def archive_sign(self, cabarchive, cabfile):

        # already signed
        detached_fn = cabfile.filename + '.asc'
        if detached_fn in cabarchive:
            return

        # create the detached signature
        blob_asc = _sigul_detached_sign_data(cabfile.buf,
                                             self.get_setting('sign_sigul_config_file', required=True),
                                             self.get_setting('sign_sigul_firmware_key', required=True))

        # add it to the archive
        cabarchive[detached_fn] = CabFile(blob_asc.encode('utf-8'))
Exemple #15
0
    def archive_sign(self, cabarchive, cabfile):

        # already signed
        detached_fn = cabfile.filename + '.asc'
        if detached_fn in cabarchive:
            return

        # create the detached signature
        affidavit = Affidavit(
            self.get_setting('sign_gpg_firmware_uid', required=True),
            self.get_setting('sign_gpg_keyring_dir', required=True))
        contents_asc = str(affidavit.create(cabfile.buf))

        # add it to the archive
        cabarchive[detached_fn] = CabFile(contents_asc.encode('utf-8'))
Exemple #16
0
    def archive_finalize(self, cabarchive, metadata):

        # does the readme file already exist?
        filename = self.get_setting('info_readme_filename', required=True)
        if filename in cabarchive:
            return

        # read in the file and do substititons
        try:
            with open(self.get_setting('info_readme_template', required=True),
                      'rb') as f:
                template = f.read().decode('utf-8')
        except IOError as e:
            raise PluginError(e)
        for key in metadata:
            template = template.replace(key, metadata[key])

        # add it to the archive
        cabarchive[filename] = CabFile(template.encode('utf-8'))
Exemple #17
0
def _repackage_archive(filename, buf, tmpdir=None, flattern=True):
    """ Unpacks an archive (typically a .zip) into a CabArchive object """

    # write to temp file
    src = tempfile.NamedTemporaryFile(mode='wb',
                                      prefix='foreignarchive_',
                                      suffix=".zip",
                                      dir=tmpdir,
                                      delete=True)
    src.write(buf)
    src.flush()

    # decompress to a temp directory
    dest = tempfile.TemporaryDirectory(prefix='foreignarchive_')

    # work out what binary to use
    split = filename.rsplit('.', 1)
    if len(split) < 2:
        raise NotImplementedError('Filename not valid')
    if split[1] == 'zip':
        argv = ['/usr/bin/bsdtar', '--directory', dest.name, '-xvf', src.name]
    else:
        raise NotImplementedError('Filename had no supported extension')

    # bail out early
    if not os.path.exists(argv[0]):
        raise IOError('command %s not found' % argv[0])

    # extract
    ps = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    if ps.wait() != 0:
        raise IOError('Failed to extract: %s' % ps.stderr.read())

    # add all the fake CFFILE objects
    cabarchive = CabArchive()
    for fn in glob.glob(dest.name + '/**/*.*', recursive=True):
        with open(fn, 'rb') as f:
            fn = fn.replace('\\', '/')
            if flattern:
                fn = os.path.basename(fn)
            cabarchive[fn] = CabFile(f.read())
    return cabarchive
Exemple #18
0
def _get_valid_inf():
    txt = """[Version]
Class=Firmware
ClassGuid={f2e7dd72-6468-4e36-b6f1-6488f42c1b52}
DriverVer=04/18/2015,2.0.3

[Firmware_CopyFiles]
firmware.bin

[Firmware_AddReg]
HKR,,FirmwareId,,{2082b5e0-7a64-478a-b1b2-e3404fab6dad}
HKR,,FirmwareVersion,%REG_DWORD%,0x0000000
HKR,,FirmwareFilename,,firmware.bin

[Strings]
Provider     = "Hughski"
MfgName      = "Hughski Limited"
FirmwareDesc = "ColorHug2 Firmware"
DiskName     = "Firmware for the ColorHug2 Colorimeter"
"""
    return CabFile(txt.encode('utf-8'))
Exemple #19
0
def _get_alternate_metainfo():
    txt = """<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright 2019 Richard Hughes <*****@*****.**> -->
<component type="firmware">
  <id>com.hughski.ColorHug.firmware</id>
  <name>ColorHug</name>
  <summary>Firmware for the ColorHug</summary>
  <description><p>Updating the firmware improves performance.</p></description>
  <provides>
    <firmware type="flashed">84f40464-9272-4ef7-9399-cd95f12da696</firmware>
  </provides>
  <url type="homepage">http://www.hughski.com/</url>
  <metadata_license>CC0-1.0</metadata_license>
  <project_license>proprietary</project_license>
  <developer_name>Hughski Limited</developer_name>
  <releases>
    <release version="1.2.3" date="2019-07-02">
      <description><p>This stable release fixes bugs</p></description>
    </release>
  </releases>
</component>
"""
    return CabFile(txt.encode('utf-8'))
Exemple #20
0
def _sign_fw(fw):

    # load the .cab file
    download_dir = app.config['DOWNLOAD_DIR']
    fn = os.path.join(download_dir, fw.filename)
    try:
        with open(fn, 'rb') as f:
            cabarchive = CabArchive(f.read())
    except IOError as e:
        raise NotImplementedError('cannot read %s: %s' % (fn, str(e)))

    # create Jcat file
    jcatfile = JcatFile()

    # sign each component in the archive
    print('Signing: %s' % fn)
    for md in fw.mds:
        try:

            # create Jcat item with SHA1 and SHA256 checksum blob
            cabfile = cabarchive[md.filename_contents]
            jcatitem = jcatfile.get_item(md.filename_contents)
            jcatitem.add_blob(JcatBlobSha1(cabfile.buf))
            jcatitem.add_blob(JcatBlobSha256(cabfile.buf))

            # sign using plugins
            for blob in ploader.archive_sign(cabfile.buf):

                # add GPG only to archive for backwards compat with older fwupd
                if blob.kind == JcatBlobKind.GPG:
                    fn_blob = md.filename_contents + '.' + blob.filename_ext
                    cabarchive[fn_blob] = CabFile(blob.data)

                # add to Jcat file too
                jcatitem.add_blob(blob)

        except KeyError as _:
            raise NotImplementedError('no {} firmware found'.format(
                md.filename_contents))

    # rewrite the metainfo.xml file to reflect latest changes and sign it
    for md in fw.mds:

        # write new metainfo.xml file
        component = _generate_metadata_mds([md], metainfo=True)
        blob_xml = b'<?xml version="1.0" encoding="UTF-8"?>\n' + \
                   ET.tostring(component,
                               encoding='UTF-8',
                               xml_declaration=False,
                               pretty_print=True)
        _show_diff(cabarchive[md.filename_xml].buf, blob_xml)
        cabarchive[md.filename_xml].buf = blob_xml

        # sign it
        jcatitem = jcatfile.get_item(md.filename_xml)
        jcatitem.add_blob(JcatBlobSha1(blob_xml))
        jcatitem.add_blob(JcatBlobSha256(blob_xml))
        for blob in ploader.archive_sign(blob_xml):
            jcatitem.add_blob(blob)

    # write jcat file
    if jcatfile.items:
        cabarchive['firmware.jcat'] = CabFile(jcatfile.save())

    # overwrite old file
    cab_data = cabarchive.save()
    with open(fn, 'wb') as f:
        f.write(cab_data)

    # inform the plugin loader
    ploader.file_modified(fn)

    # update the download size
    for md in fw.mds:
        md.release_download_size = len(cab_data)

    # update the database
    fw.checksum_signed_sha1 = hashlib.sha1(cab_data).hexdigest()
    fw.checksum_signed_sha256 = hashlib.sha256(cab_data).hexdigest()
    fw.signed_timestamp = datetime.datetime.utcnow()
    db.session.commit()
def _get_valid_firmware():
    return CabFile('fubar'.ljust(1024).encode('utf-8'))
Exemple #22
0
    def test_create(self):

        # create new archive
        arc = CabArchive()
        arc.set_id = 0x0622

        # first example
        cff = CabFile()
        cff.buf = (b"#include <stdio.h>\r\n\r\nvoid main(void)\r\n"
                   b'{\r\n    printf("Hello, world!\\n");\r\n}\r\n')
        cff.date = datetime.date(1997, 3, 12)
        cff.time = datetime.time(11, 13, 52)
        cff.is_arch = True
        arc["hello.c"] = cff

        # second example
        cff = CabFile()
        cff.buf = (b"#include <stdio.h>\r\n\r\nvoid main(void)\r\n"
                   b'{\r\n    printf("Welcome!\\n");\r\n}\r\n\r\n')
        cff.date = datetime.date(1997, 3, 12)
        cff.time = datetime.time(11, 15, 14)
        cff.is_arch = True
        arc["welcome.c"] = cff

        # verify
        data = arc.save(False)
        with open("/tmp/test.cab", "wb") as f:
            f.write(data)
        expected = (
            b"\x4D\x53\x43\x46\x00\x00\x00\x00\xFD\x00\x00\x00\x00\x00\x00\x00"
            b"\x2C\x00\x00\x00\x00\x00\x00\x00\x03\x01\x01\x00\x02\x00\x00\x00"
            b"\x22\x06\x00\x00\x5E\x00\x00\x00\x01\x00\x00\x00\x4D\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\x00\x6C\x22\xBA\x59\x20\x00\x68\x65\x6C\x6C"
            b"\x6F\x2E\x63\x00\x4A\x00\x00\x00\x4D\x00\x00\x00\x00\x00\x6C\x22"
            b"\xE7\x59\x20\x00\x77\x65\x6C\x63\x6F\x6D\x65\x2E\x63\x00\xBD\x5A"
            b"\xA6\x30\x97\x00\x97\x00\x23\x69\x6E\x63\x6C\x75\x64\x65\x20\x3C"
            b"\x73\x74\x64\x69\x6F\x2E\x68\x3E\x0D\x0A\x0D\x0A\x76\x6F\x69\x64"
            b"\x20\x6D\x61\x69\x6E\x28\x76\x6F\x69\x64\x29\x0D\x0A\x7B\x0D\x0A"
            b"\x20\x20\x20\x20\x70\x72\x69\x6E\x74\x66\x28\x22\x48\x65\x6C\x6C"
            b"\x6F\x2C\x20\x77\x6F\x72\x6C\x64\x21\x5C\x6E\x22\x29\x3B\x0D\x0A"
            b"\x7D\x0D\x0A\x23\x69\x6E\x63\x6C\x75\x64\x65\x20\x3C\x73\x74\x64"
            b"\x69\x6F\x2E\x68\x3E\x0D\x0A\x0D\x0A\x76\x6F\x69\x64\x20\x6D\x61"
            b"\x69\x6E\x28\x76\x6F\x69\x64\x29\x0D\x0A\x7B\x0D\x0A\x20\x20\x20"
            b"\x20\x70\x72\x69\x6E\x74\x66\x28\x22\x57\x65\x6C\x63\x6F\x6D\x65"
            b"\x21\x5C\x6E\x22\x29\x3B\x0D\x0A\x7D\x0D\x0A\x0D\x0A")
        _check_range(bytearray(data), bytearray(expected))

        # use cabextract to test validity
        try:
            self.assertEqual(
                subprocess.call(["cabextract", "--test", "/tmp/test.cab"]), 0)
        except FileNotFoundError as _:
            pass

        # check we can parse what we just created
        arc = CabArchive()
        with open("/tmp/test.cab", "rb") as f:
            arc.parse(f.read())

        # add an extra file
        arc["test.inf"] = CabFile(b"$CHICAGO$")

        # save with compression
        with open("/tmp/test.cab", "wb") as f:
            f.write(arc.save(True))

        # use cabextract to test validity
        try:
            self.assertEqual(
                subprocess.call(["cabextract", "--test", "/tmp/test.cab"]), 0)
        except FileNotFoundError as _:
            pass
Exemple #23
0
 def test_cabfile_length(self):
     self.assertEqual(len(CabFile(b'foofoofoofoofoofoofoofoo')), 24)