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