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")
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))