Implement AVB Android Things eXtension (ATX)

Android Things requires specific public key metadata and verification
logic to correctly verify vbmeta public keys. This CL includes the
verification logic, tools to create metadata, and documentation.

Bug: 33553097
Test: unit tests
Change-Id: I98955d3616f4c3ca1b893eb3941041e5e735fc7e
(cherry picked from commit f89ed80e7d6c1a1526a032cefca80ab146ec1753)
diff --git a/avbtool b/avbtool
index 328f927..4b1ed45 100755
--- a/avbtool
+++ b/avbtool
@@ -33,6 +33,7 @@
 import subprocess
 import sys
 import tempfile
+import time
 
 import Crypto.PublicKey.RSA
 
@@ -373,6 +374,41 @@
   raise AvbError('Unknown algorithm type {}'.format(alg_type))
 
 
+def raw_sign(signing_helper, algorithm_name, key_path, raw_data_to_sign):
+  """Computes a raw RSA signature using |signing_helper| or openssl.
+
+  Arguments:
+    signing_helper: Program which signs a hash and returns the signature.
+    algorithm_name: The algorithm name as per the ALGORITHMS dict.
+    key_path: Path to the private key file. Must be PEM format.
+    raw_data_to_sign: Data to sign (bytearray or str expected).
+
+  Returns:
+    A bytearray containing the signature.
+
+  Raises:
+    Exception: If an error occurs.
+  """
+  p = None
+  if signing_helper is not None:
+      p = subprocess.Popen(
+          [signing_helper, algorithm_name, key_path],
+          stdin=subprocess.PIPE,
+          stdout=subprocess.PIPE,
+          stderr=subprocess.PIPE)
+  else:
+      p = subprocess.Popen(
+          ['openssl', 'rsautl', '-sign', '-inkey', key_path, '-raw'],
+          stdin=subprocess.PIPE,
+          stdout=subprocess.PIPE,
+          stderr=subprocess.PIPE)
+  (pout, perr) = p.communicate(str(raw_data_to_sign))
+  retcode = p.wait()
+  if retcode != 0:
+    raise AvbError('Error signing: {}'.format(perr))
+  return bytearray(pout)
+
+
 class ImageChunk(object):
   """Data structure used for representing chunks in Android sparse files.
 
@@ -2030,25 +2066,9 @@
       binary_hash.extend(ha.digest())
 
       # Calculate the signature.
-      p = None
-      if signing_helper is not None:
-          p = subprocess.Popen(
-              [signing_helper, algorithm_name, key_path],
-              stdin=subprocess.PIPE,
-              stdout=subprocess.PIPE,
-              stderr=subprocess.PIPE)
-      else:
-          p = subprocess.Popen(
-              ['openssl', 'rsautl', '-sign', '-inkey', key_path, '-raw'],
-              stdin=subprocess.PIPE,
-              stdout=subprocess.PIPE,
-              stderr=subprocess.PIPE)
       padding_and_hash = str(bytearray(alg.padding)) + binary_hash
-      (pout, perr) = p.communicate(padding_and_hash)
-      retcode = p.wait()
-      if retcode != 0:
-        raise AvbError('Error signing: {}'.format(perr))
-      binary_signature.extend(pout)
+      binary_signature.extend(raw_sign(signing_helper, algorithm_name, key_path,
+                                       padding_and_hash))
 
     # Generate Authentication data block.
     auth_data_blob = bytearray()
@@ -2412,6 +2432,119 @@
       image.truncate(original_image_size)
       raise
 
+  def make_atx_certificate(self, output, authority_key_path, subject_key,
+                           subject_key_version, subject,
+                           is_intermediate_authority, signing_helper):
+    """Implements the 'make_atx_certificate' command.
+
+    Android Things certificates are required for Android Things public key
+    metadata. They chain the vbmeta signing key for a particular product back to
+    a fused, permanent root key. These certificates are fixed-length and fixed-
+    format with the explicit goal of not parsing ASN.1 in bootloader code.
+
+    Arguments:
+      output: Certificate will be written to this file on success.
+      authority_key_path: A PEM file path with the authority private key.
+                          If None, then a certificate will be created without a
+                          signature. The signature can be created out-of-band
+                          and appended.
+      subject_key: A PEM or DER subject public key.
+      subject_key_version: A 64-bit version value. If this is None, the number
+                           of seconds since the epoch is used.
+      subject: A subject identifier. For Product Signing Key certificates this
+               should be the same Product ID found in the permanent attributes.
+      is_intermediate_authority: True if the certificate is for an intermediate
+                                 authority.
+      signing_helper: Program which signs a hash and returns the signature.
+    """
+    signed_data = bytearray()
+    signed_data.extend(struct.pack('<I', 1))  # Format Version
+    signed_data.extend(
+        encode_rsa_key(Crypto.PublicKey.RSA.importKey(subject_key)))
+    hasher = hashlib.sha256()
+    hasher.update(subject)
+    signed_data.extend(hasher.digest())
+    usage = 'com.google.android.things.vboot'
+    if is_intermediate_authority:
+      usage += '.ca'
+    hasher = hashlib.sha256()
+    hasher.update(usage)
+    signed_data.extend(hasher.digest())
+    if not subject_key_version:
+      subject_key_version = int(time.time())
+    signed_data.extend(struct.pack('<Q', subject_key_version))
+    signature = bytearray()
+    if authority_key_path:
+      padding_and_hash = bytearray()
+      algorithm_name = None
+      hasher = None
+      if is_intermediate_authority:
+        hasher = hashlib.sha512()
+        algorithm_name = 'SHA512_RSA4096'
+      else:
+        hasher = hashlib.sha256()
+        algorithm_name = 'SHA256_RSA2048'
+      padding_and_hash.extend(ALGORITHMS[algorithm_name].padding)
+      hasher.update(signed_data)
+      padding_and_hash.extend(hasher.digest())
+      signature.extend(raw_sign(signing_helper, algorithm_name,
+                                authority_key_path, padding_and_hash))
+    output.write(signed_data)
+    output.write(signature)
+
+  def make_atx_permanent_attributes(self, output, root_authority_key,
+                                    product_id):
+    """Implements the 'make_atx_permanent_attributes' command.
+
+    Android Things permanent attributes are designed to be permanent for a
+    particular product and a hash of these attributes should be fused into
+    hardware to enforce this.
+
+    Arguments:
+      output: Attributes will be written to this file on success.
+      root_authority_key: A PEM or DER public key for the root authority.
+      product_id: A 16-byte Product ID.
+
+    Raises:
+      AvbError: If an argument is incorrect.
+    """
+    if len(product_id) != 16:
+      raise AvbError('Invalid Product ID length.')
+    output.write(struct.pack('<I', 1))  # Format Version
+    write_rsa_key(output, Crypto.PublicKey.RSA.importKey(root_authority_key))
+    output.write(product_id)
+
+  def make_atx_metadata(self, output, intermediate_key_certificate,
+                        product_key_certificate, google_key_version):
+    """Implements the 'make_atx_metadata' command.
+
+    Android Things metadata are included in vbmeta images to facilitate
+    verification. The output of this command can be used as the
+    public_key_metadata argument to other commands.
+
+    Arguments:
+      output: Metadata will be written to this file on success.
+      intermediate_key_certificate: A certificate file as output by
+                                    make_atx_certificate with
+                                    is_intermediate_authority set to true.
+      product_key_certificate: A certificate file as output by
+                               make_atx_certificate with
+                               is_intermediate_authority set to false.
+      google_key_version: The version of the Google Signing Key used in the
+                          associated vbmeta image.
+
+    Raises:
+      AvbError: If an argument is incorrect.
+    """
+    if len(intermediate_key_certificate) != 1108:
+      raise AvbError('Invalid intermediate key certificate length.')
+    if len(product_key_certificate) != 852:
+      raise AvbError('Invalid product key certificate length.')
+    output.write(struct.pack('<I', 1))  # Format Version
+    output.write(intermediate_key_certificate)
+    output.write(product_key_certificate)
+    output.write(struct.pack('<Q', google_key_version))
+
 
 def calc_hash_level_offsets(image_size, block_size, digest_size):
   """Calculate the offsets of all the hash-levels in a Merkle-tree.
