Merge "releasetools: Add PayloadSigner class." am: 8413824f4b
am: 9704dddd1f
Change-Id: I61e2ca212ce17edd3c9d5b5d733101e9ecdaeb10
This commit is contained in:
commit
7e7e6a3005
9 changed files with 198 additions and 43 deletions
|
@ -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]
|
||||
|
||||
|
@ -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()
|
||||
|
@ -1135,37 +1173,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-",
|
||||
|
|
|
@ -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 @@ 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)
|
||||
|
|
4
tools/releasetools/testdata/payload_signer.sh
vendored
Executable file
4
tools/releasetools/testdata/payload_signer.sh
vendored
Executable file
|
@ -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
|
1
tools/releasetools/testdata/sigfile.bin
vendored
Normal file
1
tools/releasetools/testdata/sigfile.bin
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
<07>Q<EFBFBD><51>ܢ<EFBFBD><DCA2><EFBFBD><EFBFBD>Gp<47><70><EFBFBD><EFBFBD><10><><EFBFBD><EFBFBD>'[4K<34>L<>c
|
2
tools/releasetools/testdata/signed-sigfile.bin
vendored
Normal file
2
tools/releasetools/testdata/signed-sigfile.bin
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
R¡<13>&‹EÿsÁ%ø?¹|¤œ&<26>Í€ñzbSŠA[ßtqç†WKÒ‘l¦àÙÙås¥Ò~Fcæ <09>`ž¯¾Í#
|
||||
T{Ý×Û½FÒÁŸxƒø1‰6Ì‹=Q°•ŒVæ^Tß°ØxX£¶/þ#©êI'ÜîtcLp““¬ŸëovzÑ’Rá:õóWþ9(¹Á26Û̬ábÂBP1¸6ãnÒß±QÕC©g<C2A9>h;r‰²O}%Ľõˆ<C3B5>áo6ã”d“ê´Éãå2Y`¦ÕÛ¼ª¥_R“OrCa,èI"n(`–ínñÜÐbaiö¹Å¨ÔäS„×Ê)kžO[`6c¬e
|
BIN
tools/releasetools/testdata/testkey.pk8
vendored
Normal file
BIN
tools/releasetools/testdata/testkey.pk8
vendored
Normal file
Binary file not shown.
24
tools/releasetools/testdata/testkey.x509.pem
vendored
Normal file
24
tools/releasetools/testdata/testkey.x509.pem
vendored
Normal file
|
@ -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-----
|
BIN
tools/releasetools/testdata/testkey_with_passwd.pk8
vendored
Normal file
BIN
tools/releasetools/testdata/testkey_with_passwd.pk8
vendored
Normal file
Binary file not shown.
24
tools/releasetools/testdata/testkey_with_passwd.x509.pem
vendored
Normal file
24
tools/releasetools/testdata/testkey_with_passwd.x509.pem
vendored
Normal file
|
@ -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-----
|
Loading…
Reference in a new issue