Merge "releasetools: Add PayloadSigner class."

This commit is contained in:
Treehugger Robot 2018-01-22 18:54:57 +00:00 committed by Gerrit Code Review
commit 8413824f4b
9 changed files with 198 additions and 43 deletions

View file

@ -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()
@ -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-",

View file

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

View 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

View file

@ -0,0 +1 @@
<07>Q<EFBFBD><51>ܢ<EFBFBD><DCA2><EFBFBD><EFBFBD>Gp<47><70><EFBFBD><EFBFBD><10><><EFBFBD><EFBFBD>'[4K<34>L<>c

View file

@ -0,0 +1,2 @@
<13>&EÿsÁ%ø?¹|¤œ&<26>Í€ñzbSŠA[ßtqç†WKÒl¦àÙÙås¥Ò~Fcæ <09>`ž¯¾Í#
T{Ý×Û½F­ÒÁŸxƒø1‰=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

Binary file not shown.

View 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-----

Binary file not shown.

View 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-----