19c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao#!/usr/bin/env python
29c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao#
39c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao# Copyright (C) 2016 The Android Open Source Project
49c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao#
59c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao# Licensed under the Apache License, Version 2.0 (the "License");
69c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao# you may not use this file except in compliance with the License.
79c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao# You may obtain a copy of the License at
89c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao#
99c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao#      http://www.apache.org/licenses/LICENSE-2.0
109c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao#
119c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao# Unless required by applicable law or agreed to in writing, software
129c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao# distributed under the License is distributed on an "AS IS" BASIS,
139c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
149c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao# See the License for the specific language governing permissions and
159c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao# limitations under the License.
169c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
179c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao"""
189c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao BaoVerify a given OTA package with the specifed certificate.
199c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao"""
209c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
219c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Baofrom __future__ import print_function
229c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
239c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Baoimport argparse
249c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Baoimport common
259c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Baoimport re
269c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Baoimport subprocess
279c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Baoimport sys
289c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
299c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Baofrom hashlib import sha1
309c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Baofrom hashlib import sha256
319c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
329c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
339c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Baodef cert_uses_sha256(cert):
349c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  """Check if the cert uses SHA-256 hashing algorithm."""
359c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
369c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  cmd = ['openssl', 'x509', '-text', '-noout', '-in', cert]
379c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  p1 = common.Run(cmd, stdout=subprocess.PIPE)
389c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  cert_dump, _ = p1.communicate()
399c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
409c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  algorithm = re.search(r'Signature Algorithm: ([a-zA-Z0-9]+)', cert_dump)
419c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  assert algorithm, "Failed to identify the signature algorithm."
429c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
439c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  assert not algorithm.group(1).startswith('ecdsa'), (
449c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao      'This script doesn\'t support verifying ECDSA signed package yet.')
459c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
469c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  return algorithm.group(1).startswith('sha256')
479c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
489c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
499c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Baodef verify_package(cert, package):
509c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  """Verify the given package with the certificate.
519c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
529c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  (Comments from bootable/recovery/verifier.cpp:)
539c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
549c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  An archive with a whole-file signature will end in six bytes:
559c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
569c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao    (2-byte signature start) $ff $ff (2-byte comment size)
579c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
589c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  (As far as the ZIP format is concerned, these are part of the
599c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  archive comment.) We start by reading this footer, this tells
609c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  us how far back from the end we have to start reading to find
619c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  the whole comment.
629c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  """
639c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
649c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  print('Package: %s' % (package,))
659c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  print('Certificate: %s' % (cert,))
669c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
679c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  # Read in the package.
689c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  with open(package) as package_file:
699c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao    package_bytes = package_file.read()
709c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
719c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  length = len(package_bytes)
729c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  assert length >= 6, "Not big enough to contain footer."
739c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
749c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  footer = [ord(x) for x in package_bytes[-6:]]
759c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  assert footer[2] == 0xff and footer[3] == 0xff, "Footer is wrong."
769c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
779c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  signature_start_from_end = (footer[1] << 8) + footer[0]
789c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  assert signature_start_from_end > 6, "Signature start is in the footer."
799c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
809c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  signature_start = length - signature_start_from_end
819c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
829c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  # Determine how much of the file is covered by the signature. This is
839c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  # everything except the signature data and length, which includes all of the
849c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  # EOCD except for the comment length field (2 bytes) and the comment data.
859c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  comment_len = (footer[5] << 8) + footer[4]
869c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  signed_len = length - comment_len - 2
879c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
889c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  print('Package length: %d' % (length,))
899c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  print('Comment length: %d' % (comment_len,))
909c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  print('Signed data length: %d' % (signed_len,))
919c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  print('Signature start: %d' % (signature_start,))
929c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
939c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  use_sha256 = cert_uses_sha256(cert)
949c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  print('Use SHA-256: %s' % (use_sha256,))
959c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
969c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  if use_sha256:
979c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao    h = sha256()
989c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  else:
999c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao    h = sha1()
1009c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  h.update(package_bytes[:signed_len])
1019c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  package_digest = h.hexdigest().lower()
1029c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
1039c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  print('Digest: %s\n' % (package_digest,))
1049c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
1059c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  # Get the signature from the input package.
1069c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  signature = package_bytes[signature_start:-6]
1074c851b1098577f67f20742edbc086ee045e61c47Tao Bao  sig_file = common.MakeTempFile(prefix='sig-')
1089c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  with open(sig_file, 'wb') as f:
1099c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao    f.write(signature)
1109c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
1119c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  # Parse the signature and get the hash.
1129c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  cmd = ['openssl', 'asn1parse', '-inform', 'DER', '-in', sig_file]
1139c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  p1 = common.Run(cmd, stdout=subprocess.PIPE)
1149c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  sig, _ = p1.communicate()
1159c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  assert p1.returncode == 0, "Failed to parse the signature."
1169c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
1179c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  digest_line = sig.strip().split('\n')[-1]
1189c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  digest_string = digest_line.split(':')[3]
1194c851b1098577f67f20742edbc086ee045e61c47Tao Bao  digest_file = common.MakeTempFile(prefix='digest-')
1209c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  with open(digest_file, 'wb') as f:
1219c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao    f.write(digest_string.decode('hex'))
1229c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
1239c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  # Verify the digest by outputing the decrypted result in ASN.1 structure.
1244c851b1098577f67f20742edbc086ee045e61c47Tao Bao  decrypted_file = common.MakeTempFile(prefix='decrypted-')
1259c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  cmd = ['openssl', 'rsautl', '-verify', '-certin', '-inkey', cert,
1269c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao         '-in', digest_file, '-out', decrypted_file]
1279c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  p1 = common.Run(cmd, stdout=subprocess.PIPE)
1289c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  p1.communicate()
1299c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  assert p1.returncode == 0, "Failed to run openssl rsautl -verify."
1309c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
1319c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  # Parse the output ASN.1 structure.
1329c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  cmd = ['openssl', 'asn1parse', '-inform', 'DER', '-in', decrypted_file]
1339c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  p1 = common.Run(cmd, stdout=subprocess.PIPE)
1349c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  decrypted_output, _ = p1.communicate()
1359c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  assert p1.returncode == 0, "Failed to parse the output."
1369c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
1379c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  digest_line = decrypted_output.strip().split('\n')[-1]
1389c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  digest_string = digest_line.split(':')[3].lower()
1399c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
1409c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  # Verify that the two digest strings match.
1419c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  assert package_digest == digest_string, "Verification failed."
1429c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
1439c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  # Verified successfully upon reaching here.
1449c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  print('VERIFIED\n')
1459c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
1469c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
1479c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Baodef main():
1489c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  parser = argparse.ArgumentParser()
1499c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  parser.add_argument('certificate', help='The certificate to be used.')
1509c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  parser.add_argument('package', help='The OTA package to be verified.')
1519c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  args = parser.parse_args()
1529c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
1539c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  verify_package(args.certificate, args.package)
1549c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
1559c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao
1569c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Baoif __name__ == '__main__':
1579c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  try:
1589c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao    main()
1599c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao  except AssertionError as err:
1609c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao    print('\n    ERROR: %s\n' % (err,))
1619c63fb59bdc51b8ec1e2e55014e53b29e0c3abe1Tao Bao    sys.exit(1)
162