Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9704dddd authored by Tao Bao's avatar Tao Bao Committed by android-build-merger
Browse files

Merge "releasetools: Add PayloadSigner class."

am: 8413824f

Change-Id: I887444e9df1a3ebd5af3e3ce82128a5f4d43cae9
parents ef29a15a 8413824f
Loading
Loading
Loading
Loading
+54 −42
Original line number Diff line number Diff line
@@ -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-",
+89 −1
Original line number Diff line number Diff line
@@ -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 −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
+1 −0
Original line number Diff line number Diff line
QܢGp'[4KLc
 No newline at end of file
+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