Example #1
0
    def test_artifact_signing_keys(
        self,
        request,
        prepared_test_build,
        bitbake_variables,
        bitbake_path,
        bitbake_image,
    ):
        """Test that MENDER_ARTIFACT_SIGNING_KEY and MENDER_ARTIFACT_VERIFY_KEY
        works correctly."""

        build_image(
            prepared_test_build["build_dir"],
            prepared_test_build["bitbake_corebase"],
            bitbake_image,
            [
                'MENDER_ARTIFACT_SIGNING_KEY = "%s"'
                % os.path.join(os.getcwd(), signing_key("RSA").private),
                'MENDER_ARTIFACT_VERIFY_KEY = "%s"'
                % os.path.join(os.getcwd(), signing_key("RSA").public),
            ],
        )

        built_rootfs = latest_build_artifact(
            request, prepared_test_build["build_dir"], "core-image*.ext[234]"
        )
        # Copy out the key we just added from the image and use that to
        # verify instead of the original, just to be sure.
        subprocess.check_call(
            [
                "debugfs",
                "-R",
                "dump -p /etc/mender/artifact-verify-key.pem artifact-verify-key.pem",
                built_rootfs,
            ]
        )
        try:
            built_artifact = latest_build_artifact(
                request, prepared_test_build["build_dir"], "core-image*.mender"
            )
            output = subprocess.check_output(
                [
                    "mender-artifact",
                    "read",
                    "-k",
                    os.path.join(os.getcwd(), "artifact-verify-key.pem"),
                    built_artifact,
                ]
            ).decode()
            assert output.find("Signature: signed and verified correctly") >= 0

        finally:
            os.remove("artifact-verify-key.pem")
