blob: 97957be28f2b45f42c17dc3bdb08e53e1224ca6d [file] [log] [blame]
Tao Bao9c63fb52016-09-13 11:13:48 -07001#!/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"""
18Verify a given OTA package with the specifed certificate.
19"""
20
21from __future__ import print_function
22
23import argparse
Tao Bao32fcdab2018-10-12 10:30:39 -070024import logging
Tao Bao9c63fb52016-09-13 11:13:48 -070025import re
26import subprocess
27import sys
Tao Baoa198b1e2017-08-31 16:52:55 -070028import zipfile
Tao Bao9c63fb52016-09-13 11:13:48 -070029from hashlib import sha1
30from hashlib import sha256
31
Tao Bao750385e2017-12-15 12:21:44 -080032import common
Tao Bao9c63fb52016-09-13 11:13:48 -070033
Tao Bao32fcdab2018-10-12 10:30:39 -070034logger = logging.getLogger(__name__)
35
Tao Baoa198b1e2017-08-31 16:52:55 -070036
37def CertUsesSha256(cert):
Tao Bao9c63fb52016-09-13 11:13:48 -070038 """Check if the cert uses SHA-256 hashing algorithm."""
39
40 cmd = ['openssl', 'x509', '-text', '-noout', '-in', cert]
Tao Bao59cf0c52019-06-25 10:04:24 -070041 cert_dump = common.RunAndCheckOutput(cmd, stdout=subprocess.PIPE)
Tao Bao9c63fb52016-09-13 11:13:48 -070042
43 algorithm = re.search(r'Signature Algorithm: ([a-zA-Z0-9]+)', cert_dump)
44 assert algorithm, "Failed to identify the signature algorithm."
45
46 assert not algorithm.group(1).startswith('ecdsa'), (
47 'This script doesn\'t support verifying ECDSA signed package yet.')
48
49 return algorithm.group(1).startswith('sha256')
50
51
Tao Baoa198b1e2017-08-31 16:52:55 -070052def VerifyPackage(cert, package):
Tao Bao9c63fb52016-09-13 11:13:48 -070053 """Verify the given package with the certificate.
54
55 (Comments from bootable/recovery/verifier.cpp:)
56
57 An archive with a whole-file signature will end in six bytes:
58
59 (2-byte signature start) $ff $ff (2-byte comment size)
60
61 (As far as the ZIP format is concerned, these are part of the
62 archive comment.) We start by reading this footer, this tells
63 us how far back from the end we have to start reading to find
64 the whole comment.
65 """
66
67 print('Package: %s' % (package,))
68 print('Certificate: %s' % (cert,))
69
70 # Read in the package.
Tao Bao59cf0c52019-06-25 10:04:24 -070071 with open(package, 'rb') as package_file:
Tao Bao9c63fb52016-09-13 11:13:48 -070072 package_bytes = package_file.read()
73
74 length = len(package_bytes)
75 assert length >= 6, "Not big enough to contain footer."
76
Tao Bao59cf0c52019-06-25 10:04:24 -070077 footer = bytearray(package_bytes[-6:])
Tao Bao9c63fb52016-09-13 11:13:48 -070078 assert footer[2] == 0xff and footer[3] == 0xff, "Footer is wrong."
79
80 signature_start_from_end = (footer[1] << 8) + footer[0]
81 assert signature_start_from_end > 6, "Signature start is in the footer."
82
83 signature_start = length - signature_start_from_end
84
85 # Determine how much of the file is covered by the signature. This is
86 # everything except the signature data and length, which includes all of the
87 # EOCD except for the comment length field (2 bytes) and the comment data.
88 comment_len = (footer[5] << 8) + footer[4]
89 signed_len = length - comment_len - 2
90
91 print('Package length: %d' % (length,))
92 print('Comment length: %d' % (comment_len,))
93 print('Signed data length: %d' % (signed_len,))
94 print('Signature start: %d' % (signature_start,))
95
Tao Baoa198b1e2017-08-31 16:52:55 -070096 use_sha256 = CertUsesSha256(cert)
Tao Bao9c63fb52016-09-13 11:13:48 -070097 print('Use SHA-256: %s' % (use_sha256,))
98
Tao Bao750385e2017-12-15 12:21:44 -080099 h = sha256() if use_sha256 else sha1()
Tao Bao9c63fb52016-09-13 11:13:48 -0700100 h.update(package_bytes[:signed_len])
101 package_digest = h.hexdigest().lower()
102
Tao Baoa198b1e2017-08-31 16:52:55 -0700103 print('Digest: %s' % (package_digest,))
Tao Bao9c63fb52016-09-13 11:13:48 -0700104
105 # Get the signature from the input package.
106 signature = package_bytes[signature_start:-6]
Tao Bao4c851b12016-09-19 13:54:38 -0700107 sig_file = common.MakeTempFile(prefix='sig-')
Tao Bao9c63fb52016-09-13 11:13:48 -0700108 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]
Tao Bao59cf0c52019-06-25 10:04:24 -0700113 sig = common.RunAndCheckOutput(cmd, stdout=subprocess.PIPE)
Tao Bao9c63fb52016-09-13 11:13:48 -0700114
Tao Bao59cf0c52019-06-25 10:04:24 -0700115 digest_line = sig.rstrip().split('\n')[-1]
Tao Bao9c63fb52016-09-13 11:13:48 -0700116 digest_string = digest_line.split(':')[3]
Tao Bao4c851b12016-09-19 13:54:38 -0700117 digest_file = common.MakeTempFile(prefix='digest-')
Tao Bao9c63fb52016-09-13 11:13:48 -0700118 with open(digest_file, 'wb') as f:
Tao Bao59cf0c52019-06-25 10:04:24 -0700119 f.write(bytearray.fromhex(digest_string))
Tao Bao9c63fb52016-09-13 11:13:48 -0700120
121 # Verify the digest by outputing the decrypted result in ASN.1 structure.
Tao Bao4c851b12016-09-19 13:54:38 -0700122 decrypted_file = common.MakeTempFile(prefix='decrypted-')
Tao Bao9c63fb52016-09-13 11:13:48 -0700123 cmd = ['openssl', 'rsautl', '-verify', '-certin', '-inkey', cert,
124 '-in', digest_file, '-out', decrypted_file]
Tao Bao59cf0c52019-06-25 10:04:24 -0700125 common.RunAndCheckOutput(cmd, stdout=subprocess.PIPE)
Tao Bao9c63fb52016-09-13 11:13:48 -0700126
127 # Parse the output ASN.1 structure.
128 cmd = ['openssl', 'asn1parse', '-inform', 'DER', '-in', decrypted_file]
Tao Bao59cf0c52019-06-25 10:04:24 -0700129 decrypted_output = common.RunAndCheckOutput(cmd, stdout=subprocess.PIPE)
Tao Bao9c63fb52016-09-13 11:13:48 -0700130
Tao Bao59cf0c52019-06-25 10:04:24 -0700131 digest_line = decrypted_output.rstrip().split('\n')[-1]
Tao Bao9c63fb52016-09-13 11:13:48 -0700132 digest_string = digest_line.split(':')[3].lower()
133
134 # Verify that the two digest strings match.
135 assert package_digest == digest_string, "Verification failed."
136
137 # Verified successfully upon reaching here.
Tao Baoa198b1e2017-08-31 16:52:55 -0700138 print('\nWhole package signature VERIFIED\n')
139
140
141def VerifyAbOtaPayload(cert, package):
142 """Verifies the payload and metadata signatures in an A/B OTA payload."""
Kelvin Zhang928c2342020-09-22 16:15:57 -0400143 package_zip = zipfile.ZipFile(package, 'r', allowZip64=True)
Tao Baoa198b1e2017-08-31 16:52:55 -0700144 if 'payload.bin' not in package_zip.namelist():
Kelvin Zhang37a42902022-10-26 12:49:03 -0700145 package_zip.close()
Tao Baoa198b1e2017-08-31 16:52:55 -0700146 return
147
148 print('Verifying A/B OTA payload signatures...')
149
Tao Bao750385e2017-12-15 12:21:44 -0800150 # Dump pubkey from the certificate.
Tao Bao04e1f012018-02-04 12:13:35 -0800151 pubkey = common.MakeTempFile(prefix="key-", suffix=".pem")
Tao Bao59cf0c52019-06-25 10:04:24 -0700152 with open(pubkey, 'w') as pubkey_fp:
Tao Bao04e1f012018-02-04 12:13:35 -0800153 pubkey_fp.write(common.ExtractPublicKey(cert))
Tao Bao750385e2017-12-15 12:21:44 -0800154
Tao Bao04e1f012018-02-04 12:13:35 -0800155 package_dir = common.MakeTempDir(prefix='package-')
Tao Baoa198b1e2017-08-31 16:52:55 -0700156
Tao Bao750385e2017-12-15 12:21:44 -0800157 # Signature verification with delta_generator.
Tao Baoa198b1e2017-08-31 16:52:55 -0700158 payload_file = package_zip.extract('payload.bin', package_dir)
Tao Bao750385e2017-12-15 12:21:44 -0800159 cmd = ['delta_generator',
160 '--in_file=' + payload_file,
161 '--public_key=' + pubkey]
Tao Bao59cf0c52019-06-25 10:04:24 -0700162 common.RunAndCheckOutput(cmd)
Kelvin Zhang37a42902022-10-26 12:49:03 -0700163 package_zip.close()
Tao Baoa198b1e2017-08-31 16:52:55 -0700164
165 # Verified successfully upon reaching here.
166 print('\nPayload signatures VERIFIED\n\n')
Tao Bao9c63fb52016-09-13 11:13:48 -0700167
168
169def main():
170 parser = argparse.ArgumentParser()
171 parser.add_argument('certificate', help='The certificate to be used.')
172 parser.add_argument('package', help='The OTA package to be verified.')
173 args = parser.parse_args()
174
Tao Bao32fcdab2018-10-12 10:30:39 -0700175 common.InitLogging()
176
Tao Baoa198b1e2017-08-31 16:52:55 -0700177 VerifyPackage(args.certificate, args.package)
178 VerifyAbOtaPayload(args.certificate, args.package)
Tao Bao9c63fb52016-09-13 11:13:48 -0700179
180
181if __name__ == '__main__':
182 try:
183 main()
Tao Baoa198b1e2017-08-31 16:52:55 -0700184 finally:
185 common.Cleanup()