Loading tools/releasetools/ota_from_target_files.py +54 −42 Original line number Diff line number Diff line Loading @@ -310,6 +310,56 @@ class BuildInfo(object): script.AssertOemProperty(prop, values, oem_no_mount) class PayloadSigner(object): """A class that wraps the payload signing works. When generating a Payload, hashes of the payload and metadata files will be signed with the device key, either by calling an external payload signer or by calling openssl with the package key. This class provides a unified interface, so that callers can just call PayloadSigner.Sign(). If an external payload signer has been specified (OPTIONS.payload_signer), it calls the signer with the provided args (OPTIONS.payload_signer_args). Note that the signing key should be provided as part of the payload_signer_args. Otherwise without an external signer, it uses the package key (OPTIONS.package_key) and calls openssl for the signing works. """ def __init__(self): if OPTIONS.payload_signer is None: # Prepare the payload signing key. private_key = OPTIONS.package_key + OPTIONS.private_key_suffix pw = OPTIONS.key_passwords[OPTIONS.package_key] cmd = ["openssl", "pkcs8", "-in", private_key, "-inform", "DER"] cmd.extend(["-passin", "pass:" + pw] if pw else ["-nocrypt"]) signing_key = common.MakeTempFile(prefix="key-", suffix=".key") cmd.extend(["-out", signing_key]) get_signing_key = common.Run(cmd, verbose=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) stdoutdata, _ = get_signing_key.communicate() assert get_signing_key.returncode == 0, \ "Failed to get signing key: {}".format(stdoutdata) self.signer = "openssl" self.signer_args = ["pkeyutl", "-sign", "-inkey", signing_key, "-pkeyopt", "digest:sha256"] else: self.signer = OPTIONS.payload_signer self.signer_args = OPTIONS.payload_signer_args def Sign(self, in_file): """Signs the given input file. Returns the output filename.""" out_file = common.MakeTempFile(prefix="signed-", suffix=".bin") cmd = [self.signer] + self.signer_args + ['-in', in_file, '-out', out_file] signing = common.Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) stdoutdata, _ = signing.communicate() assert signing.returncode == 0, \ "Failed to sign the input file: {}".format(stdoutdata) return out_file def SignOutput(temp_zip_name, output_zip_name): pw = OPTIONS.key_passwords[OPTIONS.package_key] Loading Loading @@ -1076,20 +1126,8 @@ def WriteABOTAPackageWithBrilloScript(target_file, output_file, # The place where the output from the subprocess should go. log_file = sys.stdout if OPTIONS.verbose else subprocess.PIPE # A/B updater expects a signing key in RSA format. Gets the key ready for # later use in step 3, unless a payload_signer has been specified. if OPTIONS.payload_signer is None: cmd = ["openssl", "pkcs8", "-in", OPTIONS.package_key + OPTIONS.private_key_suffix, "-inform", "DER"] pw = OPTIONS.key_passwords[OPTIONS.package_key] cmd.extend(["-passin", "pass:" + pw] if pw else ["-nocrypt"]) rsa_key = common.MakeTempFile(prefix="key-", suffix=".key") cmd.extend(["-out", rsa_key]) p1 = common.Run(cmd, verbose=False, stdout=log_file, stderr=subprocess.STDOUT) p1.communicate() assert p1.returncode == 0, "openssl pkcs8 failed" # Get the PayloadSigner to be used in step 3. payload_signer = PayloadSigner() # Stage the output zip package for package signing. temp_zip_file = tempfile.NamedTemporaryFile() Loading Loading @@ -1130,37 +1168,11 @@ def WriteABOTAPackageWithBrilloScript(target_file, output_file, assert p1.returncode == 0, "brillo_update_payload hash failed" # 3. Sign the hashes and insert them back into the payload file. signed_payload_sig_file = common.MakeTempFile(prefix="signed-sig-", suffix=".bin") signed_metadata_sig_file = common.MakeTempFile(prefix="signed-sig-", suffix=".bin") # 3a. Sign the payload hash. if OPTIONS.payload_signer is not None: cmd = [OPTIONS.payload_signer] cmd.extend(OPTIONS.payload_signer_args) else: cmd = ["openssl", "pkeyutl", "-sign", "-inkey", rsa_key, "-pkeyopt", "digest:sha256"] cmd.extend(["-in", payload_sig_file, "-out", signed_payload_sig_file]) p1 = common.Run(cmd, stdout=log_file, stderr=subprocess.STDOUT) p1.communicate() assert p1.returncode == 0, "openssl sign payload failed" signed_payload_sig_file = payload_signer.Sign(payload_sig_file) # 3b. Sign the metadata hash. if OPTIONS.payload_signer is not None: cmd = [OPTIONS.payload_signer] cmd.extend(OPTIONS.payload_signer_args) else: cmd = ["openssl", "pkeyutl", "-sign", "-inkey", rsa_key, "-pkeyopt", "digest:sha256"] cmd.extend(["-in", metadata_sig_file, "-out", signed_metadata_sig_file]) p1 = common.Run(cmd, stdout=log_file, stderr=subprocess.STDOUT) p1.communicate() assert p1.returncode == 0, "openssl sign metadata failed" signed_metadata_sig_file = payload_signer.Sign(metadata_sig_file) # 3c. Insert the signatures back into the payload file. signed_payload_file = common.MakeTempFile(prefix="signed-payload-", Loading tools/releasetools/test_ota_from_target_files.py +89 −1 Original line number Diff line number Diff line Loading @@ -15,11 +15,20 @@ # import copy import os.path import unittest import common from ota_from_target_files import ( _LoadOemDicts, BuildInfo, GetPackageMetadata, WriteFingerprintAssertion) _LoadOemDicts, BuildInfo, GetPackageMetadata, PayloadSigner, WriteFingerprintAssertion) def get_testdata_dir(): """Returns the testdata dir, in relative to the script dir.""" # The script dir is the one we want, which could be different from pwd. current_dir = os.path.dirname(os.path.realpath(__file__)) return os.path.join(current_dir, 'testdata') class MockScriptWriter(object): Loading Loading @@ -476,3 +485,82 @@ class OtaFromTargetFilesTest(unittest.TestCase): 'pre-build-incremental' : 'build-version-incremental-source', }, metadata) class PayloadSignerTest(unittest.TestCase): SIGFILE = 'sigfile.bin' SIGNED_SIGFILE = 'signed-sigfile.bin' def setUp(self): self.testdata_dir = get_testdata_dir() self.assertTrue(os.path.exists(self.testdata_dir)) common.OPTIONS.payload_signer = None common.OPTIONS.payload_signer_args = [] common.OPTIONS.package_key = os.path.join(self.testdata_dir, 'testkey') common.OPTIONS.key_passwords = { common.OPTIONS.package_key : None, } def tearDown(self): common.Cleanup() def _assertFilesEqual(self, file1, file2): with open(file1, 'rb') as fp1, open(file2, 'rb') as fp2: self.assertEqual(fp1.read(), fp2.read()) def test_init(self): payload_signer = PayloadSigner() self.assertEqual('openssl', payload_signer.signer) def test_init_withPassword(self): common.OPTIONS.package_key = os.path.join( self.testdata_dir, 'testkey_with_passwd') common.OPTIONS.key_passwords = { common.OPTIONS.package_key : 'foo', } payload_signer = PayloadSigner() self.assertEqual('openssl', payload_signer.signer) def test_init_withExternalSigner(self): common.OPTIONS.payload_signer = 'abc' common.OPTIONS.payload_signer_args = ['arg1', 'arg2'] payload_signer = PayloadSigner() self.assertEqual('abc', payload_signer.signer) self.assertEqual(['arg1', 'arg2'], payload_signer.signer_args) def test_Sign(self): payload_signer = PayloadSigner() input_file = os.path.join(self.testdata_dir, self.SIGFILE) signed_file = payload_signer.Sign(input_file) verify_file = os.path.join(self.testdata_dir, self.SIGNED_SIGFILE) self._assertFilesEqual(verify_file, signed_file) def test_Sign_withExternalSigner_openssl(self): """Uses openssl as the external payload signer.""" common.OPTIONS.payload_signer = 'openssl' common.OPTIONS.payload_signer_args = [ 'pkeyutl', '-sign', '-keyform', 'DER', '-inkey', os.path.join(self.testdata_dir, 'testkey.pk8'), '-pkeyopt', 'digest:sha256'] payload_signer = PayloadSigner() input_file = os.path.join(self.testdata_dir, self.SIGFILE) signed_file = payload_signer.Sign(input_file) verify_file = os.path.join(self.testdata_dir, self.SIGNED_SIGFILE) self._assertFilesEqual(verify_file, signed_file) def test_Sign_withExternalSigner_script(self): """Uses testdata/payload_signer.sh as the external payload signer.""" common.OPTIONS.payload_signer = os.path.join( self.testdata_dir, 'payload_signer.sh') common.OPTIONS.payload_signer_args = [ os.path.join(self.testdata_dir, 'testkey.pk8')] payload_signer = PayloadSigner() input_file = os.path.join(self.testdata_dir, self.SIGFILE) signed_file = payload_signer.Sign(input_file) verify_file = os.path.join(self.testdata_dir, self.SIGNED_SIGFILE) self._assertFilesEqual(verify_file, signed_file) tools/releasetools/testdata/payload_signer.sh 0 → 100755 +4 −0 Original line number Diff line number Diff line #!/bin/sh # The script will be called with 'payload_signer.sh <key> -in <input> -out <output>'. openssl pkeyutl -sign -keyform DER -inkey $1 -pkeyopt digest:sha256 -in $3 -out $5 tools/releasetools/testdata/sigfile.bin 0 → 100644 +1 −0 Original line number Diff line number Diff line QܢGp'[4KLc No newline at end of file tools/releasetools/testdata/signed-sigfile.bin 0 → 100644 +2 −0 Original line number Diff line number Diff line R&Es%?|&̀zbSA[tqWKґls~Fc `# T{۽Fx16̋=QV^T߰xX/#I'tcLpovzђR:W9(26̬bBP16n߱QCgh;rO}%Ľo6d2Y`ۼ_ROrCa,I"n(`nbaiŨS)kO[`6ce No newline at end of file Loading
tools/releasetools/ota_from_target_files.py +54 −42 Original line number Diff line number Diff line Loading @@ -310,6 +310,56 @@ class BuildInfo(object): script.AssertOemProperty(prop, values, oem_no_mount) class PayloadSigner(object): """A class that wraps the payload signing works. When generating a Payload, hashes of the payload and metadata files will be signed with the device key, either by calling an external payload signer or by calling openssl with the package key. This class provides a unified interface, so that callers can just call PayloadSigner.Sign(). If an external payload signer has been specified (OPTIONS.payload_signer), it calls the signer with the provided args (OPTIONS.payload_signer_args). Note that the signing key should be provided as part of the payload_signer_args. Otherwise without an external signer, it uses the package key (OPTIONS.package_key) and calls openssl for the signing works. """ def __init__(self): if OPTIONS.payload_signer is None: # Prepare the payload signing key. private_key = OPTIONS.package_key + OPTIONS.private_key_suffix pw = OPTIONS.key_passwords[OPTIONS.package_key] cmd = ["openssl", "pkcs8", "-in", private_key, "-inform", "DER"] cmd.extend(["-passin", "pass:" + pw] if pw else ["-nocrypt"]) signing_key = common.MakeTempFile(prefix="key-", suffix=".key") cmd.extend(["-out", signing_key]) get_signing_key = common.Run(cmd, verbose=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) stdoutdata, _ = get_signing_key.communicate() assert get_signing_key.returncode == 0, \ "Failed to get signing key: {}".format(stdoutdata) self.signer = "openssl" self.signer_args = ["pkeyutl", "-sign", "-inkey", signing_key, "-pkeyopt", "digest:sha256"] else: self.signer = OPTIONS.payload_signer self.signer_args = OPTIONS.payload_signer_args def Sign(self, in_file): """Signs the given input file. Returns the output filename.""" out_file = common.MakeTempFile(prefix="signed-", suffix=".bin") cmd = [self.signer] + self.signer_args + ['-in', in_file, '-out', out_file] signing = common.Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) stdoutdata, _ = signing.communicate() assert signing.returncode == 0, \ "Failed to sign the input file: {}".format(stdoutdata) return out_file def SignOutput(temp_zip_name, output_zip_name): pw = OPTIONS.key_passwords[OPTIONS.package_key] Loading Loading @@ -1076,20 +1126,8 @@ def WriteABOTAPackageWithBrilloScript(target_file, output_file, # The place where the output from the subprocess should go. log_file = sys.stdout if OPTIONS.verbose else subprocess.PIPE # A/B updater expects a signing key in RSA format. Gets the key ready for # later use in step 3, unless a payload_signer has been specified. if OPTIONS.payload_signer is None: cmd = ["openssl", "pkcs8", "-in", OPTIONS.package_key + OPTIONS.private_key_suffix, "-inform", "DER"] pw = OPTIONS.key_passwords[OPTIONS.package_key] cmd.extend(["-passin", "pass:" + pw] if pw else ["-nocrypt"]) rsa_key = common.MakeTempFile(prefix="key-", suffix=".key") cmd.extend(["-out", rsa_key]) p1 = common.Run(cmd, verbose=False, stdout=log_file, stderr=subprocess.STDOUT) p1.communicate() assert p1.returncode == 0, "openssl pkcs8 failed" # Get the PayloadSigner to be used in step 3. payload_signer = PayloadSigner() # Stage the output zip package for package signing. temp_zip_file = tempfile.NamedTemporaryFile() Loading Loading @@ -1130,37 +1168,11 @@ def WriteABOTAPackageWithBrilloScript(target_file, output_file, assert p1.returncode == 0, "brillo_update_payload hash failed" # 3. Sign the hashes and insert them back into the payload file. signed_payload_sig_file = common.MakeTempFile(prefix="signed-sig-", suffix=".bin") signed_metadata_sig_file = common.MakeTempFile(prefix="signed-sig-", suffix=".bin") # 3a. Sign the payload hash. if OPTIONS.payload_signer is not None: cmd = [OPTIONS.payload_signer] cmd.extend(OPTIONS.payload_signer_args) else: cmd = ["openssl", "pkeyutl", "-sign", "-inkey", rsa_key, "-pkeyopt", "digest:sha256"] cmd.extend(["-in", payload_sig_file, "-out", signed_payload_sig_file]) p1 = common.Run(cmd, stdout=log_file, stderr=subprocess.STDOUT) p1.communicate() assert p1.returncode == 0, "openssl sign payload failed" signed_payload_sig_file = payload_signer.Sign(payload_sig_file) # 3b. Sign the metadata hash. if OPTIONS.payload_signer is not None: cmd = [OPTIONS.payload_signer] cmd.extend(OPTIONS.payload_signer_args) else: cmd = ["openssl", "pkeyutl", "-sign", "-inkey", rsa_key, "-pkeyopt", "digest:sha256"] cmd.extend(["-in", metadata_sig_file, "-out", signed_metadata_sig_file]) p1 = common.Run(cmd, stdout=log_file, stderr=subprocess.STDOUT) p1.communicate() assert p1.returncode == 0, "openssl sign metadata failed" signed_metadata_sig_file = payload_signer.Sign(metadata_sig_file) # 3c. Insert the signatures back into the payload file. signed_payload_file = common.MakeTempFile(prefix="signed-payload-", Loading
tools/releasetools/test_ota_from_target_files.py +89 −1 Original line number Diff line number Diff line Loading @@ -15,11 +15,20 @@ # import copy import os.path import unittest import common from ota_from_target_files import ( _LoadOemDicts, BuildInfo, GetPackageMetadata, WriteFingerprintAssertion) _LoadOemDicts, BuildInfo, GetPackageMetadata, PayloadSigner, WriteFingerprintAssertion) def get_testdata_dir(): """Returns the testdata dir, in relative to the script dir.""" # The script dir is the one we want, which could be different from pwd. current_dir = os.path.dirname(os.path.realpath(__file__)) return os.path.join(current_dir, 'testdata') class MockScriptWriter(object): Loading Loading @@ -476,3 +485,82 @@ class OtaFromTargetFilesTest(unittest.TestCase): 'pre-build-incremental' : 'build-version-incremental-source', }, metadata) class PayloadSignerTest(unittest.TestCase): SIGFILE = 'sigfile.bin' SIGNED_SIGFILE = 'signed-sigfile.bin' def setUp(self): self.testdata_dir = get_testdata_dir() self.assertTrue(os.path.exists(self.testdata_dir)) common.OPTIONS.payload_signer = None common.OPTIONS.payload_signer_args = [] common.OPTIONS.package_key = os.path.join(self.testdata_dir, 'testkey') common.OPTIONS.key_passwords = { common.OPTIONS.package_key : None, } def tearDown(self): common.Cleanup() def _assertFilesEqual(self, file1, file2): with open(file1, 'rb') as fp1, open(file2, 'rb') as fp2: self.assertEqual(fp1.read(), fp2.read()) def test_init(self): payload_signer = PayloadSigner() self.assertEqual('openssl', payload_signer.signer) def test_init_withPassword(self): common.OPTIONS.package_key = os.path.join( self.testdata_dir, 'testkey_with_passwd') common.OPTIONS.key_passwords = { common.OPTIONS.package_key : 'foo', } payload_signer = PayloadSigner() self.assertEqual('openssl', payload_signer.signer) def test_init_withExternalSigner(self): common.OPTIONS.payload_signer = 'abc' common.OPTIONS.payload_signer_args = ['arg1', 'arg2'] payload_signer = PayloadSigner() self.assertEqual('abc', payload_signer.signer) self.assertEqual(['arg1', 'arg2'], payload_signer.signer_args) def test_Sign(self): payload_signer = PayloadSigner() input_file = os.path.join(self.testdata_dir, self.SIGFILE) signed_file = payload_signer.Sign(input_file) verify_file = os.path.join(self.testdata_dir, self.SIGNED_SIGFILE) self._assertFilesEqual(verify_file, signed_file) def test_Sign_withExternalSigner_openssl(self): """Uses openssl as the external payload signer.""" common.OPTIONS.payload_signer = 'openssl' common.OPTIONS.payload_signer_args = [ 'pkeyutl', '-sign', '-keyform', 'DER', '-inkey', os.path.join(self.testdata_dir, 'testkey.pk8'), '-pkeyopt', 'digest:sha256'] payload_signer = PayloadSigner() input_file = os.path.join(self.testdata_dir, self.SIGFILE) signed_file = payload_signer.Sign(input_file) verify_file = os.path.join(self.testdata_dir, self.SIGNED_SIGFILE) self._assertFilesEqual(verify_file, signed_file) def test_Sign_withExternalSigner_script(self): """Uses testdata/payload_signer.sh as the external payload signer.""" common.OPTIONS.payload_signer = os.path.join( self.testdata_dir, 'payload_signer.sh') common.OPTIONS.payload_signer_args = [ os.path.join(self.testdata_dir, 'testkey.pk8')] payload_signer = PayloadSigner() input_file = os.path.join(self.testdata_dir, self.SIGFILE) signed_file = payload_signer.Sign(input_file) verify_file = os.path.join(self.testdata_dir, self.SIGNED_SIGFILE) self._assertFilesEqual(verify_file, signed_file)
tools/releasetools/testdata/payload_signer.sh 0 → 100755 +4 −0 Original line number Diff line number Diff line #!/bin/sh # The script will be called with 'payload_signer.sh <key> -in <input> -out <output>'. openssl pkeyutl -sign -keyform DER -inkey $1 -pkeyopt digest:sha256 -in $3 -out $5
tools/releasetools/testdata/sigfile.bin 0 → 100644 +1 −0 Original line number Diff line number Diff line QܢGp'[4KLc No newline at end of file
tools/releasetools/testdata/signed-sigfile.bin 0 → 100644 +2 −0 Original line number Diff line number Diff line R&Es%?|&̀zbSA[tqWKґls~Fc `# T{۽Fx16̋=QV^T߰xX/#I'tcLpovzђR:W9(26̬bBP16n߱QCgh;rO}%Ľo6d2Y`ۼ_ROrCa,I"n(`nbaiŨS)kO[`6ce No newline at end of file