Example #2
0
    def test_signed_updates(self, sig_case, bitbake_variables, connection):
        """Test various combinations of signed and unsigned, present and non-
        present verification keys."""

        file_flag = Helpers.get_file_flag(bitbake_variables)

        # mmc mount points are named: /dev/mmcblk0p1
        # ubi volumes are named: ubi0_1
        (active,
         passive) = determine_active_passive_part(bitbake_variables,
                                                  connection)
        if passive.startswith("ubi"):
            passive = "/dev/" + passive

        # Generate "update" appropriate for this test case.
        # Cheat a little. Instead of spending a lot of time on a lot of reboots,
        # just verify that the contents of the update are correct.
        new_content = sig_case.label
        with open("image.dat", "w") as fd:
            fd.write(new_content)
            # Write some extra data just to make sure the update is big enough
            # to be written even if the checksum is wrong. If it's too small it
            # may fail before it has a chance to be written.
            fd.write("\x00" * (1048576 * 8))

        artifact_args = ""

        # Generate artifact with or without signature.
        if sig_case.signature:
            artifact_args += " -k %s" % signing_key(sig_case.key_type).private

        # Generate artifact with specific version. None means default.
        if sig_case.artifact_version is not None:
            artifact_args += " -v %d" % sig_case.artifact_version

        if sig_case.key_type:
            sig_key = signing_key(sig_case.key_type)
        else:
            sig_key = None

        image_type = bitbake_variables["MENDER_DEVICE_TYPE"]

        subprocess.check_call(
            "mender-artifact write rootfs-image %s -t %s -n test-update %s image.dat -o image.mender"
            % (artifact_args, image_type, file_flag),
            shell=True,
        )

        # If instructed to, corrupt the signature and/or checksum.
        if ((sig_case.signature and not sig_case.signature_ok)
                or not sig_case.checksum_ok
                or not sig_case.header_checksum_ok):
            tar = subprocess.check_output(["tar", "tf", "image.mender"])
            tar_list = tar.split()
            tmpdir = tempfile.mkdtemp()
            try:
                shutil.copy("image.mender",
                            os.path.join(tmpdir, "image.mender"))
                cwd = os.open(".", os.O_RDONLY)
                os.chdir(tmpdir)
                try:
                    tar = subprocess.check_output(
                        ["tar", "xf", "image.mender"])
                    if not sig_case.signature_ok:
                        # Corrupt signature.
                        with open("manifest.sig", "r+") as fd:
                            Helpers.corrupt_middle_byte(fd)
                    if not sig_case.checksum_ok:
                        os.chdir("data")
                        try:
                            data_list = subprocess.check_output(
                                ["tar", "tzf", "0000.tar.gz"])
                            data_list = data_list.split()
                            subprocess.check_call(
                                ["tar", "xzf", "0000.tar.gz"])
                            # Corrupt checksum by changing file slightly.
                            with open("image.dat", "r+") as fd:
                                Helpers.corrupt_middle_byte(fd)
                            # Pack it up again in same order.
                            os.remove("0000.tar.gz")
                            subprocess.check_call(
                                ["tar", "czf", "0000.tar.gz"] + data_list)
                            for data_file in data_list:
                                os.remove(data_file)
                        finally:
                            os.chdir("..")

                    if not sig_case.header_checksum_ok:
                        data_list = subprocess.check_output(
                            ["tar", "tzf", "header.tar.gz"])
                        data_list = data_list.split()
                        subprocess.check_call(["tar", "xzf", "header.tar.gz"])
                        # Corrupt checksum by changing file slightly.
                        with open("headers/0000/files", "a") as fd:
                            # Some extra data to corrupt the header checksum,
                            # but still valid JSON.
                            fd.write(" ")
                        # Pack it up again in same order.
                        os.remove("header.tar.gz")
                        subprocess.check_call(["tar", "czf", "header.tar.gz"] +
                                              data_list)
                        for data_file in data_list:
                            os.remove(data_file)

                    # Make sure we put it back in the same order.
                    os.remove("image.mender")
                    subprocess.check_call(["tar", "cf", "image.mender"] +
                                          tar_list)
                finally:
                    os.fchdir(cwd)
                    os.close(cwd)

                shutil.move(os.path.join(tmpdir, "image.mender"),
                            "image.mender")

            finally:
                shutil.rmtree(tmpdir, ignore_errors=True)

        put_no_sftp("image.mender", connection, remote="/data/image.mender")

        # mender-convert'ed images don't have transient mender.conf
        device_has_mender_conf = (connection.run(
            "test -f /etc/mender/mender.conf", warn=True).return_code == 0)
        # mender-convert'ed images don't have this directory, but the test uses
        # it to save certificates
        connection.run("mkdir -p /data/etc/mender")

        try:
            # Get configuration from device or create an empty one
            if device_has_mender_conf:
                connection.run(
                    "cp /etc/mender/mender.conf /data/etc/mender/mender.conf.bak"
                )
                get_no_sftp("/etc/mender/mender.conf", connection)
            else:
                with open("mender.conf", "w") as fd:
                    json.dump({}, fd)

            # Update key in configuration.
            with open("mender.conf") as fd:
                config = json.load(fd)
            if sig_case.key:
                config[
                    "ArtifactVerifyKey"] = "/data/etc/mender/%s" % os.path.basename(
                        sig_key.public)
                put_no_sftp(
                    sig_key.public,
                    connection,
                    remote="/data/etc/mender/%s" %
                    os.path.basename(sig_key.public),
                )
            else:
                if config.get("ArtifactVerifyKey"):
                    del config["ArtifactVerifyKey"]

            # Send new configuration to device
            with open("mender.conf", "w") as fd:
                json.dump(config, fd)
            put_no_sftp("mender.conf",
                        connection,
                        remote="/etc/mender/mender.conf")
            os.remove("mender.conf")

            # Start by writing known "old" content in the partition.
            old_content = "Preexisting partition content"
            if "ubi" in passive:
                # ubi volumes cannot be directly written to, we have to use
                # ubiupdatevol
                connection.run('echo "%s" | dd of=/tmp/update.tmp && '
                               "ubiupdatevol %s /tmp/update.tmp; "
                               "rm -f /tmp/update.tmp" %
                               (old_content, passive))
            else:
                connection.run('echo "%s" | dd of=%s' % (old_content, passive))

            result = connection.run("mender install /data/image.mender",
                                    warn=True)

            if sig_case.success:
                if result.return_code != 0:
                    pytest.fail(
                        "Update failed when it should have succeeded: %s, Output: %s"
                        % (sig_case.label, result))
            else:
                if result.return_code == 0:
                    pytest.fail(
                        "Update succeeded when it should not have: %s, Output: %s"
                        % (sig_case.label, result))

            if sig_case.update_written:
                expected_content = new_content
            else:
                expected_content = old_content

            try:
                content = connection.run(
                    "dd if=%s bs=%d count=1" %
                    (passive, len(expected_content))).stdout
                assert content == expected_content, "Case: %s" % sig_case.label

            # In Fabric context, SystemExit means CalledProcessError. We should
            # not catch all exceptions, because we want to leave assertions
            # alone.
            # In Fabric2 there might be different exception thrown in that case
            # which is UnexpectedExit.
            except (SystemExit, UnexpectedExit):
                if ("mender-ubi" in bitbake_variables.get(
                        "MENDER_FEATURES", "").split()
                        or "mender-ubi" in bitbake_variables.get(
                            "DISTRO_FEATURES", "").split()):
                    # For UBI volumes specifically: The UBI_IOCVOLUP call which
                    # Mender uses prior to writing the data, takes a size
                    # argument, and if you don't write that amount of bytes, the
                    # volume is marked corrupted as a security measure. This
                    # sometimes triggers in our checksum mismatch tests, so
                    # accept the volume being unreadable in that case.
                    pass
                else:
                    raise

        finally:
            # Reset environment to what it was.
            _, bootenv_set = bootenv_tools(connection)
            connection.run(f"{bootenv_set} mender_boot_part %s" % active[-1:])
            connection.run(f"{bootenv_set} mender_boot_part_hex %x" %
                           int(active[-1:]))
            connection.run(f"{bootenv_set} upgrade_available 0")
            if device_has_mender_conf:
                connection.run(
                    "cp -L /data/etc/mender/mender.conf.bak $(realpath /etc/mender/mender.conf)"
                )
            if sig_key:
                connection.run("rm -f /etc/mender/%s" %
                               os.path.basename(sig_key.public))