@@ -2772,6 +2905,77 @@
                             default='15:7:0:14:7:0')
     sub_parser.set_defaults(func=self.set_ab_metadata)
 
+    sub_parser = subparsers.add_parser(
+        'make_atx_certificate',
+        help='Create an Android Things eXtension (ATX) certificate.')
+    sub_parser.add_argument('--output',
+                            help='Write certificate to file',
+                            type=argparse.FileType('wb'),
+                            default=sys.stdout)
+    sub_parser.add_argument('--subject',
+                            help=('Path to subject file'),
+                            type=argparse.FileType('rb'),
+                            required=True)
+    sub_parser.add_argument('--subject_key',
+                            help=('Path to subject RSA public key file'),
+                            type=argparse.FileType('rb'),
+                            required=True)
+    sub_parser.add_argument('--subject_key_version',
+                            help=('Version of the subject key'),
+                            type=parse_number,
+                            required=False)
+    sub_parser.add_argument('--subject_is_intermediate_authority',
+                            help=('Generate an intermediate authority '
+                                  'certificate'),
+                            action='store_true')
+    sub_parser.add_argument('--authority_key',
+                            help='Path to authority RSA private key file',
+                            required=False)
+    sub_parser.add_argument('--signing_helper',
+                            help='Path to helper used for signing',
+                            metavar='APP',
+                            default=None,
+                            required=False)
+    sub_parser.set_defaults(func=self.make_atx_certificate)
+
+    sub_parser = subparsers.add_parser(
+        'make_atx_permanent_attributes',
+        help='Create Android Things eXtension (ATX) permanent attributes.')
+    sub_parser.add_argument('--output',
+                            help='Write attributes to file',
+                            type=argparse.FileType('wb'),
+                            default=sys.stdout)
+    sub_parser.add_argument('--root_authority_key',
+                            help='Path to authority RSA public key file',
+                            type=argparse.FileType('rb'),
+                            required=True)
+    sub_parser.add_argument('--product_id',
+                            help=('Path to Product ID file'),
+                            type=argparse.FileType('rb'),
+                            required=True)
+    sub_parser.set_defaults(func=self.make_atx_permanent_attributes)
+
+    sub_parser = subparsers.add_parser(
+        'make_atx_metadata',
+        help='Create Android Things eXtension (ATX) metadata.')
+    sub_parser.add_argument('--output',
+                            help='Write metadata to file',
+                            type=argparse.FileType('wb'),
+                            default=sys.stdout)
+    sub_parser.add_argument('--intermediate_key_certificate',
+                            help='Path to intermediate key certificate file',
+                            type=argparse.FileType('rb'),
+                            required=True)
+    sub_parser.add_argument('--product_key_certificate',
+                            help='Path to product key certificate file',
+                            type=argparse.FileType('rb'),
+                            required=True)
+    sub_parser.add_argument('--google_key_version',
+                            help=('Version of the Google signing key'),
+                            type=parse_number,
+                            default=0)
+    sub_parser.set_defaults(func=self.make_atx_metadata)
+
     args = parser.parse_args(argv[1:])
     try:
       args.func(args)
@@ -2838,6 +3042,28 @@
     """Implements the 'info_image' sub-command."""
     self.avb.info_image(args.image.name, args.output)
 
+  def make_atx_certificate(self, args):
+    """Implements the 'make_atx_certificate' sub-command."""
+    self.avb.make_atx_certificate(args.output, args.authority_key,
+                                  args.subject_key.read(),
+                                  args.subject_key_version,
+                                  args.subject.read(),
+                                  args.subject_is_intermediate_authority,
+                                  args.signing_helper)
+
+  def make_atx_permanent_attributes(self, args):
+    """Implements the 'make_atx_permanent_attributes' sub-command."""
+    self.avb.make_atx_permanent_attributes(args.output,
+                                           args.root_authority_key.read(),
+                                           args.product_id.read())
+
+  def make_atx_metadata(self, args):
+    """Implements the 'make_atx_metadata' sub-command."""
+    self.avb.make_atx_metadata(args.output,
+                               args.intermediate_key_certificate.read(),
+                               args.product_key_certificate.read(),
+                               args.google_key_version)
+
 
 if __name__ == '__main__':
   tool = AvbTool()