diff --git a/tools/releasetools/check_ota_package_signature.py b/tools/releasetools/check_ota_package_signature.py index b5e9d8bff..81b3c1ef4 100755 --- a/tools/releasetools/check_ota_package_signature.py +++ b/tools/releasetools/check_ota_package_signature.py @@ -154,15 +154,11 @@ def VerifyAbOtaPayload(cert, package): print('Verifying A/B OTA payload signatures...') # Dump pubkey from the certificate. - pubkey = common.MakeTempFile(prefix="key-", suffix=".key") - cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert, '-out', pubkey] - proc = common.Run(cmd, stdout=subprocess.PIPE) - stdoutdata, _ = proc.communicate() - assert proc.returncode == 0, \ - 'Failed to dump public key from certificate: %s\n%s' % (cert, stdoutdata) + pubkey = common.MakeTempFile(prefix="key-", suffix=".pem") + with open(pubkey, 'wb') as pubkey_fp: + pubkey_fp.write(common.ExtractPublicKey(cert)) - package_dir = tempfile.mkdtemp(prefix='package-') - common.OPTIONS.tempfiles.append(package_dir) + package_dir = common.MakeTempDir(prefix='package-') # Signature verification with delta_generator. payload_file = package_zip.extract('payload.bin', package_dir) diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py index d09f60c75..16600ed1a 100644 --- a/tools/releasetools/common.py +++ b/tools/releasetools/common.py @@ -1804,6 +1804,31 @@ def ParseCertificate(data): cert = "".join(cert).decode('base64') return cert + +def ExtractPublicKey(cert): + """Extracts the public key (PEM-encoded) from the given certificate file. + + Args: + cert: The certificate filename. + + Returns: + The public key string. + + Raises: + AssertionError: On non-zero return from 'openssl'. + """ + # The behavior with '-out' is different between openssl 1.1 and openssl 1.0. + # While openssl 1.1 writes the key into the given filename followed by '-out', + # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from + # stdout instead. + cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert] + proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + pubkey, stderrdata = proc.communicate() + assert proc.returncode == 0, \ + 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata) + return pubkey + + def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img, info_dict=None): """Generate a binary patch that creates the recovery image starting diff --git a/tools/releasetools/sign_target_files_apks.py b/tools/releasetools/sign_target_files_apks.py index 7a1126c2d..1f9a3cadb 100755 --- a/tools/releasetools/sign_target_files_apks.py +++ b/tools/releasetools/sign_target_files_apks.py @@ -538,10 +538,7 @@ def ReplaceOtaKeys(input_tf_zip, output_tf_zip, misc_info): " as payload verification key.\n\n") print("Using %s for payload verification." % (mapped_keys[0],)) - cmd = common.Run( - ["openssl", "x509", "-pubkey", "-noout", "-in", mapped_keys[0]], - stdout=subprocess.PIPE) - pubkey, _ = cmd.communicate() + pubkey = common.ExtractPublicKey(mapped_keys[0]) common.ZipWriteStr( output_tf_zip, "SYSTEM/etc/update_engine/update-payload-key.pub.pem", diff --git a/tools/releasetools/test_common.py b/tools/releasetools/test_common.py index 8fb46000d..6da286ce4 100644 --- a/tools/releasetools/test_common.py +++ b/tools/releasetools/test_common.py @@ -21,8 +21,10 @@ import zipfile from hashlib import sha1 import common +import test_utils import validate_target_files + KiB = 1024 MiB = 1024 * KiB GiB = 1024 * MiB @@ -474,6 +476,18 @@ class CommonApkUtilsTest(unittest.TestCase): with zipfile.ZipFile(target_files, 'r') as input_zip: self.assertRaises(ValueError, common.ReadApkCerts, input_zip) + def test_ExtractPublicKey(self): + testdata_dir = test_utils.get_testdata_dir() + cert = os.path.join(testdata_dir, 'testkey.x509.pem') + pubkey = os.path.join(testdata_dir, 'testkey.pubkey.pem') + with open(pubkey, 'rb') as pubkey_fp: + self.assertEqual(pubkey_fp.read(), common.ExtractPublicKey(cert)) + + def test_ExtractPublicKey_invalidInput(self): + testdata_dir = test_utils.get_testdata_dir() + wrong_input = os.path.join(testdata_dir, 'testkey.pk8') + self.assertRaises(AssertionError, common.ExtractPublicKey, wrong_input) + class InstallRecoveryScriptFormatTest(unittest.TestCase): """Checks the format of install-recovery.sh. diff --git a/tools/releasetools/test_ota_from_target_files.py b/tools/releasetools/test_ota_from_target_files.py index de03be398..849ca1db4 100644 --- a/tools/releasetools/test_ota_from_target_files.py +++ b/tools/releasetools/test_ota_from_target_files.py @@ -21,18 +21,12 @@ import unittest import zipfile import common +import test_utils from ota_from_target_files import ( _LoadOemDicts, BuildInfo, GetPackageMetadata, Payload, 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): """A class that mocks edify_generator.EdifyGenerator. @@ -513,7 +507,7 @@ class PayloadSignerTest(unittest.TestCase): SIGNED_SIGFILE = 'signed-sigfile.bin' def setUp(self): - self.testdata_dir = get_testdata_dir() + self.testdata_dir = test_utils.get_testdata_dir() self.assertTrue(os.path.exists(self.testdata_dir)) common.OPTIONS.payload_signer = None @@ -589,7 +583,7 @@ class PayloadSignerTest(unittest.TestCase): class PayloadTest(unittest.TestCase): def setUp(self): - self.testdata_dir = get_testdata_dir() + self.testdata_dir = test_utils.get_testdata_dir() self.assertTrue(os.path.exists(self.testdata_dir)) common.OPTIONS.wipe_user_data = False diff --git a/tools/releasetools/test_utils.py b/tools/releasetools/test_utils.py new file mode 100644 index 000000000..ec53731cb --- /dev/null +++ b/tools/releasetools/test_utils.py @@ -0,0 +1,28 @@ +# +# Copyright (C) 2018 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +""" +Utils for running unittests. +""" + +import os.path + + +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') diff --git a/tools/releasetools/testdata/testkey.pubkey.pem b/tools/releasetools/testdata/testkey.pubkey.pem new file mode 100644 index 000000000..418ae6086 --- /dev/null +++ b/tools/releasetools/testdata/testkey.pubkey.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvjvyO2LwWgmQNyq7z+xK +04eg0t3AL4y2NhpAAOzVnFyCArFcFjLTGQDDvkbZP6N12O6+dwJoPLntnm9A+VnP +IFFRHg0HUWSbHM+Qk8Jgv2/2AVkAUj5J1r9t4X+2WI0eRzJP15Zjn68pQKGmcyci +ry0gbvmYvXL2ZUmTm56DmEfCUCRIY2IGJ/CcMnFeItVU0LxKsV5Mlt5BO0Vv/CV4 +EaiOLwyCnoZuUhYto7dHlO/47v/H9zhkJC54OA1dkD38EPgO5GnfhGFSNXQRmJDT +XrFgd6O+QO4yUNX8lYP10MzimUpItZa05t68NADqwYl3T7nWzvuC9r4IqZDyPf21 +TQIDAQAB +-----END PUBLIC KEY-----