Home | History | Annotate | Download | only in releasetools
      1 #!/usr/bin/env python
      2 #
      3 # Copyright (C) 2016 The Android Open Source Project
      4 #
      5 # Licensed under the Apache License, Version 2.0 (the "License");
      6 # you may not use this file except in compliance with the License.
      7 # You may obtain a copy of the License at
      8 #
      9 #      http://www.apache.org/licenses/LICENSE-2.0
     10 #
     11 # Unless required by applicable law or agreed to in writing, software
     12 # distributed under the License is distributed on an "AS IS" BASIS,
     13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14 # See the License for the specific language governing permissions and
     15 # limitations under the License.
     16 
     17 """
     18 Verify a given OTA package with the specifed certificate.
     19 """
     20 
     21 from __future__ import print_function
     22 
     23 import argparse
     24 import common
     25 import re
     26 import subprocess
     27 import sys
     28 
     29 from hashlib import sha1
     30 from hashlib import sha256
     31 
     32 
     33 def cert_uses_sha256(cert):
     34   """Check if the cert uses SHA-256 hashing algorithm."""
     35 
     36   cmd = ['openssl', 'x509', '-text', '-noout', '-in', cert]
     37   p1 = common.Run(cmd, stdout=subprocess.PIPE)
     38   cert_dump, _ = p1.communicate()
     39 
     40   algorithm = re.search(r'Signature Algorithm: ([a-zA-Z0-9]+)', cert_dump)
     41   assert algorithm, "Failed to identify the signature algorithm."
     42 
     43   assert not algorithm.group(1).startswith('ecdsa'), (
     44       'This script doesn\'t support verifying ECDSA signed package yet.')
     45 
     46   return algorithm.group(1).startswith('sha256')
     47 
     48 
     49 def verify_package(cert, package):
     50   """Verify the given package with the certificate.
     51 
     52   (Comments from bootable/recovery/verifier.cpp:)
     53 
     54   An archive with a whole-file signature will end in six bytes:
     55 
     56     (2-byte signature start) $ff $ff (2-byte comment size)
     57 
     58   (As far as the ZIP format is concerned, these are part of the
     59   archive comment.) We start by reading this footer, this tells
     60   us how far back from the end we have to start reading to find
     61   the whole comment.
     62   """
     63 
     64   print('Package: %s' % (package,))
     65   print('Certificate: %s' % (cert,))
     66 
     67   # Read in the package.
     68   with open(package) as package_file:
     69     package_bytes = package_file.read()
     70 
     71   length = len(package_bytes)
     72   assert length >= 6, "Not big enough to contain footer."
     73 
     74   footer = [ord(x) for x in package_bytes[-6:]]
     75   assert footer[2] == 0xff and footer[3] == 0xff, "Footer is wrong."
     76 
     77   signature_start_from_end = (footer[1] << 8) + footer[0]
     78   assert signature_start_from_end > 6, "Signature start is in the footer."
     79 
     80   signature_start = length - signature_start_from_end
     81 
     82   # Determine how much of the file is covered by the signature. This is
     83   # everything except the signature data and length, which includes all of the
     84   # EOCD except for the comment length field (2 bytes) and the comment data.
     85   comment_len = (footer[5] << 8) + footer[4]
     86   signed_len = length - comment_len - 2
     87 
     88   print('Package length: %d' % (length,))
     89   print('Comment length: %d' % (comment_len,))
     90   print('Signed data length: %d' % (signed_len,))
     91   print('Signature start: %d' % (signature_start,))
     92 
     93   use_sha256 = cert_uses_sha256(cert)
     94   print('Use SHA-256: %s' % (use_sha256,))
     95 
     96   if use_sha256:
     97     h = sha256()
     98   else:
     99     h = sha1()
    100   h.update(package_bytes[:signed_len])
    101   package_digest = h.hexdigest().lower()
    102 
    103   print('Digest: %s\n' % (package_digest,))
    104 
    105   # Get the signature from the input package.
    106   signature = package_bytes[signature_start:-6]
    107   sig_file = common.MakeTempFile(prefix='sig-')
    108   with open(sig_file, 'wb') as f:
    109     f.write(signature)
    110 
    111   # Parse the signature and get the hash.
    112   cmd = ['openssl', 'asn1parse', '-inform', 'DER', '-in', sig_file]
    113   p1 = common.Run(cmd, stdout=subprocess.PIPE)
    114   sig, _ = p1.communicate()
    115   assert p1.returncode == 0, "Failed to parse the signature."
    116 
    117   digest_line = sig.strip().split('\n')[-1]
    118   digest_string = digest_line.split(':')[3]
    119   digest_file = common.MakeTempFile(prefix='digest-')
    120   with open(digest_file, 'wb') as f:
    121     f.write(digest_string.decode('hex'))
    122 
    123   # Verify the digest by outputing the decrypted result in ASN.1 structure.
    124   decrypted_file = common.MakeTempFile(prefix='decrypted-')
    125   cmd = ['openssl', 'rsautl', '-verify', '-certin', '-inkey', cert,
    126          '-in', digest_file, '-out', decrypted_file]
    127   p1 = common.Run(cmd, stdout=subprocess.PIPE)
    128   p1.communicate()
    129   assert p1.returncode == 0, "Failed to run openssl rsautl -verify."
    130 
    131   # Parse the output ASN.1 structure.
    132   cmd = ['openssl', 'asn1parse', '-inform', 'DER', '-in', decrypted_file]
    133   p1 = common.Run(cmd, stdout=subprocess.PIPE)
    134   decrypted_output, _ = p1.communicate()
    135   assert p1.returncode == 0, "Failed to parse the output."
    136 
    137   digest_line = decrypted_output.strip().split('\n')[-1]
    138   digest_string = digest_line.split(':')[3].lower()
    139 
    140   # Verify that the two digest strings match.
    141   assert package_digest == digest_string, "Verification failed."
    142 
    143   # Verified successfully upon reaching here.
    144   print('VERIFIED\n')
    145 
    146 
    147 def main():
    148   parser = argparse.ArgumentParser()
    149   parser.add_argument('certificate', help='The certificate to be used.')
    150   parser.add_argument('package', help='The OTA package to be verified.')
    151   args = parser.parse_args()
    152 
    153   verify_package(args.certificate, args.package)
    154 
    155 
    156 if __name__ == '__main__':
    157   try:
    158     main()
    159   except AssertionError as err:
    160     print('\n    ERROR: %s\n' % (err,))
    161     sys.exit(1)
    162