Merge "Use soong_java_prebuilt.mk to export soong jars to java"
diff --git a/tools/checkowners.py b/tools/checkowners.py
index b874955..1190d30 100755
--- a/tools/checkowners.py
+++ b/tools/checkowners.py
@@ -34,8 +34,8 @@
                + urllib.quote(address))
     echo('Checking email address: ' + address)
     result = urllib2.urlopen(request).read()
-    expected = '"email": "' + address + '"'
-    checked_addresses[address] = (result.find(expected) >= 0)
+    checked_addresses[address] = (
+        result.find('"email":') >= 0 and result.find('"_account_id":') >= 0)
   return checked_addresses[address]
 
 
diff --git a/tools/releasetools/check_ota_package_signature.py b/tools/releasetools/check_ota_package_signature.py
index 548b619..1f8b7bb 100755
--- a/tools/releasetools/check_ota_package_signature.py
+++ b/tools/releasetools/check_ota_package_signature.py
@@ -25,12 +25,19 @@
 import re
 import subprocess
 import sys
+import tempfile
+import zipfile
 
 from hashlib import sha1
 from hashlib import sha256
 
+# 'update_payload' package is under 'system/update_engine/scripts/', which
+# should to be included in PYTHONPATH.
+from update_payload.payload import Payload
+from update_payload.update_metadata_pb2 import Signatures
 
-def cert_uses_sha256(cert):
+
+def CertUsesSha256(cert):
   """Check if the cert uses SHA-256 hashing algorithm."""
 
   cmd = ['openssl', 'x509', '-text', '-noout', '-in', cert]
@@ -46,7 +53,7 @@
   return algorithm.group(1).startswith('sha256')
 
 
