releasetools: Add PayloadSigner class.
Create a wrapper class that handles the payload signing, which unifies
the paths with and without external signer. Also add tests for the newly
added class.
The test keys under testdata/ are created with the script in
development/tools/make_key. testdata/testkey_with_passwd.pk8 uses
password "foo".
Test: python -m unittest test_ota_from_target_files
Test: Get identical A/B OTA packages w/ and w/o the CL.
Change-Id: Ic770aec726498a3babb88ec509985e7f1210fb18
diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index 88cb741..95b7303 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -310,6 +310,56 @@
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]
@@ -1076,20 +1126,8 @@
# 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()
@@ -1130,37 +1168,11 @@
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-",
diff --git a/tools/releasetools/test_ota_from_target_files.py b/tools/releasetools/test_ota_from_target_files.py
index 5f6c5d0..fa6655b 100644
--- a/tools/releasetools/test_ota_from_target_files.py
+++ b/tools/releasetools/test_ota_from_target_files.py
@@ -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):
@@ -476,3 +485,82 @@
'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)
diff --git a/tools/releasetools/testdata/payload_signer.sh b/tools/releasetools/testdata/payload_signer.sh
new file mode 100755
index 0000000..a44ef34
--- /dev/null
+++ b/tools/releasetools/testdata/payload_signer.sh
@@ -0,0 +1,4 @@
+#!/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
diff --git a/tools/releasetools/testdata/sigfile.bin b/tools/releasetools/testdata/sigfile.bin
new file mode 100644
index 0000000..8682216
--- /dev/null
+++ b/tools/releasetools/testdata/sigfile.bin
@@ -0,0 +1 @@
+ºQàÂÜ¢¡½¨Gpø£Õùù°ÔÖ'[4KéL¡c
\ No newline at end of file
diff --git a/tools/releasetools/testdata/signed-sigfile.bin b/tools/releasetools/testdata/signed-sigfile.bin
new file mode 100644
index 0000000..86d2f9e
--- /dev/null
+++ b/tools/releasetools/testdata/signed-sigfile.bin
@@ -0,0 +1,2 @@
+R¡&EÿsÁ%ø?¹|¤&ÍñzbSA[ßtqçWKÒl¦àÙÙås¥Ò~Fcæ `¯¾Í#
+T{Ý×Û½FÒÁxø16Ì=Q°Væ^Tß°ØxX£¶/þ#©êI'ÜîtcLp¬ëovzÑRá:õóWþ9(¹Á26Û̬ábÂBP1¸6ãnÒß±QÕC©gh;r²O}%Ľõáo6ãdê´Éãå2Y`¦ÕÛ¼ª¥_ROrCa,èI"n(`ínñÜÐbaiö¹Å¨ÔäS×Ê)kO[`6c¬e
\ No newline at end of file
diff --git a/tools/releasetools/testdata/testkey.pk8 b/tools/releasetools/testdata/testkey.pk8
new file mode 100644
index 0000000..99be291
--- /dev/null
+++ b/tools/releasetools/testdata/testkey.pk8
Binary files differ
diff --git a/tools/releasetools/testdata/testkey.x509.pem b/tools/releasetools/testdata/testkey.x509.pem
new file mode 100644
index 0000000..65c8085
--- /dev/null
+++ b/tools/releasetools/testdata/testkey.x509.pem
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIIEADCCAuigAwIBAgIJAN/FvjYzGNOKMA0GCSqGSIb3DQEBCwUAMIGUMQswCQYD
+VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4g
+VmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEQMA4GA1UE
+AwwHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe
+Fw0xODAxMTgwMDM0NTFaFw00NTA2MDUwMDM0NTFaMIGUMQswCQYDVQQGEwJVUzET
+MBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEQMA4G
+A1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEQMA4GA1UEAwwHQW5kcm9p
+ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAL478jti8FoJkDcqu8/sStOHoNLdwC+MtjYa
+QADs1ZxcggKxXBYy0xkAw75G2T+jddjuvncCaDy57Z5vQPlZzyBRUR4NB1FkmxzP
+kJPCYL9v9gFZAFI+Sda/beF/tliNHkcyT9eWY5+vKUChpnMnIq8tIG75mL1y9mVJ
+k5ueg5hHwlAkSGNiBifwnDJxXiLVVNC8SrFeTJbeQTtFb/wleBGoji8Mgp6GblIW
+LaO3R5Tv+O7/x/c4ZCQueDgNXZA9/BD4DuRp34RhUjV0EZiQ016xYHejvkDuMlDV
+/JWD9dDM4plKSLWWtObevDQA6sGJd0+51s77gva+CKmQ8j39tU0CAwEAAaNTMFEw
+HQYDVR0OBBYEFNJPJZDpq6tc/19Z2kxPA2bj9D6UMB8GA1UdIwQYMBaAFNJPJZDp
+q6tc/19Z2kxPA2bj9D6UMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQAD
+ggEBABSUG9qrwV3WcClDJwqkNLN4yeVVYzkRMGA8/XqOiYrW4zh0mKDLfr6OeU1C
+AKwZBLhhql59Po25r4gcwPiTN2DkoCfb3T59XG8J54PAgTQjIAZ3J+mGZplnmuD3
+wj+UGUpPe0qTr33ZPoJfwxVo4RVnOt/UCsIGXch0HS/BIdpechqP0w4rOHUbq6EA
+8UEi5irKSDOU9b/5rD/tX2f4nGwJlKQEHWrsj9LLKlaL7fX36ghoSxN/pBJOhedg
+/VjT6xbaEwfyhC6Zj9av5Xl7UdpYt+rBMroAGenz0OSxKhIphdcx4ZMhvfkBoYG9
+Crupdqe+kUsfg2RlPb5grQ3klMo=
+-----END CERTIFICATE-----
diff --git a/tools/releasetools/testdata/testkey_with_passwd.pk8 b/tools/releasetools/testdata/testkey_with_passwd.pk8
new file mode 100644
index 0000000..3d567de
--- /dev/null
+++ b/tools/releasetools/testdata/testkey_with_passwd.pk8
Binary files differ
diff --git a/tools/releasetools/testdata/testkey_with_passwd.x509.pem b/tools/releasetools/testdata/testkey_with_passwd.x509.pem
new file mode 100644
index 0000000..449396e
--- /dev/null
+++ b/tools/releasetools/testdata/testkey_with_passwd.x509.pem
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIIEADCCAuigAwIBAgIJANefUd3Piu0yMA0GCSqGSIb3DQEBCwUAMIGUMQswCQYD
+VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4g
+VmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEQMA4GA1UE
+AwwHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe
+Fw0xODAxMTgwMDI3NDRaFw00NTA2MDUwMDI3NDRaMIGUMQswCQYDVQQGEwJVUzET
+MBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEQMA4G
+A1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEQMA4GA1UEAwwHQW5kcm9p
+ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBALBoA4c+qCQKapQAVGclbousC5J/L0TNZJEd
+KSW2nzXUHIwgTQ3r82227xkIvjnqXMCsc0q3/N2gGKR4sHqA30JO9Dyfgsx1ISaR
+GXe5cG048m5U5snplQgvPovtah9ZyvwNPzWPYC3uceJaDxKQKwVdsV+mOWM6WmpQ
+bdLO37jxfytyAbzaz3sG5HA3FSB8rX/xDM6If18NsxSHpcjaOjZXC4Fg6wlp0klY
+5/qhFEdmieu2zQVelXjoJfKSku8tPa7kZeDU/F3uLUq/U/xvFk7NVsRV+QvYOdQK
+1QECc/3yv1TKNAN3huWTgzCX6bMHmi09Npw3MQaGY0oS34cH9x0CAwEAAaNTMFEw
+HQYDVR0OBBYEFNsJZ0n9Opeea0rVAzL+1jwkDKzPMB8GA1UdIwQYMBaAFNsJZ0n9
+Opeea0rVAzL+1jwkDKzPMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQAD
+ggEBAJ/bzIzA+NrYwPEv56XKf6Vuj81+M1rTHAsH9PqbOvJT7iM7aU7wAl6vmXAo
+DQtvKoOBMdIXprapwe0quHCQm7PGxg+RRegr+dcTSVJFv1plnODOBOEAVlEfFwuW
+Cz0USF2jrNq+4ciH5zPL1a31ONb1rMkxJXQ/tAi0x8m6tZz+jsbE0wO6qB80UmkA
+4WY2Tu/gnAvFpD8plkiU0EKwedBHAcaFFZkQp23MKsVZ3UBqsqzzfXDYV1Oa6rIy
+XIZpI2Gx75pvAb57T2ap/yl0DBEAu7Nmpll0GCsgeJVdy7tS4LNj96Quya3CHWQw
+WNTVuan0KZqwDIm4Xn1oHUFQ9vc=
+-----END CERTIFICATE-----