-def verify_package(cert, package):
+def VerifyPackage(cert, package):
   """Verify the given package with the certificate.
 
   (Comments from bootable/recovery/verifier.cpp:)
@@ -90,7 +97,7 @@
   print('Signed data length: %d' % (signed_len,))
   print('Signature start: %d' % (signature_start,))
 
-  use_sha256 = cert_uses_sha256(cert)
+  use_sha256 = CertUsesSha256(cert)
   print('Use SHA-256: %s' % (use_sha256,))
 
   if use_sha256:
@@ -100,7 +107,7 @@
   h.update(package_bytes[:signed_len])
   package_digest = h.hexdigest().lower()
 
-  print('Digest: %s\n' % (package_digest,))
+  print('Digest: %s' % (package_digest,))
 
   # Get the signature from the input package.
   signature = package_bytes[signature_start:-6]
@@ -141,7 +148,87 @@
   assert package_digest == digest_string, "Verification failed."
 
   # Verified successfully upon reaching here.
-  print('VERIFIED\n')
+  print('\nWhole package signature VERIFIED\n')
+
+
+def VerifyAbOtaPayload(cert, package):
+  """Verifies the payload and metadata signatures in an A/B OTA payload."""
+
+  def VerifySignatureBlob(hash_file, blob):
+    """Verifies the input hash_file against the signature blob."""
+    signatures = Signatures()
+    signatures.ParseFromString(blob)
+
+    extracted_sig_file = common.MakeTempFile(
+        prefix='extracted-sig-', suffix='.bin')
+    # In Android, we only expect one signature.
+    assert len(signatures.signatures) == 1, \
+        'Invalid number of signatures: %d' % len(signatures.signatures)
+    signature = signatures.signatures[0]
+    length = len(signature.data)
+    assert length == 256, 'Invalid signature length %d' % (length,)
+    with open(extracted_sig_file, 'w') as f:
+      f.write(signature.data)
+
+    # Verify the signature file extracted from the payload, by reversing the
+    # signing operation. Alternatively, this can be done by calling 'openssl
+    # rsautl -verify -certin -inkey <cert.pem> -in <extracted_sig_file> -out
+    # <output>', then to assert that
+    # <output> == SHA-256 DigestInfo prefix || <hash_file>.
+    cmd = ['openssl', 'pkeyutl', '-verify', '-certin', '-inkey', cert,
+           '-pkeyopt', 'digest:sha256', '-in', hash_file,
+           '-sigfile', extracted_sig_file]
+    p = common.Run(cmd, stdout=subprocess.PIPE)
+    result, _ = p.communicate()
+
+    # https://github.com/openssl/openssl/pull/3213
+    # 'openssl pkeyutl -verify' (prior to 1.1.0) returns non-zero return code,
+    # even on successful verification. To avoid the false alarm with older
+    # openssl, check the output directly.
+    assert result.strip() == 'Signature Verified Successfully', result.strip()
+
+  package_zip = zipfile.ZipFile(package, 'r')
+  if 'payload.bin' not in package_zip.namelist():
+    common.ZipClose(package_zip)
+    return
+
+  print('Verifying A/B OTA payload signatures...')
+
+  package_dir = tempfile.mkdtemp(prefix='package-')
+  common.OPTIONS.tempfiles.append(package_dir)
+
+  payload_file = package_zip.extract('payload.bin', package_dir)
+  payload = Payload(open(payload_file, 'rb'))
+  payload.Init()
+
+  # Extract the payload hash and metadata hash from the payload.bin.
+  payload_hash_file = common.MakeTempFile(prefix='hash-', suffix='.bin')
+  metadata_hash_file = common.MakeTempFile(prefix='hash-', suffix='.bin')
+  cmd = ['brillo_update_payload', 'hash',
+         '--unsigned_payload', payload_file,
+         '--signature_size', '256',
+         '--metadata_hash_file', metadata_hash_file,
+         '--payload_hash_file', payload_hash_file]
+  p = common.Run(cmd, stdout=subprocess.PIPE)
+  p.communicate()
+  assert p.returncode == 0, 'brillo_update_payload hash failed'
+
+  # Payload signature verification.
+  assert payload.manifest.HasField('signatures_offset')
+  payload_signature = payload.ReadDataBlob(
+      payload.manifest.signatures_offset, payload.manifest.signatures_size)
+  VerifySignatureBlob(payload_hash_file, payload_signature)
+
+  # Metadata signature verification.
+  metadata_signature = payload.ReadDataBlob(
+      -payload.header.metadata_signature_len,
+      payload.header.metadata_signature_len)
+  VerifySignatureBlob(metadata_hash_file, metadata_signature)
+
+  common.ZipClose(package_zip)
+
+  # Verified successfully upon reaching here.
+  print('\nPayload signatures VERIFIED\n\n')
 
 
 def main():
@@ -150,7 +237,8 @@
   parser.add_argument('package', help='The OTA package to be verified.')
   args = parser.parse_args()
 
-  verify_package(args.certificate, args.package)
+  VerifyPackage(args.certificate, args.package)
+  VerifyAbOtaPayload(args.certificate, args.package)
 
 
 if __name__ == '__main__':
@@ -159,3 +247,5 @@
   except AssertionError as err:
     print('\n    ERROR: %s\n' % (err,))
     sys.exit(1)
+  finally:
+    common.Cleanup()
diff --git a/tools/releasetools/validate_target_files.py b/tools/releasetools/validate_target_files.py
index 8ac3322..4b34820 100755
--- a/tools/releasetools/validate_target_files.py
+++ b/tools/releasetools/validate_target_files.py
@@ -44,8 +44,8 @@
   return sparse_img.SparseImage(path, mappath, clobbered_blocks)
 
 
-def _CalculateFileSha1(file_name, unpacked_name, round_up=False):
-  """Calculate the SHA-1 for a given file. Round up its size to 4K if needed."""
+def _ReadFile(file_name, unpacked_name, round_up=False):
+  """Constructs and returns a File object. Rounds up its size if needed."""
 
   def RoundUpTo4K(value):
     rounded_up = value + 4095
@@ -58,7 +58,7 @@
   if round_up:
     file_size_rounded_up = RoundUpTo4K(file_size)
     file_data += '\0' * (file_size_rounded_up - file_size)
-  return common.File(file_name, file_data).sha1
+  return common.File(file_name, file_data)
 
 
 def ValidateFileAgainstSha1(input_tmp, file_name, file_path, expected_sha1):
@@ -67,7 +67,7 @@
   logging.info('Validating the SHA-1 of {}'.format(file_name))
   unpacked_name = os.path.join(input_tmp, file_path)
   assert os.path.exists(unpacked_name)
-  actual_sha1 = _CalculateFileSha1(file_name, unpacked_name, False)
+  actual_sha1 = _ReadFile(file_name, unpacked_name, False).sha1
   assert actual_sha1 == expected_sha1, \
       'SHA-1 mismatches for {}. actual {}, expected {}'.format(
       file_name, actual_sha1, expected_sha1)
@@ -92,8 +92,20 @@
       # The filename under unpacked directory, such as SYSTEM/bin/sh.
       unpacked_name = os.path.join(
           input_tmp, which.upper(), entry[(len(prefix) + 1):])
-      file_sha1 = _CalculateFileSha1(entry, unpacked_name, True)
+      unpacked_file = _ReadFile(entry, unpacked_name, True)
+      file_size = unpacked_file.size
 
+      # block.map may contain less blocks, because mke2fs may skip allocating
+      # blocks if they contain all zeros. We can't reconstruct such a file from
+      # its block list. (Bug: 65213616)
+      if file_size > ranges.size() * 4096:
+        logging.warning(
+            'Skipping %s that has less blocks: file size %d-byte,'
+            ' ranges %s (%d-byte)', entry, file_size, ranges,
+            ranges.size() * 4096)
+        continue
+
+      file_sha1 = unpacked_file.sha1
       assert blocks_sha1 == file_sha1, \
           'file: %s, range: %s, blocks_sha1: %s, file_sha1: %s' % (
               entry, ranges, blocks_sha1, file_sha1)